Kaldi学习之数据准备详细解释说明

来源:互联网 发布:神经网络数据融合 编辑:程序博客网 时间:2024/06/05 10:01

文章对Kaldi数据准备做更详细的解释,如有错误,还请指正。
数据基本源自Kaldi官网:http://www.kaldi-asr.org/doc/data_prep.html

数据准备详细介绍
在run.sh中有数据准备各个阶段的脚本。
例子中的local/文件夹下是数据准备专用的一些东西。
比如RM下的脚本run.sh部分内容:

local/rm_data_prep.sh /export/corpora5/LDC/LDC93S3A/rm_comp || exit 1;utils/prepare_lang.sh data/local/dict '!SIL' data/local/lang data/lang || exit 1;local/rm_prepare_grammar.sh || exit 1;

又比如WSJ下的脚本run.sh的部分内容:

wsj0=/export/corpora5/LDC/LDC93S6Bwsj1=/export/corpora5/LDC/LDC94S13Blocal/wsj_data_prep.sh $wsj0/??-{?,??}.? $wsj1/??-{?,??}.?  || exit 1;local/wsj_prepare_dict.sh || exit 1;utils/prepare_lang.sh data/local/dict "<SPOKEN_NOISE>" data/local/lang_tmp data/lang || exit 1;local/wsj_format_data.sh || exit 1;

WSJ涉及到准备本地语言模型脚本的脚本之后还有更多的命令,但是之前三条是更加重要的命令。
数据准备阶段的输出包括了两样东西集合。一个是“数据”(像/data/train这种目录),一个是“语言”(像/data/lang目录)。数据部分就是相关的语音数据,语言部分包含更多的是语言本身,比如字典,音素集合和一些其他的信息比如Kaldi需要的音素集。如果想准备数据,用当前系统现有的解码器和现存的语言模型,那么只需要接触“数据”部分。

数据准备之“数据”部分
看一个Switchboard的train部分例子(train和test本质上结构是一样的,只是用途不一样)。
例子目录egs/swbd/s5。里面拥有的文件如下:

s5# ls data/traincmvn.scp  feats.scp  reco2file_and_channel  segments  spk2utt  text  utt2spk  wav.scp

不是所有的文件都是同等重要。简单数据集是没有分割的信息的。自己必须创建的文件是
Utt2spk text wav.scp ,有时候需要创建 segments 和 reco2file_and_channel ,剩下的可由标准脚本创建。

需要自己创建的文件
text 文件样例:

s5# head -3 data/train/textsw02001-A_000098-001156 HI UM YEAH I'D LIKE TO TALK ABOUT HOW YOU DRESS FOR WORK ANDsw02001-A_001980-002131 UM-HUMsw02001-A_002736-002893 AND IS

格式:

语音-id 标注

语音-id可以是任意字符串,如果有说话人信息,则说话人-id是语音-id的前缀。无需保证文件内的所有词都在词汇表中,词的输出会映射到专门的一个词文件中,data/lang/oov.txt。

要注意utt2spk和spk2utt这两个文件的排序顺序的一致性。比如,从utt2spk(语音-id所对应的说话人-id)文件提取的说话人-id列表和字符串的排序顺序一样。最简单的做法就是说话人-id作为语音-id的前缀,一般用”-“分隔。如果说话人-id长度不一致,在某些情况下,用标准的C字符串排序时,说话人-id和其对应的语音-id可能以不同的顺序进行排序/分类,这可能导致崩溃的情况。

另外一个重要文件就是 wav.scp
swbd例子:

s5# head -3 data/train/wav.scpsw02001-A /home/dpovey/kaldi-trunk/tools/sph2pipe_v2.5/sph2pipe -f wav -p -c 1 /export/corpora3/LDC/LDC97S62/swb1/sw02001.sph |sw02001-B /home/dpovey/kaldi-trunk/tools/sph2pipe_v2.5/sph2pipe -f wav -p -c 2 /export/corpora3/LDC/LDC97S62/swb1/sw02001.sph |

文件格式是:

<recording-id> <extended-filename>

extended-filename 可能是确切的文件名,或者是提取wav格式文件的命令。extended-filename 最后的管道符号意味着它将被解读成管道。如果分割文件不存在,那么wav.scp每行的第一个字符就是语音-id。wav.scp必须是单声道的,如果底层语音文件有多个声道,就必须有一个短命令在wav.scp中要用到来提取一个特殊的通道。

比如在swbd中有“segments”文件,如下:

s5# head -3 data/train/segmentssw02001-A_000098-001156 sw02001-A 0.98 11.56sw02001-A_001980-002131 sw02001-A 19.8 21.31sw02001-A_002736-002893 sw02001-A 27.36 28.93

格式是:

<utterance-id> <recording-id> <segment-begin> <segment-end>

即 语音-id 记录-id 分割开始时间 分割结束时间
分割的开始和结束单位是 秒。
记录-id是wav.scp中使用的相同的标识,也是可以自己选择的任意的标识。文件”reco2file_and_channel”只在打分(测量错误率)的时候用到,用NIST的”sclite”工具。

s5# head -3 data/train/reco2file_and_channelsw02001-A sw02001 Asw02001-B sw02001 Bsw02005-A sw02005 A

格式为

<recording-id> <filename> <recording-side (A or B)>

filename是.sph文件的名字,没有后缀,但是一般来说它是stm文件中的任何标识符。recording-side是一个电话对话中两个声道的概念,如果不是的话,用A更安全。如果没有stm文件或者不知道干啥的,record2file_and_channel文件就没必要创建。

最后一个需要自己创建的文件是”utt2spk”文件。

s5# head -3 data/train/utt2spksw02001-A_000098-001156 2001-Asw02001-A_001980-002131 2001-Asw02001-A_002736-002893 2001-A

格式:

<utterance-id> <speaker-id>

说话人-id不需要精确地对应每一个说话人,有个大概的猜测就好。可能会出现这样的情况:打电话的一方说了几句话之后可能会拿电话给另外一个人。如果没有说话人标识的相关信息,可以使说话人-id和语音-id相同。因为有人创建了”global”说话人-id(所有语音只对应一个说话人)。这样不好的原因:它使得cmn在训练的时候无效(因为被全局应用,一般CMN都是在句子内或者说一条语音内减去特征的均值),而且在使用utils/split_data_dir.sh分割数据的时候会引起问题。

在一些数据集中还存在其它的文件,偶尔出现。比如性别相关的:

s5# head -3 ../../rm/s5/data/train/spk2genderadg0 fahh0 majp0 m

所有的文件都要排序。未排序会有错误。排序的最终原因就是使一些不支持fseek()函数的数据流也能够达到随机访问的效果。许多Kaldi程序从其它Kaldi命令中读取很多管道,读不同类型的对象,并且和合并排序的不同的输入做一些大致的比较。

export LC_ALL=C

如果做了这个命令,就和C++排序不一样,Kaldi会崩溃。

如果数据有NIST来的测试集组成,有”stm”和”glm”文件,使用local/score.sh脚本文件可以测试WER。swbd集有用到这样的打分文件,比如
egs/swbd/s5/local/score_sclite.sh就被egs/swbd/s5/local/score.sh调用。

无需自己创建的文件
除了上述的其它文件都可由kaldi提供的命令生成。

比如utt2spk转换成spk2utt

utils/utt2spk_to_spk2utt.pl data/train/utt2spk > data/train/spk2utt

因为utt2spk和spk2utt具有相同的信息
utt2spk 格式 :

<utterance-id><speaker-id>

spk2utt 格式 :

<spaker-id><utterance-id>

feats.scp文件格式

s5# head -3 data/train/feats.scpsw02001-A_000098-001156 /home/dpovey/kaldi-trunk/egs/swbd/s5/mfcc/raw_mfcc_train.1.ark:24sw02001-A_001980-002131 /home/dpovey/kaldi-trunk/egs/swbd/s5/mfcc/raw_mfcc_train.1.ark:54975sw02001-A_002736-002893 /home/dpovey/kaldi-trunk/egs/swbd/s5/mfcc/raw_mfcc_train.1.ark:62762

这个文件指向提取的特征——swbd例子中是MFCC特征,因为用了make_mfcc.sh脚本。

feats.scp格式如下:

<utterance-id> <extended-filename-of-features>

在Kaldi中每个特征文件都包含一个矩阵。swbd例中矩阵的维度将会是13维(文件以10s作为间隔的长度)。
扩展文件名/home/dpovey/kaldi-trunk/egs/swbd/s5/mfcc/raw_mfcc_train.1.ark:24 意思是,打开ark文件,通过fseek()文件找到第24个位置,从这开始读数据。

feat.scp通过以下命令创建:

steps/make_mfcc.sh --nj 20 --cmd "$train_cmd" data/train exp/make_mfcc/train $mfccdir

这个脚本被顶层的run.sh脚本调用。Shell变量的定义可查看脚本说明。$mfccdir是用户指定的目录,.ark文件会写入到这个目录中。

dnn/train目录的最后一个文件是”cmvn.scp”文件。这个文件包含倒谱均值和方差规整的数据,通过说话人-id进行索引。每组统计量都是一个矩阵,swbd例中是维数2*14?
swbd例中,看cmvn.scp中部分内容:

s5# head -3 data/train/cmvn.scp2001-A /home/dpovey/kaldi-trunk/egs/swbd/s5/mfcc/cmvn_train.ark:72001-B /home/dpovey/kaldi-trunk/egs/swbd/s5/mfcc/cmvn_train.ark:2532005-A /home/dpovey/kaldi-trunk/egs/swbd/s5/mfcc/cmvn_train.ark:499

和feats.scp不同的是,这个文件是通过说话人-id进行索引的,本文件通过以下命令创建:

steps/compute_cmvn_stats.sh data/train exp/make_mfcc/train $mfccdir

本例来源:egs/swbd/s5/run.sh

为了防止数据准备(不符合条件)引发后续的问题,有工具检查脚本是否符合格式规范:

utils/validate_data_dir.sh data/train

尤其是以下命令,可以修正排序错误,还会移除一些所需数据(比如特征数据或者脚本)丢失时候的语音。

utils/fix_data_dir.sh data/train

语言模型准备部分
首先需要准备dict字典,包括
lexicon.txt:包括语料中涉及的词汇与发音,与单字及其发音
lexicon_nosil.txt:同上,去掉静音部分
phones.txt:音素发音标识

然后需要使用srilm的ngram-count命令生成词汇的arpa格式文件

ngram-count -order 1 -text words.txt -lm word.arpa

ngram-count部分参数解释如下:
-order 指定n-gram的n是多少(n代表n元语言模型),默认是3
-text 提供输入的语料文件,统计该语料中的n-gram
-lm 指定输出的lm文件
-vocab 用来指定对哪些词进行n-gram统计
-wbdiscount1 表示1gram Witten-Bell discounting
注意:参数顺序无所谓

arpa文件的使用:
使用arpa2fst命令进行转换,转换成符合FST规范的文件。;
如若不然,prepare_lang.sh会使用一些自带的fst开头的程序生成.fst文件。

语言模型准备部分详述
转到”lang”目录,看目录下面的文件

s5# ls data/langL.fst  L_disambig.fst  oov.int  oov.txt  phones  phones.txt  topo  words.txt

还有很多相似的格式,比如在data/lang_test就包含了相同的内容,还多了一个G.fst(Grammer)语言模型的有限状态转换器格式。

s5# ls data/lang_testG.fst  L.fst  L_disambig.fst  oov.int  oov.txt  phones  phones.txt  topo  words.txt

lang_test是通过复制lang/下面的内容,并且增加G.fst生成的。这些目录包含几个文件之外还包含几个文件夹。比如phones就是一个文件夹:

s5# ls data/lang/phonescontext_indep.csl  disambig.txt         nonsilence.txt        roots.txt    silence.txtcontext_indep.int  extra_questions.int  optional_silence.csl  sets.int     word_boundary.intcontext_indep.txt  extra_questions.txt  optional_silence.int  sets.txt     word_boundary.txtdisambig.csl       nonsilence.csl       optional_silence.txt  silence.csl

音素目录包含很多音素集合的不同信息;有三个不同文件版本,扩展名包括.csl,.int和.txt,三种格式包含了相同信息,不需要全部创建它。因为通过一个简单的输入,脚本utils/prepare_lang.sh就会输出这些文件。接下来解释lang目录。不想了解Kaldi到底怎么工作的原理可以直接跳到Creating the “lang” directory部分


lang目录的内容
首先是phones.txtwords.txt的内容,这两个都是字符表文件,用OpenFst格式,每行都是文本形式 int形式组合。

s5# head -3 data/lang/phones.txt<eps> 0SIL 1SIL_B 2s5# head -3 data/lang/words.txt<eps> 0!SIL 1-'S 2

这些文件被Kaldi用来在这些符号的整数和文本形式之间来回映射。 它们大多只能通过脚本utils / int2sym.pl和utils / sym2int.pl以及OpenFst程序fstcompile和fstprint访问。

文件L.fst是字典的有限状态转换器形式(L,参见Mohri,Pereira和Riley的论文:
“Speech Recognition with Weighted Finite-State Transducers” ,
Springer Handbook on SpeechProcessing and Speech Communication,2008)。 输入是音素符号和输出是字词符号。 文件L_disambig.fst是词典,但是和上面说的词典比多了消歧符号#1,#2等,以及带有#0的自循环,以从语法中“消除”歧义符号。 参阅Disambiguation symbols获得消歧符号更多的解释。这个文件不需要自己处理。

文件data/lang/oov.txt只包含一行内容

s5# cat data/lang/oov.txt<UNK>

在训练的时候会映射所有词汇中的词到这个词中。<UNK>在这里没有什么特别,它不是这个文件的特定词;重要的是这个词要有一个发音,这个发音只包含一个我们指定为“垃圾音素”的音素;这个音素将与各种说话的噪音对齐。 在Kaldi设置中,这个音素被称为“SPN”(“spoken noise”)),比如在lexicon.txt文件中:

s5# grep -w UNK data/local/dict/lexicon.txt<UNK> SPN

文件oov.int包括上述内容的int形式(从words.txt中提取),在此设置中恰好为221。 可能会注意到,在资源管理设置中,oov.txt包含静音词,该设置恰好称为“!SIL”。 在这种情况下(SIL),Kaldi只是从词汇表中选择一个任意的单词——训练集中没有这样的词,所以Kaldi选择什么词都没有影响。

文件data/lang/topo中包含以下数据:

s5# cat data/lang/topo<Topology><TopologyEntry><ForPhones>21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188</ForPhones><State> 0 <PdfClass> 0 <Transition> 0 0.75 <Transition> 1 0.25 </State><State> 1 <PdfClass> 1 <Transition> 1 0.75 <Transition> 2 0.25 </State><State> 2 <PdfClass> 2 <Transition> 2 0.75 <Transition> 3 0.25 </State><State> 3 </State></TopologyEntry><TopologyEntry><ForPhones>1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20</ForPhones><State> 0 <PdfClass> 0 <Transition> 0 0.25 <Transition> 1 0.25 <Transition> 2 0.25 <Transition> 3 0.25 </State><State> 1 <PdfClass> 1 <Transition> 1 0.25 <Transition> 2 0.25 <Transition> 3 0.25 <Transition> 4 0.25 </State><State> 2 <PdfClass> 2 <Transition> 1 0.25 <Transition> 2 0.25 <Transition> 3 0.25 <Transition> 4 0.25 </State><State> 3 <PdfClass> 3 <Transition> 1 0.25 <Transition> 2 0.25 <Transition> 3 0.25 <Transition> 4 0.25 </State><State> 4 <PdfClass> 4 <Transition> 4 0.75 <Transition> 5 0.25 </State><State> 5 </State></TopologyEntry></Topology>

这是Kaldi使用的HMM拓扑。在这种情况下,“真实”音素包含三种标准的从左到右3状态的拓扑结构的发射状态——叫“Bakis模型” (发射状态是“发射”特征向量的状态,与仅用于将其他状态连接在一起的“假的”非发射状态不同)。 音素1到20是各种静音和噪音; 有很多静音和噪音是因为字位依赖,事实上大多数的静音和噪声永远都用不上; 不包括字位依赖的状态数是5个左右。 “静音音素”具有更复杂的拓扑结构,具有初始发射状态和结束发射状态,但在中间是三个发射状态。 不需要手动创建此文件。

data/lang/phones文件夹中有几个存放关于音素集的不同信息的文件。这些文件大多数都有三个不同的版本,分别是.txt .int 和 .csl版本,内容分别如下
.txt

s5# head -3 data/lang/phones/context_indep.txtSILSIL_BSIL_E

.int

s5# head -3 data/lang/phones/context_indep.int123

.csl,是一个冒号分隔的列表

s5# cat data/lang/phones/context_indep.csl1:2:3:4:5:6:7:8:9:10:11:12:13:14:15:16:17:18:19:20

这些文件总是包含相同的信息,所以集中看可读性最强的.txt格式内容。文件”context_indep.txt”包含我们要建立上下文模型的音素列表:对于这些音素,不需要建立一个决策树来提出音素左右上下文的问题。实际上,关于中心音素和HMM状态的问题Kaldi建立了一个小得多的树来实现它;这取决于下面要说的root.txt文件。关于树的问题,想要更深入了解的可以查看 How decision trees are used in Kaldi 。

context_indep.txt包含了非“真实音素”的所有音素,比如静音(SIL),说话人噪声(SPN),非说话人噪声(NSN),和笑声(LAU):

# cat data/lang/phones/context_indep.txtSILSIL_BSIL_ESIL_ISIL_SSPNSPN_BSPN_ESPN_ISPN_SNSNNSN_BNSN_ENSN_INSN_SLAULAU_BLAU_ELAU_ILAU_S

由于字位依赖(字所在的位置有多种组合),这些音素有很多变体;不是所有的这些变种都不会被使用。在这里,SIL被选择性地插入字典(不是单词的一部分),SIL_B是一个单词开头的静音音素(不应该存在),SIL_I字内的静音(不太可能存在) ),SIL_E是结尾的静音(永远不会存在),SIL_S将作为一个“单个”的静音,即只有一个字的音素—— 如果词典中有“静音词”,则可能会在转录文件中出现明显的静音。

文件silence.txtnonsilence.txt分别包含静音/噪声音素和非静音/噪声音素的列表。这些应该互斥,应该包含所有的音素。在这个特定的设置中,silence.txt与context_indep.txt相同。 “nonsilence”音素的意思是,Kaldi打算预测这些音素上的各种线性变换:即LDA和MLLT等全局变换,以及诸如fMLLR之类的说话人自适应变换。Kaldi基于以往经验的信念是,在预测这种变换中不包括任何silence音素。Kaldi的做法是将所有静音,噪音和发声的噪声音素指定为“silence”音素,所有代表传统音素的音素都是“nonsilence”音素。

s5# head -3 data/lang/phones/silence.txtSILSIL_BSIL_Es5# head -3 data/lang/phones/nonsilence.txtIY_BIY_EIY_I

文件disambig.txt包括“消歧符号”(看Disambiguation symbols)

s5# head -3 data/lang/phones/disambig.txt#0#1#2

如果这些符号是音素,它们会在phones.txt出现

文件optional_silence.txt 包括能够有选择性地在词之间出现的单音素

s5# cat data/lang/phones/optional_silence.txtSIL

在词之间可以选择性出现的机制是,在字典的FST中,它可以任意地在每个单词结尾(语音的开始)中出现。必须在phones/指定,而不是仅出现在L.fst中的原因Kaldi也未说明,不在这里细说。

文件sets.txt包括组合的音素集合(考虑是相同的音素),同时对音素进行聚类来创造上下文相关性问题(Kaldi中在构造决策树时使用自动生成的问题,而不是语言的有意义的问题)。这种设定下,sets.txt对每个音素的所有字位依赖版本做组合:

s5# head -3 data/lang/phones/sets.txtSIL SIL_B SIL_E SIL_I SIL_SSPN SPN_B SPN_E SPN_I SPN_SNSN NSN_B NSN_E NSN_I NSN_S

文件extra_questions.txt包括一些Kaldi需要包括的其它(除了自动生成问题之外)的问题

s5# cat data/lang/phones/extra_questions.txtIY_B B_B D_B F_B G_B K_B SH_B L_B M_B N_B OW_B AA_B TH_B P_B OY_B R_B UH_B AE_B S_B T_B AH_B V_B W_B Y_B Z_B CH_B AO_B DH_B UW_B ZH_B EH_B AW_B AX_B EL_B AY_B EN_B HH_B ER_B IH_B JH_B EY_B NG_BIY_E B_E D_E F_E G_E K_E SH_E L_E M_E N_E OW_E AA_E TH_E P_E OY_E R_E UH_E AE_E S_E T_E AH_E V_E W_E Y_E Z_E CH_E AO_E DH_E UW_E ZH_E EH_E AW_E AX_E EL_E AY_E EN_E HH_E ER_E IH_E JH_E EY_E NG_EIY_I B_I D_I F_I G_I K_I SH_I L_I M_I N_I OW_I AA_I TH_I P_I OY_I R_I UH_I AE_I S_I T_I AH_I V_I W_I Y_I Z_I CH_I AO_I DH_I UW_I ZH_I EH_I AW_I AX_I EL_I AY_I EN_I HH_I ER_I IH_I JH_I EY_I NG_IIY_S B_S D_S F_S G_S K_S SH_S L_S M_S N_S OW_S AA_S TH_S P_S OY_S R_S UH_S AE_S S_S T_S AH_S V_S W_S Y_S Z_S CH_S AO_S DH_S UW_S ZH_S EH_S AW_S AX_S EL_S AY_S EN_S HH_S ER_S IH_S JH_S EY_S NG_SSIL SPN NSN LAUSIL_B SPN_B NSN_B LAU_BSIL_E SPN_E NSN_E LAU_ESIL_I SPN_I NSN_I LAU_ISIL_S SPN_S NSN_S LAU_S

可以看到这个问题就是一个简单的音素集合。前四个问题是询问常规音素的字位; 最后五个对于“静音”操作是一样的。 “静音”音素也会出现一个不带_B字样的后缀,例如SIL。 这些可能在词典中表现为可选的静音,比如,不在实际的单词内。 在使用诸如音调依赖或重音标记的设置中,extra_questions.txt可能包含与这些功能相关的问题。

文件word_boundary.txt解释了音素如何和字位进行关联:

s5# head  data/lang/phones/word_boundary.txtSIL nonwordSIL_B beginSIL_E endSIL_I internalSIL_S singletonSPN nonwordSPN_B begin

这是与音素后缀相同的信息(_B等等),但是我们不喜欢在音素的文本形式中对这个进行硬编码,首先,Kaldi的可执行文件永远都不会看到音素的文本形式,只能看到整数形式。 所由文件word_boundary.txt指定。 需要这个信息的主要原因是为了恢复词图内的单词边界(例如,程序lattice-align-words读取此文件的整数形式,word_boundaray.int)。 寻找单词边界非常有用,原因包括NIST sclite评分,这需要时间标记的单词和其他下游处理。

文件roots.txt包括关于如何建立音素上下文决策树的信息:

head data/lang/phones/roots.txtshared split SIL SIL_B SIL_E SIL_I SIL_Sshared split SPN SPN_B SPN_E SPN_I SPN_Sshared split NSN NSN_B NSN_E NSN_I NSN_Sshared split LAU LAU_B LAU_E LAU_I LAU_S...shared split B_B B_E B_I B_S

现在可以忽略关键字”shared” and “split”——这些跟如何建立决策树的选项相关(更多信息看How decision trees are used in Kaldi )。每行有几个音素的意义,比如SIL SIL_B SIL_E SIL_I SIL_S,是因为这些音素在决策树中都有相同的“共享根节点”,所以这些音素的状态可能也是共享的。对于重音和音调相关系统,通常重音或音调相关的所有版本都会出现在同一行。另外,HMM的所有3个状态(对于静音来说是5个)都共享根,决策树的构造过程中可以询问状态。HMM状态之间决策树的根共享就是在roots文件中”shared”的意思。

创建 lang 文件夹
data/lang/ 目录包含一些不同的文件,所以提供了脚本来创建这些文件,可以用来从一些简单的输入入手:

utils/prepare_lang.sh data/local/dict "<UNK>" data/local/lang data/lang

输入是目录 data/local/dict/,标签 <UNK> 是字典里的单词,映射OOV单词到出现在副本中的单词中(data/lang/oov.txt)。目录data/local/lang/是脚本会用到的临时目录;data/lang/是输出目录。

作为数据准备者需要创建的是目录data/local/dict/。此目录包含以下内容:

s5# ls data/local/dictextra_questions.txt  lexicon.txt nonsilence_phones.txt  optional_silence.txt  silence_phones.txt

(实际上还有几个没有列出来的文件,但是它们只是创建目录放进去的临时文件,可以忽略它们)。下面的命令对这些文件内容有些了解:

s5# head -3 data/local/dict/nonsilence_phones.txtIYBD
s5# cat data/local/dict/silence_phones.txtSILSPNNSNLAU
s5# cat data/local/dict/extra_questions.txt
s5# head -5 data/local/dict/lexicon.txt!SIL SIL-'S S-'S Z-'T K UH D EN T-1K W AH N K EY

可以看到,目录的内容在这个例子(Switchboard)的设置中很简单。只有分开的“真实”音素和“静音”音素的列表,还有个空文件extra_questions.txt,另外还有个叫lexicon.txt 的字典文件,文件有以下形式:

<word> <phone1> <phone2> ...

注意:lexicon.txt中同一个词如果有不同发音的话,可能会包含多个重复的条目,分布在不同的行。如果想使用发音概率,创建lexiconp.txt(第二个字段是概率)代替原来的lexicon.txt。注意一般都要对发音概率做规范化,而不是对一个词求和,每个单词最有可能的发音都是一个。这样会得到更好的结果。对于以发音概率跑实验的顶层脚本,在egs/wsj/s5/run.sh中找关键字”pp”。

请注意,在该输入中,没有字位置依赖性的概念,即没有后缀如_B和_E。 这是因为添加这些后缀是脚本prepare_lang.sh才做的。

可以从空文件extra_questions.txt中看到,这里还有一些没有被充分挖掘的东西。 这涉及重音标记或音调标记的一些东西。 用户可能会需要具有不同重音或音调的特定音素的不同版本。 为了演示这样的内容,查看与上述相同的文件,但在egs / wsj / s5 / setup中。 结果如下:

s5# cat data/local/dict/silence_phones.txtSILSPNNSN
s5# head data/local/dict/nonsilence_phones.txtSUW UW0 UW1 UW2TNKYZAO AO0 AO1 AO2AY AY0 AY1 AY2SH
s5# head -6 data/local/dict/lexicon.txt!SIL SIL<SPOKEN_NOISE> SPN<UNK> SPN<NOISE> NSN!EXCLAMATION-POINT  EH2 K S K L AH0 M EY1 SH AH0 N P OY2 N T"CLOSE-QUOTE  K L OW1 Z K W OW1 T
s5# cat data/local/dict/extra_questions.txtSIL SPN NSNS UW T N K Y Z AO AY SH W NG EY B CH OY JH D ZH G UH F V ER AA IH M DH L AH P OW AW HH AE R TH IY EHUW1 AO1 AY1 EY1 OY1 UH1 ER1 AA1 IH1 AH1 OW1 AW1 AE1 IY1 EH1UW0 AO0 AY0 EY0 OY0 UH0 ER0 AA0 IH0 AH0 OW0 AW0 AE0 IY0 EH0UW2 AO2 AY2 EY2 OY2 UH2 ER2 AA2 IH2 AH2 OW2 AW2 AE2 IY2 EH2

可以看到nonsilence_phones.txt里面在单行中包括了多个音素的行有很多。这些是元音的不同重音相关版本。注意,CMU字典中每个音素都出现了四个不同版本:例如,UW UW0 UW1 UW2;由于某些原因,其中一个版本没有数字后缀。音素在一行的顺序并不重要,但分组到不同行的时候就很重要了;一般来说,Kaldi建议用户“真实音素”分组的不同形式在不同行。Kaldi使用CMU字典中存在的重音标记。文件extra_questions.txt包括一个包含所有“silence”音素的问题(实际上这是不必要的,因为脚本prepare_lang.sh会添加这样的问题),还有一个与每个不同的重音标记相对应的问题。为了从重音标记得到更好的结果,这些问题是必要的,因为实际上每个音素的不同重音相关版本都统一放在nonsilence_phones.txt文件行中,确保他们都在文件data/lang/phones/roots.txt 和data/lang/phones/sets.txt中,这反过来又确保它们共享相同的树根结点,并且永远不能被一个问题所区分。因此,我们必须提供一个特殊的问题,使决策树建立过程能够区分音素。注意:我们将音素放在sets.txt和roots.txt中的原因是,一些重音相关版本的音素可能没有足够的数据可靠地估计单独的决策树或生成问题时用到的音素聚类信息。通过将它们分组在一起,我们确保在没有足够的数据的情况下分别预测它们,这些不同版本的音素在整个决策树构建过程中都“保持在一起”。

还有一个要提出的点事是脚本utils/prepare_lang.sh支持几个选项。以下是脚本的使用信息,可以看看这些选项到底是什么:

usage: utils/prepare_lang.sh <dict-src-dir> <oov-dict-entry> <tmp-dir> <lang-dir>e.g.: utils/prepare_lang.sh data/local/dict <SPOKEN_NOISE> data/local/lang data/langoptions:     --num-sil-states <number of states>             # default: 5, #states in silence models.     --num-nonsil-states <number of states>          # default: 3, #states in non-silence models.     --position-dependent-phones (true|false)        # default: true; if true, use _B, _E, _S & _I                                                     # markers on phones to indicate word-internal positions.     --share-silence-phones (true|false)             # default: false; if true, share pdfs of                                                     # all non-silence phones.     --sil-prob <probability of silence>             # default: 0.5 [must have 0 < silprob < 1]

一个很重要的选项是–share-silence-phones选项。默认是false。如果选项是true,所有“silence”音素(比如静音,发声噪声和笑声)的高斯混合模型的概率密度函数都是共享的,只有这些模型之间的转移概率不同。不清楚为什么有帮助,但是发现这对IARPA的BABEL项目的粤语数据确实是非常有帮助的。这些数据非常混乱,并且有很长一部分没有转录,Kaldi试图为此目的与指定的特殊音素对齐。Dan怀疑训练数据不知道什么原因无法正确对齐,但是将此选项设置为true可以改变这种情况。

另外一个很重要的选项是”–sil-prob“选项。总的来说,Dan没有对这些选项进行实验,所以不能给出非常详细的建议。

创建语法的语言模型
上面说的如何创建lang/目录没有说如何创建G.fst文件,这个文件是需要解码的语言模型或者语法的有限状态转移机形式。实际上,在一些设置中可能会针对不同的测试目的有多个不同的”lang”目录,那里面有不同的语言模型和字典文件。华尔街日报(WSJ)是一个例子:

s5# echo data/lang*data/lang data/lang_test_bd_fg data/lang_test_bd_tg data/lang_test_bd_tgpr data/lang_test_bg \ data/lang_test_bg_5k data/lang_test_tg data/lang_test_tg_5k data/lang_test_tgpr data/lang_test_tgpr_5k

创建G.fst的过程根据是否用统计量语言模型或用什么种类的语法是不同的。在RM设置中有一个bigram语法,它只允许某些单词对。 在每个语法状态下,通过分配概率1在这些输出弧上,把这些单词对总计到一个?。

local/ rm_data_prep.sh中有一条语句:

local/make_rm_lm.pl $RMROOT/rm1_audio1/rm1/doc/wp_gram.txt  > $tmpdir/G.txt || exit 1;

脚本local/make_rm_lm.pl创建了一个FST格式(文本格式)的语法。它包含了以下形式的行

s5# head data/local/tmp/G.txt0    1    ADD    ADD    5.198497031265830    2    AJAX+S    AJAX+S    5.198497031265830    3    APALACHICOLA+S    APALACHICOLA+S    5.19849703126583

OpenFst的更多信息查看www.openfst.org。

脚本local/rm_prepare_grammar.sh会把这个文件的文本形式转换成二进制形式G.fst。命令如下:

fstcompile --isymbols=data/lang/words.txt --osymbols=data/lang/words.txt --keep_isymbols=false \    --keep_osymbols=false $tmpdir/G.txt > data/lang/G.fst

如果想创建自己的语法,或者会想做一些类似的事情。要注意:这个程序只适用一个固定类的语法:不允许编译完全上下文自由的语法,因为这不能以OpenFst形式表示。可以通过WFST框架做到这件事(可以看看Mike Riley 下推转移机的近期工作),但是Kaldi中还没有实现这些想法。

在关于语言模型或制作语法FST的列表上有任何问题,请阅读Joshua Goodman的“A Bit of Progress in Language Modeling” 并访问www.openfst.org并执行FST教程,以便了解有限状态转移机的基础知识。 (请注意,语言模型将被表示为有限状态接收器或FSA,可被视为有限状态转换机的特殊情况)。

脚本utils/format_lm.sh解决把ARPA格式的语言模型转换成OpenFST格式类型。脚本用法如下:

Usage: utils/format_lm.sh <lang_dir> <arpa-LM> <lexicon> <out_dir>E.g.: utils/format_lm.sh data/lang data/local/lm/foo.kn.gz data/local/dict/lexicon.txt data/lang_testConvert ARPA-format language models to FSTs.

这个脚本的一些关键命令如下:

gunzip -c $lm \  | arpa2fst --disambig-symbol=#0 \             --read-symbol-table=$out_dir/words.txt - $out_dir/G.fst

Kaldi程序arpa2fst将ARPA格式的语言模型转换成一个加权有限状态转移机(实际上是接收机)。

构建语言模型的常用工具时SRILM。不同的语言模型工具都在Kaldi的样例脚本中用到了。SRILM有最好的文档和最全面的功能,通常推荐它(唯一的缺点是它没有最免费的许可证)。 以下是utils / format_lm_sri.sh的使用信息:

Usage: utils/format_lm_sri.sh [options] <lang-dir> <arpa-LM> <out-dir>E.g.: utils/format_lm_sri.sh data/lang data/local/lm/foo.kn.gz data/lang_testConverts ARPA-format language models to FSTs. Change the LM vocabulary using SRILM.