21 解码图创建算法(测试时)

在这里,我们将逐步解释正常的图形创建方法,以及与此相关的某些数据准备阶段。

这种方法的大多数细节都没有硬编码到我们的工具中。我们只是在解释目前的工作方式。如果本节令人困惑,最好的解决方法可能是阅读Mohri等人的“带有加权有限状态传感器的语音识别”。请注意:该论文很长,对于不熟悉FST的人来说,阅读它至少需要几个小时。另一个很好的资源是OpenFst网站,它将提供有关符号表之类的更多上下文。

准备初始符号表

我们需要准备OpenFst符号表word.txt和phone.txt。这些将整数ID分配给系统中的所有单词和音素。请注意,OpenFst为epsilon保留符号零。符号表查找WSJ任务的示例如下:

## head words.txt
 0
!SIL 1
 2
 3
 4
 5
 6
!EXCLAMATION-POINT 7
"CLOSE-QUOTE 8
## tail -2 words.txt
}RIGHT-BRACE 123683
#0 123684
## head data/phones.txt
 0
SIL 1
SPN 2
NSN 3
AA 4
AA_B 5

words.txt文件包含单个歧义消除符号“#0”(用于G.fst输入上的epsilon)。这是我们食谱中的最后一个字。如果您的词典中包含单词“#0”,请务必谨慎。 phones.txt文件不包含消歧符号,但是在创建L.fst之后,我们将创建一个文件phones_disambig.txt,其中包含消歧符号(这仅对调试有用)。

准备词典L

首先,我们创建一个文本格式的词典,最初没有歧义符号。我们的C ++工具将永远不会与之交互,它将仅由创建词典FST的脚本使用。 《华尔街日报》词典的一小部分是:

## head data/lexicon.txt
!SIL SIL


 SPN
 SPN
 NSN
!EXCLAMATION-POINT EH2_B K S K L AH0 M EY1 SH AH0 N P OY2 N T_E
"CLOSE-QUOTE K_B L OW1 Z K W OW1 T_E

音素上的开始,结束和重读标记(例如T_E或AH0)特定于我们的WSJ配方,就我们的工具包而言,它们被视为不同的音素(但是,我们确实专门为此设置;请在“树的构建过程”中了解有关roots文件的信息。

注意,我们允许带有空语音表示的单词。该词典将用于创建训练中使用的L.fst(无歧义符号)。我们还创建了带有歧义符号的词典,用于解码图形的创建。此文件的摘录在这里:

# [from data/lexicon_disambig.txt]
!SIL    SIL
 #1
    #2
  SPN #3
   SPN #4
 NSN
...
{BRACE  B_B R EY1 S_E #4
{LEFT-BRACE L_B EH1 F T B R EY1 S_E #4

该文件由脚本创建;该脚本输出必须添加的消歧符号的数量,该代码用于创建符号表phone_disambig.txt。这与phones.txt相同,但是它还包含消除歧义符号#0,#1,#2等的整数ID(以此类推(#0是来自G.fst的特殊消除歧义符号,但会“通过“通过自循环进行L.fst)。文件phones_disambig.txt的中间部分是:

ZH_E 338
ZH_S 339
#0  340
#1  341
#2  342
#3  343

数字如此之高,因为在此(WSJ)配方中,我们向音素添加了压力和位置信息。请注意,用于空词(即)的消歧符号必须与用于普通词的消歧符号不同,因此本示例中的“正常”消歧符号从#3开始。

将没有歧义符号的词典转换为FST的命令是:

scripts/make_lexicon_fst.pl data/lexicon.txt 0.5 SIL | \
  fstcompile --isymbols=data/phones.txt --osymbols=data/words.txt \
  --keep_isymbols=false --keep_osymbols=false | \
   fstarcsort --sort_type=olabel > data/L.fst

在这里,脚本make_lexicon_fst.pl创建FST的文本表示。 0.5是无声概率(即在句子开头和每个单词之后,我们以无声概率0.5输出无声;分配为无声的概率质量为1.0-0.5 = 0.5。此示例中的其余命令与将FST转换为已编译的形式; fstarcsort是必需的,因为我们稍后将进行撰写。

词典的结构大致与人们预期的一样。有一个最终状态(“循环状态”)。有一个开始状态,该状态有两个过渡到循环状态的状态:一个处于静音状态,一个没有静音状态。从循环状态开始,每个单词对应一个过渡,并且该单词是过渡上的输出符号。输入符号是该单词的第一个音素。对于合成效率和最小化效率而言,重要的是输出符号应尽可能早(即,在单词的开头而不是单词的结尾)。在每个单词的末尾,为了处理可选的静音,对应于最后一个音素的过渡有两种形式:一种是循环状态,另一种是具有过渡到循环状态的“静音状态”。我们不会打扰在静音词之后放置可选的静音,我们将静音词定义为只有一个音素即静音音素的词。

用歧义符号创建词典稍微复杂些。问题是我们必须将自环添加到词典中,以便G.fst中的消歧符号#0可以通过词典传递。尽管可以在脚本make_lexicon_fst.pl中“手动”完成该操作,但是我们使用fstaddselfloops程序(请参见添加和删除消歧符号)来完成此操作。

phone_disambig_symbol=`grep \#0 data/phones_disambig.txt | awk '{print $2}'`
word_disambig_symbol=`grep \#0 data/words.txt | awk '{print $2}'`

scripts/make_lexicon_fst.pl data/lexicon_disambig.txt 0.5 SIL  | \
   fstcompile --isymbols=data/phones_disambig.txt --osymbols=data/words.txt \
   --keep_isymbols=false --keep_osymbols=false |   \
   fstaddselfloops  "echo $phone_disambig_symbol |" "echo $word_disambig_symbol |" | \
   fstarcsort --sort_type=olabel > data/L_disambig.fst

程序fstaddselfloops不是原始的OpenFst命令行工具之一,它是我们自己的工具之一(我们有许多这样的程序)。

准备语法G

语法G在大多数情况下是一个接受器(即,每个弧上的输入和输出符号都相同),以单词作为其符号。唯一的例外是消歧符号#0,它仅出现在输入侧。假设输入是一个Arpa文件,我们使用Kaldi程序arpa2fst将其转换为FST。程序arpa2fst输出带有嵌入式符号的FST。在Kaldi中,我们通常使用没有嵌入符号的FST(即,我们使用单独的符号表)。除了运行arpa2fst外,我们还需要执行以下步骤:

  • 我们必须从FST中删除嵌入式符号(并依靠磁盘上的符号表)。
  • 我们必须确保语言模型中没有词汇外的单词
  • 我们必须删除句子开头和结尾符号的“非法”序列,例如后跟,因为它们导致L o G无法确定。
  • 我们必须用特殊的消歧符号#0替换输入侧的epsilons。

执行此操作的实际脚本略有简化,如下所示:

gunzip -c data_prep/lm.arpa.gz | \
  arpa2fst --disambig-symbol=#0 \
             --read-symbol-table=data/words.txt - data/G.fst

最后一个命令(fstisstochastic)是诊断步骤(请参阅保留随机性并对其进行测试)。在一个典型的例子中,它打印出数字:

9.14233e-05 -0.259833

第一个数字很小,因此可以确认没有任何弧的质量加上最终状态的概率明显小于一个的状态。第二个数字是有效的,这意味着存在状态质量“过多”的状态(FST中权重的数值通常可以解释为对数概率)。对于带有退避的语言模型的FST表示,使某些状态具有“太多”的概率质量是正常的。在以后的图形创建步骤中,我们将验证这种非随机性没有比开始时更糟。

生成的FST G.fst当然仅用于测试时间。在训练期间,我们使用从训练单词序列生成的线性FST,但这是在Kaldi流程中完成的,而不是在脚本级别进行。

准备LG

在将L与G组合时,我们遵循一个相当标准的配方,即计算。命令行如下:

fsttablecompose data / L_disambig.fst data / G.fst | \
    fstdeterminizestar --use-log = true | \
    fstminimizeencoded | fstpushspecial | \
     fstarcsort --sort-type = ilabel> somedir / LG.fst

与OpenFst的算法略有不同。我们使用由命令行工具“ fsttablecompose”实现的更有效的合成算法(请参阅“合成”)。我们的确定化是一种也可以删除epsilon的算法,由命令行程序fstdeterminizestar实施。选项–use-log = true要求程序首先将FST强制转换为日志半环。这样可以保持随机性(在对数半圆环中);请参阅保留随机性并进行测试。

我们使用程序“ fstminimizeencoded”进行最小化。这与适用于加权受体的OpenFst最小化算法版本基本相同。此处唯一相关的更改是,它避免了施加重压,从而保持了随机性(有关详细信息,请参见最小化)。

程序“ fstpushspecial”类似于OpenFst的“ fstpush”程序,但是如果权重不加一,它可以确保所有状态“加和”为相同的值(可能与一个不同),而不是尝试将其推入相同的值。在图表的开始或结尾处“增加”权重。这样做的好处是它永远不会失败(如果FST“求和”为无穷大,“ fstpush”可能会失败或循环很长时间);它也快得多。有关更多详细文档,请参见push-special.cc。

“ fstarcsort”阶段以一种有助于以后的合成操作更快的方式对弧进行排序。

准备CLG

为了获得一个输入是上下文相关的音素的转换器,我们需要准备一个称为CLG的FST,它等效于,其中L和G是词典、语法,而C是语音上下文。对于三音系统,C的输入符号将是a/b/c的形式(即三音素),而输出符号将是单个音素(例如a或b或c)。有关语音上下文窗口的更多上下文以及如何概括为不同上下文大小的信息,请参见 Phonetic context windows。首先,我们描述了如果要单独创建上下文FST C并正常组成(出于效率和可伸缩性的原因,我们的脚本实际上不会以这种方式工作)的情况,我们将如何创建上下文FSTC。

制作上下文转换器

在本节中,我们说明如何获得C作为独立的FST。

C的基本结构是,它具有大小为N-1的所有可能的音素窗口的状态(参见 Phonetic context windows;在三音素情况下为N = 3)。第一种状态,即发声开始,仅对应于N-1个ε。每个状态在每个音素上都有一个转移(现在让我们忘记自循环)。作为一般示例,状态a/b有一个以c为输出,a/b/c为输入的转移,转移到状态b/c。话语开始和结束时都有特殊情况。

在发话开始时,假设状态为/,并且输出符号为a。通常,输入符号为//a。但这并不代表音素,因为(假设P = 1),中心元素是而不是音素。在这种情况下,我们将转移弧的输入符号设为#-1,这是为此目的引入的特殊符号(我们在这里不像标准配方那样使用epsilon,因为当有空单词时,它可能导致不确定性)。

话语结束的情况有点复杂。上下文FST在右侧(其输出端)具有一个特殊符号 $,该符号出现在语音结尾。考虑三音素的情况。话语结束时,看到所有符号后,我们需要冲洗掉最后一个三音位(例如a/b/,其中代表未定义的上下文)。有一个自然的方法是在状态a/b到最终状态(例如b / 或一个特殊的最终状态)中间,存在一个以a/b/为输入和以为输出的转移。但这对于构图是没有效率的,因为在未到达语句结尾处前,我们必须遍历所有转移,而不得进行剪枝操作。相反,我们使用$作为结束语符号,并确保它在LG中每个路径的末尾出现一次。然后,在C的输出上用$替换。通常,$的重复数等于。为了避免必须计算出要添加到LG的后继符号的数量这个麻烦,我们只允许它在语音结束时接受任意数量的此类符号。这可以通过函数AddSubsequentialLoop()和命令行程序fstaddsubsequentialloop来实现。

如果我们想单独使用C,则首先需要一个消除歧义符号的列表;并且我们还需要计算出一个未使用的符号ID,可用于后续符号,如下所示:

 grep '#' data/phones_disambig.txt | awk '{print $2}' > $dir/disambig_phones.list
 subseq_sym=`tail -1 data/phones_disambig.txt | awk '{print $2+1;}'`

然后,我们可以使用以下命令创建C(然而,关于fstcomposecontext,请参见下文;由于效率低下,我们在实践中不这样做)。

fstmakecontextfst --read-disambig-syms=$dir/disambig_phones.list \
 --write-disambig-syms=$dir/disambig_ilabels.list data/phones.txt $subseq_sym \
   $dir/ilabels | fstarcsort --sort_type=olabel > $dir/C.fst

程序fstmakecontextfst需要音素列表,消歧符号列表以及后续符号的标识。除了C.fst,它还会写出文件“ ilabels”,该文件解释C.fst左侧的符号(请参阅 The ilabel_info对象)。与LG的组合可以如下进行:

fstaddsubsequentialloop $subseq_sym $dir/LG.fst | \
 fsttablecompose $dir/C.fst - > $dir/CLG.fst

为了打印出C.fst以及使用索引“ ilabels”的相同符号的任何东西,我们可以使用以下命令制作合适的符号表:

fstmakecontextsyms data/phones.txt $dir/ilabels > $dir/context_syms.txt

此命令了解“ ilabels”格式(ilabel_info对象)。通过CLG fst(用于Resource Management)的示例随机路径随此符号表一起打印出来,如下所示:

## fstrandgen --select=log_prob $dir/CLG.fst | \
   fstprint --isymbols=$dir/context_syms.txt --osymbols=data/words.txt -
0   1   #-1 
1   2   /s/ax  SUPPLIES
2   3   s/ax/p  
3   4   ax/p/l  
4   5   p/l/ay  
5   6   l/ay/z  
6   7   ay/z/sil    
7   8   z/sil/ 
8

使用C进行动态组合

在正常的图形创建方法中,我们使用程序fstcomposecontext,它动态创建C所需的状态和转移弧边,而不会有任何浪费。命令行是:

fstcomposecontext  --read-disambig-syms=$dir/disambig_phones.list \
                   --write-disambig-syms=$dir/disambig_ilabels.list \
                   $dir/ilabels < $dir/LG.fst >$dir/CLG.fst

如果我们的上下文参数N和P与默认值(3和1)不同,我们将为该程序提供其他选项。该程序将写入文件“ ilabels”(请参阅ilabel_info对象),该文件解释CLG.fst的输入符号。Resource Management 算法中ilabels文件的前几行如下:

65028 [ ]
[ 0 ]
[ -49 ]
[ -50 ]
[ -51 ]
[ 0 1 0 ]
[ 0 1 1 ]
[ 0 1 2 ]
...

数字65028是文件中元素的数量。像[-49]这样的线表示消歧符号;像[0 1 2]这样的行表示声学上下文窗口;前两个条目是[](用于epsilon(从未使用))和[0](用于特殊歧义符号),我们在C的开头使用了输出形式#-1来代替epsilon,以确保可确定化。

减少上下文相关输入符号的数量

创建CLG.fst之后,有一个可选的解码图创建阶段可以减小其大小。我们使用make-ilabel-transducer程序,该程序从决策树和HMM拓扑信息中得出,上下文相关音素的哪些子集将对应于相同的已编译图,因此可以合并(我们选择每个中的任意元素子集并将所有上下文窗口转换为该上下文窗口)。这与HTK的逻辑到物理映射类似。该命令是:

make-ilabel-transducer --write-disambig-syms=$dir/disambig_ilabels_remapped.list \
  $dir/ilabels $tree $model $dir/ilabels.remapped > $dir/ilabel_map.fst

这个程序需要树tree和模型model。它输出一个新的名为ilabels.remappedilabel_info对象;该格式与原始“ilabels文件的格式相同,但行数较少。 FST“ ilabel_map.fst”由CLG.fst组成并重新映射标签。完成此操作后,我们确定化并最小化了尺寸,这样就可以立即实现解码图尺寸缩小:

 fstcompose $dir/ilabel_map.fst $dir/CLG.fst  | \
   fstdeterminizestar --use-log=true | \
   fstminimizeencoded > $dir/CLG2.fst

对于典型的设置,此阶段实际上并不会减少太多解码图大小(通常会减少5%到20%),并且在任何情况下,通过这种机制减少的只是中间图形创建阶段的大小。但是,对于具有更宽的上下文的系统而言,节省的损失可能变得可观。

制作H转换器

在传统的FST算法中,H转换器是这样一种转换器,在其输出上具有上下文相关的音素,在其输入上具有表示声学状态的符号。在我们的例子中,H(或HCLG)输入上的符号不是声学状态(在我们的术语中为pdf-id),而是我们称为过渡ID(请参阅TransitionModel使用的整数标识符)。 transition-id编码pdf-id以及其他一些信息,包括音素。每个过渡ID都可以映射到pdf ID。我们创建的H传感器不对自环进行编码。这些是稍后通过单独的程序添加的。 H换能器具有初始状态和最终状态,从该状态开始,除了ilabel_info对象(ilabels文件,请参见上文)中的第零个条目之外,每个条目都有一个过渡。上下文相关音素的转换转到相应HMM的结构(缺少自环),然后返回到开始状态。对于普通拓扑,HMM的这些结构将只是三个弧的线性序列。对于每个歧义消除符号(#-1, #0, #1, #2, #3等),H还在初始状态下具有自环。

组成H换能器的脚本部分(我们称其为Ha,因为此时缺少自环)是:

make-h-transducer --disambig-syms-out=$dir/disambig_tstate.list \
   --transition-scale=1.0  $dir/ilabels.remapped \
   $tree $model  > $dir/Ha.fst

有一个可选参数来设置过渡比例;在我们当前的培训脚本中,此比例为1.0。该标度仅影响与自环概率无关的过渡部分,并且在正常拓扑结构(Bakis模型)中完全没有影响。有关更多说明,请参见过渡和声学概率的缩放。除FST外,该程序还编写了一个歧义消除符号列表,以后必须将其删除。

制作HCLG

制作最终图形HCLG的第一步是制作没有自环的HCLG。当前脚本中的命令如下:

  fsttablecompose $dir/Ha.fst $dir/CLG2.fst | \
   fstdeterminizestar --use-log=true | \
   fstrmsymbols $dir/disambig_tstate.list | \
   fstrmepslocal  | fstminimizeencoded > $dir/HCLGa.fst

此处,CLG2.fst是具有简化符号集(在HTK术语中为“logical”三音素)的CLG版本。在最小化之前,我们删除了歧义消除符号和任何易于删除的epsilon(请参阅删除epsilons)。我们的最小化算法是一种避免推入符号和权重(从而保持随机性),并接受非确定性输入的算法(请参见最小化)。

向HCLG添加自环

通过以下命令将自循环添加到HCLG:

  add-self-loops --self-loop-scale=0.1 \
    --reorder=true $model < $dir/HCLGa.fst > $dir/HCLG.fst

有关如何应用0.1的自环比例的说明,请参见过渡和声学概率的缩放比例(请注意,它还会影响非自环概率)。有关“重新排序”选项的说明,请参见对过渡进行重新排序; “重新排序”选项可提高解码速度,但与kaldi解码器不兼容。 add-self-loops程序不仅会添加自我循环,还可能会添加自我循环。它可能还必须复制状态并添加epsilon转换,以确保可以以一致的方式添加自环。在“重新排序过渡”中稍微详细地提到了此问题。这是图创建的唯一不保留随机性的阶段。它不会保留它,因为自环比例不是1。因此,fstisstochastic程序应为G.fst,LG.fst,CLG.fst和HCLGa.fst的所有对象提供相同的输出,但不为HCLG.fst的输出相同。 。在添加自循环阶段之后,我们不再确定。这将失败,因为我们已经删除了歧义消除符号。无论如何,这将是缓慢的,并且我们认为,此时确定和最小化并没有什么进一步的好处。

你可能感兴趣的:(21 解码图创建算法(测试时))