音乐是将声音和噪音组合在一起以创造和声、旋律、节奏和表现内容的艺术。它经过精心编排,使得人类有时甚至是其他生物能够通过它来表达当下的情感。我们每个人都有自己的播放列表,在旅行、学习、跳舞等时候收听。
简而言之,每一种情感都有不同的流派。那么,今天我们将一起研究如何在 Python 中使用机器学习来实现流派分类的任务。在开始编写代码之前,请先从这个链接下载数据。让我们开始写代码吧。
导入库和数据集
首先,我们需要导入相关的库:
- Pandas:用于导入文件/数据集。
- Matplotlib:用于可视化数据框。
- Numpy:用于执行缩放和相关性等操作。
- Seaborn:用于可视化数据框。
- Librosa:用于可视化音频数据。可以通过 "pip install librosa" 命令安装此库。
# 导入必要的库
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
import librosa.display
# 设置绘图风格和字体,确保图表清晰美观
plt.style.use(‘seaborn‘)
plt.rcParams[‘font.sans-serif‘] = [‘SimHei‘] # 用来正常显示中文标签
plt.rcParams[‘axes.unicode_minus‘] = False # 用来正常显示负号
为什么选择这些库?
在深入代码之前,让我们简单了解一下为什么我们需要这些工具。INLINECODE6f10018f 是处理表格数据的利器,音频特征被提取后通常以 CSV 格式存储。INLINECODE873fb2c3 则是高性能计算的基础。而在音频分析领域,Librosa 是 Python 生态中的标准库,它不仅能加载音频文件,还能提取梅尔频率倒谱系数(MFCC)等关键特征,这对于识别音乐风格至关重要。
现在,运行以下命令来导入数据文件。
# 读取包含音频特征的数据集
music_data = pd.read_csv(‘file.csv‘)
# 展示前5行数据,初步了解数据结构
print("数据集的前5行:")
print(music_data.head(5))
输出示例:
!数据预览
探索性数据分析 (EDA)
在喂给模型数据之前,我们必须先了解数据本身。这一步被称为探索性数据分析(EDA)。
1. 标签分布分析
让我们找出每个音乐标签的数量,看看数据集是否平衡。如果某些流派的样本远多于其他流派,模型可能会产生偏见。
# 统计每个音乐流派(标签)的数量
label_counts = music_data[‘label‘].value_counts()
print("流派分布:
", label_counts)
# 可视化标签分布
plt.figure(figsize=(10, 6))
sns.countplot(x=‘label‘, data=music_data)
plt.title(‘各音乐流派样本数量分布‘)
plt.xlabel(‘音乐流派‘)
plt.ylabel(‘样本数量‘)
plt.xticks(rotation=45)
plt.show()
输出:
blues 100
classical 100
country 100
disco 100
hiphop 100
jazz 100
metal 100
pop 100
reggae 100
rock 100
在这个数据集中,每个流派都有 100 个样本,这意味着我们的数据是平衡的。在实际工作中,如果不平衡,我们可能需要使用过采样或欠采样技术来调整。
2. 音频波形可视化
仅仅看数字是抽象的。我们还可以使用 Librosa 库分析音频的声波。不同的流派在波形图上往往表现出截然不同的特征。例如,重金属音乐的振幅通常比爵士音乐更密集且强烈。
让我们用下面的代码可视化其中的一部分。
import IPython.display as id
# 定义一个可视化函数,避免重复代码
def visualize_audio(path, genre_name, color=‘blue‘):
plt.figure(figsize=(14, 5))
# 加载音频文件
x, sr = librosa.load(path)
# 绘制波形图
librosa.display.waveplot(x, sr=sr, color=color)
plt.title(f‘{genre_name} 的波形图‘)
plt.show()
# 播放音频
print(f"正在播放: {genre_name}")
display(id.Audio(path))
# 分析 Blues 风格
path_blues = ‘genres_original/blues/blues.00000.wav‘
visualize_audio(path_blues, ‘Blues‘, color=‘blue‘)
输出:
正在播放: Blues
对比分析:重金属
接下来,让我们看看 Metal(重金属)风格。注意观察波形的振幅变化和密集程度。
# 分析 Metal 风格
path_metal = ‘genres_original/metal/metal.00000.wav‘
visualize_audio(path_metal, ‘Metal‘, color=‘orange‘)
输出:
正在播放: Metal
通过对比,我们可以直观地看到 Metal 的波形在视觉上更加"厚重"和"充满能量"。这种视觉上的差异正是机器学习模型试图从数学特征中捕捉的信息。
3. 特征相关性分析
我们的数据集包含很多特征,如 INLINECODEc4d9c247, INLINECODE066be502, spectral_centroid_mean 等。这些特征之间可能存在高度相关性。如果两个特征完全相关,那么它们提供的重复信息可能会降低模型效率。
我们可以通过热力图来查看这些特征之间的相关性。
# 重新导入绘图库以避免环境问题
import matplotlib.pyplot as plt
import seaborn as sns
# 筛选出所有包含 ‘mean‘ 的特征列进行分析
spike_cols = [col for col in music_data.columns if ‘mean‘ in col]
# 计算相关性矩阵
corr_matrix = music_data[spike_cols].corr()
# 设置 matplotlib 图形
plt.figure(figsize=(16, 11))
# 绘制热力图
# cmap=‘YlGn‘ 使用黄绿色调,center=0 将颜色中心设为0
sns.heatmap(corr_matrix, cmap=‘YlGn‘, center=0)
plt.title(‘Mean 变量之间的相关性热力图‘, fontsize = 20)
plt.xticks(fontsize = 10, rotation=90)
plt.yticks(fontsize = 10)
plt.show()
输出:
解读技巧: 颜色越深(无论是深绿还是深黄),说明两个特征之间的相关性越强。例如,你会发现 INLINECODE506e9aeb(能量)和 INLINECODE2d56e0ee(响度)通常是高度正相关的。在后续的特征工程中,你可能会考虑剔除其中一个以简化模型。
数据预处理
数据很少是直接可用的。我们需要对其进行清洗和转换。
1. 标签编码
机器学习模型处理数字比处理字符串更高效。目前我们的标签是 "blues", "pop" 等。我们需要使用 LabelEncoder 将它们转换为 0, 1, 2…
from sklearn import preprocessing
# 初始化 LabelEncoder
label_encoder = preprocessing.LabelEncoder()
# 将文本标签转换为整数
# 例如:‘blues‘ -> 0, ‘classical‘ -> 1, ...
music_data[‘label‘] = label_encoder.fit_transform(music_data[‘label‘])
# 查看映射关系,方便后续解读
print("标签映射:", dict(zip(label_encoder.classes_, label_encoder.transform(label_encoder.classes_))))
2. 特征选择
由于“文件名”列只是路径信息,对预测流派没有帮助,甚至可能产生干扰,我们可以将其删除。同时,我们将目标变量 label 分离出来。
# 删除 ‘filename‘ 列,因为它不是特征,仅用于标识文件
# 同时将 ‘label‘ 作为目标变量 y 分离出来
X = music_data.drop([‘label‘, ‘filename‘], axis=1)
y = music_data[‘label‘]
print(f"特征矩阵形状: {X.shape}")
print(f"目标向量形状: {y.shape}")
3. 数据缩放
这是非常关键的一步。不同的特征有不同的量纲。例如,INLINECODE3c4e0881(节奏)可能在 60-200 之间,而 INLINECODEed3c0856 可能在 0-1 之间。如果不进行缩放,数值较大的特征会主导模型的计算过程。
我们将使用 MinMaxScaler 将所有特征压缩到 0 到 1 之间。
# 获取列名,以便在缩放后恢复数据框结构
cols = X.columns
# 初始化 MinMaxScaler
minmax = preprocessing.MinMaxScaler()
# 拟合并转换数据
np_scaled = minmax.fit_transform(X)
# 将缩放后的 numpy 数组转换回 Pandas DataFrame
X = pd.DataFrame(np_scaled, columns=cols)
print("缩放后的前5行数据:")
print(X.head())
常见错误提示: 在缩放时,很多人容易犯的错误是对整个数据集(包括测试集)直接 INLINECODE01c6d87e。这会导致数据泄露,即测试集的信息(均值和方差)"泄露"到了训练过程中。最佳实践是:先在训练集上 INLINECODE62ab3c4f,然后在训练集和测试集上分别 transform。在这个简单的例子中,我们先统一处理,但在实战分割数据时需格外注意。
分割数据集
为了验证模型的性能,我们需要将数据分为训练集和测试集。通常采用 80:20 或 70:30 的比例。
from sklearn.model_selection import train_test_split
# 分割数据:80% 用于训练,20% 用于测试
# random_state 保证每次运行代码时分割结果一致,便于复现
X_train, X_test, y_train, y_test = train_test_split(
X, y,
test_size=0.2,
random_state=42
)
print(f"训练集样本数: {X_train.shape[0]}")
print(f"测试集样本数: {X_test.shape[0]}")
模型构建与训练
对于分类任务,我们有多种选择:决策树、随机森林、支持向量机(SVM)或逻辑回归。在音乐分类任务中,随机森林和支持向量机(SVM)通常表现很好,因为它们能很好地处理高维特征和非线性关系。
这里我们将使用 随机森林分类器。它像是一个专家顾问团,由许多决策树组成,每棵树给出一个判断,最后通过"投票"决定结果。
from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import accuracy_score, classification_report
# 初始化随机森林模型
# n_estimators=100 表示使用 100 棵决策树
# n_jobs=-1 表示使用所有可用的 CPU 核心进行并行计算,加快训练速度
clf = RandomForestClassifier(n_estimators=100, random_state=42, n_jobs=-1)
# 训练模型
print("开始训练模型...")
clf.fit(X_train, y_train)
print("模型训练完成!")
# 在测试集上进行预测
y_pred = clf.predict(X_test)
# 计算准确率
accuracy = accuracy_score(y_test, y_pred)
print(f"模型在测试集上的准确率: {accuracy * 100:.2f}%")
评估模型
准确率虽然直观,但有时具有欺骗性(特别是在数据不平衡时)。让我们看看更详细的分类报告,包括精确率、召回率和 F1 分数。
from sklearn.metrics import confusion_matrix
import seaborn as sns
# 打印详细的分类报告
# target_names 使用 label_encoder 的 inverse_transform 将数字转回流派名称
print("
分类报告:")
print(classification_report(
y_test,
y_pred,
target_names=label_encoder.classes_
))
混淆矩阵可视化
混淆矩阵能让我们清楚地看到模型在哪些流派之间混淆了。例如,模型是否经常把 ‘Rock‘ 误判为 ‘Metal‘?
# 生成混淆矩阵
cm = confusion_matrix(y_test, y_pred)
plt.figure(figsize=(10, 8))
sn.heatmap(cm, annot=True, fmt=‘d‘, cmap=‘Blues‘,
xticklabels=label_encoder.classes_,
yticklabels=label_encoder.classes_)
plt.title(‘混淆矩阵 - 音乐流派分类结果‘)
plt.ylabel(‘真实标签‘)
plt.xlabel(‘预测标签‘)
plt.show()
进阶思路与优化建议
到这里,你已经拥有了一个基础的工作模型。但作为开发者,我们追求的是极致的性能。以下是一些你可以尝试的进阶优化方向:
- 尝试不同的模型:虽然随机森林表现不错,但试试 XGBoost 或 LightGBM 可能会带来惊喜。对于小规模数据集,支持向量机 (SVM) 有时能获得极高的准确率。
- 降维 (PCA):如果你的数据集特征非常多(或者你从音频中提取了成百上千个特征),模型可能会过拟合。使用主成分分析(PCA)将特征压缩到低维空间,有时能提高模型的泛化能力。
- 超参数调优:我们使用了随机森林的默认参数。通过网格搜索寻找最优的 INLINECODEdfc77b37(树的数量)或 INLINECODEa4e2d206(树的深度),通常能提升 2-5% 的准确率。
# 简单的网格搜索示例
from sklearn.model_selection import GridSearchCV
param_grid = {
‘n_estimators‘: [100, 200, 500],
‘max_depth‘: [10, 20, None]
}
grid_search = GridSearchCV(RandomForestClassifier(random_state=42), param_grid, cv=5)
grid_search.fit(X_train, y_train)
print(f"最佳参数: {grid_search.best_params_}")
- 深度学习:如果你想挑战更高难度,可以不使用预先提取的 CSV 特征,而是直接将音频波形或声谱图输入到 卷积神经网络 (CNN) 中。这种方法就像让计算机"看"音乐一样,往往能达到甚至超越人类的识别准确率。
结语
在这篇文章中,我们一起完成了一次从数据加载、可视化分析、预处理到模型训练的完整机器学习流程。我们不仅学会了如何使用 Python 中的 Librosa 和 Scikit-Learn 库,更重要的是,我们掌握了如何像数据科学家一样思考——从直观的波形对比,到严谨的相关性分析,再到模型的评估与优化。
不要停下探索的脚步。试着换一个数据集,或者调整一下代码中的参数,看看你能否让这个分类器变得更加聪明。如果你有任何问题或发现了有趣的实现方式,欢迎随时交流。祝你在机器学习的音乐之旅中玩得开心!
关键要点回顾:
- Librosa 是音频分析的瑞士军刀,一定要掌握它对波形和频谱的绘制。
- 数据预处理(特别是编码和缩放)直接决定了模型的上限,绝不可跳过。
- 随机森林 是处理此类分类任务的优秀基线模型。
- 通过 混淆矩阵 分析错误案例,是改进模型最直接的途径。