一、探索性数据分析 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])

更多推荐