三個小白是如何在三個月內搭一個基於kaldi的嵌入式在線語音識別系統的

前面的博客裏說過最近幾個月我從傳統語音(語音通信)切到了智能語音(語音識別)。剛開始是學語音識別領域的基礎知識,學了後把自己學到的寫了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,如今主流的語音識別中已不再使用,老闆就覺得沒有必要再調了,後面肯定會用主流的模型的,但是整個嵌入式上的在線語音識別軟件代碼尤其軟件架構和音頻採集還是有用的,後面就要基於這些代碼做真正的產品。

 

對語音識別領域的資深人士來說,這個嵌入式在線語音識別系統還很稚嫩。但通過搭這個系統,讓我們對語音識別領域有了多一點的感性認識,也有了一個良好的開端,給老闆以信心,並且可以繼續做下去。這次工程上的事情偏多,後面希望更深入的做下去,累積更多的語音識別領域的經驗。搭這個系統沒有任何可供參考的資料,純粹是根據我們以往的經驗摸索着搭出來的。做的產品可能不一樣,但很多解決問題的思路都是一樣的。如果有朋友也搭過嵌入式上的在線語音識別系統,歡迎探討,搭出一個更好的在線語音識別系統。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章