python语⾳识别库kaldi_Kaldi使⽤DFSMN训练语⾳模型 阿⾥巴巴 2018 年开源的语⾳识别模型 DFSMN,将全球语⾳识别准确率纪录提⾼⾄ 96.04%。DFSMN 模型,是阿⾥巴巴的⾼效⼯业级实现,相对于传统的 LSTM、BLSTM 等声学模型,该模型具备训练速度更快、识别更⾼效、识别准确率更⾼和模型⼤⼩压缩等效果。 本场 Chat 的主要内容包括:
语⾳识别流程简介
Kaldi 的部署使⽤网站实时监控
如何训练基于中⽂的 DFSMN 声学模型
语⾳特征提取 MFCC 算法源码解读
语⾳识别⼯具对⽐
语⾳识别流程简介
语⾳识别,通俗来讲,就是将⼀段语⾳信号转换成对应的⽂本信息。具体来说,语⾳识别是从⼀段连续
声波中采样,将每个采样值量化;然后对量化的采样⾳频进⾏分帧,对于每⼀帧,抽取出⼀个描述频谱内容的特征向量;最后根据语⾳信号的特征识别语⾳所代表的单词。
下图展⽰了语⾳识别的整个流程:
通过上图可以看到,语⾳识别的整个流程,主要包含特征提取和解码(声学模型、字典、语⾔模型)部分。 特征提取:从语⾳波形中提取出随时间变化的语⾳特征序列(即将声⾳信号从时域转换到频域),为声学模型提供合适的特征向量。主要算法有线性预测倒谱系数(LPCC)和梅尔频率倒谱系数(MFCC)。
声学模型:根据声学特性计算每⼀个特征向量在声学特征上的得分,输⼊是特征向量,输出为⾳素信息。最常⽤的声学建模⽅式是隐马尔科夫模型(HMM),基于深度学习的发展,深度神经⽹络(DNN)、卷积神经⽹络(CNN)、循环神经⽹络(RNN)等模型在观测概率的建模中取得了⾮常好的效果。
字典:字或者词与⾳素的对应,中⽂就是拼⾳和汉字的对应,英⽂就是⾳标与单词的对应。(⾳素,单词的发⾳由⾳素构成。对英语来说,⼀种常⽤的⾳素集是卡内基梅隆⼤学的⼀套由 39 个⾳素构成的⾳素集,汉语⼀般直接⽤全部声母和韵母作为⾳素集)。
语⾔模型:通过对⼤量⽂本信息进⾏训练,得到单个字或者词相互关联的概率。语⾳识别中,最常见
的语⾔模型是 N-Gram。近年,深度神经⽹络的建模⽅式也被应⽤到语⾔模型中,⽐如基于 CNN 及 RNN 的语⾔模型。
解码:通过声学模型、字典、语⾔模型对提取特征后的⾳频数据进⾏⽂字输出。
在语⾳识别整个流程中,声学模型作为识别系统的底层模型,声学模型的任务是计算 P(O|W)P(O|W)(即模型⽣成观察序列的概率),它占据着语⾳识别⼤部分的计算开销,决定着语⾳识别系统的性能。所以,声学模型是语⾳识别系统中最关键的⼀部分。
本次 Chat 主讲的阿⾥巴巴的 DFSMN 声学模型,是建⽴在另⼀个开源的语⾳识别⼯具 Kaldi 基础之上的,或者如官⽹所说的:
DFSMN 是 Kaldi 的⼀个补丁⽂件,所以,为了使⽤ DFSMN 模型,我们必须先部署 Kaldi 语⾳识别⼯具。
Kaldi 的部署使⽤
Kaldi 是⼀个开源的语⾳识别⼯具库,⾪属于 Apache 基⾦会,主要由 Daniel Povey 开发和维护。Kaldi 内置功能强⼤,⽀持 GMM-HMM、SGMM-HMM、DNN-HMM 等多种语⾳识别模型的训练和预测。随着深度学习的影响越来越⼤,Kaldi ⽬前对 DNN、CNN、LSTM 以及 Bidirectional-LSTM 等神
经⽹络结构均提供模型训练⽀持。
笔者根据官⽅⽂档实现 Kaldi 的安装,并将阿⾥的 DFSMN 补丁加载到 Kaldi。以下是部署的完整步骤。
1. 下载 Kaldi 源码
mcu解密
2. 切换到 kaldi-trunk ⽬录,下载补丁源码
[zss@gpu-1-0 ~]$ cd kaldi-trunk/
3. 检查补丁发泄壶
[zss@gpu-1-0 kaldi-trunk]$ git checkout 04b1f7d6658bc035df93d53cb424edc127fab819
4. 将补丁加载到 Kaldi 分⽀
看看补丁中有什么变化:
[zss@gpu-1-0 kaldi-trunk]$ git apply --stat Alibaba-MIT-Speech/Alibaba_MIT_Speech_DFSMN.patch
测试补丁:
[zss@gpu-1-0 kaldi-trunk]$ git apply --check Alibaba-MIT-Speech/Alibaba_MIT_Speech_DFSMN.patch
添加 Git 账户邮箱和⽤户名,否则⽆法应⽤补丁。
[zss@gpu-1-0 kaldi-trunk]$ git config --ail "userEmail"
[zss@gpu-1-0 kaldi-trunk]$ git config --global user.name "username"
应⽤补丁:
[zss@gpu-1-0 kaldi-trunk]$ git am --signoff < Alibaba-MIT-Speech/Alibaba_MIT_Speech_DFSMN.patch
5. 安装 Kaldi
切换到 tools ⽬录中,⾃动检测并安装缺少的依赖包,直到出现 all OK 为⽌。
[zss@gpu-1-0 tools]$ extras/check_dependencies.sh
编译 –j 参数表⽰内核数,根据⾃⼰环境设定运⽤多少内核⼯作。
[zss@gpu-1-0 tools]$ make -j 24
切换到 src ⽬录下,进⾏安装。
[zss@gpu-1-0 src]$cd ../src
[zss@gpu-1-0 src]$ ./configure –shared
继续安装,执⾏以下命令,最后⼀⾏是 SUCCESS 表明成功。
[zss@gpu-1-0 src]$ make depend -j 24
继续安装,执⾏以下命令,若最后⼀⾏是 Done 则安装成功。
[zss@gpu-1-0 src]$ make -j 24
⾃动安装其它扩展包,执⾏以下命令:
[zss@gpu-1-0 src]$ make ext
运⾏⾃带的 demo,检测是否成功。
切换到 /kaldi-trunk/egs/yesno/s5 ⽬录下,运⾏程序。
[zss@gpu-1-0 src]$ cd ../egs/yesno/s5/
[zss@gpu-1-0 s5]$ ./run.sh
通过运算 WER 为 0,在运算过程中,WER 越⼩,代表错误率越低。
⾄此,我们已经在服务器成功部署了 Kaldi ⼯具,并把 DFSMN 补丁打到该⼯具中。接下来,我们将利⽤该⼯具训练声学模型。训练基于中⽂的 DFSMN 声学模型
数据准备阶段
数据集包含如下内容:
下载数据集到服务器指定位置,进⼊到 ../thchs30-openslr/data_thchs30 位置,我们可以看到如下⽬录内容:其中 data ⽬录下包含所有训练的语⾳以及标注⽂件,我们随机打开⼀个语⾳标注⽂件,可以看到:
语⾳标注⽂件主要包含三部分:
分词后的语⾳⽂字
⽂字对应的拼⾳(含⾳调)
⽂字对应的⾳素(中⽂为声母、韵母)
准备完语料集,接下来我们将看⼀下如何修改脚本,训练 DFSMN 声学模型。
修改脚本阶段
进⼊到 Kaldi 的中⽂模型训练路径 ../kaldi-trunk/egs/thchs30/s5,我们可以看到如下内容:
⾸先,修改 cmd.sh 脚本,把原脚本注释掉,修改为本地运⾏:
然后,修改 run.sh 脚本,主要修改如下:
n=8 #指定并⾏任务数
thchs=../chinese-data/thchs30-openslr #设定数据集位置
选定阿⾥巴巴 DFSMN 模型类型,添加如下内容到 run.sh 脚本最后:
# ## Traing FSMN models on the cleaned-up data
# ## Three configurations of DFSMN with different model size: DFSMN_S, DFSMN_M, DFSMN_L
local/nnet/run_fsmn_ivector.sh DFSMN_S
# local/nnet/run_fsmn_ivector.sh DFSMN_M
# local/nnet/run_fsmn_ivector.sh DFSMN_L
训练模型阶段
执⾏ ./run.sh,开始训练模型,整个模型的训练过程⼤约 2 天左右。
钢筋塑料垫块模型使⽤阶段
最终训练完毕的⽂件⽬录如下:
进⼊到 exp ⽬录,我们会看到训练出来的模型:
run.sh 脚本会训练出 5 种模型:
monophone 单⾳素模型是训练单⾳⼦隐马尔科夫模型,⼀共进⾏ 40 次迭代;
tri1 三⾳素模型是训练与上下⽂相关的三⾳⼦模型;
tri2b 模型⽤来进⾏线性判别分析和最⼤似然线性转换;
tri3b 模型⽤来训练发⾳⼈⾃适应,基于特征空间最⼤似然线性回归;
tri4b 模型⽤来在现有特征上训练模型,它基于树统计中的计数的重叠判断的相似性来选择旧模型中最接近的状态。
注:笔者使⽤ GPU 训练的模型,如果不使⽤ GPU,最终训练不出 DNN 模型。
接下来我们将使⽤ tri4b 模型,识别我们提供的语⾳⽂件内容。
⾸先,拷贝 kaldi-trunk/egs/voxforge/online_demo 到 thchs30 下并重命名 online_demo_tri4b_ali,和 s5 同级;
online_demo_tri4b_ali 新建 online-data 和 work 两个⽂件夹;online-data 下新建 audio 和 models,audio 放要识别的
wav,models 新建 tri4b,最终⽬录结构如下:
然后,将 s5/exp/tri4b 下的 final.alimdl、12.mat、final.mat、20.mdl拷贝到 models/tri4b,把 s5/exp/tri4/graph_word ⾥⾯的 和 HCLG.fst 也拷过去。
最终⽬录结构:
最后,修改 run.sh 脚本,主要修改以下三部分内容:
ac_model_type=tri4b #使⽤的模型类型
online-gmm-decode-faster --rt-min=0.5 --rt-max=0.7 --max-active=4000 --beam=12.0 --acoustic-scale=0.0769 --left-context=3 --right-context=3 `$ac_model/final.alimdl $ac_model/HCLG.fst $ac_ '1:2:3:4:5' $`trans_matrix;;
online-wav-gmm-decode-faster --verbose=1 --rt-min=0.8 --rt-max=0.85 --max-active=4000 --beam=12.0 --acoustic-
scale=0.0769 --left-context=3 --right-context=3 scp:`$decode_dir/input.scp $ac_model/final.alimdl $ac_model/HCLG.fst $ac_ '1:2:3:4:5' ark,t:$decode_ ark,t:$decode_ $`trans_matrix;;
修改完毕,上传⼀段⾃⼰录制的⾳频到 online-data/audio ⽬录,执⾏ ./run.sh,识别语⾳结果如下:
通过与语⾳源⽂对⽐后,发现准确率并没有阿⾥说的 96.04%,这是为什么?
笔者重新研究了⼀下 DFSMN 模型的论⽂,以及到 GitHub 深扒了⼀下 DFSMN 源码⽂件,发现阿⾥所说的准确率⾼到 96.04% 是建⽴在 5000 个⼩时训练集基础之上的,⽽我们仅⽤了 30 个⼩时的训练集。
所以,如果我们想提⾼准确率,必须标注更多的语⾳⽂件,加⼤训练集。
如果我们已经成功地训练出了模型,可进⼊到 s5/data/mfcc/train ⽬录,查看 spk2utt ⽂件,如下:
红⾊⽅框是不同说话⼈的标号,右边的每⾏对应由该说话⼈录⾳的⽂件。通过上图,我们可以知道 Kaldi ⼯具不仅帮我们训练了模型,还帮我们识别出了有多少个录⾳⼈,即说话⼈识别技术。
那 Kaldi 是怎么做到的?接下来将从算法⾓度为⼤家分享⼀下 Kaldi 的特征提取技术。
语⾳特征提取 MFCC 算法源码解读
MFCC(MeI-Freguency CeptraI Coefficients)是语⾳特征参数提取⽅法之⼀,因其独特的基于倒谱的提
取⽅式,更加符合⼈类的听觉原理,因⽽也是最为普遍、最有效的语⾳特征提取算法。通过 MFCC,我们可以有效地区分出不同的⼈声,识别不同的说话⼈。
MFCC 语⾳特征的提取过程,如下图:
预加重
预加重其实就是将语⾳信号通过⼀个⾼通滤波器,来增强语⾳信号中的⾼频部分,并保持在低频到⾼频的整个频段中,能够使⽤同样的信噪⽐求频谱。在本实验中,选取的⾼通滤波器传递函数为:
y(n)=x(n)−a∗x(n−1)y(n)=x(n)−a∗x(n−1)
aa 为预加重系数,我们通常取 aa=0.97。预加重部分源码:
def pre_emphasis(signal, coefficient=0.97):
'''对信号进⾏预加重'''
return numpy.append(signal[0], signal[1:] - coefficient * signal[:-1])
分帧
分帧是指在给定的⾳频样本⽂件中,按照某⼀个固定的时间长度分割,分割后的每⼀⽚样本,称之为⼀帧。
分帧部分对应的源码:
def audio2frame(signal, frame_length, frame_step, winfunc=lambda x: s((x,))):
'''分帧'''
signal_length = len(signal)
frame_length = int(round(frame_length))
frame_step = int(round(frame_step))
if signal_length <= frame_length:
智能召唤frames_num = 1
else:
frames_num = 1 + il((1.0 * signal_length - frame_length) / frame_step))
pad_length = int((frames_num - 1) * frame_step + frame_length)
zeros = s((pad_length - signal_length,))
pad_signal = atenate((signal, zeros))
indices = numpy.tile(numpy.arange(0, frame_length), (frames_num, 1)) + numpy.tile(numpy.arange(0, frames_num *
frame_step, frame_step),(frame_length, 1)).T
indices = numpy.array(indices, dtype=numpy.int32)
裹尸袋
frames = pad_signal[indices]
win = numpy.tile(winfunc(frame_length), (frames_num, 1))
return frames * win
分帧是先将 N 个采样点集合成⼀个观测单位,也就是分割后的帧。通常情况下 N 的取值为 512 或 256,涵盖的时间约为 20~30ms。N 值和窗⼝间隔可动态调整。为避免相邻两帧的变化过⼤,会让两
相邻帧之间有⼀段重叠区域,此重叠区域包含了 M 个取样点,⼀般 M 的值约为 N 的 1/2 或 1/3。
语⾳识别中所采⽤的信号采样频率⼀般为 8kHz 或 16kHz。以 8kHz 来说,若帧长度为 256 个采样点,则对应的时间长度是
256/8000*1000=32ms。本次测试中所使⽤的采样率为 16kHz,窗长 37.5ms(600 个采样点),窗间隔为 10ms(160 个采样点)。
加窗
在对⾳频进⾏分帧之后,需要对每⼀帧进⾏加窗,以增加帧左端和右端的连续性,减少频谱泄漏。⽐较常⽤的窗⼝函数为 Hamming 窗。
假设分帧后的信号为 S(n),n=0,1,2…,N−1S(n),n=0,1,2…,N−1,其中 NN 为帧的⼤⼩,那么进⾏加窗的处理为:
W(n)W(n) 的形式如下:
不同的 aa 值会产⽣不同的汉明窗,⼀般情况下 aa 取值 0.46。
加窗部分的源码:
def deframesignal(frames, signal_length, frame_length, frame_step, winfunc=lambda x: s((x,))):
'''加窗'''