基本概念
在经典语音识别框架中,一个声学模型就是一组HMM。对于语音识别框架中的声学模型中的每个HMM,都定义该HMM中有多少个状态,以及以各个状态起始的马尔可夫链的初始概率、个状态间的转移概率和每个状态的概率分布函数。
在语音识别实践中,一般令初始概率恒为1,把状态间的转移概率预设为固定值,不在训练过程中更新状态转移概率。声学模型包含的信息主要是状态定义和各状态的观察概率分布。如果使用高斯混合模型对观察概率分布建模,那就是GMM-HMM模型,如果用神经网络模型对观察概率分布建模,那就是NN-HMM。
HMM状态的物理意义在语音识别中可以认为是因素的发音状态。习惯上把一个因素的发音状态分别三部分,分别称为“初始态”、“稳定态”和“结束态”。对应的,用三个HMM状态建模一个因素的发音。
根据声学模型,可以计算某一帧声学特征在某一个状态上的声学分:
A
m
S
c
o
r
e
(
t
,
i
)
=
l
o
g
P
(
o
t
∣
s
i
)
AmScore(t,i)=logP(o_t|s_i)
AmScore(t,i)=logP(ot∣si)
观察概率的经典建模方法时高斯混合模型。GMM的思路是使用过个高斯分量加权叠加,拟合出任意分布的概率密度函数,GMM建模观察概率可用下面公式表示:
log
P
(
o
t
∣
s
i
)
=
log
∑
m
=
1
M
c
i
,
m
(
2
π
)
D
/
2
∣
Σ
i
.
m
∣
1
/
2
exp
[
−
1
2
(
o
t
−
μ
i
,
m
)
T
Σ
i
.
m
−
1
(
o
t
−
μ
i
,
m
)
]
\log P(o_t|s_i)=\log\sum\limits_{m=1}^{M}\frac{c_{i,m}}{(2\pi)^{D/2}|\bold{\Sigma}_{i.m}|^{1/2}}\exp[-\frac{1}{2}( o_t-\bold{\mu}_{i,m})^T\bold{\Sigma}_{i.m}^{-1}( o_t-\bold{\mu}_{i,m})]
logP(ot∣si)=logm=1∑M(2π)D/2∣Σi.m∣1/2ci,mexp[−21(ot−μi,m)TΣi.m−1(ot−μi,m)]
其中
μ
i
,
m
\mu_{i,m}
μi,m是状态
s
i
s_i
si的第m个高斯分量的D维均值向量,
Σ
\Sigma
Σ为协方差矩阵。为了降低模型参数量,通常令协方差矩阵对角阵。在Kaldi的模型文件中,可以查看GMM-HMM参数:
<!--定义每个音素由多少个状态构成等信息-->
<TransitionModel>
<Topology>
<TopologyEntry>
<ForPhones>
2 3
</ForPhones>
<State> 0 <PdfClass> 0 <Transition> 0 0.75 <Transition> 1 0.25 </State>
<State> 1 <PdfClass> 1 <Transition> 1 0.75 <Transition> 2 0.25 </State>
<State> 2 <PdfClass> 2 <Transition> 2 0.75 <Transition> 3 0.25 </State>
<State> 3 </State>
</TopologyEntry>
...
<!--每个DiagGMM描述一个状态的高斯分量的概率分布函数-->
<DiagGMM>
<GCONSTS> [ -162.7133 -100.3285 -150.8937 -774.061 -103.6495 -126.3453 -99.05069 -113.0296 -144.241 -97.04393 -97.20042 -157.5796 -148.9068 -96.38339 -167.8063 -118.0786 -99.98055 -153.8176 -98.4172 -105.3345 -109.6815 -98.64252 -1047.926 -154.4816 -124.1997 -101.5719 -170.3122 -148.3232 -160.2819 -94.18665 -142.9411 -136.0318 -109.4299 ]
<WEIGHTS> [ 0.02608026 0.03207224 0.03214623 0.03326803 0.01074118 0.03843883 0.01074123 0.02879376 0.02798112 0.035948780.02601144 0.02568985 0.03805191 0.03835792 0.0292126 0.02467277 0.03514369 0.020061 0.03335512 0.04938995 0.04526147 0.02708234 0.03225288 0.0305144 0.03721222 0.02312371 0.03826462 0.03523141 0.02223409 0.02967945 0.029601 0.02492189 0.02846259 ]
<MEANS_INVVARS> [
-3.79898 -5.355403 0.8425668 0.920942 1.015877 0.3240763 -0.09655836 -0.01842542 0.2740531 1.434082 0.009588351 0.1420811 0.1675368 1.600755 0.7485431 -0.3694164 -0.7837143 -0.1158709 0.1351153 0.1625394 0.2112794 -0.7231259 -1.178222 -0.2539929 -0.03128162 0.1525151 3.838173 5.698206 -3.685302 -2.406258 -1.797742 0.4984115 0.258485 -0.5240998 -1.563382 -2.190646 -0.3212375 -0.05561644 -0.8344957
...
</DiagGMM>
声学模型的使用
语音识别的过程可以看成是用语音的特征序列去匹配一个状态图,搜索最优路径的过程。状态图中有无数条路径,每条路径代表一种可能的识别结果,且都有一个分数,该分数衡量语音和识别结果的匹配程度。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MQDNkffb-1641972662904)(etc/README/m3i1.png)]
状态图由若干节点和若干跳转构成,有的跳转对应一个HMM状态,并在识别过程中对当前帧计算一个分数,分数由两部分构成,分别称为声学分和图固有分,两者之和构成了该跳转在当前帧的分数。其中,图固有分主要来源于语言模型概率,同时也来源于发音词典的多音词的选择概率和HMM模型的转移概率,这些概率在状态图的构建过程中就已经固定。声学分是在识别过程中根据声学模型和待识别语音的匹配关系动态计算的,声学模型在语音识别过程中最主要的作用就是计算声学分。每条跳转和声学特征的一帧对应,最优跳转路径代表的状态序列就是状态级识别结果。Kaldi的状态图是基于WFST构建的。
模型初始化
首先学习最简单的模型训练脚本train_mono.sh
:
if [ $stage -le 8 ]; then
# train a monophone system
steps/train_mono.sh --boost-silence 1.25 --nj 20 --cmd "$train_cmd" data/train_2kshort data/lang_nosp exp/mono
fi
首先使用gmm-init-mono
创建初始化模型:
$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
这里只需要将topo文件和声学特征维数输入(这里驶入数据,是为了统计数据的均值方差来初始化参数,获得更好的训练起点),程序就会生成一个初始的声学模型,存储在exp/mono/0.mdl
中。这已经是一个完整的声学模型了,但是识别率很低,后续需要用训练数据更新这个模型的参数。并且,这个模型中每个状态只有一个高斯分量,在后续训练过程中,会进行单高斯分量到混合多高斯分量的分裂。
在经典HMM理论中,GMM-HMM使用Baum-Welch算法训练。这里使用更新的维特比训练(Viterbi training),算法速度更快,但是模型性能没有明显损失。
对齐
要使用维特比训练,首先要获取每一帧对应的状态号,作为训练的标签。在train_mono.sh
的-2阶段,构建了一个直线型的状态图来表示训练句子的标注文本所对应的状态。
$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"
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TfPFfWT4-1641972662914)(etc/README/image-20220109155309129.png)]
对齐示意图如上图所示,
x
i
x^i
xi表示第i帧,cat是要标注的文本(每个字母可由多个子音构成)。虽然状态图形状是一条直线,但都有指向自身的跳转,称为自跳(self-loop)。我们需要根据语音帧和已有的声学模型选取状态图中的一条最优路径,把各帧匹配到状态图上去,这样就得到每一帧对应的状态。这其实就是一个完整的语音识别过程,只不过解码路径限制在直线形状的状态图中,使得识别结果必定是参考文本。这个过程被称作对齐(Align)或强制对齐(Forced alignment),目的是获取每一帧对应的状态。Kaldi使用gmm-align
工具完成对齐,使用这个工具,可以根据输入声学模型及相应的Tree
文件和L.fst
文件,就可以把声学特征和文本进行对应。对齐后生成ALI
文件:
lbi-1235-135887-0000 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 8 5 18 830 832 834 833 404 406 408 407 407 1790 1789 1789 1789 1789 1789 1789 1789 1792 1791 1794 1850 1852 1854 1853 530 529 532 531 531 531 531 531 534 1778 1780 1779 1779 1782 1781 1781 1781 1340 1339 1342 1341 1344 398 400 402 2084 2086 2088 830 832 834 404 406 408 407 1166 1165 1165 1165 1168 1167 1167 1167 1170 1778 1780 1779 1782 1781 1781 1370 1369 1372 1374 1826 1828 1827 1827 1827 1827 1830 1829 1829 1829 1829 1829 410 412 414 1508 1507 1507 1510 1512 1511 1430 1429 1432 1431 1431 1434 1433 1433 1433 1433 1433 1274 1273 1276 1278 1277 1277 1532 1531 1531 1531 1531 1531 1531 1531 1534 1536 1535 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 8 18
对齐结果文件由若干个句子的对齐信息构成,每个句子以句子ID靠头,ID后面的整数序列是transition-id
,可以用copy-int-vector
进行文本和二进制格式装换。例子中使用的是gmm-align-compiled
工具。是gmm-align
的简化版本。模型训练中,会使用该脚本进行反复对齐。
Transition模型
上节提到的transition-id
是Kaldi中的概念,在Transition模型中定义:
<TransitionModel>
<Topology>
<TopologyEntry>
<!--音素id-->
<ForPhones>
11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 ... 346
</ForPhones>
<!--状态、对应的PDF、转移概率-->
<State> 0 <PdfClass> 0 <Transition> 0 0.75 <Transition> 1 0.25 </State>
<State> 1 <PdfClass> 1 <Transition> 1 0.75 <Transition> 2 0.25 </State>
<State> 2 <PdfClass> 2 <Transition> 2 0.75 <Transition> 3 0.25 </State>
<State> 3 </State>
</TopologyEntry>
<TopologyEntry>
<!--静音音素-->
<ForPhones>
1 2 3 4 5 6 7 8 9 10
</ForPhones>
<State> 0 <PdfClass> 0 <Transition> 0 0.25 <Transition> 1 0.25 <Transition> 2 0.25 <Transition> 3 0.25 </State>
<State> 1 <PdfClass> 1 <Transition> 1 0.25 <Transition> 2 0.25 <Transition> 3 0.25 <Transition> 4 0.25 </State>
<State> 2 <PdfClass> 2 <Transition> 1 0.25 <Transition> 2 0.25 <Transition> 3 0.25 <Transition> 4 0.25 </State>
<State> 3 <PdfClass> 3 <Transition> 1 0.25 <Transition> 2 0.25 <Transition> 3 0.25 <Transition> 4 0.25 </State>
<State> 4 <PdfClass> 4 <Transition> 4 0.75 <Transition> 5 0.25 </State>
<State> 5 </State>
</TopologyEntry>
</Topology>
<!--音素索引、HMM状态索引、PDF索引,每行对应一个transition state-->
<Triples> 1058
1 0 0
1 1 1
1 2 2
...
346 2 126
</Triples>
<!--按照transition id索引的对数转移概率,由于transition id从1开始,开头补0-->
<LogProbs>
[ 0 -1.386294 ... -0.2876821 -1.386294 ]
</LogProbs>
</TransitionModel>
每个transition state下可能跳转到其他HMM状态,这些跳转从0开始编号,就得到transition-index
,所有(transition state, transition index)
依次编号,就得到transition id
。
Transition-state 1: phone = SIL hmm-state = 0 pdf = 0
Transition-id = 1 p = 0.25 [self-loop]
Transition-id = 2 p = 0.25 [0 -> 1]
Transition-id = 3 p = 0.25 [0 -> 2]
Transition-id = 4 p = 0.25 [0 -> 3]
Transition-state 2: phone = SIL hmm-state = 1 pdf = 1
Transition-id = 5 p = 0.25 [self-loop]
Transition-id = 6 p = 0.25 [1 -> 2]
Transition-id = 7 p = 0.25 [1 -> 3]
Transition-id = 8 p = 0.25 [1 -> 4]
Transition-state 3: phone = SIL hmm-state = 2 pdf = 2
Transition-id = 9 p = 0.25 [2 -> 1]
Transition-id = 10 p = 0.25 [self-loop]
Transition-id = 11 p = 0.25 [2 -> 3]
Transition-id = 12 p = 0.25 [2 -> 4]
...
Transition-state 1055: phone = ZH_I hmm-state = 2 pdf = 126
Transition-id = 2189 p = 0.75 [self-loop]
Transition-id = 2190 p = 0.25 [2 -> 3]
Transition-state 1056: phone = ZH_S hmm-state = 0 pdf = 124
Transition-id = 2191 p = 0.75 [self-loop]
Transition-id = 2192 p = 0.25 [0 -> 1]
Transition-state 1057: phone = ZH_S hmm-state = 1 pdf = 125
Transition-id = 2193 p = 0.75 [self-loop]
Transition-id = 2194 p = 0.25 [1 -> 2]
Transition-state 1058: phone = ZH_S hmm-state = 2 pdf = 126
Transition-id = 2195 p = 0.75 [self-loop]
Transition-id = 2196 p = 0.25 [2 -> 3]
GMM模型迭代
对于初始模型0.mdl
,由于参数非常粗糙,,声学分计算几乎没有意义,对齐的结果也非常不准确。但在后面的训练中,会不断使用已训练的模型来重新对齐,然后再次训练、再次对齐,这样反复迭代若干轮后,模型逐渐收敛,对齐的结果也变准确了。
实际上,Kaldi采取采取根据状态个数把训练样本平均分段的方法来进行首次对齐:
$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
对齐结果被输入到gmm-acc-stats-ali
脚本中,根据初始模型、训练数据以及相应的对齐结果,输入用于GMM模型参数更新的ACC
文件。ACC
文件存储了GMM在EM训练中所需要的统计量。
生成ACC
文件后,使用gmm-est
工具更新GMM参数:
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
在GMM训练中,每次模型参数迭代都需要成对使用gmm-acc-stats-ali
和gmm-est
工具。因为有时需要并行地进行对齐,或者使用gmm-est工具对高斯分量进行分裂,所以两个工具独立操作,接下来,反复调用这两个工具,就完成了模型的训练:
x=1
while [ $x -lt $num_iters ]; do
echo "$0: Pass $x"
if [ $stage -le $x ]; then
if echo $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 - |"
# 使用gmm-align-compiled重新对齐
$cmd JOB=1:$nj $dir/log/align.$x.JOB.log \
gmm-align-compiled $scale_opts --beam=$beam --retry-beam=$retry_beam --careful=$careful "$mdl" \
"ark:gunzip -c $dir/fsts.JOB.gz|" "$feats" "ark,t:|gzip -c >$dir/ali.JOB.gz" \
|| exit 1;
fi
# 计算ACC
$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;
# 更新参数
$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=$regular_beam
x=$[$x+1]
done
三音子模型
在之前的单音子模型中,我们假设一个因素的实际发音,与其左右相邻或相近的音素无关。但是,该假设不适用于一些存在协同发音现象的语言,需要引入上下文相关的声学模型。在上下文相关的声学模型中,不仅要考虑中心音素本身,还要考虑该音素所在位置的上下文音素,上下文依赖的选取一般有以下两个维度:
- 方向:是只看上文还是下文,还是都看
- 长度:整个上下文需考虑多少个相邻音素
其中,主要使用的是1上文、1下文的三音子和1上文的双音子模型。三音子模型虽然解决了语言学中协同发音等上下文问题,缺带来了模型参数数量的爆炸。如一个单音子的英文模型,只需要对大约40个单音子建模,但在三音子系统中,需要建模的模型数量达到约64000个,在多数情况下,训练数据不足以支撑如此多的参数。解决思路是将所有三音子模型放到一起进行相似性聚类,发音相似的三音子被聚类到同一模型,共享参数。
在Kaldi中,实现方法是通过决策树算法,将所有需要建模的三音子的HMM状态放到决策树的根节点中,然后按照一套半自动生成音素问题集合,对决策树中各个节点的三音子模型的中心音素、上下文音素及HMM状态进行查询,按照最大似然准则优先进行节点分裂。通过控制似然阈值进而控制整棵决策树最终的叶子节点个数。
上下文相关声学模型和单音子模型的建模训练过程非常相似,都是用训练之后生成的模型重新对齐,作为后续系统的基础。在Kaldi中,train_delta.sh
脚本会读入训练数据data-dir
、语言词典等资源lang-dir
,以及之前单音子模型产生的对齐alignment-dir
,生成训练模型获得的三音子模型,保存到exp-dir
下,主要有三个参数可以设置:
--num-leaves
:需要建模的三音子类数--tot-gauss
:GMM-HMM语音系统中所有GMM的总数boost-silence
:静音权重
主要训练步骤如下:
if [ $stage -le -3 ]; then
# 读取特征文件和对应的对齐信息,计算决策数聚类过程中需要的统计量(phone的特征觉知、方差、以及该phone出现的语音帧数量等)
echo "$0: accumulating tree stats"
$cmd JOB=1:$nj $dir/log/acc_tree.JOB.log \
acc-tree-stats $context_opts \
--ci-phones=$ciphonelist $alidir/final.mdl "$feats" \
"ark:gunzip -c $alidir/ali.JOB.gz|" $dir/JOB.treeacc || exit 1;
sum-tree-stats $dir/treeacc $dir/*.treeacc 2>$dir/log/sum_tree_acc.log || exit 1;
rm $dir/*.treeacc
fi
# 决策树聚类
if [ $stage -le -2 ]; then
echo "$0: getting questions for tree-building, via clustering"
# preparing questions, roots file...
cluster-phones $context_opts $dir/treeacc $lang/phones/sets.int \
$dir/questions.int 2> $dir/log/questions.log || exit 1;
# 对语音系统中的单音素进行一个相似性的聚类,生成一套音素集合,即问题集,供决策树分裂聚类过程使用
cat $lang/phones/extra_questions.int >> $dir/questions.int
compile-questions $context_opts $lang/topo $dir/questions.int \
$dir/questions.qst 2>$dir/log/compile_questions.log || exit 1;
# 决策树构建
# Kaldi中决策树的构建和模型参数逇绑定,是基于HMM状态级别的
# 假设每个三音子的HMM模型有三个状态,则可用{L}-{C}+{R}.{S}描述所有状态,L、C、R是三音子,S是状态1-3
echo "$0: building the tree"
$cmd $dir/log/build_tree.log \
build-tree $context_opts --verbose=1 --max-leaves=$numleaves \
--cluster-thresh=$cluster_thresh $dir/treeacc $lang/phones/roots.int \
$dir/questions.qst $lang/topo $dir/tree || exit 1;
$cmd $dir/log/init_model.log \
gmm-init-model --write-occs=$dir/1.occs \
$dir/tree $dir/treeacc $lang/topo $dir/1.mdl || exit 1;
gmm-mixup --mix-up=$numgauss $dir/1.mdl $dir/1.occs $dir/1.mdl 2>$dir/log/mixup.log || exit 1;
rm $dir/treeacc
fi
构建的决策树如下:
ContextDependency 3 1 ToPdf SE 1 [ 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 ]
{ SE 1 [ 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 ]
{ SE 1 [ 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 ]
{ SE 1 [ 1 2 3 4 5 6 7 8 9 10 ]
{ SE 1 [ 1 2 3 4 5 ]
{ SE -1 [ 0 1 2 3 ]
{ SE -1 [ 0 1 2 ]
{ SE -1 [ 0 1 ]
{ SE -1 [ 0 ]
{ CE 0 CE 44 }
...
EndContextDependency
Kaldi实现了两种节点分裂方式TE(Table Event)和SE(Split Event)。TE是指按列表的方式进行分裂,如按不同状态号或不同中心节点分裂。SE指按问题进行分裂。最后,标记为CE表示叶子节点,后面的编号是实际物理模型的id,称为pdf-id,在GMM-HMM中对应一个GMM模型,在DNN-HMM中对应神经网络的一个输出节点。
这里不太好理解,举一个实际例子说明:如果对一个节点中所有模型的上文提问,问题为“声母集”,那么在该节点上,上文为声母的模型被划分到YES分支,以外的被划分到NO分支;如果对状态号提问,并选择{1,3}
作为问题进行分裂,就可以把HMM的开头结尾状态和中心状态区分开。如区分speak
和apple
中的p
,会因为“上文是否为s”或“上文是否为摩擦音”这样的问题而分裂开来。
每次进行三音子训练,都会重新生成tree文件以达到最合理的聚类绑定结果。因此,逻辑模型到物理模型的映射会发生改变,需要将原有对齐中的相关信息进行转换,以保证与新的tree文件一致。
if [ $stage -le -1 ]; then
# Convert the alignments.
echo "$0: converting alignments from $alidir to use current tree"
$cmd JOB=1:$nj $dir/log/convert.JOB.log \
convert-ali $alidir/final.mdl $dir/1.mdl $dir/tree \
"ark:gunzip -c $alidir/ali.JOB.gz|" "ark:|gzip -c >$dir/ali.JOB.gz" || exit 1;
fi
然后是EM迭代过程,并按照固定间隔穿插维特比重对齐和GMM模型的高斯混合分量分裂:
x=1
while [ $x -lt $num_iters ]; do
echo "$0: training pass $x"
if [ $stage -le $x ]; then
if echo $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 - |"
$cmd JOB=1:$nj $dir/log/align.$x.JOB.log \
gmm-align-compiled $scale_opts --beam=$beam --retry-beam=$retry_beam --careful=$careful "$mdl" \
"ark:gunzip -c $dir/fsts.JOB.gz|" "$feats" \
"ark:|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,s,cs:gunzip -c $dir/ali.JOB.gz|" $dir/$x.JOB.acc || exit 1;
$cmd $dir/log/update.$x.log \
gmm-est --mix-up=$numgauss --power=$power \
--write-occs=$dir/$[$x+1].occs $dir/$x.mdl \
"gmm-sum-accs - $dir/$x.*.acc |" $dir/$[$x+1].mdl || exit 1;
rm $dir/$x.mdl $dir/$x.*.acc
rm $dir/$x.occs
fi
[ $x -le $max_iter_inc ] && numgauss=$[$numgauss+$incgauss];
x=$[$x+1];
done
区分性训练
前面介绍的HMM-GMM都是基于最大似然准则进行训练的,对此以一项重要改进是区分性训练。传统最大似然训练是使正确训练路径的分数尽可能高,而区分性训练,则着眼于加大这些路径之间的打分差异,不仅要使正确的路径分数尽可能高,还要使错误路径,尤其是易混淆路径分数尽可能低。例如在识别{A,B,C,D}
四个字母的任务中,如果一条训练数据标注为B,那么训练目标不再是最大化P(B),而是最大化:
log
P
(
B
)
P
(
A
)
+
P
(
B
)
+
P
(
C
)
+
P
(
D
)
\log\frac{P(B)}{P(A)+P(B)+P(C)+P(D)}
logP(A)+P(B)+P(C)+P(D)P(B)
一方面要提高分子得分,另一方面要压制分母得分,从而使正确路径在整个空间中的分数优势更为突出。基于GMM-HMM的区分性训练现在已经使用的很少了,之后可以多了解怎么把区分性训练应用到基于神经网络的声学建模中。