2. 音频分类入门
任务说明
本案例使用ESC-50数据集的10个类别(每类400条样本中的一部分),构建一个基于梅尔频谱图(Mel Spectrogram)与简单CNN的环境声音分类器。你将完成从音频文件到分类结果的全流程:音频加载、特征提取、模型训练与评估。
环境依赖
需要安装:torch、torchaudio、librosa、numpy、pandas、scikit-learn。ESC-50数据集需从官方仓库下载,将audio文件夹与meta/esc50.csv放在本案例同一目录下。
实现代码
以下代码完整实现了数据加载、特征提取、CNN训练与评估。运行前请确认ESC-50数据路径正确。
```python import os import torch import torchaudio import torch.nn as nn import numpy as np import pandas as pd from torch.utils.data import Dataset, DataLoader from sklearn.metrics import accuracy_score # ========== 配置 ========== DATA_DIR = "esc50" # 包含 audio/ 和 meta/esc50.csv SAMPLE_RATE = 16000 N_MELS = 64 N_FFT = 512 HOP_LENGTH = 256 MAX_LEN = 16000 * 2 # 2秒音频 BATCH_SIZE = 32 EPOCHS = 10 LR = 1e-3 CLASSES = ["dog", "rain", "crying_baby", "clock_tick", "sneezing", "rooster", "sea_waves", "fire_crackling", "helicopter", "chainsaw"] NUM_CLASSES = len(CLASSES) # ========== 数据集准备 ========== def load_esc50_subset(csv_path, audio_dir, selected_classes): df = pd.read_csv(csv_path) df = df[df["category"].isin(selected_classes)] # 每类取前40条,共400条 df = df.groupby("category").head(40).reset_index(drop=True) return df class AudioDataset(Dataset): def __init__(self, df, audio_dir): self.df = df self.audio_dir = audio_dir self.label_map = {c: i for i, c in enumerate(CLASSES)} def __len__(self): return len(self.df) def __getitem__(self, idx): row = self.df.iloc[idx] wav_path = os.path.join(self.audio_dir, row["filename"]) waveform, sr = torchaudio.load(wav_path) if sr != SAMPLE_RATE: resampler = torchaudio.transforms.Resample(sr, SAMPLE_RATE) waveform = resampler(waveform) # 统一长度:截断或填充 if waveform.size(1) > MAX_LEN: waveform = waveform[:, :MAX_LEN] else: pad = MAX_LEN - waveform.size(1) waveform = torch.nn.functional.pad(waveform, (0, pad)) # 梅尔频谱图 mel_spec = torchaudio.transforms.MelSpectrogram( sample_rate=SAMPLE_RATE, n_fft=N_FFT, hop_length=HOP_LENGTH, n_mels=N_MELS)(waveform) log_mel = torch.log(mel_spec + 1e-9) # [1, n_mels, time] label = self.label_map[row["category"]] return log_mel, label # ========== CNN模型 ========== class AudioCNN(nn.Module): def __init__(self, num_classes): super().__init__() self.features = nn.Sequential( nn.Conv2d(1, 16, kernel_size=3, padding=1), nn.BatchNorm2d(16), nn.ReLU(), nn.MaxPool2d(2), # [16, 32, 62] nn.Conv2d(16, 32, kernel_size=3, padding=1), nn.BatchNorm2d(32), nn.ReLU(), nn.MaxPool2d(2), # [32, 16, 31] nn.Conv2d(32, 64, kernel_size=3, padding=1), nn.BatchNorm2d(64), nn.ReLU(), nn.AdaptiveAvgPool2d((1, 1)) ) self.classifier = nn.Linear(64, num_classes) def forward(self, x): x = self.features(x) x = x.view(x.size(0), -1) return self.classifier(x) # ========== 训练与评估 ========== def train(): df = load_esc50_subset( os.path.join(DATA_DIR, "meta/esc50.csv"), os.path.join(DATA_DIR, "audio"), CLASSES) # 按类别分层划分训练/验证 from sklearn.model_selection import train_test_split train_df, val_df = train_test_split(df, test_size=0.2, stratify=df["category"], random_state=42) train_ds = AudioDataset(train_df, os.path.join(DATA_DIR, "audio")) val_ds = AudioDataset(val_df, os.path.join(DATA_DIR, "audio")) train_loader = DataLoader(train_ds, batch_size=BATCH_SIZE, shuffle=True) val_loader = DataLoader(val_ds, batch_size=BATCH_SIZE) model = AudioCNN(NUM_CLASSES) criterion = nn.CrossEntropyLoss() optimizer = torch.optim.Adam(model.parameters(), lr=LR) for epoch in range(EPOCHS): model.train() total_loss = 0 for x, y in train_loader: optimizer.zero_grad() out = model(x) loss = criterion(out, y) loss.backward() optimizer.step() total_loss += loss.item() # 验证 model.eval() preds, labels = [], [] with torch.no_grad(): for x, y in val_loader: out = model(x) preds.extend(torch.argmax(out, dim=1).numpy()) labels.extend(y.numpy()) acc = accuracy_score(labels, preds) print(f"Epoch {epoch+1}/{EPOCHS} | Loss: {total_loss/len(train_loader):.4f} | Val Acc: {acc:.4f}") # 保存模型 torch.save(model.state_dict(), "audio_cnn.pth") print("模型已保存至 audio_cnn.pth") if __name__ == "__main__": train() ```运行与验证
运行方式:在终端执行 python audio_classifier.py(将上述代码保存为此文件名)。首次运行会自动加载ESC-50音频并提取梅尔频谱特征。
通过条件:训练完成后,验证集准确率应达到70%以上(10类均衡分类的随机基线为10%)。若准确率低于60%,可尝试增加训练轮次至20或调整学习率为5e-4。
预期输出示例:训练日志中每轮显示验证准确率,最终模型文件audio_cnn.pth生成即表示流程闭环。
常见问题
- 内存不足:若加载400条音频时内存溢出,可将
head(40)改为head(20)(每类20条,共200条),仍可完成训练闭环。 - 音频长度不足2秒:ESC-50中部分音频为5秒,代码会截取前2秒;若音频短于2秒则自动填充零值。
- 特征维度不匹配:若修改了
N_MELS或MAX_LEN,需同步调整CNN中池化层后的维度,或将AdaptiveAvgPool2d保留为全局池化以避免维度计算。