11from __future__ import annotations
22
3- __all__ = ["RasrFsaBuilder " , "WeightedFsa " , "RasrFsaBuilderV2" , "WeightedFsaV2 " ]
3+ __all__ = ["WeightedFsa " , "WeightedFsaV2 " , "RasrFsaBuilder" , " RasrFsaBuilderV2" , "RasrFsaBuilderByOrthography " ]
44
5+ from abc import ABC , abstractmethod
56from functools import reduce
6- from typing import Iterable , NamedTuple , Tuple , Union
7+ from typing import TYPE_CHECKING , Any , Iterable , List , NamedTuple , Tuple , Union
78
89import numpy as np
910import torch
1011
1112
13+ if TYPE_CHECKING :
14+ import librasr
15+
16+
17+ FsaTuple = Tuple [int , int , np .ndarray , np .ndarray ]
18+ """
19+ FSA as a tuple containing
20+ * number of states S
21+ * number of edges E
22+ * integer edge array of shape [E, 3] where each row is an edge
23+ consisting of from-state, to-state and the emission idx
24+ * float weight array of shape [E,]
25+
26+ This format is how RASR outputs FSAs when retrieving an FSA by orthography/sequence tag:
27+ https://github.com/rwth-i6/rasr/blob/2bf347fb70f1298950a4adbda39197242f78a619/src/Python/AllophoneStateFsaBuilder.cc#L60
28+ """
29+
30+
1231class WeightedFsa (NamedTuple ):
1332 """
1433 Convenience class that represents an FSA. It supports scaling the weights of the
@@ -87,44 +106,143 @@ def to(self, device: Union[str, torch.device]) -> WeightedFsaV2:
87106 )
88107
89108
90- class RasrFsaBuilder :
109+ AppendedFsa = Tuple [List [int ], List [int ], torch .Tensor , torch .Tensor ]
110+ """Data structure used for FSA appending (see function below)."""
111+
112+
113+ def _append_fsa (original_fsa : AppendedFsa , fsa_to_append : FsaTuple ) -> AppendedFsa :
91114 """
92- Builder class that wraps around the librasr.AllophoneStateFsaBuilder,
93- bringing the FSAs into the correct format for the `i6_native_ops.fbw.fbw_loss`.
115+ Appends an FSA :paramref:`fsa_to_append` at the end of another FSA :paramref:`original_fsa`.
116+
117+ :param original_fsa: Original FSA.
118+ :param fsa_to_append: FSA to concatenate to :paramref:`original_fsa`.
119+ :return: FSA with the number of states/edges, the edges, and the weights of :paramref:`fsa_to_append`
120+ appended at the end of :paramref:`original_fsa`.
121+ """
122+ edges = torch .from_numpy (np .int32 (fsa_to_append [2 ])).reshape ((3 , fsa_to_append [1 ]))
123+ return (
124+ original_fsa [0 ] + [fsa_to_append [0 ]], # num states
125+ original_fsa [1 ] + [fsa_to_append [1 ]], # num edges
126+ torch .hstack ([original_fsa [2 ], edges ]), # edges
127+ torch .cat ([original_fsa [3 ], torch .from_numpy (fsa_to_append [3 ])]), # weights
128+ )
129+
130+
131+ class _AbstractRasrFsaBuilder (ABC ):
132+ """
133+ Builder class that wraps around the `librasr.AllophoneStateFsaBuilder` class.
134+ Creates a single FSA, and joins a batch of FSAs with the correct format for the corresponding `i6_native_ops` loss.
135+
94136 Use of this class requires a working installation of the python package `librasr`.
95- Hence, the package is locally imported in case other classes are accessed from
96- this module.
97- This class provides an explicit implementation of the `__getstate__` and `__setstate__`
98- functions, necessary for pickling as the C++-class `librasr.AllophoneStateFsaBuilder`
99- is not picklable.
137+ Hence, the package is locally imported in case other classes are accessed from this module.
100138
101- :param config_path: path to the RASR fsa exporter config
102- :param tdp_scale: multiply the weights by this scale
139+ This class provides an explicit implementation of the `__getstate__` and `__setstate__` functions.
140+ This is necessary for pickling as the C++ class `librasr.AllophoneStateFsaBuilder` is not picklable.
103141 """
104142
105143 def __init__ (self , config_path : str , tdp_scale : float = 1.0 ):
144+ """
145+ :param config_path: Path to the RASR FSA exporter config. The FSA builder will be created from here.
146+ :param tdp_scale: Transition scale to be applied to the weights of the FSA.
147+ """
148+ self .config_path = config_path
149+ self .builder = self .get_builder (config_path = self .config_path )
150+ self .tdp_scale = tdp_scale
151+
152+ def get_builder (self , config_path : str ) -> librasr .AllophoneStateFsaBuilder :
106153 import librasr
107154
108- self .config_path = config_path
109155 config = librasr .Configuration ()
110- config .set_from_file (self .config_path )
111- self .builder = librasr .AllophoneStateFsaBuilder (config )
112- self .tdp_scale = tdp_scale
156+ config .set_from_file (config_path )
157+ return librasr .AllophoneStateFsaBuilder (config )
113158
114159 def __getstate__ (self ):
115160 state = self .__dict__ .copy ()
116161 del state ["builder" ]
117162 return state
118163
119164 def __setstate__ (self , state ):
120- import librasr
121-
122165 self .__dict__ .update (state )
123- config = librasr .Configuration ()
124- config .set_from_file (self .config_path )
125- self .builder = librasr .AllophoneStateFsaBuilder (config )
166+ self .builder = self .get_builder (config_path = self .config_path )
167+
168+ def apply_tdp_scale_to_fsa_tuple (self , fsa : FsaTuple , tdp_scale : float ) -> FsaTuple :
169+ """
170+ Scales the weights of an FSA represented as a tuple by the factor (TDP scale) provided.
171+
172+ :param fsa: FSA as a tuple containing
173+ * number of states S
174+ * number of edges E
175+ * integer edge array of shape [E, 3] where each row is an edge
176+ consisting of from-state, to-state and the emission idx
177+ * float weight array of shape [E,]
178+ :param tdp_scale: TDP scale by which the weights must be multiplied.
179+ :return: FSA with scaled weights corresponding to :paramref:`tdp_scale`.
180+ """
181+ if tdp_scale == 1.0 :
182+ # No scaling.
183+ return fsa
184+ else :
185+ return (fsa [0 ], fsa [1 ], fsa [2 ], fsa [3 ] * tdp_scale )
186+
187+ @abstractmethod
188+ def build_single (self , single_identifier : Any ) -> FsaTuple :
189+ """
190+ Builds a single FSA by calling the respective builder function.
191+ The specific implementation depends on the type of FSA builder that is being created.
192+
193+ Note: it's recommended that the TDP scale is applied here.
194+ For that, :funcref:`apply_tdp_scale_to_fsa_tuple` can be called.
195+
196+ :param single_identifier: Identifier of the sequence for which an FSA must be built.
197+ :return: FSA as a tuple corresponding to the sequence identified by :paramref:`single_identifier`.
198+ The returned value contains the following fields in order:
199+ * number of states S
200+ * number of edges E
201+ * integer edge array of shape [E, 3] where each row is an edge
202+ consisting of from-state, to-state and the emission idx
203+ * float weight array of shape [E,]
204+ """
205+ ...
126206
127- def build_single (self , seq_tag : str ) -> Tuple [int , int , np .ndarray , np .ndarray ]:
207+ @abstractmethod
208+ def build_batched_fsa (self , fsas : Iterable [FsaTuple ]) -> Union [WeightedFsa , WeightedFsaV2 ]:
209+ """
210+ Creates the final FSA to be used by the corresponding `fbw` op from `i6_native_ops`.
211+
212+ :param fsas: Sequence of FSAs to be batched together.
213+ :return: Single FSA which bundles together all FSAs provided as parameter.
214+ The final object is compatible with the corresponding `fbw` op from `i6_native_ops`.
215+ """
216+ ...
217+
218+ def build_batch (self , multiple_identifiers : Iterable [Any ]) -> Union [WeightedFsa , WeightedFsaV2 ]:
219+ """
220+ Build and concatenate the FSAs for a batch of data.
221+
222+ :funcref:`build_single` is called once for each item in :paramref:`multiple_identifiers`
223+ in order to obtain the individual FSAs.
224+
225+ :param multiple_identifiers: Multiple elements for which the builder should create the FSAs.
226+ In order to build each individual FSA,
227+ :funcref:`build_single` should be called once for each item in :paramref:`multiple_identifiers`.
228+ :return: Single FSA which joins all other FSAs retrieved.
229+ """
230+
231+ fsas : Iterable [FsaTuple ] = map (self .build_single , multiple_identifiers )
232+
233+ return self .build_batched_fsa (fsas )
234+
235+
236+ class RasrFsaBuilder (_AbstractRasrFsaBuilder ):
237+ """
238+ Builder class that wraps around the librasr.AllophoneStateFsaBuilder,
239+ bringing the FSAs into the correct format for the `i6_native_ops.fbw.fbw_loss`.
240+
241+ :param config_path: path to the RASR fsa exporter config
242+ :param tdp_scale: multiply the weights by this scale
243+ """
244+
245+ def build_single (self , seq_tag : str ) -> FsaTuple :
128246 """
129247 Build the FSA for the given sequence tag in the corpus.
130248
@@ -139,7 +257,7 @@ def build_single(self, seq_tag: str) -> Tuple[int, int, np.ndarray, np.ndarray]:
139257 raw_fsa = self .builder .build_by_segment_name (seq_tag )
140258 return raw_fsa
141259
142- def build_batch (self , seq_tags : Iterable [str ]) -> WeightedFsa :
260+ def build_batched_fsa (self , fsas : Iterable [FsaTuple ]) -> WeightedFsa :
143261 """
144262 Build and concatenate the FSAs for a batch of sequence tags
145263 and reformat as an input to `i6_native_ops.fbw.fbw_loss`.
@@ -149,23 +267,17 @@ def build_batch(self, seq_tags: Iterable[str]) -> WeightedFsa:
149267 the batch.
150268 Additionally we apply an optional scale to the weights.
151269
152- :param seq_tags: an iterable object of sequence tags
270+ :param fsas: Sequence of FSAs as a tuple containing:
271+ * number of states S
272+ * number of edges E
273+ * integer edge array of shape [E, 3] where each row is an edge
274+ consisting of from-state, to-state and the emission idx
275+ * float weight array of shape [E,]
153276 :return: a concatenated FSA
154277 """
155278
156- def append_fsa (a , b ):
157- edges = torch .from_numpy (np .int32 (b [2 ])).reshape ((3 , b [1 ]))
158- return (
159- a [0 ] + [b [0 ]], # num states
160- a [1 ] + [b [1 ]], # num edges
161- torch .hstack ([a [2 ], edges ]), # edges
162- torch .cat ([a [3 ], torch .from_numpy (b [3 ])]), # weights
163- )
164-
165- # concatenate all FSAs in the batch into a single one where state ids are not yet unique
166- fsas = map (self .build_single , seq_tags )
167279 empty_fsa = ([], [], torch .empty ((3 , 0 ), dtype = torch .int32 ), torch .empty ((0 ,)))
168- num_states , num_edges , all_edges , all_weights = reduce (append_fsa , fsas , empty_fsa )
280+ num_states , num_edges , all_edges , all_weights = reduce (_append_fsa , fsas , empty_fsa )
169281 num_edges = torch .tensor (num_edges , dtype = torch .int32 )
170282 num_states = torch .tensor (num_states , dtype = torch .int32 )
171283
@@ -194,13 +306,33 @@ def append_fsa(a, b):
194306 return out_fsa
195307
196308
197- class RasrFsaBuilderV2 ( RasrFsaBuilder ):
309+ class _RasrFsaBuilderFbw2 ( _AbstractRasrFsaBuilder ):
198310 """
199- An update of the RasrFsaBuilder that is compatible with the fbw2 op from i6_native_ops.
311+ Abstract base class for building an FSA.
312+ Internally uses allophones to model the FSA by means of `librasr.AllophoneStateFsaBuilder`.
313+
314+ The implementation is compatible with the `fbw2` op from `i6_native_ops`.
315+
316+ The user must overwrite the :funcref:`build_single` method.
317+
318+ The TDP scale must be explicitly called when running :funcref:`build_single`.
319+ For that, :funcref:`apply_tdp_scale_to_fsa_tuple` can be used.
320+
321+ Using any subclass requires a working installation of the python package `librasr`.
200322 """
201323
202- def build_batch (self , seq_tags : Iterable [str ]) -> WeightedFsaV2 :
203- fsas = list (map (self .build_single , seq_tags ))
324+ def build_batched_fsa (self , fsas : Iterable [FsaTuple ]) -> WeightedFsaV2 :
325+ """
326+ Joins a set of FSAs represented as tuples into a single :classref:`WeightedFsaV2` object.
327+
328+ :param fsas: FSAs to be concatenated, represented as tuples with the following fields:
329+ * number of states S
330+ * number of edges E
331+ * integer edge array of shape [E, 3] where each row is an edge
332+ consisting of from-state, to-state and the emission idx
333+ * float weight array of shape [E,]
334+ :return: Single FSA object corresponding to the joined FSAs passed as parameter.
335+ """
204336
205337 num_states = [f [0 ] for f in fsas ]
206338 num_edges = [f [1 ] for f in fsas ]
@@ -222,7 +354,55 @@ def build_batch(self, seq_tags: Iterable[str]) -> WeightedFsaV2:
222354 torch .IntTensor (np .array ([start_states , end_states ])),
223355 )
224356
225- if self .tdp_scale != 1.0 :
226- out_fsa *= self .tdp_scale
227-
228357 return out_fsa
358+
359+
360+ class RasrFsaBuilderV2 (_RasrFsaBuilderFbw2 ):
361+ """
362+ Builds an FSA given a sequence tag.
363+ The orthography will be pulled from the corpus provided in the configuration file.
364+ Internally uses allophones to model the FSA by means of `librasr.AllophoneStateFsaBuilder`.
365+
366+ The implementation is compatible with the `fbw2` op from `i6_native_ops`.
367+ """
368+
369+ def build_single (self , single_identifier : str ) -> FsaTuple :
370+ """
371+ Build the FSA for the given sequence tag in the corpus.
372+
373+ :param single_identifier: sequence tag
374+ :return: FSA as a tuple containing
375+ * number of states S
376+ * number of edges E
377+ * integer edge array of shape [E, 3] where each row is an edge
378+ consisting of from-state, to-state and the emission idx
379+ * float weight array of shape [E,]
380+ """
381+ raw_fsa = self .builder .build_by_segment_name (single_identifier )
382+
383+ return self .apply_tdp_scale_to_fsa_tuple (raw_fsa , self .tdp_scale )
384+
385+
386+ class RasrFsaBuilderByOrthography (_RasrFsaBuilderFbw2 ):
387+ """
388+ Builds an FSA given an orthography.
389+ Internally uses allophones to model the FSA by means of `librasr.AllophoneStateFsaBuilder`.
390+
391+ The implementation is compatible with the `fbw2` op from `i6_native_ops`.
392+ """
393+
394+ def build_single (self , single_identifier : str ) -> FsaTuple :
395+ """
396+ Build the FSA for the given orthography in the corpus.
397+
398+ :param single_identifier: Segment orthography.
399+ :return: FSA as a tuple containing
400+ * number of states S
401+ * number of edges E
402+ * integer edge array of shape [E, 3] where each row is an edge
403+ consisting of from-state, to-state and the emission idx
404+ * float weight array of shape [E,]
405+ """
406+ raw_fsa = self .builder .build_by_orthography (single_identifier )
407+
408+ return self .apply_tdp_scale_to_fsa_tuple (raw_fsa , self .tdp_scale )
0 commit comments