Kaldi简单解码器(SimpleDecoder)
- 总述
本节我们讲述kaldi中一个最简单的解码器,实现这个解码器的类是SimpleDecoder,理解了这个解码器,也就打下了解码搜索的基础,其他的解码器方法也与其类似,目录在kaldi/src/decoder/simple-decoder.h。解码图数据结构依赖于openfst,基本上可以通过名称猜到表达的含义。
- 分析
上一节已经知道,解码的过程就是在解码图中搜索最优路径的过程,解码图的构建也在上一节做了简单的介绍,它就是语言模型、词典、上下文音素和HMM构成的一个大的资源图。关于具体如何去做以后我们结合脚本再进行细讲。
我们知道,WFST解码图中,信息都在边上、节点上没有信息(终止节点除外),边上输入表示建模单元、输出表示词、权重表示概率(语言模型概率+转移概率+词典发音概率)。
在解码前,需要先加载解码图,设定剪枝系数beam,传进解码器中,调用构造函数
SimpleDecoder(const fst::Fst<fst::StdArc> &fst, BaseFloat beam): fst_(fst), beam_(beam) { }
bool Decode(DecodableInterface *decodable);解码一整句话。
函数bool ReachedFinal() const;表示是否有节点到达终止节点。
函数bool GetBestPath(Lattice *fst_out, bool use_final_probs = true) const;获取最后输出路径。
函数BaseFloat FinalRelativeCost() const;会返回全局最优路径和到达终止节点的最优路径的差值。
函数void InitDecoding();初始化解码器,一句新的语音开始,要初始化解码器。
函数void AdvanceDecoding(DecodableInterface *decodable,int32 max_num_frames = -1);解码一段语音数据。
函数int32 NumFramesDecoded() const { return num_frames_decoded_; }返回已经解码的帧数。
类class Token;在解码过程中挂载在某个状态上的令牌,保存当前解码信息。
函数void ProcessEmitting(DecodableInterface *decodable);每帧调用,扩展实边(ilabel!=0)。
函数void ProcessNonemitting();每帧调用,扩展空边(ilabel==0)。
成员变量unordered_map<StateId, Token*> cur_toks_;当前帧活跃的节点和对应的令牌。
成员变量unordered_map<StateId, Token*> prev_toks_;上一帧活跃的节点和对应的令牌。
成员变量const fst::Fst<fst::StdArc> &fst_;解码图
成员变量BaseFloat beam_;剪枝系数
函数static void ClearToks(unordered_map<StateId, Token*> &toks);清空令牌。
函数static void PruneToks(BaseFloat beam, unordered_map<StateId, Token*> *toks);剪枝令牌
下面解析几个主要的方法:
先来看一个整的解码过程
bool SimpleDecoder::Decode(DecodableInterface *decodable) {
//初始化解码器
InitDecoding();
//帧同步解码,循环次数为帧数
while( !deco