1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
//! CPU decoding and forced alignment for acoustic-model score matrices.
//!
//! It sits outside `sicada` proper because `sicada` is a port of OpenFst's
//! library, while the algorithms here belong to the speech-decoding layer.
//!
//! - [`dense`] reads the acoustic model's `T × V` score matrix as an FST, so
//! that composing a decoding graph against it is an ordinary composition.
//! - [`viterbi`] walks that composition one frame at a time without building
//! it, which is how a decoder works.
//! - [`lattice`] does the same but keeps the alternatives, so a second pass has
//! something to rescore. [`lattice_weight`] is the semiring its arcs carry,
//! which holds the graph cost and the acoustic cost apart.
//! - [`compact`] collapses the alignments, so each word sequence appears once
//! with the best one. [`compact_lattice_weight`] is the semiring that makes
//! that possible: a cost with the frames it spanned attached.
//! - [`nbest`] reads the answers back out, and rescales the two halves against
//! each other without decoding again.
//! - [`ctc`] builds the graph side for a CTC model, which is where a decoder
//! with no language model starts.
//! - [`align`](mod@align) covers the other half of the same model's use. When the
//! transcript is already known the graph is a single chain, and the only
//! question is which frames each phone occupies. That case is small enough to
//! solve exactly, so it has no beam. [`occupancy`](mod@occupancy) walks the
//! same chain in the log semiring, for the soft answer that a single path
//! cannot give.
//! - [`trellis`] is the solver those two are built on, and the piece to use
//! when the chain is not the shape you want. Supply the transitions into a cell
//! (how many there are, what they cost, what they mean) and the band, the
//! packed traceback and the forward-backward come with it.
//!
//! # Decoding a CTC model
//!
//! The whole pipeline, from a score matrix to the answers. The scores here are
//! made up; a real one comes out of the acoustic model as negative log
//! probabilities, `T × V` row-major, with the blank in column 0.
//!
//! ```
//! use sicada::arc::StdArc;
//! use sicada_decode::{
//! DecodeOptions, DenseFst, LatticeDecodeOptions, PrunedDeterminizeOptions, ctc_topo,
//! determinize_lattice_pruned, lattice_decode, n_best, viterbi_decode,
//! };
//!
//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
//! // Blank plus three tokens, four frames. Column 0 is the blank.
//! let (frames, symbols) = (4, 4);
//! let scores = vec![
//! 9.0, 0.0, 9.0, 9.0, // token 1
//! 9.0, 0.0, 9.0, 9.0, // token 1 again, held rather than repeated
//! 0.0, 9.0, 9.0, 9.0, // blank
//! 9.0, 0.0, 9.0, 9.0, // token 1, and the blank makes it a second one
//! ];
//!
//! let graph = ctc_topo::<StdArc>(symbols, 1)?;
//! let dense = DenseFst::<StdArc>::new(&scores, frames, symbols)?;
//!
//! // The transcript, and nothing else.
//! let best = viterbi_decode(&graph, &dense, &DecodeOptions::default())?
//! .expect("the beam kept a path");
//! // Labels are columns offset by one, which is where `ctc_topo` put them.
//! let columns: Vec<i32> = best.labels.iter().map(|label| label - 1).collect();
//! assert_eq!(columns, vec![1, 1]);
//!
//! // Or the alternatives too, for a second pass to rescore.
//! let lattice = lattice_decode(&graph, &dense, &LatticeDecodeOptions::default())?
//! .expect("the beam kept a path");
//! let compact = determinize_lattice_pruned(&lattice, &PrunedDeterminizeOptions::default())?;
//! for answer in n_best(&compact.lattice, 3)? {
//! let columns: Vec<i32> = answer.words.iter().map(|label| label - 1).collect();
//! // Each answer knows which frames produced it.
//! assert_eq!(answer.alignment().len(), frames);
//! let _ = (columns, answer.cost());
//! }
//! # Ok(())
//! # }
//! ```
//!
//! # Aligning a transcript that is already known
//!
//! Here the answer is given and only the timing is wanted, so there is no
//! topology and no lattice: a single chain, solved exactly.
//!
//! ```
//! use sicada::arc::StdArc;
//! use sicada_decode::{AlignChain, DenseFst, align, occupancy};
//!
//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
//! // Six frames over a blank and three phones. Column 0 is the blank.
//! let (frames, symbols) = (6, 4);
//! let scores = vec![
//! 9.0, 0.0, 9.0, 9.0, // phone 1
//! 9.0, 0.0, 9.0, 9.0, // still phone 1
//! 0.0, 9.0, 9.0, 9.0, // silence
//! 9.0, 9.0, 0.0, 9.0, // phone 2
//! 0.0, 9.0, 9.0, 9.0, // silence
//! 0.0, 9.0, 9.0, 9.0, // silence
//! ];
//!
//! // The reference: phones as *columns*, in order. Labels do not come into it.
//! let chain = AlignChain::new(vec![1, 2]);
//! let dense = DenseFst::<StdArc>::new(&scores, frames, symbols)?;
//!
//! let alignment = align(&chain, &dense)?.expect("the reference fits");
//! // Each phone gets the frames that sound it, and the blank frames belong to
//! // nobody, so the last phone does not swallow the silence after it.
//! assert_eq!(alignment.spans(), vec![Some(0..2), Some(3..4)]);
//! assert!(alignment.skipped().is_empty());
//!
//! // The mean per-frame cost is the warning that the reference is not what
//! // was said. Here it is low, because the reference is correct.
//! assert!(alignment.mean_acoustic_cost(&chain, &dense) < 0.1);
//!
//! // The same chain in the log semiring, when the soft answer is wanted.
//! let spread = occupancy(&chain, &dense)?.expect("the reference fits");
//! assert!((spread.expected_durations()[0] - 2.0).abs() < 0.01);
//! # Ok(())
//! # }
//! ```
//!
//! The references are Kaldi (`decoder/lattice-faster-decoder.*`,
//! `fstext/lattice-weight.h`) and k2.
pub use ;
pub use ;
pub use ;
pub use ;
pub use ;
pub use DecodeOptions;
pub use ;
pub use ;
pub use ;
pub use ;
pub use ;
pub use ;