Kaldi决策树状态绑定学习笔记(一)

建议在csdn资源页中免费下载该学习笔记的PDF版进行阅读:)点击进入下载页面

Kaldi决策树状态绑定学习笔记(一)

——如何累积相关统计量?

  目录

原理

学习资料:
1. SLP Ch10.3
2. Daney Povey Kaldi Lecture3
3. 官方决策树三个文档、HMM文档
4. 爱丁堡大学CDHMM-PPT
5. 论文《Tree-Based State Tying For High Accuracy Acoustic Modelling》S.J.Young
6. 论文《Decision Tree-Based State Tying For Acoustic Modelling》这个里面有一些具体的公式推导

个人感悟:
  决策树这一块的理论倒没有多少,公式更是少之又少。关键在于如何用代码实现,所以值得把主要精力花在看代码上。自己在学习这一块的时候,对脚本train_deltas.sh用到的程序,采取从上到下逐一攻破的方法。首先看看文献1,5,6,4, 明白决策树工作的大致原理、思路;然后看kaldi官方文档,第一遍可能对文档的写作框架、所提到的程序几乎没什么感觉;然后就开始逐一攻破程序。看不懂代码就去看文档,看不懂文档就去看原理,看不懂原理又去看代码实现了什么,代码、文档、原理、代码、文档、类、代码、类、文档,多次重复迭代,直到逐渐弄明白到底发生了什么,代码做了什么。
  看完之后,自己试着总结一下,就当再次复习下,顺一顺自己的思路。解码那部分一个月前刚看过,自己就已经忘得差不多了,所以还是很有必要总结总结、复习复习。
  决策树这块核心的就是三个程序和相关的几个类,所以准备一个笔记写一个程序吧。

  在这个笔记中,我会首先介绍与决策树相关的几个数据结构和统计量,然后介绍累积统计量的主程序和主要函数。

EventMap之EventType

  在看决策树的统计量累积相关的代码之前,有必要先搞明白EventMap相关的EventType这一数据结构。这一块建议多阅读官方文档《Decision tree internals》。
  我们知道决策树用于对三音素GMM声学模型进行状态绑定,那么问题来了:“用什么数据结构表示三音素呢?”
  一种方法是用一对数表示三音素的位置和该位置上的音素,也就是(三音素位置,该位置上的音素),用C++表示就是pair<int, int>,第一个int取0,1,2,分别代表三音素的三个位置,第二个int取音素的编号,把这三对数放在一起就可以描述三音素了,用C++表示就是由pair<int, int>组成的vector。
  此外,除了知道三音素三个位置上的音素各是什么,我们还想知道一个HMM状态是三音素的第几个HMM状态,前面使用(位置,音素)对来表示三音素,能不能也用一对数表示HMM状态信息?当然可以,此时只要把位置置为-1即可,也就是用pair<int, int>表示HMM状态信息,第一个int取-1,表示这一对数表示的是HMM状态信息;第二个int取HMM状态编号,表示这是该三音素的第几个HMM状态(对于三状态HMM,第二个int取0,1,2)。
  表示三音素的三对数和表示HMM状态的一对数放在一起,这四对数就可以描述三音素及HMM状态。在kaldi中用EventType表示这一数据结构:

typedef std::vector<std::pair<EventKeyType,EventValueType> > EventType;

  这里的EventKeyType和EventValueType都是int32的别名,这样命名的话代码更容易被理解。
  举个例子,假设我们当前的三音素是a/b/c;假设音素a,b,c的编号分别是10,11,12;假设我们用的是标准的3状态HMM拓扑结构;那么该三音素的第二个HMM状态可表示为:

EventType e = { (-1, 1), (0, 10), (1, 11), (2,12) };

  这里的-1我们通常用常量kPdfClass=-1来表示。上面这行可能不是合法的C++代码,只是为了说明问题。

Clusterable和GuassClusterable

  这一块建议阅读官方文档《Clustering mechanisms in Kaldi》。
  Clusterable是一个纯虚类,作为kaldi聚类机制的统一接口。在三音素决策树状态绑定这一块,我们主要用到的是继承自该基类的GuassClusterable。
  Clusteralbe对象的主要作用是把统计量累加在一起,和计算目标函数。下面对这句话进行说明。
  在forced alignment之后,从左到右扫描对齐数据,我们能从中得到(三音素及HMM状态)和其对应的特征向量,也就是得到一个EventType和其对应的特征向量。在扫描过所有训练数据后,出现的每个EventType会对应多个特征向量。
  目标函数,就是论文中所提到的状态集S的似然L(S),根据L(S)的计算公式,我们需要知道状态集S产生的所有观测(也就是特征向量集)的协方差,对角协方差的对角线上是特征向量集每一维的方差,要想知道每一维的方差就需要知道特征向量集的和以及特征向量集的平方和(D(X)=E(X^2)-(EX)^2);计算L(S)除了要知道协方差,还需要知道状态集S产生的特征向量的个数,也就是状态集S出现的次数,因为kaldi中使用的是Viterbi训练,得到对齐后,我们就不需要计算posterior概率,可以用状态集S对应的特征向量的个数代替posterior概率。
  (时刻记得一个EventType表示一个三音素和hmm状态id)
  于是,我们就可以发现,与一个EventType相关的统计量包括该EventType对应的特征向量的个数、这些特征向量的累加、这些特征向量的平方的累加。这三个值,就是GuassClusterable中需要保存的统计量,并且根据这三个统计量可以计算该EventType的似然。如果把多个EventType的统计量累加在一起,就可以计算这些EventType组成的状态集的似然,因为一个EventType实际就是一个状态state。
  在扫描对齐数据累积统计量时,一个EventType对应一个Clusterable对象(确切来说是GaussClusterable对象)。在这个GaussCluterable对象中,成员count_保存着该EventType出现的次数,成员stats_矩阵的第一行保存着该EventType对应的所有特征向量的和,stats_矩阵的第二行保存着该EventType对应的所有特征向量的平方之和。

BuildTreeStatsType

  在构建决策树时,我们需要知道的所有信息就是从训练数据的对齐中得到的所有EventType(三音素+HMM状态id),和每个EventType对应的Clusterable对象。很自然的,我们可以把这两者的对应关系保存成一个对pair<EventType, Clusterable*>,然后把所有的这些对保存成一个vector,所以构建决策树所用到的统计量可以表示成:

typedef std::vector<std::pair<EventType, Clusterable*> > BuildTreeStatsType;

acc-tree-stats

  • 作用:Accumulate statistics for phonetic-context tree building. 该程序为决策树的构建累积相关的统计量。
  • 输入:声学模型、特征、对齐
  • 输出:统计量
  • 示例:
acc-tree-stats $context_opts \
    --ci-phones=$ciphonelist $alidir/final.mdl "$feats" \
"ark:gunzip -c $alidir/ali.JOB.gz|" $dir/JOB.treeacc
  • 过程:
    输入的声学模型一般为单音素训练得到的GMM模型。
    1. 打开声学模型并从中读取TransitionModel,打开特征文件、打开对齐文件
    2. 对每一句话的特征和对应的对齐,调用程序AccumulateTreeStats()累积统计量tree_stats
    3. 将tree_stats转移到BuildTreeStatsType类型的变量stats中,将stats写到文件JOB.treeacc。

AccumulateTreeStats()

void AccumulateTreeStats(const TransitionModel &trans_model,
                         const AccumulateTreeStatsInfo &info,
                         const std::vector<int32> &alignment,
                         const Matrix<BaseFloat> &features,
                         std::map<EventType, GaussClusterable*> *stats)

  主要功能:这个函数拿到一句话的特征序列和对齐序列后,从对齐序列(transition-id序列)能够得到对应的音素序列(SplitToPhones())。然后从左到右扫描该音素序列(for i=..),对每一个三音素,又由transition-id得到这个三音素的HMM状态id,这样就得到了一个个EventType。对齐序列中的一个transition-id对应一个EventType,这个transition-id对应的特征向量就是属于该EventType的数据,用该Event Type对应的GaussClusterable对象累积相关的统计量。
  对所有的特征数据、对齐数据执行这个函数后,我们就从训练数据中得到了所有的EventType(注意并不是所有的三音素都会在训练数据中出现)和该Event Type对应的统计量。这些统计量可以被用于自动产生问题集、构建决策树。
  建议自己举一个特征序列+对齐序列的例子,从左到右扫描这个例子来理解这个函数的代码。不举具体的例子来对应这个函数的代码,我个人很难理解这个函数到底在讲什么,因为很容易被i,j,center_position等变量搞晕。
  下面是我在学习这个程序时的笔记草稿,主要包括一个例子。希望有一些借鉴意义。
这里写图片描述

这里写图片描述


作者:许开拓
日期:写于2017/04/04
联系方式:540262601@qq.com

发布了22 篇原创文章 · 获赞 22 · 访问量 3万+
展开阅读全文

没有更多推荐了,返回首页

©️2019 CSDN 皮肤主题: 大白 设计师: CSDN官方博客

分享到微信朋友圈

×

扫一扫,手机浏览