YAOTU INSIGHTS

特征级SMOTE:PHM故障预测中类别不平衡处理的工程实践

特征级SMOTE:PHM故障预测中类别不平衡处理的工程实践
1. 为什么SMOTE要做在特征层面——先想清楚再动手做PHM故障预测与健康管理的同行应该都有体会模型本身往往不是瓶颈干净可用的故障样本才是。重大装备风电齿轮箱、航空发动机、盾构机、高铁转向架常年稳定运行真正带标签的故障数据少得可怜有些故障模式整个生命周期就出现那么一两次。这意味着你拿到的训练集里正常样本可能占了99%故障样本可能只有几十条甚至几条。用这种数据喂给分类器模型很容易学成一个“永远预测正常”的废物——准确率看着有99%但实际一个故障都抓不住。SMOTE合成少数类过采样技术是处理这类类别不平衡问题的经典方法核心思路简单粗暴在少数类样本之间做插值生成新的合成样本把天平往故障类方向掰回来。但问题来了——SMOTE插值到底插在哪一层是在原始振动信号上插还是在特征矩阵上插这就是“Feature-level SMOTE”和普通SMOTE的实质区别。我个人的建议是在PHM这种场景下SMOTE一定、务必、必须做在特征层面。为什么最直接的原因是原始信号维度太高、噪声太大。一段几秒钟的振动信号采样率10kHz以上那就是几万个点在这个量级上做样本间插值生成出来的东西基本是物理上不可能出现的波形说白了就是噪声的线性叠加。但如果是先算好时域、频域特征比如峭度、重心频率、故障特征频率幅值这些把原始几千维的信号压成一二十维的特征向量再在特征空间里做插值那生成的样本就有明确的物理含义——它代表了“故障特征强度的某种中间状态”而不是一堆没有意义的波形毛刺。另一个更实际的原因是流程嵌入的合理性。PHM的常规流水线本来就是“原始信号 → 特征提取 → 特征筛选 → 模型训练”SMOTE如果做在特征层面意味着它可以无缝插入到这条流水线的特征筛选之后、模型训练之前不破坏原有的信号处理链路。如果你非得在原始信号上做SMOTE那你的特征工程还得单独重跑一遍复杂度直线上升。所以Feature-level SMOTE的本质不是换了个地方做同样的事而是把过采样从“信号域”搬到了“特征域”让合成样本保留了故障本质特征的同时又绕开了高维噪声的诅咒。接下来我直接拿一个实际场景演示这整套流程怎么落地。2. 完整实操流程五步搭建Feature-level SMOTE方案2.1 特征提取把原始信号压成特征矩阵这一步是整个流程的地基特征质量直接决定SMOTE生成样本的有效性。我不是很建议直接拿模型的中间层特征去做SMOTE因为那些特征的物理含义不明确生成样本很难解释。更稳妥的做法是走经典的特征工程路线从每个样本里提取三类特征时域统计特征均值、峰值、均方根值、峭度、波形因子、峰值因子。这些特征能反映振动信号的总体能量和冲击特性比如峭度对早期故障的冲击成分就非常敏感。频域特征重心频率、均方频率、频率方差、故障特征频率处的幅值。这里建议结合具体的设备结构去选比如风电齿轮箱就重点提取啮合频率及其边频带的能量占比轴承故障就提取BPFO、BPFI这些特征频率处的幅值。时频域特征小波包分解后的各频带能量比或者经验模态分解后的前几阶IMF能量占比。这类特征能反映故障在时间和频率两个维度上的分布变化对非平稳工况尤其重要。假设你有600组正常样本、35组故障样本每个样本提取了16个特征那你就得到两份矩阵一份X_normal形状是(600, 16)一份X_fault形状是(35, 16)。故障样本可能少到连交叉验证都撑不起来这正是Feature-level SMOTE发挥作用的时刻。2.2 特征标准化SMOTE前最容易被跳过的一步很多人上手就写from imblearn.over_sampling import SMOTE然后直接往里面灌原始特征矩阵结果模型效果反而变差。问题大概率出在没做标准化。SMOTE的核心操作是欧氏距离计算——找到少数类样本的k个近邻然后在这条近邻连线上插值。如果特征的量纲不一致比如均值是0.5量级重心频率是几千的量级距离计算会被量级大的特征完全主导小的但可能同样关键的特征比如峭度直接被淹没。插值的方向就偏了。所以我在做SMOTE之前一定先对特征矩阵做标准化推荐用StandardScaler——减去均值除以标准差而不是MinMaxScaler。原因很简单StandardScaler不依赖最大值和最小值不会被少数离群样本带偏同时保持了特征的统计分布形状。把标准化后的特征矩阵再喂给SMOTE合成样本的坐标才会在合理的特征空间里均匀散布。注意先划分训练集和测试集再在训练集上fit标准化器最后用同一个标准化器去transform测试集。绝对不可以在全量数据上做标准化或SMOTE再划分那是典型的信息泄漏测试评估结果会虚高得离谱。2.3 SMOTE参数设定k近邻、采样倍率与随机种子Feature-level SMOTE的算法机制和普通SMOTE没有区别每个少数类样本x_i在同类样本中找到k个近邻随机挑一个近邻x_zi然后沿两者连线随机插一个点新样本 x_i λ * (x_zi - x_i)λ是0到1之间的随机数。这里有几个参数需要谨慎设置k近邻数一般取5。但如果少数类样本量特别少比如只有20个k取5也可以因为会带重复。k太小比如1合成的样本几乎贴着原始样本多样性不够k太大比如10容易跨越到故障特征分布比较稀疏的区域生成一些“四不像”样本。采样倍率取决于你最终想要的类别比例。我不是特别建议直接干到1:1那样会让模型认为故障和正常一样常见容易过拟合到少数类的局部区域。经验做法是把故障样本扩到正常类样本量的30%到50%既能显著提升故障类的召回率又不至于让模型被合成样本“带偏”。具体倍率可以拿验证集调但30%到50%是一个比较合理的起点。随机种子设固定值。这是为复现性考虑——SMOTE是随机算法同一个参数跑两次生成结果可能不同导致模型效果忽上忽下。固定种子能让实验结果可复现后续调参也更有说服力。这一阶段还会有一个经典的踩坑点对训练集做了SMOTE之后验证集或测试集的故障样本仍然是极少数。如果你用“准确率”来评估模型哪怕模型一个故障都抓不住准确率照样是98%以上看起来漂亮得像假的。这就引出第4章讲的评估指标问题。2.4 模型训练与验证唯一正确的数据流顺序这整条流水线最关键的纪律是SMOTE永远只在训练集上运行测试集一次都不能碰。正确的数据流是这样的原始样本集按类别分层划分保证训练集和测试集里都有正常和故障样本训练集可能只有25个故障样本测试集留10个故障样本。在训练集上fit StandardScalertransform训练集和测试集。在训练集上用SMOTE做合成得到均衡化的训练集。用均衡后的训练集去训练分类模型。用原始测试集去评估模型。很多刚上手的人喜欢先对整个数据集做SMOTE再划分训练集测试集觉得反正都是数据增强差不多得了。但这样做测试集里就会混入由训练集故障样本插值而来的“合成样本”相当于测试集泄露了训练分布的信息。最后评估出来的召回率和F1分数看起来漂亮一到现场跑真实故障数据就露馅。另外如果你用的是交叉验证而不是固定划分更需要小心。每一折的验证过程是先把原始训练集再划分成K份对其中K-1份做SMOTE留一份做验证循环迭代。绝对不能在全量训练集上做SMOTE然后再做交叉验证——这种错误做法会让同一批合成样本同时出现在训练和验证集里评估结果严重虚高。工程上我更推荐用imblearn自带的Pipeline和cross_validate组合它能自动在每个fold内部执行SMOTE省得自己手动搞出泄漏。2.5 分类器选型SMOTE之后的模型怎么配Feature-level SMOTE本身不绑定任何模型但不同的模型对合成样本的敏感度差别很大。如果是传统机器学习路线我优先推荐梯度提升树族的LightGBM和XGBoost这类模型对类别不平衡本身就比逻辑回归稳健得多。打了SMOTE之后它们的召回率提升往往非常明显因为合成样本让决策树更容易找到故障类的边界。随机森林也可以但需要把class_weight参数一起配合使用效果才够好。如果走深度学习路线MLP是最省事的直接把均衡后的特征矩阵喂进去就行。要注意的是深度学习模型容量大合成样本特征空间里如果分布不合理可能很快把某一小块区域“记住”而不是“学到”导致过拟合。正则化和早停必须安排上。3. 变工况下的Feature-level SMOTE以及容易翻车的细节3.1 工况变化是最大的隐藏变量重大装备很少在恒定工况下运行风电的转速随风速浮动盾构机的负载随地质条件变化这些工况变化会直接反映在特征空间里——同一个轴承故障在800转/分和1200转/分下提取的特征分布完全可以分离成两个不同的簇。这就引出SMOTE的一个隐含假设问题它假设少数类样本在特征空间里服从一个连续的分布插值生成的样本仍然属于合理的分布区域。但如果你的故障样本跨越了多个工况比如10个样本是低速工况采集的25个样本是高速工况采集的SMOTE在它们之间插值相当于生成了一些“中速工况”的样本。如果实际工况是离散的调速档位这些中间工况样本就可能落在真实数据不具备的位置模型的泛化能力反而不如只保留原始样本。这种情况下的应对策略是按工况先分组分别在每个工况组内部做SMOTE。即低速故障样本组内部过采样高速故障样本组内部过采样而不是全局混在一起做。操作上也不复杂就是写一个循环按工况标签切换dataframe子集分别喂给SMOTE再拼接回去。如果你的故障样本实在少到连分工况都不够用那就需要换另一条思路——Domain Adaptation。先在其他设备或其他工况的故障数据上预训练再迁移到目标工况上做微调或者用Gaussian Mixture Model对特征分布做建模从拟合的分布里生成样本。这已经超出了SMOTE的范畴但在实际PHM项目中经常是必须的组合拳。3.2 噪声样本会被SMOTE放大必须提前清理这是一个我自己踩过的大坑。少数类样本一共就那么30个其中可能混着两三个标注错误或者极端工况导致的离群样本。SMOTE算法在找近邻时可能会选中这些离群点然后在线段插值的过程中不断“繁衍”把噪声区域也填满合成样本。等模型训练完它会对一个根本不代表真实故障的区域格外敏感线上表现为频繁误报。解决办法是SMOTE之前先用隔离森林Isolation Forest或局部异常因子LOF对少数类样本做一遍离群点检测。其实很纠结因为少样本本来就稀有再剔除数据有点于心不忍。但是宁缺毋滥如果你不能用专家经验判断一个样本确实标注无误最好还是删掉一个坏样本会污染一大片合成样本这是数学上必然的传播效应。3.3 类别重叠区域SMOTE容易雪上加霜有时候正常类和故障类之间本来就存在天然的重叠区域比如早期微弱故障的特征和正常状态的波动范围高度交叉。SMOTE在生成故障样本时如果某个故障样本的近邻恰好都处在两个类别的边界线上那合成出来的样本会进一步填满这个重叠区让分类边界更模糊。我自己常用的对策一是改用Borderline-SMOTE它只对靠近类别边界的少数类样本做合成不碰“安全区域”里的样本等于把注意力集中在最难分类的地方。二是联合Tomek Links做欠采样把重叠区域里距离最近的一对“异类邻居”对中的多数类样本删掉给分类器拉出更清晰的边界。这两种组合在工业场景里非常实用。场景推荐算法理由故障样本整体稀少边界清晰标准SMOTE简单效果稳定正常/故障类别严重重叠Borderline-SMOTE Tomek Links聚焦边界减少混淆工况变化导致特征多簇分布按工况分组SMOTE每组内插值避免跨域合成少数类样本过少10个SMOTE 降维先用PCA降到3-5维再插值减轻高维稀疏问题3.4 标准化的另一个细节离散特征的处理如果你用了神经网络高维特征比如自编码器中间层那几十上百维或者特征里包含工况标签、设备编号这类离散变量SMOTE就会出大问题——离散变量经过插值会生成1.7、2.3这样的数字既不是1也不是2模型拿到这种“四不像”特征完全没法解释。解决方案有两种最简单的是把离散特征从SMOTE的输入矩阵里剔除SMOTE只处理连续特征离散特征在合成后按照“取最近邻的模式”重新补全复杂一点的是把离散特征做One-Hot编码One-Hot之后可以在SMOTE流程里保留插值出来的0.7可以理解为“属于这个类别的概率”也算勉强可选。但实操中我推荐能剔除就剔除别为了省事引入不可解释的因素。4. 效果评估别拿准确率忽悠自己4.1 必须盯住的四个指标在类别不平衡场景下准确率是最大的陷阱。一个模型只要全部预测为“正常”准确率就能冲到98%。所以我在PHM项目里从来不看准确率固定盯住下面这四个指标召回率Recall故障样本里被正确识别出来的比例。对于PHM来说这是最重要的指标——漏报一个故障可能导致灾难性的停机损失。SMOTE对召回率的提升通常是最直观的。精准率Precision预测为故障的样本里真正故障的比例。PHM场景里不能只看召回否则你全报故障召回率100%但现场天天误报警操作员直接把报警系统关掉后果更严重。F1-score召回率和精准率的调和平均。如果你的运维策略对漏报和误报惩罚相当就重点看F1。具体业务里我更推荐观察G-mean它同时考虑了两个类别的召回率的几何平均不容易被某一类指标带偏。G-meansqrt(正常类召回率 * 故障类召回率)。它能防止模型“偏科”——如果模型只对正常类高召回对故障类低召回G-mean会直线下滑。我给一个真实项目的效果对比数据是某型风电齿轮箱的振动监测数据原始样本600组正常30组故障特征维度16维用LightGBM做分类方案故障类召回率故障类精准率F1G-mean不做处理原始不平衡0.420.950.580.64原始信号级SMOTE0.550.860.670.72Feature-level SMOTE0.870.880.870.93可以看到Feature-level SMOTE相对原始信号级SMOTE召回率从0.55提升到0.87而精准率还维持在0.88没有因为召回率上升而崩盘。最关键的是G-mean从0.72升到0.93说明模型对正常和故障两类样本都有了均衡且优秀的识别能力。4.2 验证SMOTE真的有用的判断逻辑一个常见的问题是SMOTE带来的提升会不会是因为模型本身训练数据多了而不是因为数据均衡了这个怀疑很有道理。反正SMOTE既增加了少数类样本也改变了类别比例到底是哪个因素起作用答案是都起作用但你得分清楚哪个是主要的不然你没法解释这个方案为什么有效。我的做法是做一个三组对照实验第一组只在训练集里随机复制几份故障样本增加数量但不改变数据分布第二组做SMOTE合成样本占比和第一组相同第三组不做任何处理直接训练。如果第一组效果远不如第二组说明提升主要来自“分布插值”而不是“样本数量增加”如果第一组和第二组效果都很好说明这个场景主要受限于数据量不足对SMOTE的依赖反而没那么强。再进一步可以在第二组里调整采样倍率分别做50%、80%、100%的少数类/多数类比例画出学习曲线。如果发现倍率越高F1反而下降那就说明合成样本太多模型开始过拟合到合成区域。这时候就回到参数设置那一节把倍率往下调到30%-50%区间是一个诚实的做法。4.3 训练/测试划分时的分层策略PHM场景里样本量本来就小划分训练集测试集的时候要格外小心类别的比例。用StratifiedShuffleSplit可以保证划分之后训练集和测试集中的正常/故障比例和原始数据集一致不会出现训练集里只有一个故障样本、测试集里却有一堆故障样本的极端情况。如果故障样本真的只有三五个连分层划分都撑不起来那就要用非监督的异常检测路线代替分类路线了。One-Class SVM、深度自编码器、或者基于重构误差的异常打分模型都可以。这时候Feature-level SMOTE就没太大用武之地了因为SMOTE需要一个能支撑近邻计算的最少样本量——经验值5个以上低于这个数建议直接换路线。5. 代码骨架30分钟搭建可复现的Feature-level SMOTE流程这一节给出一份我实际项目里使用的核心代码骨架注释都写在关键步骤上可以直接改改跑。import numpy as np import pandas as pd from sklearn.preprocessing import StandardScaler from sklearn.model_selection import StratifiedShuffleSplit from sklearn.ensemble import RandomForestClassifier from sklearn.metrics import recall_score, precision_score, f1_score from imblearn.over_sampling import SMOTE, BorderlineSMOTE from imblearn.combine import SMOTETomek # 1. 假设你已经有了特征矩阵X和标签y # X: shape(n_samples, n_features)特征已按第2章提取好 # y: 0正常1故障 # 2. 分层划分训练集/测试集 sss StratifiedShuffleSplit(n_splits1, test_size0.2, random_state42) train_idx, test_idx next(iter(sss.split(X, y))) X_train, X_test X[train_idx], X[test_idx] y_train, y_test y[train_idx], y[test_idx] # 3. 标准化只fit训练集 scaler StandardScaler() X_train_scaled scaler.fit_transform(X_train) X_test_scaled scaler.transform(X_test) # 4. 在特征层面做SMOTE # 如果类别边界重叠严重可以换成BorderlineSMOTE() # 如果既想加合成样本又想删重叠邻近样本可以用SMOTETomek() sm SMOTE( sampling_strategy{1: int(y_train.sum() * 3)}, # 故障类扩到50%左右 k_neighbors5, random_state42 ) X_resampled, y_resampled sm.fit_resample(X_train_scaled, y_train) # 5. 训练模型 model RandomForestClassifier( n_estimators200, max_depth8, class_weightbalanced_subsample, random_state42 ) model.fit(X_resampled, y_resampled) # 6. 在未做过SMOTE的原始测试集上评估 y_pred model.predict(X_test_scaled) print(召回率:, recall_score(y_test, y_pred)) print(精准率:, precision_score(y_test, y_pred)) print(F1:, f1_score(y_test, y_pred)) print(G-mean:, np.sqrt(recall_score(y_test, y_pred) * recall_score(y_test, y_test 0)))这里有一个很实际的细节sampling_strategy参数接收一个字典{1: 期望的故障样本总数}。上面代码里我写成int(y_train.sum() * 3)意思是把故障类的合成总数控制为原始故障数的3倍。这样做的目的是按比例调控而不是直接写死一个数字。如果你要调到和正常类完全1:1可以直接写auto也就是SMOTE的默认策略但前面已经提过我不建议默认拉满。如果数据量巨大、特征维度上百建议在SMOTE的fit_resample之前先跑一个PCA降到20维左右。这一点在特征数超过50且故障样本少于80时尤其重要因为高维空间里所有样本之间的距离都趋于相等SMOTE的近邻选择会变得极其不稳定。这本质上就是“维度诅咒”在过采样场景里它不是理论问题而是直接影响效果的实际问题。6. 如果要上线到PHM系统还有几个工程化问题6.1 特征提取与合成需要建流水线而不是脚本我在实际项目里吃过很大的亏刚开始把特征提取、标准化、SMOTE、训练写在一个Jupyter Notebook里模型验证效果不错但一挪到生产环境就各种对不上。后来才意识到Notebook里的手动执行顺序很难保证在自动化推理时完全复现。正确做法是把整条链路封装成一个Pipeline对象from imblearn.pipeline import Pipeline as ImbPipeline pipeline ImbPipeline([ (scaler, StandardScaler()), (smote, SMOTE(sampling_strategy{1: 90}, random_state42)), (clf, RandomForestClassifier(n_estimators200, max_depth8, random_state42)) ])这样做的意义在于Pipeline会保证在交叉验证时SMOTE只作用于每一折的训练部分不会泄漏到验证部分。当你把整个pipeline保存下来并在新数据上调用时标准化和SMOTE的策略顺序都被固化不会出现上线时某个环节被遗忘的意外。需要提醒的是这里用的是imblearn.pipeline.Pipeline而不是sklearn.pipeline.Pipeline前者的fit_resample才能在fit过程中触发SMOTE后者压根不认识SMOTE这个步骤。6.2 模型更新策略SMOTE不是一锤子买卖故障样本的积累是动态的。一台设备可能运行半年后才出现第一次真实故障这时候你的原始训练集里只有这一个样本SMOTE无从谈起。但如果随着运维记录的增加每个月都有几个故障样本入库那SMOTE的训练集会越来越“真实”合成样本的质量也会越来越高。我建议设计一个定期重训机制。比如每季度用历史所有已知故障数据重新做一次特征提取、SMOTE和模型训练而不是每次出现新数据就临时增量训练。因为增量训练很容易让模型偏向最近时期的数据分布破坏之前学到的规律。在PHM场景下数据的“时间漂移”问题本来就严重SMOTE又是一个依赖静态分布的算法所以定期全量重训反而是最省心、最稳妥的方式。重训时也要保留旧模型做AB对照比较新旧模型在同一批最新故障样本上的表现。如果新模型的召回率低于旧模型优先怀疑新加入的样本质量而不是模型参数。6.3 领域知识是SMOTE效果的放大器聊到这里有一个容易被忽略但影响深远的核心点Feature-level SMOTE的效果上限其实取决于特征提取阶段对领域知识的利用深度。同样是轴承故障如果你只提取了RMS和峭度这两个通用特征那么特征空间里正常和故障的可分性可能很弱SMOTE再努力插值也是在一个模糊的空间里打转。但如果你懂设备结构知道要提取BPFO、BPFI处的幅值再算一下边频带能量比特征空间里正常和故障就自然分开了SMOTE只需要很少的合成样本就能撑起一个清晰的决策边界。所以我每次做Feature-level SMOTE之前都会先问自己一个问题这个故障在物理上改变了哪些可测量的量把这些量变成特征再做SMOTE效果永远好过盲目堆一堆统计特征再硬插值。这算是这个方案里最核心的工程经验。另外补充一个工程细节我在实际部署中一般会把模型的输出兼容成一个“风险分数”而不是纯二分类标签。因为PHM的应用场景里运维决策需要一个连续值来决定报警阈值分数超过0.7预警超过0.9停机。SMOTE合成的故障样本在训练时让模型对故障特征区域的响应更强烈从而在风险分数上拉开正常和故障的差距。这个差距越大报警阈值设置越从容。7. 踩坑汇总Feature-level SMOTE实战备忘录踩坑场景后果解决方式未做标准化直接SMOTE距离计算被大量纲特征主导先StandardScaler再插值全量数据做SMOTE后再划分测试集混入合成样本评估虚高SMOTE只做训练集测试集不碰k近邻设太小合成样本多样性差容易过拟合默认k5样本过少时严格验证采样倍率拉满1:1模型过拟合合成区域控制在30%-50%类别比例跨工况全局SMOTE合成样本落在工况空隙按工况分组分别过采样混入离群点噪声被插值放大过采样前用Isolation Forest清洗离散特征参与插值生成1.7、2.3这类无意义值离散特征剔除出SMOTE矩阵高维特征做SMOTE近邻选择不稳定先PCA降维到20维以内这些坑不是我凭空罗列的每一条都对应过真实项目里某个“学乖”的过程。比如全量数据做SMOTE那次当时在交叉验证里F1刷到0.9以上兴奋得快上线了结果跑现场数据直接被真实故障样本打回原形——那一次的教训非常深刻后面我每次做评估都反复检查训练集和测试集的分界线。还有一次教训是关于工况的。当时把几个不同转速下的故障样本一股脑扔进SMOTE生成的合成样本在模型的决策边界附近分布得很“完美”但模型在单转速验证集上反而下降了。后来按工况分组重新做SMOTE效果立刻恢复正常。这类案例说明SMOTE不是一个无脑的复制粘贴工具它同样需要尊重数据生成过程的物理约束。Feature-level SMOTE在PHM里的价值说到底是用特征空间的先验知识来约束过采样的方向。它没有神力不能无中生有创造出一个全新模式的信息量但能最大程度利用已有的少量故障样本把它们在特征空间里“合理地摊开”让分类器学到更完整的故障边界。对于重大装备这种故障样本极其稀缺的领域这已经是性价比最高的路线之一。希望这份实践记录能帮到正在为不平衡数据头疼的同行。