1 背景
杰出的科学家和工程师们一直在努力地给机器赋予自然交流的能力,语音识别就是其中的一个重要环节。人类对语音识别技术的研究从上世纪 50 年代开始就未曾停止。在长期的探索中,一次次重大的技术突破逐渐让语音识别技术进入我们的日常生活。今天的 ASR 技术水平是前所未有的。高性能的语音识别给我们带来了更多的生活体验,我们拥有了可以对话的智能数字助手;它也在逐步改善相关领域的生产力水平。
和很多伟大技术的应用一样,语音识别技术的背后也是很多模块的组合。对其实现流程的改进往往会从一定程度上节省开发成本,并且加快技术迭代的速度。Pytorch-Kaldi 的出现就是基于这样的动力。
1.1 语音识别系统的组成
图 1. 语音识别系统的结构
一个典型的语音识别系统如图 1 所示。它包含 4 个组成部分:信号处理和特征提取、声学模型、语言模型和解码搜索。声学模型是其中的核心部分,我们可以把它理解为说话声音和语言发音之间的映射。而信号处理和特征提取就是用于构建声学模型的材料了,它主要依靠数字信号处理相关的技术。语言模型则可以被看作是语言先验知识。语音识别的最终结果就是在声学模型得分和语言模型得分上进行搜索得到的。具体的内容这里不做展开。
在语音识别技术的发展史上,深度学习绝对是极具影响力的。可以说,没有对深度学习的引入,就不会有今天如此先进的语音识别引擎。
1.2 业界的基本现状
一个成功的语音识别系统真的是很难离开一众优秀开源框架的支撑,比如:HTK,Julius,CMU-Sphinx,PWTH-ASR,LIA-ASR 以及 Kaldi。后来居上的 Kaldi 独领风骚,拥有活跃的技术社区,被广泛的应用在语音识别技术的研究和系统开发中。据笔者了解,很多国内语音公司的语音识别系统也有着对 Kaldi 或多或少的依赖。图 2 是在本文写作的时,GitHub 上 Kaldi 项目的「盛景」。
图 2. kaldi-asr
但是,Kaldi 也有不尽如人意的地方,它依赖大量的脚本语言,而且核心算法使用 C++编写的,对声学模型的更新就不是一件容易的事情了,尤其是在需要改变各种神经网络的结构时。即便是拥有丰富经验的工程师,在调试的时候也会经历巨大的痛苦。当然,尽管如此,Kaldi 还是一项伟大的工作。
有问题存在,便有了改进的需要。Yoshua Bengio 团队成员 Mirco Ravanelli 等人开发了一个试图继承 Kaldi 的效率和 PyTorch 的灵活性的开源框架——PyTorch-Kaldi。相关的论文已经在 ICASSP 2019 上发表了,论文标题如图 3 所示。
图 3. PyTorch-Kaldi 论文首页
1.3 Why pytorch-kaldi?
正如论文提到的一句话,「The PyTorch-Kaldi project aims to bridge the gap between Kaldi and PyTorch」,PyTorch-Kaldi 就是为了弥补 PyTorch 和 Kaldi 之间的鸿沟。在 PyTorch 中实现声学模型,在 Kaldi 中执行特征提取、标签/对齐计算和解码。这也再次从侧面证明了 PyTorch 作为一个深度学习框架所具有的的卓越的灵活性和便利性。事实上,很多人都认为 PyTorch 比 TensorFlow 更加适合做研究工作。本文的第二部分将会重点介绍一下 PyTorch-Kaldi 开源工具。
2 PyTorch-Kaldi 简介
PyTorch-Kaldi 项目的结构如图 4 所示。正如前面所提到的,在这个项目中,PyTorch 和 Kaldi 在项目中的分工是比较明确的。主脚本 run_exp.py(后面称其为主脚本)是用 Python 写的,它负责管理 ASR 系统的所有阶段,包括特征和标签提取、训练、验证、解码和打分。目前版本(v0.2)的 PyTorch-Kaldi 实现了混合 DNN-HMM 的语音识别器。
图 4. PyTorch-Kaldi 项目结构
2.1 配置文件
主脚本以 INI 格式的配置文件为输入,这个配置文件在项目文档中有着很详细的描述。配置文件主要由以下 5 部分内容组成。
[exp]:这部分指定了一些高级信息,例如用于实验的文件夹、训练迭代次数、随机数种子,它也允许用户指定实验是在 CPU/GPU 或者多 GPU 上进行,下面是一个例子:
[exp]
cmd =
run_nn_script = run_nn
out_folder = exp/TIMIT_liGRU_fmllr
seed = 4234
use_cuda = True
multi_gpu = False
save_gpumem = False
n_epochs_tr = 24
[dataset]:这部分指定了特征和标签。包括它们的存储路径、窗口的特征以及数据集被分割成块的数量。下面是一个例子:
[dataset1]
data_name = TIMIT_tr
fea = fea_name=mfcc
fea_lst=quick_test/data/train/feats_mfcc.scp
fea_opts=apply-cmvn --utt2spk=ark:quick_test/data/train/utt2spk ark:quick_test/mfcc/train_cmvn_speaker.ark ark:- ark:- | add-deltas --delta-order=2
ark:- ark:- |
cw_left=0
cw_right=0
fea_name=fbank
fea_lst=quick_test/data/train/feats_fbank.scp
fea_opts=apply-cmvn --utt2spk=ark:quick_test/data/train/utt2spk ark:quick_test/fbank/cmvn_train.ark
ark:- ark:- | add-deltas --delta-order=0 ark:- ark:- |
cw_left=0 cw_right=0
fea_name=fmllr fea_lst=quick_test/data/train/feats_fmllr.scp
fea_opts=apply-cmvn --utt2spk=ark:quick_test/data/train/utt2spk
ark:quick_test/data-fmllr-tri3/train/train_cmvn.ark
ark:- ark:- | add-deltas --delta-order=0
ark:- ark:- |
cw_left=0
cw_right=0
lab = lab_name=lab_cd
lab_folder=quick_test/dnn4_pretrain-dbn_dnn_ali
lab_opts=ali-to-pdf
lab_count_file=auto
lab_data_folder=quick_test/data/train/
lab_graph=quick_test/graph
lab_name=lab_mono
lab_folder=quick_test/dnn4_pretrain-dbn_dnn_ali
lab_opts=ali-to-phones --per-frame=true
lab_count_file=none
lab_data_folder=quick_test/data/train/
lab_graph=quick_test/graph
n_chunks = 5
[architecture] 这部分描述神经网络模型,下面是一个例子:
2.2 语音特征
[model]:这部分定义了神经网络的结合方式,下面是一个例子:
[model]
model_proto = proto/model.proto
model = out_dnn1=compute(liGRU_layers,fmllr)
out_dnn2=compute(MLP_layers,out_dnn1)
out_dnn3=compute(MLP_layers2,out_dnn1)
loss_mono=cost_nll(out_dnn3,lab_mono)
loss_mono_w=mult_constant(loss_mono,1.0)
loss_cd=cost_nll(out_dnn2,lab_cd)
loss_final=sum(loss_cd,loss_mono_w)
err_final=cost_err(out_dnn2,lab_cd)
[forward]
forward_out = out_dnn2
normalize_posteriors = True
normalize_with_counts_from = lab_cd
save_out_file = False
require_decoding = True
[decoding]:这部分定义了解码参数,下面是一个例子:
[decoding]
decoding_script_folder = kaldi_decoding_scripts/
decoding_script = decode_dnn.sh
decoding_proto = proto/decoding.proto
min_active = 200
max_active = 7000
max_mem = 5000000 0
beam = 13.0
latbeam = 8.0
acwt = 0.2
max_arcs = -1
skip_scoring = false
scoring_script = local/score.sh
scoring_opts = "--min-lmwt 1 --max-lmwt 10"
norm_vars = False
2.2 语音特征和标签
语音特征是使用 Kaldi 中的原生 C++库进行提取的。PyTorch-Kaldi 的一个特点就是可以管理多个语音特征流,用户在实际开发的过程中可以定义使用多个特征参数组合的模型。
用于训练声学模型的主要特征来自于语音特征和上下文无关的音素序列,这是通过 Kaldi 的音素决策树生成的。
2.3 chunk 和 minibatch 的组成
PyTorch-Kaldi 将数据集分成了多个块,每个块中的样本都是从整个语料库中随机抽样得到的。然后再训练的过程中每次迭代只使用一个小批量的数据,这也是神经网络优化的常用方法。
不过,小批量数据的聚集方式是由神经网络的结构决定的,对于普通的前馈模型而言,随机选择数据就行。但是对于 RNN 这类网络而言,minibatch 则必须由完整的句子组成。
2.4 DNN 声学模型、解码和打分
PyTorch-Kaldi 已经定义好了一些先进的神经网络模型,目前支持标准的 MLP、CNN、RNN、LSTM、GRU、Light GRU 等。实际上这部分就是神经网络模型的训练和优化。
在进行基于 HMM 的解码之前,声学模型产生的声学后验概率与其先验概率进行归一化之后便和语言模型生成的语言概率,常用的语言模型就是 n-gram 模型。然后使用波束搜索算法得到语音信号中的单词序列。最后使用 NIST SCTK 工具计算字错率(WER)。
原论文的实验部分展示了使用 PyTorch-Kaldi 进行的多组语音识别相关的实验,在一些数据集和任务上面还达到了目前最高的水平。
3 总结
就其整体架构和 Mirco Ravanelli 等人表现出来的「野心」来看,PyTorch-Kaldi 的潜力是比较大的。项目文档中关于下一个版本的描述是这样写的:「The architecture of the toolkit will be more modular and flexible. Beyond speech recognition, the new toolkit will be suitable for other applications such as speaker recognition, speech enhancement, speech separation, etc.」。当然,这只是一个工具而已,如果没有对语音识别技术的深刻理解,肯定是做不出更好东西的。许愿:有更多的人力和资源积极地投入到这个领域,帮助让 PyTorch-Kaldi 变得更好,或者打造出全新的比 PyTorch-Kaldi 更好的工具。
参考资料
[1] M. Ravanelli, T. Parcollet and Y. Bengio, "The Pytorch-kaldi Speech Recognition Toolkit," ICASSP 2019 - 2019 IEEE International Conference on Acoustics, Speech and Signal Processing (ICASSP), Brighton, United Kingdom, 2019, pp. 6465-6469.doi: 10.1109/ICASSP.2019.8683713
[2] D. Yu and L. Deng, Automatic Speech Recognition – A Deep Learning Approach, Springer, 2015.
[3] Kaldi 文档(kaldi-asr.org/doc/)(http://kaldi-asr.org/doc/%EF%BC%89)
[4] PyTorch-Kaldi Github 仓库(https://github.com/mravanelli/pytorch-kaldi)(https://github.com/mravanelli/pytorch-kaldi%EF%BC%89)
[5] 王赟. 语音识别技术的前世今生(https://www.zhihu.com/lives/843853238078963712%EF%BC%89)(https://www.zhihu.com/lives/843853238078963712%EF%BC%89%EF%BC%89)