Kaldi 语音识别工具使用记录-日语数据集CSJ
目录
- Kaldi 语音识别工具使用记录-日语数据集CSJ
- 前言
- 数据下载
- 数据前处理
-
-
- 1.数据准备
- 2. 生成训练集
- 3. 生成phone.txt 和 lexcion.txt
- 4. 语言模型
- 5. 特征提取
-
- 模型训练
- 模型使用
- 参考+推荐教程
前言
- 众所周知Kaldi是一个对新手很不友好的工具,特别是他的官方文档,非常硬核,所以我想把自己的Kaldi学习过程和大家分享一下,当然我也是刚入门,很多理论基础都还没搞清楚,也算是一个和大家交流的过程。
- 首先该记录重在操作流程,其中涉及到的一些有关语言模型,HMM和tri-phone的问题,在这里先不深入讲解,因为比较复杂,暂时不影响模型的训练和使用,所以我会在后面的博客中单独讲解。总体来说本文的受众群体是能看懂shell并刚开始接触kaldi的同学。
- 后面还会有英文数据集的记录,因为最近正好在用CSJ,就先记录一下,中文数据集最后再看看,貌似比较困难。
在这里我把数据集构成用表格列出来,参考的是内部的说明附件
语音类型 | 讲话人数量 | 文件数量 | 总时间 / h |
---|---|---|---|
学会演讲 | 819 | 987 | 274.4 |
模拟演讲 | 594 | 1715 | 329.9 |
其他 | 16 | 19 | 24.1 |
学会演讲对话 | 10 | 10 | 2.1 |
模拟演讲对话 | 16 | 16 | 3.4 |
基于课题对话 | 16 | 16 | 3.1 |
自由对话 | 16 | 16 | 3.6 |
重复朗读 | 16 | 16 | 5.5 |
朗读 | 428 | 507 | 15.5 |
总计 | 1417 | 3302 | 661.6 |
- conf\
- local\ 一些针对数据集专用的脚本都放在local下面
- steps\
- utils\ steps/ 和 utils/ 是所有数据集通用的脚本,用ls -alh ./ 可以看出来是wsj数据集软链接过来的
- cmd.sh 只有一台机器的话,就把所有queue.pl 换成 run.pl
export train_cmd="run.pl"
export decode_cmd="run.pl"
export cuda_cmd="run.pl"
export mkgraph_cmd="run.pl"
- path.sh 根据自己的环境修改,一般不用动
- run.sh 启动脚本,不建议一次直接运行,最好是按照里面的stage分块注释运行,下面分块对该脚本进行讲解:
. ./cmd.sh
. ./path.sh
1.数据准备
这几行都是在把下载好的数据进行分类整理
use_dev=false
CSJDATATOP=CSJDATA
CSJVER=usb
if [ ! -e data/csj-data/.done_make_all ];
then
echo "CSJ transcription file does not exist"
local/csj_make_trans/csj_autorun.sh $CSJDATATOP data/csj-data $CSJVER
fi
wait[ ! -e data/csj-data/.done_make_all ]\
&& echo "Not finished processing CSJ data" && exit 1;
local/csj-data_prep.sh data/csj-data 3
基本每行都有注释,理解起来也不困难,我在这里就做一些进一步的说明吧。
use_dev 这里我没用,因为自己手上数据集不太全,分不出来开发集,所以下面的记录都是基于训练+测试集。
下载好的数据集应该有这么几个目录:
- DOC/
- PLABEL/
- WAV/
- MORPH/
运行 local/csj_make_trans/csj_autorun.sh 之后会生成新的目录 data/csj-data, 该目录包含测试集eval, 训练集 core 和 noncore,core和noncore在这里可以不用区分,全当作训练集就行。如果当前的数据集不全,或者版本太老,可能会在划分测试集时报错,因为csj_autorun.sh这个脚本自己选了3*10组测试数据(1组1个讲话人),100行开始可以看到,根据自己的需求可以调整。我就只用了里面的1/3,多余的都删了。
# Evaluation set ID
eval1="A01M0110 A01M0137 A01M0097 A04M0123 A04M0121 A04M0051 A03M0156 A03M0112 A03M0106 A05M0011"
eval2="A01M0056 A03F0072 A02M0012 A03M0016 A06M0064 A06F0135 A01F0034 A01F0063 A01F0001 A01M0141"
eval3="S00M0112 S00F0066 S00M0213 S00F0019 S00M0079 S01F0105 S00F0152 S00M0070 S00M0008 S00F0148"
到这里为止是把CSJ里面的音频文件和对应的转录文件按讲话人归到一起,一个讲话人一个文件夹,并分出测试集。例如:
data/csj-data/eval/eval1/A01M0097/
A01M0097-trans.text
A01M0097-wav.list
A01M0097-trans.4lex
2. 生成训练集
local/csj_data_prep.sh data/csj-data 3
这里的参数用3,就是选择处理所有种类的数据,默认是0,只处理学术演讲和其他类型的数据。脚本会提取出训练集生成新的目录:
- data/local/train
- data/train
segments
spk2utt
utt2spk
text
wav.scp
其中segments里面保存了语音和文本片段对应的wav文件中的时间片段,格式如下
speaker_time1_time2 speaker time1 time2
后面的文本转录都是以speaker_time1_time2为基本单位记录下来的。
例:
A01F0055_0005549_0015595 A01F0055 5.549 15.595
A01F0055_0015945_0027184 A01F0055 15.945 27.184
A01F0055_0036727_0047453 A01F0055 36.727 47.453
A01F0055_0047680_0059865 A01F0055 47.68 59.865
spk2utt 就和文件名一样,speaker to utterence,保存的是一个讲话人所有的讲话片段,格式如下:
speaker speaker_time1_time2 speaker_time3_time4 …
utt2spk 和上面相反, utterence to speaker, 每个语音片段对应的讲话人, 格式如下:
speaker_time1_time2 speaker
下面是text, 最重要的文件,保存了每个语音片段对应的文本内容,格式如下:
speaker_time1_time2 文本
例:
A01F0055_0005549_0015595発表+名詞 し+動詞/サ行変格/連用形 ます+助動詞/終止形 えー+感動詞 私+代名詞 共+接尾辞 は+助詞/係助詞 乳児+名詞 が+助詞/格助詞 音楽+名詞 を+助詞/格助詞 どの+連体詞 よう+形状詞 に+助動詞/連用形 聞い+動詞/カ行五段/連用形/イ音便 て+助詞/接続助詞 いる+動詞/ア行上一段/終止形 か+助詞/終助詞 また+接続詞 聴取+名詞 に+助詞/格助詞 発達+名詞 齢+接尾辞 差+名詞 が+助詞/格助詞 見+動詞/マ行上一段/未然形 られる+助動詞/終止形 か+助詞/副助詞 を+助詞/格助詞 検討+名詞 し+動詞/サ行変格/連用形 て+助詞/接続助詞 おり+動詞/ラ行五段/連用形 ます+助動詞/終止形
A01F0055_0015945_0027184本+接頭辞 研究+名詞 で+助詞/格助詞 は+助詞/係助詞 旋律+名詞 の+助詞/格助詞 調+名詞 つまり+副詞 長調+名詞 です+助動詞/終止形 と+助詞/格助詞 か+助詞/副助詞 短調+名詞 の+助詞/格助詞 変化+名詞 の+助詞/格助詞 ひきわけん+言いよどみ 聞き分け+名詞 に+助詞/格助詞 着目+名詞 し+動詞/サ行変格/連用形 て+助詞/接続助詞 実験+名詞 を+助詞/格助詞 通し+動詞/サ行五段/連用形 て+助詞/接続助詞 えー+感動詞 知見+名詞 を+助詞/格助詞 得+動詞/ア行下一段/連用形 まし+助動詞/連用形 た+助動詞/連体形 の+助詞/準体助詞 で+助動詞/連用形 報告+名詞 し+動詞/サ行変格/連用形 たい+助動詞/終止形 と+助詞/格助詞 思い+動詞/ワア行五段/連用形 ます+助動詞/終止形
A01F0055_0036727_0047453え+感動詞 長調+名詞 短調+名詞 の+助詞/格助詞 違い+名詞 は+助詞/係助詞 え+感動詞 成人+名詞 の+助詞/格助詞 場合+名詞 は+助詞/係助詞 比較+名詞 的+接尾辞 容易+形状詞 に+助動詞/連用形 あの+感動詞 普段+名詞 から+助詞/格助詞 聞き分け+動詞/カ行下一段/連用形 て+助詞/接続助詞 い+動詞/ア行上一段/未然形 られる+助動詞/終止形 と+助詞/格助詞 い+言いよどみ 言わ+動詞/ワア行五段/未然形 れ+助動詞/連用形 て+助詞/接続助詞 い+動詞/ア行上一段/連用形 ます+助動詞/終止形 また+接続詞 海外+名詞 の+助詞/格助詞 えー+感動詞 研究+名詞 で+助詞/格助詞 は+助詞/係助詞
这里需要注意的是,词性不是必须的,很多英语数据集就没有标注词性。
最后是wav.scp,保存了每个音频文件的地址,后面提取特征就是靠这个定位到wav文件。
需要注意的是CSJ数据集是把一个讲话人保存为一个音频,有的数据集是把一个语音片段保存为一个语音片段,wav文件会有很多。
例:
A01F0055 cat CSJDATA/WAV/core/A01F0055.wav |
A01F0067 cat CSJDATA/WAV/core/A01F0067.wav |
A01F0122 cat CSJDATA/WAV/core/A01F0122.wav |
到此为止训练数据和测试数据准备完成。
在这里顺便讲一下,如果用是端到端的seq-to-seq模型,这里开始就可以训练了,但kaldi是基于统计学的HMM,所以后面还需要准备声学模型和语言模型以及alignment,相对更麻烦一些。
3. 生成phone.txt 和 lexcion.txt
先讲一下lexicon.txt这个文件
根据kaldi-tutorial里的描述:
You will need a pronunciation lexicon of the language you are working on. A good English lexicon is the CMU dictionary, which you can find here. The lexicon should list each word on its own line, capitalized, followed by its phonemic pronunciation
WORD W ER D
LEXICON L EH K S IH K AH N
lexicon直译过来是词典的意思,在这里保存的是每个词和对应的发音,英语就是A开头的单词到Z开头的单词,日语就是从あ打头的单词开始,前后会有一些字符和静音符号,比如alpha和。至于选择哪些词, 有很多不同的版本,比如上面就用的是卡纳基梅隆大学的,一般这种没经处理公开的词典有很多数据集里没有的词, 所以针对每个数据集需要删掉一部分,最后就变成了我们要用的lexicon.txt。
local/csj_prepare_dict.sh
utils/prepare_lang.sh --num-sil-states 4 data/local/dict_nosp "" data/local/lang_nosp data/lang_nosp
运行第一行会生成新的目录,data/local/dict_nosp/, 该目录会包含下面几个文本:
- lexicon.txt
- nonsilence_phones.txt
- optional_silence.txt
- silence_phones.txt
- extra_questions.txt
ゆうくくえ+言いよどみ y u: k u k u e
ゆうこ+名詞/固有名詞 y u: k o
...
係れる+動詞/ラ行下一段/連体形 k a k a r e r u
係員+名詞 k a k a r i i N
extra_questions.txt是在运行分类回归树时需要查询的问题,关系到后面的模型会如何进行学习,这里涉及到CART和HMM,我会在以后的博客中单独讲解,自己其实也还没有完全理解。
那么到这里我们就得到了lexicon.txt, 下面说一下phones.txt:,也就是用utils/prepare_lang.sh脚本,这是一个kaldi通用脚本,根据源码,该脚本的调用格式是:
utils/prepare_lang.sh options
–num-sil-states是一个可选参数,用几个state来表示一小段不发声的语音,一般是state数越多结果越精确,但也会导致计算速度大幅下降,这里仍然涉及到HMM,暂时不深入讲解。
- phones.txt
- oov.txt
- words.txt
oov.txt 列出了前面定义的out of vocabulary,一般只有一个
waors.txt 把前面lexicon里面出现的单词都标上编号,包括
到这里为止,我们得到了lexicon.txt和phones.txt两个重要的文件
4. 语言模型
# Now train the language models.
local/csj_train_lms.sh data/local/train/text data/local/dict_nosp/lexicon.txt data/local/lmsrilm_opts="-subset -prune-lowprobs -unk -tolower -order 3"
LM=data/local/lm/csj.o3g.kn.gz
utils/format_lm_sri.sh --srilm-opts "$srilm_opts" data/lang_nosp $LM data/local/dict_nosp/lexicon.txt data/lang_nosp_csj_tg
语言模型是自然语言处理的关键,可以说现在所有的对话机器人,比如苹果上的siri或者车上的自助语音识别系统,只要涉及到对人类语言的理解,就必须有语言模型,在语音识别中,系统识别出一段语音,但其文本组合确有很多,根据上下文判断出概率最大的组合就是语言模型的作用。在这里说一下语音识别中最基本的一个公式,也是我们刚开始学习时常见的一个式子:
W ′ = a r g m a x W P ( W ∣ X ) = a r g m a x W P ( X ∣ W ) P ( W ) W^{\prime}={\underset{W}{argmax}}P(W|X) = {\underset{W}{argmax}}P(X|W)P(W) W′=Wargmax?P(W∣X)=Wargmax?P(X∣W)P(W)
这个式子的最后两项分别代表声学模型和语言模型,就是找到一个词使条件概率最大。
训练语言模型需要文本信息,一个是包含所有转录文本的text,另一个是发音词典lexicon,输出保存在data/local/lm 目录下。kaldi本身也是借助外部工具来训练语言模型,目前都用的是srilm, 在local/csj_train_lm.sh脚本中会检查srilm是否正常安装:
loc='which ngram-count';
if [ -z $loc ];
then
...
最后目录下的csj.o3g.kn.gz就是生成的语言模型,后面两行将语言模型转换为G.fst(语言模型) 和 L.fst(词典),fst 是Finite State Transducer的简称,在decode的过程中有重要作用。我会在后面的博客中单独讲解语言模型的训练和srilm工具的使用。
5. 特征提取
下面是语音特征提取部分,在此之前有一个对测试集数据的处理
for eval_num in eval1 eval2 eval3 ;
do
local/csj_eval_data_prep.sh data/csj-data/eval $eval_num
done
接下来一段就是特征提取的主要代码
for x in train eval1 eval2 eval3;
do
steps/make_mfcc.sh --nj 50 --cmd "train_cmd" data/$x exp/make_mfcc/$x $mfccdir
steps/compute_cmvn_stats.sh data/$x exp/make_mfcc/$x $mfccdir
utils/fic_data_dir.sh data/$x
done
提前准备mfcc和exp两个新目录,steps/make_mfcc.sh读取训练集或测试集目录下的文件,将日志保存在exp/make_mfcc/train下面,以.log结尾,特征值保存在mfcc目录下,该目录下有ark和scp两种文件,ark是二进制文件,kaldi专用的一种文件储存格式,scp是可以直接读取的文本文件,scp里面的内容相当于ark文件的索引,例如打开raw_mfcc_train.1.scp可以看到
A01F0055_0005549_0015595 /mnt/work/WorkSpace/SpeechEmotion/kaldi-master/egs/csj/s5/mfcc/raw_mfcc_train.1.ark:25
A01F0055_0015945_0027184 /mnt/work/WorkSpace/SpeechEmotion/kaldi-master/egs/csj/s5/mfcc/raw_mfcc_train.1.ark:13214
A01F0055_0036727_0047453 /mnt/work/WorkSpace/SpeechEmotion/kaldi-master/egs/csj/s5/mfcc/raw_mfcc_train.1.ark:27950
后面的25就是这一段语音的特征值在ark文件中存储的位置,起着定位作用,我们再打开对应的ark文件,关于ark文件的打开方式,这里也简单说一下,kaldi本身有其一套完整的读写体系,在这里我就只说怎么把它读出来,在这里需要用到copy-feats这个命令,如果你敲这个命令没反应说明你的path.sh有问题, 需要检查一下。具体打开方式如下:
copy-feats ark:raw_mfcc_train.1.ark ark,t:tmp.txt
or
copy-feats ark:raw_mfcc_train.1.ark ark,t:-
第一种就是把读取到的内容写入到某个文件内,第二种就是直接命令行标准输出,内容较多,建议第一种,打开其中一个我们可以看到它里面按这种形式把每一段语音的所有特征值保存起来:
A01F0055_0005549_0015595[
68.57529 -15.02221 10.12106 6.169441 -3.06838 -14.18426 -2.388935 8.680888 -19.29002 11.12521 1.459095 2.776878 -6.49677
68.8074 -10.7807 3.45673 6.603628 4.2156 -4.825665 -15.29332 -0.4787865 -11.51265 12.40076 -15.76548 -1.53196 2.864855
75.07423 -4.484325 -12.97093 ... ]
A01F0055_0015945_0027184[
65.06477 -7.619178 3.576411 -2.145537 4.007335 1.288888 -7.116664 -4.354163 -9.968693 -1.104543 1.286701 4.533045 6.344656
65.4518 -8.208797 4.124818 -6.03487 -4.254171 11.7718 4.345001 -5.954123 -3.46464 0.4191284 1.154415 1.401041 8.727738
69.9026 -11.4517 -7.803019 ... ]
到这里我们获取了所有语音片段的MFCC,最后针对每个发言者计算一下cmvn, 结果保存在cmvn_train.ark,后面的博客中会单独讲解cmvn(倒谱均值方差归一化) 的计算方法。
模型训练 训练开始之前,还有一些数据处理,主要是数据分割,大概就是先选出一部分数据预训练,能提高效率,这里暂时就先忽视他,直接把现有数据全部训练。
utils/subset_data_dir.sh --shortest data/train_nodev 100000 data/train_100kshort
utils/subset_data_dir.sh data/train_100kshort 30000 data/train_30kshort# Take the first 100k utterances (about half the data);
we'll use
# this for later stages of training.
utils/subset_data_dir.sh --first data/train_nodev 100000 data/train_100k
utils/data/remove_dup_utts.sh 200 data/train_100k data/train_100k_nodup# 147hr 6min# Finally, the full training set:
utils/data/remove_dup_utts.sh 300 data/train_nodev data/train_nodup# 233hr 36min
下面开始正式的训练
## Starting basic training on MFCC features
steps/train_mono.sh --nj 50 --cmd "$train_cmd" \
data/train data/lang_nosp exp/monosteps/align_si.sh --nj 50 --cmd "$train_cmd" \
data/train data/lang_nosp exp/mono exp/mono_alisteps/train_deltas.sh --cmd "$train_cmd" \
3200 30000 data/train data/lang_nosp exp/mono_ali exp/tri1graph_dir=exp/tri1/graph_csj_tg
$train_cmd $graph_dir/mkgraph.log \
utils/mkgraph.sh data/lang_nosp_csj_tg exp/tri1 $graph_dir
for eval_num in eval1 eval2 eval3 $dev_set ;
do
steps/decode_si.sh --nj 10 --cmd "$decode_cmd" --config conf/decode.config \
$graph_dir data/$eval_num exp/tri1/decode_${eval_num}_csj
done
整个训练流程是train align train align…交替进行,并且新的模型是在上一个模型基础上进行训练,最开始训练的是monophone, 输入参数是data/train和data/lang_nosp两个目录,最后模型保存在exp/mono下面。
alignment用steps/align_si.sh实现,输入参数多了一个模型的目录,最后输出结果保存在exp/mono_ali下面,下一个模型的训练需要用到对齐后的模型,最新的模型保存在exp/tri1下面。
需要注意的是在steps/train_deltas.sh中3200代表HMM状态的数量,30000代表GMM的数量。什么意思呢,举个例子:
假设我们的发音词典lexicon文件中有50个phonemes(音素),我们可以为每个phoneme建立一个HMM状态,但我们知道一个发音再短,他也有开始中间和结束,因此我们需要至少3个HMM状态来代表一个phoneme,即tri-phone, 此时我们有3*50=150个HMM状态,理论上这个数字越大,模型越精确,当然花的时间也越多,在这里他设置为2000,就表示一个phoneme肯定不止3个HMM状态。
在这里先停一下,后面就是一直在重复这个过程,所有模型训练完要好几个小时,此处可以直接mkgraph,需要说一下,训练好的模型并不能直接使用,需要生成一个 (decoing graph)解码图即HCLG文件,这个文件包含了语言模型,发音词典,上下文信息以及基于HMM的声学模型,本质上是一个FST,这里涉及到很复杂的数学概念,我会在后面的博客重点讲解。
总之,我们要用utils/mkgraph.sh这个脚本生成最终可以使用的模型文件HCLG.fst,其位于exp/tri1/graph_csj_tg目录下,每个模型都对有对应的HCLG。在这里我们暂时用tri1模型来进行语音识别。
最后一段代码是 decode,就是用测试集来测试模型,会得到一个WER来衡量模型的好坏,讲道理对于我们初学者,与其看最后的错词率,不如直接拿一段语音来试一下,能帮助我们有更直观的理解。
到此为止,我们的exp目录的结构应该是
exp/
—make_mfcc/
—mono/
—mono_ali/
—tri1/graph_csj_tg
模型使用 最后我们来尝试用训练好的模型进行语音识别,kaldi本身是有这个功能的,但很多教程都没介绍,貌似大家都觉得降低错误率设计出优秀的模型才是研究的重点,话倒是没错,但对于我们初学者来说,看到训练好的模型成功将语音转录为文字会更有成就感吧。
Kaldi自带的线上识别脚本在voxforge的数据集的online_demo目录下,可以把他复制到你的工作目录,主要的脚本就是这个run.sh,下面通过代码说明一下如何设置:
ac_model_type是模型的名称,设置为tri1
decode_dir是识别结果保存的目录
test_mode是识别的模式,在这里需要纠正一下,虽然线面是线上识别,但这里我们采用的方式是给语音文件,输出文本,而不是实时的语音识别,因此我们选择simulated模式,想尝试实时识别的话可以用live
在这里我建议你把他的31行到39行删了,原本他是发现如果你们有准备好语音和模型的话自动给你下载,我们用自己的数据就行。后面是一个case我们直接看simulated那里:
mkdir -p $decode_dir
# make an input .scp file
> $decode_dir/input.scp
for f in $audio/*.wav;
do
bf=`basename $f`
bf=${bf%.wav}
echo $bf $f >> $decode_dir/input.scp
done
online-wav-gmm-decode-faster --verbose=1 --rt-min=0.8 --rt-max=0.85\
--max-active=4000 --beam=12.0 --acoustic-scale=0.0769 \
scp:$decode_dir/input.scp $ac_model/model $ac_model/HCLG.fst \
$ac_model/words.txt '1:2:3:4:5' ark,t:$decode_dir/trans.txt \
ark,t:$decode_dir/ali.txt $trans_matrix;
;
按照代码先生成需要的目录
mkdir -p online-data/audio online-data/models/tri1
mkdir work
接着把需要识别的语音片段复制到online-data/audio下,比如A01M0097.wav,脚本会自动将语音文件的地址保存到work/input.scp中。最后我们将exp/tri1/graph_csj_tg所有内容复制到online-data/models/tri1目录下,再讲exp/tri1目录下的final.mdl文件复制到同样目录下并改名为model。实际上根据脚本,需要的文件是:
- model(final.mdl)
- HCLG.fst
- words,txt
- input.scp
文章图片
脚本的最后一部分是计算WER,想要计算的话需要提前在语音文件同一目录下转备好对照文本,并重命名为trans.txt,代码通过比较work/trans.txt和正确的文本生成WER,这里就不细说了。
那么到此为止我们将从数据前处理到模型的训练和使用整个流程走了一遍,挖了很多坑,会在后面的文章中填上,毕竟kaldi确实不怎么对新手友好,后面我会将一些理论和脚本结合起来讲解,自己也算是刚学会不久,有什么问题的话欢迎大家指出。
参考+推荐教程 kaldi-tutorial
【kaldi-语音识别|Kaldi 语音识别工具使用记录】官方dummy教程
台湾大学语音识别公开课(后面的讲解也主要参考这里)
东京工业大学公开ppt-jp
TODO
- 多台机器同时运行 queue.pl
- extra_questions.txt
- tri-phone
- LM
推荐阅读
- 李沐动手学深度学习笔记|李沐动手学深度学习V2-BERT微调和代码实现
- 机器学习|深度学习经典数据集汇总
- kaggle|word2vec和常见CNN+RNN网格结构组成的文本分类模型
- 人工智能|tensorflow搭建CNN对文本进行分类
- 自然语言处理|人工智能框架实战精讲(Keras项目-英文语料的文本分类实战与调参优化)
- 目前最好用的linux环境下获取网卡地址的方法
- 一板网电子论坛|使用Arduino开发板进行语音识别
- Shell|awk学习及实例
- Linux|Linux awk命令详解