簡單的語音分類任務入門(需要些深度學習基礎)

引言

上次公衆號剛剛講過使用 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 參數
# 更多精彩,關注微信公衆號:python高效編程
# 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 係數,以及對應的標籤。

# 存儲處理過的數據,方便下一次的使用
# 更多精彩,關注微信公衆號:python高效編程
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 是指隨機打亂數據集,以獲得無序的數據集。

# 獲取訓練集與測試集
# 更多精彩,關注微信公衆號:python高效編程
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():
    # 更多精彩,關注微信公衆號:python高效編程
    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高效編程,並在在微信後臺回覆音頻