1. 音频分类项目概述音频分类是机器学习中一个非常有趣的应用领域它可以让计算机学会识别不同种类的声音。想象一下你的智能音箱能够区分门铃声和狗叫声或者你的手机能自动识别播放的音乐类型——这些都是音频分类技术的实际应用。在这个项目中我们将使用PyTorch框架从最基础的音频信号处理开始一步步构建一个完整的音频分类系统。特别的是我们会采用梅尔频谱Mel Spectrogram作为音频的特征表示方式这比直接处理原始音频波形要有效得多。为什么选择梅尔频谱因为人类的听觉系统对声音频率的感知并不是线性的。我们更容易察觉低频声音的微小变化而对高频变化的敏感度较低。梅尔频谱正是模拟了人耳的这种特性将线性频率转换为更符合人类听觉感知的梅尔频率。2. 环境配置与依赖安装在开始之前我们需要准备好开发环境。这个项目主要依赖以下几个Python库PyTorch深度学习框架Librosa音频处理库TorchaudioPyTorch的音频处理扩展Numpy和Matplotlib数据处理和可视化pip install torch torchaudio librosa numpy matplotlib如果你使用GPU进行训练建议安装支持CUDA的PyTorch版本。可以通过以下命令检查PyTorch是否正确识别了你的GPUimport torch print(torch.cuda.is_available()) # 应该返回True对于音频文件的播放和录制你可能还需要安装pyaudiopip install pyaudio3. 音频处理基础知识3.1 时域与频域表示音频信号本质上是随时间变化的压力波在时域中表现为波形图。但单纯观察波形很难提取有意义的特征。通过傅里叶变换(FT)我们可以将信号转换到频域看到不同频率成分的强度。不过标准傅里叶变换丢失了时间信息。短时傅里叶变换(STFT)通过将音频分成小段并分别进行傅里叶变换既保留了频率信息又保留了时间信息。STFT的结果可以表示为声谱图(spectrogram)这是一种二维表示横轴是时间纵轴是频率颜色深浅表示能量强度。3.2 梅尔频谱原理梅尔频谱是在普通声谱图基础上对频率轴进行非线性变换使其更符合人耳感知。具体来说人耳能听到的频率范围大约是20Hz到20kHz但对不同频率的敏感度不同对1kHz附近最敏感高低频都较差梅尔刻度通过公式将Hz频率转换为更符合听觉的梅尔频率计算梅尔频谱的步骤对音频信号进行STFT得到频谱图设计一组梅尔滤波器组三角滤波器用这些滤波器对频谱图进行滤波和降维3.3 MFCC特征MFCC梅尔频率倒谱系数是另一种常用特征它是在梅尔频谱基础上再进行离散余弦变换(DCT)得到的。MFCC能更好地表示声音的包络特征在语音识别中应用广泛。但对于一般的音频分类任务梅尔频谱通常已经足够。4. 数据处理流程4.1 数据集介绍我们将使用UrbanSound8K数据集这是一个常用的环境声音分类数据集包含10类城市环境声音空调声汽车喇叭声儿童玩耍声狗叫声钻孔声引擎空转声枪声手提钻声警笛声街道音乐声数据集已预先分为10个fold每个fold包含不同类别的样本避免同一类声音集中在少数fold中。4.2 数据加载与预处理使用Librosa加载音频文件时有几个重要参数需要注意import librosa # 加载音频文件 audio, sr librosa.load(audio_file.wav, sr16000, # 采样率设为16kHz duration3.0) # 统一截取前3秒对于短于3秒的音频我们可以用零填充对于长于3秒的截取前3秒。这样可以保证所有输入长度一致。4.3 特征提取将音频转换为梅尔频谱的关键代码如下# 计算梅尔频谱 mel_spec librosa.feature.melspectrogram(yaudio, srsr, n_fft2048, # FFT窗口大小 hop_length512, # 帧移 n_mels128) # 梅尔带数 # 转换为分贝单位 mel_spec_db librosa.power_to_db(mel_spec, refnp.max)这样得到的mel_spec_db是一个二维数组频率×时间可以直接作为神经网络的输入。5. 模型构建与训练5.1 数据集类实现我们需要自定义一个PyTorch Dataset类来加载和预处理数据from torch.utils.data import Dataset class AudioDataset(Dataset): def __init__(self, file_list, base_dir, transformNone): self.file_list file_list # 包含文件名和标签的列表 self.base_dir base_dir self.transform transform def __len__(self): return len(self.file_list) def __getitem__(self, idx): file_path, label self.file_list[idx] # 加载音频并提取特征 full_path os.path.join(self.base_dir, file_path) audio, sr librosa.load(full_path, sr16000) mel_spec extract_mel_spectrogram(audio, sr) if self.transform: mel_spec self.transform(mel_spec) return mel_spec, label5.2 模型架构选择虽然音频信号是一维的但梅尔频谱是二维的频率×时间因此我们可以使用图像分类的CNN架构。这里选择轻量级的MobileNetV2import torchvision.models as models class AudioClassifier(nn.Module): def __init__(self, num_classes): super().__init__() # 使用预训练的MobileNetV2但修改第一层接受单通道输入 self.base_model models.mobilenet_v2(pretrainedTrue) self.base_model.features[0][0] nn.Conv2d(1, 32, kernel_size3, stride2, padding1, biasFalse) # 修改最后的分类层 self.base_model.classifier[1] nn.Linear(self.base_model.last_channel, num_classes) def forward(self, x): return self.base_model(x)5.3 训练流程训练过程遵循标准的PyTorch训练循环def train_model(model, train_loader, val_loader, criterion, optimizer, num_epochs): best_acc 0.0 for epoch in range(num_epochs): # 训练阶段 model.train() running_loss 0.0 correct 0 total 0 for inputs, labels in train_loader: inputs inputs.to(device) labels labels.to(device) optimizer.zero_grad() outputs model(inputs) loss criterion(outputs, labels) loss.backward() optimizer.step() running_loss loss.item() _, predicted outputs.max(1) total labels.size(0) correct predicted.eq(labels).sum().item() train_loss running_loss / len(train_loader) train_acc 100. * correct / total # 验证阶段 val_loss, val_acc validate(model, val_loader, criterion) # 打印统计信息 print(fEpoch {epoch1}/{num_epochs}) print(fTrain Loss: {train_loss:.4f} | Acc: {train_acc:.2f}%) print(fVal Loss: {val_loss:.4f} | Acc: {val_acc:.2f}%) # 保存最佳模型 if val_acc best_acc: best_acc val_acc torch.save(model.state_dict(), best_model.pth)6. 模型优化技巧6.1 数据增强为了提高模型泛化能力我们可以对音频数据进行增强class AudioTransform: def __call__(self, spec): # 时域掩蔽 if random.random() 0.5: t random.randint(0, spec.shape[1]//4) t0 random.randint(0, spec.shape[1]-t) spec[:, t0:t0t] 0 # 频域掩蔽 if random.random() 0.5: f random.randint(0, spec.shape[0]//4) f0 random.randint(0, spec.shape[0]-f) spec[f0:f0f, :] 0 return spec6.2 学习率调度使用学习率预热和余弦退火策略from torch.optim.lr_scheduler import CosineAnnealingLR, LinearLR # 学习率预热 warmup_epochs 5 scheduler1 LinearLR(optimizer, start_factor0.01, total_iterswarmup_epochs) # 余弦退火 scheduler2 CosineAnnealingLR(optimizer, T_maxnum_epochs-warmup_epochs) # 在训练循环中 for epoch in range(num_epochs): if epoch warmup_epochs: scheduler1.step() else: scheduler2.step()7. 模型部署与应用训练好的模型可以保存为TorchScript格式便于在生产环境中使用# 保存模型 model_scripted torch.jit.script(model) model_scripted.save(audio_classifier.pt) # 加载模型 model torch.jit.load(audio_classifier.pt) model.eval()实际应用中我们可以构建一个简单的实时分类demoimport pyaudio import numpy as np def live_classification(model, class_names): p pyaudio.PyAudio() stream p.open(formatpyaudio.paFloat32, channels1, rate16000, inputTrue, frames_per_buffer16000) # 1秒的缓冲区 print(开始录音...) try: while True: # 读取1秒音频 data np.frombuffer(stream.read(16000), dtypenp.float32) # 提取特征 spec extract_mel_spectrogram(data, 16000) # 预测 with torch.no_grad(): output model(spec.unsqueeze(0)) pred output.argmax().item() print(f预测结果: {class_names[pred]}) except KeyboardInterrupt: print(停止录音) stream.stop_stream() stream.close() p.terminate()8. 性能优化与扩展8.1 模型量化为了在边缘设备上部署可以对模型进行量化# 动态量化 model torch.quantization.quantize_dynamic( model, {torch.nn.Linear}, dtypetorch.qint8 ) # 保存量化模型 torch.jit.save(torch.jit.script(model), quantized_model.pt)8.2 使用更高效的架构如果需要更高的准确率可以考虑EfficientNet或ResNeXt等更强大的架构from torchvision.models import efficientnet_b0 class EfficientAudioClassifier(nn.Module): def __init__(self, num_classes): super().__init__() self.base_model efficientnet_b0(pretrainedTrue) self.base_model.features[0][0] nn.Conv2d(1, 32, kernel_size3, stride2, padding1, biasFalse) self.base_model.classifier[1] nn.Linear(self.base_model.classifier[1].in_features, num_classes) def forward(self, x): return self.base_model(x)8.3 多模型集成结合多个模型的预测结果可以提高鲁棒性class ModelEnsemble(nn.Module): def __init__(self, model_paths): super().__init__() self.models [] for path in model_paths: model torch.jit.load(path) model.eval() self.models.append(model) def forward(self, x): outputs [] with torch.no_grad(): for model in self.models: outputs.append(model(x)) return torch.stack(outputs).mean(0)
基于PyTorch与梅尔频谱的音频分类实战:从数据预处理到模型部署
发布时间:2026/7/5 2:01:04
1. 音频分类项目概述音频分类是机器学习中一个非常有趣的应用领域它可以让计算机学会识别不同种类的声音。想象一下你的智能音箱能够区分门铃声和狗叫声或者你的手机能自动识别播放的音乐类型——这些都是音频分类技术的实际应用。在这个项目中我们将使用PyTorch框架从最基础的音频信号处理开始一步步构建一个完整的音频分类系统。特别的是我们会采用梅尔频谱Mel Spectrogram作为音频的特征表示方式这比直接处理原始音频波形要有效得多。为什么选择梅尔频谱因为人类的听觉系统对声音频率的感知并不是线性的。我们更容易察觉低频声音的微小变化而对高频变化的敏感度较低。梅尔频谱正是模拟了人耳的这种特性将线性频率转换为更符合人类听觉感知的梅尔频率。2. 环境配置与依赖安装在开始之前我们需要准备好开发环境。这个项目主要依赖以下几个Python库PyTorch深度学习框架Librosa音频处理库TorchaudioPyTorch的音频处理扩展Numpy和Matplotlib数据处理和可视化pip install torch torchaudio librosa numpy matplotlib如果你使用GPU进行训练建议安装支持CUDA的PyTorch版本。可以通过以下命令检查PyTorch是否正确识别了你的GPUimport torch print(torch.cuda.is_available()) # 应该返回True对于音频文件的播放和录制你可能还需要安装pyaudiopip install pyaudio3. 音频处理基础知识3.1 时域与频域表示音频信号本质上是随时间变化的压力波在时域中表现为波形图。但单纯观察波形很难提取有意义的特征。通过傅里叶变换(FT)我们可以将信号转换到频域看到不同频率成分的强度。不过标准傅里叶变换丢失了时间信息。短时傅里叶变换(STFT)通过将音频分成小段并分别进行傅里叶变换既保留了频率信息又保留了时间信息。STFT的结果可以表示为声谱图(spectrogram)这是一种二维表示横轴是时间纵轴是频率颜色深浅表示能量强度。3.2 梅尔频谱原理梅尔频谱是在普通声谱图基础上对频率轴进行非线性变换使其更符合人耳感知。具体来说人耳能听到的频率范围大约是20Hz到20kHz但对不同频率的敏感度不同对1kHz附近最敏感高低频都较差梅尔刻度通过公式将Hz频率转换为更符合听觉的梅尔频率计算梅尔频谱的步骤对音频信号进行STFT得到频谱图设计一组梅尔滤波器组三角滤波器用这些滤波器对频谱图进行滤波和降维3.3 MFCC特征MFCC梅尔频率倒谱系数是另一种常用特征它是在梅尔频谱基础上再进行离散余弦变换(DCT)得到的。MFCC能更好地表示声音的包络特征在语音识别中应用广泛。但对于一般的音频分类任务梅尔频谱通常已经足够。4. 数据处理流程4.1 数据集介绍我们将使用UrbanSound8K数据集这是一个常用的环境声音分类数据集包含10类城市环境声音空调声汽车喇叭声儿童玩耍声狗叫声钻孔声引擎空转声枪声手提钻声警笛声街道音乐声数据集已预先分为10个fold每个fold包含不同类别的样本避免同一类声音集中在少数fold中。4.2 数据加载与预处理使用Librosa加载音频文件时有几个重要参数需要注意import librosa # 加载音频文件 audio, sr librosa.load(audio_file.wav, sr16000, # 采样率设为16kHz duration3.0) # 统一截取前3秒对于短于3秒的音频我们可以用零填充对于长于3秒的截取前3秒。这样可以保证所有输入长度一致。4.3 特征提取将音频转换为梅尔频谱的关键代码如下# 计算梅尔频谱 mel_spec librosa.feature.melspectrogram(yaudio, srsr, n_fft2048, # FFT窗口大小 hop_length512, # 帧移 n_mels128) # 梅尔带数 # 转换为分贝单位 mel_spec_db librosa.power_to_db(mel_spec, refnp.max)这样得到的mel_spec_db是一个二维数组频率×时间可以直接作为神经网络的输入。5. 模型构建与训练5.1 数据集类实现我们需要自定义一个PyTorch Dataset类来加载和预处理数据from torch.utils.data import Dataset class AudioDataset(Dataset): def __init__(self, file_list, base_dir, transformNone): self.file_list file_list # 包含文件名和标签的列表 self.base_dir base_dir self.transform transform def __len__(self): return len(self.file_list) def __getitem__(self, idx): file_path, label self.file_list[idx] # 加载音频并提取特征 full_path os.path.join(self.base_dir, file_path) audio, sr librosa.load(full_path, sr16000) mel_spec extract_mel_spectrogram(audio, sr) if self.transform: mel_spec self.transform(mel_spec) return mel_spec, label5.2 模型架构选择虽然音频信号是一维的但梅尔频谱是二维的频率×时间因此我们可以使用图像分类的CNN架构。这里选择轻量级的MobileNetV2import torchvision.models as models class AudioClassifier(nn.Module): def __init__(self, num_classes): super().__init__() # 使用预训练的MobileNetV2但修改第一层接受单通道输入 self.base_model models.mobilenet_v2(pretrainedTrue) self.base_model.features[0][0] nn.Conv2d(1, 32, kernel_size3, stride2, padding1, biasFalse) # 修改最后的分类层 self.base_model.classifier[1] nn.Linear(self.base_model.last_channel, num_classes) def forward(self, x): return self.base_model(x)5.3 训练流程训练过程遵循标准的PyTorch训练循环def train_model(model, train_loader, val_loader, criterion, optimizer, num_epochs): best_acc 0.0 for epoch in range(num_epochs): # 训练阶段 model.train() running_loss 0.0 correct 0 total 0 for inputs, labels in train_loader: inputs inputs.to(device) labels labels.to(device) optimizer.zero_grad() outputs model(inputs) loss criterion(outputs, labels) loss.backward() optimizer.step() running_loss loss.item() _, predicted outputs.max(1) total labels.size(0) correct predicted.eq(labels).sum().item() train_loss running_loss / len(train_loader) train_acc 100. * correct / total # 验证阶段 val_loss, val_acc validate(model, val_loader, criterion) # 打印统计信息 print(fEpoch {epoch1}/{num_epochs}) print(fTrain Loss: {train_loss:.4f} | Acc: {train_acc:.2f}%) print(fVal Loss: {val_loss:.4f} | Acc: {val_acc:.2f}%) # 保存最佳模型 if val_acc best_acc: best_acc val_acc torch.save(model.state_dict(), best_model.pth)6. 模型优化技巧6.1 数据增强为了提高模型泛化能力我们可以对音频数据进行增强class AudioTransform: def __call__(self, spec): # 时域掩蔽 if random.random() 0.5: t random.randint(0, spec.shape[1]//4) t0 random.randint(0, spec.shape[1]-t) spec[:, t0:t0t] 0 # 频域掩蔽 if random.random() 0.5: f random.randint(0, spec.shape[0]//4) f0 random.randint(0, spec.shape[0]-f) spec[f0:f0f, :] 0 return spec6.2 学习率调度使用学习率预热和余弦退火策略from torch.optim.lr_scheduler import CosineAnnealingLR, LinearLR # 学习率预热 warmup_epochs 5 scheduler1 LinearLR(optimizer, start_factor0.01, total_iterswarmup_epochs) # 余弦退火 scheduler2 CosineAnnealingLR(optimizer, T_maxnum_epochs-warmup_epochs) # 在训练循环中 for epoch in range(num_epochs): if epoch warmup_epochs: scheduler1.step() else: scheduler2.step()7. 模型部署与应用训练好的模型可以保存为TorchScript格式便于在生产环境中使用# 保存模型 model_scripted torch.jit.script(model) model_scripted.save(audio_classifier.pt) # 加载模型 model torch.jit.load(audio_classifier.pt) model.eval()实际应用中我们可以构建一个简单的实时分类demoimport pyaudio import numpy as np def live_classification(model, class_names): p pyaudio.PyAudio() stream p.open(formatpyaudio.paFloat32, channels1, rate16000, inputTrue, frames_per_buffer16000) # 1秒的缓冲区 print(开始录音...) try: while True: # 读取1秒音频 data np.frombuffer(stream.read(16000), dtypenp.float32) # 提取特征 spec extract_mel_spectrogram(data, 16000) # 预测 with torch.no_grad(): output model(spec.unsqueeze(0)) pred output.argmax().item() print(f预测结果: {class_names[pred]}) except KeyboardInterrupt: print(停止录音) stream.stop_stream() stream.close() p.terminate()8. 性能优化与扩展8.1 模型量化为了在边缘设备上部署可以对模型进行量化# 动态量化 model torch.quantization.quantize_dynamic( model, {torch.nn.Linear}, dtypetorch.qint8 ) # 保存量化模型 torch.jit.save(torch.jit.script(model), quantized_model.pt)8.2 使用更高效的架构如果需要更高的准确率可以考虑EfficientNet或ResNeXt等更强大的架构from torchvision.models import efficientnet_b0 class EfficientAudioClassifier(nn.Module): def __init__(self, num_classes): super().__init__() self.base_model efficientnet_b0(pretrainedTrue) self.base_model.features[0][0] nn.Conv2d(1, 32, kernel_size3, stride2, padding1, biasFalse) self.base_model.classifier[1] nn.Linear(self.base_model.classifier[1].in_features, num_classes) def forward(self, x): return self.base_model(x)8.3 多模型集成结合多个模型的预测结果可以提高鲁棒性class ModelEnsemble(nn.Module): def __init__(self, model_paths): super().__init__() self.models [] for path in model_paths: model torch.jit.load(path) model.eval() self.models.append(model) def forward(self, x): outputs [] with torch.no_grad(): for model in self.models: outputs.append(model(x)) return torch.stack(outputs).mean(0)