语音识别 ASR:CTC、RNN-T 与注意力 本节摘要:语音识别(Speech Recognition, ASR)本质上是「每个时间步做一次音频分类,再用一个懂语言和静音的序列模型把结果粘起来」。CTC、RNN-T 和注意力机制是三种主流做法,选一种并理解为什么。本节的真正难点是结构性的:音频帧与字符并非一一对应——「okay」可能占 200 毫秒也可能占 1200 毫秒,静音穿插其间,音素长短不一,输出 token 数量事先未知。CTC 引入 blank 牌做帧级对齐的非自回归方案;RNN-T 加一个预测器显式建模 token 历史的依赖,天然可流式;注意力编码器-解码器(Whisper、SeamlessM4T)用交叉注意力打破对齐约束,离线质量最高。
本节摘要:语音识别(Speech Recognition, ASR)本质上是「每个时间步做一次音频分类,再用一个懂语言和静音的序列模型把结果粘起来」。CTC、RNN-T 和注意力机制是三种主流做法,选一种并理解为什么。本节的真正难点是结构性的:音频帧与字符并非一一对应——「okay」可能占 200 毫秒也可能占 1200 毫秒,静音穿插其间,音素长短不一,输出 token 数量事先未知。CTC 引入 blank 牌做帧级对齐的非自回归方案;RNN-T 加一个预测器显式建模 token 历史的依赖,天然可流式;注意力编码器-解码器(Whisper、SeamlessM4T)用交叉注意力打破对齐约束,离线质量最高。2026 年 LibriSpeech test-clean 的 SOTA WER 已到 1.4%(Parakeet-TDT-1.1B),数字差异微小,部署差异巨大。读完本节,你能为不同部署目标选对 ASR 范式,手写 CTC 贪心解码与 WER 计算,并避开 VAD 缺失、语种漂移、长音频不切块等生产坑。
阅读完本节,你应当能够:
你有一段 10 秒 16 kHz 音频,想要一个字符串:「turn on the kitchen lights」。难点是结构性的:音频帧与字符并不一一对应。「okay」可能占 200 毫秒,也可能占 1200 毫秒;静音把话语切成段;不同音素长度不一;输出 token 数量事先未知。
三种范式解决它:
2026 年 LibriSpeech test-clean 的 SOTA WER 是 1.4%(Parakeet-TDT-1.1B)和 1.58%(Whisper-Large-v3-turbo)。数字差异微小,部署差异巨大。
让编码器对每帧输出 V+1 个 token 上的分布(V 个字符 + blank)。对于长度 U < T 的目标串 y,任何能塌缩成 y 的帧对齐都算数。CTC 损失对所有这样的对齐求和。解码:逐帧 argmax,合并重复,丢 blank。
优势:非自回归、可流式、零前瞻。缺点:条件独立性假设——每帧预测互相独立,没有内部语言模型。补救:用外部 LM 做 beam search 或浅融合。
加一个 预测器 网络嵌入 token 历史,加一个 联合器 把预测器状态与编码器帧组合成 V+1 上的联合分布(+1 是 null / 不发射)。显式建模了 CTC 忽略的条件依赖。因为每步只依赖过去的帧与过去的 token,所以可流式。
优势:可流式 + 内置语言模型。缺点:训练更复杂、更吃内存(三维损失格);RNN-T 损失内核本身就是一整个库类别。
编码器(632 层 Transformer)在对数梅尔帧上跑,解码器(632 层)交叉注意力到编码器输出,自回归生成 token。无对齐约束——注意力可以看音频任何位置。除非限制注意力(2024 年的 chunked Whisper-Streaming),否则不可流式。
优势:离线 ASR 质量最高,标准 seq2seq 工具链易训练。缺点:自回归延迟正比于输出长度;不工程化就无法流式。
💡 三者的本质区别在对齐与依赖:CTC 强对齐、无依赖(条件独立);RNN-T 强对齐、有依赖(可流式);注意力 无对齐、有依赖(全局上下文,但不可流式)。没有银弹,选择取决于「能不能等」与「延迟预算」。
词错率(Word Error Rate) = (S + D + I) / N,其中 S 是替换、D 是删除、I 是插入、N 是参考词数。等价于词级 Levenshtein 编辑距离。越低越好。WER 高于 20% 一般不可用;低于 5% 对朗读语音达到人类水平。2026 年标准基准:
| 模型 | LibriSpeech test-clean | LibriSpeech test-other | 规模 |
|---|---|---|---|
| Parakeet-TDT-1.1B | 1.40% | 2.78% | 1.1B |
| Whisper-Large-v3-turbo | 1.58% | 3.03% | 809M |
| Canary-1B Flash | 1.48% | 2.87% | 1B |
| Seamless M4T v2 | 1.7% | 3.5% | 2.3B |
这些都是编码器-解码器或 RNN-T。纯 CTC 系统(wav2vec 2.0)在 test-clean 约 1.8~2.1%。
def ctc_greedy(frame_logits, blank=0, vocab=None): # frame_logits: 每帧的概率向量列表 preds = [max(range(len(p)), key=lambda i: p[i]) for p in frame_logits] out = [] prev = -1 for p in preds: if p != prev and p != blank: out.append(p) prev = p return "".join(vocab[i] for i in out) if vocab else out
两条规则:合并连续重复、丢 blank。例子:a a _ _ a b b _ c → a a b c。
设计要点:CTC 解码的两步看似平凡,却暗藏玄机——「合并重复」只在相邻同类时发生,中间若插了 blank,前后两个相同 token 就保留(如
a _ a→aa)。这正是 blank 的作用:它允许模型用 blank 把同一个字母的两次连续出现表达成两个独立字母。
def ctc_beam(frame_logits, beam=8, blank=0): import math beams = [([], 0.0)] # (tokens, log_prob) for p in frame_logits: log_p = [math.log(max(pi, 1e-10)) for pi in p] candidates = [] for seq, lp in beams: for t, lpt in enumerate(log_p): new = seq[:] if t == blank else (seq + [t] if not seq or seq[-1] != t else seq) candidates.append((new, lp + lpt)) candidates.sort(key=lambda x: -x[1]) beams = candidates[:beam] return beams[0][0]
生产代码用带 LM 融合的前缀树 beam search;这里只是概念骨架。
def wer(ref, hyp): r, h = ref.split(), hyp.split() dp = [[0] * (len(h) + 1) for _ in range(len(r) + 1)] for i in range(len(r) + 1): dp[i][0] = i for j in range(len(h) + 1): dp[0][j] = j for i in range(1, len(r) + 1): for j in range(1, len(h) + 1): cost = 0 if r[i - 1] == h[j - 1] else 1 dp[i][j] = min( dp[i - 1][j] + 1, dp[i][j - 1] + 1, dp[i - 1][j - 1] + cost, ) return dp[len(r)][len(h)] / max(1, len(r))
import whisper model = whisper.load_model("large-v3-turbo") result = model.transcribe("clip.wav") print(result["text"])
一行调用即得 2026 年最强通用 ASR,24 GB GPU 上约 20 倍实时。
from transformers import pipeline asr = pipeline("automatic-speech-recognition", model="nvidia/parakeet-tdt-1.1b") for chunk in streaming_audio(): print(asr(chunk, return_timestamps=True))
流式 ASR 需要分块编码器注意力和状态延续;用支持它的库(NeMo for Parakeet,带 chunk_length_s 的 transformers pipeline)。
2026 年工具栈:
| 场景 | 选 |
|---|---|
| 英语、离线、最高质量 | Whisper-large-v3-turbo |
| 多语种、鲁棒 | SeamlessM4T v2 |
| 流式、低延迟 | Parakeet-TDT-1.1B 或 Riva |
| 边缘、移动、<500 ms 延迟 | Whisper-Tiny 量化,或 Moonshine(2024) |
| 长音频 | Whisper + 基于 VAD 的切块(WhisperX) |
| 垂直领域(医疗、法律) | 微调 wav2vec 2.0 + 领域 LM 融合 |
⚠️ 四类仍在上线的坑:① 无 VAD——对静音跑 Whisper 会产生幻觉(「Thanks for watching!」),始终用 VAD 把关;② 字符 vs 词 vs 子词 WER——汇报前要先归一化(小写、去标点)再做词级 WER;③ 语种识别漂移——Whisper 自动 LID 会把嘈杂片段误判成日语或威尔士语,确定语种时强制
language="en";④ 长音频不切块——Whisper 窗口是 30 秒,任何更长内容都要chunk_length_s=30, stride=5。
本节产出技能文档(原课程 outputs/skill-asr-picker.md),针对给定部署目标选择模型、解码策略、切块方式、LM 融合。决策树:
基础:运行 code/main.py。它对一组手工构造的 CTC 输出做贪心解码,并对参考串算 WER。
进阶:正确实现第 2 步的前缀树 beam search(处理 blank 合并规则),在 10 条合成数据上与贪心解码对比。
挑战:用 whisper-large-v3-turbo 跑 LibriSpeech test-clean,对前 100 条算 WER,与公开数字对比。强制 language="en" 与自动 LID 对比,看漂移出现在哪里。
下一节,我们深入 2026 年最具统治力的 ASR 模型——Whisper,拆解它的编码器-解码器架构、多任务训练数据,以及如何在你自己的领域上微调。