源文件如下:
#!/bin/bash
train_cmd="utils/run.pl"
decode_cmd="utils/run.pl"
#========== 下载原始数据文件并解压到waves_yesno目录========
if [! -d waves_yesno ]; then
wget http://www.openslr.org/resources/1/waves_yesno.tar.gz || exit 1;
tar -xvzf waves_yesno.tar.gz || exit 1;
fitrain_yesno=train_yesno
test_base_name=test_yesno#========== 删除上一次运行生成的文件==================
rm -rf data exp mfcc#======== 第一步:数据准备 =========================
local/prepare_data.sh waves_yesno
local/prepare_dict.sh
utils/prepare_lang.sh --position-dependent-phones false data/local/dict "<SIL>" data/local/lang data/lang
local/prepare_lm.sh#========第二步:语音信号特征抽取 ====================
for x in train_yesno test_yesno; do
steps/make_mfcc.sh --nj 1 data/$x exp/make_mfcc/$x mfcc
steps/compute_cmvn_stats.sh data/$x exp/make_mfcc/$x mfcc
utils/fix_data_dir.sh data/$x
done#========第三步:单音模型训练 =======================
steps/train_mono.sh --nj 1 --cmd "$train_cmd" --totgauss 400 data/train_yesno data/lang exp/mono0a#========第四步:生成解码矩阵 =======================
utils/mkgraph.sh data/lang_test_tg exp/mono0a exp/mono0a/graph_tgpr#========第五步:解码 =============================
steps/decode.sh --nj 1 --cmd "$decode_cmd" exp/mono0a/graph_tgpr data/test_yesno exp/mono0a/decode_test_yesno
for x in exp/*/decode*; do
[ -d $x ] && grep WER $x/wer_* | utils/best_wer.sh;
done
########################################################################
下面再对每一步进行详细探究:
#======== 第一步:数据准备 =========================
- local/prepare_data.sh waves_yesno
- local/prepare_dict.sh
- utils/prepare_lang.sh --position-dependent-phones false data/local/dict "<SIL>" data/local/lang data/lang
- local/prepare_lm.sh
prepare_data.sh脚本带一个参数,参数为存放语音数据文件的目录名。这个脚本做了以下几件事情:
- 创建data和data/local目录
- 在local目录下生成音频文件列表文件waves_all.list,存放语音数据文件的文件名列表
- 再把waves_all.list一分为两个文件waves.train和waves.test,前者用来训练,后者用来测试。
- 用waves.train生成对应的scp文件,文件名为train_yesno_wav.scp
- 用waves.test生成对应的scp文件,文件名为test_yesno_wav.scp
- 用waves.train生成对应的txt文件,文件名为train_yesno_wav.txt
- 用waves.test生成对应的txt文件,文件名为test_yesno_wav.txt
- 将input目录下的task.arpabo拷贝到data/local目录下,改名为lm_tg.arpa
- 在data目录下生成test_yesno目录和train_yesno目录,将train_yesno_wav.scp和train_yesno_wav.txt拷贝到train_yesno目录下,修改文件名为wav.scp和text;将test_yesno_wav.scp和test_yesno_wav.txt拷贝到test_yesno目录下,修改文件名为wav.scp和text;同时在这两个目录下均生成spk2utt和 utt2spk两个文件。
- data目录下的数据准备完毕。data下有三个目录,local、test_yesno和train_yesno。local下存放语言模型文件lm_tg.arpa;test_yesno和train_yesno下均有四个文件,text、wav.scp、utt2spk和spk2utt,而且都是根据相应的训练数据和测试数据生成。
- text文件内容格式为<utteranceID wav_file_path>,即<语音标号 对应的语音文件路径>
- wav.scp内容格式为<utteranceID scripts>, 即<语音标号 语音对应的文本脚本>
- utt2spk内容格式为<utteranceID speakerID>, 即<语音标号 讲话人ID>
- spk2utt内容格式为<speakerID utteranceID ......>, 即<讲话人ID 属于这个讲话人的语音标号串,以空格分隔>
prepare_dict.sh脚本不带参数。分别做了以下几件事情:
- 在data/local目录下创建dict子目录。
- 在dict子目录下生成四个文件lexicon_words.txt、lexicon.txt、nonsilence_phones.txt、silence_phones.txt和optional_silence.txt。
- lexicon_words.txt内容格式为<单词 音素组合,音素之间用空格分隔>
- lexicon.txt内容格式为<单词 音素组合,音素之间用空格分隔>,与lexicon_words.txt的区别是lexicon.txt包含了静音SIL。
- nonsilence_phones.txt内容格式为<非静音音素>
- silence_phones.txt内容格式为<静音音素>
- optional_silence.txt内容格式为<其他特殊音素>
prepare_lang.sh脚本的功能主要是上面的字典dict数据来生成L.fst和HMM拓扑网络,也就是音素的状态机。
- utils/prepare_lang.sh --position-dependent-phones false data/local/dict "<SIL>" data/local/lang data/lang
- 命令行参数 --position-dependent-phones false, 这里设置为false (默认状态是true)。--option-dependent-phones与词位信息有关,如果设置为true,则用_B _E _I _S 表示开始、结束、词中、单个词。因为yesno这个例子都是孤立词,所以设置为false。
- 四个数据参数 data/local/dict "<SIL>" data/local/lang data/lang,分别代表 <input_dir> <oov> <temperary_dir> <output_dir>。
- <input_dir>表示输入数据所在的目录,这里就是上一个脚本生成的字典dict目录;
- <oov>表示数据集之外的单词,会映射到这里,这个例子没有数据集之外的单词,只把"<SIL>"放进去;
- <temperary_dir>是临时目录,存放中间生成的临时文件;
- <output_dir>是数据输出的存放目录,这里的文件才是最要关心的。
- 在<output_dir>下会生成以下文件:
- L.fst文件:这是一个 fst 格式的发音词典,输入是音素,而输出是词。
- L_disambig.fst文件:这也是一个 fst 格式的发音词典,只是在这个文件当中,添加了消除歧义的符号,比如#1,#2,以及自循环符号 #0。
- oov.int文件:这个文件里面存储了一些符号,所有ooV词(词表之外的词,out of vocabulary)都会被映射为这个符号。
- phones.txt文件:音素和整数标号的映射
- topo文件: 这个文件里面存储了我们后将要用到的 HMM 模型的拓扑模型,可以看到有两部分,第一部分 <ForPhones></ForPhones> 之间的数字是 2,3 ,是发音的编号指的是 YES, NO ,这个可以从 phones.txt 找到,自然 下面一部分 的数字 1 指的是SIL。 YES,NO 公有3种状态,SIL有五种状态,它们各自状态之间的转移以及转移概率则是每一行<Transition> state probability 指定。
- words.txt文件:单词和整数标号的映射
- phones目录:phones 目录下的文件很明显有一个特点,每一个文件都有3个,但是他们的后缀名都不一样,同名不同后缀的文件,其实有着完全一样的内容,只是他们各自存储信息的格式是不同的(下列文件不一定全部有)。
context_indep.* 存储了上下文无关的信息。
align_lexicon 表示对齐文件,是由lexiconp.txt的第一列第三列提取出来生成
nonsilence.* silence.* optional_silence.*存储了音素信息。
extra_question.*存储的是一些额外的信息,主要是和音调和重音之类的信息有关。
disambig.* 则存储了一些消除歧义使用的符号,在前面已经见过,比如#0, #1等等。
set.* 包含了一系列的因素集,一般是将同一个音素(有时候会包含词位信息 _B _E)存储在同一行,聚类使用。
align_lexicon 表示对齐文件,是由lexiconp.txt的第一列第三列提取出来生成
context_indep 里面包含的是那些非正常音素,包含(静音(SIL),口语噪声(SPN),非口语噪声(NSN)和笑声(LAU)
word_boundary 里面是音素和词位的关联信息,建立这种对应关系是需要这些信息在音素网络中恢复词的边界
roots 里面包含的是建立音素上下文决策树信息,里面的shared 表示共享根,一般语气和语调会在同一行,认为共享
prepare_lm.sh脚本的功能是利用task.arpabo(lm_tg.arpa)来生成语言模型的状态机G.fst。
在基于 wfst 的语音识别中,需要将 HCLG 四个不同层次的模型复合(composition)在一起构成一个超大的解码网络,其中的 G 即是语言模型的 WFST表示。但是我们常见的语言模型并不是以 WFST 形式存在的,而是基于 ngram 实现的,通常以 arpa 文件形式存在。所以要将 arpa 文件转为 wfst,在 kaldi 中以 arpa2fst 脚本来转换。
- 在data目录下创建一个lang_test_tg目录,将data/lang下的文件拷贝到lang_test_tg目录下
- arpa2fst --disambig-symbol=#0 --read-symbol-table=data/lang_test_tg/words.txt input/task.arpabo data/lang_test_tg/G.fst
- arpa2fst [opts] <input-arpa> <output-fst>
到这里,所有数据准备完毕。在data目录下生成了后续需要的所有数据。除了训练和测试用的音频文件目录外,在data/lang_test_tg下包含了L.fst, G.fst,topo(HMM拓扑网络)和其他相关文件。
#========第二步:语音信号特征抽取 ====================
for x in train_yesno test_yesno; do
steps/make_mfcc.sh --nj 1 data/$x exp/make_mfcc/$x mfcc // 提取语音信号特征码
steps/compute_cmvn_stats.sh data/$x exp/make_mfcc/$x mfcc // 计算标准特征码
utils/fix_data_dir.sh data/$x
done
- make_mfcc.sh [opt] <data-dir> <log-dir> <mfcc-dir> :--nj 1表示只需要一个处理进程,因为数据量很少。data/train_yesno为训练数据目录;data/test_yesno为测试数据目录。exp/make_mfcc/train_yesno为存放训练日志;exp/make_mfcc/test_yesno为存放测试日志。mfcc为存放输出特征值的目录。
- compute_cmvn_stats.sh [opt] <data-dir> [<log-dir> [<cmvn dir>] ]: data/train_yesno为训练数据目录;data/test_yesno为测试数据目录;exp/make_mfcc/train_yesno为存放训练日志;exp/make_mfcc_test_yesno为存放测试日志;mfcc为存放输出结果目录。
- fix_data_dir.sh <data-dir>:检查确保data-dir目录下的数据文件被正确的排序和过滤,比如删除掉没有特征码的语音utterance。
#========第三步:单音模型训练 =======================
steps/train_mono.sh --nj 1 --cmd "$train_cmd" --totgauss 400 data/train_yesno data/lang exp/mono0a
因为这个例子的语音都是非常简单的单个单词,单词和单词之间的影响很小,所以只需要做单音模型训练即可。训练的目标就是获得语音特征值和音素的映射网络GMM-HMM,也就是声学模型。
- steps/train_mono.sh [options] <data-dir> <lang-dir> <exp-dir>
- [options] / --config <config-file> 包含配置参数的文件
- [options] / --nj <nj> 并行工作进程的个数
- [options] / --cmd (utils/run.pl | queue.pl <queue opts> ) 工作进程的运行方式
- [options] / --totgauss 400 单高斯函数的个数为400
训练的过程:
1.首先是初始化GMM,使用的脚本是/kaldi-trunk/src/gmmbin/gmm-init-mono,输出是0.mdl和tree文件;
2.compile training graphs,使用的脚本是/kaldi-trunk/source/bin/compile-training-graphs,输入是tree,0.mdl和L.fst
输出是fits.JOB.gz,其是在训练过程中构建graph的过程;
3.接下来是一个对齐的操作,kaldi-trunk/source/bin/align-equal-compiled;
4.然后是基于GMM的声学模型进行最大似然估计得过程,脚本为/kaldi-trunk/src/gmmbin/gmm-est;
5.然后进行迭代循环中进行操作,如果本步骤到了对齐的步骤,则调用脚本kaldi-kaldi/src/gmmbin/gmm-align-compiled;
6.重新估计GMM,累计状态,用脚本/kaldi-trunk/src/gmmbin/gmm-acc-states-ali;调用新生成的参数(高斯数)重新估计GMM,调用脚本/kaldi-trunk/src/gmmbin/gmm-est;
7.对分散在不同处理器上的结果进行合并,生成.mdl结果,调用脚本gmm-acc-sum;
8.增加高斯数,如果没有超过设定的迭代次数,则跳转到步骤5重新进行训练
最后生成的.mdl即为声学模型文件
#========第四步:结合声学模型和语言模型(L.fst + G.fst)生成解码矩阵 =======================
utils/mkgraph.sh data/lang_test_tg exp/mono0a exp/mono0a/graph_tgpr
- data/lang_test_tg目录下存放着语言模型
- exp/mono0a目录下存放着声学模型
- exp/mono0a/graph_tgpr为输出目录,存放最后生成的HCLG解码网络HCLG.fst
#========第五步:解码测试 =============================
steps/decode.sh --nj 1 --cmd "$decode_cmd" exp/mono0a/graph_tgpr data/test_yesno exp/mono0a/decode_test_yesno
for x in exp/*/decode*; do
[ -d $x ] && grep WER $x/wer_* | utils/best_wer.sh;
done
- steps/decode.sh [opt] <graph-dir> <data-dir> <decode-dir>
- [opt] / --config <config-file> :存放配置的文件
- [opt] / --nj <nj> : 并行工作进程的个数
- [opt] / --iter <iter> 迭代模型测试的次数
- [opt] / --model 选用哪个模型
- [opt] / --cmd (utils/run.pl | utils/queue.pl <queue opts>) 工作进程的运行方式
- [opt] / --transform-dir <trans-dir> fMLLR转换的目录
- [opt] / -- acwt <float> 用于lattice 产生的声学模型刻度
- [opt] / --scoring-opts <string> 用于local/score.sh
- [opt] / --num-threads <n> 线程的个数
- graph-dir为解码网络文件存放的目录
- data-dir为测试音频文件的目录
- decode-dir为解码结果存放的目录
总结一下:
- data/train_yesno子目录下存放训练用的音频文件
- data/test_yesno子目录下存放测试用的音频文件
- data/lang下存放生成语言模型的文件
- 利用data/lang下的文件在data/lang_test_tg目录下生成的语言模型
- 利用data/train_yesno下的文件,训练出exp/mono0a目录下生成的声学模型
- 然后结合语言模型和声学模型生成HCLG解码网络存放在exp/mono0a/graph_tgpr下
- 最后,利用这个HCLG解码网络来测试data/test_yesno下的音频文件,得到测试结果。