用于音频分类的机器学习

839 阅读10分钟

音频分类的机器学习

机器学习可以用于音高检测、理解语音和乐器,也可以用于音乐生成。对于我们的案例,我们将使用机器学习进行音频分类。

在使用图片评估环境时,机器学习已经显示出示范性的结果。然而,这一领域还没有在音频分类中得到充分的利用。

这是因为声音可以给我们一个非方向性的视角,与相机不同。声音不依赖于光照。这意味着,不管是白天还是晚上,你都能以同样的方式听到声音。

尽管如此,将声波转换为音频和光谱图(频率的可视化表示)可以让我们使用机器学习的能力。

音频的机器学习可以用于音高检测和音乐生成。对于我们的案例,我们将把它用于分类。

音频分类问题的一个很好的例子是,机器必须确定音频是语音还是音乐。

本教程将向你介绍用于音频分类的机器学习和一些相关的理论。

我们还将使用TensorFlow实现一个音频分类任务。

前提条件

你需要具备。

  • 声音和音频领域的知识。
  • 中级Python编程技能。
  • 对[TensorFlow]和[Scikit-learn]的理解。
  • 一个[Kaggle]账户。

声音和音频的区别

声音是你听到的东西。它是一种振动,以声波的形式传播。声音的独特属性包括频率、速度、振幅和方向。

当谈到机器学习在这个领域的主要用途时,只有频率和振幅是基本特征。

声波通常可以被简化为正弦波。正弦波向我们展示了一个变量的振幅如何随时间变化。我们用一个麦克风来捕捉声音,并将其转换为电子表示。

音频是声音的电子表示。人类能听到的音频频率范围为20Hz至20kHz。

低于20Hz和高于20KHz的频率对人类来说是听不到的,因为它们要么低,要么太高。

这些样本,随着时间的推移,形成了一个波形。目前,我们无法将机器学习应用于这种波形。

什么是频谱图?

下图显示了一个频谱图。

Spectrogram

频谱图是所有频率随时间变化的可视化表示。

Y轴是以赫兹为单位的频率,而X轴代表时间。颜色代表幅度或振幅。

频谱图中的颜色要么更亮,要么更高,以分贝(计量单位)表示。

我们可以将一个波形转换为频谱图。从技术上讲,这相当于一个图像。研究人员发现,我们可以有效地将计算机视觉技术应用到频谱图上。

这意味着,我们可以用用于图像分类的相同方法来分析声音。

因此,机器学习模型可以通过寻找频谱图中的模式来提取波形中每个时间段的主导音频。

然而,在本教程中,我们将不使用频谱图来寻找模式。我们将使用一个被称为Librosa的库来帮助我们完成这一任务。

现在你对音频有了更多的了解,机器学习可以用来对其进行分类。让我们使用TensorFlow实现一个音频分类任务。

使用TensorFlow实现音频分类

我们将使用Kaggle上的UrbanSound8K数据集

这个数据集包含8,732个标记的城市声音摘录,来自10个类。这十个类包括air_conditionercar_hornchildren_playingdog_barkdrillingengine_idlinggun_shotjackhammersirenstreet_music

Librosa是一个用于音乐和音频分析的开源python包。该库可以给我们提供数据和采样率。

在这里,采样率是指每秒钟的音频的样本数。默认情况下,Librosa将所有音频混合为单声道,并在加载时将其重新采样为22050赫兹。

这在音频分类中起着至关重要的作用,因为不同的声音有不同的采样率。

探索性数据分析(EDA)

我们首先使用以下命令安装Librosa。

pip install librosa

接下来,我们安装其他所需的依赖项,如下所示。

import pandas as pd
import os
import librosa
import librosa.display
import numpy as np
import IPython.display as ipd
import matplotlib.pyplot as plt
%matplotlib inline

从Kaggle加载数据集

我们现在需要将我们在Kaggle上的外部数据加载到Google Colab中。

第1步:前往你的Kaggle账户,下载你的Kaggle API token 。你可以在API部分找到它。当你点击Create New API Token 按钮时,会生成一个kaggle.json文件并下载到你的电脑上。

第2步:将下载的kaggle.json 文件上传到你的Colab项目。

第3步:将KAGGLE_CONFIG_DIR 路径更新为当前工作目录,如图所示。

你通过在终端上输入!pwd ,得到你的当前工作目录。

os.environ['KAGGLE_CONFIG_DIR'] = "/content"

第4步:运行以下Kaggle API来下载数据集。

!kaggle datasets download -d chrisfilo/urbansound8k

下载完数据集后,运行下面的命令来解压。

!unzip urbansound8k.zip

用一个音频文件做实验

让我们用我们的数据集文件夹中的一个随机的儿童玩耍的音频文件,100263-2-0-121.wav ,进行分析。

file_name='fold5/100263-2-0-121.wav'

audio_data, sampling_rate = librosa.load(file_name)
librosa.display.waveplot(audio_data,sr=sampling_rate)
ipd.Audio(file_name)

Librosa为我们提供了audio_datasampling_rate 。让我们看看一个样本音频文件的结果。

audio_data
array([-0.00270751, -0.00303302, -0.00159557, ..., -0.0012889 ,
       -0.00184731, -0.00210062], dtype=float32)

在单声道中,只有一个信号。因此,我们的audio_data 结果显示,Librosa将音频转换成了只有一个维度的整数。

如果是立体声,我们会有两个信号,而这将是一个二维数组。虽然我们在教程中不会使用立体声信号,但重要的是要知道,立体声在音频中通常是首选。

它给我们一种方向感、视角和空间感。Librosa将这些信号简化为单声道,以方便处理。

sampling_rate
22050

默认情况下,Librosa给我们的采样率是22050。

我们现在将使用Pandas库来读取我们的csv文件。

audio_dataset_path='/content/'
metadata=pd.read_csv('UrbanSound8K.csv')
metadata.head()

我们加载我们下载的数据集文件夹中的UrbanSound8K.csv 文件。然后我们把它存储在一个称为metadata 的变量中。

接下来,我们使用head() 方法来查看我们数据集中的前20个文件。

    slice_file_name     fsID    start   end     salience    fold    classID     class
0   100032-3-0-0.wav    100032  0.000000    0.317551    1   5   3   dog_bark
1   100263-2-0-117.wav  100263  58.500000   62.500000   1   5   2   children_playing
2   100263-2-0-121.wav  100263  60.500000   64.500000   1   5   2   children_playing
3   100263-2-0-126.wav  100263  63.000000   67.000000   1   5   2   children_playing
4   100263-2-0-137.wav  100263  68.500000   72.500000   1   5   2   children_playing
5   100263-2-0-143.wav  100263  71.500000   75.500000   1   5   2   children_playing
6   100263-2-0-161.wav  100263  80.500000   84.500000   1   5   2   children_playing
7   100263-2-0-3.wav    100263  1.500000    5.500000    1   5   2   children_playing
8   100263-2-0-36.wav   100263  18.000000   22.000000   1   5   2   children_playing
9   100648-1-0-0.wav    100648  4.823402    5.471927    2   10  1   car_horn
10  100648-1-1-0.wav    100648  8.998279    10.052132   2   10  1   car_horn
11  100648-1-2-0.wav    100648  16.699509   17.104837   2   10  1   car_horn
12  100648-1-3-0.wav    100648  17.631764   19.253075   2   10  1   car_horn
13  100648-1-4-0.wav    100648  25.332994   27.197502   2   10  1   car_horn
14  100652-3-0-0.wav    100652  0.000000    4.000000    1   2   3   dog_bark
15  100652-3-0-1.wav    100652  0.500000    4.500000    1   2   3   dog_bark
16  100652-3-0-2.wav    100652  1.000000    5.000000    1   2   3   dog_bark
17  100652-3-0-3.wav    100652  1.500000    5.500000    1   2   3   dog_bark
18  100795-3-0-0.wav    100795  0.191790    4.191790    1   10  3   dog_bark
19  100795-3-1-0.wav    100795  13.059155   17.059155   1   10  3   dog_bark

我们看到,这些音频文件都是以.wav 文件格式存储的音频文件。它们也被组织在各自的文件类别中。

我们的数据集不应该是不平衡的。我们使用下面的命令进行快速检查,以确保它不是。

metadata['class'].value_counts()
street_music        1000
air_conditioner     1000
jackhammer          1000
engine_idling       1000
drilling            1000
children_playing    1000
dog_bark            1000
siren                929
car_horn             429
gun_shot             374
Name: class, dtype: int64

结果显示,数据集中的大多数类是平衡的。因此,这将是一个可以使用的好数据集。

现在我们已经完成了EDA,我们已经想到这个数据是原始格式的。我们需要对这些数据进行预处理以提取有意义的特征。

然后我们将使用这些提取的特征进行训练,而不是使用原始形式的数据。

数据预处理

为了提取特征,我们将使用Mel-Frequency Cepstral Coefficients(MFCC)算法。

自20世纪80年代以来,这种算法已被广泛用于自动语音和说话人识别。它是由Davis和Mermelstein提出的。

MFCC算法总结了整个窗口大小的频率分布。这使得对所提供的声音的频率和时间特征的分析成为可能。它可以让我们确定分类的特征。

mfccs = librosa.feature.mfcc(y=audio_data, sr=sampling_rate, n_mfcc=40)

n_mfcc 参数表示要返回的MFCCs 的数量。在我们的案例中,我们选择了40。你可以选择任何你想要的值。

mfccs
array([[-4.6613168e+02, -4.6417816e+02, -4.7455182e+02, ...,
        -4.4540848e+02, -4.5221939e+02, -4.5637799e+02],
       [ 1.0846554e+02,  1.1128984e+02,  1.0955853e+02, ...,
         1.1160173e+02,  1.1063791e+02,  1.1319142e+02],
       [-2.5252140e+01, -2.7399439e+01, -3.2546665e+01, ...,
        -3.8440331e+01, -3.4312595e+01, -3.5521683e+01],
       ...,
       [ 2.3573508e+00,  1.6371250e+00,  3.2692363e+00, ...,
         7.8856702e+00,  1.0755114e+01,  1.1197763e+01],
       [-3.2311397e+00, -2.6380532e+00,  4.6177328e-01, ...,
         1.0223865e+01,  1.1984882e+01,  1.3385002e+01],
       [-1.3852274e+01, -1.0576165e+01, -2.1510942e+00, ...,
         2.9695926e+00,  2.1894133e+00,  6.6635776e-01]], dtype=float32)

上面这些模式是根据频率和时间特征从一个音频文件中提取的。

def features_extractor(file):
    audio, sample_rate = librosa.load(file_name, res_type='kaiser_fast') 
    mfccs_features = librosa.feature.mfcc(y=audio, sr=sample_rate, n_mfcc=40)
    mfccs_scaled_features = np.mean(mfccs_features.T,axis=0)
    
    return mfccs_scaled_features

为了从数据集中的所有音频文件中提取特征,我们创建一个列表来存储所有提取的特征。

然后,我们遍历每个音频文件,并使用梅尔-频率Cepstral系数提取特征。

extracted_features=[]
for index_num,row in tqdm(metadata.iterrows()):
    file_name = os.path.join(os.path.abspath(audio_dataset_path),'fold'+str(row["fold"])+'/',str(row["slice_file_name"]))
    final_class_labels=row["class"]
    data=features_extractor(file_name)
    extracted_features.append([data,final_class_labels])

让我们使用Pandas库将整个列表转换成一个数据框架。这将结果转换成表格,以便更直接地进行分析。

extracted_features_df=pd.DataFrame(extracted_features,columns=['feature','class'])
extracted_features_df.head(10)
              feature                                    class
0   [-215.79301, 71.66612, -131.81377, -52.09133, ...   dog_bark
1   [-424.68677, 110.56227, -54.148235, 62.01074, ...   children_playing
2   [-459.56467, 122.800354, -47.92471, 53.265705,...   children_playing
3   [-414.55377, 102.896904, -36.66495, 54.18041, ...   children_playing
4   [-447.397, 115.0954, -53.809113, 61.60859, 1.6...   children_playing
5   [-447.70856, 118.409454, -35.24866, 56.73993, ...   children_playing
6   [-477.1972, 120.63773, -29.692501, 57.051914, ...   children_playing
7   [-464.84656, 117.71454, -30.163269, 50.72254, ...   children_playing
8   [-472.1215, 126.76601, -38.36653, 58.748646, -...   children_playing
9   [-196.18527, 114.94506, -14.661183, 1.2298629,...   car_horn

上面的结果显示了提取的特征和它们各自的类别。

下面的命令将数据集分割成独立数据集和从属数据集,即x和y。

X=np.array(extracted_features_df['feature'].tolist())
y=np.array(extracted_features_df['class'].tolist())

然后我们从TensorFlow和Sklearn导入to_categoricalLabelEncoder 方法。

from tensorflow.keras.utils import to_categorical
from sklearn.preprocessing import LabelEncoder
labelencoder=LabelEncoder()
y=to_categorical(labelencoder.fit_transform(y))

这一步包括使用sklearn的train_test_split 方法将我们的数据集分成训练集和测试集。

from sklearn.model_selection import train_test_split
X_train,X_test,y_train,y_test=train_test_split(X,y,test_size=0.2,random_state=0)

现在我们已经完成了数据预处理,我们现在需要创建我们的模型。

创建模型

在这一步,我们将使用TensorFlow创建一个模型。任何高于2.0.0的TensorFlow版本都可以使用。

我们把它导入到我们的笔记本中,如下图所示。

import tensorflow as tf
print(tf.__version__)
2.6.0

使用TensorFlow,我们导入以下库。

from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Dense,Dropout,Activation,Flatten
from tensorflow.keras.optimizers import Adam
from sklearn import metrics

我们的层将按顺序堆叠。最后一层将有一个softmax激活层,因为它是一个多类分类问题。

model=Sequential()
###first layer
model.add(Dense(100,input_shape=(40,)))
model.add(Activation('relu'))
model.add(Dropout(0.5))
###second layer
model.add(Dense(200))
model.add(Activation('relu'))
model.add(Dropout(0.5))
###third layer
model.add(Dense(100))
model.add(Activation('relu'))
model.add(Dropout(0.5))

###final layer
model.add(Dense(num_labels))
model.add(Activation('softmax'))
model.compile(loss='categorical_crossentropy',metrics=['accuracy'],optimizer='adam')

我们现在可以训练我们的模型了。epochs的数量越多,准确性就越高。对于我们的案例,我们只用200个epochs来训练模型。

from tensorflow.keras.callbacks import ModelCheckpoint
from datetime import datetime 

num_epochs = 200
num_batch_size = 32

checkpointer = ModelCheckpoint(filepath='saved_models/audio_classification.hdf5', 
                               verbose=1, save_best_only=True)
start = datetime.now()

model.fit(X_train, y_train, batch_size=num_batch_size, epochs=num_epochs, validation_data=(X_test, y_test), callbacks=[checkpointer], verbose=1)


duration = datetime.now() - start
print("Training completed in time: ", duration)

我们通过运行下面的代码来获得验证的准确性。

test_accuracy=model.evaluate(X_test,y_test,verbose=0)
print(test_accuracy[1])
00.7870635390281677

我们得到的验证准确率是78.71%。增加训练历时的数量会增加这个准确率的分数。

测试模型

在本节中,我们将执行以下三个步骤。

  • 对测试音频数据进行预处理。它涉及到使用MFCC算法提取特征。
  • 在我们创建的模型的帮助下,预测其类别。
  • 对预测的标签进行反转和转换,得到我们的类别标签。

我们从数据集中随机选择一个狗叫声的音频文件,103076-3-0-0.wav ,用于测试。

在下面的代码中,我们重复我们先前用来预处理音频数据的步骤。

然后我们对它所属的类别进行预测,最后使用scikitlearn中的inverse_transform 方法来给我们预测的标签名称。

filename="fold8/103076-3-0-0.wav"
audio, sample_rate = librosa.load(filename, res_type='kaiser_fast') 
mfccs_features = librosa.feature.mfcc(y=audio, sr=sample_rate, n_mfcc=40)
mfccs_scaled_features = np.mean(mfccs_features.T,axis=0)

print(mfccs_scaled_features)
mfccs_scaled_features=mfccs_scaled_features.reshape(1,-1)
print(mfccs_scaled_features)
print(mfccs_scaled_features.shape)
predicted_label=model.predict(mfccs_scaled_features)
print(predicted_label)
classes_x=np.argmax(predicted_label,axis=1)
prediction_class = labelencoder.inverse_transform(classes_x)
prediction_class
[[1.1630526e-21 5.3596851e-09 2.6831966e-09 9.9984801e-01 4.6294679e-09
  9.3139047e-12 1.5179862e-04 4.0151480e-34 1.1097348e-07 4.4551591e-08]]

array(['dog_bark'], dtype='<U16')

该模型已经正确预测了狗叫声。

总结

音频信号处理给开发者带来了许多挑战。然而,使用像Librosa这样的库使它更容易理解。

你不必为这项任务使用Librosa库。一旦你有了波形,你可以把它转换成频谱图,并使用卷积神经网络(CNN)进行分类。