语音识别

语音识别根据实际需求的不同也会有所不同。目前主要追求大词汇量、连续、非特定人。

       语音识别主流开源框架:HTK、Kaldi、CMUSphinx。

因为目前只接触了Sphinx,对于另外两个框架没有太深入研究。

        1)Kaldi适用于在服务器搭建的语音识别系统,也可以适用于android,但没有实验过,根据官方提供方法:http://jcsilva.github.io/2017/03/18/compile-kaldi-android/

其使用模型很多:DNN-HMM、GMM-HMM等

        2)PocketSphinx是应用于嵌入式而开发的(Sphinx用于联网后台识别系统,Sphinx现在版本是Sphinx4-5prealpha,在0.8版本时还有支持中文模型,现在已经没有中文语音模型了)。PocketSphinx和Sphinx由于不同的解码器使用了不同的声学模型。

 

        该文讲解:将语音识别大概流程和涉及到的一些算法汇总,最后PocketSphinx安装、编译、训练、结构以及涉及到的一些坑。

android 免费语音识别 android语音识别开源库_建模

一、语音的特征

       推荐该博客文章,请一定细读,讲解语音产生、基本名词和语音特征,比较详尽。

二、识别流程及原理

       对于语音识别系统而言,第一步要检测是否有语音输入,即,语音激活检测(VAD)。在低功耗设计中,相比于语音识别的其它部分,VAD采用always on的工作机制。当VAD检测到有语音输入之后,VAD便会唤醒后续的识别系统。识别系统总体流程如图2所示,主要包括特征提取、识别建模及模型训练、解码得到结果几个步骤。


图2.语音识别系统

1、VAD(语音激活检测)

       用于判断什么时候有语音输入,什么时候是静音状态。语音识别后续的操作都是在VAD截取出来的有效片段上进行,从而能够减小语音识别系统噪声误识别率及系统功耗。在近场环境下,由于语音信号衰减有限,信噪比(SNR)比较高,只需要简单的方式(比如过零率、信号能量)来做激活检测。但是在远场环境中,由于语音信号传输距离比较远,衰减比较严重,因而导致麦克风采集数据的SNR很低,这种情况下,简单的激活检测方法效果很差。使用深度神经网络(DNN)做激活检测是基于深度学习的语音识别系统中常用的方法(在该方法下,语音激活检测即为一个分类问题)。在MIT的智能语音识别芯片中使用了精简版的DNN来做VAD,该方法在噪声比较大的情况下也具有很好的性能。但是更复杂的远场环境中,VAD仍然是未来研究的重点。

2、特征提取

       梅尔频率倒谱系数(MFCC)是最为常用的语音特征,梅尔频率是基于人耳听觉特征提取出来的。MFCC主要由预加重、分帧、加窗、快速傅里叶变换(FFT)、梅尔滤波器组、离散余弦变换几部分组成,其中FFT与梅尔滤波器组是MFCC最重要的部分。但是近年研究表明,对于语音识别而言,梅尔滤波器组不一定是最优方案。受限的玻尔兹曼机(RBM)、卷积神经网络(CNN)、CNN-LSTM-DNN(CLDNN)等深度神经网络模型作为一个直接学习滤波器代替梅尔滤波器组被用于自动学习的语音特征提取中,并取得良好的效果。

       目前已经证明,在特征提取方面,CLDNN比对数梅尔滤波器组有明显的性能优势。基于CLDNN的特征提取过程可以总结为:在时间轴上的卷积、pooling、pooled信号进入到CLDNN中三个步骤。

       远场语音识别领域,由于存在强噪声、回响等问题,麦克风阵列波束成形仍然是主导方法。

       另外,现阶段,基于深度学习的波束成形方法在自动特征提取方面亦取得了众多研究成果。

       通俗形象点解释特征提取过程

       在开始识别之前,有时需要把首尾端的静音切除,降低对后续步骤造成的干扰。这个静音切除的操作一般称为 VAD,需要用到信号处理的一些技术。

  要对声音进行分析,需要对声音分帧,也就是把声音切开成一小段一小段,每小段称为一帧。分帧操作一般不是简单的切开,而是使用移动窗函数来实现,这里不详述。帧与帧之间一般是有交叠的,就像下图这样:

  

  图中,每帧的长度为 25 毫秒,每两帧之间有 25-10=15 毫秒的交叠。我们称为以帧长 25 ms、帧移 10 ms 分帧。图中,每帧的长度为 25 毫秒,每两帧之间有 25-10=15 毫秒的交叠。我们称为以帧长 25 ms、帧移 10 ms 分帧。

  分帧后,语音就变成了很多小段。但波形在时域上几乎没有描述能力,因此必须将波形作变换。常见的一种变换方法是提取 MFCC 特征,根据人耳的生理特性,把每一帧波形变成一个多维向量,可以简单地理解为这个向量包含了这帧语音的内容信息。这个过程叫做声学特征提取。实际应用中,这一步有很多细节,声学特征也不止有 MFCC 这一种,具体这里不讲。

  至此,声音就成了一个 12 行(假设声学特征是 12 维)、N 列的一个矩阵,称之为观察序列,这里 N 为总帧数。观察序列如下图所示,图中,每一帧都用一个 12 维的向量表示,色块的颜色深浅表示向量值的大小。(不过一般提取MFCC的39维特征值)

  

3、识别建模(解码)

       语音识别本质上是音频序列到文字序列转化的过程,即在给定语音输入的情况下,找到概率最大的文字序列。基于贝叶斯原理,可以把语音识别问题分解为给定文字序列出现这条语音的条件概率以及出现该条文字序列的先验概率,对条件概率建模所得模型即为声学模型,对出现该条文字序列的先验概率建模所得模型是语言模型。

      通俗形象点解释识别过程

      首先解释两个概念


  1.音素:单词的发音由音素构成。对英语,一种常用的音素集是卡内基梅隆大学的一套由 39 个音素构成的音素集,参见 The CMU Pronouncing DicTIonary。汉语一般直接用全部声母和韵母作为音素集,另外汉语识别还分有调无调,不详述。

  2.状态:这里理解成比音素更细致的语音单位就行啦。通常把一个音素划分成 3 个状态。

  语音识别是怎么工作的呢?实际上一点都不神秘,无非是:

  第一步,把帧识别成状态(难点);

  第二步,把状态组合成音素;

  第三步,把音素组合成单词。

  如下图所示:

  

  图中,每个小竖条代表一帧,若干帧语音对应一个状态,每三个状态组合成一个音素,若干个音素组合成一个单词。也就是说,只要知道每帧语音对应哪个状态了,语音识别的结果也就出来了。图中,每个小竖条代表一帧,若干帧语音对应一个状态,每三个状态组合成一个音素,若干个音素组合成一个单词。也就是说,只要知道每帧语音对应哪个状态了,语音识别的结果也就出来了。

  那每帧音素对应哪个状态呢?有个容易想到的办法,看某帧对应哪个状态的概率最大,那这帧就属于哪个状态。比如下面的示意图,这帧对应 S3 状态的概率最大,因此就让这帧属于 S3 状态。

  

  那这些用到的概率从哪里读取呢?有个叫「声学模型」的东西,里面存了一大堆参数,通过这些参数,就可以知道帧和状态对应的概率。获取这一大堆参数的方法叫做「训练」,需要使用巨大数量的语音数据,训练的方法比较繁琐,这里不讲。

  但这样做有一个问题:每一帧都会得到一个状态号,最后整个语音就会得到一堆乱七八糟的状态号,相邻两帧间的状态号基本都不相同。假设语音有 1000 帧,每帧对应 1 个状态,每 3 个状态组合成一个音素,那么大概会组合成300个音素,但这段语音其实根本没有这么多音素。如果真这么做,得到的状态号可能根本无法组合成音素。实际上,相邻帧的状态应该大多数都是相同的才合理,因为每帧很短。

  解决这个问题的常用方法就是使用隐马尔可夫模型(Hidden Markov Model,HMM)。这东西听起来好像很高深的样子,实际上用起来很简单:

  第一步,构建一个状态网络。

  第二步,从状态网络中寻找与声音最匹配的路径。

  这样就把结果限制在预先设定的网络中,避免了刚才说到的问题,当然也带来一个局限,比如你设定的网络里只包含了「今天晴天」和「今天下雨」两个句子的状态路径,那么不管说些什么,识别出的结果必然是这两个句子中的一句。

  那如果想识别任意文本呢?把这个网络搭得足够大,包含任意文本的路径就可以了。但这个网络越大,想要达到比较好的识别准确率就越难。所以要根据实际任务的需求,合理选择网络大小和结构。

  搭建状态网络,是由单词级网络展开成音素网络,再展开成状态网络。语音识别过程其实就是在状态网络中搜索一条最佳路径,语音对应这条路径的概率最大,这称之为「解码」。路径搜索的算法是一种动态规划剪枝的算法,称之为 Viterbi 算法,用于寻找全局最优路径。

  

  这里所说的累积概率,由三部分构成,分别是:

  观察概率:每帧和每个状态对应的概率

  转移概率:每个状态转移到自身或转移到下个状态的概率

  语言概率:根据语言统计规律得到的概率

  其中,前两种概率从声学模型中获取,最后一种概率从语言模型中获取。语言模型是使用大量的文本训练出来的,可以利用某门语言本身的统计规律来帮助提升识别正确率。语言模型很重要,如果不使用语言模型,当状态网络较大时,识别出的结果基本是一团乱麻。



3.1  声学模型

       声学模型是把语音转化为声学表示的输出,即找到给定的语音源于某个声学符号的概率。对于声学符号,最直接的表达方式是词组,但是在训练数据量不充分的情况下,很难得到一个好的模型。词组是由多个音素的连续发音构成,另外,音素不但有清晰的定义而且数量有限。因而,在语音识别中,通常把声学模型转换成了一个语音序列到发音序列(音素)的模型和一个发音序列到输出文字序列的字典。

       需要注意的是,由于人类发声器官运动的连续性,以及某些语言中特定的拼读习惯,会导致音素的发音受到前后音素的影响。为了对不同语境的音素加以区分,通常使用能够考虑前后各一个音素的三音子作为建模单元。

       另外,在声学模型中,可以把三音子分解为更小的颗粒—状态,通常一个三音子对应3个状态,但是这会引起建模参数的指数增长,常用的解决方案是使用决策树先对这些三音子模型进行聚类,然后使用聚类的结果作为分类目标。

       至此,语音识别有了最终的分类目标—状态。最常用的声学建模方式是隐马尔科夫模型(HMM)。在HMM下,状态是隐变量,语音是观测值,状态之间的跳转符合马尔科夫假设。其中,状态转移概率密度多采用几何分布建模,而拟合隐变量到观测值的观测概率的模型常用高斯混合模型(GMM)。基于深度学习的发展,深度神经网络(DNN)、卷积神经网络(CNN)、循环神经网络(RNN)等模型被应用到观测概率的建模中,并取得了非常好的效果。下文给出各个模型的原理、所解决的问题及各自局限性,且给出了由模型的局限性而引起建模方式发展的脉络。


1)高斯混合模型(GMM)

       观测概率密度函数由高斯混合模型建模,训练中,不断迭代优化,以求取GMM中的加权系数及各个高斯函数的均值与方差。GMM模型训练速度较快,且GMM声学模型参数量小,可以容易地嵌入到终端设备中。在很长一段时间内,GMM-HMM混合模型都是表现最优秀的语音识别模型。但是GMM不能利用语境信息,其建模能力有限。


2)深度神经网络(DNN)

       最早用于声学模型建模的神经网络,DNN解决了基于高斯混合模型进行数据表示的低效问题。语音识别中,DNN-HMM混合模型大幅度的提升了识别率。目前阶段,DNN-HMM基于其相对有限的训练成本及高识别率,仍然是特定的语音识别工业领域常用的声学模型。需要注意的是,基于建模方式的约束(模型输入特征长度的一致性需求),DNN模型使用的是固定长度的滑动窗来提取特征。


3)循环神经网络(RNN)/卷积神经网络(CNN)模型

       对于不同的音素与语速,利用语境信息最优的特征窗长度是不同的。能够有效利用可变长度语境信息的RNN与CNN在语音识别中能够取得更好的识别性能。因而,在语速鲁棒性方面,CNN/RNN比DNN表现的更好。

       在使用RNN建模方面,用于语音识别建模的模型有:多隐层的长短期记忆网络(LSTM)、highway LSTM、ResidualLSTM、双向LSTM、时延控制的双向LSTM。

       LSTM,基于门控电路设计,其能够利用长短时信息,在语音识别中取得了非常好的性能。另外,可以通过增加层数进一步提升识别性能,但是简单地增加LSTM的层数会引起训练困难及梯度消失问题。

       Highway LSTM,在LSTM相邻层的记忆单元间添加一个门控的直接链路,为信息在不同层间流动提供一个直接且不衰减的路径,从而解决梯度消失问题

       Residual LSTM,在LSTM层间提供一个捷径,亦能解决梯度消失问题。

       双向LSTM,能够利用过去及未来的语境信息,因而其识别性能比单向的LSTM好,但是由于双向LSTM利用了未来的信息,因而基于双向LSTM建模的语音识别系统需要观察完整的一段话之后才能识别,从而不适用于实时语音识别系统。

       时延控制的双向LSTM,通过调整双向LSTM的反向LSTM,实现了性能与实时性的一个折中建模方案,能够应用于实时的语音识别系统。


       CNN建模方面,包括时延神经网络(TDNN)、CNN-DNN、CNN-LSTM-DNN(CLDNN)、CNN-DNN-LSTM(CDL)、深度CNN、逐层语境扩展和注意(LACE)CNN、dilated CNN。


       TDNN,最早被用于语音识别的CNN建模方式,TDNN会沿频率轴和时间轴同时进行卷积,因此能够利用可变长度的语境信息。TDNN用于语音识别分为两种情况,第一种情况下:只有TDNN,很难用于大词汇量连续性语音识别(LVCSR),原因在于可变长度的表述(utterance)与可变长度的语境信息是两回事,在LVCSR中需要处理可变长度表述问题,而TDNN只能处理可变长度语境信息;第二种情况:TDNN-HMM混合模型,由于HMM能够处理可变长度表述问题,因而该模型能够有效地处理LVCSR问题。


       CNN-DNN,在DNN前增加一到两层的卷积层,以提升对不同说话人的可变长度声道(vocal tract)问题的鲁棒性,对比于单纯DNN,CNN-DNN性能有一定幅度(5%)的提升


       CLDNN及CDL,在这两个模型中,CNN只处理频率轴的变化,LSTM用于利用可变长度语境信息。


       深度CNN,这里的“深度”是指一百层以上。语谱图可以被看作是带有特定模式的图像,通过使用比较小的卷积核以及更多的层,来利用时间及频率轴上长范围的相关信息,深度CNN的建模性能与双向LSTM性能相当,但是深度CNN没有时延问题。在控制计算成本的情况下,深度CNN能够很好的应用于实时系统。


       逐层语境扩展和注意(LACE)CNN及dilatedCNN,深度CNN的计算量比较大,因而提出了能够减小计算量的 LACE CNN与dilatedCNN,其把整个话语看作单张输入图,因而可以复用中间结果,另外,可以通过设计LACE CNN及dilatedCNN网络每一层的步长,使其能够覆盖整个核,来降低计算成本。


       语音识别的应用环境常常比较复杂,选择能够应对各种情况的模型建模声学模型是工业界及学术界常用的建模方式。但是各个单一模型都有局限性。HMM能够处理可变长度的表述,CNN能够处理可变声道,RNN/CNN能够处理可变语境信息。声学模型建模中,混合模型由于能够结合各个模型的优势,是目前声学建模的主流方式。


3.2  语言模型


       语音识别中,最常见的语言模型是N-Gram。近年,深度神经网络的建模方式也被应用到语言模型中,比如基于CNN及RNN的语言模型。


4、端到端的语音识别系统


       在DNN-HMM或者CNN/RNN-HMM模型中,DNN/CNN/RNN与HMM是分开优化的,但是语音识别本质上是一个序列识别问题,如果模型中的所有组件都能够联合优化,很可能会获取更好的识别准确度,这一点从语音识别的数学表达式也可以看出(利用贝叶斯准则变化之后的表达式),因而端到端的处理方式亦被引入到语音识别系统中。


4.1  CTC准则


       其核心思想是引入空白标签,然后基于前向后向算法做序列到序列的映射。CTC准则可分为character-basedCTC、other output units-based CTC、word-basedCTC,由于CTC准则是直接预测字符、单词等,而不是预测音素,因而其能够剔除语音识别中的字典等专家知识。由于在非word-basedCTC中,仍然需要语言模型及解码器。因而,character-basedCTC与other output units-basedCTC是非纯粹的端到端的语音识别系统。相反,word-based CTC模型是纯粹的端到端语音识别系统。


       基于word-based CTC准则,使用10万个词作为输出目标且使用 12.5 万小时训练样本得到的语音序列到单词序列的模型,能够超越基于音素单元的模型。但是word-based CTC模型有训练困难及收敛慢的问题。


4.2  Attention-based模型


       相比于CTC准则,Attention-based模型不需要有帧间独立性假设,这也是Attention-based模型的一大优势,因而Attention-based模型可能能够取得更好的识别性能。但是相比于CTC准则,Attention-based模型训练更加困难,且有不能单调地从左到右对齐及收敛更慢的缺点。通过将CTC 目标函数用作辅助代价函数,Attention训练和 CTC训练以一种多任务学习的方式结合到了一起。这种训练策略能够很大程度上改善Attention-based模型的收敛问题,并且缓解了对齐问题。


       语音识别的发展过程中,深度学习起到了关键的作用。声学模型遵循从DNN 到LSTM再到端到端建模的发展路径。深度学习最大的优势之一是特征表征。在有噪声、回响等情况下,深度学习可以把噪声、回响看为新的特征,并通过对有噪声、回响数据的学习,达到比较理想的识别性能。目前阶段,端到端的建模方式是声学模型建模的重点研究方向,但是相比于其它的建模方式,其还没有取得明显的性能优势。如何在端到端建模的基础上,提升训练速度及性能,并解决收敛问题是声学模型的重要研究方向。


5、解码


       基于训练好的声学模型,并结合词典、语言模型,对输入的语音帧序列识别的过程即为解码的过程。传统的解码是将声学模型、词典以及语言模型编译成一个网络。解码就是在这个动态网络空间中,基于最大后验概率,选择一条或多条最优路径作为识别结果(最优的输出字符序列)。搜索常用的方法是Viterbi算法。对于端到端的语音识别系统,最简单的解码方法是beamsearch算法。


6、远场复杂环境下解决方案


       目前阶段,在近场安静环境下,语音识别能够取得非常理想的识别效果,但是在高噪声、多人说话、强口音等环境,特别是远场环境下,语音识别还有诸多问题需要解决。语音模型自适应、语音增强与分离、识别模型优化等是常用的可选解决方案。


6.1  语音增强与分离


       远场环境下,语音输入信号衰减比较严重,为了对语音信号增强,常采用麦克风阵列的波束形成技术,比如,GoogleHome采用双麦的设计方案,亚马逊Echo采用6+1的麦克风阵列设计方案。近年,深度学习方法被应用到语音增强与分离中,核心思想是把语音增强与分离转化为一个监督学习问题,即预测输入声音源的问题。有研究使用DNN替代波束形成,实现语音增强,并在一定场景下取得了比较理想的效果。但是在背景噪声很大的环境中,该方法性能还有较大提升空间。


       在多人说话的情况下,如果不对输入信号做分离处理,而进行语音识别的话,识别效果会很差。对于该问题,在多个说话人距离较远的情况下,波束形成是一个比较好的解决方案,但是当多个说话人距离很近的时候,波束形成的语音分离效果也很差。为了避开波束形成所带来的场景分类问题,传统的方法多是在单通道下尝试解决该问题,常用算法有computationalauditory scene analysis、非负矩阵分解、deep clustering等,但是这些方法只有当噪声信号(除声源外的其他信号)与声音源信号有明显不同的特征时,这些技术才取得比较好的效果。其它情况下,这些方法在语音分离中取得的效果一般。2016年,俞栋博士提出了一种新的深度学习训练准则--permutation invariant training,巧妙地解决了该问题,并取得了不错的效果。


6.2  语音模型自适应


      大量且丰富(能够提供更多信息)的数据集是提升模型泛化能力的最直接简单的方法;

基于成本及训练时间的考虑,一般情况下只使用有限的训练数据。此时,在模型训练中加入Kullback-Leiblerdivergence正则项是解决模型自适应问题非常有效的方式;

除了加入正则项外,使用非常少的参数来表征说话者特征是另一种自适应方式,其包括:奇异值分解瓶颈自适应,把满秩矩阵分解为两个低秩矩阵,减小训练参数;子空间法,子空间法又包括:


      1. 在输入空间及深度网络的各个层中加入i-vector、扬声器(speaker)编码、噪声估计等辅助特征;


      2. 聚类自适应训练(CAT);


      3. 隐层分解(FHL),相比于CAT,FHL只需要少量的训练数据,原因在于FHL的基是秩为1的矩阵,而CAT的基是满秩矩阵,在基数量一样的情况下,CAT需要更多的训练数据。


      实时性是语音识别应用中关注度很高的问题之一,实时性直接影响用户的体验感,提高语音识别的实时性可以通过降低运算时间成本与提升识别硬件计算能力两方面完成。


7、降低运算时间成本

      SVD,基于奇异值分解的数学原理,把满秩矩阵分解为两个低秩矩阵,减小深度模型的参数,且能够不降低模型识别性能;

压缩模型,使用向量量化或者极低比特量化算法;

      改变模型结构,主要针对LSTM,在LSTM中增加一个线性映射层,降低原有LSTM的输出维度,从而降低运算时间成本;

      使用跨帧的相关性来降低评估深度网络分数的频率,对于DNN或CNN而言,这可以通过使用跳帧策略完成,即每隔几帧才计算一次声学分数,并在解码时将该分数复制到没有评估声学分数的帧 。

      另外,提升识别阶段硬件的运算能力,开发专用的语音识别芯片对增强语音识别的实时性意义重大,下文将会在这方面展开讨论。


三、算法

       至此语音识别的大体过程已经OK了,下面来看具体传统模型GMM-HMM的算法。


       语音识别过程就是输入一段语音信号,找到一串文字(字或词)序列的过程,


语音输入



O =o1,o2,o3,...,ot

对应的标注




W =w1,w2,w3,...,w



这个过程一般用概率来表示,用O表示语音信号,用W表示文字序列,则是要解决下面这个问题:




android 免费语音识别 android语音识别开源库_DNN_02


由贝叶斯公式


android 免费语音识别 android语音识别开源库_DNN_03

展开,可得




android 免费语音识别 android语音识别开源库_语音识别_04




P(O|W )P(W ) / P(O) 是对每个句子进行计算的,而对每个句子来说 P ( O ) 是不变的, 所以可以改写成如下


android 免费语音识别 android语音识别开源库_DNN_05


即:


android 免费语音识别 android语音识别开源库_android 免费语音识别_06


其中P(O|) 称做观测最大释然,由声学模型计算可得


其中P(w)称做先验概率,由语言模型模型计算可得


综上所述,语音识别就是解码(decoding)过程,如下图所示:


android 免费语音识别 android语音识别开源库_语音识别_07





声学模型的任务是计算P(O|), 即给定文字之后发出这段语音的概率(最后利用贝叶斯,求P(O|)是使用)。 首先第一问题: 怎么才能知道每个单词发什么音呢? 这就需要另外一个模块,叫做词典,看eesen的源码在数据准备阶段就是先求出词对应音素的dict, 它的作用就是把单词串转化成音素串,然后再求的语言模型和 训练声学模型(用lstm+ctc 训练声学模型).

有了dict的帮助,声学模型就知道给定的文字串该依次发哪些音了。不过为了计算语音跟音素串的匹配程度,还需要知道每个音素的起止时间。 这是利用动归来进行的,可以高效的找到音素的分界点,使得每一段语音与音素的匹配程度(用概率表示)之积最大。实际使用的算法称为viterbi算法,它不仅仅考虑了每一段语音和音素的匹配程度,还考虑了各个音素之间转换的概率(转换概率通过HMM估计) 实际中使用的比音素更小的单位,原理一样(不是很确定,值得是一帧数据(25ms)吗,一帧不到不到一个音素的长度?)



在求音素分界点的过程中,以及在有了分界点后计算 P ( O | )时,声学模型都需要知道怎样计算一个音素与一段语音信号的匹配程度。要做这件事,需要找到一种合适的表示语音信号的方法。一般是把语音信号分成许多帧,对于每一帧,通过傅里叶变换等一系列操作,把它转换成一个特征向量。最常用的特征是MFCC,从训练数据中,我们可以提取出大量的特征向量,以及它们对应的音素;利用这些数据,就可以训练从特征到音素的分类器。前些年最常用的分类器是高斯混合模型(GMM),它的大致原理是估计出每个音素的特征向量的分布,然后在识别阶段,计算每一帧的特征向量x_t由相应音素s_i产生的概率P(x_t|s_i),把每一帧的概率相乘,就得到 P ( O | )。现在,神经网络渐渐火了起来,它可以直接给出P(s_i|x_i),用贝叶斯公式可以转换成P(x_t|s_i),再相乘得到 P ( O | )



P(O|) 称做观测最大释然,由声学模型计算可得,本章就主要描述HMM+GMM来计算最大释然的过程。

首先回顾一下:在解码过程中

android 免费语音识别 android语音识别开源库_DNN_08

P(O|)由声学模型训练得到,


P(O|)是W的似然函数,结合之前讲述的声学特征也就是说,在给定的W情况,使得当前的特征向量(MFCC)的概率最大,结合HMM的概念,也就是说在在t时刻给定状态qi 的前提下,求输出O的概率,即p(ot|qi) ,即矩阵B,状态对应的是word,phone或者subphone,在LVCSR中对应的是subphone

在解码阶段,在固定观测向量ot 的前提下,我们需要计算每一个HMM状态可能产生的概率,然后找到最大可能的状态(subphone)序列,所以训练过程就是计算观测似然矩阵B的过程。

理想的方式计算MFCC的时候,可以把输入的帧映射为一些离散的量化符号方便计算,如下图所示

android 免费语音识别 android语音识别开源库_语音识别_09

然后这么计算似然是有问题的,因为输入音频是连续的,特征基本变化很大的,很难进行比较好的聚类,因此提出连续空间的概率密度函数(PDF),最常用的计算声学似然的方式是高斯混合模型,即GMM模型(当然SVM,CRF,NN也可以)。

高斯分布也是一种正态分布,函数如下所示

android 免费语音识别 android语音识别开源库_DNN_10

不同的均值,方差下,对应的高斯分布如下所示:

android 免费语音识别 android语音识别开源库_语音识别_11

离散情况下,均值,方差计算如下所示:

 

android 免费语音识别 android语音识别开源库_建模_12

android 免费语音识别 android语音识别开源库_语音识别_13

当高斯函数用来当做概率密度函数时,曲线下的面积和应该为1,如下所示,灰色区域面积为0.341

android 免费语音识别 android语音识别开源库_android 免费语音识别_14


我们可以用单GMM pdf来估测一个特定的HMM状态j,产生一个单一维度的声学特征向量O的概率,(假设


ot 服从正态分布),换句话说,就是用对一维特征来说,一元高斯来代表观测似然函数bj(ot)  ,

假设HMM状态j 对应的均值方式是μj 和σ2j  ,那么计算似然  bj(ot) 可以通过Gaussian PDF来计算,如下所示:

android 免费语音识别 android语音识别开源库_DNN_15

有了以上公式,我们就可以来进行HMM decoding了

然而如何计算均值和方差呢,按理来说可以通过如下公式进行计算

android 免费语音识别 android语音识别开源库_android 免费语音识别_16


然而,状态是HMM隐藏状态,我们不知道Ot是由哪个状态产生的,

但是我们可以通过HMM中t时刻在状态i的概率来按比例分配,即把每个Ot分配给每个可能的状态i。

把ξt (i) 记做在t时刻状态i产生Ot的概率,

那么通过Baum-Welch 迭代算法进行计算,如下所示:

android 免费语音识别 android语音识别开源库_建模_17

也叫作HMM前向-后向(Baum-Welch)训练。

以上讨论的是一维特征,在实际中,MFCC是39维特征,因此我们使用了多元高斯模型,

多元高斯分布函数如下所示:

android 免费语音识别 android语音识别开源库_语音识别_18

协方差计算公式如下:

android 免费语音识别 android语音识别开源库_android 免费语音识别_19

则,高斯概率似然函数b j (ot ) 如下所示:

android 免费语音识别 android语音识别开源库_DNN_20

因为对角协方差计算量更小,所以可以简化为对角协方差

可以表述为:在t时刻,状态j产生声学特征向量Ot的似然函数可以表述为对角协方差多元高斯,

上述公式可以简化成如下公式:


android 免费语音识别 android语音识别开源库_android 免费语音识别_21

ξt (i) 记做t时刻,状态i产生Ot的似然,对应均值方差为:

android 免费语音识别 android语音识别开源库_DNN_22

以上的前提声学特征符合正态分布,但是实际倒谱特征MFCC不是正态分布,因此我们可以改进为

加权的混合多远高斯模型,即GMM,对应函数公式如下所示:

android 免费语音识别 android语音识别开源库_DNN_23

对应的输出似然函数bj(ot如下所示:

android 免费语音识别 android语音识别开源库_语音识别_24

把ξtm(j)记做t时刻,状态j,在m元 混合模型情况下产生声学特征Ot的概率,公式如下

android 免费语音识别 android语音识别开源库_android 免费语音识别_25

对应均值方差,同样可以由Baum-Welch 迭代算出来,公式如下:

android 免费语音识别 android语音识别开源库_DNN_26

在实际计算中,一个句子的似然是一串概率相乘,导致概率数值非常低,如.00000001=10−8 容易下溢出,而且不方便计算,,所以常常用log来计算,则上述输出似然函数(o)可以改写如下所示:

android 免费语音识别 android语音识别开源库_DNN_27

重写可得:

android 免费语音识别 android语音识别开源库_android 免费语音识别_28

其中C为:

android 免费语音识别 android语音识别开源库_建模_29

C为常数,可以再decoding之前提前计算出来,节省计算时间

以上就是GMM在训练声学模型中的应用。



本章主要讲解HMM训练过程,首先回顾上章的HMM模型如下:


Q =q1q2...qN   状态集合(subphone集合)


A =a01a02...an1...ann   状态(subphone)转移矩阵,Q和A构成了发音字典

B=bi(ot)  观测似然,也叫作发射概率 ,表述为:每个subphone状态i产生倒谱特征Ot的概率

最简单的方式是给定手工标注的孤立词和对应的音频文件,计算每个子音素(subphone)对应的标注来计算矩阵B,然而实际中却无法做到,因为每个subphone对应的Ot是很难用手工去标注的。(用手工去在一断连续的音频上标注一个子因素是不可行的)因此,训练每个phone的HMM是嵌入在整个句子中进行的,音素的分割和对其是在训练过程中自动进行的,所以整个这个训练过程叫做嵌入式训练(embedded training )

数据准备:在训练过程前,需要准备wav音频文件,对应的标注文本,还有发音字典,基于句子的HMM构建如下:

android 免费语音识别 android语音识别开源库_android 免费语音识别_30

接下来就是训练状态转移矩阵A和似然估计矩阵B了,

用ξj(t) 表示:在t时刻,状态i生成观测序列O的概率。

在初始阶段,我们需要对ai j和bj(ot) 一个初始的估计 ,最简单的做法叫做flat start,

在flat start中,状态转移矩阵中,状态的自环和跳转到下一个状态(subphone)的概率相同,为0.5,高斯的均值方差取全局训练数据的均值和方差。现在有了基础的HMM-GMM参数了,接下来就要在整个训练集合上跑Balum-Welch算法,每次迭代,都要修改HMM参数,直到系统趋于一致不变。首先在给定初始的矩阵A和B的情况下,计算前向-后向概率,然后,用前向-后向概率重新估算新的矩阵A和矩阵B,具体推导会在下一篇文章《HMM基础-HMM训练-前向后向算法》章节详细讨论。同时用EM算法来更新多元高斯模型的均值和方差。

综上所述,标准的嵌入式训练过程如下所述:

给定训练音频文件,标注文件,发音字典情况下

1)如上图所述,对每个句子,构建一个句子的HMM模型。

2)初始化状态转移中的非零元素(自环为0.75,和跳转到下一个状态的为0.25)

3)初始化发射概率矩阵每个高斯的均值方差为所以训练集合的全局均值和方差。

4)EM迭代多次,用Viterbi来计算ξj(t) (在t时刻,状态i生成观测序列O的概率),

为计算ξj(t) ,要累计所有可能的路径,这样计算太慢了,一个更高效的算法是Viterbi 算法,

训练算法中,不再是用前向-后向算法累计所有的路径来计算ξj(t),而是通过重复的跑Viterbi路径(最大概率路径)

来接近估测这个值。

用Viterbi来训练数据过程,也叫作强制Viterbi对齐,或强制对齐过程

在Viterbi对齐过程中,因为已经知道观察序列对应的词序列,所以合适的设置aij ,就可以强制Viterbi算法来通过某个指定的词。

Viterbi对齐其实是Viterbi解码的一个简化版,因为,Viterbi强制对齐过程中只需要找到观测值Ot对应正确的状态(或subphone)序列,而不需要找到正确的词序列。训练结果就是Viterbi强制对齐:即,对应观测序列O的,一条最优状态序列。

接下来,我们可以用对齐的HMM状态到累计counts,从而重新估计HMM参数。


Viterbi对齐中重新训练高斯模型参数的公式如下所示:

android 免费语音识别 android语音识别开源库_建模_31

android 免费语音识别 android语音识别开源库_android 免费语音识别_32

高斯混合模型的计算参加上一篇文章。

以上就是嵌入式训练过程。


上一篇讨论了语音识别中的训练过程,本章讨论语音识别中,解码的过程。

解码的过程就是在给定声学特征的情况下,找到最可能对应的词组的过程,再次看如下求解目的的公式:

android 免费语音识别 android语音识别开源库_建模_33

其中似然概率是在一系列给定声学frame情况下,计算每个对应的分类器得分,然后相乘得出的概率,使得其值变得很小,而P(W)比较大,这样就导致

P(w)权重太大了,所以需要对齐进行缩放,以平衡贡献值,所以把上面公式改写如下:

android 免费语音识别 android语音识别开源库_android 免费语音识别_34

因为P(w)小于1,使LMSF大于1,(5-15),这样就减小了P(w)对整个公式的贡献,以达到缩放的目的。

但是在P(w)中以上惩罚对词插入的情况下是有副作用的,所以改写如下:

android 免费语音识别 android语音识别开源库_语音识别_35

在log形式展开,最后解码的目标就是如下公式所示:

android 免费语音识别 android语音识别开源库_DNN_36

有了上述的目标公式,接下来就要讨论,如何解码取其最大值

解码中最常用的是Viterbi算法,首先看一下语音识别中HMM模型:


Q =q1q2...qN   对应subphone的状态序列


A =a01a02...an1...ann   状态转移矩阵(自环和转到下一个状态)


B =bi(ot)   观测似然,或者叫做发射概率,代表在t时刻,状态i产生声音倒谱特征O的概率

其中A和B由上一章中的嵌入式训练得到。下图为识别数字的HMM结构图。

android 免费语音识别 android语音识别开源库_建模_37

首先我看用前向算法O(N2T) 来进行解码的过程,

举例如下:英文字母five,有对应状态[f], [ay], 和[v] ,观测序列O,如下所示:

     

android 免费语音识别 android语音识别开源库_建模_38

android 免费语音识别 android语音识别开源库_语音识别_39

首先我们引入αt(j)  ,记做:在看见前t个观测值之后,处于状态j的概率。

android 免费语音识别 android语音识别开源库_语音识别_40

这里qt=j 表示在状态序列中,t时刻状态为j,

αt ( j) 可以理解为,所有能到到达当前状态的所有路径的概率之和,即:

android 免费语音识别 android语音识别开源库_DNN_41

其中,αt1(i) 表示前一个(t-1)之前的路径的概率,

aij 表示概率转移矩阵,表示从状态qi  到当前状态q j 的概率


bj(ot) 叫做状态观测似然,也叫作发射概率,表示给定状态j,观测到ot  的概率

前向算法如下所示:

android 免费语音识别 android语音识别开源库_DNN_42

qF 表示结束状态。

假设矩阵A自环概率概率为0.5,假设矩阵B如下所示:

android 免费语音识别 android语音识别开源库_android 免费语音识别_43

则单词“five”的前向算法过程如下所示:

android 免费语音识别 android语音识别开源库_android 免费语音识别_44

接着讨论Viterbi算法,

Viterbi算法是返回最可能的状态序列,虽然不是最可能的次序列,但是也足够接近了,其时间复杂度为

O(N2T) ,

使得vt(j)记做:在给定 λ 的情况下,在看到前t个观测特征Ot,且通过了最可能的q1...qt−1 状态序列的情况下,HMM当前状态为j的概率,即:

android 免费语音识别 android语音识别开源库_android 免费语音识别_45

根据动态规划算法,可以理解为t-1时刻的在状态i时候的最大概率路径到当前时刻t时候,状态为j的概率,记做:

android 免费语音识别 android语音识别开源库_语音识别_46

vt−1(i) 表示:前一时刻,Viterbit路径的概率;

ai j 表示:状态qi 到状态qj的概率 ;


bj(ot) 表示:状态观测似然,表示为给定状态j,产生观测向量ot  的概率。

根据上式可知,Viterbi算法是在给定观测序列o= (o1o2o3...oT) 情况下,找到最优的状态序列q=(q1q2q3...qT)的过程

,同时找到对应的最大的概率。对比上面的前向算法可知,他们目标都是一致的,但是Viterbi算法是求其最大值。

Viterbi具体解码算法如下所示:(假设起始状态为0,结束状态为qF,是非发射状态)

android 免费语音识别 android语音识别开源库_android 免费语音识别_47

例子:

假设矩阵A自环概率概率为0.5,假设矩阵B如下所示:

android 免费语音识别 android语音识别开源库_android 免费语音识别_43

则对应的计算数值如下所示:

android 免费语音识别 android语音识别开源库_语音识别_49

Viterbi解码的真正用处不仅仅是在词内解码,更重要的是可以解码一串词,为了使Viterbi能够进行词间进行解码,我们得增加矩阵A,使其不仅要有词内的状态转移概率,还需要增加从一个词末尾,到另一个词开始的状态转移概率。

下图补充了2-gram间的转移概率,

android 免费语音识别 android语音识别开源库_DNN_50

下图展示了2-gram词间解码的过程:

android 免费语音识别 android语音识别开源库_语音识别_51

一旦一句话的Viterbi解码计算完毕,就可以用过后项指针回溯,来找到最大概率的状态序列,即最大概率的词序列。

如下图所示:最后的回溯词序列为w1wN···w2 

android 免费语音识别 android语音识别开源库_建模_52

实际上Viterbi解码过程中需要进行beam剪枝,通过设定beam的宽度θ 来进行beam剪枝。

Beam剪枝算法我们以后章节继续讨论。

以上就是语音识别过程中,解码的所有细节,谢谢!



由于本人实在看不懂这些算法,这也是个人理出来的算法流程逻辑,如有错误请见谅!

详情请参考博客


四、PocketSphinx的安装、编译、训练以及一些坑

       先说一些遇到的问题:1、我说过在Sphinx0.8以后,Sphinx就没有支持中文model,(后面是猜测)所以Sphinx并没有更新和优化中文相关的逻辑,也就是中文模型的训练没搞好,在实际应用的时候在他的网站训练后的Grammer存在一定误识别率,我想原因就是因为它的训练集比不是很多。2、在0.8之后,现在最新的版本中没有中文模型,所以面临的问题也是训练数据集不足,也会出现误识别率很高,所以根据官网给出的建议,200人5小时的训练时长(非特定人)和覆盖较大的字典(需大词汇量,否则小词汇量会导致分类较少)是比较好的选择。

       具体安装、编译、训练方法,请根据描述进行,已验证过可行。这里不做过多描述了。注意的细节我之后会总结!

       对于PocketSphinx的代码框架结构网上没有找到解析,但是可以根据http://ishare.iask.sina.com.cn/f/66428902.html这篇文章(讲解Sphinx4的代码结构)其中氛围预处理模块、搜索管理模块、语言模块。大部分逻辑在SphinxBase项目中、PocketSphinx做一些封装调用(猜的)。


五、总结

       说一些我个人看法。

1)以我们当前只做非特定人、小词汇量、半连续语音识别,注意的同样在于训练数据集较少,导致误识别率很高。

2)语音识别框架最好用C/C++,好处:可移植性强、高效、安全。