![Kaldi语音识别实战](https://wfqqreader-1252317822.image.myqcloud.com/cover/839/31391839/b_31391839.jpg)
2.4 一个简单的示例
本节将展示一个语音识别的示例:YesNo。这个示例的功能很有限,只能识别Yes和No两个单词。示例虽然简单,却“麻雀虽小,五脏俱全”。读者通过学习这个示例,可以了解创建语音识别系统的基本流程。当理解了这个示例后,读者将会发现,自己借助Kaldi也能够搭建一个简单的语音识别系统。
2.4.1 运行run.sh
这个示例无需修改就可以直接运行,包括数据的下载和整理、模型的训练、识别率的测试。所有脚本都在目录egs/yesno下。
首先我们来看一下这个目录的结构:
![](https://epubservercos.yuewen.com/7CD2F5/16992237104786406/epubprivate/OEBPS/Images/txt002_21.jpg?sign=1739691336-FLajYudyXr7Pvm9ElWKiojr8DLFg7Mqh-0-db58ccd754949327da5eaf36cbd5a63e)
可以看到,这个示例由若干Shell脚本、Perl脚本和一些文本文件构成。看到这么多文件,读者可能会不知从何入手。其实,Kaldi的所有示例,无论由多少个文件构成,都是以run.sh为入口的。各示例中的其他脚本和可执行程序,都是被run.sh直接或间接调用的。所以,直接执行run.sh就可以运行这个示例了。
![](https://epubservercos.yuewen.com/7CD2F5/16992237104786406/epubprivate/OEBPS/Images/txt002_22.jpg?sign=1739691336-v5GrcC4pW1NSbfgBdwZnnp1WAdK1uVh1-0-155d4ce0536f09f8e5feb9788f177710)
我们暂且不理会这个示例背后的原理,先看看执行结果。如果Kaldi被正确安装,那么运行run.sh后,屏幕上首先输出的信息是:
![](https://epubservercos.yuewen.com/7CD2F5/16992237104786406/epubprivate/OEBPS/Images/txt002_23.jpg?sign=1739691336-tRPwULv5rpbenzY1n7rZnBNqgnxJ6HKa-0-41e82dc8be8cf81a741b47382909af02)
上面的信息很容易理解,脚本从OpenSLR网站下载了一个名为waves_yesno.tar.gz的压缩包,这个压缩包就是这个示例所用的音频数据。
OpenSLR是Kaldi社区建立的一个用于存储语音和语言资源的网站,网站上提供了大量英语、汉语、西班牙语等语料,可以免费下载,可用于训练语音识别、语音合成、说话人识别等模型。
接下来屏幕显示了许多信息,这些信息对于不熟悉语音识别的读者来说很难理解。读者如果看不懂这些信息,可以暂时不用理会。
这个示例的数据集规模非常小,在普通硬件配置的计算机上,大约一两分钟,整个脚本就运行完毕了。
输出信息的最后一行是:
![](https://epubservercos.yuewen.com/7CD2F5/16992237104786406/epubprivate/OEBPS/Images/txt002_24.jpg?sign=1739691336-FNXFEBopTX1Do4rCZuKD7g4sQLOl6Uin-0-23dbca988c183db9a969c10e192d7447)
这就是测试结果了:WER为0.00。也就是说,总共测试了232个词,全部识别正确。
2.4.2 脚本解析
本节将解析刚才运行过的run.sh,帮助读者理解这个脚本所做的事情。
1)脚本的前两行设置了train_cmd和decode_cmd两个变量:
![](https://epubservercos.yuewen.com/7CD2F5/16992237104786406/epubprivate/OEBPS/Images/txt002_25.jpg?sign=1739691336-oBbiKSIEX9Tx6HDH3nY6FzQb3uL5Ee4E-0-5b41a38f15a247dfc4e889fec0536af2)
这两个变量在后面会用到,比如后面的:
![](https://epubservercos.yuewen.com/7CD2F5/16992237104786406/epubprivate/OEBPS/Images/txt002_26.jpg?sign=1739691336-Hp9Du4jABfuT6RWefvBhB6sHZ2t7WOWO-0-65cd2c300c2f52ee79f4db1b29ffe5a6)
以及
![](https://epubservercos.yuewen.com/7CD2F5/16992237104786406/epubprivate/OEBPS/Images/txt002_27.jpg?sign=1739691336-7KlM2GCh313YUH5wBm7GwVCdQVFqQskB-0-f7c3bcefeb73a6a8dcc6cfd315408497)
Kaldi的很多脚本,比如这个示例中要用到的steps/train_mono.sh和steps/decode.sh,都允许设置cmd参数。在本例中,cmd参数被设置成了utils/run.pl。
utils/run.pl这个Perl脚本的作用是多任务地执行某个程序。这是一个非常方便的工具,是可以独立于Kaldi之外使用的。这里用一个示例展示其用法:
![](https://epubservercos.yuewen.com/7CD2F5/16992237104786406/epubprivate/OEBPS/Images/txt002_28.jpg?sign=1739691336-8pAo0B1acLXYkuJMbqETkOkkDx7nveJz-0-2e2c9ee980dbb6a32462db11d9820854)
上面的命令同时执行了8个echo命令,并把屏幕显示输出分别写入/tmp/log.[1-8].txt这8个文本文件中。我们打开其中一个文件看一下:
![](https://epubservercos.yuewen.com/7CD2F5/16992237104786406/epubprivate/OEBPS/Images/txt002_29.jpg?sign=1739691336-rf9akD7qzLAzL2ar1Ptev0D56EM1SXp2-0-876c798b0f56e7aa5218f2a4403a2210)
可以看到,各个进程被分别执行,并将输出信息写入了不同的日志文件中。
Kaldi工具包中提供了utils/run.pl、utils/queue.pl和utils/slurm.pl作为cmd的可选工具,它们的命令行接口相同,任务所需的内存大小等选项也相同,不同之处在于run.pl在本地并行地执行命令,而queue.pl和slurm.pl把命令提交到计算集群上执行。
执行任务分发的Perl脚本名及其选项拼接在一起,作为cmd参数传入Kaldi的脚本中,然后Kaldi脚本使用cmd参数传入的Perl脚本来并行地执行程序。如果需要,读者也可以编写自己的任务分发脚本作为cmd的参数。
2)设置cmd参数后,脚本从OpenSLR网站下载数据并解压。
waves_yesno.tar.gz压缩包被解压后,除一个README文件外,就是很多WAV文件了。通常来说,用于训练语音识别模型的数据,除音频外,还需要有音频对应的文本。这个数据集由于情况简单,只包含YES和NO两个单词,因此这个数据集的提供者直接把文本标注写到了文件名中,用1代表YES,用0代表NO。比如,1_0_1_0_1_0_0_1.wav这个文件,其对应的文本就是:
![](https://epubservercos.yuewen.com/7CD2F5/16992237104786406/epubprivate/OEBPS/Images/txt002_30.jpg?sign=1739691336-Yh5zQ1ss2ZCWmOwrVdQs3AwFjY9MHz1J-0-0510f6abc8c7f9aef6716d3052dc04e6)
接下来,需要对数据进行整理。数据整理有两个目的,其一是把数据规范成Kaldi规定的数据文件夹格式,其二是划分训练集和测试集。run.sh中整理数据的脚本是:
![](https://epubservercos.yuewen.com/7CD2F5/16992237104786406/epubprivate/OEBPS/Images/txt002_31.jpg?sign=1739691336-Fcjtzna6B1Lcfn90aru7H0FkiVoVoagk-0-b661f84c440a19a85bfc942cc85d96fb)
执行这行脚本后,将生成data/train_yesno目录和data/test_yesno目录,分别作为这个示例的训练集和测试集。两个目录的结构完全相同:
![](https://epubservercos.yuewen.com/7CD2F5/16992237104786406/epubprivate/OEBPS/Images/txt002_32.jpg?sign=1739691336-Q9KkEHiWSSGkAZfYJ0IMqqekUV4gEFFd-0-4f90c5f2aef36924c8fea11611fa9a1d)
生成的这两个目录使用的是Kaldi的标准数据文件夹格式,我们查看一下这些文件的前几行:
![](https://epubservercos.yuewen.com/7CD2F5/16992237104786406/epubprivate/OEBPS/Images/txt002_33.jpg?sign=1739691336-iKdu6iFZfRp5RyZyas6C2haGRDsOllOf-0-f4020420c69122c8e350e8725554cc38)
每个句子都被指定了一个唯一的ID。wav.scp文件记录每个ID的音频文件路径,text文件记录每个ID的文本内容,spk2utt文件和utt2spk文件记录每个ID的说话人信息,本例中统一为global。
3)除下载数据外,还有一些资源需要手动准备。在这个示例中,这些资源已经由贡献者准备好了,在input路径下。
首先是发音词典lexicon.txt:
![](https://epubservercos.yuewen.com/7CD2F5/16992237104786406/epubprivate/OEBPS/Images/txt002_35.jpg?sign=1739691336-01LA80koTIdgRLViNqwZtQ6Smei1JXpQ-0-cd74fc9f2e6a99293cd3611427826d37)
lexicon.txt文件给出了YES、NO和<SIL>这三个单词的音素序列,其中<SIL>是一个特殊单词,表示静音。这里由于任务简单,每个单词都只用一个音素表示。lexicon_nosil.txt文件和lexicon.txt文件的内容相同,只是去掉了<SIL>行。
phones.txt文件给出了这个示例的音素集:
![](https://epubservercos.yuewen.com/7CD2F5/16992237104786406/epubprivate/OEBPS/Images/txt002_36.jpg?sign=1739691336-IUL9auq2dP8KQdtV0UOq2XEkveraMC6m-0-ca49c4d83b37fe03425862b59705821e)
其实phones.txt文件也可以从lexicon.txt文件中将所有音素去重得到。
task.arpabo是语言模型。本例中的语言模型不必训练,直接手工书写即可:
![](https://epubservercos.yuewen.com/7CD2F5/16992237104786406/epubprivate/OEBPS/Images/txt002_37.jpg?sign=1739691336-RWfDpThEvhEoz7zPw2sqPjnuXCviC1F3-0-22e71fc64261c3505864544577d97b53)
上面的语言模型定义了识别空间:只可能是Yes和No这两个单词,并且这两个单词出现的概率相同。关于语言模型的知识将在本书第5章中详细介绍。
4)数据文件夹生成后,就可以根据其中的文本信息,以及事先准备好的发音词典等文件,生成语言文件夹了。脚本如下:
![](https://epubservercos.yuewen.com/7CD2F5/16992237104786406/epubprivate/OEBPS/Images/txt002_38.jpg?sign=1739691336-y2T7rH3bGdpEIngvAiFC7o52JEj6JQV2-0-af39cdbfae7695180fa37c111651a55e)
前两行脚本读取input的资源文件,生成data/lang目录。这个目录是Kaldi标准的语言文件夹,存储了待识别语言的单词集、音素集等信息。第三行脚本把语言模型构建成图的形式,其细节将在本书第5章中介绍。
5)接下来是定义声学特征,这是训练声学模型的前提,脚本如下:
![](https://epubservercos.yuewen.com/7CD2F5/16992237104786406/epubprivate/OEBPS/Images/txt002_39.jpg?sign=1739691336-8yiNSK3i5Dpscif0Q3VaQF4D11eUldwX-0-4d7bee51598d801141f3cef079057f01)
脚本执行完毕后,train_yesno目录和test_yesno目录下将分别生成feats.scp文件,里面记录了每个ID的声学特征存储位置。
6)下面是声学模型训练和测试阶段。由于这个示例的任务比较简单,因此只需训练最简单的声学模型,脚本如下:
![](https://epubservercos.yuewen.com/7CD2F5/16992237104786406/epubprivate/OEBPS/Images/txt002_40.jpg?sign=1739691336-9jzZyaPErbyprgX9KQN682ql1ZtIN6qk-0-9c79f63959d36228a1b0f9a6ff674cfd)
脚本执行完毕后,声学模型被存储在exp/mono0a目录下。至此,模型训练完毕,进入测试识别阶段。识别的过程也被称作解码,解码前需要构建状态图:
![](https://epubservercos.yuewen.com/7CD2F5/16992237104786406/epubprivate/OEBPS/Images/txt002_41.jpg?sign=1739691336-qyho8vPBPHmAsZHahcvWXSoUKu0ZaUCE-0-7b88e5f131910bcdd3ca219d99d6e5b2)
本书将在第5章中详细讲解为何需要构建状态图及构建状态图的原理。构建状态图完毕后,调用Kaldi的解码器解码:
![](https://epubservercos.yuewen.com/7CD2F5/16992237104786406/epubprivate/OEBPS/Images/txt002_42.jpg?sign=1739691336-0fjwAfN2hitbhT1JuR3KYb3OzskMwmvy-0-187986eb827726a1a8828f7c56e8636f)
现在识别结果已经输出到exp/mono0a/decode_test_yes下面了。我们看一下识别结果:
![](https://epubservercos.yuewen.com/7CD2F5/16992237104786406/epubprivate/OEBPS/Images/txt002_43.jpg?sign=1739691336-hFhLu9u6cZg06lC2OotVDeQRS2VjO9BM-0-8dd1ebade308a4278d3f95a97df650eb)
这里我们只查看了exp/mono0a/decode_test_yes下的scoring_kaldi/penalty_0.0/10.txt文件。实际上,这个脚本输出了很多类似的识别结果文件,这些文件的区别是使用了不同的解码参数,其WER有微小的差异。
run.sh运行的最后,是寻找最好的解码器调参结果并输出:
![](https://epubservercos.yuewen.com/7CD2F5/16992237104786406/epubprivate/OEBPS/Images/txt002_44.jpg?sign=1739691336-EgCIcjKMg2HfEjiK0bc8z497P9zWUK3G-0-c3d7173b4cf0924e23388d330de1615c)
最终找到了最好的结果:scoring_kaldi/penalty_0.0/7.txt,WER为0.0%。
以上是对YesNo这个示例较顶层的介绍。YesNo示例是一个很好的用来入门的示例,但其声学模型训练过于简单,只训练了单音素的GMM模型,同时这个示例的发音词典的设置也不具备一般性。
从第3章起,本书主要使用Librispeech作为示例,这个示例是一个通用英文识别任务,使用近千小时的训练数据,是一个可以真正使用的语音识别系统。第3章~第6章将通过Librispeech示例,详细地介绍语音识别系统的模型训练及解码的流程与原理。有了YesNo示例作为基础,相信读者能够更容易地理解其他更复杂的示例流程及其背后的原理。