歡迎你們前往騰訊雲+社區,獲取更多騰訊海量技術實踐乾貨哦~javascript
通常沒有網絡時,語音識別是這樣的算法
▽性能優化
而同等環境下,嵌入式語音識別,是這樣的微信
▽網絡
不只能夠幫您邊說邊識、出口成章,有個性化名字的時候也難不倒它。數據結構
這就是嵌入式語音識別的魅力。機器學習
本文將從微信智聆的嵌入式語音識別引擎的實現和優化,函數
介紹嵌入式語音識別的技術選型。oop
01
語音識別,大致是這麼來的
語音識別,能讓機器「聽懂」人類的語音,把說話內容識別爲對應文本。
開始於上世紀50年代
從最初的小詞量孤立識別系統
到現在的大詞量連續識別系統
語音識別系統的發展,性能獲得顯著的提高,主要得利於如下幾個方面:
大數據時代的到來
深度神經網絡在語音識別中的應用
GPU硬件的發展
所以,語音識別逐步走向實用化和產品化
語音輸入法,語音智能助手,語音車載交互系統……
能夠說,語音識別是人類征服人工智能的前沿陣地,是目前機器翻譯、天然語言理解、人機交互等的奠定石。
然而,性能的提高基於服務端CPU/GPU高計算能力和大內存,沒有網絡的時候將沒法享受語音識別的便利。
爲了解決這個問題,微信智聆針對嵌入式語音識別進行研發。嵌入式語音識別,也稱爲嵌入式LVCSR(或離線LVCSR,Large Vocabulary Continuous Speech Recognition),指全程運行在手機端的語音識別,而不依賴於服務端強大的計算能力。
在一些網絡不穩的特殊場景(車載、境外等等),嵌入式語音識別可「曲線救國」。
那麼,實現一個嵌入式語音識別,存在哪些難點呢?
語音識別的基本流程
主流的語音識別算法當中,包括聲學和語言兩大模型。聲學模型得利於近十年深度學習的發展,從GMM(高斯模型)到DNN(深度神經網絡),再從DNN到LSTM RNN(循環神經網絡),識別率不斷提高的同時,計算量也不斷地飛漲。而語言模型經常使用的n-gram算法,階數越高性能越好,經常使用的模型多達數十G的內存。
因此綜合起來,嵌入式語音識別有如下幾個難點:
\1. 深度學習運算複雜,僅僅對模型進行裁剪性能損失大,需尋找挽回性能的方法;
\2. 裁剪模型不可避免,在模型訓練環節如何避免小模型訓練易陷入局部最優的問題;
\3. 如何計算的更快,知足嵌入式的CPU環境;
\4. 如何組織語言模型存儲,能在有限的內存下存儲更多的語言信息。
本文將以語音識別的技術原理出發,淺談微信智聆嵌入式的實現技術。
內容將分爲四個部分:
\1. 回顧語音識別的基本概念;
\2. 簡單介紹在速度和內存優化上咱們作的部分工做,側重於工程應用實現;
\3. 說一說爲了更好的性能咱們作了哪些事,側重於算法研究介紹;
\4. 咱們進行實驗對比,最後咱們進行總結。
02
語音識別的各個組件
語音識別「黑盒」
語音識別從輸入錄音輸出文字,黑盒子處理通過特徵提取、聲學模型、發音詞典、語言模型等流程,筆者認爲能夠把語音識別比做一臺計算機。
特徵提取至關因而路由器,做爲領頭羊給後續環節提供源源不斷的數據來源。
聲學模型至關於語音識別的心臟——CPU,他將最直接影響着識別的準確性能。
語言模型至關於語音識別的硬盤,大量的詞彙組合信息存儲於此。
發音詞典至關於內存條,能有效組織聲學模型與語言模型的關係。
除此以外,語音識別包含一個解碼器,他如同計算機的操做系統,有效地組織着各個環節。
接下來,咱們基於每一個「部件」簡介其基本概念,以便後續介紹如何在這些「部件」上對嵌入式ASR工做的展開。
1.特徵提取
音識別特徵提取包括預加劇、分幀、加窗、FFT(Fast Fourier Transform)等一系列流程,經常使用的特徵有PLP、MFCC、FBANK等等。通常來講,語音識別把一秒語音分紅100段(之間有互相重疊),而特徵提取能把每段語音數據轉化爲一個向量(常見的有39維MFCC特徵)。
爲了關聯上下文信息,特徵做爲聲學模型的輸入時,常將相鄰幀拼湊一塊兒。好比以39維特徵爲例,先後各取5幀信息,那麼總共有11幀,輸入的向量維度爲11*39=429。通常地,語音識別的性能與取幀寬度是正相關的。
做爲語音識別的路由器,特徵提取環節的運算量並不大。然而其做爲聲學模型拓撲結構的輸入,間接影響着深度學習的運算量,是咱們在嵌入式ASR中要考慮的問題。
2.幀率抖動
5s統計一次直播流視頻幀率,1min計算一次幀率方差,方差過大,視爲推流幀率抖動.
3.聲學模型(acoustic model)
聲學模型做爲語音識別的CPU,其重要性不言自喻。
通常地,它佔據着語音識別大部分的運算開銷,直接影響着語音識別系統的性能。傳統語音識別系統廣泛基於GMM-HMM的聲學模型,其中GMM對語音聲學特徵的分佈進行建模,HMM則用於對語音信號的時序性進行建模。
2006年深度學習興起之後,深度神經網絡(DNN,Deep Neural Networks)被應用於聲學模型。
近十多年,聲學模型的上深度學習的發展一路高歌,各類CNN、RNN、TDNN的拓撲結構如雨後春筍一一冒出,關於深度學習在聲學模型的更多介紹見文。
對於嵌入式LVCSR來講,選擇合適的DNN拓撲結構,並用合理的優化在手機實現結構的運算,是聲學模型在其的核心訴求。
4.語言模型(language model)
語言模型,NLP從業者相對更爲熟悉。在語音識別裏,語言模型用來評估一個句子(即圖2的詞語序列)出現的機率高低。
在語言模型的實現算法中,最多見的爲n-gram模型(n-gram models),利用當前詞前面的n個詞來計算其機率,是一個上下文有關模型。幾年來,神經語言模型(Neural language models)使用詞彙Embedding來預測,也獲得普遍的發展與應用。
在嵌入式ASR中,因爲計算資源要留予聲學模型,因此語言模型採用的依舊是n-gram的思想。那麼在有限的內存中,如何最大化存儲語言模型,是嵌入式ASR要解決的問題。
5.發音詞典
發音詞典,是語音識別的內存條。內存能將硬盤的數據讀入,並使用cpu進行運算。一樣的,發音詞典,能將語言模型的詞條序列轉化爲音素序列,並用聲學模型進行分數評估運算。
發音詞典是鏈接聲學模型和語言模型的橋樑,他的大小直接影響聲學模型和語言模型的發揮空間。
在嵌入式ASR中,發音詞典的大小,與語言模型的規模互相共鳴,因此要解決的問題能夠與語言模型歸爲一談。
6.解碼器
解碼器,估計這個詞的來自英文decoder的直譯,筆者認爲更恰當的名字應稱爲識別器。之因此叫解碼器,還有另一個比較形象的緣由。以16bit語音數據爲例,計算機的存儲是一堆咱們看不懂的short類型數字,如同密碼通常。語音識別能破解這些密碼,將明文展現在咱們面前。
因此通俗來說,解碼器就是將語音識別各個流程串聯的代碼工程。通常雲端採用與WFST(帶權優有限狀態自動機)搭檔的靜態解碼器,能夠更方便地綜合處理語音識別的各個環節。而嵌入式爲了節省語言模型的內存開支,採用特定的動態解碼器。
03
開始優化這些組件——速度和內存優化
爲了優化這些「部件」佔用的時間與內存,咱們作了一系列工做:
neon計算優化,奇異值分解優化,哈夫曼編碼優化。
1.neon優化聲學模型計算
neon的計算優化,已經是廣大工程師們的老生常談,機器學習相關的T族們更是耳熟能詳。在嵌入式ASR引擎中,咱們對核心高頻運算的函數進行了neon優化,採用了彙編語言進行編寫,最終有效提升了25%的計算速度。
接下來,本文現以實現char類型向量乘的介紹優化的實現,分三版原本介紹:
A. 優化前的樸素版
B. neon c版
C. neon彙編版
首先,咱們將要實現的函數是:
/** * 實現兩個char類型向量乘 * start_a: 向量A * start_b: 向量B * cnt:向量元素個數 * result:向量乘返回存儲變量 */
void vector_product_neon(const char * start_a, const char * start_b, int & result,
const int cnt);
複製代碼
A. 優化前樸素版
void vector_product_neon(const char * start_a, const char * start_b, int & result,
const int cnt) {
int res = 0;
for(int j = 0; j < cnt; j++) {
res += int(*start_a) * int(*start_b);
start_a++;
start_b++;
}
result = res;
}
複製代碼
B. neon c版
Neon寄存器能實現128位空間的並行運算,對於char類型的向量乘而言,兩兩相乘的結果在short類型範圍內,故可8個爲一組實現。如下代碼,8個元素一組,一次循環處理兩組。在咱們的深度學習運算中,隱層的向量長度保證爲16倍數,實現代碼以下:
void vector_product_neon(const char * start_a, const char * start_b, int & result,
const int cnt) {
int res = 0;
int32x4_t neon_sum = vdupq_n_s32(0);
int8x8_t neon_vector1;
int8x8_t neon_vector2;
for(int j = 0; j < cnt / 16; j++) {
neon_vector1 = vld1_s8((char *)start_a);
neon_vector2 = vld1_s8((char *)start_b);
int16x8_t neon_tmp = vmull_s8(neon_vector1, neon_vector2);
start_a += 8;
start_b += 8;
neon_vector1 = vld1_s8((char *)start_a);
neon_vector2 = vld1_s8((char *)start_b);
neon_tmp = vmlal_s8(neon_tmp, neon_vector1, neon_vector2);
neon_sum = vaddw_s16(neon_sum, vget_low_s16(neon_tmp));
neon_sum = vaddw_s16(neon_sum, vget_high_s16(neon_tmp));
start_a += 8;
start_b += 8;
}
for(int j = 0; j < 4; j++)
res += vgetq_lane_s32(neon_sum, j);
result = res;
}
複製代碼
C. neon彙編版
彙編版本的neon代碼編寫與維護成本高,但速度比c版本更快。秉着精益求精的態度,咱們實現了彙編代碼:
void vector_product_neon(const char * start_a, const char * start_b, int & result,
const int cnt) {
int res = 0;
asm volatile(
"vmov.s32 q2, #0" "\n\t"
"lsr %[cnt], %[cnt], #4" "\n\t"
".charloop:"
"vld1.s8 {d0}, [%[vec1]]!" "\n\t"
"vld1.s8 {d1}, [%[vec2]]!" "\n\t"
"vmull.s8 q1, d0, d1" "\n\t"
"vld1.s8 {d0}, [%[vec1]]!" "\n\t"
"vld1.s8 {d1}, [%[vec2]]!" "\n\t"
"vmlal.s8 q1, d0, d1" "\n\t"
"vaddw.s16 q2, q2, d2" "\n\t"
"vaddw.s16 q2, q2, d3" "\n\t"
"subs %[cnt], %[cnt], #1" "\n\t"
"bne .charloop" "\n\t"
"vadd.s32 d4, d4, d5" "\n\t"
"vmov.s32 r4, d4[0]" "\n\t"
"add %[sum], r4" "\n\t"
"vmov.s32 r4, d4[1]" "\n\t"
"add %[sum], r4" "\n\t"
: [sum]"+r"(res)
: [vec1]"r"(start_a),
[vec2]"r"(start_b),
[cnt]"r"(cnt)
: "r4", "cc", "memory"
);
result = res;
}
複製代碼
2.奇異值分解優化聲學模型運算量
爲了下降乘加運算的次數,咱們決定利用奇異值分解來對DNN進行重構,經過裁剪掉最小的奇異值及其相對應的特徵向量,來達到減小乘加運算數量的目標。奇異值分解將任意矩陣Wm×n(不失通常性,假設m≤n)分解成3個矩陣相乘:Wm×n =Um×mΣm×mVm×n。
其中:Σm×m 爲對角矩陣,即Σm×m =diag(σ1,σ2,…,σm),它的對角元素即爲Wm×n的奇異值;Um×m 爲單位正交矩陣,其列向量爲與奇異值對應的特徵向量;Vm×n中的行向量是互相單位正交的,也是與奇異值對應的特徵向量。
下圖是咱們以DNN模型其中一層網絡做爲例子,闡述咱們在重構DNN中的模型轉化,其中原始DNN模型爲圖中上方子圖(a),新重構DNN模型在下方子圖(b)所示:
a:原始DNN模型的一層結構
(b)新DNN模型的兩層對應結構
利用SVD對聲學模型計算量優化大體分爲3個步驟
(1)訓練初始DNN神經網絡;
(2)對權重矩陣進行奇異值分解;
(3)對重構後的DNN模型從新訓練。
經過基於SVD的模型壓縮方法,咱們能夠在稍微下降模型性能的前提下,將聲學模型計算量減小30%。
3.哈夫曼優化語言模型內存
通常地,n-gram語言模型能夠用一張有向圖存儲便於介紹存儲空間以及快速查詢,這張圖上的邊要存儲詞彙信息。咱們知道以漢語爲例,不一樣詞語的出現頻率相差極大,若是全部詞彙的label id都用int類型存儲,那空間的利用率較爲低下。
以「我」「要」「吃飯」爲例,假設語言模型的詞彙頻率:我>要>吃飯,那麼咱們能夠構建圖3的哈夫曼樹,則四個字使用的編號碼分別爲:我(0),要(10),吃飯(110)
二叉哈夫曼
十六叉哈夫曼樹
然而,採用圖4的二叉樹數據結構,一次只能處理1bit效率較低,也不便於工程實現。因此在工程實現的時候,咱們按4bits編碼爲單位,對詞彙進行分類存儲處理。
咱們使用一棵16叉樹的哈夫曼樹結構,每層樹節點的編號總量是上一層的16倍。樹中的全部編號爲0的子節點用於儲存詞彙,越高頻的詞彙儲存於深度越低的節點位置。
經過哈夫曼優化,咱們的引擎最終成功下降了25%的內存佔用,同時引擎是資源文件也獲得50%左右的優化。
04
識別性能的優化
1.基於TDNN優化聲學模型
近幾年,TDNN(Time-Delay Neural Network,延時神經網絡)【5】的拓撲結構被應用於語音識別。事實上,該結構於1989年被提出,隨着近幾年技術的發展,從新進入了你們的視線。
DNN結構
DNN的拓撲網絡僅針對單一特徵時刻點建模。
TDNN結構
TDNN的隱層結構,對語音特徵多個時刻點進行抽象建模,擁有更強的建模能力。除此以外,TDNN結構的多時刻建模參數是共享的(圖中紅、綠、紫用的是一樣的拓撲矩陣傳播)。
因此,TDNN雖然在訓練的時候,比DNN須要更多的BP運算。而在語音識別時,因爲參數共享的緣由,隱層的計算結果能夠複用,每一幀僅需對全部參數進行一次運算,大大節省了計算量。最後,咱們基於TDNN結構,引擎在保持計算量一致的前提下,識別率提高了相對20%的準確率。
2.基於多任務訓練優化性能
採用多任務聯合訓練,能有效提升聲學訓練的魯棒性,避免過早陷入局部最優。在嵌入式的模型中,模型輸出目標比較少,訓練容易陷入局部最優。因此咱們,同時用目標多的大模型聯合訓練,讓訓練的隱層結構更爲魯棒。
聲學模型多任務訓練
在訓練的時候,咱們網絡同時擁有輸出1和輸出2兩個,多任務訓練時,逆向迭代須要殘差協調,咱們採用如下公式分配殘差,其中λ權衡兩個模型的訓練權重:
最終咱們採用多任務訓練優化性能,對語音識別率帶來了必定提高,接下來全部的性能提高咱們將在下一章結實驗給出。
3.基於區分性訓練(Discriminative Training)性能優化
聲學模型區分性訓練是針對MLE訓練的不足而提出的。DT訓練一般定義一個目標函數(Objective Function),或者說是準則函數(Criterion Function),來近似一個與分類代價相關的度量。經過區分性訓練,咱們能夠從必定程度上弱化模型假設錯誤所帶來的影響。
同時,因爲區分性訓練致力於優化與識別效果好壞相關的度量,所以也就爲提升識別器性能提供了更直接的途徑。形象的說,MLE訓練告訴模型「這是椅子,那是桌子」,而區分性訓練則告訴模型「這是桌子而不是椅子,那是椅子而不是桌子」。MLE訓練更重視調整模型參數以反映訓練數據的機率分佈,而區分性訓練則更重視調整模型之間的分類面,以更好的根據設定的準則對訓練數據進行分類。
DT的目標函數是這樣的:
對DT的目標函數用一次貝葉斯公司能夠獲得:
分子正是ML的目標函數;而分母則是全部文本(包括訓練文本和它的全部競爭者)產生訓練語音的機率的(按語言模型加權的)和。因爲分母上要枚舉全部可能的文本並不現實,因此實際中,通常是用一個已有的ML訓練的語音系別系統對訓練語音作一次解碼,獲得n-best list或lattice,用這裏面的文原本近似分母上的求和。n-best list或lattice中包含了訓練文本的足夠接近的競爭者。
4.基於互信息的新詞發現
對於語音識別系統來講,語言模型對結果影響相當重要;而對於語言模型來說,語言模型的詞典是關鍵。一個好的分詞詞典,對於獲得魯棒的語言模型是相當重要的,若是才能選出合理正確的「詞」所組成的詞典,首先最關鍵的一步就是基於現有語料的新詞挖掘。
因爲嵌入式系統性能有限,所以選擇合適大小的詞表,並對語言模型進行適當剪枝頭,能夠壓縮安裝包大小、限制內存消耗、提升識別性能。壓縮詞表能夠篩選高頻詞,並經過必定的模型來識別篩掉截斷詞,如「新功」、「嘉年」、「扛生」、「鵝卵」、「劉德」、「利亞」等半個高頻詞。
一個簡單而又有效的新詞發現和篩選方案能夠採用互信息和左右信息熵的計算方法,計算二元的信息熵的分數由三個對應部分組成: 1)點間互信息:點間互信息越高,內部聚合程度越高; 2)兩個單詞片斷信息熵 h_r_l 和 h_l_r 的最小值:這個數值越大,則意味着兩個單詞一塊兒出現的可能性越小; 3)單詞左右信息熵的最小值:這個數值越大就表示着候選詞出現的語境越多,越有可能成詞所以,分數越高表示成詞的可能性越大。
計算完二元的信息熵後,能夠依次計算三元、四元的信息熵,三元的新詞發現和篩選是將二元替換原有的兩個單字作爲一個單字繼續進行,候選集能夠取左信息熵或者右信息熵爲0的候選集,四元、五元以此類推。 另外,語言模型直接關係到識別結果輸出,所以選與應用場景相對應的語料進行統計尤其重要。
05
實驗對比
第二章節和第三章節,介紹了一些咱們完成的工做,本章節將分爲兩部分。首先,咱們經過實驗對比驗證工做的成果。其次,咱們將引擎和行業競品進行對比。
工做成果驗證
目前總共有6個通用測試集,測試集大小分別爲1220、691七、406九、297七、294六、2500條語音。其中測試集1是手機錄製測試集,集2是命令類的錄音,集3是麥克風錄音涉及通常生活情景,四、五、6集都是線上實網數據,區別是 集四、5背景比較乾淨,集6背景帶噪。
測試集
DNN
TDNN
TDNN優化版
1
10.4
8
6.9
2
13.7
11.3
9.3
3
22.9
18.3
15.6
4
15.8
13.3
12
5
15.3
12.2
10.5
6
22.6
20.3
17.8
在模型選取對比,咱們針對DNN、TDNN、以及TDNN優化版(優化內容爲第三章的二、三、4小結內容),總共設計出三個不一樣版本的嵌入式語音識別引擎進行對比。
三個版本的嵌入式語音識別引擎在6個通用測試集上的實驗結果如表中所示。表中的數字表示字錯誤率,即100個字裏面識別錯字的數量。整體來看,TDNN對識別率帶來了20%左右的提高,其餘工做也帶來了10%左右的提高。
從語音識別的基本概念,到語音識別速度和內存優化的介紹,以及沉澱的一些算法研究、實驗結果驗證,本文大致講述了語音識別從原理到實踐的基本過程。歡迎一樣從事語音AI識別的小夥伴加入咱們~
此文已由做者受權騰訊雲+社區發佈,更多原文請點擊
搜索關注公衆號「雲加社區」,第一時間獲取技術乾貨,關注後回覆1024 送你一份技術課程大禮包!
海量技術實踐經驗,盡在雲加社區!