1. 异音检测这件事,为什么模型选型比调参更要命
做工业质检这行的朋友应该都有体会,异音检测(Anomalous Sound Detection,ASD)跟图像缺陷检测完全是两个物种。图像那边你还能靠标注数据堆量、靠数据增强硬撑,但异音检测的痛点在于:正常声音样本永远比异常声音多得多,而且异常的种类根本无法穷举。一台电机在产线上跑,正常运转的声音就那么几种工况,但异常可能是轴承磨损、齿轮缺齿、风叶变形、螺丝松动、异物卡入……你不可能为每一种异常都录到足够样本。
所以这个领域的核心矛盾从一开始就不是"模型够不够强",而是"在只有正常样本的前提下,怎么让模型学会识别没见过的东西"。这就直接决定了模型选型的逻辑跟常规分类任务完全不同。CNN、Transformer、AutoEncoder 这三类模型在异音检测里各有各的生态位,选错了不是精度差几个点的问题,而是整个方案在产线上根本跑不起来。
我前后在几个工业声学项目里踩过坑,从最早的 MFCC+AutoEncoder 到后来的 CNN 频谱分类,再到最近两年尝试 Transformer 做时序建模,每一代方案都有它适用的场景和明确的边界。这篇文章就把这三类模型在异音检测里的选型逻辑、实操细节、参数配置和踩坑经验完整拆一遍,适合正在做设备预测性维护、产线声学质检、或者单纯想搞清楚这三类模型差异的工程师参考。不管你是刚入门深度学习的新手,还是已经做过几个项目想系统梳理选型思路的老手,应该都能从里面找到能直接抄作业的部分。
2. 三类模型的核心设计思路与选型逻辑拆解
2.1 异音检测任务的本质:无监督 vs 半监督的路线之争
在讨论模型之前,必须先把任务定义清楚,否则选型就是空中楼阁。异音检测在实际落地时通常有三种设定:
- 纯无监督:训练集只有正常声音,测试时判断是否异常。这是最贴近工业现实的设定,因为异常样本太稀缺。
- 半监督:训练集有大量正常样本 + 极少量异常样本。适合已经积累了一些历史故障数据的场景。
- 有监督分类:正常和各类异常都有标注。这种设定在学术数据集上常见,但工业现场几乎不成立。
我个人的经验是,90% 的工业异音检测项目最终都要落到无监督或半监督路线上。这个前提直接决定了 AutoEncoder 类方法天然占优,因为它就是为"只学正常分布"设计的;CNN 和 Transformer 则需要通过改造(比如自监督预训练、对比学习)才能适配无监督设定。
提示:如果你手上的项目异常样本充足且类别明确,那直接上 CNN 分类器就行,别绕弯子。但如果你面对的是"只知道正常是什么样"的场景,选型思路要完全换一套。
2.2 AutoEncoder 的生态位:重构误差就是异常分数
AutoEncoder 在异音检测里的逻辑非常朴素:用正常声音训练一个重构网络,让它学会把正常声音压缩再还原;测试时如果输入是异常声音,网络没见过这种模式,重构误差就会显著变大。这个误差直接当作异常分数用,不需要任何异常标签。
它的优势在于:
- 训练目标明确,就是最小化重构误差,不需要设计复杂的损失函数
- 对数据量要求相对低,几百条正常样本就能训出一个可用的基线
- 推理速度快,一个 encoder 前向就出结果,适合边缘部署
但它的短板也很明显:AutoEncoder 容易"过度泛化",也就是说它可能把异常声音也重构得不错,导致异常分数区分度不够。这个问题在异常和正常声音频谱差异不大的场景下特别致命。解决办法通常是引入记忆模块(Memory-augmented AE)、或者用多个 AE 做集成、或者把重构误差和 latent 空间的密度估计结合起来。
2.3 CNN 的生态位:局部频谱模式的强特征提取器
CNN 在异音检测里通常不是直接做分类,而是作为特征提取器或者自监督学习的主干网络。声音信号转成 Mel 频谱图之后,本质上就是一张二维图像,横轴是时间、纵轴是频率、像素值是能量。CNN 的卷积核天然适合捕捉这种局部时频模式,比如某个频段的周期性冲击、某个时间窗口内的谐波结构。
CNN 的核心优势是归纳偏置强:平移不变性和局部性让它在小数据集上也能学到有意义的特征。这在工业场景里非常重要,因为你的正常样本可能只有几百条,CNN 比 Transformer 更不容易过拟合。
但 CNN 的局限在于感受野有限。异音检测里很多异常是长时序的,比如轴承磨损导致的周期性冲击,它的周期可能跨越几百毫秒,CNN 要靠堆叠很多层才能覆盖这么大的时间跨度,而且池化操作会丢失时间精度。
2.4 Transformer 的生态位:长时序依赖与全局建模
Transformer 进入异音检测领域是最近两三年的事,核心驱动力是自注意力机制能建模任意距离的时序依赖。对于旋转机械这种周期性信号,Transformer 可以同时关注到多个周期之间的关系,捕捉到 CNN 感受野覆盖不到的长程模式。
但 Transformer 在工业场景落地有两个硬门槛:
- 数据量要求高:自注意力没有 CNN 那种强归纳偏置,需要大量数据才能学到有效模式。工业场景正常样本往往只有几百到几千条,直接训 Transformer 很容易过拟合。
- 计算开销大:标准自注意力的复杂度是 O(n²),音频序列长度动辄几千帧,推理延迟在边缘设备上很难接受。
所以实际项目里,Transformer 通常以两种形式出现:一是预训练+微调,用大规模音频数据预训练再迁移;二是轻量化改造,比如用 Swin Transformer 的窗口注意力、或者用 Conformer 这种 CNN+Transformer 混合结构。
2.5 三类模型的选型决策表
把上面的分析整理成一张表,方便你快速对照自己的场景:
| 维度 | AutoEncoder | CNN | Transformer |
|---|---|---|---|
| 适用数据设定 | 纯无监督 | 半监督/有监督 | 半监督/预训练+微调 |
| 最小样本量 | 200-500 条正常样本 | 1000+ 条 | 10000+ 条或预训练 |
| 长时序建模能力 | 弱(取决于 latent 维度) | 中(受感受野限制) | 强 |
| 训练难度 | 低 | 中 | 高 |
| 推理延迟 | 低 | 中 | 高 |
| 边缘部署友好度 | 高 | 中 | 低 |
| 异常定位能力 | 弱 | 中 | 强(注意力可视化) |
| 典型应用 | 设备异常预警 | 产线声学质检 | 复杂工况诊断 |
这张表不是绝对的,但能帮你快速排除明显不合适的方案。比如你只有 300 条正常样本、要部署在嵌入式设备上,那 AutoEncoder 几乎是唯一选择;如果你有海量音频数据、要做多工况细粒度诊断,Transformer 才值得投入。
3. 核心细节解析与实操要点
3.1 音频预处理:所有模型的地基
不管选哪个模型,音频预处理这一步做不好,后面全是白搭。异音检测的预处理链路通常是:
- 重采样:统一到 16kHz 或 32kHz。工业设备的声音主要能量集中在 0-8kHz,16kHz 采样足够,还能省一半计算量。
- 分帧加窗:帧长 25ms、帧移 10ms 是语音领域的经典配置,但异音检测里我建议帧长拉到 50-100ms,因为机械冲击的持续时间比语音音素长。
- 特征提取:Mel 频谱是最常用的,通常取 64 或 128 个 Mel 滤波器。如果要做 CNN 输入,再转成对数 Mel 频谱图。
- 归一化:按样本做 z-score 归一化,或者用训练集的全局均值和方差。这一步很关键,工业现场的录音音量差异很大,不归一化模型会学偏。
注意:分帧参数不是拍脑袋定的。帧长要覆盖至少一个完整的冲击周期,帧移要保证相邻帧有足够重叠。我一般会先画一下正常和异常样本的时频图,肉眼看看异常在哪个频段、持续多久,再反推参数。
3.2 AutoEncoder 的关键设计:latent 维度和瓶颈层
AutoEncoder 能不能work,核心在于瓶颈层的设计。latent 维度太大,网络会把异常也重构得很好;太小,正常样本都重构不出来。我的经验值是:
- 输入是 128 维 Mel 频谱时,latent 维度取 8-32 之间
- 如果输入是整段音频的频谱图(比如 128×128),latent 取 64-128
另一个关键点是重构目标的选择。直接重构频谱图效果一般,更好的做法是重构频谱图的掩码版本或者用多个 AE 分别重构不同频段。我试过把 0-8kHz 分成 4 个频段,每个频段训一个独立 AE,最后把 4 个重构误差加权求和,异常检测的 AUC 比单 AE 提升了差不多 8 个百分点。
损失函数方面,MSE 是默认选择,但对异常检测来说,MSE 对大幅值误差惩罚过重,容易让模型只关注能量高的频段。可以试试用平滑 L1 损失,或者对频谱做对数压缩后再算 MSE。
3.3 CNN 的实操要点:从频谱图到异常分数
CNN 在无监督设定下的典型用法是自监督对比学习。具体做法是:
- 对同一条正常音频做两种不同的数据增强(比如时间平移、频率掩码、加噪),得到两个视图
- 用 CNN 编码器提取特征,让同一音频的两个视图在特征空间靠近,不同音频的远离
- 测试时用特征距离或者 one-class 分类器(如 SVDD)判断异常
如果是有监督设定,那就简单了,直接当图像分类做。但要注意类别不平衡问题,异常样本少的时候要用 focal loss 或者重采样。
CNN 结构选择上,我推荐从ResNet-18 或 MobileNetV2起步。ResNet-18 在频谱图上表现稳定,MobileNetV2 适合边缘部署。别一上来就上 EfficientNet 或者 ConvNeXt,工业数据量撑不起那么大的模型。
输入尺寸方面,频谱图通常裁成 128×128 或 224×224。时间轴和频率轴的裁剪比例要匹配,别把时间轴压得太扁,否则周期性冲击的模式就看不出来了。
3.4 Transformer 的落地姿势:预训练与轻量化
Transformer 在异音检测里直接从头训基本不现实,我的建议是走预训练+微调路线。预训练数据可以用 AudioSet、或者自己积累的大规模正常音频。预训练任务是 masked spectrogram modeling,跟 BERT 的 masked language modeling 一个思路:随机遮住频谱图的一部分,让模型预测被遮住的部分。
微调阶段,把预训练好的 encoder 接一个轻量分类头或者异常检测头。如果做无监督,可以用预训练 encoder 提取特征,再套一个 AutoEncoder 或者 one-class SVM。
轻量化方面,Swin Transformer的窗口注意力机制能把复杂度从 O(n²) 降到 O(n),在音频任务上表现不错。另一个选择是Conformer,它把 CNN 的局部特征提取和 Transformer 的全局建模结合起来,在音频领域是经过验证的架构。
提示:Transformer 的 positional encoding 在音频任务里要特别注意。标准正弦编码对周期性信号不一定最优,可以试试可学习的相对位置编码,或者直接用卷积做下采样后再加位置编码。
3.5 三类模型的训练策略对比
训练策略上,三类模型的差异很大:
- AutoEncoder:端到端训练,损失函数简单,收敛快。关键是早停,别让它把异常也学进去。我一般用验证集上的重构误差做早停,patience 设 10 个 epoch。
- CNN:如果做对比学习,需要精心设计数据增强策略。增强太弱学不到不变性,太强会破坏异常信号。我通常用时间平移 ±10%、频率掩码 2-4 个频段、加高斯噪声 SNR=20dB 这三件套。
- Transformer:预训练阶段用大 batch size(256+)和 warmup+cosine 学习率调度。微调阶段学习率要调小,通常是预训练的 1/10 到 1/100。
4. 实操过程与核心环节实现
4.1 数据准备与基线建立
假设你手上有一台设备的录音数据,正常样本 500 条,每条 10 秒,采样率 16kHz。第一步是建立基线,我建议先用最简单的 AutoEncoder 跑通全流程,再考虑上更复杂的模型。
数据划分上,训练集只用正常样本,验证集用正常+少量异常(如果有的话),测试集用正常+异常。如果完全没有异常样本,那就用正常样本的一部分做验证,看重构误差的分布是否稳定。
预处理代码大致长这样:
import librosa import numpy as np def extract_mel_spectrogram(audio_path, sr=16000, n_mels=128, frame_length=1024, hop_length=256): y, _ = librosa.load(audio_path, sr=sr) mel = librosa.feature.melspectrogram( y=y, sr=sr, n_mels=n_mels, n_fft=frame_length, hop_length=hop_length ) log_mel = librosa.power_to_db(mel, ref=np.max) # 按样本做 z-score 归一化 log_mel = (log_mel - log_mel.mean()) / (log_mel.std() + 1e-8) return log_mel这里 frame_length 取 1024(对应 64ms),hop_length 取 256(对应 16ms),比语音领域的默认值大一些,更适合机械声音。
4.2 AutoEncoder 完整实现与参数配置
AutoEncoder 的网络结构我一般用对称的卷积结构:
import torch import torch.nn as nn class ConvAutoEncoder(nn.Module): def __init__(self, latent_dim=16): super().__init__() # Encoder self.encoder = nn.Sequential( nn.Conv2d(1, 16, 3, stride=2, padding=1), # 128x128 -> 64x64 nn.BatchNorm2d(16), nn.ReLU(), nn.Conv2d(16, 32, 3, stride=2, padding=1), # 64x64 -> 32x32 nn.BatchNorm2d(32), nn.ReLU(), nn.Conv2d(32, 64, 3, stride=2, padding=1), # 32x32 -> 16x16 nn.BatchNorm2d(64), nn.ReLU(), nn.AdaptiveAvgPool2d((4, 4)), nn.Flatten(), nn.Linear(64*4*4, latent_dim) ) # Decoder self.decoder = nn.Sequential( nn.Linear(latent_dim, 64*4*4), nn.ReLU(), nn.Unflatten(1, (64, 4, 4)), nn.ConvTranspose2d(64, 32, 4, stride=2, padding=1), nn.BatchNorm2d(32), nn.ReLU(), nn.ConvTranspose2d(32, 16, 4, stride=2, padding=1), nn.BatchNorm2d(16), nn.ReLU(), nn.ConvTranspose2d(16, 1, 4, stride=2, padding=1), nn.Sigmoid() ) def forward(self, x): z = self.encoder(x) return self.decoder(z), z训练配置:Adam 优化器,学习率 1e-3,batch size 32,训练 100 个 epoch,用验证集重构误差做早停。latent_dim 从 16 开始试,如果异常检测 AUC 不理想,再往小调。
异常分数计算:测试时对每个样本算重构误差,用 MSE 或者 MAE。为了增强鲁棒性,我会把误差按频段加权,高频段权重稍大,因为机械异常的频谱特征通常在高频更明显。
4.3 CNN 对比学习方案实现
CNN 路线我推荐用 SimCLR 风格的对比学习。核心是数据增强和 NT-Xent 损失:
class AudioAugment: def __init__(self, time_shift=0.1, freq_mask=4, noise_snr=20): self.time_shift = time_shift self.freq_mask = freq_mask self.noise_snr = noise_snr def __call__(self, spec): # 时间平移 shift = int(spec.shape[1] * self.time_shift * np.random.uniform(-1, 1)) spec = np.roll(spec, shift, axis=1) # 频率掩码 for _ in range(self.freq_mask): f = np.random.randint(0, spec.shape[0]) w = np.random.randint(1, 8) spec[max(0,f-w):f+w, :] = 0 # 加噪 noise = np.random.randn(*spec.shape) * (10 ** (-self.noise_snr/20)) spec = spec + noise return spec编码器用 ResNet-18,把第一层改成单通道输入,输出 512 维特征。投影头用两层 MLP,输出 128 维。损失函数用 NT-Xent,温度系数 0.5。
训练完之后,异常检测用两种方式:一是直接用特征做 kNN,正常样本的特征作为参考集,测试样本到最近正常样本的距离作为异常分数;二是训一个 one-class SVM。我实测下来 kNN 更简单也更稳。
4.4 Transformer 预训练与微调流程
Transformer 路线我以 Swin Transformer 为例。预训练阶段用 masked spectrogram modeling:
class MaskedSpectrogramModeling(nn.Module): def __init__(self, encoder, mask_ratio=0.4): super().__init__() self.encoder = encoder self.mask_ratio = mask_ratio self.decoder = nn.Linear(encoder.embed_dim, 128) # 预测被遮住的频谱 def forward(self, spec): # 随机遮罩 B, F, T = spec.shape mask = torch.rand(B, F, T) < self.mask_ratio masked_spec = spec.clone() masked_spec[mask] = 0 # 编码 features = self.encoder(masked_spec) # 解码预测 pred = self.decoder(features) # 只计算被遮住位置的损失 loss = F.mse_loss(pred[mask], spec[mask]) return loss预训练数据量建议至少 10 万条音频片段,每条 5-10 秒。预训练 100-200 个 epoch,学习率 1e-4,warmup 10 个 epoch。
微调阶段,把 encoder 冻结前几层,只训后几层和分类头。如果做无监督异常检测,就用 encoder 提取特征,再套一个轻量 AutoEncoder 或者直接算特征空间的马氏距离。
4.5 三类模型的推理部署对比
部署这块差异很大,直接上表:
| 部署维度 | AutoEncoder | CNN | Transformer |
|---|---|---|---|
| 模型大小 | 1-5 MB | 10-50 MB | 50-300 MB |
| CPU 推理延迟(10s音频) | 20-50 ms | 100-300 ms | 500-2000 ms |
| GPU 推理延迟 | 5-10 ms | 20-50 ms | 50-200 ms |
| 量化友好度 | 高 | 中 | 低 |
| 边缘设备适配 | 树莓派可跑 | Jetson Nano 可跑 | 需要 Jetson Xavier 以上 |
如果产线要求实时检测(延迟 < 100ms),AutoEncoder 是唯一稳妥的选择。CNN 在 Jetson 上做 TensorRT 加速后也能勉强达到,但 Transformer 基本只能跑在服务器端。
5. 常见问题与排查技巧实录
5.1 异常分数区分度不够怎么办
这是异音检测最常见的问题:正常和异常的分数分布重叠严重,阈值怎么调都有一堆误报漏报。排查思路按优先级来:
- 检查预处理:归一化是不是按样本做的?如果按全局做,音量差异会淹没异常特征。频谱图有没有做对数压缩?线性频谱的动态范围太大,模型学不好。
- 检查 latent 维度:AutoEncoder 的 latent 太大是头号嫌疑。试着把 latent 减半,看区分度是否提升。
- 检查重构目标:直接重构频谱图可能不够,试试重构频谱图的差分(时间方向的一阶差分),异常在差分图上更明显。
- 多模型集成:单个 AE 不行就训 3-5 个不同初始化的 AE,分数取平均或最大值。我实测集成能把 AUC 提升 5-10 个点。
5.2 模型过拟合正常样本
工业场景正常样本少,模型很容易过拟合。表现是训练集重构误差极低,但验证集上正常样本的误差也偏高,导致阈值难定。解决办法:
- 数据增强:对正常音频做时间平移、加轻微噪声、频率掩码。注意增强强度要控制,别把正常样本增强成"异常"。
- 正则化:Dropout、权重衰减、BatchNorm 都用上。AutoEncoder 的瓶颈层本身就是一种正则化。
- 早停:别等训练损失降到最低,验证损失开始上升就停。
- 降低模型容量:AutoEncoder 的通道数减半,CNN 换更小的 backbone。
5.3 不同设备之间的域偏移
产线上有多台设备,每台的声音特性都不一样。在 A 设备上训的模型,到 B 设备上误报率飙升。这是域偏移问题,处理起来比较麻烦:
- 每台设备单独训模型:最直接但成本高,适合设备数量少的场景。
- 域自适应:用 MMD 或者对抗训练对齐不同设备的特征分布。实现复杂,但效果不错。
- 设备指纹归一化:提取每台设备的"正常声音指纹",测试时先减去指纹再做检测。这个方法简单有效,我比较推荐。
5.4 常见问题速查表
| 问题现象 | 可能原因 | 排查方向 | 解决方案 |
|---|---|---|---|
| 异常分数区分度低 | latent 过大/预处理不当 | 检查归一化和 latent 维度 | 减小 latent,按样本归一化 |
| 验证集误差偏高 | 过拟合 | 看训练/验证损失曲线 | 加正则化,早停,数据增强 |
| 跨设备误报高 | 域偏移 | 对比不同设备的频谱图 | 设备指纹归一化或域自适应 |
| 推理延迟超标 | 模型太大 | profile 各层耗时 | 量化、剪枝、换轻量模型 |
| 异常定位不准 | 特征粒度太粗 | 看注意力/重构误差热力图 | 用更细的帧级别特征 |
| 训练不收敛 | 学习率不当 | 看损失曲线 | 调小学习率,加 warmup |
5.5 几个容易忽略的实操细节
最后分享几个我在项目里踩过的坑:
- 录音质量比模型重要:麦克风选型、安装位置、采样率这些"非算法"因素,对最终效果的影响可能比换模型还大。我见过一个项目,换了麦克风之后 AUC 直接从 0.75 跳到 0.92。
- 阈值不是固定的:产线环境噪声会变化,阈值要动态调整。我一般用正常样本分数的 99 分位数作为初始阈值,然后根据误报率在线微调。
- 异常分数要平滑:单帧的异常分数波动大,用滑动窗口做平滑能显著降低误报。窗口大小取 1-2 秒比较合适。
- 保留原始音频:模型误报的时候,回听原始音频是最快的排查手段。别只存特征,原始音频一定要留。
6. 模型选型的最终决策路径
把整个选型逻辑收束成一条决策路径,方便你直接套用:
第一步,看数据量。正常样本少于 500 条,直接上 AutoEncoder,别犹豫。500-5000 条,CNN 对比学习是甜点区。超过 5000 条且有预训练资源,才考虑 Transformer。
第二步,看部署环境。边缘设备(树莓派、Jetson Nano)只能跑 AutoEncoder 或轻量 CNN。服务器端部署可以上 Transformer。
第三步,看异常类型。短时冲击类异常(敲击、摩擦)CNN 就够。长周期异常(轴承磨损、齿轮故障)需要 Transformer 的长时序建模能力。
第四步,看可解释性要求。如果产线需要知道"哪里异常",Transformer 的注意力可视化或者 AutoEncoder 的重构误差热力图都能提供定位信息,CNN 相对弱一些。
第五步,看维护成本。AutoEncoder 最省心,训练快、部署简单、对数据漂移鲁棒。Transformer 最娇贵,需要持续的数据和算力投入。
我个人的经验是,大部分工业异音检测项目,AutoEncoder 打底 + CNN 增强 + Transformer 做补充是最务实的组合。先用 AutoEncoder 快速上线拿到基线,再用 CNN 对比学习提升精度,最后在数据积累够了之后用 Transformer 做细粒度诊断。别一上来就追求最先进的模型,工业场景里"稳定跑起来"比"刷高指标"重要得多。
这个方向后续还可以往几个方向扩展:一是多模态融合,把声音和振动信号结合起来;二是持续学习,让模型能在线适应新工况;三是自监督预训练在工业音频上的规模化应用。等这几个方向有新的实践结果,我再单独写一篇拆解。