【机器学习】实战1——逻辑回归实现音乐分类器
·
一、探索性数据分析 EDA(Exploratory Data Analysis)
# 导入音乐信号处理和可视化所需的库
import numpy as np # 新增:用于处理傅里叶变换的复数结果
from scipy import fft # 导入傅里叶变换模块,用于分析音频频率成分
from scipy.io import wavfile # 导入读取WAV音频文件的模块
from matplotlib.pyplot import specgram # 导入频谱图绘制函数,展示音频时频分布
import matplotlib.pyplot as plt # 导入绘图库,用于可视化分析结果
# ================= 新增:解决中文显示乱码配置 =================
plt.rcParams['font.sans-serif'] = ['SimHei'] # Windows系统:设置黑体为默认中文字体(Mac/Linux可改为 'WenQuanYi Zen Hei')
plt.rcParams['axes.unicode_minus'] = False # 解决负号 '-' 显示为方块的问题
# =============================================================
# 读取指定路径的蓝调(blues)音乐音频文件
# wavfile.read()返回两个值:sample_rate(采样率,单位Hz)和X(音频原始数据,时域信号)
(sample_rate, X) = wavfile.read("D:/genres/blues/converted/blues.00000.au.wav")
# 打印音频核心信息:采样率、音频数据形状(采样点数量)
print('采样率(sample_rate,单位Hz):', sample_rate, ' 音频数据形状(X.shape,采样点数量):', X.shape)
# 打印音频原始时域数据(前部分,完整数据量较大)
print('音频原始时域信号(X):', X[:100], '...(仅展示前100个采样点,完整数据共{}个采样点)'.format(X.shape[0]))
# 再次明确打印数据形状,方便观察采样点总数
print('音频数据形状(X.shape):', X.shape, '→ 含义:共{}个采样点,单声道(1维数据)'.format(X.shape[0]))
# 创建画布,设置尺寸(10x4英寸)和分辨率(80dpi)
plt.figure(figsize=(10, 4), dpi=80)
plt.xlabel("时间(秒)") # x轴标签:时间维度
plt.ylabel("频率(Hz)") # y轴标签:频率维度
plt.grid(True, linestyle='-', color='0.75') # 添加网格线,便于观察
# 绘制频谱图:将时域音频转换为时频分布,Fs=采样率,xextent限制时间范围为0-30秒
specgram(X, Fs=sample_rate, xextent=(0, 30))
plt.title("蓝调音乐(blues.00000)频谱图 → 颜色深浅表示对应频率的能量强度") # 添加中文标题说明
plt.show() # 显示图像
# 定义频谱图绘制函数:根据音乐类型和文件编号读取音频并绘制频谱图
# 参数g:音乐类型(如classical、jazz等);参数n:文件编号(如'00001')
def plotSpec(g, n):
# 拼接音频文件路径,读取对应类型+编号的音频文件
sample_rate, X = wavfile.read("D:/genres/"+g+"/converted/"+g+'.'+n+'.au.wav')
# 绘制该音频的频谱图,时间范围限制为0-30秒
specgram(X, Fs=sample_rate, xextent=(0, 30))
# 设置子图标题:音乐类型+样本编号(取n的最后一位)+ 频谱图说明
plt.title(f"{g}音乐-{n[-1]}号样本 频谱图")
# 创建大画布:6行3列共18个子图,用于对比不同音乐类型的频谱特征
plt.figure(num=None, figsize=(18, 9), dpi=80, facecolor='w', edgecolor='k')
# 第1-3个子图:古典音乐(clasusical)的3个样本(00001-00003)
plt.subplot(6, 3, 1); plotSpec("classical", '00001')
plt.subplot(6, 3, 2); plotSpec("classical", '00002')
plt.subplot(6, 3, 3); plotSpec("classical", '00003')
# 第4-6个子图:爵士音乐(jazz)的3个样本
plt.subplot(6, 3, 4); plotSpec("jazz", '00001')
plt.subplot(6, 3, 5); plotSpec("jazz", '00002')
plt.subplot(6, 3, 6); plotSpec("jazz", '00003')
# 第7-9个子图:乡村音乐(country)的3个样本
plt.subplot(6, 3, 7); plotSpec("country", '00001')
plt.subplot(6, 3, 8); plotSpec("country", '00002')
plt.subplot(6, 3, 9); plotSpec("country", '00003')
# 第10-12个子图:流行音乐(pop)的3个样本
plt.subplot(6, 3, 10); plotSpec("pop", '00001')
plt.subplot(6, 3, 11); plotSpec("pop", '00002')
plt.subplot(6, 3, 12); plotSpec("pop", '00003')
# 第13-15个子图:摇滚音乐(rock)的3个样本
plt.subplot(6, 3, 13); plotSpec("rock", '00001')
plt.subplot(6, 3, 14); plotSpec("rock", '00002')
plt.subplot(6, 3, 15); plotSpec("rock", '00003')
# 第16-18个子图:金属音乐(metal)的3个样本
plt.subplot(6, 3, 16); plotSpec("metal", '00001')
plt.subplot(6, 3, 17); plotSpec("metal", '00002')
plt.subplot(6, 3, 18); plotSpec("metal", '00003')
# 自动调整子图间距:避免标题/标签重叠,pad=子图间距,h_pad=垂直间距
plt.tight_layout(pad=0.4, w_pad=0, h_pad=1.0)
plt.suptitle("6种音乐类型(古典/爵士/乡村/流行/摇滚/金属)频谱图对比 → 每行3个样本", y=1.02) # 总标题
plt.show() # 显示所有子图
# 读取流行音乐(pop)的指定音频文件,用于深入分析时域→频域特征
sample_rate, X = wavfile.read("D:/genres/pop/converted/pop.00000.au.wav")
# 创建画布:2行1列子图,用于对比频谱图和傅里叶变换结果
plt.figure(num=None, figsize=(9, 6), dpi=80, facecolor='w', edgecolor='k')
# 第1个子图:流行音乐的频谱图(时频分布)
plt.subplot(2, 1, 1)
plt.xlabel("时间(秒)")
plt.ylabel("频率(Hz)")
plt.title("流行音乐(pop.00000)频谱图 → 时频能量分布")
specgram(X, Fs=sample_rate, xextent=(0, 30)) # 时间范围0-30秒
# 第2个子图:傅里叶变换结果(频域幅度分布)
plt.subplot(2, 1, 2)
plt.xlabel("频率(Hz)")
plt.xlim((0, 3000)) # 限制频率范围为0-3000Hz(人类听觉敏感区间)
plt.ylabel("幅度(Amplitude)")
plt.title("流行音乐傅里叶变换 → 频率-幅度分布(0-3000Hz)")
# 修复:傅里叶变换返回复数,取绝对值后再绘图(获取真实幅度值)
plt.plot(fft(X, sample_rate).real) # 或用 np.abs(fft(X, sample_rate)),两种方式均可
# plt.plot(np.abs(fft(X, sample_rate))) # 备选方案:更通用的复数幅度计算
plt.tight_layout() # 调整间距
plt.show() # 显示两个子图
运行结果:



'''
采样率(sample_rate,单位Hz): 22050 音频数据形状(X.shape,采样点数量): (661794,)
音频原始时域信号(X): [ 240 544 250 -115 -74 -307 -743 -733 -1025 -1419 -1046 -761
-769 -540 -314 -504 -607 -509 -1063 -1436 -1080 -957 -969 -700
-394 -31 419 382 43 -61 -220 -233 -328 -1142 -1487 -969
-786 -589 -1 181 -10 -53 -163 -482 -616 -904 -1370 -1179
-776 -600 -351 -283 -367 -467 -587 -718 -968 -908 -603 -582
-844 -1200 -1038 -563 -462 -473 -591 -849 -956 -1230 -1541 -1468
-1550 -1559 -975 -463 -551 -576 -61 -386 -1423 -1457 -1145 -1264
-1392 -1520 -1508 -1484 -1610 -1532 -1374 -1193 -923 -849 -1270 -1755
-1429 -1030 -1139 -992] ...(仅展示前100个采样点,完整数据共661794个采样点)
音频数据形状(X.shape): (661794,) → 含义:共661794个采样点,单声道(1维数据)
进程已结束,退出代码为 0
'''
简洁版
import numpy as np
from scipy import fft
from scipy.io import wavfile
from matplotlib.pyplot import specgram
import matplotlib.pyplot as plt
plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False
(sample_rate, X) = wavfile.read("D:genres/blues/converted/blues.00000.au.wav")
print('采样率(sample_rate,单位Hz):', sample_rate, ' 音频数据形状(X.shape,采样点数量):', X.shape)
print('音频原始时域信号(X):', X[:100], '...(仅展示前100个采样点,完整数据共{}个采样点)'.format(X.shape[0]))
print('音频数据形状(X.shape):', X.shape, '→ 含义:共{}个采样点,单声道(1维数据)'.format(X.shape[0]))
plt.figure(figsize=(10, 4), dpi=80)
plt.xlabel("时间(秒)")
plt.ylabel("频率(Hz)")
plt.grid(True, linestyle='-', color='0.75')
specgram(X, Fs=sample_rate, xextent=(0, 30))
plt.title("蓝调音乐(blues.00000)频谱图 → 颜色深浅表示对应频率的能量强度")
plt.show()
def plotSpec(g, n):
sample_rate, X = wavfile.read("D:genres/"+g+"/converted/"+g+'.'+n+'.au.wav')
specgram(X, Fs=sample_rate, xextent=(0, 30))
plt.title(f"{g}音乐-{n[-1]}号样本 频谱图")
plt.figure(num=None, figsize=(18, 9), dpi=80, facecolor='w', edgecolor='k')
plt.subplot(6, 3, 1); plotSpec("classical", '00001')
plt.subplot(6, 3, 2); plotSpec("classical", '00002')
plt.subplot(6, 3, 3); plotSpec("classical", '00003')
plt.subplot(6, 3, 4); plotSpec("jazz", '00001')
plt.subplot(6, 3, 5); plotSpec("jazz", '00002')
plt.subplot(6, 3, 6); plotSpec("jazz", '00003')
plt.subplot(6, 3, 7); plotSpec("country", '00001')
plt.subplot(6, 3, 8); plotSpec("country", '00002')
plt.subplot(6, 3, 9); plotSpec("country", '00003')
plt.subplot(6, 3, 10); plotSpec("pop", '00001')
plt.subplot(6, 3, 11); plotSpec("pop", '00002')
plt.subplot(6, 3, 12); plotSpec("pop", '00003')
plt.subplot(6, 3, 13); plotSpec("rock", '00001')
plt.subplot(6, 3, 14); plotSpec("rock", '00002')
plt.subplot(6, 3, 15); plotSpec("rock", '00003')
plt.subplot(6, 3, 16); plotSpec("metal", '00001')
plt.subplot(6, 3, 17); plotSpec("metal", '00002')
plt.subplot(6, 3, 18); plotSpec("metal", '00003')
plt.tight_layout(pad=0.4, w_pad=0, h_pad=1.0)
plt.suptitle("6种音乐类型(古典/爵士/乡村/流行/摇滚/金属)频谱图对比 → 每行3个样本", y=1.02)
plt.show()
sample_rate, X = wavfile.read("D:genres/pop/converted/pop.00000.au.wav")
plt.figure(num=None, figsize=(9, 6), dpi=80, facecolor='w', edgecolor='k')
plt.subplot(2, 1, 1)
plt.xlabel("时间(秒)")
plt.ylabel("频率(Hz)")
plt.title("流行音乐(pop.00000)频谱图 → 时频能量分布")
specgram(X, Fs=sample_rate, xextent=(0, 30))
plt.subplot(2, 1, 2)
plt.xlabel("频率(Hz)")
plt.xlim((0, 3000))
plt.ylabel("幅度(Amplitude)")
plt.title("流行音乐傅里叶变换 → 频率-幅度分布(0-3000Hz)")
plt.plot(fft(X, sample_rate).real)
plt.tight_layout()
plt.show()
二、特征工程、数据预处理
读取指定音乐文件,提取FFT(快速傅里叶变换)频域特征,并保存为numpy格式文件
import numpy as np # 导入numpy库,用于数值计算和数组处理(保存特征需用到)
from scipy import fft # 导入傅里叶变换模块,将音频时域信号转为频域特征
from scipy.io import wavfile # 导入读取WAV音频文件的模块
def create_fft(g, n):
"""
函数功能:读取指定音乐文件,提取FFT(快速傅里叶变换)频域特征,并保存为numpy格式文件
参数说明:
g: str - 音乐类型(如'classical'古典、'jazz'爵士等)
n: int - 音乐样本编号(0-99,对应每种类型100个样本)
"""
# 拼接音频文件路径:rad = 根目录/音乐类型/converted/音乐类型.5位编号.au.wav
# str(n).zfill(5):将编号补零为5位字符串(如n=0→'00000',n=1→'00001'),保证文件名格式统一
rad = "D:/genres/"+g+"/converted/"+g+'.'+str(n).zfill(5)+'.au.wav'
# 读取音频文件:返回采样率(sample_rate,单位Hz)和音频时域数据(X,1维数组,存储每个采样点的振幅)
sample_rate, X = wavfile.read(rad)
# 提取FFT频域特征:
# 1. fft(X):对时域信号X做快速傅里叶变换,将信号从"时间-振幅"转为"频率-幅度"
# 2. abs(...):取复数结果的绝对值(FFT返回复数,绝对值表示频率成分的幅度强度)
# 3. [:1000]:取前1000个频率点的特征(FFT结果对称,前半部分已包含完整频率信息;取1000个特征平衡效率和区分度)
fft_features = abs(fft(X)[:1000])
# 拼接特征保存路径:sad = 训练集目录/音乐类型.5位编号.fft(npy格式)
sad = "d:/trainset/"+g+'.'+str(n).zfill(5)+".fft"
# 保存FFT特征为numpy数组文件(.npy格式):便于后续机器学习模型直接加载使用,无需重复计算
np.save(sad, fft_features)
# 定义需要处理的6种音乐类型列表(与数据集目录结构对应)
genre_list = ['classical', 'jazz', 'country', 'pop', 'rock', 'metal']
# 遍历每种音乐类型
for g in genre_list:
# 遍历每种类型的100个样本(编号0-99,共100个文件)
for n in range(100):
# 调用函数生成当前音乐样本的FFT特征并保存
create_fft(g, n)
# 代码执行完成后,会在 d:/trainset/ 目录下生成 6类×100个=600个 .fft.npy 特征文件
# 每个文件包含1000维FFT频域特征,后续可用于训练音乐分类模型(如SVM、逻辑回归等)
简洁版
import numpy as np
from scipy import fft
from scipy.io import wavfile
def create_fft(g, n):
rad = "D:/genres/"+g+"/converted/"+g+'.'+str(n).zfill(5)+'.au.wav'
sample_rate, X = wavfile.read(rad)
fft_features = abs(fft(X)[:1000])
sad = "d:/trainset/"+g+'.'+str(n).zfill(5)+".fft"
np.save(sad, fft_features)
genre_list = ['classical', 'jazz', 'country', 'pop', 'rock', 'metal']
for g in genre_list:
for n in range(100):
create_fft(g, n)
三、训练、保存模型
# 三、训练、保存模型
# 导入所需库
import numpy as np # 用于数组处理和数值计算(模型训练需输入numpy数组格式数据)
from sklearn.linear_model import LogisticRegression # 导入逻辑回归模型(用于音乐类型分类)
import pickle # 用于序列化保存训练好的模型,后续可直接加载使用
from pprint import pprint # 导入格式化打印工具(本代码未实际使用,保留原导入)
from scipy.io import wavfile # 导入音频读取模块(本代码未实际使用,保留原导入)
from scipy import fft # 导入傅里叶变换模块(本代码未实际使用,保留原导入)
# 定义音乐类型列表:与之前特征提取的音乐类型一一对应,顺序一致
genre_list = ["classical", "jazz", "country", "pop", "rock", "metal"]
# 初始化特征矩阵X和标签向量y:X存储所有样本的FFT特征,y存储对应样本的类别标签
X = [] # 特征矩阵:每行对应一个样本,每列对应一个FFT特征(共1000维)
y = [] # 标签向量:每个元素对应样本的音乐类型索引(如classical→0,jazz→1,依此类推)
# 遍历所有音乐类型和对应样本,加载FFT特征并构建X和y
for g in genre_list: # 循环遍历6种音乐类型
for n in range(100): # 循环遍历每种类型的100个样本(编号0-99)
# 拼接FFT特征文件路径:对应之前保存的.npy格式特征文件
# str(n).zfill(5):将样本编号补零为5位字符串,保证与保存的文件名格式一致(如0→'00000')
rad = "D:/StudyMaterials/python/python-sklearn/trainset/" + g + "." + str(n).zfill(5) + ".fft.npy"
# 加载FFT特征:np.load读取.npy文件,返回1000维的特征数组
fft_features = np.load(rad)
# 将当前样本的特征添加到特征矩阵X
X.append(fft_features)
# 将当前样本的类别标签添加到标签向量y:用genre_list的索引作为标签(便于模型训练)
y.append(genre_list.index(g))
# 将X和y转换为numpy数组格式:sklearn模型要求输入为numpy数组
X = np.array(X) # 转换后形状:(600, 1000) → 600个样本(6类×100个),每个样本1000维特征
y = np.array(y) # 转换后形状:(600,) → 每个元素为0-5的整数(对应6种音乐类型)
# 初始化逻辑回归模型:默认参数,适用于多分类任务(自动处理6类分类)
model = LogisticRegression()
# 训练模型:输入特征矩阵X和标签向量y,模型学习特征与音乐类型的映射关系
model.fit(X, y)
# 保存训练好的模型:使用pickle序列化模型,后续无需重复训练可直接加载
output = open('model.pkl', 'wb') # 以二进制写入模式打开文件(.pkl为模型文件后缀)
pickle.dump(model, output) # 将训练好的模型写入文件
output.close() # 关闭文件流,确保模型保存完成
# 执行完成后,当前目录会生成model.pkl文件,后续可通过pickle.load加载使用该分类模型
简洁版
import numpy as np
from sklearn.linear_model import LogisticRegression
import pickle
from pprint import pprint
from scipy.io import wavfile
from scipy import fft
genre_list = ["classical", "jazz", "country", "pop", "rock", "metal"]
X = []
y = []
for g in genre_list:
for n in range(100):
rad = "D:/StudyMaterials/python/python-sklearn/trainset/"+g+"."+str(n).zfill(5)+".fft.npy"
fft_features = np.load(rad)
X.append(fft_features)
y.append(genre_list.index(g))
X = np.array(X)
y = np.array(y)
model = LogisticRegression()
model.fit(X, y)
output = open('model.pkl', 'wb')
pickle.dump(model, output)
output.close()
四、运用模型、进行测试
#四、运用模型、进行测试
# 导入必要的库(需确保与训练代码环境一致)
import pickle
from pprint import pprint
import numpy as np
from scipy.io import wavfile
from scipy import fft
# 定义音乐类型列表:需与训练模型时的列表顺序完全一致,用于将预测索引转换为具体类型
genre_list = ["classical", "jazz", "country", "pop", "rock", "metal"]
# ===================== 加载训练好的分类模型 =====================
# 以二进制读取模式打开保存的模型文件(model.pkl为训练阶段生成的模型文件)
pkl_file = open('model.pkl', 'rb')
# 反序列化加载模型:将保存的模型文件还原为可用于预测的LogisticRegression模型实例
model_loaded = pickle.load(pkl_file)
# 格式化打印模型信息:展示模型的参数、配置等(用于调试/验证模型加载是否正常)
pprint(model_loaded)
# 关闭文件流:释放文件资源,避免内存泄漏
pkl_file.close()
# ===================== 读取并预处理测试音频文件 =====================
# 打印提示信息:标记开始读取测试音频文件
print('Starting read wavfile...')
# 定义测试音频文件名(可切换不同音频测试分类效果)
# music_name = 'heibao-wudizirong-remix.wav' # 测试用例1:黑豹《无地自容》Remix版(预期:rock/metal)
music_name = 'small-apple.wav' # 测试用例2:《小苹果》(预期:pop)
# music_name = 'xiaobang.wav' # 测试用例3:肖邦钢琴曲(预期:classical)
# 读取测试音频文件:参数为音频文件完整路径,返回两个值
# sample_rate:音频采样率(单位Hz,如22050);X:音频时域原始数据(数组形式)
sample_rate, X = wavfile.read("D:/StudyMaterials/python/python-sklearn/trainset/sample/"+music_name)
# 打印原始音频数据形状:用于调试,若为立体声会是二维数组(采样点数量×2),单声道为一维数组
print(X.shape)
# 重塑音频数据为一维数组:
# np.reshape(X, (1, -1)):将任意形状的X转为1行,列数自动计算(-1表示自适应)
# [0]:取转换后二维数组的第一行,最终得到一维数组,适配FFT处理逻辑(与训练时的数据格式一致)
X = np.reshape(X, (1, -1))[0]
# 打印重塑后的数据形状:验证是否为一维数组
print(X.shape)
# 打印采样率和重塑后的音频时域数据(便于调试,查看数据范围/长度)
print(sample_rate, X)
# ===================== 提取测试音频的FFT特征(与训练时保持一致) =====================
# 1. fft(X):对一维时域音频数据做快速傅里叶变换,转为频域复数数组
# 2. abs(...):取复数的绝对值,得到频率成分的幅度(物理意义:各频率的能量强度)
# 3. [:1000]:截取前1000个频率点的特征,与训练时提取特征的维度保持一致(保证模型输入维度匹配)
test_fft_features = abs(fft(X)[:1000])
# 打印采样率、测试FFT特征、特征长度:验证特征维度是否为1000(与训练一致)
print(sample_rate, test_fft_features, len(test_fft_features))
# ===================== 使用模型预测音乐类型 =====================
# model_loaded.predict():模型预测方法,输入需为二维数组(样本数×特征数)
# [test_fft_features]:将一维特征数组转为二维(1个样本×1000维特征)
# [0]:取预测结果数组的第一个元素(唯一的预测结果),得到音乐类型对应的索引(0-5)
result_index = model_loaded.predict([test_fft_features])[0]
# 根据预测索引,从genre_list中取出对应的音乐类型名称并打印
print(genre_list[result_index])
简洁版
import pickle
from pprint import pprint
import numpy as np
from scipy.io import wavfile
from scipy import fft
genre_list = ["classical", "jazz", "country", "pop", "rock", "metal"]
pkl_file = open('model.pkl', 'rb')
model_loaded = pickle.load(pkl_file)
pprint(model_loaded)
pkl_file.close()
print('Starting read wavfile...')
music_name = 'small-apple.wav'
sample_rate, X = wavfile.read("D:/StudyMaterials/python/python-sklearn/trainset/sample/"+music_name)
print(X.shape)
X = np.reshape(X, (1, -1))[0]
print(X.shape)
print(sample_rate, X)
test_fft_features = abs(fft(X)[:1000])
print(sample_rate, test_fft_features, len(test_fft_features))
result_index = model_loaded.predict([test_fft_features])[0]
print(genre_list[result_index])
更多推荐
所有评论(0)