簡單的語音分類任務入門(需要些深度學習基礎)
- 2019 年 12 月 22 日
- 筆記
引言
上次公眾號剛剛講過使用 python 播放音頻與錄音的方法,接下來我將介紹一下簡單的語音分類處理流程。簡單主要是指,第一:數據量比較小,主要是考慮到數據量大,花費的時間太長。作為演示,我只選取了六個單詞作為分類目標,大約 350M 的音頻。實際上,整個數據集包含 30 個單詞的分類目標,大約 2GB 的音頻。第二 :使用的神經網路比較簡單,主要是因為分類目標只有 6 個。如果讀者有興趣的話,可以使用更加複雜的神經網路,這樣就可以處理更加複雜的分類任務。第三:為了電腦能夠更快地處理數據,我並沒有選擇直接把原始數據『』喂「給神經網路,而是藉助於提取 mfcc 係數的方法,只保留音頻的關鍵資訊,減小了運算量,卻沒有犧牲太大的準確性。
註:本文中涉及 「微信公眾號/python高效編程」 的路徑都要改成讀者保存文件的地址。
簡介
傳統的語音識別技術,主要在隱馬爾可夫模型和高斯混合模型兩大」神器「的加持之下,取得了不錯的成績。但是深度學習演算法後來者居上,節省了原先耗費在特徵提取上的時間,甚至可以直接進行端到端的語音識別任務,大有燎原之勢。
今天我們只介紹語音分類任務的簡單流程,旨在讓讀者對語音識別有個初步的認識。本文主要藉助 python 的音頻處理庫 librosa 和非常適合小白使用的深度學習庫 keras。通過調用他們的 api ,我們可以快速地實現語音分類任務。
載入標籤
首先大家要把從公眾號下載來的音頻文件保存在一個固定的文件夾中,比如取名為「audio」。我們通過函數os.listdir
,獲取「audio」文件夾中所有的音頻的類別,比如 「bed」,「bird」,「cat」 等等類別。這些標籤就是我們需要分類的目標。
# 載入標籤 label_path = '微信公眾號/python高效編程/audio' def load_label(label_path): label = os.listdir(label_path) return label
提取 mfcc 係數
mfcc 係數,全稱「Mel Frequency Cepstrum Coefficient」,音譯為:梅爾頻率倒譜係數,是模仿人類聽覺特性而提取的特徵參數,主要用於特徵提取和降維處理。就像主成分分析方法(PCA),可以將高維度的數據壓縮到低維,從而起到減小計算量以及過濾雜訊的目的。拿我們這次的音頻為例,我們選取了 5000 多個取樣點 ,經過提取 mfcc 係數,得到 20 * 11 的矩陣,大大減小了計算量。
首先,第一個函數 librosa.load
用於讀取音頻文件,path 為音頻路徑,sr 為取樣率(也就是一秒鐘取樣點的個數),設置為None,就按音頻本身的取樣率進行讀取。mono 為雙聲道,我們讀取的音頻都是單聲道的,所以也要設置為 None。其次,我們並不需要這麼高的取樣率,所以就每三個選取一個取樣點,y=y[::3]
。
如何提取 mfcc 參數呢?
傳統的語音識別預處理,要經過 分幀>>加窗>>快速傅里葉變換 等一系列操作,才能提取 mfcc 參數。但是呢,我們可以調用 librosa.feature.mfcc
方法,快速提取 mfcc 係數,畢竟我們只是簡單地熟悉下語音處理的流程。這裡要注意的是,由於我們拿到的音頻文件,持續時間都不盡相同,所以提取到的 mfcc 大小是不相同的。但是神經網路要求待處理的矩陣大小要相同,所以這裡我們用到了鋪平操作。我們 mfcc 係數默認提取 20 幀,對於每一幀來說,如果幀長小於 11,我們就用 0 填滿不滿足要求的幀;如果幀長大於 11,我們就只選取前 11 個參數。我們用到的函數numpy.pad(array, pad_width, mode)
,其中 array 是我們需要填充的矩陣,pad_width是各個維度上首尾填充的個數。舉個例子,假定我們設置的 pad_width 是((0,0), (0,2)),而待處理的 mfcc 係數是 20 * 11 的矩陣。我們把 mfcc 係數看成 20 行 11 列的矩陣,進行 pad 操作,第一個(0,0)對行進行操作,表示每一行最前面和最後面增加的數個數為零,也就相當於總共增加了 0 列。第二個(0,2)對列操作,表示每一列最前面增加的數為 0 個,但最後面要增加兩個數,也就相當於總共增加了 2 行。mode 設置為 『constant』,表明填充的是常數,且默認為 0 。
這樣,我們就成功提取了一個音頻文件的 mfcc 參數。
# 提取 mfcc 參數 # train: (6980, 20, 11) (6980,) # test: (4654, 20, 11) (4654,) def wav2mfcc(path, max_pad_size=11): y, sr = librosa.load(path=path, sr=None, mono=False) y = y[::3] # 默認提取 20 幀 audio_mac = librosa.feature.mfcc(y=y, sr=16000) y_shape = audio_mac.shape[1] if y_shape < max_pad_size: pad_size = max_pad_size - y_shape audio_mac = np.pad(audio_mac, ((0, 0), (0, pad_size)), mode='constant') else: audio_mac = audio_mac[:, :max_pad_size] return audio_mac
我們首先要建立兩個列表,分別用來存儲 mfcc 係數和相應的標籤(也就是 bed,bird 等)。然後每提取到一個 mfcc 參數就把它添加到 mfcc_vectors 中,並且在 target 中存儲它的標籤名。當我們把六個文件夾所有的音頻文件 全部處理完畢後,我們要把數據存儲用 npy(numpy 矩陣的存儲格式) 格式存儲起來。讀者可能會疑問,為什麼要保存起來,我一下子做完整個流程,不就可以了嗎?一方面,我們並不一定可以一次性地完成所有的操作,保存階段性成果是很有必要的,省得我們下次又要從頭開始執行程式。特別是這種需要處理大量數據的情況,會造成不必要的時間浪費。另一方面,即使我們可以一次完成所有的操作,得到我們想要的結果。但萬一,下次有朋友請教你這方面的問題,你又要從頭演示給他看,不划算啊。
保存數據之後,我們就得到了所有音頻的 mfcc 係數,以及對應的標籤。
# 存儲處理過的數據,方便下一次的使用 def save_data_to_array(label_path, max_pad_size=11): mfcc_vectors = [] target = [] labels = load_label(label_path=label_path) for i, label in enumerate(labels): path = label_path + '/' + label wavfiles = [path + '/' + file for file in os.listdir(path)] for wavfile in wavfiles: wav = wav2mfcc(wavfile, max_pad_size=max_pad_size) mfcc_vectors.append(wav) target.append(i) np.save(DATA, mfcc_vectors) np.save(TARGET, target)
簡單的神經網路
接著我們要為神經網路準備食物了。
我們藉助 sklearn 中的train_test_split
,把數據集分為訓練集和驗證集。其中訓練集占 6 成,測試集占 4 成。隨機狀態為 42,隨機狀態設置為 42 是為了方便優化,如果每次隨機結果都不相同的話,那麼就沒有可比性了。shuffle 是指隨機打亂數據集,以獲得無序的數據集。
# 獲取訓練集與測試集 def get_train_test(split_ratio=.6, random_state=42): # 載入保存的 mfcc 係數以及對應的標籤 X = np.load(DATA) y = np.load(TARGET) assert X.shape[0] == y.shape[0] # 把數據集分為訓練集和驗證集 X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=(1 - split_ratio), random_state=random_state,shuffle=True) return X_train, X_test, y_train, y_test
最後,就到了神經網路大顯身手的時候了。
首先,我們要改變 mfcc 係數的 shape,使它變成二維矩陣且第二個維度大小為 220。其次,我們要用到 keras 的One-hot 編碼。舉個例子,原先的標籤為『bed』,『bird』,『cat』,經過編碼,凡是對應標籤,就編碼成 1,反之編碼成 0。下圖為示例:左邊為原矩陣,右邊為編碼後的矩陣。

接著,我們就可以向搭建樂高積木一樣,搭建我們簡單的神經網路模型了。
首先我們選擇 keras 的 Sequential 模型 ,也就是序列模型,這是一個線性的層次堆棧。model.add 表示向堆棧中增加一層網路,Dense 的第一個參數是每層網路的節點的個數,也就是輸出矩陣第二個維度的大小。假如輸入矩陣大小為 5743 * 220,設定節點個數為 64,那麼輸出的矩陣的大小為 5743 * 64。第二個參數是激活函數的類型。
其中 relu 函數定義如下:
def naive_relu(x): if x > 0: return x else: return 0

numpy 中有個函數 numpy.maximum(x, 0),也是類似的功能。
對於多元分類問題,最後一層常用 softmax 函數,節點數為 6,表明返回這六個標籤的可能性。
# softmax funtion def softmax(x): a = np.max(x) c = x - a exp_c = np.exp(c) sum_c = np.sum(exp_c) return exp_c / sum_c # target是x對應的標籤 x = ([0.5,0.6,2.9]) target = (['bird','bed','cat']) y = softmax(x) print(y)
# output:[0.08 0.08 0.84] 和為1
# 標籤為'bird'的可能性為:0.08
# 標籤為'bed'的可能性為:0.08
# 標籤為'cat'的可能性為:0.84
# 即 softmax 函數輸出三種類別的可能性
接著編譯模型,即 model.compile。其中,損失函數使用的是多元分類交叉熵函數,優化器使用 RMSprop,是隨機梯度下降法的加強版。metrics 選擇 accuracy。
最後我們就可以擬合數據了。
我們可以看到,隨著 epoch 的增加,訓練集的準確度基本上是不斷增加的,而測試集很快就收斂了,甚至還會下降。藍線是訓練集的準確性,我跑 20 輪的結果是:0.9381。橘色的線是測試集的準確性,我跑 20 輪的結果是: 0.7823。

def main(): x_train, x_test, y_train, y_test = get_train_test() x_train = x_train.reshape(-1, 220) x_test = x_test.reshape(-1, 220) y_train_hot = to_categorical(y_train) y_test_hot = to_categorical(y_test) model = Sequential() model.add(Dense(64, activation='relu', input_shape=(220,))) model.add(Dense(64, activation='relu')) model.add(Dense(64, activation='relu')) model.add(Dense(6, activation='softmax')) model.compile(loss=keras.losses.categorical_crossentropy, optimizer=keras.optimizers.RMSprop(), metrics=['accuracy']) history = model.fit(x_train, y_train_hot, batch_size=100, epochs=20, verbose=1, validation_data=(x_test, y_test_hot)) # plot_history(history)
以上便是本節教程的全部內容了,如果想要獲取音頻文件和源碼,請關注微信公眾號: python高效編程 ,並在在微信後台回復:音頻。