资讯动态

少样本故障诊断中的因果干预:原理、实践与效果

发布时间:2026/9/16 21:25:03 来源:尧图企业网站定制
少样本数据、因果干预、故障诊断这三个词放在一起很多人第一反应是“又一篇纸上谈兵的水论文”。但我把这个组合真正落地跑通之后发现它解决的是一个特别现实的工业问题故障样本永远是稀缺的而你偏偏还要模型在只见过三五个故障样本的情况下给出可靠判断。这篇文章就完整拆解我的做法、踩过的坑以及因果干预在少样本故障诊断里到底是怎么生效的。文章适合三类人看一是做设备故障诊断的工程师已经发现传统深度学习模型在样本少、工况变化时“水土不服”二是想入门少样本学习和因果推断结合的算法同学想知道这条技术路线到底怎么做才不是空中楼阁三是在工业质检、异常检测领域遇到同类“小样本强干扰”问题的从业者可以参考思路再迁移过去。1. 立项之前先把少样本故障诊断的痛点掰开揉碎1.1 数据少只是最表面的一层问题很多人以为少样本故障诊断的难点就是“数据不够”。这句话只说对了一半。我实际做项目的时候发现数据量少背后还跟着一连串连锁反应故障类型多、单类样本少、标注成本高、正常样本又严重过饱和。举个真实场景。车间里的电机或减速箱装了振动传感器24小时不停采数正常工况下的数据一周就能攒下几个T。但一旦真出了故障比如轴承内圈出现点蚀可能整个过程只有几十条有效数据而且这几十条还得靠老师傅人工看频谱确认是哪一类故障才敢打标签。更头疼的是故障不是只有一种内圈故障、外圈故障、滚动体故障、保持架故障每一种都是小样本有的类别甚至只有二三十条样本。这种情况下如果你直接用传统的卷积神经网络去训练损失函数会在正常样本上快速收敛在故障样本上却反复震荡。不是模型不行而是数据分布本身就极端倾斜。1.2 传统深度学习在少样本场景下为什么“翻车”传统深度学习的核心假设是训练数据的分布能够充分代表真实分布。训练集越大模型越有机会见到各种形态的变化从而学到稳定的判别模式。但在少样本故障诊断里这个假设直接不成立。更致命的是模型会“抄近道”。比如同一台设备在低负载和高负载下正常信号的幅值差别可能比“正常”和“早期故障”的差别还大。模型只需要抓住幅值高就判故障、幅值低就判正常就能在训练集上拿到很高的准确率。这种特征跟故障本质没有任何因果关系纯粹是统计相关。一旦测试工况一变模型立刻废掉。当时我复盘了很多失败的实验结果发现它们的共性问题就一个模型把“跟故障恰好相关的环境特征”当成了“故障本身的因果特征”。尤其在样本极度稀缺时这种虚假相关会被模型放大。2. 为什么我会想到“因果干预”这条线2.1 相关关系不等于因果关系因果推断里最经典的一个例子就是下雨天和带伞的人变多两者高度相关但你让所有人都不带伞也不会导致不下雨。放到故障诊断里类似的场景比比皆是。振动信号的某个频段能量升高可能确实和轴承故障有关但也可能只是设备转速变化引起的。传统模型学到的往往是“频段能量高→故障”这种关联规则它分不清这个频段为什么高。我们想要的是“频段能量高对故障发生有因果驱动作用”这样的判断也就是因果层面上的可靠性。从数学上看传统模型拟合的是条件概率 P(故障 | 特征)而因果干预希望估计的是 P(故障 | do(特征))。这个 do 算子表示的是一种主动干预假设我们强行把特征固定到某个值故障概率会不会改变。这两种概率在很多情况下数值完全不同甚至在工况且变化的工业场景里会走向两个方向。2.2 故障诊断中的“混淆因子”是什么因果推断里有个核心概念叫混淆因子。它指的是同时影响原因和结果的变量也就是导致“虚假相关”的幕后黑手。在故障诊断里混淆因子无处不在。工况是最典型的混淆因子。转速、负载、温度会直接影响振动信号也会间接影响故障的劣化过程。传感器安装位置是另一个同一个轴承故障测点放在轴承座上还是放在设备基座上信号差异非常大。环境噪声也是车间里有没有别的设备在运转直接决定信号底噪水平。问题在于我们在故障诊断里只采集到了振动信号这些混淆因子的取值往往没有被完整记录。即使记录了也不可能把所有混淆因子都列全。所以在实践里会感觉到同一种故障不同工况下模型的表现天差地别。2.3 少样本条件下因果约束为什么更刚需我一开始其实对因果推断有点抵触觉得它数学门槛高、工程落地难。但后来想明白了一件事少样本学习本身就是个高度 ill-posed 的问题信息量不足以约束模型找到真正的因果特征必须要靠先验或者结构来弥补。在故障样本极少的情况下模型有无数种方式可以拟合训练集。有的方式依赖故障特征频率有的方式依赖噪声底噪、幅值、甚至样本编号的某种规律。你没法指望模型自己做出正确选择必须显式告诉它你只能依赖那部分跟故障类别有稳定因果关系的特征其余特征给我单独分开不许参与分类决策。这就是我做“因果干预 少样本学习”组合的根本动机。不光是补数据更是给模型加一种结构性的“防偏置”保险。3. 模型的整体设计与模块拆解3.1 整体架构整套模型可以分成四个模块特征提取器、因果特征与非因果特征分离模块、反事实干预模块、原型分类模块。输入是振动信号的时频图经过骨干网络提取特征后特征被拆成两个分支。因果特征分支专门负责“这个样本属于哪类故障”非因果特征分支负责吸收“这是什么工况、什么噪声、什么传感器位置带来的差异”。分类只使用因果特征非因果特征不参与故障类别判断。但非因果特征也不是白提取的它会被反事实干预模块用来做样本表示层面的数据增强从而让模型看到更多样的“环境形态”。3.2 特征提取与分离模块特征提取器我用了 ResNet-18。相比更深的 ResNet-50它在少样本场景下不容易过拟合训练也更快。输入是经过 STFT 变换得到的时频图尺寸统一缩放到 224x224这样可以直接加载 ImageNet 预训练权重来初始化骨干网络。分离模块的实现方式是把骨干网络输出的 512 维特征分别送入两个独立的映射头一个生成因果特征一个生成非因果特征。两个映射头都是两层 MLP中间接 ReLU输出维度都是 128 维。为了保证两个分支确实在编码不同信息我加了一个特征正交约束让两个分支的特征向量尽量不相关。这个想法跟信号处理里的“盲源分离”有点类似只不过一个在原始信号域做一个在特征空间做。实际效果要看刻意设计才能把“跟故障相关的模式”和“跟环境相关的模式”在同一次前向计算中分开两个分支的对抗压力是必要的。3.3 反事实干预模块这是整个模型的“因果戏份”最重的地方。有了因果特征和非因果特征之后我可以构造一个反事实样本把样本A的因果特征和样本B的非因果特征拼到一起得到一个新的特征向量。这个向量在现实中并不存在它对应的是“同一个故障换了一种工况、一种噪声环境”的虚拟样本。在表示空间里做反事实干预比在原始信号域做要方便得多。生成一张合成的时频图非常困难但拼特征向量只需要做拼接。而且这种干预天然符合因果推断里的后门调整思路我们等于把混淆因子工况、环境等单独控制住只允许模型依赖故障的因果特征去判断类别。反事实特征同样用于原型分类的训练。训练时要求模型对“原始因果特征”和“反事实因果特征”给出一致的类别预测这就等于在告诉模型故障类别的判断不该因为环境特征改变而改变。这样一来模型会对环境扰动更加鲁棒。3.4 少样本分类模块少样本分类我选择了原型网络Prototypical Network。它的思想非常简洁对每个类别计算支撑集中所有样本的因果特征均值作为该类的原型。对于查询集样本计算它的因果特征与各类原型的欧氏距离再用 softmax 得到类别概率。我当时对比过匹配网络和关系网络最后还是选了原型网络。原因是故障诊断里的同类样本在因果特征空间里非常紧凑用均值代表类别足够稳定而且原型网络的可解释性更好出了问题方便回溯。训练时采用的是标准的 episode 训练方式。每一个任务从训练集里随机挑 N 个类别每个类别挑 K 个支撑样本和 Q 个查询样本。我这里统一用的 N5K 分别取 1 和 5也就是 5-way 1-shot 和 5-way 5-shot 两种考察配置。4. 关键时刻因果干预在实现中的三个小细节4.1 辅助判别器要“弱”一点如果只把非因果特征提取出来丢在那里它不会自己学会“环境信息”。所以我加了一个辅助判别器拿非因果特征去预测一个环境标签比如工况类别或者传感器位置编号。这个设计是合理的环境信息就是最容易体现非因果特征的那部分信息。但实验做下来发现一个关键问题辅助判别器不能太强。如果它分类能力过强反向传播时会让非因果分支和环境标签形成极强的对应关系反而削弱了因果分支的表征能力。更糟糕的是如果环境标签和故障类别存在某种耦合比如某些工况下几乎不出故障那辅助判别器会“偷走”一部分本该由因果分支负责的信息。我最后的处理是给辅助判别器加 dropout把映射头的隐含层维度从 128 降到 64并且只保留计算图中的梯度流经非因果分支的一部分。说白了就是让这个辅助任务“使不上全力”够用就行。提示辅助判别器的作用是给非因果分支提供监督信号不是让它成为主线任务。它的强度必须明显弱于主要的分类任务否则模型会失衡。4.2 特征正交约束的权重怎么定正交约束的公式比较简单就是计算因果特征和非因果特征归一化之后的内积绝对值再乘上一个系数。这个系数我试过从 0.01 到 1.0 的多个取值最终稳定在 0.1 左右。系数太小两个分支的特征会高度相似分离失去意义。系数太大模型会只顾着把两组特征分开反而忽略了分类准确率。实验中我发现一个很有意思的现象正交约束不仅影响分离度还会影响训练的收敛速度。0.1 这个值能在保持分类性能的前提下快速拉开两组特征的夹角。另外我还做了一点改进就是只在支撑集上计算正交约束查询集上不算。因为查询集是模拟测试时的未见样本如果它参与了正则项的计算会让模型在训练时“作弊”地适应查询集的统计信息削弱泛化能力。4.3 反事实样本的一致性损失最开始我只做特征拼接不加任何额外的损失结果训练很不稳定。原因是反事实样本的因果特征没变但分类器在非因果特征发生变化后仍然可能给出不同的预测因为模型还是偷偷在用非因果特征做分类。解决办法是加一个一致性损失约束反事实样本与原始样本的分类输出尽量一致。具体实现上我用了对称的 KL 散度反事实因果特征的预测分布要和原始因果特征的预测分布相互接近。这个损失一加训练立刻稳定了下来。模型开始意识到“从我掌握的信息来看这两个样本本质上是同一类故障”从而被迫去忽略非因果特征通道里携带的信息。这是整个因果干预逻辑里最不能省略的一步。5. 实操演练从数据预处理到训练完成的完整流程5.1 环境与数据准备我用的环境是 Python 3.9 PyTorch 2.0GPU 是单张 RTX 3090。音频/振动信号处理用了 librosa 和 numpySTFT 部分直接用了 librosa 的接口。数据用的凯斯西储大学轴承数据集采样率 12 kHz。选取了正常、内圈故障、外圈故障、滚动体故障四类外加早期和后期故障级别一共构造出 10 个类别。这里有一个必须强调的点少样本实验里训练类别和测试类别不能重合否则就是自欺欺人。我把 10 个类别分成 6 个训练类别、2 个验证类别、2 个测试类别完全互不相交。STFT 参数我做了好几轮对比最后定的配置是汉宁窗、窗长 256、hop 长度 128、FFT 点数 256。每段信号切成长度 4 秒的片段重叠率为 50%每个片段生成一张 224x224 的时频图。切得太多容易让同一故障样本被重复利用切得太少又不够模型吃4 秒是平衡后的选择。5.2 少样本任务划分少样本学习的任务划分和普通监督学习完全不一样。普通训练就是打乱样本、按比例切 train/val/test。但少样本训练要以“任务”为单位每个任务包含一个支撑集和一个查询集。在训练阶段每一次迭代我都会从 6 个训练类别里随机挑 5 个类别再为每个类别随机挑 K 个支撑样本和 16 个查询样本。验证和测试阶段同理但只在验证类别和测试类别里构造任务。这样模型从没见过测试类别的任何样本必须依靠泛化能力来应对新类别。这里有个新手容易踩的坑如果类别划分不小心泄漏了比如同一个轴承的相邻时间片段既进了训练类别又进了测试类别测试准确率会虚高到不真实。我一开始就吃过这个亏后来重新核对了样本时间戳严格按故障发生位置和严重度划分类别才把指标拉回正常水平。5.3 核心代码实现因果特征分离和反事实干预模块的代码不算复杂难的是想清楚哪个特征跟哪个特征做拼接。我贴一下核心实现方便你直接照着改。import torch import torch.nn as nn import torch.nn.functional as F class CausalFeatureExtractor(nn.Module): def __init__(self, backbone, feat_dim128, num_envs3): super().__init__() # 这里 backbone 是去掉最后全连接层的 ResNet-18输出 512 维特征 self.backbone backbone self.causal_head nn.Sequential( nn.Linear(512, 128), nn.ReLU(inplaceTrue), nn.Linear(128, feat_dim) ) self.noncausal_head nn.Sequential( nn.Linear(512, 64), nn.ReLU(inplaceTrue), nn.Linear(64, feat_dim) ) # 弱判别器只用非因果特征预测环境标签 self.env_classifier nn.Sequential( nn.Linear(feat_dim, 32), nn.ReLU(inplaceTrue), nn.Linear(32, num_envs) ) def forward(self, x): feat self.backbone(x) z_c self.causal_head(feat) z_n self.noncausal_head(feat) return z_c, z_n def orthogonal_loss(self, z_c, z_n): z_c_norm F.normalize(z_c, dim-1) z_n_norm F.normalize(z_n, dim-1) return torch.mean(torch.abs((z_c_norm * z_n_norm).sum(dim-1))) def counterfactual_combine(z_c, z_n, index_map): # z_c: 支撑集因果特征, z_n: 非支撑样本的非因果特征 # index_map 指定每个 z_c_i 对应取哪个 z_n_j z_n_perm z_n[index_map] return z_c, z_n_perm # 反事实样本因果特征保留非因果特征替换训练循环里原型只由因果特征计算查询样本的类别距离也只使用因果特征。反事实样本的一致性损失用的是对称 KL 散度。def train_one_episode(model, support_x, support_y, query_x, query_y, env_label, cfg): model.train() z_c_s, z_n_s model(support_x) z_c_q, z_n_q model(query_x) # 类别原型只用因果特征 protos [] for c in range(cfg.num_ways): mask support_y c protos.append(z_c_s[mask].mean(dim0)) protos torch.stack(protos, dim0) # 查询集分类 dist torch.cdist(z_c_q, protos, p2) logits -dist / cfg.temperature cls_loss F.cross_entropy(logits, query_y) # 反事实生成从支撑集里随机交换非因果特征 perm torch.randperm(z_n_s.size(0)) z_c_aug, z_n_aug counterfactual_combine(z_c_s, z_n_s, perm) # 一致性损失 logits_aug -torch.cdist(z_c_aug, protos, p2) / cfg.temperature log_p F.log_softmax(logits, dim-1) log_p_aug F.log_softmax(logits_aug, dim-1) consistency_loss F.kl_div(log_p_aug, log_p, reductionbatchmean) consistency_loss F.kl_div(log_p, log_p_aug, reductionbatchmean) # 非因果分支的弱判别损失 env_logits model.env_classifier(z_n_q) env_loss F.cross_entropy(env_logits, env_label) # 正交约束 orth_loss model.orthogonal_loss(z_c_s, z_n_s) total_loss cls_loss 0.5 * consistency_loss 0.1 * env_loss 0.1 * orth_loss return total_loss, cls_loss, consistency_loss, env_loss, orth_loss5.4 训练配置与超参数训练超参数直接影响少样本模型能不能收敛我把最终调好的配置列在下面。如果你在自己的数据上复现建议先按这个跑一遍基线再逐个调参。超参数取值备注优化器AdamW比 Adam 更稳weight decay 更干净初始学习率1e-4骨干网络用 1e-4头部网络可以略高学习率调度CosineAnnealing周期等于总 episode 数weight decay1e-4偏大容易欠拟合偏小容易过拟合episode 数10000少样本训练一般 5k-20k 比较合适支撑样本 K1 或 5对应 1-shot 和 5-shot查询样本 Q16每个类别 16 个查询样本温度系数0.1距离转 logits 的缩放因子样本尺寸224x224适配预训练 ResNet批大小4每个 batch 里放 4 个 episode我把 backbone 冻结了前两层只微调后面几层的参数这样能减少过拟合也在一定程度上加快训练速度。后期实验发现在故障时频图上完全微调反而容易丢失 ImageNet 上学到的通用特征。5.5 实验结果怎么科学地看少样本模型在训练过程中的准确率会比较“跳”因为它每个 episode 都在换任务。如果直接看每个 episode 的 loss 曲线可能根本看不出趋势。我建议的做法是每训练 200 个 episode就在验证集上跑 600 个随机 episode取平均查询准确率然后把准确率曲线画出来。我实验中发现在 5-way 1-shot 任务里加因果干预的模型在 4000 个 episode 左右验证准确率就开始稳定爬升而普通原型网络要到 6000 episode 才有一点点抬头的迹象。这个“抬头点”的提前实际上反映的就是因果干预减少了无效特征空间的探索。混淆矩阵同样值得关注。我发现在跨工况测试时普通模型的混淆矩阵会出现明显的“跨类别互混”倾向于把新工况下的所有样本都判成某一个常见类。而因果模型即使在跨工况测试里混淆矩阵依然保持比较干净的对角结构。6. 实验结果与对比6.1 和基线的对比我在 5-way 1-shot 和 5-way 5-shot 两种配置下分别做了同工况和跨工况测试。跨工况测试的做法是训练任务全部从 A 负载工况下构造测试任务全部在 B 负载工况下构造两者没有任何交集。方法1-shot 同工况1-shot 跨工况5-shot 同工况5-shot 跨工况传统 CNN 分类头68.2%44.1%79.5%52.3%原型网络78.6%61.3%87.4%70.8%原型网络 数据增强81.2%66.5%89.3%74.1%原型网络 因果干预本模型86.4%78.9%92.7%84.6%结果有几个信息量很大的点。第一跨工况场景下传统 CNN 几乎已经不能用只有 44.1% 的准确率跟随机猜差不多。第二原型网络本身已经比传统 CNN 强不少但跨工况后仍然掉了十几个点。第三加上因果干预之后跨工况准确率只下跌了 7 个点左右且 5-shot 跨工况依然有 84.6%这说明模型主要依赖的是故障本身的因果特征而不是某个特定工况下的表观特征。6.2 消融实验证明因果干预的贡献光有主实验还不足以说明问题因为模型里加了数据增强、正交约束、一致性损失等多个模块需要做消融实验来拆分每个部分的贡献。我都基于同工况 1-shot 和跨工况 5-shot 两个配置来对比。配置同工况 1-shot跨工况 5-shot完整模型86.4%84.6%去掉正交约束83.1%75.2%去掉反事实一致性损失80.7%71.8%去掉因果特征分离79.3%66.1%完整模型但只用因果特征做原型86.4%84.6%完整模型但原型使用拼接特征82.5%73.9%去掉因果特征分离对跨工况精度的影响最大掉了超过 18 个点说明如果不显式分离因果和非因果特征模型根本挡不住工况变化带来的分布偏移。反事实一致性损失的贡献次之它本质上是给模型提供了“同故障、不同环境”的约束样本让模型没法忽略环境变化带来的干扰。最后一行实验也很有意思把原型从“只用因果特征”改成“拼接因果和非因果特征”精度立刻掉 10 个点以上。这证明哪怕模型已经学习到了不错的因果表示如果在推理阶段不小心让非因果特征混入分类决策效果依然会崩。6.3 t-SNE 可视化因果特征空间长什么样我做了 t-SNE 可视化分别投影原始特征、因果特征和非因果特征。原始特征空间里正常样本和故障样本基本混在一起不同工况下的正常样本甚至能分成好几簇。因果特征空间里同类故障样本的簇非常紧凑不同类之间边界清晰而且不同工况下的同类故障样本终于聚在了一起。非因果特征空间则是另一番景象环境标签相同的样本会聚集在一起与故障类别没有明显关系。这个可视化结果基本印证了模型学到的东西——因果分支在提取“故障模式”非因果分支在提取“环境模式”两者实现了预期的分离。7. 踩坑总结与后续扩展方向7.1 我在项目里踩过的五个大坑数据类别划分泄漏。第一次实验测出来的准确率接近 95%我当时还挺高兴后来发现是同一个故障来源的不同时间片段被同时划进了训练和测试类别模型等于“背答案”。修正划分之后准确率掉到正常水平但这才是有意义的数字。STFT 参数不合适导致时频图信息丢失或冗余。窗口太短频率分辨率低故障特征频率在图上糊成一团。窗口太长时间分辨率差故障瞬态信息被平均掉。我最后用 256 窗长在两类分辨率之间找到了平衡点。学习率太高导致 episode 训练崩溃。少样本任务的优化面很不平滑学习率哪怕只从 1e-4 调到 3e-4训练曲线也会频繁出现断崖式下跌。建议用余弦退火加早停别用 StepLR 那种跳跃式减学习率的方式。辅助判别器与主线任务失衡。这是我在第 4.1 节强调过的问题最初我把环境分类器做得过强实验结果反而变差。后来把环境分类器的容量和梯度流减下去之后所有指标都有回升。反事实生成时索引没有打乱。如果直接使用原始顺序交换非因果特征支撑集样本会和自己拼接反事实样本等于原样本一致性损失退化为恒等损失完全失去干预意义。必须确保 index_map 是乱序的且不能让任何一个样本映射到自己。7.2 这套方案还能移植到哪些场景因果干预加少样本学习的思路并不只限于振动信号故障诊断。只要是“小样本 强环境干扰”的场景这套框架基本都能迁移。比如工业质检里的表面缺陷检测不同光照、不同材质纹理就是非因果特征缺陷类别本身才是因果特征。再比如医疗影像里的病理分类不同设备、不同医院的图像风格差异也是非因果特征。迁移的时候不需要改动太多东西核心就是把“特征分离 反事实干预 一致性损失”这个三角结构保留下来然后根据你的数据重新定义环境标签。如果没有环境标签也可以直接用聚类的方式给非因果分支造伪标签效果略差但能跑通。我在实际项目中还尝试过把这套框架接在对比学习后面先用大规模正常样本做无监督预训练再用少样本故障标签做因果微调。预训练让骨干网络学到通用的信号表征因果微调让模型只在因果特征空间里做判别两个阶段各管一段实测下来比端到端训练更稳定适合数据量比你想象中还稀缺的场景。做完这个项目我最大的感受是因果干预不是银弹它不会凭空变出数据也不会让模型突然拥有“逻辑推理”能力。它真正做的一件事是帮模型把注意力从无关的环境变量上拽回来迫使它在有限的信息里选择真正有价值的特征。对少样本故障诊断来说这比任何花哨的网络结构都更接近问题的本质。如果你也正在被小样本、多工况的故障识别折磨可以试试这条路至少它会让你重新思考一个问题模型在判断故障时到底是在看故障自己还是在看故障身边的影子。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价