建议在csdn资源页中免费下载该学习笔记的PDF版进行阅读:)点击进入下载页面
三音素GMM与单音素GMM的主要差别在于决策树状态绑定,与GMM参数更新相关的原理、程序和类两者都是一样的。 在这个笔记中,我会首先介绍表示HMM的类HmmTopology和TransitionModel,然后介绍三音素GMM训练脚本train_deltas.sh用到的几个程序,这几个程序与单音素GMM的不同或者只在三音素GMM训练中出现。与GMM相关的其余部分请参考单音素GMM学习笔记。
目录
Kaldi三音素GMM学习笔记 HmmTopologyTransitionModelgmm-init-modelgmm-mixup AmDiagGmmSplitByCount convert-ali为什么要介绍HmmTopology(后简称HT)和TransitionModel(后简称TM)?前面我们几乎一直在讲GMM和决策树,那么HMM用什么表示?在Kaldi中用TM表示HMM,TM中包含一个HT对象,用来表示HMM拓扑结构。 在Kaldi数据准备阶段,Kaldi会在data/lang目录下自动生成表示HMM拓扑结构的文件topo,HT对象就保存topo中的信息。一是topo中都有哪些音素,保存在HT的数据成员phone_中;二是每个音素的HMM结构是什么,由HT的数据成员phone2idx_和entries_共同决定。我们用下面一个图来解构HmmTopology的数据成员。
在单音素GMM初始化程序gmm-init-mono和三音素GMM初始化程序gmm-init-model中都会调用TM构造函数TransitionModel(const ContextDependencyInterface &ctx_dep, const HmmTopology &hmm_topo)来初始化TM。我们也就以此构造函数为切入口,来学习TransitionModel中各数据成员是怎么构造出来的。 我们先来看看TM都有哪些数据成员以及各自的作用:
HmmTopology topo_; /// 由transition state – 1进行索引; std::vector<Tuple> tuples_; /// Gives the first transition_id of each transition-state; indexed by /// the transition-state. Array indexed 1..num-transition-states+1 (the last one /// is needed so we can know the num-transitions of the last transition-state. std::vector<int32> state2id_; /// For each transition-id, the corresponding transition /// state (indexed by transition-id). std::vector<int32> id2state_; std::vector<int32> id2pdf_id_; /// For each transition-id, the corresponding log-prob. Indexed by transition-id. Vector<BaseFloat> log_probs_; /// For each transition-state, the log of (1 - self-loop-prob). Indexed by /// transition-state. Vector<BaseFloat> non_self_loop_log_probs_; /// This is actually one plus the highest-numbered pdf we ever got back from the /// tree (but the tree numbers pdfs contiguously from zero so this is the number /// of pdfs). int32 num_pdfs_;构造函数的调用过程如下两图所示:
下面我们对train_deltas.sh中与三音素GMM相关的几个程序逐个进行说明。
根据occs,调用AmDiagGmm::GetSplitTargets()得到am_gmm中每个DiagGmm i应该增加到的混个分量个数targets[i]。 GetSplitTargets()对观测数最多的pdf优先增加混合分量个数(使用优先队列实现)。对每个DiagGmm i,根据targets[i],调用DiagGmm::Split()增加该DiagGmm i的混合分量。Split()对混合分量中weights_最大的分量优先进行分割,将其权值对半分,一半留给自己一半分给新的分量,被分割分量的均值、方差相关参数直接复制给新分量,复制完后对新分量的均值、方差相关参数加一个随机的扰动。
由单音素GMM我们得到训练数据的对齐文件,但是单音素GMM中的TransitionModel tm1和三音素GMM中的TransitionModel tm2不同,两者的每个数据成员都不一样,所以要把用tm1的tid(transition-id)表示的对齐转换成tm2的tid表示的对齐。这就是convert-ali的作用。 要看懂convert-ali,首先要对TransitionModel理解的比较清楚。建议先搞明白TM再去看该程序的代码。 我个人觉得这里的核心在于:根据tid能知道当前是哪个音素的哪个HMM状态(知道tid和对应的TM,由id2state_知道t-state,由state2id和tid只能t-idx,由t-state索引tuple_知道tuple,tuple保存音素、HMM state-id,也就知道了这两者),而无论该特征向量所对应的tid编号怎么变化,该特征向量对应的音素和HMM状态都是不变的。 从旧的tid转换成新的tid的流程大致如下:
明白了上述流程之后,convert-ali的代码就容易看懂了,这里我不再讲述细节,只把自己当时学习的手写笔记草稿放在这里,希望能有一定启发。
由convert-ali得到基于新的TransitionModel的对齐后,后面的GMM参数更新就和单音素GMM一样,GMM参数更新就是对每一个GMM(也就是每一个pdf)更新相应的参数。由对齐序列(tid序列),我们就能找到属于每一个pdf的特征向量,用这些特征向量更新该pdf的每个分量的均值、方差和权值即可。
作者:许开拓 日期:写于2017-04-13 联系方式:540262601@qq.com