【Kaldi例子】构图与解码

构图和解码

基于HMM的语音识别模型实际上是在解码图中寻找最优路径。因此,要进行解码,需要先构建解码图:

# Graph compilation
utils/mkgraph.sh data/lang_test_tg exp/mono0a exp/mono0a/graph_tgpr

# Decoding
steps/decode.sh --nj 1 --cmd "$decode_cmd" \
    exp/mono0a/graph_tgpr data/test_yesno exp/mono0a/decode_test_yesno

构图过程和语言模型联系紧密,因此在介绍构图前,先介绍N元文法语言模型。

N元文法语言模型

在实践中,经常简单地用单词在前文环境下出现的条件概率来建模语言知识,这种语言模型称为N元文法(N-gram),N元文法对N-1个单词的历史条件概率建模,即:
P ( w i ∣ w i − ( N − 1 ) ⋯ w i − 2 w i − 1 ) P(w_i|w_{i-(N-1)}\cdots w_{i-2}w_{i-1}) P(wiwi(N1)wi2wi1)
在Kaldi中,语言模型用ARPA格式保存,内容如下:

\data\
# x-gram对应的组合次数
ngram 1=200003
ngram 2=2451827
ngram 3=1134656

\1-grams:
# 每个词对的条件概率P和回退值(未出现词对的惩罚因子)
-2.348754	</s>
-2.752519	<UNK>	-0.2779175
-99	<s>	-1.070027
-2.619969	A	-0.7371845
-7.211563	A''S
-6.221141	A'BODY	-0.1187751
-6.583487	A'COURT
-6.240468	A'D	-0.05992588
-7.108924	A'GHA
-6.260695	A'GOIN	-0.2617707
-5.804425	A'LL	-0.1488222
-5.638036	A'M	-0.1254423
-6.221141	A'MIGHTY
...
\end\

加权有限状态转录机

Kaldi构图使用加权有限状态转录机(Weighted Finite-State Transducer,WFST)算法。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-n2ONycaY-1642231496923)(etc/README/image-20220112171621829.png)]

如图所示,一个WFST由一组状态(State)和状态间的有向跳转(Transition)构成,其中每个跳转上保存三种信息,即input_label:output_label/weight。如图a中从状态1向状态2跳转,输入输出都是data,权重是0.66。图b中从状态0向状态1跳转,输入是d,初始是data,权重是1。

WFST还具备一个起始状态(粗圈表示)和至少一个终止状态(双圈表示)。每个终止状态可以有一个终止权重,但在图中没有画出。WFST还需要定义两个二元操作+x,这两个操作和权重集合应构成一个半环(Semiring)。根据半环类别不同,两种操作可被定义为各种运算。

Kaldi的WFST基于OpenFst,以C++ API和二进制可执行文件的形式提供了WSFT的表示和各种操作的实现。

如上图a中内容表示成OpenFst的描述语言为:

# example-a.fst.txt
0 1 using using 1
1 2 data data 0.66
1 3 intuition intuition 0.33
2 4 is is 0.5
2 4 are are 0.5
3 4 is is 1
4 5 better better 0.7
4 5 worse worse 0.3
5 1.0

除了最后一行外,每行代表一个跳转,最后一行表示终止状态5,终止权重为1.0。

OpenFst中输入和输出标签都要用数字表示,因此还需要定义一个标签文本到数字的映射表:

# symbols.txt
<eps> 0
using 1
data 2
intuition 3
is 4
are 5
better 6
worse 7

使用OpenFst编译工具可以将该WFST编译成二进制文件:

fstcompile --isymbols=symbols.txt --osymbols=symbols.txt example-a.fst.txt eample-a.fst

编译完成后,就可以使用OpenFst工具其进行各种操作。如使用fstinfo查看信息,使用fstprint打印成文本或者使用fstdraw输出成Graphviz软件定义的图格式以便可视化:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cNo9rWJG-1642231496926)(etc/README/example-a.png)]

用WFST表示语言模型

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-p4c8EErF-1642231496934)(etc/README/image-20220113145821292.png)]

以二元文法(2-gram)为例,每个词只讲前一个词作为历史信息,假定词w2前面的词是w1,那么这个条目的语言模型可写为p(w2|w1)回退概率可写为 β \beta β(w1)。我们为w1和w2分别在图中简历一个状态,用跳转从w1指向w2,跳转的输入标签和输出标签均为w2,权重为p(w2|w1)。这样当序列存在序词对w1w2时,就可以匹配这个跳转。在图中还需建立一个回退状态b,用跳转从w1指向b,跳转权重为p(w2|w1)。该跳转用于匹配从w1出发找不到w2的情况,此时序列的语言概率用回退概率作为惩罚因子。如果经过了回退状态,继续跳转到w2时,就直接使用w2的一元概率p(w2)。

这样生成的WFST就是单词级语法(Word-level Grammar),称为“G”。经过这样展开,如果把概率换成概率的负对数,任意序列的语言模型负对数积累概率恰好等于图中某条路径的累积权重。

Kaldi中从语言模型构建G操作由local/format_lms.sh完成,核心代码如下:

for lm_suffix in tgsmall tgmed; do
  # tglarge is prepared by a separate command, called from run.sh; we don't
  # want to compile G.fst for tglarge, as it takes a while.
  test=${src_dir}_test_${lm_suffix}
  mkdir -p $test
  cp -r ${src_dir}/* $test
  gunzip -c $lm_dir/lm_${lm_suffix}.arpa.gz | \
    arpa2fst --disambig-symbol=#0 \
             --read-symbol-table=$test/words.txt - $test/G.fst
  utils/validate_lang.pl --skip-determinization-check $test || exit 1;
done

该脚本对tgsmall和tgmed两个不同规模的语言模型进行了G构建操作。对APRA格式语言模型文件解压后,直接输入到arpa2fst程序中,就得到了G.fst。

状态图构建

经典语音识别需要发音词典来获取每个单词的发音。词典会出给每个常见单词提供发音序列。有的单词在发音词典中找不到,这些词被称为集外词(OOV),一般需要人工补充或者使用预料训练的词转音素(G2P)算法自动预测单词发音。发音词典条目示例如下:

yes y eh s
am ae m

发音词典准备好后,需要把发音词典用WFST表示:对每个单词的发音条目,从初始状态引入一条路径,其输入标签序列为音素序列,输出标签序列为单词和若干用于填充的 ϵ \epsilon ϵ。以上条目对应的WFST为:
请添加图片描述

yes这个单词,路径输入标签序列为“y eh s”,即发音词典中的音素序列,其输出标签序列为“yes e e”,其中后面两个e只是为了保证输入标签序列和输出标签序列的长度一致,最后该路径返回初试状态。这个WFST由于和发音词典包含相同信息,故也被称为发音词典,简称L。

在Kaldi中,分别使用utils/lang/make_lexicon_fst.pyutils/lang/make_lexicon_fst_silprob.py构建不带静音概率和带静音概率的L。这两种脚本输出的都是文本形式的OpenFst格式,之后用fstcompile编译,并使用fstaddselfloops添加自跳转,用fstarcsort对生成的图按照输出标签做排序。

WFST定义了复合运算,把词图展开成因素图。我们用一个示例解释WFST的复合:

在这里插入图片描述

上图所示的WFST记为T1,它把小写字母abc转录为了相应的大写字母。

在这里插入图片描述

再看另一个T2,它把A转录为YES,把BC转录为NO。

如果把序列“a c b a”先通过T1转录,转录后再经过T2转录,那么得到得到序列是“YES NO NO YES”。一般说,如果任意输入序列被T转录结果和先后被T1和T2转录结果相同,则称T是T1和T2的复合,记为:
T = T 1 ∘ T 2 T=T_1\circ T_2 T=T1T2
T的状态为T1和T2的状态对。如果记状态集合为Q,那么:
∀ ( q 1 , q 2 ) ∈ Q → q 1 ∈ Q 1 , q 2 ∈ Q 2 \forall(q_1,q_2)\in Q\rightarrow q_1\in Q_1,q_2\in Q_2 (q1,q2)Qq1Q1,q2Q2
复合后的结果如下所示:

在这里插入图片描述

图的复合有固定算法,由fstcompose实现。

了解了WFST的复合运算,把此图按发音展开到音素级别,可表示为:
L G = L ∘ G LG=L\circ G LG=LG
LG图把单音子序列转录成单词序列,如下图所示,a为G,b为L,c为LG:

在这里插入图片描述

除了G和L外,最终图中还要复合上下文转录机C,HMM拓扑结构H构成HCLG。HCLG是可以把HMM状态序列转录为单词序列的WFST,这样结合声学特征和声学模型,就可以进行解码了。

基于令牌传递的维特比搜索

构建了HCLG后,我们希望在图中找到一条最优路径,该路径上输出标签锁代表的HMM状态在待识别语音上的代价要尽可能小,这条路径上取出e后的输出标签序列就是单词级别识别结果,这个过程就是解码。

有时我们也希望找到最优的多条路径,这个识别结果被称为N-best列表。在HMM上解码的经典算法是维特比算法。维特比算法的朴素实现通常是简历一个TxS的矩阵,T为帧数,S为HMM状态综述。对声学特征按帧遍历,对于每一帧的每个状态,把前一帧各个状态的累积代价和当前帧在当前状态下的代价累加,选择使当前帧代价最低的前置状态作为当前路径的前置状态。在实现中,并不需要始终存储整个矩阵信息,而只保留当前帧以及上一帧的信息即可。

在语音识别中,更常见的是使用一种更灵活的算法来实现维特比算法,即令牌传递算法。该算法的基本思路是把令牌进行传递。 算法启动后,首先在所有起始状态上放置一个令牌,然后对于每一帧,所有令牌都沿跳转向前传递,把传递代价进行累积。如果一个状态有多个跳转,则把令牌复制多分,分别传递。这样传递到最后一帧,检查所有令牌的代价,选出一个最优令牌,就是搜索结果了。上述算法令牌个数随令牌复制剁成会指数级增长。考虑到维特比算法的一个主要思想是全局最优必然局部最优,即如果一条路径是全局最优的,那么该路径必然是其经过任意状态的局部最优路径。所以,当多个令牌传递到同一状态时,只保留最优的令牌即可。

以下是最简单的构建状态图和解码的代码:

# 构建状态图
utils/mkgraph.sh data/lang_nosp_test_tgsmall exp/tri1 exp/tri1/graph_nosp_tgsmall || exit 1
# 特征处理
data=data/test_clean
apply-cmvn --utt2spk=ark:$data/utt2spk scp:$data/cmvn.scp \
  scp:$data/feats.scp ark:- | add-deltas ark:- ark:feats_cmvn_delta.ark || exit 1
# 解码
am=exp/tri1/final.mdl
hclg=exp/tri1/graph_nosp_tgsmall/HCLG.fst
gmm-decode-simple $am $hclg ark:feats_cmvn_delta.ark ark,t:result.txt || exit 1
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值