如何訓練一個簡單的音訊識別網路

大資料文摘發表於2017-09-28

本文將一步步向你展示,如何建立一個能識別10個不同詞語的基本語音識別網路。你需要知道,真正的語音與音訊識別系統要複雜的多,但就像影像識別領域的MNIST,它將讓你對所涉及的技術有個基本瞭解。

完成本教程後,你將擁有一個模型,能夠辨別一個1秒鐘的音訊片段是否是無聲的、無法識別的詞語,或者是“yes”、“no”、“up”、“down”、“left”、“right”、“on”、“off”、“stop”、“go”。你還可以使用這個模型並在Android應用程式中執行它。

注:本文含有大量程式碼,需要程式碼原文的同學請參考文末來源地址中的內容。

準備工作

確保你已經安裝了TensorFlow,由於指令碼程式將下載超過1GB的訓練資料,你需要暢通的網路連線,而且你的機器需要有足夠的空餘空間。訓練過程本身可能需要幾個小時,所以確保你有一臺可以使用這麼長時間的機器。

訓練

開始訓練前,在TensorFlow 原始碼樹下執行:

如何訓練一個簡單的音訊識別網路

這個指令碼程式將開始下載“語音命令資料集”,包括65000條由不同的人說30個不同詞語組成的WAVE音訊檔案。這份資料由Google收集,並在CC-BY協議許可下發行,你可以透過貢獻自己五分鐘的聲音來幫助提升它。這份檔案大小超過1GB,所以這部分可能需要一段的時間,但你應該看一下過程日誌,一旦它被下載一次,你就不需要再進行這一步了。

如何訓練一個簡單的音訊識別網路

這表明初始化程式已經完成,迴圈訓練已經開始,你將看到每一次訓練產生的輸出資訊。這裡分別解釋一下含義:

如何訓練一個簡單的音訊識別網路

在100步之後,你將會看到一行輸出如下:

如何訓練一個簡單的音訊識別網路

就可以從該點重新開始指令碼。

混淆矩陣

在400步之後,將記錄如下的資訊:

如何訓練一個簡單的音訊識別網路

第一部分是混淆矩陣。為了理解它的含義,首先需要知道所使用的標籤,它們是 “silence”、 “unknown”、 “yes”、 “no”、 “up”、 “down”、 “left”、 “right”、 “on”、 “off”、“stop”和“go”。每一列代表一組被預測為某個標籤的樣本,因此第一列代表著所有預測為“silence”的片段,第二列都被預測為“unknown”詞,第三列是“yes”,以此類推。

每一行代表著正確的、完全真實為該標籤的片段。第一行是所有為“silence”的片段,第二行的片段都是“unknown”詞,第三行是“yes”,以此類推。

這個矩陣比單單一個準確率更有用,因為它能很好地總結出網路所犯的錯誤。在這個例子中,除了第一項之外,第一行中的所有項都是零。因為第一行指的是所有實際上是“silence”的片段,這意味著沒有一個被否定地標記為詞語,所以我們沒有漏報一個“silence”。這表明網路已經能夠很好地區分“silence”和詞語。

但是,如果我們看一下第一列,就會看到很多非零值。列代表所有被預測為“silence”的片段,所以除第一項之外的正數都是錯誤的。這意味著一些真實發聲的詞語實際上被預測為“silence”,所以我們存在一些誤報。

一個完美的模型會產生一個混淆矩陣,其中所有項都是零,除了透過中心這條對角線。透過該模式發現偏差,可以幫助你弄清楚模型是如何混淆的,一旦你發現了問題,你就可以透過新增更多的資料或清理類別來解決這些問題。

驗證

混淆矩陣之後,你會看到一行資訊如下:

如何訓練一個簡單的音訊識別網路

把資料分為三份是很好的做法。最大一份(在這個例子中大約資料的80%)是用來訓練網路,較小(這裡用10%,作為“驗證”)的一份保留用於評估訓練過程中的準確率,另一份(最後的10%,作為“測試”)用於在訓練完成時評估準確率

劃分資料是因為網路會在訓練過程中記錄輸入,這是有風險的。透過將驗證集分開,你可以確保模型在從未使用過的資料上執行。測試集是一個額外的保障,以確保你在調整模型過程中沒有同時執行訓練集和驗證集,也沒有更大量的輸入。

訓練指令碼自動將資料集劃分為這三類,上述日誌行展示了模型在驗證集上執行的準確率。理想情況中,這個值將與訓練集準確率十分接近。如果訓練集準確率上升的同時驗證集準確率沒有上升,這意味著出現過擬合,你的模型僅僅只在訓練集上學習,並沒有推廣到更廣泛的模式中。

Tensorflow的視覺化工具

視覺化訓練過程的一個好方法是使用tensorboard。該指令碼預設將事件儲存到/tmp/retrain_logs,你可以透過輸入命令列來載入:

如何訓練一個簡單的音訊識別網路

在瀏覽器輸入“http://localhost:6006”,你將看到一些展示訓練過程的圖表。

結束訓練

經過幾個小時的訓練(取決於機器的速度),指令碼將完成了所有18000個步驟。它將列印一個最終的混淆矩陣,連同一個準確率,這些都是在測試集上執行得到的。使用預設配置,你將得到85%~90%之間的準確率

因為音訊識別在移動裝置上特別有用,接下來我們將把它匯出到壓縮包,使得它能夠在這些平臺上使用。為此,執行命令如下:

如何訓練一個簡單的音訊識別網路

一旦壓縮後的模型建好,你可以執行label_wav.py指令碼來測試,如下:

如何訓練一個簡單的音訊識別網路

它將輸出三個標籤:

如何訓練一個簡單的音訊識別網路

希望“left”是最高分,指的是正確的標籤,但由於訓練是隨機的,它可能不是你測試的第一個檔案。在同一個資料夾中測試一些其他WAV檔案,看看結果如何。

分數將在0到1之間,值越高意味著模型對預測越自信。

在Android應用裡執行模型

如果你想觀察模型在實際應用中表現如何,最簡單的方法就是下載並在你的手機中安裝已構建好的Android演示應用了。你可以在你的應用列表找到名為“TF Speech”的應用,開啟應用,它會展示與我們剛剛訓練模型所用相同的動作詞列表,選擇“Yes”或者“No”開始。一旦你給予app使用手機的許可權,你就可以說一些詞,看看是否被模型識別出來並在UI高亮顯示。

你也可以自己來構建這個應用程式,因為它是開原始碼並且在github的TensorFlow儲存庫中可呼叫。預設情況下,它會從tensorflow.org下載一個預先訓練的模型,但你可以輕鬆地用自己訓練的模型替換它。如果這樣做的話,你需要確保主要的SpeechActivity Java原始檔(如SAMPLE_RATE和SAMPLE_DURATION)中的常量與你在進行訓練時對預設值進行的更改相匹配。 你還可以看到一個Java版本的RecognizeCommands模組與本教程中的C ++版本非常相似。 如果你因此調整了引數設定,可以在SpeechActivity中進行更新,以獲得與你的伺服器測試相同的結果。

演示app會根據你在壓縮圖形旁複製的標籤文字檔案自動更新其使用者介面列表,這意味著你可以輕鬆地嘗試不同的模型,而無需進行任何程式碼更改。如果你更改了路徑,則需要更新LABEL_FILENAME和MODEL_FILENAME以指向你新新增的檔案。

這個模型是如何運作的?

本教程使用的體系結構是基於《用於小尺寸關鍵字檢測的卷積神經網路》一文中的部分描述。選用它的原因是其相對簡單,可快速訓練和易於理解,而不是技術的先進性。建立神經網路模型以處理音訊有許多不同的方法,包括反覆網路或擴張(無序)卷積等。而本教程基於的卷積網路則對於使用影像識別的人來說非常熟悉。這乍一聽似乎有點讓人驚訝,畢竟音訊是跨越時間的一維連續訊號,而不是2D的空間問題。

如何訓練一個簡單的音訊識別網路

如果你開啟/tmp/spectrogram.png,你將看到:


不同於時間從左向右的常規頻譜圖,由於TensorFlow的記憶順序,影像中的時間是從上到下增加的,頻率從左到右。你應該可以看到幾個明顯不同的部分,第一個音節“ha”明顯區別於與“ppy”。

由於人耳對某些頻率比其他頻率更敏感,因此在語音識別中,慣用的方法會是針對該特性做一個進一步的處理,將其轉換為一組Mel-Frequency倒譜系數,簡稱為MFCC。這也是一個二維的單通道顯示,所以它可以被看作是影像。如果你針對的是一般聲音而不是語音,你會發現你是可以跳過此步驟並直接在頻譜圖上操作的。

接下來,由這些處理步驟產生的影像會被輸入到多層卷積神經網路,其含有一個全連結層後以分類器結尾。 你可以在tensorflow / examples / speech_commands / models.py中看到此部分的定義。

精度流

大多數音訊識別應用程式需要在連續的音訊流上執行,而不是單獨的剪輯段。在這種環境中使用模型的典型方法是在不同的偏移時間上重複應用它,並在短時間內平均結果以產生平滑的預測。如果你將輸入視為影像,它則會沿著時間軸不斷滾動。我們想要識別的詞可以隨時開始,所以需要採取一系列的快照來在提供給模型的時間視窗中捕獲大部分的話語。如果我們以足夠高的速度進行取樣,那麼是很有可能在多個時間視窗中捕獲該單詞的,因此將結果進行平均可以提高預測的整體信度。

有關如何在流式傳輸資料上使用模型的示例,可以檢視test_streaming_accuracy.cc。 它使用了RecognizeCommands來執行長格式輸入音訊,以嘗試查詢單詞,並將這些預測與標籤和時間的完全真值列表進行比較。這使它成為將模型應用到音訊訊號流的一個很好的例子。

你需要一個長音訊檔案和顯示其中每個單詞被說出位置的標籤來做測試。如果不想自己錄製,可以使用generate_streaming_test_wav實用程式生成一些合成的測試資料。預設情況下,該程式將建立一個10分鐘的.wav檔案,檔案的詞頻基本上是每三秒一個,同時提供一個包含了每個單詞被說出位置的完全真值文字檔案。詞彙選自當前資料集的測試部分,並與背景噪聲混合。想要執行它,請使用

如何訓練一個簡單的音訊識別網路

這將儲存一個.wav檔案/tmp/speech_commands_train/streaming_test.wav,

並提供一個包含標籤的文字檔案在

如何訓練一個簡單的音訊識別網路

執行精度測試:

如何訓練一個簡單的音訊識別網路

這部分程式將輸出正確匹配的詞數,有多少詞語被給出了錯誤標籤,以及沒有真正的詞語被說出時模型卻被觸發的次數。這裡有各種引數可以控制訊號平均的工作原理,包括--average_window_ms,它設定用以結果平滑的時間長度,--sample_stride_ms,是模型應用程式之間的時間,--suppression_ms,用以設定在找到第一個詞後再次觸發後續檢測的間隔時間,以及--detection_threshold,它控制給出肯定性預測的平均得分的閾值。

你會看到精度流輸出三個數字,而不僅僅是訓練中使用的一個度量。這是因為不同的應用程式有不同的要求,一些能夠容忍頻繁的不正確結果,只要最終找到真實的單詞即可(高查全),而另一些則非常專注於確保預測的標籤是高可能正確的,即使一些詞語並沒有被監測出來(高精度)。該工具輸出的數值會讓你瞭解到你的模型在應用程式中的表現效能,基於此你可以嘗試調整訊號平滑引數來調優其效能。要了解你的應用程式的正確引數,可以檢視生成的ROC曲線來幫助瞭解平衡。

識別命令

精度流工具使用了一個簡單的解碼器,該解碼器被包含在一個叫做識別命令的小型C ++類中。這個類隨著時間推移執行TensorFlow模型的輸出,對訊號進行平均,當有足夠的證據認為已經找到識別單詞時,返回標籤資訊。它的執行很簡單,只需跟蹤最後幾個預測值並對其進行平均,因此可以根據需要輕鬆地移植到其他平臺和語言上。例如,在Android上的Java或Raspberry Pi上的Python上執行類似的操作都很方便。只要這些演算法執行上具有相同的邏輯,就可以使用流測試工具調整控制平均值的引數,然後將其傳輸到應用程式以獲得類似的結果。

高階訓練

培訓指令碼的預設設定旨在於較小的檔案中生成良好的端到端結果,但其實有很多選項可以更改,你可以根據自己的要求自定義結果。

自定義訓練集

預設情況下,指令碼程式將下載Speech Commands dataset資料集,但你也可以提供自己的訓練資料。為了在自定義資料上做訓練,你應該確保每個識別目標單詞至少有幾百個錄音,並按類別歸入資料夾。例如,如果你想從貓叫聲中識別狗叫聲,需要先建立一個名為animal_sounds的根資料夾,然後將其中的兩個子資料夾命名為bark(狗叫)和miaow(貓叫)。最後,將音訊檔案分類放入相應的資料夾中。

將指令碼設定為指向新的音訊資料夾,需要設定--data_url= to disable downloading of the Speech Commands dataset(取消下載Speech Commands dataset), 並設定--data_dir=/your/data/folder/,從而找到你新建的音訊檔案。

這些檔案本身應該是16位小端PCM編碼的WAVE格式。取樣率預設為16,000,但只要所有音訊的速率保持一致(指令碼不支援重複取樣),你可以使用--sample_rate更改此引數。剪輯段也應該採用大致相同的時間區段。預設的預期時間區段為1秒,但也可以使用--clip_duration_ms進行設定。如果在開始時一些剪輯段有不同數量的靜音時間,可以檢視編輯工具來標準化它們(這是一種快速卻投機的方法)。

要注意的一個問題是,你可能會在資料集中重複相同的聲音,如果它們分佈在訓練,驗證和測試集中,則可能會產生有誤導性的指標表現。例如,“語音命令”集中含有一個人多次重複的相同單詞。這些重複中的每一個都可能與其他重複相當接近,所以如果在訓練時過度匹配且對其中之一進行記憶,那麼在測試集中看到非常相似的副本時,它可能表現出不切實際的好。為了避免這種風險,“語音命令”會盡力確保將單個人說出的同一個單詞的所有剪輯放入同一分割槽。

基於片段名稱的雜湊值,會將片段分為訓練集,測試集,以及校驗集。那麼在有新的片段加入時也可以保證集合的平穩劃分,避免任何訓練樣本遷移到其他集合。為了保證所有說話人的聲音都在同樣的類別內,在使用雜湊函式計算分配時會忽略“非雜湊”之後的檔名。即就是,如果你有兩個檔案,命名分別為pete_nohash_0.wav和pete_nohash_1.wav,這兩個檔案將會被分配到同一資料集。

不確定型別

在使用你的應用時,很可能聽到一些不在訓練集範圍內的聲音,你會希望模型可以在這些情況下標記出那些它無法識別的噪音。為了幫助神經網路學習需要忽略哪些聲音,你需要準備一些不屬於你的預測型別的音訊片段。怎麼做呢?你可以建立“呱呱”“嚕嚕”“哞哞”等子資料夾,然後將你的使用者可能碰到的其他動物的聲音混入子資料夾。--wanted_words引數對應的指令碼定義了你所關心的型別,所有上述子檔案中的聲音會用來在測試中混入_unknown_的型別。語音命令資料集中含有二十種未知型別,包含了從0到9的數字,和一些隨機的命名,例如“sheila”。

預設情況下,測試資料的10%是來自於未知型別,但是你可以透過引數--unknown_percentage來進行調整,增加這個值可以使模型更好的區分未知和預測的聲音,但是如果這個數值過大可能會適得其反,因為模型會為了安全而將所有的聲音都歸類到未知型別!

背景噪音

真實的應用需要在有噪音的環境中進行語音識別。為了使模型在干擾下具有良好的魯棒性,我們需要對具有相似屬性的錄音進行訓練。語音命令資料集中的檔案不是來自錄音室,而是使用者在不同的環境中透過不同裝置獲取的錄音,這在一定程度上可以增強訓練的真實性。此外,你可以在輸入端混合一些隨機的環境音訊。在語音命令資料集中有一個特殊的資料夾“_background_noise_”(背景噪音),其中包含了數分鐘的白噪音和機器的聲音,以及日常家務的活動的錄音。

背景噪音檔案的小片段是隨機選擇,然後在訓練中以一個較低的音量混入音訊片段中。這些檔案的音量也是隨時選擇的,透過--background_volume(背景音量)引數進行控制,0是靜音,1是最大音量。不是所有的片段都需要新增背景噪音,--background_frequency(背景噪音頻率)可以控制背景噪音混入的比例。

你的應用程式可能執行在某種特定的環境下,具有不同的背景噪聲模式,而不是預設的這些,所以你可以在_background_noise_(背景噪音)資料夾中新增自己的音訊片段。這些片段應該保持與主資料集相同的取樣率,但持續時間要更長,這樣可以從它們中選擇一組較好的隨機片段。

靜音

在大多數情況下,你關心的聲音是斷斷續續的,所以知道什麼時候沒有匹配的音訊是很重要的。為了支援這一點,我們使用特殊的_silence_(靜音)標籤來標誌模型沒有識別出有用資訊。因為在真實的環境中從來沒有完全的靜音狀態,實際訓練時,我們必須提供一些安靜的和一些不相關的音訊。為此,我們使用_background_noise_(背景噪音)資料夾,這些音訊也被混在真正的剪輯,從中選擇一些段的音訊資料然後標記它們的型別為_silence_(靜音)。預設情況下訓練集的10%的資料來自該資料夾中,但是,--silence_percentage(靜音比例)可以用來控制靜音檔案的混入比例。與未知型別音訊相同,比例的調整是以假陰性作為代價,如果設定的比例越高,模型會將更多的聲音設定為靜音型別,但是如果比例過高,就會使模型陷入傾向於預測是靜音型別的困境。

時間推移

在訓練中增加背景噪音是一種有效的方法來擴大資料集和增加整體的準確性,時間推移也可以起到同樣的作用。這包括了對訓練樣本資料進行隨機的時間抵消,在音訊的開始或者結束會有一個小片段被切除,並以0進行填充。在訓練集的開始階段使用這種方法來模擬真實的變化,並透過--time_shift_ms引數來進行控制,預設值是100毫秒。增大這個值可以為訓練集提供更多的變化,但是會增加切除音訊重要部分的風險。還可以使用時間收縮和音量縮放來實現真實的扭曲,從而擴大資料集,但這兩種方法超出了本教程的範圍。

自定義模型

這個指令碼對應的模型相當大,每次的推算都使用了超過8億次的浮點運算以及94萬個權重引數。這在桌上型電腦或現代的手機上會以有限的速度執行,但是因為太多的計算使得在現有裝置有限的資源下很難有一個較高的互動速度。為了支援這些使用場景,我們提供了幾個可用的替代方案。

low_latency_conv引數基於神經網路少量關鍵詞識別論文中描述的拓撲'cnn-one-fstride4'。準確度相對於卷積要低一些,但是權重引數的數量基本相同,更重要的是每次預測只需要1.1億浮點運算,很大的提升了執行速度。

你可以在命令列中使用--model_architecture=low_latency_conv來設定使用這種模型。同時,需要更新訓練集的學習率以及訓練的次數,整體的程式碼如下:

如何訓練一個簡單的音訊識別網路

程式碼中設定了訓練的迭代次數為20,000,學習率為0.01,然後將學習率調整為0.001,迭代次數調整為6000,對模型進行最佳化。

low_latency_svdf 基於論文“使用秩約束拓撲結構實現深度神經網路壓縮”中的拓撲結構。同樣的,準確度相對於卷積網路來說偏低,但只需要使用75萬個引數,最重要的是可以最佳化測試時的執行(當你實際使用你的應用時),最終只有75萬次的浮點運算。

你可以在命令列中使用--model_architecture=low_latency_svdf來設定使用這個模型,然後更新訓練的學習率和迭代次數,整體的程式碼如下:

如何訓練一個簡單的音訊識別網路

需要注意的是儘管這個模型的迭代次數與前兩個拓撲結構相比大了很多,但計算量的減少意味著在訓練時最終所使用的時間相當,最終的準確率可以達到85%。你可以透過調整SVDF層的這些引數,相對簡單地調整這個拓撲結構的計算量和準確率

  • 秩 – 相似度的秩(這個值越高,準確度越好,但同時計算量會增大)

  • 節點數量 – 和其他層型別相似,層中的節點數量(節點數越多,質量越好,同時計算量會越大)

關於執行時間,考慮到本層允許透過快取一些內部神經網路的啟用結果來進行最佳化,你需要保證當你暫停執行,或者在流傳輸模式下執行模型時,需要保證使用的是同一個步調(例如,'clip_stride_ms' 標誌)。當你壓縮圖時,以及在流模式下執行模型時(例如test_streaming_UNK acy.cc)。

如果你想嘗試自定義模型,還有一些引數也可以進行自定義,比如可以從調整聲譜圖的建立引數開始。這個引數會調整模型輸入的影像大小,在models.py檔案中的建立程式碼會根據不同的維度對計算量和權重進行自適應。如果你的輸入較小,那麼模型會使用更小的運算量來進行處理,所以這是權衡準確度和減少延遲時間的好方法。--window_stride_ms 引數可以控制每個頻率的分析樣本與前一個之間的距離。如果增大這個值,那麼在給定區間內的取樣數會減少,輸入的時間軸也會縮小。--dct_coefficient_count引數控制用來統計頻率的分類數量,所以如果減小這個值意味著從另一個維度上縮小了輸入。--window_size_ms引數不會影響輸入的大小,但是它控制了計算每個樣本頻率的區域的寬度。如果你需要驗證的聲音很短,可以透過--clip_duration_ms引數來減少訓練樣本的時長,因為這樣就是從時間維度上減少了輸入。但是你需要確保所有的訓練資料在片段的初始部分中包含你所需要的正確音訊。

針對你的問題,如果你腦海中有一個完全不同的模型,你可以將其插入到models.py檔案中,然後使用其他部分的指令碼處理所有的預處理和訓練機制。同時你需要在create_model中新增程式碼用來查詢你的架構名稱,然後呼叫模型的建立函式。這個函式中包含了聲譜圖的輸入,以及一些其他模型資訊,同時會建立TensorFlow的操作來讀取資料、建立輸出的預測向量,以及使用一個佔位符來控制神經元的丟失率。剩下的程式碼會將整個模型進行整合,執行輸入計算,應用softmax函式以及損失函式來進行訓練。

當你調整模型以及訓練超引數時,普遍遇到的問題是由於數字精度的問題,有些數值並不可以進行緩慢變化。一般來說,你可以透過降低這些值的量級來處理,比如對於學習率權重初始化函式。但如果這個問題還是持續存在,你可以使用--check_nans標誌進行跟蹤,尋找問題的根源。這個操作將在TensorFlow中的大多數常規操作之間插入檢查操作,這樣在遇到問題時,會停止訓練過程並返回有用的錯誤資訊。

相關文章