Speech Processing & Human–Machine Interaction · Week 3
Week 2 gave us the clock. Nobody gave us what each tick belongs to — and that gap is the whole of this week.
W2 settings · 16 kHz · 25 / 10 ms · 80 mel · a dozen phones/s against 100 frames/s
Week 2 left us with 100 feature vectors per second, \mathbf{X} = (\mathbf{x}_1, \dots, \mathbf{x}_T). The training data gives each clip one line of text, \ell = (\ell_1, \dots, \ell_U) — and nothing in between.
| Axis | This week’s question |
|---|---|
| Supervision | How do you train on (\mathbf{X}, \ell) when nobody says which frames belong to which symbol? |
| Representation | The label unit and the frame rate now constrain each other. Who gives way? |
| Latency | If every frame emits on its own, streaming is free — what did we pay for it? |
| Why | The last slide put the gap in symbols: T frames in, U symbols out, nothing in between. This demo shows that gap on a real voice, before any equation touches it. |
| What runs | A volunteer says 今天天氣很好 twice: at normal speed, then about twice as fast. Each take is trimmed of silence; the two spectrograms are drawn one above the other, aligned at the start, one column per 10 ms frame. |
| Watch for | The frame count T, the symbol count U, and anything on the screen that says which frames belong to which character. And: does T halve when the speed doubles? |
Demo 1 · code: demos/w03_d1_two_rates · rehearsed on the M5 Max, 2026-10-05
Demo 1. Before the plots appear: which numbers change between the two takes, and which stay the same?
Demo 1 · two takes, starts aligned · one spectrogram column = one 10 ms frame
Speech recognition is sequence supervision under an unknown alignment. Every method this week is a way of handling that hidden variable.
Today’s route: remedial → HMM (road 1) → CTC (road 2) · attention (road 3) is W4
P(\ell \mid \mathbf{X}) \;=\; \sum_{\pi} P(\ell, \pi \mid \mathbf{X})
\pi is the thing we never observe — the alignment. If we cannot know it, we add up every value it could take.
Every equation today has this shape: a probability we want, written as a sum over a hidden alignment. The only question is how to do the sum without enumerating it.
P(\pi \mid \mathbf{X}) \;=\; \prod_{t=1}^{T} P(\pi_t \mid \pi_{1:t-1}, \mathbf{X}) \;\;\xrightarrow{\;\text{assume}\;}\;\; \prod_{t=1}^{T} P(\pi_t \mid \mathbf{X}) \;=\; \prod_{t=1}^{T} y^t_{\pi_t}
The chain rule is exact. The arrow is an assumption: given \mathbf{X}, the symbol at frame t does not depend on the symbols at other frames.
This line is the whole assumption of CTC. Every pathology in the last hour of today — no output language model, homophones, peaky spikes — comes back to this arrow.
y^t_k is the network’s softmax output for symbol k at frame t. The network sees all of \mathbf{X}; the outputs see nothing of each other.
\ln \prod_t y^t_{\pi_t} = \sum_t \ln y^t_{\pi_t}, \qquad \ln \sum_i e^{a_i} \;=\; a^{*} + \ln \sum_i e^{a_i - a^{*}}, \quad a^{*} = \max_i a_i
Two operations cover every algorithm today: (+, \times) in probability space is (\operatorname{logsumexp}, +) in log space, and (\max, \times) is (\max, +).
| Symbol | Meaning |
|---|---|
| \mathbf{x}_t \in \mathbb{R}^d, \mathbf{X} = (\mathbf{x}_1, \dots, \mathbf{x}_T) | frame vectors from the W2 front-end; T frames |
| \ell = (\ell_1, \dots, \ell_U), U = \lvert \ell \rvert | the transcript, over vocabulary \mathcal{V} |
| \varnothing, \mathcal{V}' = \mathcal{V} \cup \{\varnothing\} | the blank, and the output alphabet with it |
| \ell', S = 2U + 1 | \ell with a blank before, between and after every label |
| \pi \in \mathcal{V}'^{\,T} | a path: one output symbol per frame |
| \mathbf{u}^t, \mathbf{y}^t = \operatorname{softmax}(\mathbf{u}^t), y^t_k | logits, softmax, and one entry of it at frame t |
| \mathcal{L}_{\text{CTC}} = -\ln P(\ell \mid \mathbf{X}), \gamma_t(k) | the loss, and the alignment posterior P(\pi_t = k \mid \ell, \mathbf{X}) |
| q_t \in \{1, \dots, N\}, \pi_i, a_{ij}, b_j(\mathbf{x}_t) | HMM side: state, initial, transition, emission |
| m | encoder lookahead in frames when streaming (W1 wrote it \ell; today \ell is the transcript) |
Logs are \ln throughout · L is not used today: W2 gave it to the window length
P(\mathbf{X} \mid W) \;=\; \sum_{q} \pi_{q_1}\, b_{q_1}(\mathbf{x}_1) \prod_{t=2}^{T} a_{q_{t-1} q_t}\, b_{q_t}(\mathbf{x}_t)
q = (q_1, \dots, q_T) is the hidden state sequence — it is the alignment. W fixes the topology: a chain of states per phone, left to right.
| Rabiner’s problem | Question | Algorithm |
|---|---|---|
| Evaluation | How likely is \mathbf{X} under this W? | forward |
| Decoding | Which state sequence best explains \mathbf{X}? | Viterbi |
| Learning | Which a_{ij}, b_j make the data most likely? | Baum–Welch (forward–backward) |
Rabiner, Proc. IEEE 1989 · generative: the model explains the audio, given the words
\alpha_t(j) = b_j(\mathbf{x}_t) \sum_{i} \alpha_{t-1}(i)\, a_{ij}, \qquad \alpha_1(j) = \pi_j\, b_j(\mathbf{x}_1), \qquad P(\mathbf{X} \mid W) = \sum_j \alpha_T(j)
O(TN^2) instead of N^T · the lattice is the alignment space drawn as a grid
\delta_t(j) = b_j(\mathbf{x}_t)\, \max_{i}\, \delta_{t-1}(i)\, a_{ij}, \qquad \text{then backtrack the argmax.}
| Space | “add every way” | “keep the best way” | Gives |
|---|---|---|---|
| probability | (+, \times) | (\max, \times) | forward / Viterbi |
| log | (\operatorname{logsumexp}, +) | (\max, +) | what the code actually runs |
Same lattice, same recursion, different semiring. This week: HMM and CTC. W4: the RNN-T lattice. W7: prefix beam search. You will see this table three more times.
Forward is a sum over every alignment, \sum_q P(\mathbf{X}, q \mid W) — how likely is the audio under the words. Viterbi is a max, \max_q P(\mathbf{X}, q \mid W) — how likely is the audio under the single best state sequence.
The best single alignment usually carries a small fraction of the total. And the argmax over paths need not agree with the argmax over label sequences — many weak paths can outvote one strong one.
Week 2’s fact: articulators cannot jump, so a phone sounds different depending on its neighbours.
The HMM’s answer: write the neighbours into the unit. A triphone /s-k+i/ is its own model, distinct from /u-k+o/.
The unit absorbs the context because the model cannot: an HMM state sees one frame, and the outputs are conditionally independent given the state. Remember that sentence — it returns for CTC.
Gales & Young, FnT Signal Processing 2008 · tying: Young, Odell & Woodland 1994
You need a working recogniser before you can train the recogniser. The targets are hard — one state per frame — and they are only as good as the model that produced them.
Hybrid DNN–HMM · YD Ch 3–6 · the bootstrap is what CTC removes
The HMM is not a system you will deploy. It is the grammar of every alignment algorithm you will read for the rest of this course.
\mathcal{B} maps a path of length T to a label sequence: merge adjacent repeats, then delete every \varnothing.
Graves et al., ICML 2006 · the blank is bookkeeping: “no new label at this frame”
| Why | P(\ell \mid \mathbf{X}) is a sum over every path that \mathcal{B} collapses to \ell. Before deriving a recursion for that sum, look at what it sums over; once the recursion is derived, check it against the brute-force sum. |
| What runs | One terminal program, pure numpy, used twice. Next slide: list the paths for “aa” and “aba” on paper, then let the program list them. After the forward recursion: a random \mathbf{y}^t, every path’s probability summed, next to the forward table filled column by column — then T = 500. |
| Expect | The two sums agree to floating-point error; if they do not, the recursion is wrong. Watch how fast the number of paths grows with T, whether every pair (\ell, T) has a path at all, and what the linear-domain table does at T = 500. |
Demo 2 · code: demos/w03_d2_paths_vs_forward · pure numpy, terminal only
Demo 2, first half — on paper first. Vocabulary \{a, b\} plus \varnothing. Write every path of length T = 3 that collapses to “aa”, and every one that collapses to “aba”. Then T = 4 for “aa”.
| \ell | T | paths in \mathcal{B}^{-1}(\ell) | count |
|---|---|---|---|
| aa | 2 | — | 0 |
| aa | 3 | \mathsf{a}\varnothing\mathsf{a} | 1 |
| aba | 3 | \mathsf{aba} | 1 |
| aa | 4 | \mathsf{aa}\varnothing\mathsf{a} · \mathsf{a}\varnothing\mathsf{aa} · \mathsf{a}\varnothing\mathsf{a}\varnothing · \mathsf{a}\varnothing\varnothing\mathsf{a} · \varnothing\mathsf{a}\varnothing\mathsf{a} | 5 |
| ab | 4 | \mathsf{aaab} · \mathsf{aabb} · \mathsf{aab}\varnothing · … · \varnothing\varnothing\mathsf{ab} | 15 of 3^4 = 81 candidates |
Demo 2 · pure numpy, live · counts: w03-data.json → paths
| HMM | CTC | |
|---|---|---|
| what is modelled | P(\mathbf{X} \mid W) — generative | P(\ell \mid \mathbf{X}) — discriminative |
| the hidden alignment | state sequence q | path \pi |
| the sum | \displaystyle\sum_{q} \pi_{q_1} b_{q_1}(\mathbf{x}_1) \prod_{t=2}^{T} a_{q_{t-1}q_t} b_{q_t}(\mathbf{x}_t) | \displaystyle\sum_{\pi \in \mathcal{B}^{-1}(\ell)} \prod_{t=1}^{T} y^t_{\pi_t} |
| per-frame factor | transition \times emission | softmax output, nothing else |
Both are a sum over alignments of a product over frames. CTC has no transition term and a discriminative emission. Everything else — the lattice, the recursion, the semiring — carries over.
\ell' = (\varnothing, \ell_1, \varnothing, \ell_2, \dots, \ell_U, \varnothing), length S = 2U + 1. A path is a walk on this graph, one node per frame.
Three moves per frame: stay · advance one · advance two, between different labels · every edge has weight 1 — no transition probabilities
\alpha_t(s) = y^t_{\ell'_s}\Bigl(\alpha_{t-1}(s) + \alpha_{t-1}(s-1) + [\ell'_s \neq \varnothing \,\wedge\, \ell'_s \neq \ell'_{s-2}]\;\alpha_{t-1}(s-2)\Bigr)
\alpha_t(s): total probability of paths at position s of \ell' after t frames
| Quantity | Value | Why |
|---|---|---|
| \alpha_1(1) | y^1_{\varnothing} | the path may open with a blank |
| \alpha_1(2) | y^1_{\ell_1} | or with the first label |
| \alpha_1(s),\; s > 2 | 0 | unreachable in one frame |
| \alpha_t(s),\; s < S - 2(T-t) - 1 | 0 | cannot reach the end in the frames left |
| P(\ell \mid \mathbf{X}) | \alpha_T(S) + \alpha_T(S-1) | the last frame is a blank, or the last label |
Exponentially many paths, O(TS) cells. The same idea as the HMM forward, O(TN^2) — here every cell has at most three predecessors, so the N^2 becomes 3S.
“ab”, T = 4: 15 legal paths of 81, one 4 \times 5 table · w03-data.json → paths
T < U + r \;\Rightarrow\; \mathcal{B}^{-1}(\ell) = \varnothing \;\Rightarrow\; P(\ell \mid \mathbf{X}) = 0 \;\Rightarrow\; \mathcal{L}_{\text{CTC}} = \infty, \qquad r = \bigl|\{u : \ell_u = \ell_{u+1}\}\bigr|
inf comes from in practiceWho eats the frames: a 10 ms hop gives 100 per second; a 4\times subsampling encoder leaves 25. A fast talker with character-level labels can push U + r past that.
Who inflates U: character-level Mandarin (one symbol per character), letter-level English, any label unit finer than the encoder’s clock can afford.
zero_infinity=True replaces an infinite loss by zero and a gradient by nothing. It hides the utterance; it does not fix it. Count U + r against T first.
When the loss is inf |
Check, in this order |
|---|---|
| 1 | U + r against T — the label is too long for the frames |
| 2 | a label symbol that is not in \mathcal{V} (mapped to blank or to an unknown id) |
| 3 | only then, numerics: are you summing in the log domain? |
characters per second: order of magnitude · one dev-clean sentence (Demo 3): 12.5 letters/s, 15.2 symbols/s incl. word boundaries
With every y^t_k = 1/3 (vocabulary \{a, b, \varnothing\}), each path has probability 3^{-T}, so P(\ell \mid \mathbf{X}) must equal \lvert \mathcal{B}^{-1}(\ell) \rvert \cdot 3^{-T} — a number you can check by hand.
| \ell | T | brute force | forward table | match |
|---|---|---|---|---|
| aa | 2 | 0 / 9 | 0 | ✓ |
| aa | 3 | 1 / 27 | 0.037037 | ✓ |
| aba | 3 | 1 / 27 | 0.037037 | ✓ |
| aa | 4 | 5 / 81 | 0.061728 | ✓ |
| ab | 4 | 15 / 81 | 0.185185 | ✓ |
Demo 2, live version. Same script, random \mathbf{y}^t: enumeration and forward agree to floating-point error. Then T = 500 with \lvert \mathcal{V}' \rvert = 32.
Uniform-\mathbf{y} rows: w03-data.json → forward_uniform · random-\mathbf{y}^t run: Demo 2, live
The forward table gives one number, P = P(\ell \mid \mathbf{X}). Training needs \partial \mathcal{L}_{\text{CTC}} / \partial u^t_k at every (t, k).
P = \sum_{\pi \in \mathcal{B}^{-1}(\ell)} \prod_{t'=1}^{T} y^{t'}_{\pi_{t'}} \quad\Longrightarrow\quad \frac{\partial P}{\partial y^t_k} = \frac{1}{y^t_k} \sum_{\pi \in \mathcal{B}^{-1}(\ell),\; \pi_t = k} P(\pi \mid \mathbf{X})
Paths through a cell: ways in \times ways out. The ways in, \alpha_t(s), are already in the forward table; the ways out need a second table, filled from the end.
“ab”, T = 4, cell (2, 2): 2 \times 4 = 8 of the 15 paths · w03-data.json → through_cell
\beta_t(s) = \beta_{t+1}(s)\, y^{t+1}_{\ell'_s} + \beta_{t+1}(s+1)\, y^{t+1}_{\ell'_{s+1}} + [\,\cdot\,]\; \beta_{t+1}(s+2)\, y^{t+1}_{\ell'_{s+2}}, \qquad \beta_T(S) = \beta_T(S-1) = 1
\beta_t(s) covers frames t+1 \dots T, so it excludes y^t · same [\,\cdot\,] condition, read at s+2
\gamma_t(k) \;=\; \frac{1}{P(\ell \mid \mathbf{X})} \sum_{s:\, \ell'_s = k} \alpha_t(s)\, \beta_t(s) \;=\; P(\pi_t = k \mid \ell, \mathbf{X})
\frac{\partial \mathcal{L}_{\text{CTC}}}{\partial u^t_k} \;=\; y^t_k - \gamma_t(k)
Cross-entropy’s y_k - e_k, with the one-hot \mathbf{e} replaced by the posterior \gamma_t(\cdot).
Graves (2012) convention · positions of \ell' with the same symbol pool into one \gamma_t(k)
1. \mathcal{L}_{\text{CTC}} = -\ln P with P = P(\ell \mid \mathbf{X}), and P = \sum_s \alpha_t(s)\,\beta_t(s) holds for this t (the backward slide).
2. Inside \alpha_t(s) only the factor y^t_{\ell'_s} involves \mathbf{y}^t; \beta_t(s) does not (that is what the convention bought). So \frac{\partial P}{\partial y^t_k} = \frac{1}{y^t_k}\sum_{s:\, \ell'_s = k} \alpha_t(s)\,\beta_t(s) \qquad\Rightarrow\qquad \frac{\partial \mathcal{L}}{\partial y^t_k} = -\frac{\gamma_t(k)}{y^t_k}
3. Softmax Jacobian \dfrac{\partial y^t_j}{\partial u^t_k} = y^t_j\,(\delta_{jk} - y^t_k), then chain rule over j: \frac{\partial \mathcal{L}}{\partial u^t_k} = \sum_j \Bigl(-\frac{\gamma_t(j)}{y^t_j}\Bigr)\, y^t_j\,(\delta_{jk} - y^t_k) = -\gamma_t(k) + y^t_k \underbrace{\sum_j \gamma_t(j)}_{=\,1} = y^t_k - \gamma_t(k)
The 1/y^t_k cancels against the Jacobian; \sum_j \gamma_t(j) = 1 because \gamma_t(\cdot) is a posterior
| Hybrid DNN–HMM (step 3 of the recipe) | CTC | |
|---|---|---|
| loss per frame | cross-entropy | cross-entropy |
| gradient on logits | y^t_k - e_k | y^t_k - \gamma_t(k) |
| the target | hard: one state, from a forced alignment | soft: a posterior over symbols |
| who made the target | the previous model, once | the current model, every step |
| needs a bootstrap | yes | no |
CTC is per-frame cross-entropy against a soft alignment recomputed at every update. That is the whole difference from hybrid training — and it is also where the pathologies begin.
W7: sequence-discriminative training softens the hybrid target the same way
y^{T} < 5 \times 10^{-324} \iff T > \frac{-323.3}{\log_{10} y}, \qquad y = 0.1:\; T > 323.3, \text{ so the product is } 0 \text{ from } T = 324
Demo 2, second half · float64 floors, not a measurement · w03-data.json → underflow
| Graves et al., ICML 2006 | Graves, Supervised Sequence Labelling, 2012 (this deck) | |
|---|---|---|
| \beta_t(s) runs over frames | t, \dots, T — includes y^t | t+1, \dots, T — excludes y^t |
| P(\ell \mid \mathbf{X}) at frame t | \sum_s \alpha_t(s)\beta_t(s) / y^t_{\ell'_s} | \sum_s \alpha_t(s)\beta_t(s) |
| gradient on the logits | carries an extra 1/y^t_k inside the sum | y^t_k - \gamma_t(k) |
Neither is wrong; mixing them is. A \beta that starts at t plus the book’s gradient raises no error — it just trains slowly and strangely.
| Why | Every posterior so far was made up: uniform, random, or drawn. Now a real character-level CTC model on one LibriSpeech sentence, with two questions: what does the blank do in real speech, and what happens to the loss when the text is too long for the audio? |
| What runs | wav2vec2-base-960h on the CPU. First a heat-map: one column per 20 ms frame, one row per symbol, the blank row enlarged at the bottom, non-speech frames shaded grey. Later, ctc_loss on the same logits, with the text lengthened one character at a time past U + r = T. |
| Watch for | The blank row where there is no grey: if blank meant silence, it would be low there. For the loss, the zero-probability condition already says where it breaks; watch what the loss looks like there, and what zero_infinity turns it into. |
Demo 3 · code: demos/w03_d3_blank_heatmap · LibriSpeech dev-clean, CC BY 4.0
Demo 3. Before the plot: in a sentence with no pauses, what fraction of frames will the blank win? Write down a number.
| Measured on the sentence | value |
|---|---|
| frames T (after the model’s own subsampling) | 292 (5.86 s, one frame per 20 ms) |
| characters U, repeats r, U + r | 89 (73 letters + 16 |), 2, 91 |
| frames where \varnothing is the argmax | 149 / 292 = 51 % |
| frames inside speech where \varnothing is the argmax | 94 / 232 = 41 % |
Demo 3 · facebook/wav2vec2-base-960h (94.4 M params) via transformers 5.17, CPU · LibriSpeech dev-clean 1272-128104-0000 · measured 2026-09-26
The blank is the symbol for “no new label at this frame.” It is bookkeeping, not acoustics.
If you ever find yourself “explaining” a blank-dominated posterior by pauses, look at Demo 3’s last row again: blank wins 4 frames in 10 inside speech. W1’s \varnothing — “say nothing this frame” — is the same kind of object.
schematic — Demo 3 shows a real one · Zeyer, Schlüter & Ney, arXiv:2105.14849
Six letters on eight frames, blanks in the gaps, the word boundary | emitted like any other symbol. This is what Demo 3’s heat-map looks like up close — and why “4 in 10” inside speech is not a majority: every label still needs at least one frame.
Demo 3 · runs/rehearsal/logits.npy, softmax, frames 26–42 (20 ms each) · rows for the other 24 symbols omitted — none wins a frame here
From the gradient y^t_k - \gamma_t(k), with \gamma recomputed from the current model at every step:
Zeyer, Schlüter & Ney (2021) give a formal analysis of this as a convergence property of the CTC loss under gradient descent; on a toy example they prove a feed-forward network from uniform initialisation converges to peaky behaviour, and show a related loss with a label prior does not.
arXiv:2105.14849, abstract level · same localisation in full-sum HMM training (2017)
Phone level: no. The spike is where the model chose to emit, not where the sound starts or ends. A peaky output puts the spike somewhere inside the segment, and nothing in the loss says where. For phone boundaries use a hybrid forced alignment or a dedicated aligner.
Word level: yes, routinely. A CTC forced alignment — Viterbi through \ell' with the known transcript — gives word timestamps good enough for subtitles and for training data:
torchaudio alignment tutorial)“CTC cannot align” overshoots as badly as “CTC alignments are ground truth.” The right statement is about granularity.
MMS: Pratap et al., arXiv:2305.13516 · WhisperX: Bain et al., Interspeech 2023
inf, and what zero_infinity hidesDemo 3, second half. Same logits, correct transcript → a finite loss. Now append text until U + r > T.
| loss | gradient | what you learn | |
|---|---|---|---|
| correct transcript | finite | y^t_k - \gamma_t(k) | the sentence trains normally |
too long, zero_infinity=False |
inf |
nan |
the batch is poisoned — you notice |
too long, zero_infinity=True |
0 |
0 |
the sentence silently contributes nothing |
Row three is the dangerous one. A data pipeline that trims audio but not text, or an encoder with one stride too many, produces a training set that looks fine and trains on a fraction of itself.
Demo 3 · torch.nn.functional.ctc_loss, batch of one, reduction="sum" · torch 2.14.0: identical on CPU and MPS (forward + backward, measured 2026-09-26)
Every arrow from \mathbf{X} is there; every arrow between outputs is missing. The assumption lives only in the top row.
| Statement | Verdict |
|---|---|
| “The encoder sees the whole utterance” | true — \mathbf{y}^t is computed from all of \mathbf{X}, or \mathbf{x}_{1:t+m} when streaming with m frames of lookahead |
| “The outputs are conditionally independent” | true — P(\pi \mid \mathbf{X}) = \prod_t y^t_{\pi_t}: no output is conditioned on another |
| “CTC has no language model at all” | overshoots — acoustic context carries linguistic information, and the encoder uses it |
| “CTC needs no language model” | overshoots — nothing at the output couples one symbol to the next; fusion gains are large |
The assumption is about the output layer, not about the network. Whatever the acoustics can support, the encoder can learn; whatever needs the previous symbol, it cannot.
Both overshoot; the previous slide has the precise version. Here is why it matters more for this room:
My judgement, not consensus: the conditional-independence cost bites harder in Mandarin than in English, so the gain from an external LM should be larger. W7 will test this with numbers.
Tone as lexical content: W2 · shallow fusion and internal-LM estimation: W7
CTC needs a fixed, finite \mathcal{V}. Everything downstream follows from that choice:
| Choice of \mathcal{V} | U for one second of speech | Subsampling you can afford |
|---|---|---|
| characters | large | little |
| phones | medium | some |
| BPE / word pieces | small | more |
Taiwanese has no single orthography — Hàn-jī, Tâi-lô and Pe̍h-ōe-jī coexist. “Which symbols are in \mathcal{V}” must be decided before the first line of training code, and it fixes U, hence the floor on T, hence the encoder’s stride.
W1’s orthography gap for local languages has its first engineering consequence here: the label set is a modelling decision that precedes the loss, not a preprocessing detail.
| HMM (GMM or hybrid) | CTC | |
|---|---|---|
| states | phone states, N of them, tied | the S = 2U + 1 positions of \ell' |
| allowed transitions | as the topology says, with a_{ij} | stay · +1 · +2 under the condition, all with weight 1 |
| emission | generative b_j(\mathbf{x}_t), or scaled posterior | discriminative y^t_k, shared across positions with the same symbol |
| training | Viterbi (hybrid) or full-sum (Baum–Welch) | full-sum (forward), always |
| alignment | hard (hybrid) or soft (full-sum) | soft, every step |
| direction | P(\mathbf{X} \mid W) | P(\ell \mid \mathbf{X}) |
Zeyer, Beck, Schlüter & Ney (2017) formulate generalised full-sum hybrid HMM training and identify CTC as a special case. Raissi et al. (SLT 2022) compare the two under one from-scratch full-sum setup.
Zeyer et al., Interspeech 2017 · Raissi et al., IEEE SLT 2022 · abstract level
The previous table is the comparison. Put the two sums side by side once more:
\sum_{q} \pi_{q_1} b_{q_1}(\mathbf{x}_1) \prod_{t=2}^{T} a_{q_{t-1}q_t}\, b_{q_t}(\mathbf{x}_t) \qquad\text{vs.}\qquad \sum_{\pi \in \mathcal{B}^{-1}(\ell)} \prod_{t=1}^{T} y^t_{\pi_t}
Drop the transition probabilities, replace the generative emission by a softmax, fix the topology to \ell', and train full-sum: the left sum becomes the right one. A loss that sums over alignments is a model of alignments.
| frame-level (hybrid) | CTC | attention (W4) | |
|---|---|---|---|
| alignment | hard, contiguous | monotonic, many-to-one, blank-padded | soft, learned, need not be monotonic |
| duration model | yes (a_{ij}) | none | none, but no blank either |
| streaming | yes | yes, encoder permitting | not without extra machinery |
| CTC (today) | RNN-T (W4) | |
|---|---|---|
| what \varnothing means | this frame emits no new label | consume one frame; advance time |
| labels per frame | exactly one symbol from \mathcal{V}' | zero, one, or several labels, then a blank |
| the lattice | T \times S, one axis is \ell' | T \times (U+1), two independent axes |
| output dependency | none | yes — a prediction network sees the previous labels |
Same word, same glyph, different semantics. Next week the blank stops meaning “nothing” and starts meaning “next frame, please” — and that change is what buys back the output dependency CTC gave up.
W2 asked who decides the frame rate and answered “the hop, times every stride.” Today adds the other side: the label unit sets how slow the clock is allowed to get. Stride and vocabulary are one decision.
W2 p28 for the ladder · characters per second: order of magnitude · one dev-clean sentence (Demo 3): 12.5 letters/s, 15.2 symbols/s
| Axis | What CTC does | The price |
|---|---|---|
| Latency | every frame emits on its own, so a causal encoder streams with no extra decoder state; algorithmic latency is W1’s D_{\text{alg}} of the encoder alone — no decoder adds to it | no output dependency — the symbol you just emitted cannot inform the next |
| Supervision | (\mathbf{X}, \ell) pairs are enough; the alignment is summed out and never annotated | no duration model; the target chases the model (peaky); the vocabulary must be fixed first |
CTC made streaming and alignment-free training almost free. W4 pays for the output dependency (RNN-T); W7 pays for the language model (fusion). Nothing today was free — it was deferred.
| Axis | What we now know |
|---|---|
| Supervision | Audio plus a transcript is enough: the alignment is summed out. The gradient is a per-frame cross-entropy against a soft alignment, recomputed every step. |
| Representation | T \geq U + r: label unit and encoder stride are one decision, and the vocabulary is fixed before the loss exists. |
| Latency | Per-frame emission makes CTC streamable for the encoder’s lookahead alone. The output dependency it gives up returns next week. |
Every alignment method handles one hidden variable. HMM and CTC sum it out on the same lattice; they differ in what each refuses to model.
Read this week
If one thing today was new to you, make it this one
Next week — sequence models and streaming architectures
The blank changes meaning, the lattice gains a second axis, and the encoder learns to look ahead by exactly as much as the latency budget allows: Transformer, Conformer, RNN-T.