kaldi中基于决策树的状态绑定

为什么要做状态绑定

假如我们有218monophone,然后现在要考虑上下文音素对发音的影响,这时候我们通常使用triphone。那么会有几个triphone呢?答案是218的3次方。如果不进行聚类,我们需要建立(218的3次方)*3个混合gmm模型(假设每个triphone有3个状态),计算量巨大,另一方面会引起数据稀疏。所以通常我们会根据数据特征对triphone的状态进行绑定。

常见的状态绑定方法有数据驱动聚类(Data-Driven Clustering)和决策树聚类(Tree-Based Clustering)。
数据驱动聚类的方式缺点引用HTK book中的表述:

One limitation of the data-driven clustering procedure described above is that it does not deal with triphones for which there are no examples in the training data. When building word-internal triphone systems, this problem can often be avoided by careful design of the training database but when building large vocabulary cross-word triphone systems unseen triphones are unavoidable.

所以现在基本上是使用决策树聚类的方式。

kaldi中基于决策树的状态绑定

要建立一颗决策树,我们首先要有问题集。*在HTK中,问题集是我们人工自己定义的。而在kaldi中,问题集是通过训练数据自动生成的

1、怎么自动生成问题集?

我们先引入一个概念:EventType,表示三音素的某个状态。比如三音素a/b/c的状态0就是一个EventType。为了方便说明,这里我们认为的使用这种表示方法:{a b c 0}。

一、先得到统计量:EventType的出现次数count_,特征向量的均值和均值的平方(至于为什么需要这些统计量,后面生成问题集的时候回解释)

在得到统计量之前,我们需要对每个句子进行维特比强制对齐(kaldi中的格式如下):在这里插入图片描述
有了强制对齐结果,我们就可以得到每一帧是属于哪个EventType(上面的数字在kaldi中表示transition-id,根据transition-id可以得到三音素及其对应的状态)。然后我们就可以用这个EventType的所有帧对应的特征(比如MFCC)来得到这个EventType的统计量(出现次数count_,特征向量的均值和均值的平方)。

kaldi中统计量的结果保存在treeacc文件中,如下:
在这里插入图片描述
简单解释下这个图的格式:

BTS 35930表示训练集中出现过的三音素个数。
EV 4 -1 0 0 0 1 29 2 134就是一个EventType,4表示后面有4对pair。-1 0 0 0 1 29 2 134表示该三音素是0/29/134(这些数字是phone的id)的第1个hmm状态。
GCL 1表示该EventType出现的次数(对应count_变量); 0.01表示方差如果有小于0.01,则等于0.01。
接下来的一行 表示特征向量的均值
在接下来的一行 表示特征向量的均值的平方 图中0.577325就是-0.759819的平方

二、构造问题集
为了清楚地说明问题集的构建过程,我们假设现在有如下三音素及其hmm状态(假设特征是2维的,实际多用39维的mfcc;统计量:出现次数,均值和方差,也是假设的,不过不影响问题的说明):

{d a g 0} count:3 mean:4,3 square:16,9
{d a g 1} count:1 mean:2,3 square:4,9
{d a g 2} count:2 mean:4,4 square:16,16
{a b c 0} count:1 mean:1,2 square:1,4
{a b c 1} count:1 mean:1,3 square:1,9
{a b c 2} count:2 mean:1,4 square:1,16
{d b a 0} count:3 mean:2,1 square:4,1
{d b a 1} count:2 mean:3,1 square:9,1
{d b a 2} count:2 mean:2,1 square:4,1
{f b c 0} count:2 mean:1,3 square:1,9
{f b c 1} count:4 mean:1,4 square:1,16
{f b c 2} count:6 mean:1,5 square:1,25
{m c a 0} count:5 mean:3,1 square:9,1
{m c a 1} count:5 mean:3,2 square:9,4
{m c a 2} count:3 mean:3,3 square:9,9
{z c d 0} count:4 mean:3,4 square:9,16
{z c d 1} count:5 mean:3,5 square:9,25
{z c d 2} count:3 mean:3,6 square:16,36
{p d h 0} count:5 mean:4,6 square:9,36
{p d h 1} count:2 mean:3,5 square:9,25
{p d h 2} count:3 mean:3,6 square:9,36
{k e n 0} count:1 mean:5,2 square:25,4
{k e n 1} count:8 mean:5,3 square:25,9
{k e n 2} count:3 mean:5,5 square:25,25
{m f k 0} count:7 mean:4,8 square:16,64
{m f k 1} count:4 mean:4,4 square:16,16
{m f k 2} count:1 mean:7,4 square:49,16

  • 选取三音素第2个hmm状态的统计量,其余的状态扔掉,经过这一步之后,我们剩下的音素就变成了:

{d a g 1} count:1 mean:4,3 square:16,9
{a b c 1} count:1 mean:1,3 square:1,9
{d b a 1} count:2 mean:2,1 square:4,1
{f b c 1} count:4 mean:1,4 square:1,16
{m c a 1} count:5 mean:3,2 square:9,4
{z c d 1} count:5 mean:3,5 square:9,25
{p d h 1} count:2 mean:3,6 square:9,36
{k e n 1} count:8 mean:5,3 square:25,9
{m f k 1} count:4 mean:4,4 square:16,16

  • 根据三音素的中间音素对前一步得到的统计量进行划分:

中间音素a: {d a g 1} count:1 mean:4,3 square:16,9
中间音素b: {a b c 1} count:1 mean:1,3 square:1,9 {d b a 2} count:2 mean:2,1 square:4,1 {f b c 1} count:4 mean:1,4 square:1,16
中间音素c: {m c a 1} count:5 mean:3,2 square:9,4 {z c d 2} count:5 mean:3,5 square:9,25
中间音素d: {p d h 1} count:2 mean:3,6 square:9,36
中间音素e: {k e n 1} count:8 mean:5,3 square:25,9
中间音素f: {m f k 1} count:4 mean:4,4 square:16,16

  • 把中间音素相同的统计量相加:

中间音素a的统计量:count:1 mean:4,3 square:16,9
中间音素b的统计量:count:7 mean:4,8 square:6,26
中间音素c的统计量:count:10 mean:6,7 square:18,29
中间音素d的统计量:count:2 mean:3,6 square:9,36
中间音素e的统计量:count:8 mean:5,3 square:25,9
中间音素f的统计量:count:4 mean:4,4 square:16,16

  • 根据sets.int指定的集合,累加同一个集合中音素的统计量:
    假设我们的set.int是这样的格式:

a
b
c
d
e
f

那么我们最后得到的统计量还是:

中间音素a的统计量:count:1 mean:4,3 square:16,9
中间音素b的统计量:count:7 mean:4,8 square:6,26
中间音素c的统计量:count:10 mean:6,7 square:18,29
中间音素d的统计量:count:2 mean:3,6 square:9,36
中间音素e的统计量:count:8 mean:5,3 square:25,9
中间音素f的统计量:count:4 mean:4,4 square:16,16

  • 构建决策树,得到问题集
    首先我们将上面第四步得到的所有音素及其统计量放到树的根节点。然后根据如下公式(《Tree-Based State Tying For High Accuracy Acoustic Modelling》)得到最大似然提升对应的划分,对节点进行分裂。
    在这里插入图片描述
    不断地递归进行节点分裂,我们就可以得到一颗决策树,如下:
    在这里插入图片描述
注意,这里是把除根节点以外的所有节点都看做是问题集!!!

kaldi中生成的问题集存储在questions.int文件中,格式如下:
在这里插入图片描述
上图中的一行就对应一个问题,也是一系列phone组成的集合。

  • 对hmm state以及三音素位置进行提问
    第5步我们已经得到了问题集,那么现在需要对不同的hmm state和三音素位置进行提问。在kaldi中,提问的格式如下:

<Questions> <Key> -1 <QuestionsForKey> 4 [ 0 ]
[ 0 1 ]
[ 0 1 2 ]
[ 0 1 2 3 ]
<RefineClustersOptions> 0 2 </RefineClustersOptions>
<Key> :-1:表示对hmm-state提问 ;0: 表示对左边的音素提问;1: 表示对中间的音素提问;2: 表示对右边的音素提问。
<QuestionsForKey> 4: 对于hmm-state的提问,总共有4个问题。中括号[]的内容即代表问题。比如[0]就表示的“当前三音素的hmm状态是0吗?”这个问题;[ 0 1 ]表示的“当前三音素的hmm状态是0或1吗?”这个问题。
<RefineClustersOptions>: 第一个数字代表num-iters-refine(Number of iters of refining questions at each node.) 第二个数字代表top_n(用k-means算法时,对于每一个point,计算离他最近的top_n个clusters)
有了问题集,怎么构建决策树呢?

同理,下面是对三音素的左音素进行提问(比如当前三音素的左边音素是{p,m,n}中的其中一个吗?):

</QuestionsForKey> <Key> 0 <QuestionsForKey> 438 [ 1 ]
[ 2 21 23 65 66 67 68 69 75 76 77 78 79 80 81 83 84 155 157 159 209 ]
[ 2 65 66 67 68 69 155 157 159 ]
[ 2 155 157 159 ]
[ 2 159 ]
… … …

下面对三音素的中间音素进行提问(比如当前三音素的中间音素是{a,b,c}中的其中一个吗?):
ps:中括号中的数字是音素的编号

</QuestionsForKey> <Key> 1 <QuestionsForKey> 438 [ 1 ]
[ 2 21 23 65 66 67 68 69 75 76 77 78 79 80 81 83 84 155 157 159 209 ]
[ 2 65 66 67 68 69 155 157 159 ]
[ 2 155 157 159 ]
[ 2 159 ]
… … …

下面对三音素的右音素进行提问(比如当前三音素的右边音素是{g,d,e}中的其中一个吗?):

</QuestionsForKey> <Key> 2 <QuestionsForKey> 438 [ 1 ]
[ 2 21 23 65 66 67 68 69 75 76 77 78 79 80 81 83 84 155 157 159 209 ]
[ 2 65 66 67 68 69 155 157 159 ]
[ 2 155 157 159 ]
[ 2 159 ]
… … …

kaldi中,对于三音素的位置进行提问时,可选的问题集都是一样的(如上图,都是438个问题),都是来自第5步得到的问题集。

截止到当前,我们已经得到了三音素不同位置以及hmm state对应的可选的问题集。接下来我们需要从这些问题集中选取一些比较”好“的问题,对每个音素的每个hmm state建立一颗属于他们自己的决策树,从而达到状态绑定的目的。

2、有了问题集,怎么构建决策树?
  • 确定需要建立几颗决策树

在建立决策数之前我们需要读取roots文件。该文件包含有两组关键词
第一组:shared or not-shared 表示对一个音素的3(或5)个hmm state分别建立3颗不同的决策树,还是所有hmm state共享一颗决策树树根。
第二组:split or not-split 表示是否需要对该音素对应的决策树进行分裂,如果不进行分裂,该决策树就只有一个树根。
一个典型的roots文件形式如下:

shared split 1
shared split 2
shared split 3
shared split 4
shared split 5
shared split 6
shared split 7
shared split 8
shared split 9
shared split 10
shared split 11
shared split 12
shared split 13
shared split 14
… … …

上面的数字是音素的编号。假设我们有218个音素,每个音素有3个hmm state。根据这个roots文件,那么我们就需要建立218颗决策树(而不是218*3,因为shared,即同一个音素的不同hmm state共享用一颗决策树)。

  • 建立决策树

理论上我们需要建立218颗决策树,但我们把它放在一颗大的决策树下,如下图所示:
在这里插入图片描述
接下来需要对每一颗小决策树进行分裂,如下图所示:
在这里插入图片描述

实际上多个小决策树是同时进行分裂的,那么每次选取哪颗决策树的结点进行分裂呢?可以通过优先队列这样的数据结构进行调度,每次对似然度提升最大的结点进行进一步分裂。

构建完决策树后,我们还会将两两空间距离较近(如欧式距离)的叶子结点绑定在一起。最后生成的决策树如下:
在这里插入图片描述在这里插入图片描述
这两张图其实是同一张,只是图太大了,分开截取。第一张主要展示的是决策树的前面几层,第二张主要展示的是决策树的后面几层。双圆圈就是叶子结点,对应一个混合高斯分布

kaldi最后构建出来的树的结构如下:

ContextDependency 3 1 ToPdf TE 1 219 ( NULL SE -1 [ 0 1 ]
{ SE -1 [ 0 ] -1hmm-state [0]hmm-statepdfclass0
{ CE 0 CE 220 }
SE -1 [ 0 1 2 3 ]
{ SE -1 [ 0 1 2 ]
{ CE 218 CE 233 }
CE 219 }
}
SE -1 [ 0 1 ]
{ SE -1 [ 0 ]
{ SE 0 [ 144 149 ]
{ CE 969 SE 0 [ 29 30 123 145 217 ]
{ CE 969 SE 0 [ 7 59 124 ]
{ CE 969 SE 0 [ 7 28 31 57 58 59 117 124 138 144 149 218 ]
{ CE 1206 CE 1255 }
}
}
}
SE 2 [ 1 ]
{ CE 275 SE 0 [ 144 149 ]
{ CE 276 CE 701 }
}
}

说明:
ContextDependency 3 1表示音素宽度N_(=3)、中间音素位置P_(=1)
TE 1 219:TE表示决策树树根。1表示对中间音素进行划分,219表示划分完后总共有219个结点(实际上我们只有218个音素(1-218),但代码中计算的是0-218,所以总共有219个结点。NULL就是0对应的结点)
SE -1 [0 1]:SE表示决策树的非叶子结点,-1表示该结点是对hmm state进行提问;[0 1]表示提的问题是”该三音素的hmm state是0或1吗“。
CE 0:CE表示决策树的叶子结点 0表示该叶子结点的编号(在kaldi中叫pdf-id)

最后还是要强调下,上面流程实际上我们做了两次的决策树建立,要注意区分:
第一次:是为了得到问题集。通过将所有音素作为决策树根节点,然后计算对音素集进行划分带来的似然度提升,不断对结点进行分裂,进而得到一系列问题集;
第二次:是为了进行状态绑定。对所有中间音素以及hmm state相同的EventType,我们从第一步得到的问题集中选出对似然度提升最大的问题,对建立一颗决策树。然后比较所有叶子结点,把两两空间距离较近(如欧式距离)的叶子结点绑定在一起,有共同的pdf-id(混合高斯函数)。

  • 16
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 7
    评论
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值