PaddleSpeech 中的 PANNs 音频分类模型:panns 模块架构解析与训练部署实战
【免费下载链接】PaddleSpeechEasy-to-use Speech Toolkit including Self-Supervised Learning model, SOTA/Streaming ASR with punctuation, Streaming TTS with text frontend, Speaker Verification System, End-to-End Speech Translation and Keyword Spotting. Won NAACL2022 Best Demo Award.项目地址: https://gitcode.com/gh_mirrors/pa/PaddleSpeech
导读
本文围绕 PaddleSpeech 音频分类(Audio Classification / CLS)子系统的核心模型模块paddlespeech.cls.models.panns.panns展开,该模块是docs/source/api/paddlespeech.cls.models.panns.panns.rst通过automodule指令自动生成 API 文档的对象,也是 ESC-50 环境声音分类示例 与 TESS 情绪语音分类示例 的骨干网络来源。读完本文,你将掌握 PANNs 系列模型(CNN14 / CNN10 / CNN6)的网络结构与前向细节、预训练权重的加载机制、SoundClassifier分类头的工作原理,以及从 YAML 配置、模型微调、静态图导出到 C++/Android 端部署的完整实战链路。
模块概览:API 文档指令背后的真实代码
docs/source/api/paddlespeech.cls.models.panns.panns.rst的内容是 Sphinx 的标准 API 文档骨架:
paddlespeech.cls.models.panns.panns module ========================================== .. automodule:: paddlespeech.cls.models.panns.panns :members: :undoc-members: :show-inheritance:它本身不携带实现,而是在文档构建时把paddlespeech.cls.models.panns.panns模块的公开成员、类与方法原样渲染为参考文档。因此该文档的技术主体即模块源码 panns.py,其对外导出的 API 集合定义在文件末尾:
__all__ = ['CNN14', 'CNN10', 'CNN6', 'cnn14', 'cnn10', 'cnn6']同时,同级目录的 classifier.py 提供SoundClassifier分类头,而init.py 通过from .classifier import *与from .panns import *将两者合并导出,形成paddlespeech.cls.models命名空间下完整的音频分类模型族。
基础卷积块:ConvBlock 与 ConvBlock5x5
ConvBlock:双 3×3 卷积残差式堆叠
ConvBlock是 CNN14 / CNN10 的基本构件,每个块内部包含两个kernel_size=(3, 3)、padding=(1, 1)、无偏置(bias_attr=False)的二维卷积,以及对应的两个BatchNorm2D,激活函数统一为 ReLU。其forward的签名是:
def forward(self, x, pool_size=(2, 2), pool_type='avg'):池化行为由pool_type决定,且实现了 PANNs 论文中的三种策略:
'max':F.max_pool2d(x, kernel_size=pool_size)'avg':F.avg_pool2d(x, kernel_size=pool_size)(默认)'avg+max':平均池化与最大池化结果逐元素相加- 其他取值直接抛出
Exception,提示仅支持"max"、"avg"与"avg+max"
这种"avg+max 双路池化"是 PANNs 对特征时间维度进行压缩的关键技巧,能同时保留响度均值与瞬态峰值信息。
ConvBlock5x5:单 5×5 卷积块
ConvBlock5x5是 CNN6 的构件,结构与ConvBlock类似,但每个块只包含一个kernel_size=(5, 5)、padding=(2, 2)的卷积加一个BatchNorm2D,同样支持三种池化模式。5×5 大卷积核以更少的层数获得更大的感受野,适合更轻量的骨干。
三个骨干网络:CNN14、CNN10、CNN6
三个网络共享同一个"前端 + 卷积骨干 + 全局汇聚 + 分类头"的整体框架,区别在于卷积块数量、通道数与输出嵌入维度。
CNN14:6 个卷积块、embedding 2048 维
从源码 panns.py#L109-L169 可以看到其结构:
| 组成部分 | 配置 | 说明 |
|---|---|---|
bn0 | BatchNorm2D(64) | 对输入频谱做批归一化 |
conv_block1 | 1 → 64 通道,池化 (2, 2) | |
conv_block2 | 64 → 128 通道,池化 (2, 2) | |
conv_block3 | 128 → 256 通道,池化 (2, 2) | |
conv_block4 | 256 → 512 通道,池化 (2, 2) | |
conv_block5 | 512 → 1024 通道,池化 (2, 2) | |
conv_block6 | 1024 → 2048 通道,池化 (1, 1) | 最后一层不做空间下采样 |
fc1 | Linear(2048, 2048) | 嵌入层,emb_size = 2048 |
fc_audioset | Linear(2048, 527) | 对应 AudioSet 的 527 个类别 |
每个卷积块之后都施加F.dropout(x, p=0.2)(仅在训练时生效),全局汇聚采用x.mean(axis=3)与x.max(axis=2) + x.mean(axis=2)的组合,即先对频率轴求均值、再对时间轴做 max+mean 融合,最后接p=0.5的 dropout 与 ReLU 激活的fc1。
CNN10 与 CNN6:轻量变体
CNN10由 4 个ConvBlock组成,通道沿 1 → 64 → 128 → 256 → 512 扩展,emb_size = 512,汇聚方式与 CNN14 一致。可以推断它用更少的参数换取更快的推理,适合资源受限场景。CNN6由 4 个ConvBlock5x5组成,通道同样扩展至 512,emb_size = 512,是三者中最轻量的选择。- 一个小提醒:
CNN10/CNN6的类注释沿用了 "14-layer CNNs" 的说明文案,属于上游注释的笔误,实际结构以构造代码为准。
双模式输出:embedding 还是 AudioSet logits?
三个网络的前向尾部都由extract_embedding标志控制:
extract_embedding=True(默认):直接返回fc1之后、经p=0.5dropout 的 512/2048 维嵌入向量,供下游SoundClassifier接自定义分类头;extract_embedding=False:对fc_audioset(x)施加F.sigmoid,输出 AudioSet 527 类的多标签概率。
注意forward开头先执行x = x.transpose([0, 3, 2, 1]),在bn0前后交换张量维度,说明模块期望的输入布局为(batch, 1, n_mels, frames)经转置到通道维后做归一化,使用时应保证特征张量的维度顺序与之一致。
工厂函数与预训练权重加载
模块提供三个工厂函数cnn14/cnn10/cnn6,签名统一为:
def cnn14(pretrained: bool=False, extract_embedding: bool=True) -> CNN14:预训练权重表定义在模块顶部 panns.py#L24-L28:
pretrained_model_urls = { 'cnn14': 'https://bj.bcebos.com/paddleaudio/models/panns_cnn14.pdparams', 'cnn10': 'https://bj.bcebos.com/paddleaudio/models/panns_cnn10.pdparams', 'cnn6': 'https://bj.bcebos.com/paddleaudio/models/panns_cnn6.pdparams', }当pretrained=True时,通过load_state_dict_from_url(来自 download.py)下载权重,保存到os.path.join(MODEL_HOME, 'panns')(MODEL_HOME定义于 env.py),随后用model.set_state_dict(state_dict)注入模型。这意味着只要网络可访问,第一次实例化cnn14(pretrained=True)便会自动获取 AudioSet 预训练参数,无需手动下载。
分类头:SoundClassifier
panns/classifier.py 中的SoundClassifier把 PANNs 骨干封装为端到端分类模型:
class SoundClassifier(nn.Layer): def __init__(self, backbone, num_class, dropout=0.1): self.backbone = backbone self.dropout = nn.Dropout(dropout) self.fc = nn.Linear(self.backbone.emb_size, num_class) def forward(self, x): # x: (batch_size, num_frames, num_melbins) -> (batch_size, 1, num_frames, num_melbins) x = x.unsqueeze(1) x = self.backbone(x) x = self.dropout(x) logits = self.fc(x) return logits它接收形状为(batch, num_frames, num_melbins)的对数梅尔频谱,先unsqueeze(1)补出单通道维(匹配骨干的(batch, 1, n_mels, frames)输入约定),经骨干提取嵌入后接Linear(emb_size, num_class)输出各类别 logits。分类头层的维度由backbone.emb_size动态决定,因此更换骨干只需替换backbone,num_class则来自数据集类别数。
配置文件全解:特征与训练超参数
ESC-50 示例:panns.yaml
examples/esc50/cls0/conf/panns.yaml 是官方微调配置,逐段说明如下:
data: dataset: 'paddle.audio.datasets:ESC50' num_classes: 50 train: mode: 'train' split: 1 dev: mode: 'dev' split: 1 model: backbone: 'paddlespeech.cls.models:cnn14' feature: sr: 32000 n_fft: 1024 hop_length: 320 window: 'hann' win_length: 1024 f_min: 50.0 f_max: 14000.0 n_mels: 64 training: epochs: 50 learning_rate: 0.00005 num_workers: 2 batch_size: 16 checkpoint_dir: './checkpoint' save_freq: 10 log_freq: 10 predicting: audio_file: '/audio/dog.wav' top_k: 10 checkpoint: './checkpoint/epoch_50/model.pdparams'要点说明:
- 特征对齐:
sr=32000、n_fft=1024、hop_length=320、n_mels=64、f_min=50、f_max=14000与 custom_dataset.md 中"特征配置必须与预训练模型对齐"的要求一致——预训练权重是在 32 kHz 采样率、64 维梅尔滤波组上训练的,微调时不可随意改动; backbone使用dynamic_import字符串加载机制(见 dynamic_import.py),'paddlespeech.cls.models:cnn14'即"模块路径:符号名";split指定 ESC-50 的折数(fold),训练集使用fold != split的样本,验证集使用fold == split,实现单折交叉验证;top_k控制预测时输出概率最高的类别个数。
TESS 示例:四种特征后端对比
examples/tess/cls0/conf 下提供了四份配置,用于对比不同前端特征对同一骨干的影响:
| 配置文件 | feat_type | 特征专属参数 | 训练轮数 / 学习率 |
|---|---|---|---|
panns_logmelspectrogram.yaml | logmelspectrogram | n_mels: 64 | 5 / 0.0005 |
panns_melspectrogram.yaml | melspectrogram | n_mels: 64 | 10 / 0.0005 |
panns_mfcc.yaml | mfcc | n_mfcc: 64,n_mels: 64 | 5 / 0.0005 |
panns_spectrogram.yaml | spectrogram | n_fft: 126(无梅尔) | 10 / 0.0005 |
四份配置的data.train/dev段都带有feat_type字段,model.backbone均为cnn14,说明同一骨干可以在不同特征表示上微调,便于在 TESS 7 类情绪数据集上做特征工程消融实验。
训练与评估实战
一键脚本流水线
examples/esc50/cls0/run.sh 按 stage 组织完整流程:
| stage | 脚本 | 功能 |
|---|---|---|
| 1 | ./local/train.sh ${ngpu} ${cfg_path} | 训练 / 微调 |
| 2 | ./local/infer.sh ${cfg_path} | 加载 checkpoint 推理 |
| 3 | ./local/export.sh ${ckpt} ${output_dir} | 导出静态图 |
| 4 | ./local/static_model_infer.sh ${infer_device} ${graph_dir} ${audio_file} | 静态图部署推理 |
按照 docs/source/cls/quick_start.md 的引导,进入示例目录后执行:
cd examples/esc50/cls0 source path.sh CUDA_VISIBLE_DEVICES=0 ./run.sh 1path.sh会把MAIN_ROOT加入PYTHONPATH,并将BIN_DIR指向paddlespeech/cls/exps/panns;train.sh内部根据ngpu决定是否调用paddle.distributed.launch做多卡分布式训练。没有 GPU 时将CUDA_VISIBLE_DEVICES置空即可回退到 CPU。
训练主循环的数据流
paddlespeech/cls/exps/panns/train.py 是训练入口,其核心流程:
yaml.safe_load解析配置,分别取出model、data、feature、training四段;dynamic_import(data_conf['dataset'])实例化数据集,用DistributedBatchSampler+DataLoader构建(waveforms, labels)数据流;- 用
LogMelSpectrogram(**feat_conf)在线提取特征,paddle.transpose(feats, [0, 2, 1])转成[N, length, n_mels]送入模型; - 构建
backbone_class(pretrained=True, extract_embedding=True)+SoundClassifier,配Adam优化器与CrossEntropyLoss; - 每个 batch 依次
loss.backward()→optimizer.step()→clear_grad(),统计 loss 与argmax准确率,按log_freq打印、按save_freq保存 checkpoint。
源码注释特别提示:当 batch 内波形长度不一致时需要自行 padding,这与 deploy/predict.py 中np.pad对齐max_length的处理逻辑相互印证。
推理与部署
动态图推理(top-k 输出)
paddlespeech/cls/exps/panns/predict.py 通过soundfile_load读取 wav,LogMelSpectrogram提特征,加载predicting.checkpoint后计算F.softmax(logits, axis=1),按概率降序输出top_k个label: prob结果。
静态图导出
paddlespeech/cls/exps/panns/export_model.py 将动态模型转静态图:
model = paddle.jit.to_static( model, input_spec=[ paddle.static.InputSpec( shape=[None, None, 64], dtype=paddle.float32) ], full_graph=True) paddle.jit.save(model, os.path.join(args.output_dir, "inference"))注意InputSpec的形状[None, None, 64]与训练时[N, length, n_mels=64]的约定一致,导出的inference.pdmodel/inference.pdiparams即静态推理产物。
静态图部署推理
deploy/predict.py 基于paddle.inference提供生产级推理入口,支持的参数:
--device:cpu/gpu/xpu/gcu;--use_tensorrt与--precision(fp32/fp16):GPU 下启用 TensorRT 加速;--cpu_threads(默认 10)与--enable_mkldnn:CPU 下线程数与 MKL-DNN 加速;- 当检测到 Paddle 版本 >= 3.0.0-beta 时,改用
inference.Config(model_dir, 'inference')的新式加载方式。
调用方式(对应 stage 4):
./local/static_model_infer.sh cpu ./export /path/to/test.wav输出形如Wav: xxx.wav Label: Dog。
C++ 运行时与端侧部署
除了 Python 侧,PANNs 模型还通过 FastDeploy 接入 PaddleSpeech 的 C++ 运行时,相关代码位于 runtime/engine/audio_classification/nnet:
panns_interface.cc的ClsCreateInstance从配置读取推理参数(默认值见 panns_interface.cc#L26-L40):wav_normal(默认 true)与wav_normal_type(默认linear):波形归一化;samp_freq(默认 32000)、frame_length_ms(32)、frame_shift_ms(10)、num_bins(64)、low_freq(50)、high_freq(14000)、dither(0.0):FBank 前端参数,与 Python 侧特征配置对齐;model_path/param_path/dict_path:模型文件与标签表;num_cpu_thread(默认 12):CPU 线程数。
panns_nnet.cc的Init用上述参数构造 FBank 选项,并通过fastdeploy::Runtime按编译宏选择USE_PADDLE_INFERENCE_BACKEND(Paddle Inference)、USE_ORT_BACKEND(ONNX Runtime)或USE_PADDLE_LITE_BACKEND(Lite)后端加载模型。
运行示例(见 runtime/examples/audio_classification/README.md):
../../build/Linux/x86_64/engine/audio_classification/nnet/panns_nnet_main --conf_path=./conf --scp_path=./scp --topk=1输出如test.wav{"Clock alarm":"16.5309"},即 wav 文件名 + top-1 标签 + 得分。同一份 README 还说明了通过 build_android.sh 构建 Android 动态库、拷贝panns_interface.h与.so到examples/audio_classification/android_demo,再以adb push部署 conf / label_list / wav 到/data/local/tmp的端侧流程,说明 PANNs 模型从云端服务到移动端均可运行。
自定义数据集:把 PANNs 迁移到自己的音频分类任务
docs/source/cls/custom_dataset.md 给出了完整迁移方案:继承基类paddlespeech.audio.datasets.dataset.AudioClassificationDataset,提供label_list与_get_data,从"每行wav路径 标签"的 meta 文件构建数据集(如 ESC50 的实现见 esc50.py,它内置 50 类标签、自动下载解压ESC-50-master.zip并按 fold 划分 train/dev)。随后用cnn14(pretrained=True, extract_embedding=True)+SoundClassifier(backbone, num_class=len(label_list))即可微调:
from paddlespeech.cls.models import cnn14, SoundClassifier backbone = cnn14(pretrained=True, extract_embedding=True) model = SoundClassifier(backbone, num_class=len(train_ds.label_list)) optimizer = paddle.optimizer.Adam(learning_rate=1e-6, parameters=model.parameters()) criterion = paddle.nn.loss.CrossEntropyLoss()训练循环中特征配置(32 kHz、64 维梅尔)必须与预训练一致,仅替换数据集与num_class,即可把 AudioSet 预训练知识迁移到诸如环境声音、情绪语音等自定义音频分类任务上。
小结
paddlespeech.cls.models.panns.panns是 PaddleSpeech 音频分类链路中承上启下的核心模块:向上承接SoundClassifier与微调/推理脚本,向下通过 FBank 特征与预训练权重对齐,横向又打通了 FastDeploy 驱动的 C++/Android 部署。从 panns.py 的 CNN14/CNN10/CNN6 结构,到 panns.yaml 的逐项超参数,再到 quick_start.md 与 custom_dataset.md 的实操指南,读者可以依据本文按"结构理解 → 配置微调 → 静态导出 → 多端部署"的路径完整走通一个音频分类项目。
【免费下载链接】PaddleSpeechEasy-to-use Speech Toolkit including Self-Supervised Learning model, SOTA/Streaming ASR with punctuation, Streaming TTS with text frontend, Speaker Verification System, End-to-End Speech Translation and Keyword Spotting. Won NAACL2022 Best Demo Award.项目地址: https://gitcode.com/gh_mirrors/pa/PaddleSpeech
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考