来源丨https://zhuanlan.zhihu.com/p/531934889
本文主要基于我们之前发表的论文“Tiny Transducer: A Highly-Efficient Speech Recognition Model on Edge Devices“[1][2]。Tiny Transdcuer论文主要集中在ASR任务上,整体上还是一个“声学模型+WFST解码器”的框架。在此工作中,我们采用非常轻量级的CNN-LSTM-FSMN作为Transducer模型的Encoder,采用Stateless Predictor来进一步压缩模型的参数量。同时,我们在论文中还提到了几个非常实用的技巧,比如对blank的输出概率进行抑制、解码跳过blank等,这些简单的trick,可以给系统的准确率和实时性上,带来非常显著的收益,而且这些简单技巧,同样适用于CTC+WFST解码器的框架。以本论文所提出的架构和系统,已经落地到智能车载语音助手中,相比于之前的系统,在CPU占用和识别准确率上, 都有了质的飞跃。

本文作为一篇技术文章而非PR文章,将更加详细的解析Tiny Transducer这篇论文的细节,并且做一些补充,当然,整个工作并没有多少创新,但是作为一个可以实用的系统,我们注重解决实际问题,提供了一整套流程,并对一些有用的经验做了细致的分享。本文所涉及的技术,几乎都是已经公开发表的,而最终能否取得效果,往往和自己的数据和任务、实现细节和训练策略等都有关系,不过希望通过分享,提供一些经验,共同提高,共同学习。整个工作持续时间比较长,论文发表也有一年多了,不过最近我们基于Tiny Transducer还有一些新的工作,所以首先对它进行分享。
整个系统,仍然是一个“声学模型+WFST解码器”的混合系统。虽然粗力度建模单元(比如汉字)在某些论文中能取得不错的效果,但是当考虑到实际的产品应用时,有几个问题:- 就是无法很好的融合外部语言模型信息来实现个性化的定制;
- 而且一旦出现bad case,往往很难定位和解决问题;
已经有有大量的文章也提到过这些问题,特别是最近大家在研究的“内部语言模型估计”,其实也是为了解决这样一个问题。为了避免这些问题,我们采用了音素作为建模单元,外接基于WFST的解码器去融合语言模型,这也是目前端到端系统应用的一个主流方式。在端到端声学模型上,我们采用了Transducer模型,Transducer模型的优势就是它的天然流式的特性,不需任何改进,就可以实现流式识别。虽然整个系统仍然是一个混合系统,但是整个系统相比于Kaldi的Chain model + WFST混合系统在训练和解码上都简化了很多:- AM采用了Tranducer模型,训练简单了很多,不需要对齐信息;
- 采用phone建模,WFST为TLG复合,解码器也得到了简化。
虽然vanilla Transducer模型的训练不需要帧级的对齐信息,但是如果手头上有GMM模型可以进行音频和音素的对齐,那么我还是建议进行对齐,因为音素的帧级别对齐进行Encoder辅助训练,也能够提供性能收益;更重要的时候,当使用帧级音素对齐进行辅助训练后,帧级别的输出可以用来进行识别结果和音频的对齐,这在某些需要获得准确时间戳的应用中,具有很重要的作用。更具体的可以参考我的下一篇文章Tiny Transducer(2)。
基于Conv1d-LSTM-FSMN的EncoderTransducer的声学Encoder,我们采用这样的设计,具体的细节以及每一部分配置如下,便于大家复现:- Conv1d:两层,卷积核分别是5和3,步长均为2,实现4倍下采样;Conv1d之后紧跟一层LayerNorm;输入40维度的PNCC特征,输出256维度的表示;
- LSTM:输入和输出都是256维度,hidden dim为128;我们的实验表明,即使使用这么一层简单的LSTM,就能够使模型对噪声的鲁棒性有显著的提高;
- FSMN:总共6层,每一层的left order均为8,right order分别是[2, 2, 1, 2, 2, 1];FSMN采用了DFSMN论文[3]图2的结构,hidden-dim=512,proj-dim=320;每一层最后都使用了LayerNorm。整个FSMN的right order比较大,但是实际在使用的时候完全没有必要担心这个由于right order导致的延时,模型看到了足够的right order反而具有了前瞻性。
整个模型的总参数量大概是3M(参数量,非浮点数存储)。模型中使用的LSTM、FSMN中的大量的线性层,都可以使用pytorch的动态量化进行int8的量化而没有任何识别性能的损失。如果使用动态量化,其实只有LSTM结构和Linear层会被量化,Conv1d和Conv2d这些结构是不支持的。我的FSMN实现的时候使用了Conv2d,所以量化的时候这部分是不会被量化的,不过这部分参数量并不大。
基于Conv1d的Stateless Predictor对于Transducer模型中的Predictor网络,我们没有采用LSTM,一方面是为了缩小参数量,一方面对于论文中提到的助手类业务,文本模式相对来说并不是特别丰富,大部分都是查询类和控制类的,因此我们采用了Conv1d结构,卷积核为4,也就是使用历史的3个预测的音素和当前预测的音素的embedding作为Conv1d的输入。
在这里我们使用了phone作为建模单元,建模单元总计210多个。很多人都在这个建模单元上争论用phone还是用syllable,我觉得这种实验意义不大,最后效果都差不太多。音素作为建模单元,网络最终的输出维度小,这对于Transducer这种损失函数来说是非常有利的;在基于TLG的WFST解码的时候,建模单元少,解码时每一步的发射弧也会少,同样起到了加速的作用。同时,采用phone建模,颗粒度相对较小,外部语言模型起到的作用相对可以更大一些,在实际使用中,由于外部语言模型的作用,即使单纯看Transducer模型的音素识别出现了错误,最终结果可能也是对的。采用音素建模,涉及到多音字的发音问题。在实际使用中,我们采用了Kaldi的GMM模型进行了对齐,然后将对齐转换为音素序列(ali-to-phones)作为音素标签来进行Transducer的训练,这样就可以解决多音字发音问题。同时frame-level的音素对齐也保留,后续的联合训练中会使用到。
Transducer Greedy Search + TLG解码器的一些优化在解码的过程中,我们仍然采用了TLG的解码器来复合语言模型。Transducer模型是一种自回归模型,本身在解码的时候可以采用beam search,但是如果Transducer模型采用了beam search,这样就很难和TLG复合,因此,最简单的方式就是Transducer采用Greedy search,每一帧都会输出一个后验概率向量送给WFST的解码器。不过由于Transducer模型的特性,大多数帧的输出都是blank概率占绝对优势的,因此在解码的时候,完全可以跳过这些帧,只把最大概率为nonblank的帧的后验送给解码器即可,这样,解码器从时间同步变成了音素同步了,极大的减少了解码的步数而不带来任何损失,但解码效率能提高很多!另外一个十分有用的技术是每一步都需要对blank的概率进行抑制,实验中我们发现,如果不对blank的概率进行抑制,模型会过度倾向blank的输出,这样就会导致非常高的删除错误。我们的做法很简单,在对数域上,每一步都需要给blank的概率剪掉一个常数,这里,我们实验发现在我们数据集合上,
取1.5或者2.0的时候效果最好。
这两个技巧,同样适用于CTC的系统,之前一个外国小哥给我发邮件,表示这两个技术对他们的系统都有不错的收益,而且我们也在多个不同的数据和模型上进行过测试。最后,我们在Transducer的Greedy search时,使用了ILME,无论是在大模型还是小模型,都能带来收益,有兴趣的朋友也可以尝试一下,简单好用。
为了更进一步的提升模型的效果,我们后面还用了多个loss联合训练模型的Encoder。除了Transducer loss,我们又增加了逐帧的音素对齐来辅助训练Encoder,增加了Transformer decoder+音素建模来联合优化Encoder,最后,我们最终的系统是采用了三个损失函数联合训练的:
由于最终的结果都是内部数据,我这里就不再展示,但是可以确定的说,联合多个损失函数训练,可以带来稳定而可观的收益!虽然论文中也提到了SVD,但是我们发现在我们的设备上,完全没必要用SVD了,这里就不再多说。
pytorch训练模型之后,使用JIT导出模型,可以很轻松的在C++中部署,直接接入到基于WFST的解码器中。我们采用了libtorch作为推理引擎,onnx也是支持的。
本文提供了基于DFSMN的轻量级的Transducer模型的全部细节,从训练到解码。后续还会继续给大家分享我们Cascaded tinyTransducer +Transformer (CaTT)的自定义唤醒工作(Interspeech2022)[4],欢迎大家持续关注。
参考
[1]论文地址 https://ieeexplore.ieee.org/document/9413854
[2]论文地址 https://arxiv.org/abs/2101.06856
[3]DFSMN https://arxiv.org/pdf/1803.05030.pdf
[4]论文地址 https://arxiv.org/pdf/2207.01267.pdf