我们基于kaldi开发的嵌入式语音识别系统升级成深度学习啦

article/2025/11/8 19:00:54

先前的文章《三个小白是如何在三个月内搭一个基于kaldi的嵌入式在线语音识别系统的 》说我们花了不到三个月的时间搭了一个基于kaldi的嵌入式语音识别系统,不过它是基于传统的GMM-HMM的,是给我们练手用的,通过搭这个系统我们累积了一定的语音识别领域的经验,接下来我们就要考虑做什么形态的产品了。语音识别可以分大词汇量连续语音识别(Large Vocabulary Continuous Speech Recognition, LVCSR)和关键词识别(Keyword Spotting, KWS)。LVCSR 要求很强的计算能力,这类方案主要在服务器上实现。KWS只要识别出关键词即可,对算力要求不是很高,可以在终端芯片上实现。由于我们公司的芯片主要用于终端产品上,算力不是很强,因此我们就准备做关键词识别。对于关键词识别又可分为几种应用场景。一是音频文献中关键词检索,用于快速找到音频文献中需要的内容。二是语音唤醒词识别,用于唤醒终端设备,让其工作(不唤醒时设备处于睡眠状态)。三是命令词识别,用于语音命令控制的场景,终端设备收到某个命令词后就执行相应的操作。比如智能家居场景中,当用户说出“打开空调”被识别到后就把空调打开了。经过讨论后我们决定做中文命令词识别,暂时把应用场景定在智能家居上,并定义了几个命令词,例如“打开空调”、“关闭空调”等。后面如果要做其他场景,只要改变命令词重新训练模型即可,代码部分是不需要改动的。

 

先前的系统是基于GMM-HMM的,已out,我们想用深度神经网络(Deep Neural Networks,DNN)来做。kaldi中的DNN分为nnet1、nnet2、nnet3三种。nnet1是由Karel写的,使用的是DNN-HMM架构,这里DNN说白了就是MLP(MultiLayer Perceptron,多层感知机)。nnet2和nnet3是由Daniel写的,nnet2同样使用的是DNN-HMM架构,nnet3还包含了其他网络架构,有CNN/RNN/LSTM等。nnet1没有online decoder,nnet2和nnet3则有online decoder,比较下来我们决定用nnet2。DNN-HMM是基于GMM-HMM的,是用DNN替代GMM,因而我们前面的工作还可以用得上,所以这次的工作主要分两部分,一是模型训练,二是nnet2 online decoder相关代码的移植。上次负责模型训练的同学由于忙其他工作,这次模型训练就由我来做。nnet2 online decoder代码移植由另外一个同学负责。同时我们在前处理中把VAD(Voice Activity Detection,语音活动检测)加上,只把检测到语音的部分送到后面模块处理,这样降低了功耗。

 

这次我来弄模型训练。由于是新手,先得学习怎么训练模型,然后根据新的需求训练出新的模型。经过半个多月的学习,大体上搞清楚了模型训练的步骤。首先是数据准备,包括准备语料、字典和语言模型等。对于语料,可以花钱买,也可以自己录,要将其分成训练集、测试集和交叉验证集。字典表示一个词是由哪些音素组成的。语言模型通过专业的工具(如srilm的ngram-count)生成。然后处理语料得到scp/spk2utt/utt2spk等文件,处理字典、语言模型等得到FST等文件。再就是做MFCC得到每一帧的特征向量,最后进行各个阶段的训练得到相应的模型文件(final.mdl)。主要的阶段有单音素训练(mono)、三音素训练(tri1)、LDA_MLLT训练(tri2b)、SAT训练(tri3b)、quick训练(tri4b),每一步训练都是基于上一步训练解码后对齐的结果。上面这几步是GMM-HMM的训练,如果要做深度神经网络(DNN)的训练,则还要把DNN训练这步加上去。我们这次做的是中文命令词的识别,先定好命令词,然后从thchs30里找到这些词的声韵母的写法,需要注意的是thchs30里声韵母的写法跟通常拼音的写法有些不一样,再根据这些命令词用工具把语言模型生成。我们的语料是自己录的,发动了周围的同学帮忙录,有男声和女声。这些都准备好后先处理语料得到scp等文件,再根据字典、语言模型等生成fst等文件,最后就开始各个阶段的训练了。先训练传统的GMM-HMM,不断的调整参数,直至WER有一个不错的值。GMM-HMM模型训练好后我把模型load进我们先前搭好的demo,实测下来效果还不错。这说明GMM-HMM的模型训练是OK的,接下来就要开始训练DNN(nnet2)的模型了。

 

 

我没有立刻去训练nnet2的模型,而是再去学习了下DNN的基础知识(以前简单学习过,一直没用到,理解的不深,有些已经忘记了),重点关注了梯度下降法和网络参数怎么更新,并写成了两篇博客:《机器学习中梯度下降法原理及用其解决线性回归问题的C语言实现 》& 《kaldi中CD-DNN-HMM网络参数更新公式手写推导》。接下来就去看怎么训练nnet2的模型了。先到kaldi的官方网站上看训练nnet2的相关内容,大致明白就开始基于我们自己录制的语料库调试了。nnet2的训练脚本较乱,一个脚本下有多个版本(nnet4a / nnet4b / nnet4c / nnet4d / nnet5c / nnet5d)。我刚开始不清楚孰优孰劣,把每个都调通。在网上搜索调查了一下,kaldi的作者Daniel Povey在一个论坛里说隐藏层用p-norm做激活函数的性能更好一些。于是决定用推荐的nnet4d(激活函数就是用的p-norm)来继续训练。经过多次参数tuning后得到了一个WER相对不错的模型。

 

 

在我训练DNN模型的同时,负责代码移植的同学也在把nnet2 online decoder的相关代码往我们平台上移植,套路跟我先前的一样。同时kaldi也提供了一个应用程序(代码见online2-wav-nnet2-latgen-faster.cc),对WAV文件做nnet2的online decoder。我们先要把模型在这个应用程序上调通(通常kaldi代码是没有问题的,我们在这个应用程序里调通就说明模型训练是没有问题的,后面在我们自己的平台上去调试就有基准可参考了)。当我们把模型放进应用程序里运行,报了“Feature dimension is 113 but network expects 40”的错。调查下来发现kaldi应用程序要求MFCC是13维的,且有i-vector的功能(100维的),这样加起来就是113维的。而我训练的nnet2模型是基于tri3b的(DNN-HMM要利用GMM-HMM的训练解码对齐结果,对齐的越好DNN模型的识别率就越高),13维MFCC+26维delta+1维pitch,共40维,所以模型输入是40维的。讨论后为了降低复杂度,我们决定先把应用程序中的i-vector功能给去掉,同时我基于单音素的模型(13维MFCC)重新训练nnet2模型。基于新的模型运行应用程序不报错了,但是识别率很低。我们一时没有了方向,做了几次尝试还是识别率很低。后来我们开始比较我的训练处理流程和应用程序里的处理流程,发现我训练时用了CMVN(以前做GMM-HMM训练时就有),而应用程序代码处理流程里没有。于是在代码里把CMVN的处理加上,再去运行应用程序,识别率显著提升了。我们长舒了一口气,因为我们知道这个问题被解决了,从而心里有底了。再把应用程序的机制移植到我们平台上,同时另外一个同学也帮忙把webRTC的VAD也移植进来,有语音才会把那段语音往后面模块送,这跟应用程序中读WAV文件很类似,所以处理起来机制就很类似。用了两三天就把包含VAD、前处理(ANS、AGC)和nnet2 online decoder的系统联调好了。测试了一下,被训练过的人说命令词识别率大于90%,而未被训练过的识别率大于80%。但是有个严重的问题,就是集外词(out-of-vocabulary,OOV,就是命令词以外的词)都会被识别成一个集内词(命令词),即集外词没有被拒识。

 

 

针对这个问题,我查了些资料并静下心来想了想,在当前架构下说出一个词,只会以WFST中路径最短的一个作为识别结果输出,所以才会有集外词被识别成了集内词。我们的系统目前只能识别那些指定的关键词,但是还不具备关键词识别系统的任何特点。我在前面的文章《语音识别中唤醒技术调研》 中曾总结过实现关键词识别的三种方法,一是基于LVCSR来做,在终端芯片上不太可行。二是keyword/filler方法,说白了就是把一些垃圾词也放进模型里去训练(大意如下图),识别时说集外词很大可能是垃圾词作为识别结果而不输出从而实现集外词拒识,在终端芯片上可行。三是纯深度学习方法(相对于3,1和2是传统方法)。我们的架构是DNN-HMM,虽然也用了深度神经网络,但DNN是用来替代GMM的,本质上还是一种传统方法,所以我决定把keyword/filler方法用到我们的系统上。先从thchs30里找到几百个集外词(垃圾词),然后根据这些词录制语料并放进模型里训练。用新生成的模型去测试,集外词拒识率大幅提高,但是一些情况下集外词还是被识别为集内词。例如关键词是“深度科技”,如说成“深度科学”就有可能被识别成“深度科技”。针对这种情况,我把相关的词(常用的)都放进垃圾词里,如“深度科学”、“深度科普”、“深度科研”等,再去测试这些词就不会被识别成集内词了。再例如一些词发音跟集内词发音很相似,比如说“深度科器”会被识别成“深度科技”,我试了试百度的小度音箱,把唤醒词“小度小度”说成“角度角度”或者“巧度巧度”,小度音箱依旧会被唤醒。市面上已大规模商用的产品都有这个现象,我也就没管它。与此同时,我还在看一些集外词拒识的相关论文,发现好多都结合用置信度(conference measure)来解决这个问题,其中中科院自动化所的一篇博士论文《语音识别中的置信度研究与应用》讲的比较好。看后我明白了要想在传统架构下把集外词拒识问题解决好,一是要用上keyword/filler方法,二是要用上置信度。目前我是没有能力根据论文去实现置信度的,也没有找到开源的关于置信度的实现,于是在kaldi WFST lattice代码里想办法。通过大量的集内词和集外词的测试我发现可以用一些变量去做判断,但是有可能集外词拒识率提高了,集内词识别率也下降了(用置信度也会有同样的问题,这个度很难掌控。这块内容也是挺难的,尤其对我一个做工程的且做语音识别没多久的来说) 。经过一段时间的努力后集内词的识别率和集外词的拒识率都有了一个相当的水准,但离商用还有一段距离,后面还有很多事情要做,比如加大语料(我们目前只有一个几十人的语料库,没有好几百人并且覆盖男女以及不同年龄段的语料库是不能商用的),后面会越来越难!

 

 

 

马上就2019年过去了。回首这一年,三分之二的时间都用来做语音识别了,全是摸索着向前走,有痛苦,也有欢乐,从最初的什么都不懂到现在的懂一点。希望2020年自己在这个领域进步再大一点。


http://chatgpt.dhexx.cn/article/jk9h2Odf.shtml

相关文章

三个小白是如何在三个月内搭一个基于kaldi的嵌入式在线语音识别系统的

前面的博客里说过最近几个月我从传统语音(语音通信)切到了智能语音(语音识别)。刚开始是学语音识别领域的基础知识,学了后把自己学到的写了PPT给组内同学做了presentation(语音识别传统方法(GMMHMMNGRAM)概…

【毕业设计】基于stm32的语音识别 - 单片机 嵌入式 物联网 语音识别

文章目录 0 简介1 项目目标2 项目背景意义3 需求分析3.1 功能性需求3.2 非功能性需求 4 设备器件4.1 STM32单片机4.2 LD3320语音识别芯片4.3 TFT-LCD液晶显示屏 5 设计方案6 语音识别模块的开发7 报警电路模块的开发8 上位机模块的开发9 项目成果9 最后 0 简介 Hi,…

基于80251的嵌入式语音识别

一、文档介绍 嵌入式语音识别技术在251内核的实现。 缩写、术语 解 释 Specific Person Isolated Word Speech Recognition 特定人孤立词语音识别 Endpoint detection 端点检测 Feature parameter extraction 特征参数提取 DTW (Dynamic Time Warping) 动态时间规整…

嵌入式linux 声控,基于Cortex-A9的嵌入式语音识别系统设计

打开文本图片集 摘 要:在人工智能技术发展的进程中,语音识别已经成为重要分支之一。语音识别技术作为人与机器的一种交互方式,将前端处理之后的信号转换成文本或者指令,进而用于控制相关智能设备。当前,基于深度学习的云端语音识别技术已经逐渐成熟,但考虑到离线设备无法…

《嵌入式 - 语音识别TWen-ASR-ONE开发笔记》第3章 TWen-ASR-ONE 多线程和消息队列

3.1 Scratch简介 在开始本章之前,需要介绍下TWen-ASR-ONE的用户开发语言-Scratch,Scratch是麻省理工学院开发的一款简易图形化编程工具主要是为青少年开发的一种编程工具,它图形化编程工具当中最广为人知的一种形式,所有人都可以…

Android导航语音识别——语音听写(嵌入式)

嵌入式项目之Android导航语音识别——语音听写 文章目录 系列文章目录前言一、原理流程图二、语音听写 1.初始化无ui识别听写2.设置识别监听器3.启动功能总结 前言 最近在学习嵌入式系统的一个功能——导航语音识别,该功能是基于Android和科大讯飞语音识别语音合成…

static的三个作用

static作用 一、修饰局部变量-静态局部变量二、修饰全局变量-静态全局变量三、修饰函数-静态函数四、总结和注意点 一、修饰局部变量-静态局部变量 static修饰局部变量时,改变了变量的生命周期,让静态局部变量出了作用域依然存在,到程序结束&…

java中的静态变量的作用域_详解JAVA中static的作用

1、深度总结 引用一位网友的话,说的非常好,如果别人问你static的作用;如果你说静态修饰 类的属性 和 类的方法 别人认为你是合格的;如果是说 可以构成 静态代码块,那别人认为你还可以; 如果你说可以构成 静态内部类, 那别人认为你不错;如果你说了静态导包,那别人认为你…

express.static 作用及用法

express相信是很多人用nodejs搭建服务器的首选框架,相关教程有很多,也教会了大家来如何使用。如果你想更深的了解他的细节,不妨和我一起来研究一下。 先来看一个每个人都用到的方法app.use(express.static(__dirname/public)); 你肯定在你的a…

java static的作用

static是什么 static是java中的关键字,static表示”全局”、“静态”的意思,用来修饰成员变量和成员方法,定义的变量,我们通常将用static修饰的成员称为静态变量,静态方法或者静态成员。 static的作用 被static修饰…

static的作用有哪些?

C语言中的static的作用有:1.修饰局部变量。2.修饰全局变量。3.修饰函数。 1.修饰局部变量。 如图: 当只用int 定义变量a时,此时程序运行结果为: 程序运行结果为10个2,这是为什么呢?? 原因是此时的变量a为…

C语言中 static作用

在C语言中,static的字面意思很容易把我们导入歧途,其实它的作用有三条。隐藏保持数据持久初始化为0 该图是下面文字的总结: 1.先来介绍它的第一条也是最重要的一条:隐藏。 当我们同时编译多个文件时,所有未加static前…

C语言中static作用

1,static修饰局部变量 在局部变量之前加上关键字static,局部变量就被定义成为一个局部静态变量。 特点如下: 1)存储区:由于栈变为静态存储区rw data,生存期为整个源程序,只能在定义该变量的函数内使用。退出该函…

对DSP的基本了解(一)--DSP是什么?

Preface:在学习一个新东西之前,我们需要对一些基本概念或者专业术语进行基本的理解,这样才能扫除壁垒,让学习更加高效。因此,DSP学习系列首先会比较注重对DSP相关术语上的理解。所以最开始,我们先来学习一些…

DSP学习

DSP数字电源学习一 软件环境搭建相关硬件资源TMS320C28x 32 位 CPU可编程控制律加速器 (CLA)片上存储器时钟和系统控制1.2V 内核、3.3V I/O 设计系统外设通信外设模拟系统 软件相关控制算法数据记录功能代码示例MathWorks Simulink 模型文档 由于最近工作需要,需要学…

DSP/BIOS详解入门

DSP/BIOS详解入门 百度百科DSPBIOS DSP/BIOS是TI公司特别为其TMS320C6000TM,TMS320C5000TM和TMS320C28xTM系列DSP平台所设计开发的一个尺寸可裁剪的实时多任务操作系统内核,是TI公司的Code Composer StudioTM开发工具的组成部分之一。 DSP/BIOS 实时操作…

DSP基础知识回顾

文章目录 1. 什么是DSP2. DSP与其他主流芯片的区别2.1 DSP与MCU的区别2.2 DSP与ARM的区别2.3 DSP与FPGA的区别 3. TI公司DSP具体型号的含义4. TMS320x2814的片内外设4.1 事件管理器4.2 模拟量转换为数字量的ADC采样模块4.3 串行通信接口SCI4.4 串行外围设备接口SPI4.5 局域网通…

DSP学习 -- 前言

已经换工作20天了,感觉又是换行业了一样。跳入DSP和FPGA的大坑了。 除了C语言基础还算可以,其他要学的东西太多了。 也是,终于可以又回到那种能学习和吸取新鲜知识的节奏了。相对来说虽然很累,但是我还是比较喜欢这种状态。总比吃…

最强ADI SHARC系列DSP:ADSP-SC589的开发入门详解

作者的话 ADI的SHARC系列DSP,现阶段最强的型号是SC589,那么我就以SC589为例,写一点资料,让新手能够比较便捷的熟悉开发环境,软硬件,顺利的进入开发。 文档解决的是入门的问题,所以不会深入去写…

嵌入式开发之DSP学习

一、ARM、DSP、FPGA ARM具有比较强的事务管理功能,可以用来跑界面以及应用程序等,其优势主要体现在控制方面;ARM是32位的单片机,其内部硬件资源的性能较高,可以加载操作系统成为其主要特点,有了操作系统&am…