【Kaldi】train_mono.sh源码阅读
这个脚本会训练一个mono phone模型。
整体结构
除去数据准备等命令,我们逐行解析里面的指令。首先还是了解一下脚本的大致情况:
- 前30行,定义训练超参数
- 30~49行,定义个解析该脚本的命令行参数
- 51~65行,准备数据
- 67~110行,初始化训练
- 112行之后,后续训练
初始化训练
gmm-init-mono
80行的gmm-init-mono
指令:
1 | gmm-init-mono $shared_phones_opt "--train-feats=$feats subset-feats --n=10 ark:- ark:-|" $lang/topo $feat_dim \ |
这个指令用于初始化mono phone的GMM,它的一般用法为:
1 | gmm-init-mono <topology-in> <dim> <model-out> <tree-out> |
这个指令可以参考之前的文章 ==> 【Kaldi】gmm-init-mono源码阅读
ark文件
注意到“ark”字样频繁出现,在kaldi中,ark文件是一种数据存储文件,比如常用的mfcc就会用ark存储。ark文件可以用kaldi目录下的src/featbin/copy-feats
查看,一般用法为:
1 | copy-feats [options] <feature-rspecifier> <feature-wspecifier> |
<feature-rspecifier>
表示读取文件,<feature-wspecifier>
表示写入文件,比如要查看一个二进制的abc.ark
文件可以用:
1 | copy-feats ark:./abc.ark ark,t:target.ark |
其中ark,t
表示用文本形式展现,如果不加上这个指令那么默认采用二进制格式。target.ark
可以为空,这个使用copy-feats
会把数据打印到终端。
compile-train-graphs
90行的compile-train-graphs
指令:
1 | compile-train-graphs --read-disambig-syms=$lang/phones/disambig.int $dir/tree $dir/0.mdl $lang/L.fst \ |
这个指令用于编译训练图,一般用法为:
1 | compile-train-graphs [options] <tree-in> <model-in> <lexicon-fst-in> <transcriptions-rspecifier> <graphs-wspecifier> |
align-equal-compiled和gmm-acc-stats-ali
98行的align-equal-compiled
和99行的gmm-acc-stats-ali
指令:
1 | align-equal-compiled "ark:gunzip -c $dir/fsts.JOB.gz|" "$feats" ark,t:- \| \ |
首先align-equal-compiled
的一般用法为:
1 | align-equal-compiled <graphs-rspecifier> <features-rspecifier> <alignments-wspecifier> |
这个指令用于产生最简单的对齐方式,即假设每个状态持续时间相同,这种方式便于初始化GMM的初步训练,单后续的训练一定会使用这种方式。
接着gmm-acc-stats-ali
将读取初始化模型0.mdl
、特征$feats
以及对齐结果,输出用于更新GMM参数的0.JOB.acc
文件。其一般用法如下:
1 | gmm-acc-stats [options] <model-in> <feature-rspecifier> <posteriors-rspecifier> <stats-out> |
gmm-est
107行的gmm-est
指令:
1 | gmm-est --min-gaussian-occupancy=3 --mix-up=$numgauss --power=$power \ |
这段代码会使用初始化模型0.mdl
和之前的gmm-acc-stats-ali
的输出更新参数后的GMM 1.mdl
。另外,先前的0.mdl
的GMM只有一个分量,而从1到多GMM的操作也是在这个指令中完成的,通过扰动1个高斯分量的均值,把1个高斯分量分裂为2个,作为下次迭代的基础。
后续训练
经过上面的初步训练,我们得到了1.mdl
,但为了得到更精确的模型,还需要多轮迭代,从112行到140行的代码做的就是这个事。我们可以把上面的代码简化一下,省略打印日志等细节,只保留逻辑:
1 | beam=$initial_beam # will change to regular_beam below after 1st pass |
这段代码的总体流程和之前的第1次迭代类似,不过生成对齐数据时第一次迭代会将beam
设置为$initial_beam
(在timit中是6),之后就会变成$regular_beam
(timit中是10),beam
的数值越大对齐结果越准确,但是时间消耗也越大,所以生成对齐数据的操作并不是在每一次迭代中都有的。其余的部分和初始化迭代类似,无非是使用EM算法获取更新GMM的参数、更新GMM,总之每次迭代训练GMM都会用到gmm-acc-stats-ali
和gmm-est
。