基于深度学习的声音分类
前言
今天花了一天时间来复现哭声识别,从网上找了很多资料发现整合起来还是比较困难的。这里我做一下笔记方便后面的学习,希望对大家也有一定的帮助。
基础知识
音频与我们生活有着十分联系。 我们的大脑不断处理和理解音频数据,并为您提供有关环境的信息。 一个简单的例子就是你每天与人交谈。 这个演讲被另一个人看出来进行讨论。 即使你认为自己处于一个安静的环境中,你也会听到更微妙的声音,比如树叶的沙沙声或雨水的飞溅。 这是您与音频连接的程度。所以你能以某种方式抓住你周围的音频,做一些有建设性的事情吗? 当然是! 有一些设备可以帮助您捕获这些声音并以计算机可读格式表示。 这些格式是:
-
wav(波形音频文件)格式
-
mp3(MPEG-1 Audio Layer 3)格式
-
WMA(Windows Media Audio)格式
音频处理的是目前深度学习应用做火热的方向之一,虽然我们讨论过音频数据可用于分析。 但是音频处理的潜在应用是什么? 在这里,我将列出其中的一些:
-
根据音频功能索引音乐集
-
推荐用于广播频道的音乐
-
相似性搜索音频文件(又名Shazam)
-
语音处理和合成 - 为会话代理生成人工语音
当我们对音频数据进行采样时,我们需要更多的数据点来表示整个数据,并且采样率应该尽可能高。另一方面,如果我们在频域中表示音频数据,则需要更少的计算空间。
实战
传统的语音识别技术,主要在隐马尔可夫模型和高斯混合模型两大”神器“的加持之下,取得了不错的成绩。但是深度学习算法后来者居上,节省了原先耗费在特征提取上的时间,甚至可以直接进行端到端的语音识别任务,大有燎原之势。本文主要借助 python 的音频处理库 librosa 和非常适合小白使用的深度学习库 keras。通过调用他们的 api ,我们可以快速地实现语音分类任务。
1.准备数据集
我们通过一个实际的案例进行实战,首先我在github上下载了哭声数据集https://github.com/gveres/donateacry-corpus/tree/master/donateacry-android-upload-bucket,然后又使用了一些其他的城市声音数据集,整体加起来有1000个。在此数据基础上做了一些工作。数据集截图如下,baby目录下存放了457个音频数据,others下存放了大致相同的数据。
2.排序
通过自己编写的排序代码对文件夹下的文件进行统一命名。代码如下:
import sys
import os
def rename(pic_path):
piclist=os.listdir(pic_path)
i=1
print("ok")
for pic in piclist:
if pic.endswith(".wav"):
old_path=os.path.join(os.path.abspath(pic_path),pic)
new_path=os.path.join(os.path.abspath(pic_path),str(1000+(int(i)))+'.wav')#这里对两个目录下文件进行排序,第一个我设置成1001这种,第二个others目录下设置成了9001这种。
os.renames(old_path,new_path)
print ("把原命名格式:"+old_path+u"转换为新命名格式:"+new_path)
i=i+1
if __name__ == '__main__':
rename("./data/babytest/")
3.添加数据标签
首先大家要下载来的音频文件保存在一个固定的文件夹中,比如取名为“data”。我们通过函数os.listdir
,获取“./data/”文件夹中所有的音频的类别,这些标签就是我们需要分类的目标。
# 加载标签
def load_label(label_path):
label = os.listdir(label_path)
return label
4.提取mfcc系数
mfcc 系数,全称“Mel Frequency Cepstrum Coefficient”,音译为:梅尔频率倒谱系数,是模仿人类听觉特性而提取的特征参数,主要用于特征提取和降维处理。就像主成分分析方法(PCA),可以将高维度的数据压缩到低维,从而起到减小计算量以及过滤噪声的目的。拿我们这次的音频为例,我们选取了 5000 多个采样点 ,经过提取 mfcc 系数,得到 20 * 11 的矩阵,大大减小了计算量。
首先,第一个函数 librosa.load用于读取音频文件,path 为音频路径,sr 为采样率(也就是一秒钟采样点的个数),设置为None,就按音频本身的采样率进行读取。mono 为双声道,我们读取的音频都是双声道的(注意,有些是单声道),所以也要设置为True。其次,我们并不需要这么高的采样率,所以就每三个选取一个采样点,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 参数
def wav2mfcc(path, max_pad_size=11):
y, sr = librosa.load(path=path, sr=None, mono=1)
y = y[::3] #每三个点选用一个
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
5.数据预保存
首先建立两个列表,分别用来存储 mfcc 系数和相应的标签(也就是 baby,other)。然后每提取到一个 mfcc 参数就把它添加到 mfcc_vectors 中,并且在 target 中存储它的标签名。当我们把2个文件夹所有的音频文件 全部处理完毕后,我们要把数据存储用 npy(numpy 矩阵的存储格式) 格式存储起来。这样方便程序下次的快速启动,不用预先花太多时间加载数据。
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)
6.训练模型
将之前的处理代码整合起来,总结一下:首先主函数调用save_data_to_array("./data/", max_pad_size=11) 将当前目录中data下的两个数据集保存成npy格式。接着我们要使用get_train_test()为神经网络准备训练集和测试集了。我们借助 sklearn 中的train_test_split,把数据集分为训练集和验证集。其中训练集占 6 成,测试集占 4 成。shuffle 是指随机打乱数据集,以获得无序的数据集。之后改变 mfcc 系数的 shape,使它变成二维矩阵且第二个维度大小为 220。其次,我们要用到 keras 的One-hot 编码。举个例子,原先的标签为‘baby’,‘others’,,经过编码,凡是对应标签,就编码成 1,反之编码成 0。这里我们使用了很简单了CNN模型进行训练。
import os
import keras
import librosa
import numpy as np
import matplotlib.pyplot as plt
from keras import Sequential
from keras.utils import to_categorical
from keras.layers import Dense
from sklearn.model_selection import train_test_split
DATA = 'data.npy'
TARGET = 'target.npy'
# 加载标签
def load_label(label_path):
label = os.listdir(label_path)
return label
# 提取 mfcc 参数
def wav2mfcc(path, max_pad_size=11):
y, sr = librosa.load(path=path, sr=None, mono=1)
y = y[::3] #每三个点选用一个
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
# 存储处理过的数据,方便下一次的使用
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)
# return mfcc_vectors, target
# 获取训练集与测试集
def get_train_test(split_ratio=.6, random_state=42):
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
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(2, 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=100, verbose=1,
validation_data=(x_test, y_test_hot))
plot_history(history)
def save():
label_path = './data/'
save_data_to_array(label_path, max_pad_size=11)
def plot_history(history):
plt.plot(history.history['acc'],label='train')
plt.plot(history.history['val_acc'],label='validation')
plt.legend()
plt.show()
if __name__ == "__main__":
save_data_to_array("./data/", max_pad_size=11)
main()
7. 验证
import wave
import numpy as np
import os
from keras.models import load_model
# 提取 mfcc 参数
def wav2mfcc(path, max_pad_size=11):
y, sr = librosa.load(path=path, sr=None, mono=1)
y = y[::3] #每三个点选用一个
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
if __name__ == '__main__':
# 构建模型
model = load_model('classaud.h5') # 加载训练模型
wavs=[]
wavs.append(wav2mfcc("./7816.wav",11))
X=np.array(wavs)
X= X.reshape(-1, 220)
print(X.shape)
result=model.predict(X[0:1])[0] #
print("识别结果",result)
# 因为在训练的时候,标签集的名字 为: 0:seven 1:stop 0 和 1 是下标
name = ["baby crying","other"] # 创建一个跟训练时一样的标签集
ind=0 # 结果中最大的一个数
for i in range(len(result)):
if result[i] > result[ind]:
ind=1
print("识别的语音结果是:",name[ind])