41、音频样本分类的深度学习探索

音频样本分类的深度学习探索

1. 线性SVM的困境

线性支持向量机(SVM)在处理当前特征时失效了,原因在于特征似乎并非线性可分。虽然没有尝试径向基函数(RBF,高斯核)SVM,但读者可以自行尝试。若进行尝试,需注意有两个超参数需要调整:C和γ。

2. 使用传统神经网络

此前尚未尝试传统神经网络。可以像之前那样使用 sklearn MLPClassifier 类,但现在是展示如何在Keras中实现传统网络的好时机,代码如下:

import keras
from keras.models import Sequential
from keras.layers import Dense, Dropout, Flatten
from keras import backend as K
import numpy as np

batch_size = 32
num_classes = 10
epochs = 16
nsamp = (882,1)

x_train = np.load("esc10_raw_train_audio.npy")
y_train = np.load("esc10_raw_train_labels.npy")
x_test = np.load("esc10_raw_test_audio.npy")
y_test = np.load("esc10_raw_test_labels.npy")

x_train = (x_train.astype('float32') + 32768) / 65536
x_test = (x_test.astype('float32') + 32768) / 65536

y_train = keras.utils.to_categorical(y_train, num_classes)
y_test = keras.utils.to_categorical(y_test, num_classes)

model = Sequential()
model.add(Dense(1024, activation='relu', input_shape=nsamp))
model.add(Dropout(0.5))
model.add(Dense(512, activation='relu'))
model.add(Dropout(0.5))
model.add(Flatten())
model.add(Dense(num_classes, activation='softmax'))

model.compile(loss=keras.losses.categorical_crossentropy,
              optimizer=keras.optimizers.Adam(),
              metrics=['accuracy'])

model.fit(x_train, y_train,
          batch_size=batch_size,
          epochs=epochs,
          verbose=0,
          validation_data=(x_test, y_test))

score = model.evaluate(x_test, y_test, verbose=0)
print('Test accuracy:', score[1])

加载必要的模块后,加载数据并像处理经典模型那样进行缩放。接着构建模型架构,仅需要全连接层(Dense)和丢弃层(Dropout),在最终的softmax输出之前添加了一个扁平化层(Flatten)以消除额外维度。遗憾的是,该模型并未带来改善,准确率仅为27.6%。

3. 使用一维卷积神经网络

经典模型和传统神经网络都不太理想,接下来尝试将一维卷积神经网络(1D CNN)应用于该数据集,看看效果是否更好。除了输入数据的结构不同外,一维CNN与二维CNN的唯一区别是将 Conv2D MaxPooling2D 替换为 Conv1D MaxPooling1D 。第一个尝试的模型代码如下:

import keras
from keras.models import Sequential
from keras.layers import Dense, Dropout, Flatten
from keras.layers import Conv1D, MaxPooling1D
import numpy as np

batch_size = 32
num_classes = 10
epochs = 16
nsamp = (882,1)

x_train = np.load("esc10_raw_train_audio.npy")
y_train = np.load("esc10_raw_train_labels.npy")
x_test = np.load("esc10_raw_test_audio.npy")
y_test = np.load("esc10_raw_test_labels.npy")

x_train = (x_train.astype('float32') + 32768) / 65536
x_test = (x_test.astype('float32') + 32768) / 65536

y_train = keras.utils.to_categorical(y_train, num_classes)
y_test = keras.utils.to_categorical(y_test, num_classes)

model = Sequential()
model.add(Conv1D(32, kernel_size=3, activation='relu',
                 input_shape=nsamp))
model.add(MaxPooling1D(pool_size=3))
model.add(Dropout(0.25))
model.add(Flatten())
model.add(Dense(512, activation='relu'))
model.add(Dropout(0.5))
model.add(Dense(num_classes, activation='softmax'))

model.compile(loss=keras.losses.categorical_crossentropy,
              optimizer=keras.optimizers.Adam(),
              metrics=['accuracy'])

history = model.fit(x_train, y_train,
                    batch_size=batch_size,
                    epochs=epochs,
                    verbose=1,
                    validation_data=(x_test[:160], y_test[:160]))

score = model.evaluate(x_test[160:], y_test[160:], verbose=0)
print('Test accuracy:', score[1])

该模型同样加载并预处理数据集。这种架构称为浅层架构,有一个包含32个滤波器、核大小为3的卷积层,随后是一个池化核大小为3的最大池化层,接着是丢弃层和扁平化层,再之后是一个有512个节点并带有丢弃层的全连接层,最后以softmax层结束。
训练16个周期,批量大小为32,保留训练历史以检查损失和验证性能随周期的变化。1600个测试样本中,10%用于训练验证,90%用于整体准确率评估。还会尝试将 Conv1D 的核大小从3变化到33,以找到最适合训练数据的核大小。

另外定义了四种架构,分别为中等架构(medium)、深度架构0(deep0)、深度架构1(deep1)和深度架构2(deep2),代码如下:

# medium
model = Sequential()
model.add(Conv1D(32, kernel_size=3, activation='relu',
                 input_shape=nsamp))
model.add(Conv1D(64, kernel_size=3, activation='relu'))
model.add(Conv1D(64, kernel_size=3, activation='relu'))
model.add(MaxPooling1D(pool_size=3))
model.add(Dropout(0.25))
model.add(Flatten())
model.add(Dense(512, activation='relu'))
model.add(Dropout(0.5))
model.add(Dense(num_classes, activation='softmax'))

# deep0
model = Sequential()
model.add(Conv1D(32, kernel_size=3, activation='relu',
                 input_shape=nsamp))
model.add(Conv1D(64, kernel_size=3, activation='relu'))
model.add(Conv1D(64, kernel_size=3, activation='relu'))
model.add(MaxPooling1D(pool_size=3))
model.add(Dropout(0.25))
model.add(Conv1D(64, kernel_size=3, activation='relu'))
model.add(Conv1D(64, kernel_size=3, activation='relu'))
model.add(MaxPooling1D(pool_size=3))
model.add(Dropout(0.25))
model.add(Flatten())
model.add(Dense(512, activation='relu'))
model.add(Dropout(0.5))
model.add(Dense(num_classes, activation='softmax'))

# deep1
model = Sequential()
model.add(Conv1D(32, kernel_size=3, activation='relu',
                 input_shape=nsamp))
model.add(Conv1D(64, kernel_size=3, activation='relu'))
model.add(Conv1D(64, kernel_size=3, activation='relu'))
model.add(MaxPooling1D(pool_size=3))
model.add(Dropout(0.25))
model.add(Conv1D(64, kernel_size=3, activation='relu'))
model.add(Conv1D(64, kernel_size=3, activation='relu'))
model.add(MaxPooling1D(pool_size=3))
model.add(Dropout(0.25))
model.add(Conv1D(64, kernel_size=3, activation='relu'))
model.add(Conv1D(64, kernel_size=3, activation='relu'))
model.add(MaxPooling1D(pool_size=3))
model.add(Dropout(0.25))
model.add(Flatten())
model.add(Dense(512, activation='relu'))
model.add(Dropout(0.5))
model.add(Dense(num_classes, activation='softmax'))

# deep2
model = Sequential()
model.add(Conv1D(32, kernel_size=3, activation='relu',
                 input_shape=nsamp))
model.add(Conv1D(64, kernel_size=3, activation='relu'))
model.add(Conv1D(64, kernel_size=3, activation='relu'))
model.add(MaxPooling1D(pool_size=3))
model.add(Dropout(0.25))
model.add(Conv1D(64, kernel_size=3, activation='relu'))
model.add(Conv1D(64, kernel_size=3, activation='relu'))
model.add(MaxPooling1D(pool_size=3))
model.add(Dropout(0.25))
model.add(Conv1D(64, kernel_size=3, activation='relu'))
model.add(Conv1D(64, kernel_size=3, activation='relu'))
model.add(MaxPooling1D(pool_size=3))
model.add(Dropout(0.25))
model.add(Conv1D(64, kernel_size=3, activation='relu'))
model.add(Conv1D(64, kernel_size=3, activation='relu'))
model.add(MaxPooling1D(pool_size=3))
model.add(Dropout(0.25))
model.add(Flatten())
model.add(Dense(512, activation='relu'))
model.add(Dropout(0.5))
model.add(Dense(num_classes, activation='softmax'))

多次训练不同模型,每次改变第一个 Conv1D 的核大小,得到的结果如下表所示:
| 核大小 | 浅层架构 | 中等架构 | 深度架构0 | 深度架构1 | 深度架构2 |
| ---- | ---- | ---- | ---- | ---- | ---- |
| 3 | 44.51 | 41.39 | 48.75 | 54.03 | 9.93 |
| 5 | 43.47 | 41.74 | 44.72 | 53.96 | 48.47 |
| 7 | 38.47 | 40.97 | 46.18 | 52.64 | 49.31 |
| 9 | 41.46 | 43.06 | 46.88 | 48.96 | 9.72 |
| 11 | 39.65 | 40.21 | 45.21 | 52.99 | 10.07 |
| 13 | 42.71 | 41.67 | 46.53 | 50.56 | 52.57 |
| 15 | 40.00 | 42.78 | 46.53 | 50.14 | 47.08 |
| 33 | 27.57 | 42.22 | 41.39 | 48.75 | 9.86 |

从表中可以看出,随着模型深度增加,准确率总体呈上升趋势,但到深度架构2时情况开始变差,部分模型无法收敛,准确率接近随机猜测。深度架构1在所有核大小下表现最佳,核大小为3时,在五种架构中的三种表现最佳。这意味着一维CNN的最佳组合是初始核大小为3和深度架构1。
仅对深度架构1训练16个周期,若增加训练周期是否会有改善呢?将深度架构1训练60个周期并绘制训练和验证损失及误差随周期的变化图,发现验证集的损失在大约18个周期后开始爆炸式增长,训练损失持续下降,这是明显的过拟合现象。过拟合的可能原因是训练集规模有限,即使进行了数据增强,也只有6400个样本。验证误差在初始下降后基本保持不变,结论是使用一维向量处理该数据集,准确率很难超过54%。

4. 频谱图的应用

回到增强后的音频文件集,此前构建数据集时,只保留了两秒的音频且每隔100个样本取一个,最佳准确率略高于50%。
如果处理输入音频文件中一小段(如200毫秒)的声音样本,可以使用样本向量计算傅里叶变换。傅里叶变换能告诉我们构成信号的频率,简单信号(如陶笛声音)的傅里叶变换在特定频率上有几个峰值,复杂信号(如语音或音乐)则有许多不同频率的峰值。
傅里叶变换是复数值的,取其绝对值可得到代表特定频率能量的实数,即信号的功率谱。简单音调可能只在少数频率上有能量,而钹声或白噪声的能量则更均匀地分布在所有频率上。
功率谱仅代表一小段时间的频谱,而音频样本长达五秒,因此使用频谱图。频谱图是由代表各个频谱的列组成的图像,x轴表示时间,y轴表示频率,像素颜色与该时间该频率的能量成正比。
要为增强后的音频文件创建频谱图,需要一个新工具 sox ,它是一个命令行工具,在Ubuntu Linux系统中可能已安装,若未安装可使用以下命令安装:

sudo apt-get install sox

使用Python脚本调用 sox 生成所需的频谱图图像,处理训练图像的代码如下:

import os
import numpy as np
from PIL import Image

rows = 100
cols = 160
flist = [i[:-1] for i in open("augmented_train_filelist.txt")]
N = len(flist)
img = np.zeros((N,rows,cols,3), dtype="uint8")
lbl = np.zeros(N, dtype="uint8")
p = []

for i,f in enumerate(flist):
    src, c = f.split()
    os.system("sox %s -n spectrogram" % src)
    im = np.array(Image.open("spectrogram.png").convert("RGB"))
    im = im[42:542,58:858,:]
    im = Image.fromarray(im).resize((cols,rows))
    img[i,:,:,:] = np.array(im)
    lbl[i] = int(c)
    p.append(os.path.abspath(src))
    os.system("rm -rf spectrogram.png")

p = np.array(p)
idx = np.argsort(np.random.random(N))
img = img[idx]
lbl = lbl[idx]
p = p[idx]

np.save("esc10_spect_train_images.npy", img)
np.save("esc10_spect_train_labels.npy", lbl)
np.save("esc10_spect_train_paths.npy", p)

首先定义频谱图的大小为100×160像素,加载训练文件列表,创建NumPy数组来存储频谱图图像和标签,列表 p 用于存储每个频谱图的源文件路径。然后遍历文件列表,调用 sox 将声音文件转换为频谱图图像,加载输出的频谱图并去除边框信息,调整大小后存储,同时记录标签和文件路径。生成所有频谱图后,删除临时文件,随机打乱图像顺序,最后将图像、标签和路径名保存到磁盘。对测试集重复此过程。
从可视化的频谱图来看,不同类别的频谱图通常可以区分,这为使用二维CNN进行分类提供了希望。

5. 频谱图分类

要处理频谱图数据集,需要使用二维卷积神经网络(2D CNN)。一个可能的起点是将浅层一维CNN架构转换为二维,即将 Conv1D 替换为 Conv2D MaxPooling1D 替换为 MaxPooling2D 。但这样得到的模型有3070万个参数,数量过多。因此,选择一个更深但参数更少的架构,并探索不同的第一个卷积层核大小的影响,代码如下:

import keras
from keras.models import Sequential
from keras.layers import Dense, Dropout, Flatten
from keras.layers import Conv2D, MaxPooling2D
import numpy as np

batch_size = 16
num_classes = 10
epochs = 16
img_rows, img_cols = 100, 160
input_shape = (img_rows, img_cols, 3)

x_train = np.load("esc10_spect_train_images.npy")
y_train = np.load("esc10_spect_train_labels.npy")
x_test = np.load("esc10_spect_test_images.npy")
y_test = np.load("esc10_spect_test_labels.npy")

x_train = x_train.astype('float32') / 255
x_test = x_test.astype('float32') / 255

y_train = keras.utils.to_categorical(y_train, num_classes)
y_test = keras.utils.to_categorical(y_test, num_classes)

model = Sequential()
model.add(Conv2D(32, kernel_size=(3,3), activation='relu',
                 input_shape=input_shape))
model.add(Conv2D(64, (3, 3), activation='relu'))
model.add(MaxPooling2D(pool_size=(2, 2)))
model.add(Dropout(0.25))
model.add(Conv2D(64, (3, 3), activation='relu'))
model.add(MaxPooling2D(pool_size=(2, 2)))
model.add(Dropout(0.25))
model.add(Flatten())
model.add(Dense(128, activation='relu'))
model.add(Dropout(0.5))
model.add(Dense(num_classes, activation='softmax'))

model.compile(loss=keras.losses.categorical_crossentropy,
              optimizer=keras.optimizers.Adam(),
              metrics=['accuracy'])

history = model.fit(x_train, y_train,
                    batch_size=batch_size, epochs=epochs,
                    verbose=0, validation_data=(x_test, y_test))

score = model.evaluate(x_test, y_test, verbose=0)
print('Test accuracy:', score[1])
model.save("esc10_cnn_deep_3x3_model.h5")

这里使用的小批量大小为16,训练16个周期,采用Adam优化器。模型架构包含两个卷积层、一个带丢弃层的最大池化层、另一个卷积层和第二个带丢弃层的最大池化层,在softmax输出之前有一个包含128个节点的全连接层。

测试第一个卷积层的两种核大小:3×3和7×7。3×3配置的代码如上所示,将 (3,3) 替换为 (7,7) 即可改变核大小。之前的一维卷积运行都只对模型进行一次训练评估,由于随机初始化,即使其他条件不变,每次训练的结果也会略有不同。对于二维CNN,每个模型训练六次,并将整体准确率表示为均值 ± 均值标准误差,结果如下表所示:
| 核大小 | 得分 |
| ---- | ---- |
| 3×3 | 78.78 ± 0.60% |
| 7×7 | 78.44 ± 0.72% |

这表明使用3×3或7×7的初始卷积层核大小没有显著差异,因此后续选择3×3的核大小。

绘制一次二维CNN在频谱图上训练的训练和验证损失(顶部)及误差(底部)随周期的变化图,和一维CNN情况类似,几个周期后验证误差开始增加。

二维CNN的表现明显优于一维CNN,准确率达到79%,而一维CNN只有54%。不过,这个准确率对于很多应用来说可能还不够,但对某些应用可能是可以接受的。需要注意的是,数据和硬件存在一些限制,因为采用仅使用CPU的方法,限制了训练模型的时间。如果使用GPU,性能可能会提高25倍,但如果要在嵌入式系统上运行模型,可能无法使用GPU,就需要选择更小的模型。

下面是整个音频样本分类的流程mermaid图:

graph LR
    A[数据加载] --> B[线性SVM尝试]
    B --> C{是否成功}
    C -- 否 --> D[传统神经网络尝试]
    D --> E[一维CNN尝试]
    E --> F[生成频谱图]
    F --> G[二维CNN尝试]
    C -- 是 --> H[结束]
    D --> I{是否满意结果}
    I -- 否 --> E
    I -- 是 --> H
    E --> J{是否满意结果}
    J -- 否 --> F
    J -- 是 --> H
    G --> K{是否满意结果}
    K -- 是 --> H
    K -- 否 --> L[调整模型或数据]
    L --> G

综上所述,在音频样本分类任务中,从线性SVM到传统神经网络,再到一维CNN和二维CNN,不断尝试不同的模型架构和数据处理方法,逐步提高了分类准确率。虽然目前的准确率还有提升空间,但通过合理选择模型和利用频谱图等数据处理技巧,已经取得了一定的成果。未来可以进一步探索更多的模型架构和数据增强方法,以提高分类性能。同时,考虑硬件资源的限制,选择合适的模型进行部署。

【四轴飞行器】非线性三自由度四轴飞行器模拟器研究(Matlab代码实现)内容概要:本文围绕非线性三自由度四轴飞行器的建模与仿真展开,重点介绍了基于Matlab的飞行器动力学模型构建与控制系统设计方法。通过对四轴飞行器非线性运动方程的推导,建立其在三维空间中的姿态与位置动态模型,并采用数值仿真手段实现飞行器在复杂环境下的行为模拟。文中详细阐述了系统状态方程的构建、控制输入设计以及仿真参数设置,并结合具体代码实现展示了如何对飞行器进行稳定控制与轨迹跟踪。此外,文章还提到了多种优化与控制策略的应用背景,如模型预测控制、PID控制等,突出了Matlab工具在无人机系统仿真中的强大功能。; 适合人群:具备一定自动控制理论基础和Matlab编程能力的高校学生、科研人员及从事无人机系统开发的工程师;尤其适合从事飞行器建模、控制算法研究及相关领域研究的专业人士。; 使用场景及目标:①用于四轴飞行器非线性动力学建模的教学与科研实践;②为无人机控制系统设计(如姿态控制、轨迹跟踪)提供仿真验证平台;③支持高级控制算法(如MPC、LQR、PID)的研究与对比分析; 阅读建议:建议读者结合文中提到的Matlab代码与仿真模型,动手实践飞行器建模与控制流程,重点关注动力学方程的实现与控制器参数调优,同时可拓展至多自由度或复杂环境下的飞行仿真研究。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值