环境要求:ubuntu16.04
Kaldi是一款基于c++编写的完全开源的语音识别工具箱。
Kaldi 主要是运行在 Unix-like 操作系统下, Kaldi 项目现在托管在 github上 ,需要使用git命令将其下载到本地
在终端键入:
git clone https://github.com/kaldi-asr/kaldi.git kaldi-tian
编译kaldi源码
在终端键入cd kaldi-tian/tools/extras
键入./check_dependencies.sh执行该脚本检查包依赖情况
根据提示安装所需要的包
在终端键入cd kaldi-tian/tools
键入make进行编译
这里出现警告 IRSTLM库没有安装,其他一切OK
键入cd kaldi-tian/tools
键入./extras/install_irstlm.sh安装IRSTLM
在终端键入cd kaldi-tian/src
键入./configure检查外部库安装情况,根据提示进行安装
键入make depend进行预编译
键入make进行编译,make耗时较长请耐心等待
检测 Kaldi 是否编译成功,使用kaldi-tian/egs/yesno例子进行测试
yesno是关于yes和no两个孤立词的识别
在终端键入cd kaldi-tian/egs/yesno/s5进入yesno样例目录
键入ls命令查看该目录下有哪些文件
ai@tiancuixia-1:~/kaldi-tian/egs/yesno/s5$ ls
conf exp local path.sh steps waves_yesno
data input mfcc run.sh utils waves_yesno.tar.gz
conf文件夹里是一些配置文件例如MFCC的参数 HMM的拓扑结构;
local文件夹里主要是一些准备数据的脚本,供顶层脚本run.sh调用;
steps和utils文件夹里主要是一些运行时调用的脚本;
data文件夹里主要存放语言模型、发音字典和音素信息等等。
键入./run.sh
说明我们kaldi已经正确安装了
CVTE开源了kaldi的中文模型,模型下载地址:http://kaldi-asr.org/models/0002_cvte_chain_model.tar.gz
解压放到kaldi-tian/egs下即可
将egs/wsj/s5中的steps和utils拷贝到egs/cvte/s5目录下
cp -r egs/wsj/s5/steps egs/cvte/s5/steps
cp -r egs/wsj/s5/utils egs/cvte/s5/utils
将egs/hkust/s5/local/score.sh拷贝到egs/cvte/s5/local目录下
cp egs/hkust/s5/local/score.sh egs/cvte/s5/local
cd kaldi-tian/egs/cvte/s5
注释掉utils/lang/check_phones_compatible.sh中if语句中的exit 1:
36 # check if the files exist or not
37 if [ ! -f $table_first ]; then
38 if [ ! -f $table_second ]; then
39 echo "$0: Error! Both of the two phones-symbol tables are absent."
40 echo "Please check your command"
41 #exit 1;
42 else
43 # The phones-symbol-table1 is absent. The model directory maybe created by old script.
44 # For back compatibility, this script exits silently with status 0.
45 exit 0;
46 fi
CVTE文件结构:
ai@tiancuixia-1:~/kaldi-tian/egs/cvte/s5$ ls
cmd.sh conf data exp fbank local path.sh run.sh steps utils
运行示例脚本:
./run.sh
ai@tiancuixia-1:~/kaldi-tian/egs/cvte/s5$ ./run.sh
steps/make_fbank.sh --nj 1 --cmd run.pl data/fbank/test exp/make_fbank/test fbank/test
steps/make_fbank.sh: moving data/fbank/test/feats.scp to data/fbank/test/.backup
utils/validate_data_dir.sh: WARNING: you have only one speaker. This probably a bad idea.
Search for the word 'bold' in http://kaldi-asr.org/doc/data_prep.html
for more information.
utils/validate_data_dir.sh: Successfully validated data-directory data/fbank/test
steps/make_fbank.sh: [info]: no segments file exists: assuming wav.scp indexed by utterance.
Succeeded creating filterbank features for test
steps/compute_cmvn_stats.sh data/fbank/test exp/fbank_cmvn/test fbank/test
Succeeded creating CMVN stats for test
steps/nnet3/decode.sh --acwt 1.0 --post-decode-acwt 10.0 --nj 1 --num-threads 1 --cmd run.pl --mem 64G --iter final --frames-per-chunk 50 exp/chain/tdnn/graph data/fbank/test exp/chain/tdnn/decode_test
utils/lang/check_phones_compatible.sh: Error! Both of the two phones-symbol tables are absent.
Please check your command
grep: exp/chain/tdnn/phones.txt: No such file or directory
grep: exp/chain/tdnn/graph/phones.txt: No such file or directory
steps/nnet3/decode.sh: feature type is raw
steps/diagnostic/analyze_lats.sh --cmd run.pl --mem 64G --iter final exp/chain/tdnn/graph exp/chain/tdnn/decode_test
run.pl: job failed, log is in exp/chain/tdnn/decode_test/log/analyze_alignments.log
score best paths
- steps/score_kaldi.sh --cmd 'run.pl --mem 64G' data/fbank/test exp/chain/tdnn/graph exp/chain/tdnn/decode_test
steps/score_kaldi.sh --cmd run.pl --mem 64G data/fbank/test exp/chain/tdnn/graph exp/chain/tdnn/decode_test
steps/score_kaldi.sh: scoring with word insertion penalty=0.0,0.5,1.0
- steps/scoring/score_kaldi_cer.sh --stage 2 --cmd 'run.pl --mem 64G' data/fbank/test exp/chain/tdnn/graph exp/chain/tdnn/decode_test
steps/scoring/score_kaldi_cer.sh --stage 2 --cmd run.pl --mem 64G data/fbank/test exp/chain/tdnn/graph exp/chain/tdnn/decode_test
steps/scoring/score_kaldi_cer.sh: scoring with word insertion penalty=0.0,0.5,1.0
- echo 'local/score.sh: Done'
local/score.sh: Done
score confidence and timing with sclite
Decoding done.
这里会报error
utils/lang/check_phones_compatible.sh: Error! Both of the two phones-symbol tables are absent.
原因是CVTE作者没有提供phones.txt, 不影响结果,忽略就好了。
预测结果保存在exp相应的路径下,可以自行查看
ai@tiancuixia-1:~/kaldi-tian/egs/cvte/s5/data/wav/audio$ ls
停止运行_standardTTS.wav 打开本地总线工具_standardTTS.wav 暂停测试_standardTTS.wav
单次触发_standardTTS.wav 打开物理层管理工具_standardTTS.wav 正常模式_standardTTS.wav
开始测试_standardTTS.wav 打开电源工具_standardTTS.wav 缩小垂直分辨率_standardTTS.wav
开始运行_standardTTS.wav 打开通用信号工具_standardTTS.wav 缩小存储深度_standardTTS.wav
打开串行外设工具_standardTTS.wav 打开闪存工具_standardTTS.wav 缩小水平时基_standardTTS.wav
打开二次方工具_standardTTS.wav 放大垂直分辨率_standardTTS.wav 缩小采样间隔_standardTTS.wav
打开数据方向工具_standardTTS.wav 放大存储深度_standardTTS.wav 退出测试_standardTTS.wav
打开时序工具_standardTTS.wav 放大水平时基_standardTTS.wav
打开时钟工具_standardTTS.wav 放大采样间隔_standardTTS.wav
这里是用阿里云的TTS工具合成了一些音频
存放在data/fbank/test路径下
ai@tiancuixia-1:~/kaldi-tian/egs/cvte/s5/data/fbank/test$ ls
cmvn.scp feats.scp spk2utt text utt2spk wav.scp
需要手动创建的文件包括 text、wav.scp、utt2spk
文件text包含每段发音的标注
ai@tiancuixia-1:~/kaldi-tian/egs/cvte/s5/data/fbank/test$ head -3 text
audio_停止运行_standardTTS 停止运行
audio_单次触发_standardTTS 单次触发
audio_开始测试_standardTTS 开始测试
这个文件的格式是:
<speaker_id>-<utterance-id> transcription
每行的第一项是发音编号(utterance-id), 可以是任意的文本字符串(如果设置中还包含说话人信息, 则应该把说话人编号(speaker-id)作为发音编号的前缀,和utterance-id以破折号"-"隔开)。这对于音频文件的排序非常重要。发音编号后面跟着的是每段发音的标注。你不用保证这里出现的每一个字都出现在你的词汇表中。词汇表之外的词会被映射到oov.txt 中。
另外一个很重要的文件是wav.scp
ai@tiancuixia-1:~/kaldi-tian/egs/cvte/s5/data/fbank/test$ head -3 wav.scp
audio_停止运行_standardTTS /home/ai/kaldi-tian/egs/cvte/s5/data/wav/audio/停止运行_standardTTS.wav
audio_单次触发_standardTTS /home/ai/kaldi-tian/egs/cvte/s5/data/wav/audio/单次触发_standardTTS.wav
audio_开始测试_standardTTS /home/ai/kaldi-tian/egs/cvte/s5/data/wav/audio/开始测试_standardTTS.wav
这个文件的格式是:
<recording-id> <extended-filename>
其中, extended-filename 是一个实际的文件名, 或者一段提取wav格式文件的命令。
在这里 wav.scp 每一行的第一项就是发音编号,即recording-id等于utterance-id。
最后一个需要手动创建的文件是utt2spk。
该文件指明某一段发音是哪一个说话人发出的
ai@tiancuixia-1:~/kaldi-tian/egs/cvte/s5/data/fbank/test$ head -3 utt2spk
audio_停止运行_standardTTS audio_停止运行_standardTTS
audio_单次触发_standardTTS audio_单次触发_standardTTS
audio_开始测试_standardTTS audio_开始测试_standardTTS
格式是:
<utterance-id> <speaker-id>
注意一点,说话人编号并不需要与说话人实际的名字完全一致——只需要大概能够猜出来就行。在这种情况下,我们假定每一个说话方(电话对话的每一方)对应一个说话人。这可能不完全正确—— 有时一个说话人会把电话交给另外一个说话人,或者同一个说话人会在不同的对话中出现——但是上述假定对我们来说也足够用了。如果完全没有关于说话人的信息, 可以把发音编号当做说话人编号。那么对应的文件格式就变为<utterance-id> <utterance-id>。
上述所有文件都应该按照utterance-id排序。如果没有排序, 运行脚本的时候就会出现错误。这与Kaldi的I/O框架有关,归根到底是因为排序后的文件可以在一些不支持 fseek()的流中,例如,含有管道的命令,提供类似于随机存取查找的功能。许多Kaldi程序都会从其他Kaldi命令中读取多个管道流,读入各种不同类型的对象,然后对不同输入做一些类似于―合并然后排序‖的处理。既然要合并排序, 当然需要输入是经过排序的。
不需要手动创建的文件
以用如下的一条命令创建spk2utt文件
#通过 utt2spk 创建 spk2utt 文件
utils/utt2spk_to_spk2utt.pl data/fbank/test/utt2spk > data/fbank/test/spk2utt
最后需要移除旧测试数据产生的cmvn.scp, 否则后续执行steps/make_mfcc.sh的时候代码会自动去检查cmvn.scp与utt2spk的一致性, 导致冲突error
以上创建相应文件的过程比较麻烦,因此我写了一个python自动化处理脚本(establish_fbank_files.py),需要copy到s5目录下执行。
准备好以上数据,就可以运行run.sh等待结果了,结果保存在exp相应的路径下。
我们的任务是将wav文件识别为特定的指令词(指令词汇有限),但是有时候文本不一定一致,比如:识别结果“但 次 触发” 对应的指令是 “单次触发”。我们需要对识别的文本进行纠错。
考虑的方案是先将识别的文字转换为拼音,然后与指令词汇表里每一条指令对应的拼音进行比较,比较方式是计算编辑距离。此外,针对一些容易识别错的发音组合,再通过增加一些规则及正则表达式组合来处理。相应的脚本名为asr_modify.py, 调用之前需要安装汉字转拼音及计算编辑距离的python包,pypinyin 及Levenshtein。
这里写了一些数据处理及修正相关的脚本(包含establish_fbank_files.py、asr_modify.py在内),详见PythonProjects/utils:代码
cvte 提供了offline识别(即数据的特征提取、解码都是批量进行的,主要用于评估预测效果),而cvte的模型加载起来要17个G左右,预测起来会很慢。
Kaldi 中新版的 online 识别,其特征处理过程跟CVTE的不一致,另外它也只是模拟实时,Kaldi 官网给的建议是自己根据实际需求写实时服务器。
(http://kaldi-asr.org/doc/online_decoding.html#online_decoding_scope)
我们的想法是基于CVTE提供的offline识别代码进行修改。
想要实现实时语音识别,就要把模型预加载起来,offline中特征、模型加载、特征解码都是批量的,需要把代码拆分融合。
首先把整个预测过程中相关的部分脚本梳理一下:
从s5/run.sh着手, 分步执行
steps/make_fbank.sh --nj 1 --cmd run.pl data/fbank/test exp/make_fbank/test fbank/test
调用了
./compute-fbank-feats --verbose=2 --config=../../egs/cvte/s5/conf/fbank.conf scp,p:../../egs/cvte/s5/data/fbank/test/wav1.scp ark:out.ark
输出文件out.ark
./copy-feats --compress=true ark:out.ark ark,scp:raw_fbank_test.1.ark,raw_fbank_test.1.scp
接受out.ark作为输入,输出文件raw_fbank_test.1.ark,raw_fbank_test.1.scp
shell脚本后续将raw_fbank_test.1.scp合并成feats.scp
注:
.ark (archieve)文件为二进制格式,一般很大,里面是真正的数据,
.scp(script)文件记录对应ark的路径
ai@tiancuixia-1:~/kaldi-tian/egs/cvte/s5/data/fbank/test$ head -1 feats.scp
audio_停止运行_standardTTS /home/ai/kaldi-tian/egs/cvte/s5/fbank/test/raw_fbank_test.1.ark:31
意 思 是 , 打 开 存 档(archive)文件/home/ai/kaldi-tian/egs/cvte/s5/fbank/test/raw_fbank_test.1.ark, fseek()定位到 31(字节), 然后开始读数据。
steps/compute_cmvn_stats.sh data/fbank/test exp/fbank_cmvn/test fbank/test
./compute-cmvn-stats --spk2utt=ark:spk2utt scp:raw_fbank_test.1.scp ark,scp:cmvn_test.ark,cmvn_test.scp
输出文件cmvn_test.ark,cmvn_test.scp
shell脚本后续复制cmvn_test.scp并保存为cmvn.scp
steps/nnet3/decode.sh --acwt 1.0 --post-decode-acwt 10.0 --nj 1 --num-threads 1 --cmd run.pl --mem 64G --iter final --frames-per-chunk 50 exp/chain/tdnn/graph data/fbank/test exp/chain/tdnn/decode_test
调用了src/nnet3bin/下编译好的nnet3-latgen-faster
nnet3-latgen-faster --frame-subsampling-factor=3 --frames-per-chunk=50 --extra-left-context=0 --extra-right-context=0 --extra-left-context-initial=-1 --extra-right-context-final=-1 --minimize=false --max-active=7000 --min-active=200 --beam=15.0 --lattice-beam=8.0 --acoustic-scale=1.0 --allow-partial=true --word-symbol-table=/home/ai/kaldi-tian/egs/cvte/s5/exp/chain/tdnn/graph/words.txt /home/ai/kaldi-tian/egs/cvte/s5/exp/chain/tdnn/final.mdl /home/ai/kaldi-tian/egs/cvte/s5/exp/chain/tdnn/graph/HCLG.fst 'ark,s,cs:apply-cmvn --norm-means=true --norm-vars=false --utt2spk=ark:/home/ai/kaldi-tian/src/featbin/utt2spk scp:/home/ai/kaldi-tian/src/featbin/cmvn_test.scp scp:/home/ai/kaldi-tian/src/featbin/raw_fbank_test.1.scp ark:- |' 'ark:|lattice-scale --acoustic-scale=10.0 ark:- ark:- | gzip -c >lat.1.gz'
代码中很多地方出现rspecifier和wspecifier标记,这里解读一下:
rspecifier: 用于说明如何读表的字符串;
wspecifier: 用于说明如何写入表的字符串。
(因为改了方案,所以CVTE的实时识别暂时就不做了,这里只是梳理一下代码,记录一下,后续有时间可以尝试继续做下去)