单音素建模脚本(steps/train_mono.sh):
#monophone
steps/train_mono.sh--boost-silence 1.25 --nj$n --cmd "$train_cmd" data/mfcc/train data/lang exp/mono \
|| exit 1;
输出在exp/mono中,下面按脚本train_mono.sh运行流程逐项说明。
1.1.1 特征倒谱归一化(Cepstralnormalization)
exp/mono/cmvn.logexp/mono/cmvn.ark:对每个说话人,计算归一化倒谱均值和方差的统计值
#cepstralnormalization
#feature: add"delta" and "accelaration", dim = 39
feats="ark,s,cs:apply-cmvn$cmvn_opts --utt2spk=ark:$sdata/JOB/utt2spkscp:$sdata/JOB/cmvn.scp scp:$sdata/JOB/feats.scp ark:- | add-deltas ark:- ark:- |"
example_feats="`echo$feats| sed s/JOB/1/g`";
1.1.2 模型初始化(Modelinitialization)
#modelinitialization
#options used toget plausible means and variances
#tree isphonetic-context decision tree-- doesn’t have any splits in monophone case.
$cmd JOB=1$dir/log/init.log \
gmm-init-mono $shared_phones_opt"--train-feats=$feats subset-feats --n=10 ark:- ark:-|"$lang/topo$feat_dim $dir/0.mdl $dir/tree || exit 1;
- 运行log:exp/mono/log/init.log
- 调用gmm-init-mono命令行工具,即gmm-init-mono.cc中的函数。
gmm-init-mono<topology-in> <dim> <model-out> <tree-out>
- 双引号中为一些命令行选项,确保求得的均值方差是可行的。
- 输入:HMM拓扑文件data/lang/topo和GMM特征维度39。
- 输出:模型文件exp/mono/0.mdl和决策树exp/mono/tree。
注1:单音素决策树
--可以通过copy-tree命令行工具查看音素上下文决策树文件。
--决策树文件中存储的是多态类型对象EventMap。
--TE指的是TableEventMap,表示树的各种节点的查询Table。
--CE指的是ConstantEventMap,表示树的叶子节点。
--SE指的是SplitEventMap,表示树的分支,而在单音素模型中不需要做split处理。
--“TE 0 219”表示从第0个音素开始分裂,这里0指向NULL,因为phone-id中0是为epsilon保留的。随后是219个non-NULL event map。
--“TE -1 3 ( CE5 CE 6 CE 7 ) ”这个字符串表示一个音素的分裂从key-1开始,这个key表示拓扑结构中的pdfclass,在这里也就是HMM状态,其中,-1是初始状态,0、1、2是 HMM 中间的转移状态,3 是结束状态。中间的3个转移状态可以有对应的CE,每一个表示决策树的一个叶子节点。
注2:HMM拓扑文件
--可以通过cat data/lang/topo查看。
--给定了所有非静音音素的3状态left-to-right型HMM结构,并给出了开始训练前默认的状态转移概率。
--静音音素单独给出了5状态的HMM结构,具有更多的状态转移。
注3:模型文件0.mdl
--通过gmm-copy命令行工具查看模型文件gmm-copy --binary=false exp/mono/0.mdl - | less
--模型文件中包含两部分,一部分是TransitionModel(转移模型),其中包含HMM拓扑结构、triples以及转移概率;另一部分是AmGmm(声学模型)。模型文件中的对象都不是Table,所以直接通过Read/Write进行对象读写。写入是 binary or text,取决于命令行选项 --binary。
--Topology信息中,有一个音素(标号1)与其他音素的拓扑结构不同,对比phones.txt可知该音素为sil(代表静音silence)。
--topo 文件的惯例,初始状态和结束状态(概率为 1)为非发射状态。在如下所示的拓扑结构里,-1 是初始状态,0、1、2是 HMM 中间的转移状态,3 是结束状态。
--每一个Triple表示一个三元组(phone,hmm-state,pdf-id),与转移状态一一对应。
--<NUMPDFS>即pdf总数,与Triple总数一致。
1.1.3 预编译训练图模型(Compiletraining graphs)
#compile training graphs(FSTs)
#one for each train utterance
#encode HMM structure for that training utterance
#we precompile them because otherwise this would dominatetraining time
if [$stage -le -2 ];then
echo"$0: Compiling training graphs"
$cmd JOB=1:$nj$dir/log/compile_graphs.JOB.log\
compile-train-graphs --read-disambig-syms=$lang/phones/disambig.int$dir/tree $dir/0.mdl $lang/L.fst \
"ark:sym2int.pl --map-oov$oov_sym -f 2-$lang/words.txt <$sdata/JOB/text|" \
"ark:|gzip -c >$dir/fsts.JOB.gz" ||exit 1;
fi
- 运行log:exp/mono/log/compile_graphs.log
- 调用compile-train-graphs命令行工具,编译FSTs,每个训练语句得到一个FST。
- n compile-train-graphs[options] <tree-in> <model-in> <lexicon-fst-in><transcriptions-rspecifier> <graphs-wspecifier>
- 构造训练的fst网络,从源码级别分析,是每个句子构造一个phonelevel的fst网络。这里采用预编译的原因是,可以尽量减少占用训练时间。$data/text中包含对每个句子的单词级别(words level)或音素级别(phone level)的标注, L.fst是词典的fst表示,作用是将一串的音素转换成单词。构造monophone解码图就是先将text中的每个句子,生成一个fst(类似于语言模型中的G.fst,只是相对比较简单,只有一个句子),然后和L.fst进行composition 形成训练用的音素级别(phone level)fst网络(类似于LG.fst)。fsts.JOB.gz中使用 key-value 的方式保存每个句子和其对应的fst网络,通过 key(句子索引)就能找到这个句子的fst网络,value中保存的是句子中每两个音素之间互联的边(Arc),例如句子转换成音素后,标注为:"a b c d e f",那么value中保存的其实是 a->b b->c c->d d->e e->f这些连接(kaldi会为每种连接赋予一个唯一的id),后面进行 HMM训练的时候是根据这些连接的id进行计数,进一步得到音素内(intra)状态的转移概率和音素间(inter)状态的转移概率。
- 输入:决策树文件,模型文件,词典fst文件,训练文件标注transcript_rspecifier(Table格式输入)。
- 输出:训练图fsts_wspecifier(Table格式输出)。
注1 :训练图模型
--可以通过fstcopy命令将存档格式转到标准输出查看
--存档格式:utt-idgraph utt-id graph...
--其中graph格式:from-state to-state input-symboloutput-symbol cost
--input-symbol,传统意义上是指概率密度函数,GMM中每个混合分量都有一个值。但这样会增加训练转移概率和音素对齐时的难度,所以Kaldi中将input-symbol定义为转移标识符trans-id。
--output-symbol是word.txt中的词。
--cost是转移概率,其中包含发音概率,对于预编译训练图来说,除初始状态外没有转移概率(后面训练时加入)。
注2 :rspecifier/wspecifier
--这两个字符串指明了Table文件是archiv还是script-file,文件在磁盘上的存储位置(普通文件、管道还是标准输入输出)以及其他的选项信息。
1.1.4 第一次对齐和求累积统计量(Firstalignment stage)
#first alignmentstage
#produces alignments“equally spaced” for each utterance, accumulates 1st iteration stats.
#an alignment isa vector of ints (per utterance)
#Note: we doViterbi training not forward-backward, means we use 1-best path.
if [$stage -le -1 ];then
echo"$0: Aligning data equally(pass 0)"
$cmd JOB=1:$nj$dir/log/align.0.JOB.log \
align-equal-compiled"ark:gunzip -c $dir/fsts.JOB.gz|""$feats" ark,t:- \| \
gmm-acc-stats-ali --binary=true$dir/0.mdl "$feats" ark:- \
$dir/0.JOB.acc ||exit 1;
fi
- 运行log:exp/mono/log/align.0.log
- 调用align-equal-compiled和gmm-acc-stats-ali命令行工具
align-equal-compiled <graphs-rspecifier> <features-rspecifier> <alignments-wspecifier>
gmm-acc-stats-ali [options] <model-in> <feature-rspecifier> <alignments-rspecifier> <stats-out>
- 对每个语句采用等空间对齐(alignments“equally spaced”),并累积迭代统计量,开始HMM-GMM训练。
- 对每个语句来说,对齐相当于一个整数向量vector<int32>,其中存储trans_id的序列,它的长度与需要对齐的语句一样长,即一帧数据提取的一个特征向量对应得到一个trans_id,其中trans_id与(trans_state,trans_index)二元组一一映射,而trans_state与(phone,hmm_state,pdf)三元组一一映射,pdf由GMM计算。对齐(alignments)通常用在训练阶段的Viterbi算法和测试阶段的自适应算法。音素trans_id编码了音素的信息,所以从对齐中可以计算出音素的序列。
- 训练转移概率时,累积的统计量就是数每个trans_id在训练阶段出现了多少次。
- 由于采用Viterbi算法训练而不是前向-后向算法,所以只选一个最佳路径。
- align-equal-compiled输入:训练图fst_rspecifier(Table格式输入),特征feature_rspecifier(Table格式输入)。
- align-equal-compiled输出:对齐向量alignment_wspecifier(Table格式输出)。
- gmm-acc-stats-ali输入:初始模型文件0.mdl、特征文件(Table格式输入)、对齐向量(Table格式输出)。
- gmm-acc-stats-ali输出:初始累积统计量0.acc
# In thefollowing steps, the --min-gaussian-occupancy=3 option is important,
# otherwise wefail to est "rare" phones and later on, they never align properly.
#first update
#transitionmodel update
#gmm update
#split gauss
#modelfile(.mdl) contains transition-model object, then GMM object
if [$stage -le 0 ];then
gmm-est --min-gaussian-occupancy=3 --mix-up=$numgauss --power=$power \
$dir/0.mdl"gmm-sum-accs- $dir/0.*.acc|"$dir/1.mdl 2>$dir/log/update.0.log||exit 1;
rm $dir/0.*.acc
fi
- 运行log:exp/mono/log/update.0.log
- 调用gmm-est和gmm-sum-accs命令行工具
gmm-est [options]<model-in> <stats-in> <model-out>
gmm-sum-accs [options]<stats-out> <stats-in1> <stats-in2> ...
- 状态转移(Transition-model)更新,GMM更新,状态split
- 输入:初始模型exp/mono/0.mdl,GMM统计量exp/mono/0.acc
- 输出:第一次更新后的模型exp/mono/1.mdl
1.1.6 建模过程(Modelbuilding schedule)
beam=6 # will change to 10 below after 1st pass
# note: usingslightly wider beams for WSJ vs. RM.
#model buildingschedule
x=1
while [$x -lt $num_iters ]; do
echo"$0: Pass$x"
if [$stage -le $x ]; then
ifecho$realign_iters | grep -w$x >/dev/null;then
echo"$0: Aligning data"
mdl="gmm-boost-silence --boost=$boost_silence `cat$lang/phones/optional_silence.csl`$dir/$x.mdl - |"
#newalignment
$cmd JOB=1:$nj$dir/log/align.$x.JOB.log \
gmm-align-compiled$scale_opts --beam=$beam --retry-beam=$[$beam*4] --careful=$careful"$mdl" \
"ark:gunzip -c$dir/fsts.JOB.gz|""$feats""ark,t:|gzip -c >$dir/ali.JOB.gz" \
|| exit 1;
fi
$cmd JOB=1:$nj$dir/log/acc.$x.JOB.log \
gmm-acc-stats-ali $dir/$x.mdl"$feats""ark:gunzip -c$dir/ali.JOB.gz|" \
$dir/$x.JOB.acc ||exit 1;
#newupdate
$cmd$dir/log/update.$x.log \
gmm-est --write-occs=$dir/$[$x+1].occs --mix-up=$numgauss --power=$power$dir/$x.mdl \
"gmm-sum-accs-$dir/$x.*.acc|"$dir/$[$x+1].mdl || exit 1;
rm $dir/$x.mdl$dir/$x.*.acc$dir/$x.occs 2>/dev/null
fi
if [$x -le $max_iter_inc ]; then
numgauss=$[$numgauss+$incgauss];
fi
beam=10
x=$[$x+1]
done
- 多次对齐(newalignment):调用gmm-align-compiled和gmm-acc-stats-ali命令行工具,输出对齐向量和累积统计量。
gmm-align-compiled[options] model-in graphs-rspecifier feature-rspecifier alignments-wspecifier[scores-wspecifier]
gmm-acc-stats-ali[options] <model-in> <feature-rspecifier><alignments-rspecifier> <stats-out>
- 多次更新(newupdate):调用gmm-est命令行工具,输出更新后的模型(更新转移模型参数和GMM参数)。间接调用gmm-sum-accs命令行工具,计算累积统计量总和。
gmm-est [options]<model-in> <stats-in> <model-out>
gmm-sum-accs [options]<stats-out> <stats-in1> <stats-in2> ...