我們基於kaldi開發的嵌入式語音識別系統升級成深度學習啦

先前的文章《三個小白是如何在三個月內搭一個基於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年自己在這個領域進步再大一點。

發佈了39 篇原創文章 · 獲贊 79 · 訪問量 6萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章