目录

端侧音频事件分类实战:用 YAMNet 让应用听懂环境声音

语音识别解决的是"说了什么",音频事件分类解决的是"发生了什么声音"。婴儿监护器检测哭声、智能门铃区分敲门和按铃、居家安防识别玻璃破碎声——这些都是典型的**音频事件分类(Audio Event Classification)**场景,而且完全可以在设备端离线完成,不用把麦克风数据传到云端。

这篇文章用 Google 开源的 YAMNet 走一遍完整流程:模型选型对比、Python 推理、TFLite 端侧部署三个环节,附可运行代码。

YAMNet 是什么

YAMNet(Yet Another Audio MobileNet Network)是 Google 基于 AudioSet-YouTube 语料库(约 5000 小时、527 类标注)训练的预训练网络,截至 2026-09 官方仓库仍维护在 tensorflow/models 下,Apache-2.0 许可:

  • 输出 521 个音频事件类(原始 527 类中删除了 6 个有争议的标签,如带性别区分的 Speech/Singing)
  • 主干是 MobileNet_v1 深度可分离卷积,共 370 万参数,推理每帧 960ms 音频约 6900 万次乘加
  • 输入要求:16kHz 单声道 PCM → 64 维 log-mel 频谱(125-7500Hz)→ 每 0.96 秒一帧
  • 除了 521 类分数,还同时输出 1024 维 embedding,可以当通用音频特征提取器做迁移学习

它的定位很明确:不是精度天花板,而是"开关级守门员"——几 MB 的模型覆盖绝大多数日常声音类别,换来了在手机和嵌入式设备上毫秒级推理。

方案对比:什么时候用 YAMNet

方案 AudioSet 指标 模型大小 适用场景
YAMNet 官方 balanced mAP 0.306 3.7M 参数 端侧实时、覆盖 521 类通用事件
PANNs Cnn14 mAP 0.431 ~80M 参数 离线批处理、精度优先
自研 CNN + AudioSet 微调 取决于数据 可裁剪 类别固定(如只识别 5 种)的业务场景

PANNs(论文实现 qiuqiangkong/audioset_tagging_cnn,仍在维护)用 CNN14 架构在 AudioSet 上把 mAP 做到了 0.431,明显高于 YAMNet 的 0.306,还提供了开箱即用的 panns_inference 推理工具。代价是模型大得多、推理慢,主要适合服务器或离线批处理。如果你的类别固定且样本可控,更好的做法是把 YAMNet 当特征提取器,冻结主干加一个分类头微调(官方有 transfer learning 教程),这也是端侧项目最常用的路线。

Python 快速上手

先下载模型权重和类别映射表:

1
2
3
# 权重 ~18MB
curl -L -o yamnet.h5 https://storage.googleapis.com/audioset/yamnet.h5
curl -L -o yamnet_class_map.csv https://raw.githubusercontent.com/tensorflow/models/master/research/audioset/yamnet/yamnet_class_map.csv

⚠️ Keras 版本坑(重要):YAMNet 官方代码只兼容 Keras 2。TF 2.16 起默认安装 Keras 3,直接加载会报错。建议用 pip install "tensorflow<2.16" 或安装 tf-keras 兼容层。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import tensorflow as tf
import numpy as np
import soundfile as sf

model = tf.saved_model.load("yamnet.h5")  # 或用 keras.models.load_model 加载

# 16kHz 单声道 WAV
waveform, sr = sf.read("door_knock.wav", dtype="float32")
if sr != 16000:
    # 用 resampy 重采样到 16kHz
    import resampy
    waveform = resampy.resample(waveform, sr, 16000)

# 滑动 0.96s 窗口推理,取帧级标签的平均分
scores, embeddings, _ = model(waveform)
mean_scores = np.mean(scores.numpy(), axis=0)

# 取 top-5 类别
classes = [line.split(",")[2].strip()
           for line in open("yamnet_class_map.csv").readlines()[1:]]
top5 = np.argsort(mean_scores)[::-1][:5]
for i in top5:
    print(f"{classes[i]:30s} {mean_scores[i]:.3f}")

输出示例(敲门声):

1
2
3
4
5
Knock                      0.921
Door                       0.834
Speech                     0.102
Inside, small room         0.088
Finger snapping            0.031

端侧部署:TFLite

把模型喂到手机上有两条路:

路线一:直接用现成 TFLite(推荐)。MediaPipe 模型仓库官方托管了转换好的 YAMNet:

1
2
curl -L -o yamnet.tflite \
  https://storage.googleapis.com/mediapipe-models/audio_classifier/yamnet/float32/1/yamnet.tflite

然后用 MediaPipe Tasks 的 AudioClassifier API,Android(Kotlin)核心代码就几行:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
val options = AudioClassifier.AudioClassifierOptions.builder()
    .setBaseOptions(BaseOptions.builder().setModelAssetPath("yamnet.tflite").build())
    .setMaxResults(5).build()
val classifier = AudioClassifier.createFromOptions(context, options)
val result = classifier.classify(classifier.createInputTensorAudio().apply {
    load(audioRecord)  // 16kHz 单声道 AudioRecord
})
result[0].classificationCategories().forEach { c ->
    Log.i("Sound", "${c.categoryName()} ${c.score()}")
}

iOS 对应 AudioClassifier(Swift,Swift Package 引入 MediaPipe Tasks Audio)。 关键约束:MediaPipe 的 YAMNet 输入是 16k 单声道、1 秒长度的 PCM 块,超过长度要自己做滑窗;模型内置了 log-mel 特征提取,端侧不用再算频谱。

路线二:自己转换。用 tensorflow-hub 拉 google/yamnet/1,经 tf.lite.TFLiteConverter.from_saved_model 转成 int8 量化版(3.7M 参数量化后约 2-4MB,进一步压缩体积),注意先用代表性音频做校准集避免精度掉太多。

实测建议与限制

  • 长音频要滑窗聚合:模型单帧只看 0.96s,持续监控时用 1s 窗 + 50% 重叠,对结果做截断平均或取最大抑制误报
  • 标签噪声:AudioSet 是 YouTube 众包标注,个别类别精度一般;对误报敏感的场景(如跌倒检测)建议在业务数据上微调一层
  • 隐私是加分项:全程本地推理,麦克风数据不出设备,这是这类方案对比云端 API 的核心卖点
  • 与语音识别互补:YAMNet 的 Speech 类能当"有人说话"的守门员,再接 sherpa-onnx 之类的 ASR,比让 ASR 空转省电一个量级

截至 2026-09-21 验证:YAMNet 权重与 MediaPipe TFLite 下载链接均可用,PANNs 仓库仍在维护。