Gen 1 跨度长达 25 年(1990–2014)——以论文范式为界,Gen 1 持续到 2014 年左右(工业界 Kaldi 混合系统到 2017-2018 年仍在生产环境占主导)。若只算商用 ASR,它是五代里持续时间最长的一代。从”能识别 16 个数字词”(1970s Bell 数字识别器)一路推到”办公室听写机 90% 准”(2010 年 Dragon NaturallySpeaking),支撑了整个前深度学习时代绝大多数 ASR 产品——iPhone 前 Nuance 电话客服 IVR、Windows 语音输入、Google Voice Search 早期。

这一代的核心哲学是”结构先验分而治之”——ASR 是个又难又结构化的问题,与其让一个模型端到端硬 argmax,不如把它拆成 3-4 个能独立建模的子问题:HMM 管音素状态怎么跳、发音字典管字怎么念、语言模型管句子通不通、WFST 管解码怎么搜。每个模块用当时可用的最优统计工具单独训,最后组合。

Gen 1 之所以能主导 25 年——不是因为效果最好是因为它足够”能拆”:可解释、可调试、可分阶段升级。GMM → DNN 也发生在这一代内部——2011-2012 年,DNN-HMM 在主流 LVCSR benchmark 上迅速证明优于 GMM-HMM,开始成为 Gen 1 后半段的主流方案,只替换了 GMM 建”发射概率”的角色,其他所有模块保持不动——发音字典、HMM 状态图、WFST 解码器全部不动。这就是为什么本系列把 GMM-HMM 和 DNN-HMM 合并成一代的两个阶段,不是两代。

一、Gen 1 的三段接力

Gen 1 的核心不是一个大模型,而是三段独立 pipeline——前端把波形变成 MFCC,声学模型把帧对齐到 HMM 状态,解码器用发音字典 + n-gram LM + WFST(加权有限状态转换器,Weighted Finite-State Transducer · 详见 §4.3)搜词序列。优点是可拆、可调、可替换代价是三段各训各的,每段 loss ≠ 最终 WER,误差沿 pipeline 累积。这就是 Gen 2 端到端要抛掉的架构。

二、前端:MFCC + CMVN

Gen 1 的前端跟 Gen 2-5 差别不大,主要区别是默认输入 MFCC 而不是 FBank——多一步 DCT。

2.1 波形级前处理(AEC / AGC / 降噪 / VAD)

这四件事都作用在原始波形上(时域采样点),在分帧加窗之前跑,Gen 1 时代都是独立的传统 DSP 模块(不进声学模型、写死在前端 pipeline 里)。常见工程顺序之一:AEC → 降噪 → AGC → VAD → 送分帧(具体顺序会因设备、场景、任务调整)。

  • AEC(回声消除)· NLMS / RLS 自适应滤波估回声路径 · 智能音箱 / IVR 必需
  • 降噪 · Wiener 滤波 · 谱减法(假设噪声加性平稳)
  • AGC(自动增益控制)· 把语音段能量拉到目标区间
  • VAD · 传统方案常用能量、过零率等规则特征(ETSI ES 202 050 是典型标准)· 工业上也常见 WebRTC VAD · 切出有效语音段

当代对照——今天的音频前端两条不同路径:(1) 前端本身走 NN 联合建模(DTLN / DeepFilterNet / AEC-NN 把降噪 + AEC + 波束合并成一个端到端模型);(2) ASR 主体是 NN 之后前端可以更薄甚至省掉,靠 multi-condition training / SpecAugment 让模型直接以有噪输入为输入。Gen 1 因为 GMM/DNN + HMM 对输入分布敏感,工程上必挂厚重的传统前端——本节仍按”传统四件套”讲。

2.2 特征提取:MFCC = FBank + DCT

FBank 与 MFCC 的关系——两者共享前 4 步(STFT → mel → log),MFCC 多一步 DCT。

FBank(Mel Filterbank · Gen 2+ 神经网络主流输入)——对功率谱做 mel 滤波 + log:

其中 是第 m 帧第 i 频点的 STFT 复数值, 是第 k 个 mel 滤波器在频点 i 的权重, 防 log(0)。

MFCC(Gen 1 GMM-HMM 标准特征)——在 FBank 之上再做 DCT:

其中 是第 k 个 mel 滤波器组的能量, 是滤波器个数(典型 40), 是保留的 MFCC 系数个数(典型 13)。

为什么 Gen 1 要 DCT——GMM 用对角协方差矩阵(假设特征各维独立)省参数,但 mel 通道相邻高度相关,直接送 GMM 会违反假设。DCT 把 mel 通道降低相关性后再送 GMM,让对角协方差近似更合理,参数量数量级降低。Gen 2+ 神经网络没有对角假设,DCT 被抛掉,FBank(40-80 维 log-mel)成为主流。

pipeline 一句话波形 → 分帧 25 ms + Hamming 窗 → STFT → mel filterbank (40 bins) → log ⇒ FBank → DCT → 取前 13 维 MFCC → Δ + ΔΔ → CMVN → 送 HMM。Gen 2+ 神经网络 ASR 的输入特征在 log-mel 处止步——一般不做 DCT / Δ / ΔΔ;CMVN 或均值归一化是否保留取决于具体 recipe。

Δ / ΔΔ 是什么——Δ 是 MFCC 的一阶差分(当前帧减邻近帧,刻画特征变化率),ΔΔ 是二阶差分(Δ 再做一次差分,刻画加速度)。13 维 MFCC + 13 维 Δ + 13 维 ΔΔ = 39 维/帧 是 Gen 1 GMM-HMM 标配。为什么要它:GMM 只能看到单帧、不知道相邻帧怎么变,只能靠把时间上下文手工塞进单帧特征补上。Gen 2+ 神经网络能自己看多帧上下文(TDNN、LSTM、attention),Δ / ΔΔ 就没意义了。

不关心实现可直接跳到下面的中间态图

import librosa
import numpy as np
from scipy.fftpack import dct

y, sr = librosa.load("audio.wav", sr=16000)

# 25 ms window, 10 ms hop, Hamming
stft = librosa.stft(y, n_fft=512, hop_length=160, win_length=400, window="hamming")
power = np.abs(stft) ** 2

# FBank: STFT power → mel → log
mel_filter = librosa.filters.mel(sr=sr, n_fft=512, n_mels=40)
mel_power = mel_filter @ power
fbank = np.log(mel_power + 1e-10)                       # 40 × T · Gen 2+ 输入

# MFCC: log-mel → DCT → 前 13 维
mfcc = dct(fbank, type=2, axis=0, norm="ortho")[:13]    # 13 × T

# Δ + ΔΔ + CMVN
delta = librosa.feature.delta(mfcc)
ddelta = librosa.feature.delta(mfcc, order=2)
feats = np.vstack([mfcc, delta, ddelta])                # 39 × T
feats = (feats - feats.mean(axis=1, keepdims=True)) / (feats.std(axis=1, keepdims=True) + 1e-8)

这段代码只展示 Gen 1 特征主路径:log-mel 得到 FBank,DCT 得到 MFCC,再拼 Δ/ΔΔ 并做 CMVN 归一化。

把上一节的英文 demo 灌进去,每级中间态可视化如下:

上图 6 个子图对应 pipeline 的每级中间态。各级形状(3 秒音频 · 16 kHz · hop 10 ms):波形 (48000,) → STFT (257, 301) → mel filterbank (40, 257) → mel 谱 & log-mel (FBank) (40, 301) → MFCC (13, 301) → 加 Δ + ΔΔ + CMVN 后 (39, 301) 送 HMM。

三、声学模型:HMM + GMM/DNN

核心难题——对齐:特征几百到几千帧,音素几十个,谁对谁?Gen 1 的解决方案是 HMM 状态图 + Viterbi 强制对齐

一句话主线——HMM 规定哪些状态序列合法(骨架),GMM/DNN 给每帧打分(打分员),Viterbi 在合法路径里搜总分最高的一条(搜索器);triphone / senone 是让”状态”更懂上下文发音的补丁。

3.1 一个问题:帧和音素怎么对齐?

设想识别 1 秒英文 cat——前端切成 ~100 帧 × 39 维 MFCC,但每帧属于 /k/ 还是 /æ/ 还是 /t/——没标签。Gen 1 的解决方案是先给出一张合法路径图,再在这张图上搜出最合理的对齐。

HMM 的一个关键设定——每个音素拆成 3 个状态b (begin · 起始)、m (middle · 稳态)、e (end · 结束)。之所以拆 3 段,是因为一个音素从发声开始到结束、声学特征本身在变(比如 /k/ 从爆破前静音 → 爆破瞬间 → 气流释放),用 3 个子状态各建一份分布比”整个音素塞一份分布”更准。

所以识别 cat 时,HMM 建的合法路径图是依次走过 9 个状态/k/-b → /k/-m → /k/-e → /æ/-b → /æ/-m → /æ/-e → /t/-b → /t/-m → /t/-e。每个状态允许自环停留若干帧(因为一段音素通常跨多帧),但顺序不能乱、不能回跳。下面这张图就是把这 9 个状态放在纵轴、100 帧放在横轴,搜路径。

读图指南——横轴是时间帧(图里为可视化画 60 帧;实际 10 ms hop 每秒约 100 帧),纵轴是 9 个 senone(/k//æ//t/ 各三态 b/m/e)。背景蓝色深浅 = GMM/DNN 给”帧-状态”格子的分数红线 = Viterbi 找到的最优对齐路径。沿红线回读得到音素序列 /k/ /æ/ /t/,查发音字典拼回单词 cat。识别就完成了。

3.2 三个角色:骨架、打分员、搜索器

角色是什么输入输出
HMM(骨架)一张状态图 + 转移规则合法状态序列(如 /k/-b→m→e → /æ/-b→m→e → /t/-b→m→e)· 每状态可自环停留
GMM / DNN(打分员)每帧的概率打分器一帧 MFCC 每个 senone 的分数
Viterbi(搜索器)动态规划算法HMM 图 + 每帧打分最优对齐路径(哪些帧属于哪个 senone)

HMM 三件套——状态空间 + 状态转移 + 发射概率。状态和转移是骨架结构(本节),发射概率的具体建模器(GMM 还是 DNN)留 §3.4。

  • 状态:每个音素 3 个——b (begin · 起始)、m (middle · 稳态)、e (end · 结束) · Bakis 拓扑(左到右不回跳)
  • 转移 :每帧只能自环(概率 ,停在当前状态)或前进(概率 ,跳下一状态)。HMM 的时长建模能力弱——左到右 + 自环的结构约束很强,但具体转移概率在不同系统里做法不一:可能被数据估计、平滑、或固定简单先验;工程上主要靠发射概率 + WFST 里的语言模型分数补
  • 发射 :GMM 或 DNN 建的每帧打分(§3.4 展开)

联合概率

Viterbi 就是在这张图上做动态规划——每一步只保留到该状态的最大累积分,最后回溯得到最优路径。训练时也可以用 Viterbi 做硬对齐(把每帧分给一个状态)或用 Baum-Welch / forward-backward软对齐(每帧对每个状态给一个占用概率);不同训练阶段可能采用 Viterbi 对齐或 forward-backward 统计。

Viterbi 具体怎么算——先看一个简化版(3 帧 × 3 状态 · Bakis 拓扑只能自环或前进)。实际系统里是数千 senone × 数百帧,但 DP 原理完全相同:

状态t=1t=2t=3
S10.70.14(S1 自环)0.021
S200.21(S1 前进)0.053(S2 自环 · 打败 S1 前进的 0.035)
S3000.0735(S2 前进)✅ 全局最优

关键操作在 t=3 到达 S2 时:候选 1 “从 S2 自环” = 0.053,候选 2 “从 S1 前进” = 0.035,只保留大的、扔掉小的——这就是 Viterbi 的核心 max。回溯指针得到最优路径 S1 → S2 → S3

为什么这样能算对——HMM 马尔可夫性保证”最优路径的每一段前缀本身也是最优的”(optimal substructure),所以每帧只需 O(状态数²)、整段 O(帧数 × 状态数²) 就够——比枚举全部 状态数^帧数 条路径快指数级。在 §3.1 cat trellis 图里,红线就是这套 DP 跑完之后回溯出的最优路径

3.3 从 monophone 到 senone:让状态懂上下文

协同发音——同一个 /a/b-a-tm-a-n 里发音差别很大。孤立音素(monophone)会把两种分布糊在一起。

解法——把”当前 + 左右邻居”打包成一个 triphone 建模单元。b-a+tm-a+n 是不同的 triphone,各建各的分布。

问题——单元爆炸:40 个音素 × 40² 上下文 × 每音素 3 状态 ≈ 20 万个上下文相关状态(工业界叫 senone)。在典型训练数据规模下(几百到几千小时),这么多独立单元根本训不出可靠的 GMM 参数。

收敛——决策树聚类:按语音学问题(”当前元音吗”、”左邻塞音吗”、”右邻鼻音吗”)分裂,把发音相近的合并共享参数,压到 3000-8000 个 tied senone——数据够训了,还保住”上下文敏感”。

Kaldi 的 tri1 / tri2b / tri3b 就是逐级细化 triphone 建模的三个阶段(用 delta / LDA / SAT 等特征)。Gen 2 的 CTC / attention 抛掉这套的原因——NN 自己就能在序列层面建上下文,不需要在建模单元里手工塞。

3.4 GMM 换成 DNN,只换打分员

HMM 骨架 + 转移概率 + 发音字典 + WFST 全都不动——Gen 1 内部从 GMM-HMM 到 DNN-HMM 的换代,只换发射概率的建模器

GMM(1990-2010)DNN(2012-)
建模对象生成式 判别式 · 贝叶斯反演 送 HMM
输入单帧 39 维 MFCC上下文 11 帧 × 40 mel = 440 维
参数共享每 senone 独立 8-64 个对角高斯所有 senone 共享 5-7 层 DNN 主干
表达能力混合高斯叠加 · 对角协方差限制强非线性多层 · 高维流形
LibriSpeech-960h + tgsmall LM · test-clean WER · 早期 DNN-HMM(2012-2014)——~5%(示意)
LibriSpeech-960h + tgsmall LM · test-clean WER · Kaldi chain 集大成(2015+)tri3b baseline ~6-7%(示意)~3.9%(示意)

DNN 更强的三点:① 一次看 11 帧上下文(GMM 只 1 帧)· ② 所有 senone 共享 DNN 主干(GMM 每 senone 独立)· ③ 多层非线性够到 GMM 单峰叠加够不到的高维分布。

关键约束——DNN 要靠 GMM 引导对齐:DNN 训练需要”每帧属于哪个 senone”的硬标签,标签来自 GMM-HMM 的 Viterbi 对齐。所以 DNN-HMM 不是独立一代,是 Gen 1 内部的第二阶段——先跑 GMM-HMM 拿对齐,再用对齐训 DNN。Kaldi 后期把 DNN-HMM 推到极致的 chain 系统(TDNN-F / LF-MMI)留 §五 讲。

四、LM + 解码器:发音字典 + n-gram + WFST

Gen 1 的第三段是四层结构静态编译:H (HMM) ∘ C (Context) ∘ L (Lexicon) ∘ G (Grammar) = HCLG 解码图

4.1 发音字典 L:词到音素的人工桥

发音字典 L 规定每个词怎么拆成音素。HELLO 展开成 HH AH0 L OW1WORLD 展开成 W ER1 L D;如果 KOKORO 不在字典里,典型词级闭词表系统里 WFST 就没有通向这个词的路径,解码器再怎么搜也搜不到它(部分系统会挂 <unk> token、G2P 后处理或子词 fallback 缓解,但字典外的词仍是 Gen 1 硬伤)。OOV 不是”模型不聪明”,而是搜索图里根本没有这条路

发音字典的三个致命问题

  1. OOV 死角:新词、专名、缩写不在字典就废(GPT / DeepSeek / TikTok 每天在冒)
  2. 多音字消歧:中文”重”读 zhòng 还是 chóng · 英文”read”过去时/现在时读音不同——字典给不出上下文相关选择,得挂 POS 标注 + 消歧规则补丁
  3. 方言/口音:同字不同地区念法不同——每方言一套字典,工程量爆炸

发音字典的规模

语言词条数标注方式
英文 CMU dict13.5 万人工 + LTS 规则
中文发音词典可达百万级人工 + 拼音表

这决定了 Gen 1 多语种扩展成本极高、长尾语言覆盖困难。Gen 2 抛 HMM 骨架的最大红利之一,就是同步抛掉发音字典——char 输出基本消除词级 OOV(char 本身仍有 Unicode 未收录字符的边界);BPE 也能用子词组合覆盖长尾词,显著缓解 OOV

4.2 n-gram 语言模型 G

基于计数的统计模型

必须加平滑(否则未登录词概率为 0): - 加一平滑 · 古德-图灵 · Kneser-Ney(工业主流)· Witten-Bell

举个经典例子——recognize speech vs wreck a nice beach 是 ASR 领域教科书级的同音陷阱:声学上两条候选几乎无法区分,全靠 LM 消歧。

WFST 综合每帧声学分和每段 LM 联合分数,最终倾向输出前者——这就是 n-gram LM 在 Gen 1 pipeline 里的角色:把声学模糊的多条候选按”语言常识”(其实是训练语料里的共现频率)排序,选出联合概率最高的一条

Gen 1 时代典型:4-gram · 词表 5 万 · 内存 100 MB - 1 GB · 训练数据 Wikipedia + 新闻 + 书籍。Gen 2+ 的 neural LM(Transformer / GPT 类)能建更长的上下文依赖,n-gram 因为参数按 爆炸只能停在 4-5 阶。

4.3 WFST HCLG 解码图

核心思想——把 H · C · L · G 四层预编译成一张巨大的静态状态机,Viterbi 一次搜到底:

四层含义(对照图左列,从 senone 序列走到词序列):

  • ① senone 序列——打分员输出,每帧一个 senone 编号(0-8000)。senone = “tied (左邻音素, 当前音素, 右邻音素, 状态)” 的代号
  • ② context-dep triphone(H 转后)——把 senone 编号翻译回”哪个 triphone 的哪个子状态”,如 sil-k+æ:b(前 sil、后 æ 的 /k/ 起始态)
  • ③ monophone(C 转后)——扔掉上下文标记,只留当前音素本身 /k/ /æ/ /t/
  • ④ word(L 转后)——查发音字典把音素序列拼成词 /k/ /æ/ /t/ → cat
  • ⑤ weighted word sequence(G 转后)——n-gram LM 给词序列打分,"the cat sat" P=0.023 vs "the cab sat" P=0.0002

拆 4 层的好处——每层独立映射:发音字典换了不影响声学模型LM 换了不影响字典。WFST 的魔法在于把 4 层预编译成一张静态图(HCLG.fst),Viterbi 一次搜到底、运行时不用逐层查表。

HCLG 图规模: - LibriSpeech-960h + 3-gram LM 的 HCLG.fst 约 数百 MB 到 1 GB - 包含 数千万状态和边 - 压缩靠 determinize + minimize + LM pruning

Viterbi + Beam 剪枝在这张图上搜路径——理论上 Viterbi 在完整 HCLG 上可求全局最优路径,工业实现必挂 beam / lattice pruning 控制内存和时延,实际得到的是剪枝后搜索空间里的近似最优

WFST 是 Gen 1 工程精华,至今仍在工业系统里跑——Kaldi 生产系统里 WFST 还在用;k2(面向 PyTorch/autograd 的 FSA/FST 工具库,跟 Kaldi 生态关系很近但不是 Kaldi 的 PyTorch 版本)把 WFST 搬进现代 NN 训练框架;Gen 2/3 有些系统仍挂外部 LM 做融合——WFST rescoring / shallow fusion / cold fusion 都是常见路径。

五、代表工具链:Kaldi tri3b + chain

Gen 1 三个代表工具链,几乎覆盖所有商用 ASR:

工具链时期语言特色
HTK1989-2015C剑桥出品 · 学术标准 · 源码可申请/可下载,但 license 限制较强
CMU Sphinx1988-2010C / Java卡内基梅隆 · Sphinx-4 是 Java · PocketSphinx 是 C 嵌入式 · 完全开源
Kaldi2011-C++Dan Povey 主导 · 至今工业主流 · Gen 1 集大成

Kaldi 主导 Gen 1 后半段——C++ 高性能 + Bash recipe 极简 · WFST 用 OpenFst 实现 · nnet3 训练框架灵活 · Kaldi recipe 长期是经典 baseline,仍常被用作传统系统对照

5.1 Kaldi recipe 阶梯(LibriSpeech-100h)

GMM-HMM 逐级细化 → DNN 集大成,每一级都用前一级的对齐作为初始化:

阶段建模单元关键特征 / tricktest-clean WER
monomonophone(40 音素)flat-start · 粗对齐启动~35%
tri1triphone + delta加入一阶时间导数~24%
tri2btriphone + LDA + MLLT线性判别降维 + 最大似然线性变换~17%
tri3btriphone + SAT说话人自适应训练 · GMM 最强配置~13%
chain TDNNsenone + TDNN-FLattice-Free MMI 序列级损失 · 帧率降 3×~5.5%

(示意数值 · 具体 WER 随 Kaldi recipe / LM / speed perturbation / rescoring 配置变化,这里只取量级对比 · Kaldi 官方 LibriSpeech RESULTS 按 recipe / LM / 数据集分列,不适合把单个数字当唯一基准。原文见 Kaldi repo egs/librispeech/s5/RESULTS

chain 阶段的三个技术亮点(Kaldi 后期集大成,不属于 2011-2014 早期 DNN-HMM): - TDNN-F 结构(Time-Delay + Factorized 空洞卷积 · 等效看 30+ 帧) - LF-MMI 损失(Lattice-Free MMI · 序列级鉴别性 · 类 CTC 但保留 HMM 骨架) - 帧率降 3×(100 Hz → 33 Hz · 推理快 3×)

recipe 命令细节(steps/train_mono.shsteps/train_deltas.shlocal/chain/tuning/run_tdnn_1a.sh 等)可从 kaldi/egs/librispeech/s5 直接读取,本文不列。

5.2 推理示例:识别统一 demo

拿 Kokoro TTS 合成的中英混合样本 demo.wav 灌进 LibriSpeech-960h + chain TDNN 训好的模型解码:

内容
原文本"Hello world, welcome to Whisper 大模型评测, today's WER is 1.5%, thanks!"
识别输出HELLO WORLD WELCOME TO WHISPER
备注只识别出英文部分;Whisper 之后的英文 大模型评测 是中文,Gen 1 英文 pipeline 直接跳过;today's WER is 1.5% thanks 也没识别出——LibriSpeech 训练语料是朗读体,跟带数字/符号的自然口语分布不一致

这段 demo 暴露了 Gen 1 的一个核心短板——每语种需要独立训一套完整 pipeline(AM + LM + Lexicon),无法一模型多语种。当然,用中文 AISHELL 训过的 Kaldi 模型跑中文部分不会崩,但那是另一套完整的 pipeline;同样,Gen 2 英文模型跑中文也会崩——只是 Gen 2 用一个模型多语种共训的成本远比 Gen 1 低。

六、Gen 1 的能力边界 · 以及 Gen 2 从哪四个点接管

Gen 1 擅长——封闭词表命令词(WER 1-2%,示意 · 银行 IVR、智能音箱早期)、朗读体听写(LibriSpeech test-clean WER 5-6%,示意 · Dragon NaturallySpeaking)、单语种工业系统。核心优势是可拆可调,每段能独立优化和替换。

Gen 1 卡住——OOV 新词(GitHub / TikTok / GPT)字典赶不上;多语种需要每语种独立训一套完整 pipeline,无法共享 backbone;自然对话体(filler、快语速、口音)GMM 表达力弱、字典也覆盖不到;三段独立训练每段的 loss 都不等于最终 WER,端到端优化无从谈起。

Gen 2 的核心跃迁——同时抛掉这四个补丁(图下半部分四条竖直箭头): - 发音字典 L → char / BPE 输出(char 基本消除 OOV;BPE 用子词组合覆盖长尾词,显著缓解 OOV) - HMM 对齐假设 → CTC 的 blank + 路径积分 / attention 的隐式对齐 / RNN-T 的 2D lattice - 三段独立训练 → 端到端一个网络训一个 loss - n-gram 静态 LM → neural LM + shallow fusion(外部 LM 也走 NN)

Gen 2 讲的就是这四条替换路线在工业界的分工。

更多推荐