前面的博客里说过最近几个月我从传统语音 (语音通信) 切到了智能语音 (语音识别). 刚开始是学语音识别领域的基础知识, 学了后把自己学到的写了 PPT 给组内同学做了 presentation(语音识别传统方法(GMM+HMM+NGRAM) 概述). 一段时间后老板就布置了具体任务: 在我们公司自己的 ARM 芯片上基于 kaldi 搭建一个在线语音识别系统, 三个人花三个月左右的时间完成. 由于我们都是语音识别领域的小白, 要求可以低些, 就用传统的 GMM-HMM 来实现. 说实话接到这个任务我们心里是有点没底的, 不知道能不能按时完成, 毕竟我们对语音识别不熟, 对 kaldi 不熟. 既然任务下达了, 硬着头皮也要上, 并尽最大努力完成. 我本能的先在网上用百度 / google 搜了搜, 看有没有一些经验可供参考, 好让我们少走弯路. 遗憾的是没搜到有价值的东西. 没办法, 我们只能根据自己以前的经验摸索着前进. 最终我们按计划花了不到三个月的时间完成了嵌入式平台上在线语音识别系统的搭建. 虽然只是 demo, 但是为后面真正做商用的产品打下了良好的基础, 累积了不少的经验. 今天我就把我们怎么做的分享出来, 给也想做类似产品的朋友做个参考.
既然作为一个项目来做, 就要有计划, 分几个阶段完成这个项目. 我在学习语音识别基础知识时对 kaldi 有一个简单的了解 (在做语音识别前就已知 kaldi 的大名, 没办法这几年人工智能(AI) 太热了. 智能语音作为人工智能的主要落地点之一, 好多都是基于 kaldi 来实现的. 我是做语音的, 自然会关注这个热门领域的动态). 根据对 kaldi 的简单了解, 我把项目分成了三个阶段, 第一阶段是学习 kaldi, 对 kaldi 有一个更深的认识, 同时搞清楚基于 kaldi 做方案后面有哪些事情要做, 计划花一个月左右的时间完成. 第二阶段是设计软件架构, 写代码, 训练模型等, 也是花一个月左右的时间完成. 第三阶段是调试, 提升识别率, 还是花一个月左右的时间完成. 计划的时间会根据实际情况做微调.
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 屏幕上就有词打印出来, 不过不正确. 这也正常呀, 因为还没调试呢!
3, 第三阶段
第三阶段就是调试. 第二阶段结束后说话就有词出来, 但都是错的, 需要排查定位问题. 在线语音识别系统从大的角度可以分两块: 模型和代码实现. 首先我们需要定位是模型的问题还是代码实现的问题, 先从模型排查. 在第一阶段时利用 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, 如今主流的语音识别中已不再使用, 老板就觉得没有必要再调了, 后面肯定会用主流的模型的, 但是整个嵌入式上的在线语音识别软件代码尤其软件架构和音频采集还是有用的, 后面就要基于这些代码做真正的产品.
对语音识别领域的资深人士来说, 这个嵌入式在线语音识别系统还很稚嫩. 但通过搭这个系统, 让我们对语音识别领域有了多一点的感性认识, 也有了一个良好的开端, 给老板以信心, 并且可以继续做下去. 这次工程上的事情偏多, 后面希望更深入的做下去, 累积更多的语音识别领域的经验. 搭这个系统没有任何可供参考的资料, 纯粹是根据我们以往的经验摸索着搭出来的. 做的产品可能不一样, 但很多解决问题的思路都是一样的. 如果有朋友也搭过嵌入式上的在线语音识别系统, 欢迎探讨, 搭出一个更好的在线语音识别系统.
来源: https://www.cnblogs.com/talkaudiodev/p/11240033.html