语音识别解码过程使用的是Viterbi算法,本质上是一种动态规划算法,能够得到全局最优解。为了进一步减少计算复杂度,引用了Beam Search 算法,可以在损失微小性能的条件下提高解码速度,Beam Search无法保证全局最优解。
Viterbi解码算法
如果从起点到终点有一条最终路径,那么这条路径子路径也是从起点到相应时刻点的最优路径。如上图,红线是一条从起点到终点的最优路径,那么从起点到时刻4的红线部分也是该时间段的最优路径。换句话说,任一时刻,只需记录到该时刻所有状态的最优路径即可,以时刻4为例,在时刻4,只需记录时刻4上经过三个状态S1,S2,S3的最优路径即可,也就是只需要记录三条路径,接着到时刻5,时刻5的S3状态有两条路径经过,取其中最优路径,时刻5的S2、S1状态类似,也就是说到了时刻5,仍然只需要记录三条路径即可。
所以每一时刻需要做两次循环,外层循环现在时刻所有状态,内层循环现在时刻某一状态到下一时刻所有状态。时间复杂度,所有时间段时间复杂度。上图中,N=3,实际大规模语音识别中任一时刻的状态可能很大,比如5000个,这样即使使用了维特比,时间复杂度还是太大,实际中为了解决这个问题,引入了Beam Search算法
Beam Search 算法
Viterbi解码中涉及到现在时刻state数目以及下一时刻state数目,如果我们想要提高解码速度,需要对这两个数值都做缩减。
实际做法是设置阈值,减少语音识别中现在时刻以及下一时刻状态数目,具体做法是:
- 对所有状态排序,最优状态放最前面,最优状态得分=best_weight
- 设置一个beam,设置阈值1=cur_cutoff,cur_cutoff=best_weight+beam,所有得分在cur_cutoff以内的,保留,反之丢弃,现在时刻的state数目减少。
- 计算到下一时刻的最优路径得分new_weight。
- 设置一个adaptive_beam, 设置阈值2=next_cutoff,next_cutoff=new_weight+adaptive_beam,所有得分在next_cutoff以内的,保留,反之丢弃,下一时刻的state数目减少。
kaldi实际解码过程中,cur_cutoff、next_cutoff这两个阈值是计算出来,涉及到的传入参数包括:config_.beam,config_.beam_delta,config_.max_active,config_.min_active。之所以引入max_active,min_active是为了保证任意时刻的state数目在[min_active,max_active]之间,单纯的只用beam,有可能会导致某一时刻state数目过大或者过小。
具体Kaldi代码解析见Kaldi 解码代码解析
Reference
http://kaldi-asr.org/doc/index.html
注1: 安德鲁·维特比(Andrew J. Viterbi),CDMA之父,IEEE Fellow ,高通公司创始人之一,高通首席科学家。他开发了卷积码编码的最大似然算法