news 2026/10/21 7:33:41

聚类欠采样+孪生自注意力:解决不平衡故障诊断的实战方案

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
聚类欠采样+孪生自注意力:解决不平衡故障诊断的实战方案

你见过那种"准确率99.8%"的故障诊断模型吗?我曾经在一个轴承振动数据集上跑出一个看起来非常漂亮的分类器——准确率99.8%,精确率99.7%,召回率99.6%,我当时以为这个项目已经稳了。直到另一个同事提醒我:你那个数据集里,正常样本有18000条,故障样本只有96条。一个把所有样本都判成"正常"的傻瓜模型,准确率都能到99.5%。而我那个"99.8%"的模型,故障类召回率其实只有37%——十个真实故障,能漏掉六个。

这就是不平衡故障诊断最坑人的地方:绝大多数工业场景里,设备正常时间是绝对的多数,故障样本可能只有1%甚至千分之几。采集数据时,你不可能为了凑样本量故意把设备跑到坏。所以面对这种"历史故障样本极度稀缺,正常样本多到用不完"的数据,我试过随机欠采样、过采样、改损失函数、设计异常检测模块,效果都一般。最后让我在这个问题上获得明显提升的,是一个组合方案:聚类欠采样 + 深度孪生自注意力网络。

这篇文章就把我完整跑通这套方案的思路、代码要点和踩坑记录整理出来。它解决的问题非常明确:当故障样本稀少、类别分布严重倾斜时,如何在不过拟合、不丢失少数类特征的情况下,稳定提升故障识别率。适合正在处理设备监测数据的算法工程师、做预测性维护的团队,以及被"类不平衡"折磨的科研人员参考。

1. 不平衡故障诊断的痛点到底在哪:不只是"样本不够"

很多人一听"不平衡",第一反应就是"那我多复制几份故障样本不就行了?"这个想法我一开始也有,但后来发现,工业故障诊断的不平衡问题,远比"复制粘贴"复杂得多。

1.1 稀缺样本同时存在数量维度和分布维度双重挑战

故障样本少,首先带来的是统计意义上的问题:数据太少,模型看不到足够的模式。但更麻烦的是第二个问题——故障样本的分布是稀疏且碎片化的。

拿旋转机械来说,"轴承内圈磨损"不是一个单一的模式,它有早期轻微磨损、中期局部剥落、晚期严重碎裂等多种状态;不同转速、不同负载下,同一个故障的表现特征差异极大。96条故障样本里可能包含5种故障类型、每种类型下又有不同劣化阶段,真正落到"某一种故障的某个阶段",可能只有5到10条样本。

这就是为什么单纯用SMOTE(合成少数类过采样技术)这类方法效果有限。SMOTE在特征空间里对少数类样本做插值,但如果少数类样本本身就分散在多个互相隔开的簇里,插值出来的"假样本"可能会落在不同故障类型之间的空白区域,反而污染了类别边界。我后来跑过一次SMOTE + CNN的基线,故障类F1从0.31提到了0.42,看着是涨了,但把生成样本单独拿出来做T-SNE可视化,发现相当一部分合成样本落在了正常样本的领地里——这些样本进训练集之后,直接把正常类和故障类的决策边界搅浑了。

1.2 数据层面和算法层面,先后顺序是有讲究的

我的核心经验是:面对极度不平衡的故障数据,先做数据层面的压缩,再做算法层面的适配,顺序不能反。

先用欠采样把多数类(正常样本)压缩到合理范围内,把类别比例从100:1降到3:1或5:1这个量级,后面不管接什么模型都好办。如果数据层面不动,直接上一堆"类别平衡损失函数"(比如Focal Loss),训练过程很容易被大量正常样本主导,损失函数的调节强度很难掌握。反过来,如果先把数据处理到相对平衡,模型就可以用标准的对比学习或度量学习框架来训练,而不必过度依赖那些为了对抗不平衡而设计的复杂损失函数。

所以整个流程我定为三步:第一步,聚类欠采样压缩正常样本,同时保留其分布多样性;第二步,设计孪生自注意力网络,把"分类问题"转化成"相似度度量问题";第三步,用对比损失训练,最后接一个轻量分类头输出具体故障类别。下面逐个拆开。

2. 聚类欠采样:"压"多数类样本,但要压得有策略

刚开始我做欠采样是用最原始的方式——随机抽。从18000条正常样本里随机抽300条,和故障样本凑在一起训练。跑了几个实验,发现随机欠采样有一个极隐蔽的坑:它会破坏多数类的局部密度结构。

2.1 为什么随机欠采样会伤到正常样本的"骨架"

正常样本虽然量大,但并不是均匀分布的。设备在不同工况下运行,正常振动信号的幅值、频率成分差别很大。比如说某风机在800转/分和1500转/分下,正常振动特征在特征空间里就是两个相距很远的簇。如果你的故障样本恰好主要出现在低速段,而随机欠采样把高速段的正常样本抽走了大半,模型就会学到"低频就是故障"这种错误的相关性。

更麻烦的是,随机欠采样没有"代表性"概念。它抽出来的300条正常样本,可能在特征空间里扎堆在一个小区域内,而另一些正常工况区域完全没被覆盖。模型根本没见过那些区域的正常样本,等真正的在线数据来了,那些工况下出现一点正常的波动,模型就会误报成故障。

聚类欠采样解决的就是这个问题。核心思路简单:先把多数类样本聚成K个簇,然后从每个簇里按比例抽取样本。这样无论正常样本有多少种工况模式,每一种模式都能在压缩后的数据集里保留代表样本,压缩的是"每个簇内的冗余度",而不是"某些簇的生存权利"。

2.2 聚类欠采样的两种实现路线

我在项目里尝试过两种路线,效果差别不小。

第一种是用现成库里的ClusterCentroids。它的逻辑是:对多数类做KMeans聚类,然后用每个簇的质心点作为该簇的代表样本。这样做的优点是执行极快,只需要一行代码:

from imblearn.under_sampling import ClusterCentroids cc = ClusterCentroids( sampling_strategy=0.33, # 欠采样后多数类与少数类的数量比 estimator=KMeans(n_clusters=120, random_state=42) ) X_resampled, y_resampled = cc.fit_resample(X_train, y_train)

这里sampling_strategy=0.33的意思是让多数类样本数压缩到少数类样本数的1/3——注意是少数类样本数的三分之一,也就是说压缩完后多数类反而变少了,整个训练集以故障样本为主。这个比例我反复调过,最终在0.2到1.0之间选值。如果故障样本的类内差异不大,取1左右即可;如果故障样本稀碎、工况跨度大,取0.3到0.5会更稳。

ClusterCentroids的问题在于:直接用簇心代表一个簇,会丢掉簇内部的方差信息。正常样本在一个工况模式下也是有波动的,簇心只是平均水平,极端一点的正常波动可能会在压缩后消失,导致模型把那些正常的极端波动误判为故障。

第二种路线是我最终采用的方式:先对少数类(故障样本)做聚类,再对多数类做聚类,然后根据"簇间距离"抽样。完整过程如下:

  1. 对故障样本做KMeans聚类,得到几个故障子簇。比如已知有3种故障模式,就聚成3簇。
  2. 对正常样本做KMeans聚类,得到一个初始簇划分。
  3. 计算每个正常样本簇中心与各个故障子簇中心的距离,优先保留距离故障簇较近的正常簇中的样本,同时拉远簇中的样本也按比例保留一部分。

这个做法的逻辑是:靠近故障边界的正常样本是最容易混淆的区域,必须保留足够多,帮助模型学到细边界;远离故障区域的正常样本则减少冗余即可。这个思路已经带有一定的"难例挖掘"味道,比单纯ClusterCentroids效果好很多。

2.3 聚类数的选择与最终效果对比

聚类数K的选择,我的经验法则是不超过最终想保留的多数类样本数除以每簇最小样本数。比如目标保留300条正常样本,希望每个簇至少10条,那K就控制在30以内。实际操作中,K取20到50比较常见,最好用轮廓系数粗筛一遍,选轮廓系数较高的区间。

下面这张表是我在某轴承故障数据集上做的对比,训练集原始正负比例约187:1,所有模型结构一致(同一个轻量CNN),只改变数据预处理方式:

数据预处理方式总体准确率故障类召回率故障类精确率故障类F1
原始数据直接训练99.5%18.3%76.4%0.295
随机欠采样94.7%62.5%71.2%0.665
SMOTE过采样93.1%70.8%55.6%0.623
ClusterCentroids欠采样96.2%75.0%82.8%0.787
距离感知聚类欠采样95.4%81.3%84.5%0.829

注意,这个实验里故障样本本身数量少,测试集中故障样本也只有50条左右,所以召回率的每一步提升都对应着几条样本,不需要神化某个数字。但趋势是稳定的:聚类欠采样的故障类F1明显高于随机欠采样和SMOTE,而且精确率和召回率相对均衡,没有出现"召回率提上去了但误报一堆"的情况。

3. 深度孪生自注意力网络:把"分类"翻译成"比较"

数据层面把比例处理好之后,模型层面的设计是真正拉开差距的地方。我最开始用的是CNN分类器,压缩后的数据上准确率能到95%+,但有一个绕不开的问题:故障样本实在太少(每种类型可能就十几条),CNN分类器很容易把某个故障类型的"个别特征"当成"类型通用特征"来学——说白了就是过拟合。

3.1 孪生网络的逻辑:学习的是关系,而不是模板

孪生网络(Siamese Network)的思路和普通分类器完全不同。它不直接输出"这个样本属于哪一类",而是接受一对输入样本,输出两个样本之间的相似度得分。训练时,给模型看的是"这一个故障样本和那一个故障样本是同一类"、"这个故障样本和那个正常样本不是同一类"这样的成对比较。

为什么这个思路对稀缺故障样本特别友好?打个比方:普通分类器像一个第一次见大象的人,你只给他看3张大象照片,让他以后在动物堆里找大象,他很容易只记住照片里的背景颜色,把红色墙前面的大象认成"墙",把背景里有水的大象认成"水"。而孪生网络是让他回答"这两个动物是不是同一种",他学会的是比较纹路、体态、轮廓这些内在属性,背景干扰的影响就小得多。

具体到故障诊断场景:如果训练集里"严重轴承剥落"样本只有8条,普通分类器可能把这个故障的振动冲击特征和一个特定转速下的背景噪声绑定了。但孪生网络在学习过程中会不断拿这8条样本之间的公共特征做"正样本对"约束,把这8条样本里的真正共性提炼出来,过滤掉单条样本的个性噪声。

3.2 自注意力模块在振动信号里抓什么

孪生网络的两个分支我最初用的是1D CNN。CNN的问题在于感受野有限,想抓到振动序列里相隔很远的两个冲击之间的关联,需要堆很多层网络,训练难度增大。后来我引入自注意力机制(Self-Attention)替换掉部分卷积层,效果出现了明显提升。

自注意力天然适合处理非规则的长程依赖。振动信号里的故障特征往往是这样的:初始冲击出现在第200个采样点附近,经过共振衰减后,残余信号里还有另一段微弱冲击出现在第800个采样点附近;同时不同传感器通道(比如驱动端、风扇端)之间的相位关系也包含信息。CNN要捕捉这种跨通道、跨时间步的关联很吃力,而自注意力机制在计算每个时刻的输出时,可以直接"看"整个序列,对任意两处特征做关联建模。

我在每个孪生分支内嵌入了两层Transformer编码器层,输入是1D振动信号先经过卷积stem切块映射成token序列,然后加位置编码,送入编码器。两个分支共享权重——这一点很重要,如果不共享权重,两个分支会各自学到不同的映射,距离度量就失去意义。

3.3 损失函数与距离从哪来

训练孪生网络用的是对比损失(Contrastive Loss),公式如下:

L = y * D^2 + (1 - y) * max(margin - D, 0)^2

其中,D表示两个分支输出的嵌入向量之间的欧氏距离,y=1表示这一对样本属于同一类(都是故障,或者都是正常),y=0表示它们不是同一分类。margin是一个超参数,表示"不相似样本对"的最小期望距离。

这里有一个细节:如果D是欧氏距离,那么margin设多大,直接影响特征空间的聚集程度。我把margin设为1.0,距离通常被L2正则限制在0到2之间。推理阶段,模型依然接收一对样本(一个来自支撑集的已知样本,一个来自待测样本),计算距离D,如果D小于阈值就判定为同类,否则判定为不同类。这个阈值可以用训练集上性能最好的约登指数来确定,不需要手动瞎调。

最终如果要输出具体故障类别,我的做法是:在训练收敛后,取出孪生网络的特征嵌入层,接上一个线性分类头(一个全连接层 + Softmax),用少量标注样本微调。这样既保留了度量学习的泛化性,又补足了"告诉我具体是哪种故障"的输出能力。

4. 落地实战:数据构造、训练顺序与参数配置

原理说清楚之后,下面是我在一个内部项目里完整走过的流程。整体跑通大概花了三天时间,大部分时间耗在数据构造和阈值调优上,真正模型训练反而很快。

4.1 数据时段切分与工况归一

振动数据通常是连续采样的,1秒钟可能就有上万个采样点。我第一步是把连续数据切成固定长度的序列片段,长度选择1024个采样点,对应一个脉冲周期内能覆盖至少几次旋转冲击。切段时要注意,同一个连续数据文件里的相邻片段不能同时分到训练集和测试集,否则会造成严重的数据泄漏。

具体做法是按"时间窗口ID"做分组划分:先给每条原始记录一个唯一的设备运行ID,再按ID而不是按序列片段做train/test split。这样训练集和测试集里的片段不会来自同一条连续记录,模型不会通过"片段在时间上相邻"这种捷径作弊。

不同工况下的数据幅值差很多,我在预处理里做了简单的归一化:每个1024点片段减去自身均值,再除自身标准差。这个操作能让模型关注波形形状而不是绝对幅值,对跨转速的诊断很有帮助。

4.2 样本对构造的坑与技巧

孪生网络的数据构造比普通分类器麻烦。我的做法是维护一个"样本索引表",对于每个epoch:

  • 从故障样本中随机选两条同类型样本,构造成一个正样本对(y=1)
  • 从故障样本和正常样本中各取一条,构造一个负样本对(y=0)
  • 从不同故障类型的样本中各取一条,也构造一个负样本对(y=0)

正样本对的比例很关键。如果正样本对太少,模型会倾向于把所有样本对都推远,最后导致所有嵌入都坍塌到一个点附近。我控制每个batch里正负样本对的比例为1:1.5,保证模型既能看到同类样本的距离要拉近,又看到不同类样本的距离要拉开。

还有一个陷阱是"锚点复用太频繁"。如果训练集总共只有30条故障样本,那么每条故障样本可能在一次epoch里被构造了十几次样本对,模型容易把这些样本本身背下来。解决办法是每个epoch进行轻微的时域增强:随机裁剪、添加高斯噪声、小幅幅值缩放。增强之后的样本对每次都不完全一样,模型被迫学不变特征,对减少过拟合帮助很大。

4.3 训练顺序:先度量后分类

我建议的训练顺序分两个阶段,不要一上来就联合训练。

第一阶段只训练孪生网络,用对比损失约束嵌入空间。优化器用Adam,学习率初始1e-3,batch size 128,训练30个epoch。这个阶段的核心目标是把正常和故障的嵌入空间分开,分隔越清晰越好。

第二阶段固定孪生网络的底层参数(冻结前几层),只训练分类头。此时每个故障类别只有很少的样本,分类头参数少(一个全连接层),不容易过拟合。把分类头的学习率降到1e-4,再微调5到10个epoch。

微调结束后,在验证集上搜索最佳距离阈值。搜索方式很简单:遍历0到1之间步长0.01的候选阈值,计算每个阈值的F1,选F1最高的那个。这一步得到的是一个单纯的浮点数,在部署阶段直接使用。

4.4 关键超参数速查表

我把实验里调过觉得比较合理的参数汇总如下,具体数值需根据数据调整:

参数推荐值说明
序列长度1024 / 2048点根据采样率和旋转周期确定,需覆盖至少一个完整周期
卷积stem1D CNN,Conv1d(3, 64, kernel=7)输入多通道振动信号,输出64维token
Transformer层数2每层4个注意力头,维度128
嵌入维度128两个分支共享的最终特征维度
对比损失margin0.5 ~ 1.0不相似样本对的最小距离,宁大勿小
欠采样类别比1:0.3 ~ 1:1多数类与少数类的目标比例
温度/学习率第一阶段1e-3,第二阶段1e-4阶段切换时必须降学习率

其中margin这个参数要对嵌入维度做适配。嵌入维度是128维时,高维空间中的欧氏距离天然比低维空间大,margin取1.0比较稳定。如果嵌入维度只有16维,margin降到0.5更合适。

5. 验证与部署中的实战教训:四个高频坑

训练跑通只是第一步。我在把这个方案推向实际测试环境时,又踩了几个坑,每个都挺典型,值得记录。

5.1 别拿"总体准确率"当验收指标

这是最要命的一个坑。我前面说了,不平衡数据里总体准确率毫无参考价值。正确做法是盯住少数类(故障类)的召回率和精确率双侧指标,尤其要把"故障漏报"看作比"正常误报"更严重的问题。我在验证时时刻关注故障类F1和混淆矩阵,而不是盯着总体准确率,这两者反映的信息完全不在一个量级。

我曾见过一个团队用总体准确率98.7%的标准验收了模型,等到现场发现故障根本抓不住,回头检查才发现故障类召回率只有22%。验收指标一开始设错了,后面全盘重来,这是最亏的。

5.2 阈值标定要在设备现场数据上做

实验室里的验证正常样本来自训练集采集时的工况,但现场设备的负荷、温度、环境噪声都不一样。嵌入空间里"正常区域"的中心可能发生了漂移。所以最佳路径是:模型训练好后,先部署到边缘网关空转一段时间,收集当天的正常振动数据,再基于这些新正常样本重新标定距离阈值。这一步看似繁琐,却能大幅减少新设备投产初期的误报警。

我遇到过实际案例:某设备换了一个安装位置后,振动基座共振频率变了,离线测得的正常样本距离分布整体偏移了0.4,原本0.6的阈值直接失效。现场重新收集一小时正常数据后,把阈值校正到0.85,误报率立刻降下来了。

5.3 故障类型新增后,分类头必须重训,度量层不用

代码里我把故障类别列表写死过一次,后期现场新增了一种故障类型,结果模型把新故障全部丢进"未知"类别——这本身没问题,但分类头的Softmax输出会强制归一化,导致已有故障类别的概率被强行压低,出现"新版模型把所有正常样本都误报成旧故障"的诡异现象。重新做分类头微调之后才恢复正常。

更合理的流程是:迭代式维护一份支撑集(每个故障类型保留10到20条代表性样本),新故障类型确认后,把新样本加入支撑集,只跑分类头微调,底层的度量嵌入不动。这样增量学习的成本很低,效果稳定。

5.4 部署推理速度:两个孪生分支只需跑一次

刚开始我犯过一个低级错误:推理阶段把待测样本和支撑集里每一个已知样本配对,依次输入孪生网络,如果支撑集有30条样本,就要跑30次前向计算,单次推理耗时从5毫秒变成150毫秒,边缘网关根本撑不住。

正确做法是:把支撑集所有样本的嵌入向量提前算好并缓存下来。推理时只需要把待测样本单独经过一次孪生网络的分支,得到它的嵌入向量,然后再和缓存的支撑集向量批量计算距离。整个流程只做一次前向推理,加上一个小矩阵乘法,耗时基本可控。这个优化极其简单,但很多人第一次写孪生网络都会踩同样的坑。

6. 写在最后:这套方案的适用边界

用这套方案跑过几个项目之后,我对它适合什么场景、不适合什么场景有了比较清晰的判断。

如果你的数据里故障样本有几千条甚至更多,类别不平衡比例在10:1以内,那么完全没必要上孪生网络,一个普通的CNN加标准的数据处理就能做得很好。这套方案的真正适用场景是:故障样本极少(几十条量级)、故障模式多样、且正常样本的工况分布宽广——这时候聚类欠采样保住正常工况多样性,孪生网络保住故障类别不靠"死记硬背",自注意力保住长程特征不丢失,三者各自解决问题,形成互补。

另外一个不可忽视的收益是:这套方案天然支持"开放集"场景。因为它的核心是距离度量而不是固定类别分类,遇到完全没见过的新故障模式时,模型会给出较低的相似度得分而不是硬归到一个已知类别里,给运维人员留出了人工判断的空间。

最后再分享一个小经验:做这类项目,数据管理花费的时间往往比建模多得多。为每条样本记录设备ID、工况转速、采集时间、故障类型标注人,这些元信息在构造样本对、做分组划分、现场阈值校准时全都会用到。我吃过"元信息缺失导致重新返工"的亏,多花几天时间整理数据台账,绝对值得。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/21 7:32:42

QQ聊天记录恢复全攻略:误删、迁移、备份一篇讲透

聊到QQ聊天记录误删,我估计不少人第一反应是“完了”。第二反应可能是去下载各种来历不明的恢复软件,结果要么被收智商税,要么把手机搞出一堆垃圾。说实话,真正靠谱的第一条路,就藏在QQ自己的设置里,而且绝…

作者头像 李华
网站建设 2026/10/21 7:32:30

达梦主备集群如何恢复为单机库:物理备份与逻辑迁移实践指南

1. 先说清楚:主备集群和单机库到底差在哪做数据库运维的人,遇到“把达梦主备集群恢复到单机库”这种需求,第一反应通常不是“会不会”,而是“为什么”。因为主备集群架构本身就带着冗余、高可用的使命,改成单机库&…

作者头像 李华
网站建设 2026/10/21 7:32:29

视频监控系统故障排查与图像干扰处理检修指南

简介:这是一份面向安防运维人员、弱电工程师及高职院校相关专业学生的《视频监控系统的维护与检修》教学课件,以系统排障为主线,系统讲解维修基本步骤、常用检修方法、维修记录要求以及常见故障定位与处理思路。资源为单个PPTX演示文稿&#…

作者头像 李华
网站建设 2026/10/21 7:31:31

AI厂商能力评估不是比参数,而是看业务嵌入性

1. 项目概述:为什么“厂商AI能力对比”不是一张表格,而是一套动态评估体系最近在多个技术交流群和行业闭门会上,几乎每场讨论都会绕回一个高频词——“厂商AI能力对比”。这个词听起来像一份采购清单,但实际操作中,它根…

作者头像 李华
网站建设 2026/10/21 7:31:12

SPEC CPU2017基准测试实战:CPU性能对比与选型分析

简介:一份发表于《电子产品可靠性与环境试验》2021年增刊的学术论文PDF,基于SPEC CPU2017国际权威评测工具,系统对比了国产ARM架构处理器Hi1616与Intel E5-2650v4的性能差异。论文先介绍国产CPU发展现状与研究背景,随后从不同线程…

作者头像 李华
网站建设 2026/10/21 7:30:43

无人机三维动态避障路径规划:CTCM与DWA分层融合实战

做无人机三维动态避障路径规划这个课题时,我踩过最大的坑是“算法各管一段”:纯全局规划算出的路径看着漂亮,遇到动态障碍物立刻失效;纯局部避障算法反应够快,却经常被地形困住,绕来绕去出不来。后来把部落…

作者头像 李华