ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Feature-level SMOTE:破解PHM故障诊断中的类别不平衡难题

Feature-level SMOTE:破解PHM故障诊断中的类别不平衡难题

1. 从一次翻车的轴承故障诊断说起

去年帮一个做风电齿轮箱状态监测的团队调模型,他们手里有三年多的振动数据,正常样本几十万条,早期微弱故障样本满打满算不到三百条。当时用常规的SMOTE在原始时序上做增强,训练集AUC能刷到0.98,一上测试集直接掉到0.71,混淆矩阵里故障类几乎全被漏报。这个坑我相信做过PHM(Prognostics and Health Management,故障预测与健康管理)的人都踩过——不是模型不行,是增强的位置错了。

Feature-level SMOTE,说白了就是把过采样从原始信号空间搬到特征空间去做。这个思路在重大装备健康管理里特别对路,因为PHM的原始数据是振动、温度、电流、声发射这类高维时序,直接插值生成的合成样本在物理上根本说不通,而到了特征层,工况解耦、量纲统一之后再做插值,合成样本才具备工程意义。这篇文章我会把整套Feature-level SMOTE的落地流程拆开讲清楚,包括特征工程怎么做、SMOTE在哪一层介入、DSMHSA这类注意力机制怎么配合、故障诊断代码怎么写、以及我实际踩过的那些坑。适合已经做过基础PHM分类、但被类别不平衡卡住的工程师,也适合刚接触不平衡学习想找工程落地方向的同学。

2. 为什么原始空间的SMOTE在PHM里会失效

2.1 时序插值的物理荒谬性

SMOTE的核心操作是找近邻、在两点连线上随机插值。放到二维表格数据上没问题,但放到振动时序上就出事了。假设你有两段轴承外圈故障的振动信号,一段是转速1500rpm下的,一段是转速1200rpm下的,SMOTE会在两个时序点之间做线性插值,生成一段"既不是1500也不是1200"的波形。这段波形在频域上会出现根本不存在的边频成分,包络谱里冒出一堆假峰。模型学到的是这些假峰,测试时遇到真实故障自然认不出来。

我做过一个对比实验,用CWRU轴承数据集,原始空间SMOTE增强后训练,测试集准确率比不做增强还低了6个百分点。原因就是合成样本把决策边界往错误方向推了。

2.2 工况耦合带来的近邻污染

重大装备的工况变化剧烈,风电齿轮箱从切入风速到额定风速,转速跨度能到3倍。原始信号空间里,两个样本的欧氏距离主要反映的是工况差异,而不是故障差异。SMOTE找的"近邻"很可能是同一故障在不同工况下的样本,插值出来的东西工况属性模糊,故障属性也被稀释了。

注意:不平衡学习的近邻度量必须建立在"同类工况可比"的前提下,否则SMOTE就是在制造噪声。

2.3 高维稀疏下的距离失效

原始振动信号动辄几千点,直接算距离会遭遇维数灾难,所有样本对的距离趋于相等,近邻选择基本随机。这时候SMOTE合成的样本质量完全不可控。Feature-level的做法是先降维、先提特征,把维度压到几十维甚至十几维,距离度量才有意义。

3. Feature-level SMOTE的整体设计思路

3.1 三层架构:信号层、特征层、决策层

我把整套流程分成三层来理解。信号层负责数据清洗、工况切片、重采样;特征层负责时域、频域、时频域特征提取,以及工况归一化;决策层才是分类器和SMOTE介入的地方。Feature-level SMOTE的关键动作全部发生在特征层和决策层之间,也就是特征提取完成之后、送入分类器之前。

这样设计的好处是:合成样本在特征空间里插值,每个特征都有明确的物理含义(比如峭度、均方根、包络谱峰值),插值结果可以反查、可以解释,工程师能判断这个合成样本是否合理。

3.2 与DSMHSA的配合逻辑

DSMHSA(Dual-Stream Multi-Head Self-Attention,双流多头自注意力)是我在最近一个项目里用的特征融合模块。它的作用是同时处理时域特征流和频域特征流,通过多头注意力做跨域交互。Feature-level SMOTE放在DSMHSA之后、分类头之前,原因是注意力模块已经把原始特征映射到了一个更具判别性的隐空间,在这个空间里做SMOTE,合成样本的类间可分性更好。

如果你的模型没有注意力模块,SMOTE就放在特征标准化之后、分类器之前,效果同样成立,只是增益会小一些。

3.3 方案选型的取舍

方案增强位置优点缺点适用场景
原始空间SMOTE时序信号实现简单物理不合理,噪声大几乎不适用PHM
Feature-level SMOTE特征向量物理可解释,效果好依赖特征工程质量主流推荐
损失函数加权决策层不改数据对极端不平衡乏力轻度不平衡
生成模型增强特征/信号可生成多样样本训练不稳定,需大量数据数据量充足时

我实测下来,Feature-level SMOTE在样本比1:100以内的场景下,F1提升普遍在8到15个百分点,比损失加权稳定得多。

4. 特征工程:Feature-level SMOTE的地基

4.1 时域特征提取

时域特征是最基础的一层,计算快、物理意义明确。我常用的有:均方根值(RMS)、峭度(Kurtosis)、峰值因子(Crest Factor)、裕度因子(Margin Factor)、波形因子(Shape Factor)。这几个指标对早期冲击性故障特别敏感,峭度在轴承早期点蚀阶段就会明显上升。

代码上直接用numpy和scipy就能算:

import numpy as np from scipy.stats import kurtosis def time_domain_features(signal): rms = np.sqrt(np.mean(signal**2)) peak = np.max(np.abs(signal)) kurt = kurtosis(signal) crest = peak / rms shape = rms / np.mean(np.abs(signal)) margin = peak / (np.mean(np.sqrt(np.abs(signal)))**2) return [rms, kurt, crest, shape, margin]

这段代码我用了三年,稳定可靠。注意峭度对异常值敏感,信号里如果有明显的工频干扰或者传感器脱落导致的尖峰,要先做剔除。

4.2 频域特征提取

频域特征主要靠FFT和包络谱。轴承故障的特征频率(BPFO、BPFI、BSF、FTF)是核心,但实际工况下转速波动会导致频率漂移,所以我会同时提取故障特征频率处的幅值、以及其谐波能量占比。

def freq_domain_features(signal, fs, fault_freqs): n = len(signal) spectrum = np.abs(np.fft.rfft(signal)) / n freqs = np.fft.rfftfreq(n, 1/fs) features = [] for ff in fault_freqs: idx = np.argmin(np.abs(freqs - ff)) features.append(spectrum[idx]) # 谐波能量 harm_energy = 0 for k in range(2, 5): idx_k = np.argmin(np.abs(freqs - k*ff)) harm_energy += spectrum[idx_k] features.append(harm_energy) return features

提示:故障特征频率需要根据轴承几何参数和实时转速计算,转速建议用键相传感器或者从振动信号里做阶次跟踪提取,不要用额定转速硬套。

4.3 时频域特征与工况归一化

时频域我用小波包分解,把信号分成若干频带,每个频带的能量占比作为特征。小波包比STFT更适合处理非平稳的冲击信号。分解层数一般取3到4层,层数太多会导致特征维度爆炸,而且高频段信噪比低。

工况归一化是Feature-level SMOTE能不能work的关键一步。我的做法是按转速和负载分箱,每个工况箱内单独做Z-score标准化。这样不同工况下的同一特征才具备可比性,SMOTE插值才不会跨工况污染。

5. Feature-level SMOTE的实操流程

5.1 数据准备与工况切片

拿到原始数据后,第一步是按工况切片。以风电齿轮箱为例,我会按转速每100rpm一个箱、负载每20%额定功率一个箱,把连续数据切成若干工况片段。每个片段长度取2048点或4096点,保证频率分辨率足够。

切片之后统计每个工况箱内的类别分布。如果某个工况箱里故障样本为零,那这个箱就没法做SMOTE,只能靠其他工况箱的样本迁移,或者用域适应方法。我一般会先做这个统计,避免后面白忙活。

5.2 特征提取与标准化

对每个切片提取前面说的时域、频域、时频域特征,拼成一个特征向量。维度控制在30到60之间比较合适,太低信息不足,太高SMOTE的距离度量会退化。

标准化按工况箱内做,用RobustScaler而不是StandardScaler,因为振动特征里常有离群值,均值和方差容易被带偏。RobustScaler用中位数和四分位距,更稳。

from sklearn.preprocessing import RobustScaler scaler = RobustScaler() X_scaled = scaler.fit_transform(X)

注意scaler要在训练集上fit,然后transform验证集和测试集,不能全量fit,否则数据泄漏。

5.3 SMOTE参数选择与采样策略

SMOTE的核心参数是k_neighbors,默认5。在特征空间里,我一般取3到7之间。k太小合成样本多样性不足,k太大近邻可能跨类。采样比例上,我不建议把少数类补到和多数类1:1,那样容易过拟合。实测1:3到1:5之间比较稳,具体看少数类样本的绝对数量。如果少数类只有几十条,补到1:5也就几百条,模型能学到但不会过拟合。

from imblearn.over_sampling import SMOTE smote = SMOTE( sampling_strategy=0.3, # 少数类补到多数类的30% k_neighbors=5, random_state=42 ) X_res, y_res = smote.fit_resample(X_train, y_train)

注意:SMOTE只能在训练集上做,验证集和测试集必须保持原始分布,否则评估结果全是假的。

5.4 与DSMHSA的集成

如果模型里有DSMHSA模块,SMOTE放在注意力输出之后。具体做法是把训练集过一遍DSMHSA,拿到隐层特征,在隐层特征上做SMOTE,然后用合成后的隐层特征训练分类头。推理时不需要SMOTE,直接前向传播。

这种做法的好处是注意力模块已经学到了跨域交互,隐空间里的类间距离更大,SMOTE插值更安全。我对比过在注意力前和注意力后做SMOTE,后者F1平均高3到5个百分点。

6. 故障诊断代码的关键实现细节

6.1 训练循环与类别权重

即使做了SMOTE,我仍然会在损失函数里加类别权重,双保险。权重按类别频率的倒数算,归一化到合理范围。

import torch import torch.nn as nn class_counts = np.bincount(y_res) weights = 1.0 / class_counts weights = weights / weights.sum() * len(class_counts) criterion = nn.CrossEntropyLoss(weight=torch.FloatTensor(weights))

这样即使SMOTE合成样本有噪声,损失函数也不会被带偏。

6.2 评估指标的选择

不平衡场景下准确率毫无意义。我只看三个指标:少数类的召回率(Recall)、F1分数、以及AUC-PR(Precision-Recall曲线下面积)。AUC-ROC在不平衡时会被多数类主导,AUC-PR更能反映少数类的识别能力。

from sklearn.metrics import f1_score, recall_score, average_precision_score f1 = f1_score(y_test, y_pred, pos_label=1) recall = recall_score(y_test, y_pred, pos_label=1) auc_pr = average_precision_score(y_test, y_prob)

6.3 交叉验证的正确姿势

不平衡数据的交叉验证要用分层K折,而且SMOTE必须在每折的训练集内部做,不能在全量数据上做完再分折。我见过太多人在这里翻车,报告的结果虚高十几个点。

from sklearn.model_selection import StratifiedKFold skf = StratifiedKFold(n_splits=5, shuffle=True, random_state=42) for train_idx, val_idx in skf.split(X, y): X_train, X_val = X[train_idx], X[val_idx] y_train, y_val = y[train_idx], y[val_idx] X_res, y_res = smote.fit_resample(X_train, y_train) # 训练和评估

7. 常见问题与排查技巧实录

7.1 合成样本导致验证集性能下降

这是最常见的问题。原因通常是SMOTE的k_neighbors太大,近邻跨了类,或者特征没有做工况归一化。排查方法:把合成样本和真实样本一起降维到二维可视化,看合成样本是否落在少数类簇内。如果散得到处都是,就是近邻选择出了问题。

7.2 少数类召回率上不去

如果SMOTE之后少数类召回率还是低,先检查特征里有没有对故障敏感的指标。我遇到过特征全是时域统计量、没有频域特征的情况,模型根本学不到故障模式。补上包络谱特征后召回率从0.6涨到0.85。

7.3 训练集AUC高但测试集崩

典型的过拟合。SMOTE合成样本太多,模型把合成样本的噪声也学进去了。解决办法:降低采样比例,加Dropout,加L2正则,或者用SMOTE的变体如Borderline-SMOTE只对边界样本做增强。

7.4 问题速查表

现象可能原因排查动作解决方向
验证集性能下降近邻跨类可视化合成样本减小k_neighbors
少数类召回低特征不敏感检查频域特征补充包络谱特征
训练测试差距大过拟合看学习曲线降采样比例加正则
合成样本物理不合理未工况归一化按工况分箱检查分箱标准化
交叉验证结果虚高SMOTE泄漏检查流程SMOTE放折内

7.5 独家避坑技巧

第一个技巧:SMOTE之前先做一次特征选择,把冗余特征去掉。冗余特征会让距离度量失真,近邻选择不准。我用方差阈值加互信息做初筛,特征从60维降到35维左右,SMOTE效果明显变好。

第二个技巧:对合成样本做一次合理性过滤。计算每个合成样本到其同类真实样本最近邻的距离,如果超过某个阈值就丢弃。这个阈值可以取同类真实样本距离分布的95分位数。我实测能过滤掉10%到15%的劣质合成样本,F1再涨2个点。

第三个技巧:如果少数类样本实在太少(少于50条),SMOTE之前先用简单的数据增强,比如加高斯噪声、做时间缩放,把少数类扩到100条以上再做SMOTE。这样近邻选择更可靠。

8. 一个完整的风电齿轮箱案例复盘

去年那个风电齿轮箱项目,原始数据是三年振动监测,正常样本42万条,早期磨损样本217条,断齿样本89条。类别比接近1:2000。最初用原始空间SMOTE,测试集断齿类召回率只有0.42。后来改成Feature-level SMOTE,流程是:按转速和功率分12个工况箱,每箱提取38维特征,RobustScaler标准化,DSMHSA融合时频双流,隐层做SMOTE补到1:5,损失加类别权重。

最终测试集断齿类召回率0.89,磨损类0.83,AUC-PR从0.51涨到0.87。推理延迟增加不到2毫秒,完全满足在线监测要求。这个案例让我确信,Feature-level SMOTE在PHM里不是锦上添花,而是极端不平衡场景下的必需品。

后续我还想试试把SMOTE和对比学习结合,在隐空间里做有监督的样本生成,应该能进一步提升合成样本的质量。这个方向等有新的实测结果再跟大家分享。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进