引言
上次公众号刚刚讲过使用 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 参数。
代码语言:javascript复制# 提取 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 系数,以及对应的标签。
代码语言:javascript复制# 存储处理过的数据,方便下一次的使用
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 函数定义如下:
代码语言:javascript复制def naive_relu(x):
if x > 0:
return x
else:
return 0
numpy 中有个函数 numpy.maximum(x, 0),也是类似的功能。
对于多元分类问题,最后一层常用 softmax 函数,节点数为 6,表明返回这六个标签的可能性。
代码语言:javascript复制# 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。
代码语言:javascript复制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高效编程 ,并在在微信后台回复:音频。