如果你正好在啃语音识别的经典教材或者课程大概率会在前几章就碰到 CTC 和 RNN-T 这两个名字。我当年第一次看到这两个缩写脑子里全是问号CTC 不是通讯技术么RNN-T 和 RNN 又差在哪后来在几个语音项目里真正把这两种模型都训练过、部署过、也踩过坑之后才慢慢把它们的来龙去脉理顺了。这篇东西就是想把这块硬骨头啃碎从“为什么需要它们”讲到“训练的时候会发生什么”最后说说实际工程里怎么选型、怎么调尽量让看完的人能直接拿去跟同事讨论或者照着搭基线。先说清楚这东西适配谁看。如果你是要做语音识别系统、做家居助手、做实时字幕或者会议转写需要自己训模型这篇可以帮你把最常用的端到端建模方案摸熟。如果你只是调 API可能不需要自己训网络但理解 CTC 和 RNN-T 的区别也能帮你解释为什么有些 API 在流式场景下表现好、有些断句总是慢半拍。下面这些内容全是围绕语音识别里最核心的“序列到序列”建模展开的。1. 语音识别的核心矛盾两段不等长的序列怎么对上语音识别说白了就是给一段声音输出一段文字。但你随便拿一个句子去录下来用帧率提取特征之后语音帧的数量和文本里的字符数量完全不是一个量级。假设一个三秒钟的句子按 10ms 一帧提特征大概有 300 帧而这句话可能只有十几个汉字。让模型把 300 帧映射到 15 个字这中间谁跟谁对齐就是语音识别最绕不开的问题。在早年间业界主流的做法是用 HMM 来承担对齐这个任务。HMM 把每个音素建模成几个状态状态之间允许跳转然后用维特比算法在训练数据上自动算出每一帧对应哪个状态。这套框架和深度神经网络结合之后变成 DNN-HMM 混合系统在很长一段时间里都是工业界的主力。但它的麻烦在于整个流程太复杂你需要音素词典、需要上下文相关状态聚类、需要决策树绑定还要单独训练语言模型任何一环出问题整个识别链条都会受影响。端到端模型想做的事情就是把这套复杂的对齐机制从显式设计变成隐式学习。模型自己决定某一帧对最终输出的哪个单元贡献更大不需要你手工设计“每个音素由三个状态构成”这种先验。而实现隐式对齐最经典的方案一个是 CTC一个是基于注意力的编解码结构另一个就是 RNN-T。这三个方案各占一条路也各有各的脾性。如果打一个比方对齐问题就像两个人要合作翻译一句很长的话一个人在朗读一个人在记录。CTC 的做法是让朗读的人自己加很多“停顿和空拍”记录的人只需要按顺序把听到的字写下来RNN-T 的做法是记录的人每写完一个字会轻声“嗯”一下告诉朗读的人继续念下一段。后面你会看到这两个比方其实非常贴近两者的数学设计。2. CTC用“允许跳过的对齐路径”绕开繁琐的逐帧标注CTC 全称 Connectionist Temporal Classification翻译过来是联结主义时间分类。它最早是 2006 年提出的专门用来解决时序分类问题后来在语音识别领域被大规模验证。它的核心想法并不复杂既然我们不知道每一帧对应哪个字符那就把所有可能的对齐方式都考虑进来然后让模型去最大化所有合法对齐路径的总概率。2.1 引入 blank 符号为什么是 CTC 最关键的一步CTC 有一个在刚接触时比较容易懵的设计——引入了一个名为 blank 的额外符号。它不是一个真的字符而是表示“当前这一帧不输出任何内容”。为什么需要它因为语音帧数远多于字符数中间必然有大量帧是同一个字符的持续发音。如果一个字符持续了十帧你怎么知道这十帧对应的是同一个字符而不是十次重复CTC 的方案是允许同一个字符连续输出并且中间用 blank 隔开。比如“hello”这个词假设语音里“e”这个音拖了 8 帧一种合理的对齐路径可能是h h blank e e e e e e l l l o o最后把重复的相邻字符折叠再把 blank 删掉就得到h e l l o。注意这里有个细节识别目标里连续出现两个“l”在折叠时要保证它们之间一定有一个 blank。如果模型把两个 l 紧挨着输出折叠后只会算成一个 l。这就是 CTC 处理重复字符的方式相邻相同输出如果没有 blank 隔开就会被合并。训练的时候模型对每一帧都输出一个在所有字符加 blank 上的概率分布。整条路径的概率就是每一帧概率的连乘而最终损失函数是“所有折叠后能得到目标文本的路径概率之和”的负对数。听着好像要枚举无数条路径但实际可用动态规划高效计算前向变量 α 和后向变量 β 就是用来做这件事的。这也是 CTC 经典的前向-后向算法跟 HMM 里的前向-后向思路一脉相承。2.2 CTC 训练时你会在日志里看到什么我印象最深的是第一次训练一个 CTC 语音模型观察训练 loss 的下降曲线和最终输出的对齐结果那种感觉非常奇妙。训练初期网络几乎把所有帧都输出成 blank因为这样最安全——只要不犯错空白路径也能得到一定概率。随着训练推进输出的字符概率开始集中在一些特定帧附近形成了很明显的“尖峰”。这里有个典型现象**学习率调太高时对齐尖峰可能过早定型后期想纠正就很难。**比如一个词的重音位置偏向中间如果早期模型把它对齐到了开头后面无论怎么训它都很难把概率挪回去。CTC 的这个特性让它在训练时需要相对保守的学习率策略尤其是大规模数据训练时。另外CTC 在做推理时有几种解码方式。最简单的是贪心路径合并每帧取概率最大的输出合并重复、去掉 blank。这个方式快但容易出错尤其在没有语言模型纠正时同音的错字会比较多。更好一点的是束搜索它不是只保留一条路径而是同时维护多条候选序列考虑前缀合并再加上语言模型分数这是 CTC 在工程上比较实用的解码方式。我在项目里常用的是在 beam search 里加一个词级语言模型做浅融合能明显压低词错误率。2.3 CTC 的“条件独立假设”为什么被吐槽最多CTC 最大的理论缺陷是它假设各帧输出之间相互独立。也就是说模型在判断当前帧输出什么时不能依赖它上一帧刚输出了什么。这在语音识别里很不自然因为语言是有依赖性的前面已经说了“今天天”后面更可能是“气”语音上也会有对应唇形和发音的协同发音现象。CTC 没有建模这种输出端依赖它更多是靠一个外部语言模型在解码阶段弥补。这就导致了 CTC 的另一个实际问题**它对词汇和语法的建模能力偏弱。**如果你在纯中文普通话数据上训练不带任何语言模型你会看到识别结果经常出现声学上很像但语义上狗屁不通的句子。它不是没听清而是没有机会利用“哪个词跟哪个词更常搭配”的知识。为了缓解这个毛病工程上几乎都会在解码时外挂一个语言模型把语言模型得分和 CTC 声学得分按权重相加。3. RNN-T把“声学模型”和“语言模型”喂到一个联合网络里RNN-T 全称 Recurrent Neural Network Transducer这个名字里带着“换能器”的叫法。设计它的人很明显是为了解决 CTC 没有输出依赖的问题。核心思路是在 CTC 的框架上再加一路“已经预测出的文本信息”让每一帧的决策不仅看声学特征还看到目前已经生成了什么内容。这样系统的行为就更像一个会边听边想的人。3.1 三个子网络各司其职Joint 层负责融合RNN-T 的总体结构由三个部分组成。首先是编码器也就是声学编码网络作用跟 CTC 里的声学网络一样把语音特征变成高层表示。然后是预测网络它接收已经输出的 token 序列负责建模“基于前面已说了什么下一个词大概该是什么”。最后是联合网络把编码器输出和预测网络输出组合起来得到最终在输出词表上的概率分布。联合网络内部常见的做法是把两个输入向量相加经过一个非线性变换再经过一个线性层输出 logits。有的实现是拼接有的是相加实验上相加的效果通常不错而且参数更少。但这个融合不是简单地两个特征叠加它需要学习“声学证据”和“语言先验”之间如何妥协。比如预测网络觉得下一个字大概率是“的”但是声学信息明显更像“地”此时联合网络就要根据发音差异来裁决。这个裁决过程是 RNN-T 最核心的学习内容。预测网络本身也很讲究它通常是一个单层或者两层的循环神经网络输入是已经输出的 token 的 embedding第一个输入是特殊的句子开始符号。这个网络的目的不是做完整的语言模型而是做一个轻量级的、逐字的语言先验生成器。工程上为了控制延迟预测网络的层数和隐层大小都不会太大否则解码时每步要多算一次前向会拖慢实时率。3.2 RNN-T 的损失函数怎么算一张 T×U 的动态规划表RNN-T 的训练是另一个层面的动态规划。CTC 算的是“帧序列上所有对齐路径的概率总和”RNN-T 算的是“帧序列和对齐路径同时推进的概率总和”。具体到实现上你会看到一张 (T1)×(U1) 的网格T 是声学帧数U 是目标 token 数。从左上角走到右下角每一步要么“消耗一帧”输出 blank表示继续侦听声学要么“消耗一个 token”输出一个预测的字符。每条从左上到右下的路径就是一种对齐方式RNN-T 的 loss 就是所有合法路径概率和的负对数。这个动态规划在训练时比较折腾因为每个 mini-batch 里样本的 T 和 U 都不一样。用得比较多的优化是提前算好所有 t、u 位置的 logits拼成一个 [batch, T, U, V] 的大张量然后再用 GPU 上的并行扫描算法做前向-后向。这么做能大幅减少计算次数但显存开销不小。我试过对中文词表V 大概几千字一个 batch 16 条音频每条 500 帧、30 个字这个张量也要好几个 GB所以实际训练时 batch size 常常比 CTC 小得多。用更小的 batch size 意味着梯度估计更不稳定这对学习率的设置和梯度裁剪的要求就更高步子太大会直接训飞步子太小又半天不收敛需要多试几次才能找到平衡点。RNN-T 推理时也很有意思。它逐帧推进每一帧上模型会不断输出字符直到输出一个 blank才认为“当前帧已经榨干了信息”转到下一帧。这种机制天然是流式的只要系统一边拿音频一边逐帧解码。这也是 RNN-T 在语音交互场景里大受欢迎的原因之一。3.3 预测网络的梯度问题怎么解RNN-T 训练中一个隐藏难点是预测网络的梯度。因为预测网络是自回归的每一步输入依赖前面生成的内容误差信号在时间步上传播梯度很容易消失或者爆炸。更麻烦的是在动态规划的计算里预测网络的输出不仅影响当前位置还通过多条路径传播这让它的梯度信号看起来特别不直观。业界常见的处理方式是给预测网络单独设置学习率通常比编码器的学习率低一截或者配合较大的梯度裁剪阈值。我在实测中发现如果不加约束预测网络很容易在训练早期就过拟合到几个高频词上比如中文里的“的”、“了”导致模型只要声学稍微模糊一点就偏着头往高频词上猜。所以处理这个问题的建议是踩坑之后果断加上 label smoothing让预测网络的输出不要那么尖效果会稳很多。另外RNN-T 在工程上还有一个版本叫 RNNT-T 的“无递归预测网络”比如用 embedding 累加替代 RNN目的就是绕开梯度传播和推理延迟问题。我在做低延迟项目时用过这种变体效果跟标准 RNN-T 差别不大但推理时少算了一个循环网络实时性提高很明显。4. 注意力机制、CTC、RNN-T 三条路线到底该站哪边除了 CTC 和 RNN-T还有一条非常流行的路线是基于注意力的编码解码结构也就是常说的 LAS 或者 attention-based encoder-decoder。它跟前面两个方案的区别在于解码每一帧对应什么字符时采用注意力机制去“回看”编码器输出而不是像 CTC 那样机械地对每一帧对齐也不是像 RNN-T 那样逐帧盲目推进。注意力模型天然能学到一种较灵活的软对齐从而在整句建模上往往有优势。但注意力模型有个致命问题——它通常是面向离线场景设计的。标准的注意力计算需要看到整段音频的 encoder 输出解码第一个单词时就要把所有帧的表示都准备好。即便使用限制单调注意力的变体流式化也会牺牲部分精度并且实现复杂度明显高于 RNN-T。实际项目里见过不少团队在“要不要用 attention”这件事上摇摆。如果你的产品是会议转写、录音文件识别这种不要求实时出字的场景注意力模型的效果通常很好。但如果是对话式语音助手、实时字幕这类的产品RNN-T 才是更顺手的工具。拿我训练过的几个模型放在一张表里看差异会变得更清楚维度CTCattention encoder-decoderRNN-T对齐方式帧独立隐式对齐靠 blank 分隔注意力软对齐全局可回看逐帧推进 语言预测联合对齐是否建模输出依赖否条件独立假设是解码器自带自回归是预测网络建模语言先验流式识别支持支持但精度受限差需要特殊改造好天然逐帧解码复杂度较低beam search 简单较高attention 一步步生成中等需处理非单调前缀路径对语言模型的依赖特别依赖外部 LM较弱解码器内置语言模型中等浅融合外部 LM 仍有效训练资源开销低高中高T×U 网格计算较贵这张表里最值得琢磨的是“对语言模型的依赖”这一行。CTC 那行我特别标了“特别依赖”因为它真是一点不建模输出端依赖纯声学模型。而 attention 模型虽然在训练时见过大量文本但遇到专业领域词汇和生僻表达时仍然可能因为解码器自身的语言先验不够强而吃亏。RNN-T 的预测网络给了它一定语言建模能力但这只是一个浅层模型离一个上 GB 参数的语言模型还差很远所以工程上一般还是要做浅融合。还要提一句很多人把端到端语音识别和“不需要文本数据”挂钩这是个误解。CTC 和 RNN-T 训练依旧需要大量配对好的语音文本数据而且需要做大量数据清洗、文本规范化比如把数字转成汉字、英文词转成音素序列这些前置处理往往比模型结构选择更影响最终效果。我接手的项目里数据清洗阶段经常占掉一半的时间这部分工作看起来不起眼但做好之后模型效果能涨几个点做不好后面全白干。5. 工程落地中比算法更磨人的几个问题很多人在读 CTC 和 RNN-T 的理论时觉得头头是道但真正上手训练和部署就会碰到一堆论文里不写的小麻烦。我在这部分把近期踩过的坑、调理好的方案集中说一下都是会直接影响上线质量的东西。5.1 训练稳定性怎么防止 loss 突然变 NaNRNN-T 的 loss 计算涉及大量指数和对数运算数值稳定性是个大坑。早期的实现里经常在序列很长、batch 较大时出现 loss 变 NaN 的问题。正常人第一反应是降低学习率但有时候降低学习率也不管用因为问题出在对数域和指数域的转换精度上。标准解法是在计算动态规划时全都在 log 空间进行用 log-sum-exp 而不是先取指数再求和。很多深度学习框架的 loss 实现已经内置了这个处理但你如果自己写过 RNN-T loss十有八九会遇到这个问题。另一个常见技巧是梯度裁剪设为全局范数裁剪阈值大概在 1.0 左右对 RNN-T 来说算一个比较安全的默认值。如果还想更稳可以对预测网络单独设置更小的学习率比如编码器的 0.5 倍或者 0.3 倍这个方案在很多开源代码里也能看到。5.2 推理延迟预测网络是流式解码的隐形瓶颈RNN-T 看起来是流式的但真正做实时解码时你会发现除了声学编码器预测网络每次生成一个非 blank token 都需要做一次前向计算。如果你的预测网络是一个双层 LSTM这一步就点亮了每出一个字就要跑一遍 LSTM而 LSTM 的时间步依赖导致它没法并行。偏偏在交互式场景里用户每说一句话可能产生十几个字每字都跑一次 LSTM延迟就被拉上去了。解决思路有几个。先看你是追求极限延迟还是追求精度。延迟敏感的可以把预测网络改成纯 embedding 累加结构或者用 qRNN 这类简化循环层。不太敏感的可以在解码时做局部束搜索用一定的计算量换取准确率。实际工程里还会用到一个技巧就是限制每一帧最多输出的 token 数量比如每帧最多出一个字否则容易出现“一帧连出五个字”这种罕见但会让用户明显感到卡顿的路径。5.3 中文场景的特殊性分字还是分词中文语音识别有个绕不开的分叉口建模单元用字还是用词。CTC 和 RNN-T 在建模单元上都有点挑食。用词做建模单元词表会特别大而且 OOV 问题严重遇到人名地名就抓瞎。用字做建模单元词表很小但模型输出的文本没有一个自然分词边界后续的标点恢复、语义理解都得再想办法。我最后跑通的做法是字建模加句级语言模型浅融合。声学模型输出汉字序列然后解码时用一个基于词的语言模型去打分数这样既控制了词表又能在解码阶段利用词级别约束。也看到一些团队直接基于子词建模但中文里子词切分方法不像英文 BPE 那么普及相对小众。如果你做的是中文项目一开始就按字建模后面接标点和逆文本正则化是比较稳妥的路径。5.4 WER 降低和主观体验不一定同步这是很多团队容易忽视的点。CTC 和 RNN-T 的优化目标是词错误率或字错误率但这个指标和你用户的主观体验并不总是一致。典型情况是模型把一个四字成语说错了一个字WER 是 25%但用户完全看不懂另一句长语音有一个助词没识别对WER 是 5%用户却毫不在意。前者才是真正让用户觉得“这识别好差”的场景。因此在调模型时除了盯着 WER还必须盯着关键语义错误率。实践中可以在测试集里专门标注命名实体和核心槽位看看这些位置的准确率。如果 CTC 模型在这块表现明显低于 RNN-T往往不是因为声学差异而是因为它缺少语言先验同音字更容易选错。这时候与其改模型结构不如先加强解码阶段的语言模型权重拿特定领域的热词表做 biased beam search见效会更快。5.5 语言模型浅融合里的两个参数怎么调浅融合的一般公式是最终的得分 声学模型 log 概率 语言模型 log 概率 × 权重 惩罚项。这个权重直接决定识别结果更偏向声学还是语言。经验范围通常在 0.1 到 1.0 之间但具体值跟语料领域关系很大。如果你用的是通用领域文本训练的语言模型权重太高容易把音频里少见的词强行纠正成常见词导致凭空多出一些识别错误太低又等于没加。可以从 0.3 起试每次加减 0.1用一小块验证集跑一遍看整句准确率和关键实体准确率的变化趋势。另一个参数是“插入惩罚”或者叫“词数惩罚”它控制模型倾向于多输出还是少输出几个字符。在中文里这个尤其敏感因为同一个句子加不加“了”、“的”、“吗”都可能影响语义。如果发现识别结果总是丢尾字比如“今天天气很好”识别成“今天天气好”可以稍微调低惩罚鼓励模型多输出字符反过来如果经常出现多余的字就调高惩罚。6. 选型决策和个人经验总结到了真正要落地的时候选哪条路线其实没有一个万能答案。我把自己的场景梳理成几个决策条件如果产品是纯离线转写而且数据多、算力足attention 编解码模型常常能拿到最低的 WER如果产品是实时交互场景首选 RNN-T如果团队刚起步、希望在相对短时间内跑通一个可用的基线CTC 是最稳妥的入门选择。CTC 的代码实现、调参经验、并发解码方案都更成熟虽然在最高精度上有天花板但作为第一版产品完全够用后续再迭代到 RNN-T 也顺理成章。从端到端语音识别的发展脉络来看CTC 解决的是“机器怎么知道一帧对哪个字”这个问题RNN-T 在这个基础上补上了“机器怎么结合已说的内容预测下一个字”这半块拼图。理解它们的核心差异后再去看一些更时髦的变体无论是对比损失、基于 transducer 的流式模型还是大规模预训练语音模型自带的对齐机制都会觉得没那么玄乎了。最后想分享一个实操中比较铭心刻骨的教训。之前训练 RNN-T 时日志显示的 loss 一直在降但测试 WER 就是不见好后来排查发现是预测网络崩了它输出的 embedding 全部变成同一个向量联合网络基本退化成一个纯声学模型。当时在调试上花了两三周才发现是学习率过大导致的。从那之后我在训练日志里不仅看 loss还会定期把预测网络输出的 embedding 拉出来看分布检查离散度一旦发现分布坍缩就立刻降学习率。这个小习惯建议正在训练这类模型的朋友也保留一下。
阅读完成 · 觉得有帮助?