语音识别WFST核心算法讲解(2. Generic Composition)

上一篇介绍了WFST的基本概念,是了解一下算法的核心,如果对此不了解,建议先看语音识别WFST核心算法讲解(1. WFST的基本概念)
本文介绍Composition算法。首先介绍epsilon-free的composition,然后扩展至generic composition,最后对时间复杂度分析,介绍实际工程中openfst和kaldi中的一些优化技巧。
epsilon-free的composition算法伪代码如下:
这里写图片描述

接下来line by line介绍这一算法。

首先对于T1, T2两个图,将两者的初始状态合并作为输出结果图的初始状态, 将其放入队列S中(line1 - line5),只要S不为空(line 6)依次拿出S中第一个状态对q(q1, q2)(q是一个pair,分别是两个图中的State),如果q1是T1 的终止状态, q2是T2的终止状态,将q作为输出结果的终止状态(line9 - line12)。 对于每个从q1出发的转移e1,从q2出发的转移e2,组成转移对(e1, e2),如果e1的输出等于e2的输入(line13), n[e1]和n[e2]组成的状态对如果之前没有出现过,放入到队列S中(line14 - line17)。之后添加新的转移(line18),转移的起始状态为(q1,q2),终止状态为 ( n[e1], n[e2]), 输入为e1的输入,输出为e2的输出,权重为e1和e2的权重之和(注意这里是tropical semiring)。
最后输出结果(line 21).

然而这种算法并不general 无法处理T1中有ε作为输出或者T2中有ε作为输入的情况。 因为在以上算法line13中o[e1] = i[e2],ε作为特殊的label,可以和任意匹配,这会导致 redundant paths。

针对此问题,解决方案是给一个Filter作为F,对于T1,每个转移输出如果为ε,换为εo,并且每一个state加一个自旋边,同样T2中每个转移输入如果为ε,换为εi,也加自旋边。新的图作为T1′ 和T2′,最后变成T1′ ◦ F ◦ T2′ 。这样做使得状态对由二元祖变为三元祖,多了一个fileter state,通过设置filter的规则,T1的εo无法立刻和T2中的εi组合起来,这样做相当于过滤了redundant paths。
这里写图片描述

在实际的实现中,filter并不是临时创建,而是hard coded在composition算法中,本人就openfst算法做简要介绍。
首先引入更general的composition算法,伪代码如下:
这里写图片描述
其中 Φ就是之前说的filter,和之前的epsilon-free的算法区别不大,状态对由二元祖变为三元祖,多了一个fileter state 也就是变成了q(q1, q2, q3) 以Epsilon-Sequencing Filter为例,该filter规则如下:
这里写图片描述
这里写图片描述
⊥表示阻塞状态,在以上算法line10中,如果三元祖中出现阻塞状态,则跳过不做处理,这里取代了部分epsilon-free算法的line13。

至此,我们分别介绍了epsilon-free的composition和 generic composition算法,然而实际代码实现时候仍然有一些优化tricks。
在实际操作中,最费时的epsilon-free算法的line13和generic算法的line10。如果实际q1有N1条转移,q2有N2条转移,暴力搜索的话,时间复杂度是O(N1•N2)。 Openfst引入一个叫Matcher的东西,就是优化这一操作。例如SortedMatcher就是对label排序,比较N1或者N2的大小,对大的那个采取二分查找,这样复杂度降为O(N1•logN2)(N2 > N1)或者O(N2•logN1)(N1 > N2)。在kaldi中进一步优化,例如函数TableCompose中的TableMatcher通过判断每个q1,q2上所有转移上label大小的稀疏性(转移的总数除以最大label和事先决定的阈值table ratio比较判定),在不过分稀疏的时候建立一个table缓存来记录是否存在某个label及其相应的位置,这样匹配的时候直接常数时间内就能决定,这样直接查表避免了二分查找, 时间复杂度为O(N1+N2).

猜你喜欢

转载自blog.csdn.net/fengzhou_/article/details/80778240
今日推荐