基於混合模型的語音降噪實踐
前面的文章(語音降噪論文「A Hybrid Approach for Speech Enhancement Using MoG Model and Neural Network Phoneme Classifier」的研讀 )梳理了論文的思想。本篇就開始對其實踐,主要分以下幾步:1,基於一個語料庫算出每個音素的單高斯模型;2,訓練一個輸出是一幀是每個音素概率的NN分類判別模型;3,演算法實現及調優。
1,得到每個音素的單高斯模型
要想得到每個音素的單高斯模型,首先得做好音素對齊,知道某一幀對應哪個音素。論文里用的是英文的TIMIT語料庫,已經做好了音素對齊,知道了哪個時間段是哪個音素。現在做的是中文的語音降噪,最好能有一個像TIMIT一樣已經做好音素對齊的語料庫。網上搜了 一下,沒有已做好音素對齊的中文語料庫,只能自己做音素對齊了。做語音識別時用過清華的Thchs30語料庫,且它在中文語音識別領域裡知名度較高,於是就決定基於Thchs30做每個音素的單高斯模型。自己來做音素對齊,肯定花不少時間,最好是有開源工具幫忙來做。幸運的是找到了支援中文音素對齊的工具speech-aligner(//github.com/open-speech/speech-aligner),它是基於kaidi的,我用過一段時間kaldi,自然對它用起來也比較順手。speech-aligner里音素較多,主要是因為音素帶音調了(包括輕聲共5個音調,分別是0/1/2/3/4,0表示輕聲)。Phoneme.txt里是音素和id的映射,wav.scp放wav文件名對應的音頻位置,text里放wav文件對應的中文和帶調拼音,自己弄時依葫蘆畫瓢就可以了。比較好的是Thchs30語料庫里已有了每個wav文件對應的中文和帶調拼音(trn文件里),我只需要寫python腳本把它們集中起來使用,這讓我省了不少時間,尤其是在拼音標註上。用speech-aligner做好音素對齊後得到的是out.ali文件,裡面指出了哪個時間段是什麼音素,示意如下圖:
從上圖可以看出,0~25ms是音素y,25~460ms是音素e_3。
接下來就是基於out.ali得到每幀是哪個音素。這裡的幀跟語音識別里的幀一樣,幀長25ms和幀移10ms。依舊用python腳本處理後得到csv文件,每行代表一個wav文件的資訊,開頭是文件名,後面就是每幀對應的音素id,示意如下圖:
有了每幀跟音素的映射後就開始算每個音素的單音素高斯模型了。統計出語料庫中每個音素共有多少幀,每幀求出對數幅度譜。由於幀長是25ms,16k HZ取樣下是400個點,所以STFT用512個點的,即有512個頻段。時域數據轉換到頻域後是複數形式,除了0和N/2維外其他值是對稱的,所以只要(N/2 + 1)表示頻域數據就可以了。當N=512時,N/2 + 1是257,從而對數幅度譜的數據是257維的。對每維分別算均值和方差,這樣每個音素的單高斯模型就有了,它是257維的,每維有均值和方差。
2,分類判別NN模型訓練
原理篇中說過這是個典型的分類問題,需要訓練一個分類模型 。在做KWS(keyword spotting,關鍵詞識別)時我們就訓練過分類模型,這裡可以拿來用,不過要做一些修改,比如分類label的處理上。我把語料庫按8:1:1分成訓練集/驗證集/測試集,網路用的是CNN(卷積神經網路)。論文是幾年前的啦,當時神經網路剛流行,更多用的是DNN全連接網路。訓練時發現模型不能收斂,在測試集下準確率跟論文里的差不多。我認真想了下,不能收斂也是合理的,畢竟音素對齊時就不是很準確,尤其是兩個音素的臨界處。論文里用9幀(當前幀+前後各4幀)作為網路的輸入。我試過單幀(當前幀)輸入 / 3幀(當前幀+前後各1幀)輸入 /5幀(當前幀+前後各2幀)輸入 / 7幀(當前幀+前後各3幀)輸入 / 9幀(當前幀+前後各4幀)輸入,準確率都差不多,考慮到更多幀的輸入導致模型參數更多,我最後選了單幀輸入。既然準確率跟論文里差不多,我也就沒再深究。這樣基於NN的分類判別模型就訓練好了。
3,演算法實現和調優
上面的兩個模型做好後就開始做演算法實現了。依舊在python下實現,因為python里有好多現成的庫,實現起來更快,要把更多的時間放在降噪效果調優上。演算法實現依據原理篇中給出的步驟一幀一幀的做。先根據前25幀(約250ms)算出雜訊單高斯模型的均值和方差的初始值用於後面迭代。在每一幀里,先根據NN模型求出這幀對應每個音素的後驗概率,然後算對數譜,根據數學表達式算出降噪後的每一維的對數譜,再做反變換得到時域的PCM值。最後再更新雜訊的單高斯模型的均值和方差,用於下一幀的計算。演算法實現還是挺快的,有兩個值α和β需要tuning。先簡單調了下這兩個參數,能起到部分降噪效果,接下來就是調優了。
經過好多次的嘗試,得到了一個相對不錯的α和β值,在各種不同的SNR下MOS分平均能提高0.3左右,與論文里說的差不多(由於語言/語料庫/網路模型等不一樣,結果與論文里的也不可能完全一樣)。我也將其與webRTC里的ANS(用的是我手頭上的一個C語言的版本,且有3個不同的level,分別是弱/一般/激進 )的降噪效果做了比較,具體MOS分如下表:
訓練分類模型時都是乾淨語音,降噪時都是拿帶噪語音去算每幀是每個音素的後驗概率,準確率相對乾淨語音會低一些,進而會影響到降噪效果。如果拿降噪後的語音去再次算後驗概率,準確率會高些,降噪效果也會好些。於是對於一段降噪的語音,我把演算法做了幾次迭代,即拿前一次降噪後的語音經過NN算後驗概率,再次降噪,直到最後一次的降噪結果作為輸出。實踐下來第一次迭代後MOS分有近0.1的提升,後面的迭代效果就不明顯了。迭代能提升一點MOS分,但是會帶來運算量(即CPU load)的增加,真正用時做一次演算法迭代就可以了。