Python数据分析案例65——基于深度学习的音频文件分类(音频文件特征提取和模型构建)

背景

之前都是做的表格数据分类回归,文本的分类,图片的分类,还没有做过音频文件的分类。本次案例是3年前帮同学写论文做的,这次拿出来分享一下。 由于是早年刚开始学深度学习写的代码,肯定有很多不成熟的地方,我也懒得修改了,就原汁原味的秀出来吧。


数据介绍

本次数据装到代码同目录的"./戏曲"文件夹里面,文件夹里面又有很多不同音乐的类别

每个文件夹里面都有100首MP3歌曲文件。我们就取:"京剧 越剧 黄梅戏 秦腔 豫剧 评剧 "6个文件夹里面的歌曲吧。

我们要做的就是对这个600首歌曲提取特征,然后进行神经网络的模型训练和分类评估。

这些歌曲都是我上淘宝买的....由于文件太大,某面包平台放不下,这里就不提供数据的获取了。


音频特征提取

先导入包。音频特征提取主要依靠librosa包,它会提取一堆音频界描述音频的特征,什么频谱滚降点,频谱中心,时变频谱图,过零率,Mel-Scaled频率.......我也不太懂了,反正就是特征,提取就完事了。

python 复制代码
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
import librosa
import os

然后循环遍历,提取每个文件的特征的代码:

python 复制代码
genres = '京剧 越剧 黄梅戏 秦腔 豫剧 评剧 '.split()
data_set = []
label_set = []
label2id = {genre:i for i,genre in enumerate(genres)}
#id2label = {i:genre for i,genre in enumerate(genres)}

print(label2id)
for g in genres:
    print(g)
    for filename in os.listdir(f'./戏曲/{g}/'):
        songname = f'./戏曲/{g}/{filename}'
        y, sr = librosa.load(songname, mono=True, offset=15,duration=10)
        chroma_stft = librosa.feature.chroma_stft(y=y, sr=sr)#12
        chroma_cqt = librosa.feature.chroma_cqt(y=y, sr=sr)#12
        chroma_cens = librosa.feature.chroma_cens(y=y, sr=sr)#12
        melspectrogram=librosa.feature.melspectrogram(y=y,sr=sr)#128

        rmse = librosa.feature.rms(y=y)#1
        spec_cent = librosa.feature.spectral_centroid(y=y, sr=sr)#1
        spec_bw = librosa.feature.spectral_bandwidth(y=y, sr=sr)#1
        spec_cont=librosa.feature.spectral_contrast(y=y, sr=sr)#7
        spec_flat=librosa.feature.spectral_flatness(y=y)#1
        rolloff = librosa.feature.spectral_rolloff(y=y, sr=sr)#1

        poly=librosa.feature.poly_features(y=y,sr=sr)#2
        tonnetz=librosa.feature.tonnetz(y=y,sr=sr)#6
        zcr = librosa.feature.zero_crossing_rate(y)#1
        mfcc = librosa.feature.mfcc(y=y, sr=sr)#20
        x=np.concatenate((chroma_stft.T ,chroma_cqt.T,chroma_cens.T,melspectrogram.T,rmse.T,spec_cent.T,spec_bw.T,spec_cont.T,
                  spec_flat.T,rolloff.T,poly.T,tonnetz.T,zcr.T,mfcc.T),axis=1)
        data_set.append(x)
        label_set.append(label2id[g])

这个吃cpu,有点慢,

我们看看数据的量和每条数据的形状:

python 复制代码
print(len(data_set))
data_set[0].shape

可以看到是600个数据,就是600首歌曲。然后每一个数据的形状是(431,205),431是时间步长,205是提取的特征维度,总共有205个特征。

可以检查一下,有没有歌曲的形状不是这样的:

python 复制代码
for i in range(len(data_set)):
    if data_set[i].shape!=(431,205):
        print(i,data_set[i].shape)
        

没有打印的话就表示没有。数据很规整。

如果有的话就用前一个数据填充一下吧:

python 复制代码
#data_set[204][428:,:].shape
#data_set[432]=np.r_[data_set[432],data_set[431][428:,:]]

然后转为数据矩阵,查看形状

python 复制代码
X = np.array(data_set,dtype='float')
y = np.array(label_set)
X.shape,y.shape

进行储存

python 复制代码
#df1=pd.DataFrame(X)
df2=pd.DataFrame(y)
np.save(file='特征10.npy',arr=X)
df2.to_csv('标签2.csv',index=False)

标签是1维的,可以存为csv,但是X是3维的,表格是二维的解构存不了,就存为np数组好了。


神经网络分类

导入包

python 复制代码
import numpy as np
import os
import random as rn
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns

from sklearn.model_selection import train_test_split
from sklearn.preprocessing import MinMaxScaler
from sklearn.metrics import confusion_matrix
from sklearn.preprocessing import StandardScaler

from keras.models import Model, Sequential
from keras.layers import GRU, Dense,Conv1D, MaxPooling1D,GlobalMaxPooling1D,Embedding,Dropout,Flatten,Conv2D, MaxPooling2D
from keras.callbacks import EarlyStopping
from tensorflow.keras import regularizers
from keras.utils.np_utils import to_categorical
from tensorflow.keras  import optimizers

import tensorflow as tf
import keras
#from keras.datasets import mnist, reuters, boston_housing
# from keras.models import Sequential
# from keras.layers import Dense, Dropout, Flatten
#from keras.layers import Conv1D, MaxPooling2D

设定随机数种子

python 复制代码
def set_my_seed():
    os.environ['PYTHONHASHSEED'] = '0'
    np.random.seed(1)
    rn.seed(12345)
    tf.random.set_seed(123)

读取数据

python 复制代码
label_set=pd.read_csv('标签.csv')
feature= np.load(file='特征10.npy')
y=np.array(label_set)

feature.shape,y.shape

X进行标准化

python 复制代码
def check(X):
    two=X.reshape((X.shape[0]*X.shape[1],X.shape[2]))
    scaler=StandardScaler()
    scaler.fit(two)
    two_s=scaler.transform(two)
    three=two_s.reshape((X.shape[0],X.shape[1],X.shape[2]))
    return three
X=check(feature)

我们构建二维卷积神经网络,所以要把数据转为4维结构

python 复制代码
#二维卷积才运行
X=X.reshape(X.shape[0],X.shape[1],X.shape[2],1)

划分训练集和测试集

python 复制代码
X_trainval, X_test, y_trainval, y_test = train_test_split(X, y, stratify=y,test_size=0.15, random_state=0)
X_train, X_val, y_train, y_val = train_test_split(X_trainval, y_trainval,stratify= y_trainval,test_size=0.2, random_state=321)
X_trainval.shape, X_train.shape, X_val.shape, X_test.shape

y进行独热编码

python 复制代码
y_trainval = to_categorical(y_trainval)
y_train = to_categorical(y_train)
y_val = to_categorical(y_val)

y_test_original = y_test
y_test = to_categorical(y_test)

y_train.shape, y_val.shape, y_test.shape

数据形状都没问题,开始构建模训练。

我这里写了很多模型,主要就是用二维卷积就行了,

python 复制代码
set_my_seed()
model = Sequential()

#多层感知机
# model.add(Dense(512, activation='relu', kernel_regularizer=regularizers.l2(0.0001),input_shape=(X_train.shape[1], )))
# model.add(Dropout(0.25))
# model.add(Dense(256, activation='relu',kernel_regularizer=regularizers.l2(0.0001)))
# model.add(Dropout(0.25)) # 加入Dropout层抗过拟合

#循环网络
#model.add(GRU(128,input_shape=(X_train.shape[-2],X_train.shape[-1])))
#model.add(GRU(16,return_sequences=True, ))
#一维卷积
# model.add(Conv1D(128,9,activation='relu',input_shape=(X_train.shape[-2],X_train.shape[-1])))
# model.add(Conv1D(64,7,activation='relu'))
# model.add(MaxPooling1D(5))
# model.add(Conv1D(32,7,activation='relu'))
# model.add(GlobalMaxPooling1D())

#二维卷积
model.add(Conv2D(32,(3,3),activation='relu',input_shape=(X_train.shape[1],X_train.shape[2],1)))
model.add(Conv2D(16,(3,3),activation='relu'))
model.add(MaxPooling2D(2,2))
model.add(Flatten())
model.add(Dense(32,activation='relu'))

model.add(Dense(6,activation='softmax'))
model.compile(optimizer='Adam', loss='binary_crossentropy',metrics=['accuracy'],run_eagerly=True)

查看模型信息

python 复制代码
model.summary()

设定早停机制

python 复制代码
earlystop = EarlyStopping(monitor='val_loss', min_delta=0, patience=5)

训练模型

python 复制代码
hist=model.fit(X_train, y_train,batch_size=32,epochs=100,
          validation_data=(X_val, y_val),shuffle=False,
               callbacks=[earlystop],
               verbose=1)

查看训练第几轮的时候验证集损失最小

python 复制代码
val_loss = hist.history['val_loss']
index_min = np.argmin(val_loss)
index_min

画出来

python 复制代码
plt.plot(hist.history['loss'], 'k', label='Training Loss')
plt.plot(val_loss, 'b', label='Validation Loss')
plt.axvline(index_min, linestyle='--', color='k')
plt.title('Training and Validation Loss')
plt.xlabel('Epochs')
plt.ylabel('Loss')
plt.legend()

查看最大的准确率是多少

python 复制代码
val_accuracy = hist.history['val_accuracy']
np.max(val_accuracy)
python 复制代码
index_max = np.argmax(val_accuracy)
index_max

最大的准确率是在第8轮。

可视化

python 复制代码
plt.plot(hist.history['accuracy'], 'k', label='Training Accuracy')
plt.plot(val_accuracy, 'b', label='Validation Accuracy')
plt.axvline(index_max, linestyle='--', color='k')
plt.title('Training and Validation Accuracy')
plt.xlabel('Epochs')
plt.ylabel('Accuracy')
plt.legend()

得到预测概率,并且进行最大索引化转为类别

python 复制代码
prob = model.predict(X_test)
prob[1]

#pred = model.predict_classes(X_test)
pred=np.argmax(prob,axis=-1)
pred[:5]

模型保存

python 复制代码
#from keras.models import load_model
model.save('音频特征卷积.h5')

画混淆矩阵

python 复制代码
y_test_original=y_test_original.reshape(-1,)
table = pd.crosstab(y_test_original, pred, rownames=['Actual'], colnames=['Predicted'])
table

可视化

python 复制代码
sns.heatmap(table, cmap='Blues', annot=True, fmt='d')
plt.tight_layout()

分类就做完了。整体的准确率只有60%多,一般般的效果。


特征分析

音频特征是可以进行可视化的。我这里在6只种戏曲里面都任取1首,放入'./样本'文件夹里面,然后对他们的特征进行可视化对比。

导入包

python 复制代码
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns

import librosa
import librosa.display
import os
%matplotlib inline

plt.rcParams['font.sans-serif'] = ['KaiTi']
plt.rcParams['font.serif'] = ['KaiTi']
plt.rcParams['axes.unicode_minus'] = False # 解决保存图像是负号'-'显示为方块的问题,或者转换负号为字符串
sns.set_style("darkgrid",{"font.sans-serif":['KaiTi', 'Arial']})

振幅图

python 复制代码
plt.figure(figsize=(18, 12))
for i,songname in enumerate(os.listdir(f'./样本')):
    filename = f'./样本/{songname}'
    y, sr = librosa.load(filename , mono=True, duration=30)
    plt.subplot(3,2,i+1)
    
    librosa.display.waveplot(y, sr=sr)
    plt.title(songname.strip('.mp3'),fontsize=24)
    plt.tight_layout()
plt.savefig('时间-振幅图.png',dpi=300)

时变频谱图

python 复制代码
plt.figure(figsize=(18, 12))
for i,songname in enumerate(os.listdir(f'./样本')):
    filename = f'./样本/{songname}'
    y, sr = librosa.load(filename , mono=True, duration=30)
    X = librosa.stft(y)
    Xdb = librosa.amplitude_to_db(abs(X))
    
    plt.subplot(3,2,i+1)
    librosa.display.specshow(Xdb, sr=sr, x_axis='time', y_axis='hz')
    plt.colorbar()
    plt.title(songname.strip('.mp3'),fontsize=24)
    plt.tight_layout()
plt.savefig('信号的时变频谱图.png',dpi=500)

过零点

python 复制代码
#plt.figure(figsize=(18, 12))
s=[]
n=[]
for i,songname in enumerate(os.listdir(f'./样本')):
    filename = f'./样本/{songname}'
    n.append(songname.strip('.mp3'))
    y, sr = librosa.load(filename , mono=True, duration=30)
    
    zero_crossings = librosa.zero_crossings(y, pad=False)
    sumzero=sum(zero_crossings)
    s.append(sumzero)
    
df=pd.DataFrame({'戏曲':s},index=n)
df.plot.bar()
plt.xticks(fontsize=20,rotation=0)
plt.tight_layout()
plt.savefig('过零点.png',dpi=200)

频谱中心图

python 复制代码
#频谱中心代表声音的"质心",又称为频谱一阶距。频谱中心的值越小,表明越多的频谱能量集中在低频范围内
from sklearn.preprocessing import minmax_scale

def normalize(y, axis=0):
    #scaler=MinMaxScaler()
    return minmax_scale(y, axis=axis)
    
    
plt.figure(figsize=(18, 12))
for i,songname in enumerate(os.listdir(f'./样本')):
    filename = f'./样本/{songname}'
    y, sr = librosa.load(filename , mono=True, duration=30)
    spectral_centroids = librosa.feature.spectral_centroid(y, sr=sr)[0]

    frames = range(len(spectral_centroids))
    t = librosa.frames_to_time(frames, sr=sr)
    
    
    plt.subplot(3,2,i+1)
    librosa.display.waveplot(y, sr=sr, alpha=0.4)
    plt.plot(t, normalize(spectral_centroids), color='r')
    
    plt.title(songname.strip('.mp3'),fontsize=24)
    plt.tight_layout()
plt.savefig('频谱中心图.png',dpi=500)

频谱滚降点

python 复制代码
plt.figure(figsize=(18, 12))
for i,songname in enumerate(os.listdir(f'./样本')):
    filename = f'./样本/{songname}'
    y, sr = librosa.load(filename , mono=True, duration=30)
    spectral_rolloff = librosa.feature.spectral_rolloff(y, sr=sr)[0]

    plt.subplot(3,2,i+1)
    librosa.display.waveplot(y, sr=sr, alpha=0.4)
    plt.plot(t, normalize(spectral_rolloff), color='r')
    
    plt.title(songname.strip('.mp3'),fontsize=24)
    plt.tight_layout()
plt.savefig('频谱滚降点.png',dpi=500)

梅尔频率倒谱系数

python 复制代码
plt.figure(figsize=(18, 12))
for i,songname in enumerate(os.listdir(f'./样本')):
    filename = f'./样本/{songname}'
    y, sr = librosa.load(filename , mono=True, duration=30)
    mfccs = librosa.feature.mfcc(y, sr=sr)
    
    plt.subplot(3,2,i+1)
    librosa.display.specshow(mfccs, sr=sr, x_axis='time')
    
    plt.title(songname.strip('.mp3'),fontsize=24)
    plt.tight_layout()
plt.savefig('梅尔频率倒谱系数.png',dpi=500)

色度频率图

python 复制代码
plt.figure(figsize=(18, 12))
for i,songname in enumerate(os.listdir(f'./样本')):
    filename = f'./样本/{songname}'
    y, sr = librosa.load(filename , mono=True, duration=30)
    mfccs = librosa.feature.mfcc(y, sr=sr)
    
    plt.subplot(3,2,i+1)
    hop_length = 512
    chromagram = librosa.feature.chroma_stft(y, sr=sr, hop_length=hop_length)
    librosa.display.specshow(chromagram, x_axis='time', y_axis='chroma', hop_length=hop_length, cmap='coolwarm')
    
    plt.title(songname.strip('.mp3'),fontsize=24)
    plt.tight_layout()
plt.savefig('色度频率图.png',dpi=500)

Mel-Scaled频率图

python 复制代码
# Mel-Scaled频率图
plt.figure(figsize=(18, 12))
for i,songname in enumerate(os.listdir(f'./样本')):
    filename = f'./样本/{songname}'
    y, sr = librosa.load(filename , mono=True, duration=30)
    D = np.abs(librosa.stft(y)) ** 2  # stft频谱
    S = librosa.feature.melspectrogram(S=D)  # 使用stft频谱求Mel频谱

    plt.subplot(3,2,i+1)
    librosa.display.specshow(librosa.power_to_db(S, ref=np.max),
                            y_axis='mel', fmax=8000, x_axis='time')
    plt.colorbar(format='%+2.0f dB')
    plt.title(songname.strip('.mp3'),fontsize=24)
    plt.tight_layout()
plt.savefig('Mel-Scaled频率图.png',dpi=500)

功率谱案例图

python 复制代码
#功率谱案例图
S = np.abs(librosa.stft(y))
# print(librosa.power_to_db(S**2))
plt.figure()
plt.subplot(2,1,1)
librosa.display.specshow(S**2, sr=sr, y_axis='log')
plt.colorbar()
plt.title('Power spectrogram')

plt.subplot(2,1,2)
# 相对于峰值功率计算db
librosa.display.specshow(librosa.power_to_db(S**2, ref=np.max), sr=sr, y_axis='log', x_axis='time')
plt.colorbar(format='%+2.0f dB')
plt.title('Log Power spectrogram')
plt.set_cmap('autumn')
plt.tight_layout()
plt.show()

频率谱图

python 复制代码
# 频率谱图
plt.figure()

plt.subplot(2, 1, 1)
librosa.display.specshow(D, y_axis='linear')
plt.colorbar(format='%+2.0f dB')
plt.title('Linear freq. Power Spec.') # 线性频率功率谱

plt.subplot(2, 1, 2)
librosa.display.specshow(librosa.amplitude_to_db(np.abs(librosa.stft(y)), ref=np.max), y_axis='log')
plt.colorbar(format='%+2.0f dB')
plt.title('Log freq. Power Spec.') # 对数频率功率谱
plt.show()

由于音频文件是典型的序列文件,其实最经典应该是用循环神经网络系列来做分类,LSTM,GRU这些,还可以加注意力机制等。本文重点在于构建特征,和音频分析,而不是神经网络模型,模型构建可以进行更多的尝试,也是很简单的。

创作不易,看官觉得写得还不错的话点个关注和赞吧,本人会持续更新python数据分析领域的代码文章~(需要定制类似的代码可私信)

相关推荐
测试老哥8 分钟前
基于Pytest接口自动化的requests模块项目实战以及接口关联方法
自动化测试·软件测试·python·测试工具·职场和发展·测试用例·pytest
成为不掉头发的工程师1 小时前
conda下载与pip下载的区别
开发语言·python
pblh1231 小时前
spark 3.4.4 利用Spark ML中的交叉验证、管道流实现鸢尾花分类预测案例选取最优模型
分类·数据挖掘·spark-ml
lisacumt1 小时前
【spark】pyspark kerberos 案例,即pyspark-utils客户端工具类
大数据·python·spark
AI原吾1 小时前
探索Python的HTTP之旅:揭秘Requests库的神秘面纱
开发语言·python·http·requests
_可乐无糖1 小时前
如何还原 HTTP 请求日志中的 URL 编码参数?详解 %40 到 @
网络·python·https
云帆码上学2 小时前
Django快速上手:从零到一构建Web应用
python·django
2739920292 小时前
python(四)os模块、sys模块
python
gxchai2 小时前
用PythonSudio在控件中添加、删除控件,并传递参数(以ScrollBox中添加删除按钮为例)
开发语言·python