前面的博客里说过最近几个月我从传统语音(语音通讯)切到了智能语音(语音识别)。刚开始是学语音识别领域的基础知识,学了后把本身学到的写了PPT给组内同窗作了presentation(语音识别传统方法(GMM+HMM+NGRAM)概述)。一段时间后老板就布置了具体任务:在咱们公司本身的ARM芯片上基于kaldi搭建一个在线语音识别系统,三我的花三个月左右的时间完成。因为咱们都是语音识别领域的小白,要求能够低些,就用传统的GMM-HMM来实现。说实话接到这个任务咱们内心是有点没底的,不知道能不能按时完成,毕竟咱们对语音识别不熟,对kaldi不熟。既然任务下达了,硬着头皮也要上,并尽最大努力完成。我本能的先在网上用百度/google搜了搜,看有没有一些经验可供参考,好让咱们少走弯路。遗憾的是没搜到有价值的东西。没办法,咱们只能根据本身之前的经验摸索着前进。最终咱们按计划花了不到三个月的时间完成了嵌入式平台上在线语音识别系统的搭建。虽然只是demo,可是为后面真正作商用的产品打下了良好的基础,累积了很多的经验。今天我就把咱们怎么作的分享出来,给也想作相似产品的朋友作个参考。html
既然做为一个项目来作,就要有计划,分几个阶段完成这个项目。我在学习语音识别基础知识时对kaldi有一个简单的了解(在作语音识别前就已知kaldi的大名,没办法这几年人工智能(AI)太热了。智能语音做为人工智能的主要落地点之一,好多都是基于kaldi来实现的。我是作语音的,天然会关注这个热门领域的动态)。根据对kaldi的简单了解,我把项目分红了三个阶段,第一阶段是学习kaldi,对kaldi有一个更深的认识,同时搞清楚基于kaldi作方案后面有哪些事情要作,计划花一个月左右的时间完成。第二阶段是设计软件架构、写代码、训练模型等,也是花一个月左右的时间完成。第三阶段是调试,提高识别率,仍是花一个月左右的时间完成。计划的时间会根据实际状况作微调。web
1,第一阶段网络
第一阶段就是学习kaldi。因为咱们是三我的作这个项目,我就把学习任务分红三块:数据准备和MFCC、GMM-HMM模型训练、解码网络建立和解码。在其余两位同窗挑好模块后剩下的解码网络建立和解码就有我来学习了。学习过程就是看网上文章、看博客和看kaldi代码、脚本的过程。学完后你们搞清楚了后面有哪些事情要作,同时作了PPT给组内同窗讲,让你们共同提升。解码相关的见我前面的文章(基于WFST的语音识别解码器 )。Kaldi中解码有两种类型:offline(多用于模型调试等)和online(多用于在线识别等),其中online也有两种方式,一种是经过PortAudio从MIC采集语音数据作在线语音识别,另外一种是经过读音频WAV文件的方式作在线语音识别。咱们要作的是在线语音识别,这两个就是很好的参考,尤为是经过PortAudio从MIC采集方式的,颇有必要弄明白运行机制。因而我根据网上的博客基于thchs30搭建了PC上的在线识别来调试,基本上搞清楚了代码的运行机制。Kaldi中设定采样率为16kHZ,每帧25ms(其中帧移10ms),每27帧为一组集中作MFCC特征提取和解码,这样处理一组的语音时长是285ms(25+(27-1)*10=285),共4560(16*285=4560)个采样点。每次处理完一组后就从buffer中再取出一组作MFCC和解码,解码后看有没有识别的字出来,有的话就打印出来。多线程
2,第二阶段 架构
第一阶段主要是学习,第二阶段就要真正干活了。咱们在Linux上开发,先制定系统搭建完成后的目标:设备用数据线连在PC上,能在线实时识别英文数字0—9(选识别这些是由于网上有现成的英国人说的音频源,咱们能够省去录音频源的工做,好节约时间),即人对着设备说出英文数字0—9后PC屏幕上能实时打印出来,识别率接近GMM-HMM模型下的较好值。你们的任务仍是沿袭第一阶段的。学习数据准备和MFCC的同窗先数据准备相关的工做,如标注等,好给模型训练的同窗用,而后移植kaldi中MFCC相关的代码。学习模型训练的同窗先开始模型训练的准备工做,等要准备的数据好了后就开始训练。我负责整个软件架构的设计,同时还要把kaldi中的绝大部分(除了MFCC)移植进咱们系统中。经过对kaldi的学习,使我对怎么设计这个在线语音识别的软件架构有了更深的认识。语音识别分两个阶段,即训练阶段和识别阶段。训练阶段就是获得模型给识别阶段用。它相对独立,咱们就基于kaldi来训练模型,最终获得final.mdl等文件给识别阶段的软件用(在初始化时读取这些文件获得解码网络)。识别阶段的软件主要分两部分,声音采集和识别(包括特征提取和解码)。这样系统就有两个thread,一个是声音采集thread(audio capture thread),它基于ALSA来作,负责声音的采集和前处理(如噪声抑制),另外一个是识别thread(kaldi process thread),负责MFCC和解码。两个thread经过ring buffer交互数据,同时要注意数据的保护。这样系统的软件架构框图以下:框架
你们对软件架构讨论以为没什么问题后我就开始写代码搭建软件框架了。在 Linux中建立thread等都是一些套路活。Audio capture thread里先作初始化,包括ALSA的配置以及前处理模块的初始化等。而后就每隔必定时间经过ALSA_LIB的API完成一次音频数据的采集工做,读完数据后就作前处理,处理好后把音频数据放进ring buffer中,同时激活kaldi process thread,让kaldi process thread开始干活。Kaldi thread也是先作一些初始化的工做,而后睡下去等待激活。激活后先从ring buffer里取语音数据,而后作MFCC和decoder。完成后又睡下去等待下次再被激活。搭建软件框架时kaldi相关的代码还没被移植进去,kaldi process thread里仅仅把从ring buffer里拿到的语音数据写进PCM文件,而后用CoolEdit听,声音正常就说明软件框架基本成型了。刚开始时audio capture thread里也没加前处理模块,调试时把从ALSA里获取的数据写进PCM文件听后发现有噪声,就加了噪声抑制(ANS)模块。这个模块用的是webRTC里的。webRTC里的三大前处理模块(AEC/ANS/AGC)几年前我就用过,此次拿过来简单处理一下就用好了,去噪效果也挺好的。ANS一个loop是10ms,而前面说过kaldi里在线识别解码一次处理一组27帧是285ms,我就取二者的最小公倍数570ms做为audio capture thread的loop时间。从ALSA取到语音数据后分57(570/10 = 57)次作噪声抑制,再把抑制后的语音数据写进ring buffer。Kaldi thread激活后仍是每次取出285ms语音数据作处理,只不过要取两次(570/285 = 2)。函数
软件架构搭好后就开始移植kaldi代码了。Kaldi代码量大,不可能也不必所有移植到咱们系统里,只须要移植咱们须要的就能够了。怎样才能移植咱们须要的代码呢?考虑后我用了以下的方法:先把在线解码相关的代码移植进去,而后开始不停的编译,报什么错提示缺什么就加什么,直到编译经过。这种方法保证了把须要的文件都移植进系统了,但有可能某些文件中的函数没用到,即到文件级还没到函数级。因为时间紧,这个问题就暂时无论了。移植过程更多的是一个体力活,须要当心细致。在移植过程当中遇到问题就去网上搜,最后都圆满解决了。Kaldi主要用到了三个开源库:openfst、BLAS、LAPACK。BLAS和LAPACK我用的常规方法,即到官网上下载编译后生成库,而后把库和头文件放到系统的”/usr/lib”和“/use/include”下,让其余代码用。kaldi支持的有BALS库有 ATLAS / CLAPACK / openBLAS / MKL等。在X86的Ubuntu PC上跑kaldi时就用的Intel的MKL,在ARM上就不能用了,须要用其余的几种之一。我评估下来用了openBLAS,主要由于三点:1)它是BSD的;2)它支持多种架构(ARM/X86/MIPS/….),是开源库里性能最好的(各类架构里都嵌了不少的汇编代码),被多家著名公司使用,如IBM/ARM/nvidia/huawei等;3)它有多个编译选项可供选择,好比单线程/多线程选择、设定线程数等。BLAS的早期代码都是用fortran写的,后来用C对其进行了封装,因此系统还要加上对fortran的支持。对openFST,我发现用到的代码并很少,也就没用常规的方法,而是直接把用到的代码移植进系统。我移植好编译没问题后另外一个同窗把剩下的MFCC以及和ALSA接口(用ALSA接口替代kaldi里的PortAudio接口)相关的也移植进去了。这样移植工做就算结束了。对比了下移植进系统的kaldi代码和kaldi里SRC下的代码,应该是只用了其中一小部分。下图显示了移植进系统的kaldi文件(没列出相关的头文件)。同时负责模型训练的同窗也有了一个初步的模型生成的文件,把这些文件放进系统里就能够跑起来了,人说话后PC屏幕上就有词打印出来,不过不正确。这也正常呀,由于还没调试呢!oop
3,第三阶段post
第三阶段就是调试。第二阶段结束后说话就有词出来,但都是错的,须要排查定位问题。在线语音识别系统从大的角度能够分两块:模型和代码实现。首先咱们须要定位是模型的问题仍是代码实现的问题,先从模型排查。在第一阶段时利用thchs30大体搞清楚了在线解码的机制,是用模型tri1调的,当时识别率不好。如今要关注识别率了,把模型换成了tri2b,识别率有所提升。这说明kaldi里的在线解码的代码是没有问题的,识别率差问题出在模型。何况全球这么多人在用kaldi,若是在线解码有问题应该早就fix了。因此咱们决定把咱们生成的模型文件放进thchs30里来验证模型是否有问题。为了排除从MIC输入的音频数据有噪声等的干扰,先用读文件的方式验证。把咱们的模型文件放进去后发现基本识别不正确,这说明模型是有问题的。负责模型的同窗去调查,发现用于训练的音源都是8K采样的,可是在线解码用的都是16K采样的,这是咱们本身挖的坑,用重采样程序把8K的所有转成16K的,这个坑也就填好了,可是识别率依旧很差。又发现训练集全是英国人的发音,而测试集是咱们中国人的发音,有必定口音的,最好用咱们中国人本身的发音做为训练集。因而咱们本身又录了用于训练的音源,为了加大训练的数据,又请好多其余人录了音源。训练后获得了新的模型,再放到thchs30里面验证,识别率有六七成了,这说明模型的大方向对了,为了提升识别率,模型还须要继续调试。性能
接下来就要看代码部分是否有问题了。把新生产的模型放进咱们本身的系统,而且用从音频文件都数据的方式(咱们的系统既能够从MIC采集数据也能够从音频文件读数据,从音频文件读数据是为了debug)来替代从MIC采集到的数据(这样作是为了排除噪声等因素的干扰)来看代码是否有问题。运行下来发现识别率依旧不好,这说明咱们的代码也是有问题的。在第二阶段我已经调试过部分代码,确保了在kaldi process thread里从PCM ring buffer里拿到的音频数据是没有问题的。还有两方面须要调试,一是送进MFCC的PCM数据要是OK的,二是咱们的在线解码机制要跟kaldi里的在线解码机制彻底同样。一很快就调试好了。二是先再深刻研究吃透kaldi里的在线解码机制,改正咱们与它不同的地方,通过两三天调试后识别率跟thchs30里的差很少了,这说明咱们的代码通过调试后也有一个好的base了,后面就要开始调性能了。
前面是经过从音频文件中读取数据来作在线识别的,数据相对干净些。如今要从MIC读取音频数据作真正在线识别了,试下来后识别率明显偏低,这说明咱们的前处理还没彻底作好(前面调试时只加了ANS模块)。我把前处理后的音频数据dump出来用CoolEdit听,的确有时候音质很差,因而我又把webRTC中的AGC模块加上去,再次dump出前处理后的音频数据听,屡次听后都感受音质正常。再来运行加了AGC后的从MIC采集音频数据的在线识别,识别率果真有了明显的提高。前处理能作的都作了,要想再提升识别率,就要靠模型发力了。作模型的同窗一边请更多的人录音源来训练,一边尝试各类模型,最终用的是tri4b,有了一个相对不错的识别率。因为咱们用的是GMM-HMM,现在主流的语音识别中已再也不使用,老板就以为没有必要再调了,后面确定会用主流的模型的,可是整个嵌入式上的在线语音识别软件代码尤为软件架构和音频采集仍是有用的,后面就要基于这些代码作真正的产品。
对语音识别领域的资深人士来讲,这个嵌入式在线语音识别系统还很稚嫩。但经过搭这个系统,让咱们对语音识别领域有了多一点的感性认识,也有了一个良好的开端,给老板以信心,而且能够继续作下去。此次工程上的事情偏多,后面但愿更深刻的作下去,累积更多的语音识别领域的经验。搭这个系统没有任何可供参考的资料,纯粹是根据咱们以往的经验摸索着搭出来的。作的产品可能不同,但不少解决问题的思路都是同样的。若是有朋友也搭过嵌入式上的在线语音识别系统,欢迎探讨,搭出一个更好的在线语音识别系统。