流式语音识别模型(Streaming ASR Model)是指可以在处理音频流的过程中,支持实时返回识别结果的一类 ASR 模型。与之相对的是非流式模型,它必须在处理完整句音频后才能返回结果。流式 ASR 可以更好地用于需要实时获取识别结果的场景,例如直播实时字幕、会议实时记录、语音输入、语音唤醒等场景。

Streaming automatic speech recognition (ASR) aims to emit each hypothesized word as quickly and accurately as possible, while full-context ASR waits for the completion of a full speech utterance before emitting completed hypotheses.[1]


1. 流式 ASR 问题形式化

流式 ASR 是一类具体的 ASR 问题,和前文语音识别算法原理不完全归纳类似,可以这样定义流式 ASR:持续接收音频流进行识别,并根据已经接收到的一部分[公式],获取对应的后验概率最大的 Token 序列[公式]。即:


流式和非流式的区别在于[公式]是取整句上下文[公式],还是上文[公式],其中[公式]为已接收的音频特征流的当前帧索引。在实现中,允许模型有一定的延迟,此时虽然已经接收了[公式]帧输入,但实际解码到的是第[公式]帧,[公式]为有限的下文长度。或者对上文长度也有一定限制,即在对第[公式]帧后验概率建模的时候,只考虑指定长度的上下文内容:


[公式]和[公式]分别是上文(left-context)和下文(right-context)长度,[公式]则代表使用所有上文。

另外,流式 ASR 有实时率的要求。实时率(Real Time Factor, RTF)定义为模型处理时间和音频长度的比值,例如处理 2s 的音频耗时 0.6s,那么[公式]。由于模型是对历史输入建模,历史输入会随着时间不断增长,这就有可能导致模型计算量逐渐增加,RTF 不断增长。当[公式]时,说明模型已经来不及处理缓存的音频了,这就是无法实时处理的定量标准。因此在流式 ASR 中基本的要求是[公式],因此常见采用固定上下文,从而得到较为稳定的 RTF。

为什么要专门提流式的 ASR 模型呢?或者说难道现有模型无法流式么?要知道 ASR 模型通常都是可以处理不定长的数据的,既然可以处理短音频,那在推理的时候,一段一段地传输给模型识别,不就流式了么?

确实,这样也可以实现流式 ASR,问题是:直接在推理阶段做了这样的修改之后,会导致训练和推理不一致,流式的效果相比非流式识别下降过大,达不到我们期望效果。

流式 ASR 的落脚点在于:如何让模型在训练阶段的建模对象和流式推理阶段一致,从而提高流式识别的效果。在这一点上,传统的 ASR 算法和端到端 ASR 处理思路是一样的。


2. 实现思路

2.1 传统 ASR 算法实现流式的关键点

我在前一篇文章语音识别算法原理不完全归纳中讲到,传统 ASR 算法的优化目标为


其中,语言模型建模的是根据历史 Token 预测下一个 Token 的概率,并且只依赖于上文,它本身就是支持流式的。词典模型把发音序列转换为 Token 序列,也是流式的转换。因此关键在于声学模型是否支持流式。

从另一个角度看,在使用 WFST 把声学发音单元转换关系(HMM topo 和转移概率、CTC topo等)、词典转换关系、语言模型得分编码成同一个静态图之后,已编码的部分是支持时间同步的流式解码的。因此关键点就在于抽象出来的声学得分计算(HMM 中是发射概率,CTC 中直接是似然概率)是否支持流式。

在 HMM 声学模型中,发射概率只与当前状态有关, GMM-HMM 中,GMM 在建模发射概率时,直接建模的是[公式],即只考虑当前帧,不受下文影响,因而本身就是支持流式的。DNN-HMM 中,DNN 建模的是[公式],是否支持流式关键在于 DNN 考虑的下文长度。CTC 声学模型和 DNN-HMM 类似,也是看DNN 考虑的下文长度。


2.2 端到端 ASR 算法实现流式的关键点

端到端 ASR 直接建模序列后验概率,序列整体的后验概率通过 Token 的后验概率累积得到,并假设当前 Token[公式]只依赖于之前出现的 Token[公式],即:


是否支持流式,关键也是在于每一项[公式]中模型考虑的下文长度。


3. 具体实现方式

目前已经有的流式 ASR 算法主要可以归纳为两类,第一类从模型出发,选取支持只考虑上文和有限下文模型结构,第二类从训练方式出发,手动限制模型下文长度,让模型只对有限上下文进行建模。


3.1 思路1:调整模型结构

许多模型本身是不考虑(或只考虑有限的)下文的,例如 FullyConnect 只考虑当前帧或固定长度窗口的拼帧,RNN、LSTM、GRU等只考虑上文,CNN、TDNN等只考虑有限的上下文窗口。或者优化模型结构,如因果卷积。具体的模型结构这里就不一一列举了。



不依赖下文的模型示意图

在算法实现过程中,尽量使用这一类模型,就可以省去很多麻烦。例如 kaldi 算例里实现的大部分模型都是选取的支持流式的结构。再比如说,RNN-T 从一开始就选用了 RNN,当然也就支持流式。反之像 LAS、Transformer 等选取 BLSTM、Self-Attention 结构,要想实现流式就比较复杂。


3.2 思路2:修改训练方式

对于 BLSTM、Self-Attention 等模型结构,期望上下文越多越好,为了让这一类模型具备只考虑有限上下文的建模能力,需要修改模型训练方式,在对第[公式]帧后验概率建模的时候,屏蔽掉指定长度上下文之外的内容。

主要的实现方法是基于 chunk 的方式,在序列建模的框架下,间接实现局部建模,例如 LC-BLSTM[2]、以及诸多的流式 Transformer 等。


    基于 chunk 的流式 ASR
    • 逻辑上是切分的方式,控制模型每次运算接收到的上下文窗口大小,让模型可以有对序列局部建模能力,然后计算损失函数的时候将局部计算的结果拼接起来;
    • 实现上常常采用 mask [5] 来屏蔽不相关的上下文的方式来提高训练速度。

    对 chunk 的有几种不同使用方式:

      • 第一种是只考虑固定的 chunk,只在局部的 chunk 内(只有一个 chunk、相邻 chunk、或者控制 chunk 之间有重叠部分)执行 BLSTM 和 Attention 运算,单纯地建模局部信息。


      • 第二种是类似 RNN,加入 Memory 让模型具备长时建模能力,如 TransformerXL [4]。


        加入 memory 的 chunk
        • 还有一些有趣的思路,比如研究[3] 在训练过程设置动态 chunk 长度,让一遍训练完成后的模型既支持流式也支持非流式识别。

        对于 Transformer 等 Encoder-Decoder 结构的模型,其流式的关键在于 Encoder 的流式,因为大部分 Decoder 充当的是语言模型的角色,它的建模方式是


        对于预测下一 Token 的概率的建模本身是只考虑上文的,因此最终还是围绕 Encoder 是否能流式。当然,Encoder 采用不同的流式方式,Decoder 相应也要做一定的适配,但理论上,Decoder 本身是支持流式的。


        4. 流式 ASR 的用户体验问题

        在语音交互场景等,引入流式 ASR 是为了让用户看看中间结果,避免长时间没有回应的等待,提高用户体验。但流式 ASR 系统在和用户交互过程中,又会引入一些新的问题,这里分析几个比较常见的,每个问题的解决方案都可能比较复杂,我留到后续文章再讨论。


        4.1 单次识别延迟过大

        上文我们提到基于 chunk 实现流式的方式,其中不同长度的 chunk 就意味着不同长度的延迟。一方面我们希望模型见到的上下文越丰富效果越好,另一方面,我们希望下文越少响应越快。这是chunk 流式方案存在的内在矛盾,从这点上大家可以吐槽这种方案不够优雅。

        如果设置的 chunk size 太大,会造成用户说一小段内容,都必须要等待一个 chunk size 缓存装满才送到识别引擎。这个在测试首字响应时间和尾字响应时间中体现得比较明显。作为一个用户来看,尾字响应时间对使用体验的影响要更大一些。例如,语音输入场景,我并不会很关注什么时候出来第一个字,只要不要慢得过分就行,然而假如我说完一句话,需要等待的很长时间才得到最终结果,就会影响我发送消息。再比如语音搜索、语音助手等系统中,NLU 模块需要等待 ASR 的结果,如果尾字响应太慢,就会导致系统整体延迟较大。


        4.2 音频越长延迟越大

        我们见的较多的 ASR 应用场景是短音频文件识别,但也有一些场景输入的音频时间较长,例如实时的会议转录、语音朗读、写作等,用户要是说的内容较长,越到后面出词越慢,甚至出现明显的卡顿和丢字((`へ´))。原因也就是前面说的[公式]。Unfortunately,无论是传统 ASR 还是端到端 ASR,对长音频的处理都不够完善,没有很好地解决 RTF 不断增长的问题。因此许多 ASR 系统都会对输入音频长度有限制,或者通过强制截断、vad 截断、尾端点检测截断等方式来间接支持长音频识别。


        4.3 中间结果突变

        在 ASR 解码过程中,中间结果的获取一般是通过回溯 best-path 来得到,但是随着解码不断进行,不同时刻的最优路径可能发生较大的改变,尤其是在一些声学模型结果区分度较小的场景,比如一些近似音、噪声太大等,就会使得 n-best 路径本身的得分比较接近,后续较小的得分变化就导致 top-1 路径改变,又或者因为加入了纠错、 Rescore 等模块,导致最终的识别结果与中间结果有较大差异。

        对于许多系统真正有用的是最终的结果,因此对系统最终效果而已这个问题影响不大。但是对用户而言,(怒ε=( o`ω′)ノ)这个现象就不一定能忍受了!试问你一边说话,看着中间上屏结果跳来跳去,结果一下跟你说的差不多,一下又变成其他不相关内容,你还会觉得这个系统很准确么?

        另外,不少模型建模单元是词,再加上 chunk 的识别方式,就有可能出现这种情况:在一段时间内系统不输出结果,然后突然又蹦出来一大串文字,对体验要求较高的用户对这个也是有意见的。


        5. 总结

        流式 ASR 研究的是一类实际应用的问题:为了实现实时 ASR 的功能需求,以及提高人机语音交互过程中用户体验。本文对流式 ASR 问题做了形式化定义,分析了流式 ASR 关键在于声学模型考虑的上下文长度的原因,并把常见的实现方式归纳成调整模型结构和修改训练方式两大类。同时,本文列举了几个影响用户体验的问题。

        流式 ASR 是目前的一个研究热点,但它的技术还不够成熟,仍待大家深入探索,不断突破。