资讯动态

RNN-T训练深度笔记:从CTC对比到稳定收敛的实践指南

发布时间:2026/10/16 14:30:20 来源:尧图企业网站定制
语音识别Speech Recognition这套东西我在Chapter1-6之前已经跑过好几版baseline但真正静下心把RNN-T Training从头到尾捋一遍还是这一章的事。当时我以为端到端模型都差不多CTC、Attention都玩过直接照搬经验就行结果第一轮实验就给了我一个下马威RNN-T的loss曲线、训练节奏、甚至坑的形状和CTC完全是两个物种。这篇文章就是我在整理这一章时的完整笔记既有模型结构层面的拆解也有实际训练过程中踩过的坑和对应的调优思路适合那些刚接触序列转导类语音模型、想把手里的数据跑出一个稳定RNN-T baseline的读者。如果你已经在训练RNN-T但总觉得收敛不畅后面的监控和诊断部分应该能帮上忙。1. 先厘清RNN-T在整个语音识别光谱里的位置1.1 为什么不直接沿用CTC很多人第一次接触RNN-T心里都有个疑问CTC已经能处理变长对齐问题训练又简单为什么还要换要回答这个问题得先看清CTC和RNN-T在“序列转导”这件事上的根本分歧。CTC把音频帧和文本token之间的关系建模成“帧级条件独立”给定输入特征每一帧各自独立地预测一个标签包括blank标签。这个假设极大地简化了训练但代价是模型无法显式建模文本token之间的依赖关系。也就是说CTC模型在发音层面做决策但对“今天中午吃了什么”里面“吃”后面更可能接“了”还是“完”这类语言规律它只能靠外部语言模型在解码阶段补救。Attention-based encoder-decoder模型走了另一个极端它让解码器在生成每个token时回顾全部编码器输出文本内部依赖建模得非常强训练也友好。但问题是这个“回顾”通常是全局软对齐天然不适合流式场景边听边出结果时每一次注意力计算都要看到未来帧才能对齐当前位置这在工程上非常麻烦。RNN-T的定位恰好卡在两者之间。它保留CTC式的单调对齐约束——音频从左往右走输出文本也从左往右走不会出现回头重新对齐的情况同时在每个时间步上引入一个预测网络Prediction Network让模型在决定“这一帧要不要吐字、吐哪个字”的时候能看到已经生成过的文本历史。换句话说RNN-T在不需要未来上下文的前提下把文本序列内部的依赖关系嵌进了训练过程本身。1.2 RNN-T想解决的核心问题流式场景中的单调对齐与左上下文建模我用一个词概括RNN-T的设计哲学带着记忆的单调对齐。先看“单调”再细说“记忆”。语音转文本本身是个天然单调的过程一句话从第一个音节说到最后一个音节对应文本从左写到右不会中间跳回去重读。Attention模型其实没有这种硬约束它只学到一个“大概率单调”的软约束一旦遇到语速变化、噪声干扰对齐就容易飘。CTC虽然有单调性但它同时强迫每个时间步的决策互相独立模型说“这个音是‘想’”却不参考自己刚刚说过的“我”。RNN-T的预测网络就是专门来补这块的它是一个以“前一个非空输出token”为输入的循环网络相当于在音频编码器之外单独建了一条文本记忆通道。在联合网络Joint Network里音频信息和文本预测信息被揉到一起共同决定当前帧该输出blank还是某个token。这样设计带来的直接好处是录音没结束模型已经能稳定输出词因为决定“现在出不出字”只依赖左文信息和当前音频片段不需要看右文。这也是RNN-T在实时语音助手、会议转写这类场景里被大量采用的根本原因——它把“流式”和“序列内部依赖”这两件事同时做到了。1.3 这一章的实验规格与训练目标概览为了让后面的细节不悬空先交代一下我这一章搭建的基线系统规格。训练集是约三百小时的普通话语料音频统一做16kHz单声道、80维log-mel特征词表用字节对编码BPE切分到1024个subword。模型主体是Conformer Encoder加两层LSTM Prediction NetworkJoint Network用简单的“相加后过一个线性层”结构。优化器用AdamW峰值学习率1.5e-3warmup步数设成15k。目标也很朴素先跑出一个验证集上可用、训练过程稳定、没有明显退化现象的RNN-T baseline把整个链路打通再谈进一步调优。这一章记录的所有经验基本都来自这个系统反复“炸掉”又救回来的过程。2. Encoder、Prediction Network与Joint Network三个子网络如何协同2.1 Encoder特征序列的时间降采样与上下文建模Encoder负责把声学特征序列变成更高层级的表征。RNN-T论文里Encoder可以是任意序列编码器但几年前大家普遍用BLSTM后来几乎都换成了Conformer核心原因是Conformer在同样的参数量下能把局部音频特征和全局上下文兼顾得更好而且对GPU计算更友好。在实现细节上最影响训练结果的往往是时间降采样怎么设计。原始log-mel特征是每10ms一帧30秒的音频进来就是3000帧。如果直接把这么长的序列送进12层Transformer注意力矩阵会非常大训练速度肉眼可见地变慢。常见做法是在Encoder前端加几层卷积子采样把时间维降到原来的1/4或1/8。比如用两层stride2的卷积3000帧就会变成750帧左右后面无论是注意力计算还是RNN-T的动态规划算法压力都会小很多。这里有一个特别想强调的坑降采样不是你随便怎么下都行。我一开始用stride4的卷积直接下采样结果在语速快的句子上频繁丢字后来换成两层stride2并且每层后面都跟着一个轻量卷积做平滑效果明显好转。原因是激进降采样会把同一发音内部的细节过度压缩导致音素边界模糊。合理的递进式降采样让模型在每一层都能保留足够的局部判别信息。2.2 Prediction Network没有显式语言模型时的隐式语言建模Prediction Network是RNN-T区别于CTC的核心组件。它接收“前一个非空token”的embedding输出一个预测向量。别小看这个结构它的作用其实相当于在训练过程中内嵌了一个语言模型只不过这个LM是跟声学特征一块儿端到端学出来的而不是单独预训练再插进来。实现Prediction Network时最常规的选择是两层LSTMembedding维度和隐藏维度通常比Encoder的d_model小一到两倍。我用的配置是embedding维256、LSTM隐藏维320。第一层输入是上一个token的embedding初始状态是零向量在batch内按序列长度做动态处理。这一块最难理解、也最容易出错的是“前一个非空token”这句话。训练时正样本的token序列是已知的所以Prediction Network输入的就是标准文本序列的前一个位置这叫做teacher forcing。但在推理阶段模型并不知道最终答案Prediction Network输入的是自己前一步生成出来的token如果中途出了一个错字后面的预测就会跟着错下去。这种训练和推理之间的差异称为exposure bias在RNN-T里比在纯自回归LM里更隐蔽它不只影响看词准确率还会影响模型对blank的判断——一个被带偏的预测向量会让Joint Network更倾向于输出无意义的token或长时间沉默。2.3 Joint Network把音频信息与预测信息揉到一个格子里的关键层Joint Network是RNN-T所有信息汇聚的地方也是最容易被忽略出问题的地方。它的输入是两个向量Encoder对当前帧的输出h_t以及Prediction Network对前文历史的输出p_u。最常用的融合方式是直接把两个向量相加然后经过一个线性变换加tanh激活最后投影到词表大小加1的logits上。选择相加而不是拼接是一个值得说道的设计相加意味着h_t和p_u的维度必须完全一致我都用320模型被迫在两个表征之间共享相同的子空间信息融合更紧密拼接虽然能保留更多原始信息但需要Joint层有足够容量去学习两个空间的关系参数量也更大在小数据集上很容易过拟合。实际效果上相加结构在相同参数量下往往给出更稳定的WER所以我一直默认加法。Joint输出多出来的那个维度就是blank标签它的含义跟CTC里的blank完全不同。CTC的blank表示“当前帧没有有效输出跳过”RNN-T的blank表示“当前音频帧我已经处理完了但我不吐新的token继续走下一帧”。正是这个blank让一个时间步上最多一个token的约束成为可能——如果模型想连续输出“谢谢”两个字它可以在同一段时间内连续前进多个时间帧并在适当的帧位置吐出两个token而“适当位置”完全由模型自己学。2.4 三件套的典型参数表不同任务对三件套的规模需求不太一样但一个比较稳的baseline参数可以参考下面这张表模块典型配置备注Encoder12层Conformerd_model256FFN1024卷积核31两层stride2卷积做时间降采样总降采样率4Prediction Network2层LSTM隐藏层320embedding256输入为上一个非空token的embeddingJoint Network线性投影到256维tanh激活输出为词表数1使用加法融合Encoder和Prediction输出词表BPE1024-4096粒度对训练影响很大见下一章优化器AdamWlr1.5e-3warmup15k步超过峰值学习率容易退化成全部输出blank这套配置在三百小时数据上大概会有四千到五千万个可训练参数单卡训练时如果想跑得动batch内总帧数控制在2到3万帧左右比较合适。3. 训练数据、词表与特征收敛速度的隐性决定因素3.1 词表设计BPE的粒度选择直接影响Predict网络学习很多人在语音识别里把词表当成一个不起眼的预处理选项觉得用现成BPE就行。实际上RNN-T对词表粒度的敏感程度比CTC高得多原因在于Prediction Network要学的是“上一个token之后通常会接什么”。如果词表是纯字级别比如中文按单字切分模型要预测的下一步非常离散每个字的接续可能性很多LM部分学习压力大容易出现“想”后面接什么都行导致输出发散的现象。如果词表是整词级别罕见词问题会立刻暴露而且语音上很多常见词存在多音字情况整词级别的音韵映射不够灵活。BPE的好处是它介于两者之间把常见词拆成少数几个subword让模型既能记住常见搭配又能对罕见的拼读组合做出泛化。在切分英文数据时我通常把BPE词表从4000起步500小时以内的数据甚至不需要超过1024。切分中文数据时把文本先统一转成小写、去掉所有标点、数字按读音展开再做BPE会让训练稳定不少。只要文本里有标点残留Joint输出层就会浪费容量去学顿号和句号的读音这类token在语音识别里几乎不该出现。3.2 特征处理帧移、下采样、SpecAugment的标准组合RNN-T训练最稳的特征配置是80维log-mel25ms窗10ms帧移。这个配置和大多数公开语音识别系统一致模型能直接继承预训练Encoder的权重也容易跟别的实验做对比。要做全局的倒谱均值方差归一化CMVN我习惯用每个短句独立计算。说话人级归一化更精确但如果训练数据本身包含大量不同说话人句级CMVN反而相当于额外做了一层动态范围压缩对噪声鲁棒有好处。这里要注意CMVN系数要与音频同时计算不能直接把训练集统计量复用在另一个采样率或信道的数据上。数据增强方面SpecAugment已经是标配。我用的参数是频率掩蔽最大宽度30、时间掩蔽最大宽度40、掩蔽次数各自2次和很多语音识别工具包的默认值一致。但RNN-T训练时要特别留意时间掩蔽RNN-T的对齐本身依赖音频时间轴如果一段完整的音节被时间掩蔽整个遮住Encoder在那个区域几乎拿不到信息Joint网络却可能因为预测网络的惯性输出一个token造成“无中生有”的错误。我的经验是训练早期前几千步时间掩蔽强度可以暂时调低甚至只做频率掩蔽等blank和token的先验分布稳定下来再叠加时间掩蔽。3.3 动态batching与长度分桶实际收益非常明显RNN-T训练里一个很容易被忽视的效率瓶颈是padding。不同音频的时长差异可能大到两倍以上如果单纯按样本数量组batch最终计算的很大一部分时间是浪费在填充帧上的。更麻烦的是RNN-T的损失计算要对每个batch做一张 T×U 的动态规划表格如果batch里最长的样本是20秒最短只有3秒动态规划里会有一大片区域都是无效的padding轨迹。目前工业界和开源实现里最通用的做法是“时长分桶”。把所有训练样本按音频帧数排序然后每32或64条放一个桶桶内帧长接近padding量被压到最小。更进一步的做法是设置“每个batch最大总帧数”比如固定总帧数在3万帧左右短样本自动多放几条长样本少放几条这样每个batch的计算开销基本一致训练吞吐率能提高30%以上。3.4 数据清洗在文本一致性上抠细节这个部分最枯燥但也最值钱。同样的发音在训练集里如果对应了三种不同的写法RNN-T的Joint输出会被逼着同时建模多个候选对齐不确定性骤增。我在清洗阶段做的事包括把全角字符统一转半角把所有数字展开成对应的汉字读法把英文单词按字母逐个读或按常见发音读法统一删除所有的静音标记、咳嗽声标记和非语言事件token。还有一个容易遗漏的细节去重。训练集里如果有一批完全相同的句子被重复了好多遍模型会对固定文本产生强烈先验验证集一换就崩。按文本内容做一遍去重代价极小收益却不小。4. RNN-T损失函数的梯度来源前向算法的动态规划直觉4.1 从格子路线图理解对齐空间RNN-T损失函数是所有可能对齐路径的概率和理解它最好的方式是在脑海里面画一张格子图。横向是时间轴t从0到T对应音频帧纵向是输出token轴u从0到U对应一句话里的U个token。每一条从左上角(0,0)走到右下角(T,U)的单调路径代表一种“不到底有多少个时间帧分配给了每一个输出token”的解释。路径的走法有两种动作横向移动一个格子表示“当前帧处理完了输出blank跳到下一帧”纵向移动一个格子表示“在当前帧输出一个tokenu加一”。CTC的对齐路径里竖着跳的每一步都要对应到某个时间帧而RNN-T允许在同一个时间点连续输出多个token连续向上跳多格也允许在某段时间内什么都不输出连续向右走。正是这种宽松的路径空间让RNN-T能够自由学习说话快慢和音节长短。4.2 前向变量的递推与归一化RNN-T训练的核心是算所有路径的总概率这里用动态规划。定义前向变量α(t,u)为“处理到第t帧、已经输出u个token的所有前缀路径概率之和”。递推关系非常简单从左边来α(t-1,u) 之后在当前帧输出blank概率为 φ(t-1,u)从下边来α(t,u-1) 之后在当前帧输出第u个token概率为 y(t,u-1)。合并起来就是α(t,u) α(t-1,u)·φ(t-1,u) α(t,u-1)·y(t,u-1)边界条件里α(0,0)1第一行只能靠垂直方向填充第一列只能靠水平方向填充。递推过程中所有概率都会乘起来直接做浮点乘法很快就会下溢所以实际实现必须把概率投影到对数域用log域加法log_add来做加法。很多开源代码里对结尾的处理略有差异一种是在计算结束时强制让最后一个动作是blank即损失对应 α(T,U)·φ(T,U)另一种则直接把 α(T,U) 当作总概率。两种做法数学上是等价的因为可以在格子图的右侧额外补一列blank来“收尾”但看代码时如果发现这个细节不明不白会导致后期调loss时非常困惑。4.3 前后向算法对损失梯度的作用看到前向递推大部分人会觉得“这不就跟HMM的前向算法一样吗”。确实包括后向变量β(t,u)的定义也类似β(t,u)表示“从(t,u)状态出发走完剩余路径到达(T,U)的总概率”。前向和后向一碰就能算出每个格子的后验概率γ(t,u)也就是“真正的对齐最可能经过这个格子的概率”。为什么这对训练重要因为RNN-T没有逐帧的“标准答案”模型只能通过这个后验来软性监督每一个对齐位置。格子的后验概率高说明这个表格位置对最终预测的贡献大那么对应到Encoder输出的h_t和Prediction Network输出的p_u的梯度就大如果某个格子后验接近0模型就倾向于不修改那个位置的表征。这也就解释了为什么RNN-T训练初期会有“梯度浪费”的现象。模型刚开始时blank概率很高大量格子被blank动作占住后验分布特别平真正有效反映token依赖的格子梯度很稀疏。如果这时候学习率开太大参数会乱七八糟地更新损失曲线看起来像在被随机扰动。4.4 为什么RNN-T损失初期的数值变化看起来“慢”我在第一次训练RNN-T时看到loss从几百降到几十心里很高兴结果之后几千步几乎不动一度以为是bug。反复排查后才发现RNN-T的loss数值本身并不等价于错误率。它是对所有对齐路径求和后的负对数概率模型初期先学会的常常是把blank用对——因为大多数对齐路径里blank占了绝对多数降低blank路径的概率总能把loss降下来。而真正提高词序列准确率的“把正确token放在正确位置”的信号要到对齐分布相对集中之后才开始发力。所以训练RNN-T时盯着loss曲线是可以的但不要用loss绝对值评判模型好坏更不要拿它和CTC的loss比大小。我习惯同时记录每个batch的平均blank概率、平均token熵以及每隔一段时间把某个样本的对齐格子可视化出来。看到热力图上有几条清晰的对角带比loss降到多少更能说明问题。5. 训练稳定性几个反复踩过的坑与对应解法5.1 blank偏置一个润物无声的初始化技巧RNN-T训练头几千步最常见的问题是模型疯了一样地往外吐token随便一句话生成几十上百个字的胡话。原因在于Joint输出层的初始参数是随机的blank和非blank logits差不多大而RNN-T在训练早期又缺乏对齐先验于是模型倾向于在每个时间帧都输出一个小概率但非空的token久而久之把预测网络带进“不停说话”的沟里。解决办法非常简单在初始化时将blank对应的logit加上一个正的偏置比如3.0。这个操作让模型初始时显著偏向blank所有时间步都倾向于“什么都不说”然后再逐步学习何时该打破沉默。注意偏置不能太大如果初始blank logit比其他token高太多模型会很长时间学不会输出有效文本。我在实际中试过6.0结果前两万步几乎都在学“打开blank”WER迟迟不降。最后定在3.0左右比较好。5.2 学习率策略warmup之后别急着开大RNN-T对学习率的敏感程度比一般分类模型高得多。它不像图像分类那样模型预训练得很充分直接用一个固定lr跑就行。RNN-T的训练是从完全随机对齐开始的前期warmup如果不充分模型会快速收敛到一个“全部输出blank”的局部最优且很难再爬出来。我的配置是warmup步数设在10k到25k之间峰值学习率在1e-3到2e-3之间。重点是峰值之后采用倒数衰减inverse sqrt decay而不是阶梯式下降。阶梯式下降容易在某个边界处让已经在挣扎的RNN-T训练彻底崩溃。每次训练到一半如果发现WER不降但loss还在降先检查是不是学习率峰值太高。5.3 辅助CTC损失多任务训练的实际取舍给RNN-T加上辅助CTC损失是提升收敛速度和稳定性的常用招数。具体做法是在Encoder输出上再挂一个线性层单独计算CTC损失总损失变成L α·L_RNNT (1-α)·L_CTC我一般取α0.8或0.9CTC权重保持在0.1到0.3。CTC损失有一个好处它的帧级对齐比RNN-T更“硬”能给Encoder提供一个更直接的声学对齐梯度的来源帮助模型早期更快学会“这一帧对应哪个音素地区”的粗对齐。但也不能让CTC权重太高。CTC和RNN-T在建模粒度上存在冲突CTC训练出来的Encoder倾向于每个帧都做一个局部决策而RNN-T的Encoder需要保留一定的信息给Prediction Network去决定“要不要继续输出”两者的表征需求和梯度方向不完全一致。我试过等权混合结果RNN-T分支学的语言模型功能被明显压制重复输出问题反而增多。5.4 数值稳定性混合精度、梯度裁剪与难样本RNN-T的动态规划算法对数值下溢非常敏感。即便把概率取了对数log_add里的指数运算也容易因为两个对数差距过大而产生大量“吞掉小值”的行为。我的一套稳健配置是全部在log域运算log_add用max log1p(exp(-|a-b|))实现混合精度训练时让动态规划部分保持在fp32只在encoder和线性层用fp16否则loss会在几个batch之内爆成NaN梯度范数裁剪设5.0RNN-T的动态规划产生的梯度尺度非常不稳定尤其当batch里混入超长样本时。还有一个容易忽视的数值坑是样本长度。如果一个batch里混进一条60秒以上的长音频DP表格的时间维会变得巨大梯度反向传播经过这么长的序列后LSTM部分的梯度会消失或爆炸。我们在训练时会把超过25秒的音频随机切成20到25秒的短段并且让每一段都独立带文本标签而不是直接丢弃长音频。这样既保住了长句训练数据又稳定了梯度尺度。5.5 监控指标训练阶段比loss更有参考价值的是什么我在RNN-T训练早期吃过一次大亏loss降到一定程度后WER完全不动模型输出的内容全是同一个词的循环比如“嗯嗯嗯嗯嗯”。原因是对齐退化模型发现循环输出同一个token比正确输出不同的token更容易降低loss。这类退化发生在Prediction Network与Joint Network协同出错的地方很难单纯靠调学习率解决。有效的监控组合是监控项正常范围参考异常信号平均blank概率0.5~0.8之间长时间超过0.9可能退化成静音模型每batch token熵中等随训练下降突然飙升说明输出开始发散字级循环率同一token连续出现的batch占比很低持续出现是alignment退化前兆对齐热力图清晰对角带大块模糊区域提示数据或编码器问题一旦发现循环退化我的习惯是先加载最近一次WER还在下降的checkpoint把学习率降到原峰值的一半同时给Non-blank logit加上小幅label smoothing迫使模型不要把概率过度集中到一小撮高频token上。6. 从训练到推理解码链路与工程衔接6.1 贪心解码验证模型是否学起来的第一个检查点训练出一个看起来loss还在下降的模型后第一件事不是上复杂的beam search而是先用贪心解码跑几条验证集音频看看输出是否可读。RNN-T的贪心解码和CTC不一样它的状态是有记忆的解码器需要维护一个“已经输出的token序列”用于喂给Prediction Network。基本流程是Encoder一次前向推理全部音频帧得到h_1到h_T。Prediction Network初始状态为零初始“前一个token”设为起始符。从t0开始对每个时间帧计算当前h_t和当前Prediction输出拼接后的Joint logits。如果argmax是blank时间步加1输出端保持原状态。如果argmax是第v个token输出该token用它更新Prediction Network状态时间步也加1。这个流程跑起来很快适合在训练中做定期的快速检验。贪心解码效果不差尤其是训练数据相对干净时很多情况下贪心结果已经能当作一个还不错的baseline。如果贪心解码都大量重复或漏词那么beam search大概率也救不回来问题应该追溯到训练环节而不是解码环节。6.2 Beam search与预测量网络状态和CTC解码最大的不同如果想进一步压WER就要上beam search。RNN-T的beam search跟CTC的beam search有本质差别CTC解码时beam的内容可以简单地是一个输出序列因为每个时间步的扩展方式相对独立RNN-T的beam每个候选不仅包含token序列还包含一个Prediction Network的隐状态因为下一步的Joint计算取决于这个候选自己输出过的历史。工程上最朴素的RNN-T beam search可以这样理解用一个集合保存当前活跃的beam每个beam维护“当前时间步t”和“hypothesis token序列”以及对应Prediction隐状态。每个时间步对每个活跃beam除了可以选择空格子里的token之外还可以选择输出blank然后前进到下一帧。这里就出现了一个关键点选择blank只是时间步前进不产生新token如果所有beam都不在当帧输出token就集体推进到下一帧。为了防止beam数爆炸通常在每个时间步结束后做一次按累计分数的top-K裁剪K常见取10到30。一个特别要注意的细节是同一个hypothesis可能经由多条对齐路径到达分数应该累加而不是保留最大。这就是prefix合并当某个beam扩展出一个已经存在于另一个beam中的纯token序列时要把它们的概率分数相加。如果不做这一步模型内部存在多条等价对齐路径时beam search会产生重复碎片解码效果会明显下降。6.3 模型导出时的注意事项RNN-T的模型部署和普通分类模型不太一样。训练时我们把它当作一个整体端到端训练但到了部署阶段通常会拆成三个独立模块Encoder、Prediction Network、Joint Network。Encoder部分在流式场景里需要支持增量式计算音频持续进来Encoder要能对当前已到达的chunk输出表征而不是等全部音频收齐。训练时如果用非因果Conformer解码阶段贸然改成因果/流式结构表征分布完全不匹配WER会断崖式下跌。我的建议是如果你想部署成流式系统训练阶段就必须用因果卷积和掩蔽注意力模拟chunk流式输入哪怕每个chunk只能看左边有限的历史。Prediction Network导出时最麻烦的点是可变长度的隐状态。用固定步数trace模型时最容易踩的坑是隐状态形状被固定成了batch1、序列长度1。在线解码时一旦生成一个token就要把新的隐状态传回给模型。工程上我习惯把Prediction Network拆成“更新一步”的函数输入包括旧的隐状态和上一个token输出新的隐状态保证每一步只做一次完整的前向计算。6.4 常见WER评估偏差与文本后处理训练时我们拿到的文本标签一般是干净的但模型输出的结果里经常会有“呃”、“嗯”之类的填充词以及把常见字的儿化音、连读写得跟标准文本不一致的情况。如果直接拿原始输出跟标签做对齐算WER会发现错误率高得离谱但这不完全是模型的问题。我一般会先做一套统一的文本后处理删除所有非中文字符和括号把连续重复的字压缩比如“好好好好”变“好好”处理常见同音替换规则。评估时也把这套规则用在训练标签上让两端处于同一种文本规范下得到的WER才具有可比性。不要小看这一步同一个模型规范化前后WER可以差两到三个百分点。如果让我给这一章做个总结我不会再复述模型结构而是会强调一件贯穿始终的事RNN-T训练的核心其实是“对齐不确定性”的管理。loss曲线只是表象真正藏着问题的是那棵对齐格子的后验热力图。打开中间步骤的alignment看一下比在损失函数上调半天参数有用得多。另一个小技巧是每次实验都把第一次出现合理词序的checkpoint单独存一份。很多次后期loss降不下去、调参也救不回来的时候这个早期checkpoint反而能在验证集上给出惊喜——RNN-T经常在训练早期已经学会了正确的对齐骨架后期只是在这个骨架上反复修缮。这个现象初期会让人困惑但摸透之后你会觉得RNN-T的训练过程像是雕刻先大致出形再慢慢打磨。希望这篇笔记能让你在打磨的路上少走点弯路。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑