本文主要解读thchs30/s5/run.sh的主要步骤,以及一些编写脚本过程中可能遇到的问题。对于文中的差错,欢迎各位指正!
1.数据准备
在处理数据之前,我们要知道thchs30数据集包含三部分:train(训练集)、dev(开发集)和test(测试集)。其中dev的作用是在某些步骤与train进行交叉验证的,如local/nnet/run_dnn.sh
同时用到exp/tri4b_ali
和exp/tri4b_ali_cv
。训练和测试的目标数据也分为两类:word(词)和phone(音素)。
local/thchs-30_data_prep.sh
主要工作是从$thchs/data_thchs30
(下载的数据)三部分分别生成word.txt(词序列),phone.txt(音素序列),text(与word.txt相同),wav.scp(语音),utt2pk(句子与说话人的映射),spk2utt(说话人与句子的映射)
#produce MFCC features
是提取MFCC特征,分为两步,先通过steps/make_mfcc.sh
提取MFCC特征,再通过steps/compute_cmvn_stats.sh
计算倒谱均值和方差归一化。#prepare language stuff
是构建一个包含训练和解码用到的词的词典。而语言模型已经由王东老师处理好了,如果不打算改语言模型,这段代码也不需要修改。
- 基于词的语言模型包含48k基于三元词的词,从gigaword语料库中随机选择文本信息进行训练得到,训练文本包含772000个句子,总计1800万词,1.15亿汉字
- 基于音素的语言模型包含218个基于三元音的中文声调,从只有200万字的样本训练得到,之所以选择这么小的样本是因为在模型中尽可能少地保留语言信息,可以使得到的性能更直接地反映声学模型的质量。
- 这两个语言模型都是由SRILM工具训练得到。
2.算法流程
文献[1]介绍了thchs30示例的主要流程。
- 首先用标准的13维MFCC加上一阶和二阶导数训练单音素GMM系统,采用倒谱均值归一化(CMN)来降低通道效应。然后基于具有由LDA和MLLT变换的特征的单音系统构造三音GMM系统,最后的GMM系统用于为随后的DNN训练生成状态对齐。
- 基于GMM系统提供的对齐来训练DNN系统,特征是40维FBank,并且相邻的帧由11帧窗口(每侧5个窗口)连接。连接的特征被LDA转换,其中维度降低到200。然后应用全局均值和方差归一化以获得DNN输入。DNN架构由4个隐藏层组成,每个层由1200个单元组成,输出层由3386个单元组成。 基线DNN模型用交叉熵的标准训练。 使用随机梯度下降(SGD)算法来执行优化。 将迷你批量大小设定为256,初始学习率设定为0.008。
- 被噪声干扰的语音可以使用基于深度自动编码器(DAE)的噪声消除方法。DAE是自动编码器(AE)的一种特殊实现,通过在模型训练中对输入特征引入随机破坏。已经表明,该模型学习低维度特征的能力非常强大,并且可以用于恢复被噪声破坏的信号。在实践中,DAE被用作前端管道的特定组件。输入是11维Fbank特征(在均值归一化之后),输出是对应于中心帧的噪声消除特征。然后对输出进行LDA变换,提取全局标准化的常规Fbank特征,然后送到DNN声学模型(用纯净语音进行训练)。
3.训练与解码脚本解读
本节结合官方文档对主要脚本进行解读。
以下流程中的符号解释:->表示下一步,{}表示循环,[]表示括号内每一个都要进行一次,()表示不同分支下可能进行的操作
1.train_mono.sh
用来训练单音子隐马尔科夫模型,一共进行40次迭代,每两次迭代进行一次对齐操作
gmm-init-mono->compile-train-graphs->align-equal-compiled->gmm-est->
{gmm-align-compiled->gmm-acc-stats-ali->gmm-est}40->analyze_alignments.sh
2.train_deltas.sh
用来训练与上下文相关的三音子模型
check_phones_compatible.sh->acc-tree-stats->sum-tree-stats->cluster-phones->compile-questions->
build-tree->gmm-init-model->gmm-mixup->convert-ali->compile-train-graphs->
{gmm-align-compiled->gmm-acc-stats-ali->gmm-est}35->analyze_alignments.sh
3.train_lda_mllt.sh
用来进行线性判别分析和最大似然线性转换
check_phones_compatible.sh->split_data.sh->ali-to-post->est-lda->acc-tree-stats->sum-tree-stats->
cluster-phones->compile-questions->build-tree->gmm-init-model->convert-ali->compile-train-graphs->
{gmm-align-compiled->gmm-acc-stats-ali->gmm-est}35->analyze_alignments.sh
4.train_sat.sh
用来训练发音人自适应,基于特征空间最大似然线性回归
check_phones_compatible.sh->ali-to-post->acc-tree-stats->sum-tree-stats->cluster-phones->compile-questions->
build-tree->gmm-init-model->gmm-mixup->convert-ali->compile-train-graphs->
{gmm-align-compiled->(ali-to-post->)gmm-acc-stats-ali->gmm-est}35->ali-to-post->
gmm-est->analyze_alignments.sh
5.train_quick.sh
用来在现有特征上训练模型。
对于当前模型中在树构建之后的每个状态,它基于树统计中的计数的重叠判断的相似性来选择旧模型中最接近的状态。
check_phones_compatible.sh->ali-to-post->est-lda->acc-tree-stats->sum-tree-stats->
cluster-phones->compile-questions->build-tree->gmm-init-model->convert-ali->compile-train-graphs->
{gmm-align-compiled->gmm-acc-stats-ali->gmm-est}20->analyze_alignments.sh
6.run_dnn.sh
用来训练DNN,包括xent和MPE,
{make_fbank.sh->compute_cmvn_stats.sh}[train,dev,test]->train.sh->{decode.sh}[phone,word]->
align.sh->make_denlats.sh->train_mpe.sh->{{decode.sh}[phone,word]}3
7.train_mpe.sh
用来训练dnn的序列辨别MEP/sMBR。
这个阶段训练神经网络以联合优化整个句子,这比帧级训练更接近于一般ASR目标。
sMBR的目的是最大化从参考转录对齐导出的状态标签的期望正确率,而使用网格框架来表示竞争假设。
训练使用每句迭代的随机梯度下降法。
首先使用固定的低学习率1e-5(sigmoids)运行3-5轮。
在第一轮迭代后重新生成词图,我们观察到快速收敛。
我们支持MMI, BMMI, MPE 和sMBR训练。所有的技术在Switchboard 100h集上是相同的,仅仅在sMBR好一点点。
在sMBR优化中,我们在计算近似正确率的时候忽略了静音帧。
{nnet-train-mpe-sequential}3->make_priors.sh
8.train_dae.sh
用来实验基于dae的去噪效果
compute_cmvn_stats.sh->{add-noise-mod.py->make_fbank.sh->compute_cmvn_stats.sh}[train,dev,test]->
train.sh->nnet-concat->{{decode.sh}[phone,word]}[train,dev,test]
9.train.sh
用来训练深度神经网络模型,帧交叉熵训练,该相位训练将帧分类为三音状态的DNN。这是通过小批量随机梯度下降完成的。
默认使用Sigmoid隐藏单元,Softmax输出单元和完全连接的AffineTransform层,学习率是0.008,小批量的大小为256。
我们没有使用动量或正则化(注:最佳学习率和隐藏单元的类型不同,sigmoid的值为0.008,tanh为0.00001。
通过‘–feature-transform’和‘-dbn’将input——transform和预训练的DBN传入此脚本,只有输出层被随机初始化。
我们使用提前停止来防止过度拟合,为此我们测量交叉验证集合(即保持集合)上的目标函数,
因此需要两对特征对齐dir来执行监督训练
feat-to-dim->nnet-initialize->compute-cmvn-stats->nnet-forward->nnet-concat->cmvn-to-nnet->
feat-to-dim->apply-cmvn->nnet-forward->nnet-initialize->train_scheduler.sh
10.train_scheduler.sh
典型的情况就是,train_scheduler.sh被train.sh调用。
一开始需要在交叉验证集上运行,主函数需要根据$iter来控制迭代次数和学习率。
学习率会随着目标函数相对性的提高而变化:
如果提高大于’start_halving_impr=0.01’,初始化学习率保持常数
否则学习率在每次迭代中乘以’halving_factor=0.5’来缩小
最后,如果提高小于’end_halving_impr=0.001’,训练终止。
11.mkgraph.sh
用来建立一个完全的识别网络
12.decode.sh
用来解码并生成词错率结果
13.align_si.sh
对制定的数据进行对齐,作为新模型的输入
14.make_fmllr_feats.sh
用来保存FMLLR特征
15.pretrain_dbn.sh
深度神经网络预训练脚本
16.decode_fmllr.sh
对发音人自适应的模型进行解码操作
17.nnet-train-frmshuff.cc
最普遍使用的神经网络训练工具,执行一次迭代训练。过程:
–feature-transform 即时特征扩展
NN输入-目标对的每帧重排
小批量随机梯度下降(SGD)训练
支持的每帧目标函数(选项 - 对象函数):
Xent:每帧交叉熵
Mse:每帧均方误差
18.nnet-forward.cc
通过神经网络转发数据,默认使用CPU。选项:
–apply-log :产生神经网络的对数输出(比如:得到对数后验概率)
–no-softmax :从模型中去掉soft-max层
—class-frame-counts:从声学得分中减去计算对数的计数
4.可能遇到的问题
4.1 脚本传参
在大多数脚本中都会用到这段代码进行参数格式化:
[ -f path.sh ] && . ./path.sh;
. parse_options.sh || exit 1;
它的作用是将脚本开头定义的参数默认值与传过来的参数合并,并以传过来的参数为准。例如在run_dnn.sh
中调用decode.sh --nj 5
会将参数nj=5
传入decode.sh
中,如果在. parse_options.sh
之前定义了nj=8
,那么在参数格式化后nj=5
。
4.2 脚本并行
在很多脚本中都可能会用到并行处理来加速处理,如:
for ITER in 3 2 1; do
(
steps/nnet/decode.sh --nj $nj --cmd "$decode_cmd" --nnet $outdir/${ITER}.nnet --config conf/decode_dnn.config --acwt $acwt \
$gmmdir/graph_word data/fbank/test $outdir/decode_test_word_it${ITER} || exit 1;
)&
(
steps/nnet/decode.sh --nj $nj --cmd "$decode_cmd" --nnet $outdir/${ITER}.nnet --config conf/decode_dnn.config --acwt $acwt \
$gmmdir/graph_phone data/fbank/test_phone $outdir/decode_test_phone_it${ITER} || exit 1;
)&
done
这里的&
就是并发执行括号内的代码,相当于开多个进程来进行处理。上例中decode.sh
会同时有6个一起执行,内存很容易爆满。而decode.sh
本身就可以通过nj
参数将任务分为nj份,来进行并行处理。而thchs30的数据中总共有10个说话人,所以nj=5
正好可以将任务数整除,如果不整除,那么肯定会出现cpu多数核心闲着而少数核心满载的情况。对于双核八线程的cpu来说,要想最大限度的发挥cpu计算能力,线程数至少为2,但是线程数过高可能会导致线程之间抢占调度时间,反而影响效率。
综上,建议的decode参数为steps/nnet/decode.sh --nj 5 --num-threads 2
。并且删掉&。
观察cpu和内存使用情况推荐使用htop工具。
4.3 脚本注释
单行注释可以用#,块注释可以用:
<<\EOF
被注释的代码
EOF
4.4 脚本计时
很多脚本的操作都比较耗时,为了在后续分析时有针对性的处理,可以在脚本开始处添加:
start=$(date +%s)
echo "$0: $(date)" # 输出当前时间
在脚本结尾处添加:
end=$(date +%s)
time_consume=$(($end - $start ))
echo "$0:Done in $time_consume s = $(($time_consume/60)) min" && exit 0;
echo "$0:All finished" && exit 0
4.5 在dae训练出错
WARNING (apply-cmvn[5.1.0~2-9866]:Open():util/kaldi-table-inl.h:1650) Script file exp/tri4b_dnn_dae/tgt_cmvn.scp contains duplicate key: A02
ERROR (apply-cmvn[5.1.0~2-9866]:RandomAccessTableReader():util/kaldi-table-inl.h:2528) Error opening RandomAccessTableReader object (rspecifier is: scp:exp/tri4b_dnn_dae/tgt_cmvn.scp)
是由于tgt_cmvn.scp
去重失败导致的,定位到文件local/dae/run_dae.sh
第119行
将cat data/fbank/train/cmvn.scp data/fbank/dev/cmvn.scp | sort -u > $dir/tgt_cmvn.scp
修改为cat data/fbank/train/cmvn.scp data/fbank/dev/cmvn.scp | sort -u -k 1.1,1.0 > $dir/tgt_cmvn.scp
错误发现过程异常曲折,虽然出错点已经提示duplicate key了,但是想不通为什么会生成这个包含重复行的数据。根据dev和train数据生成过程一步步回溯,因为train数据是不包含重复项的,并且data/fbank/train/feats.scp与data/fbank/dev/feats.scp正好互补,将编号补齐。
4.6 utt2spk不匹配
是由于传到train_scheduler.sh
的参数$labels
缺少--utt2spk
参数引起的,定位到run_dae.sh
的120多行,在$cuda_cmd exp/tri4b_dnn_dae/log/train_nnet.log \
前一行添加如下代码:
cat data/dae/train/utt2spk data/dae/dev/0db/utt2spk | sort -u > $dir/utt2spk
然后修改紧接着的--labels
参数,添加--utt2spk
,修改后为:
--labels "ark:copy-feats scp:$dir/tgt_feats.scp ark:- | apply-cmvn --norm-vars=false --utt2spk=ark:$dir/utt2spk scp:$dir/tgt_cmvn.scp ark:- ark:- | feat-to-post ark:- ark:-|" \
4.7 beam设置
多处脚本会传入参数--config conf/decode_dnn.config
,在这个配置文件中有beam
和lattice_beam
参数,这两个参数设置的过小会在解码过程中出现如下警告:
LOG (latgen-faster-mapped-parallel[5.1.0~2-9866]:RebuildRepository():determinize-lattice-pruned.cc:283) Rebuilding repository.
LOG (latgen-faster-mapped-parallel[5.1.0~2-9866]:RebuildRepository():determinize-lattice-pruned.cc:283) Rebuilding repository.
LOG (latgen-faster-mapped-parallel[5.1.0~2-9866]:RebuildRepository():determinize-lattice-pruned.cc:283) Rebuilding repository.
LOG (latgen-faster-mapped-parallel[5.1.0~2-9866]:RebuildRepository():determinize-lattice-pruned.cc:283) Rebuilding repository.
LOG (latgen-faster-mapped-parallel[5.1.0~2-9866]:RebuildRepository():determinize-lattice-pruned.cc:283) Rebuilding repository.
WARNING (latgen-faster-mapped-parallel[5.1.0~2-9866]:CheckMemoryUsage():determinize-lattice-pruned.cc:316) Did not reach requested beam in determinize-lattice: size exceeds maximum 50000000 bytes; (repo,arcs,elems) = (26023968,641600,23414304), after rebuilding, repo size was 18734528, effective beam was 7.98757 vs. requested beam 10
但是如果设置的过大则会消耗更多内存和时间。
5.附录
5.1 一些缩写
缩写 | 中文 |
---|---|
cmvn | 倒谱均值和方差归一化 |
fft | 快速傅里叶变换 |
GMM | 高斯混合模型 |
MFCC | 梅尔倒谱系数 |
pcm | 脉冲编码调制 |
概率分布函数 | |
PLP | 感知线性预测系数 |
SGMM | 子空间高斯混合模型 |
UBM | 通用背景模型 |
VTLN | 特征级声道长度归一化 |
5.2 参考文献
- Wang D, Zhang X. THCHS-30: A free Chinese speech corpus[J]. arXiv preprint arXiv:1512.01882, 2015.
- http://www.kaldi-asr.org/doc/dnn1.html
- http://blog.csdn.net/DuishengChen/article/details/52575926