尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

工业设备故障诊断:随机森林+IsolationForest+TF-IDF多模型融合实战

工业设备故障诊断:随机森林+IsolationForest+TF-IDF多模型融合实战 1. 工业设备故障诊断为什么值得用多模型融合来做工业现场的旋转机械轴承、转子、齿轮箱这些部件一旦出问题停机损失动辄按小时算。我接触过好几个做设备预测性维护的团队早期基本都走过同一条路先上一套振动传感器采一堆数据然后拿一个分类器硬跑。结果往往是——正常样本识别率很高一到早期故障就拉胯尤其是“过热”和“转子不平衡”这两类特征空间高度重叠单模型根本分不开。这个项目的核心思路就是用随机森林 IsolationForest TF-IDF三条线做融合诊断分别覆盖三类典型故障轴承故障、设备过热、转子不平衡。为什么是这三个模型而不是别的组合我拆一下背后的逻辑。轴承故障的振动信号通常表现为周期性冲击频谱上有明显的特征频率BPFO、BPFI、BSF等这类问题适合用有监督的随机森林去学——因为轴承故障的样本相对容易标注而且随机森林对高维特征、非线性边界的容忍度很好不像SVM那样对核函数和参数敏感。过热问题比较特殊。它很多时候不是“突变”而是温度缓变叠加负载波动早期样本极少甚至没有明确标签。这时候IsolationForest就派上用场了——它做的是异常检测不需要标签靠“孤立程度”判断样本是否偏离正常分布。温度、电流、转速这几个维度的组合异常IsolationForest能比较早地给出预警。转子不平衡的难点在于它的振动特征和轴承故障有重叠但频域上1倍频转频幅值占比会明显偏高。这时候TF-IDF的思路就很有意思了——它本来是NLP里做文本特征加权的但把频谱的频段当作“词”把幅值当作“词频”就能构造出一种加权频谱特征让1倍频、2倍频这些关键频段的权重被放大抑制噪声频段。这个思路我在实际项目里试过对转子类故障的区分度提升很明显。所以这个融合模型不是简单“三个模型投票”而是分工协作随机森林管有标签的轴承故障IsolationForest管无标签的过热异常TF-IDF负责把频谱特征重新加权后喂给分类器提升转子不平衡的辨识度。最后用一个加权融合层把三路输出整合成最终诊断结论。适合谁来参考如果你正在做工业设备预测性维护、PHM故障预测与健康管理、或者振动信号分析这个方案可以直接抄。如果你只是想学随机森林、IsolationForest、TF-IDF的实际用法这里也有完整的代码和参数解释。哪怕你是刚入门Python的跟着步骤走也能跑通。提示这个项目的代码和数据我会在文中给出关键片段完整版可以按文中结构自己搭。环境用Python 3.10主要依赖scikit-learn、numpy、pandas、scipy。2. 三模型融合诊断的整体架构与选型逻辑2.1 为什么不是“一个模型打天下”先说一个我踩过的坑。最早我做轴承故障诊断直接拿随机森林跑原始振动信号准确率能到95%以上但一上线就发现——新设备、新工况下准确率掉到70%都不到。原因很简单训练集和测试集的分布不一致而且“过热”和“转子不平衡”这两类故障在训练集里样本太少随机森林直接把它们当噪声忽略了。后来我改成多模型融合核心思路是不同故障类型用不同的检测范式。有标签、样本充足的用监督学习无标签、样本稀少的用异常检测特征区分度不够的用加权方法重新构造特征。这样每个模型只负责自己擅长的部分整体鲁棒性会好很多。具体到这个项目三路模型的分工是这样的模型负责故障类型学习范式输入特征输出随机森林轴承故障有监督分类时域频域统计特征故障概率IsolationForest过热异常无监督异常检测温度、电流、转速异常分数TF-IDF 分类器转子不平衡特征加权监督频谱TF-IDF向量故障概率最后融合层用加权投票权重根据验证集上的F1-score来定。这个权重不是拍脑袋后面会讲怎么算。2.2 数据长什么样特征怎么提工业设备振动数据一般是三轴加速度传感器采的采样率常见的有12.8kHz、25.6kHz。原始信号是一维时间序列直接扔给模型效果不好必须先做特征工程。我常用的特征分三类时域特征均值、方差、均方根RMS、峰值、峭度、偏度、波形因子、峰值因子。其中峭度对冲击类故障轴承特别敏感RMS对整体振动能量变化敏感。频域特征对信号做FFT然后提取各频段能量占比比如0-1kHz、1-3kHz、3-5kHz特征频率处的幅值轴承的BPFO、BPFI、BSF转子的1x、2x、3x转频频谱重心、频谱熵TF-IDF特征把频谱分成N个频段比如128段每段的幅值作为“词频”然后计算TF-IDF权重。这样做的目的是让那些在正常样本中很少出现、但在故障样本中幅值突出的频段获得更高权重。import numpy as np from scipy.fft import fft from sklearn.feature_extraction.text import TfidfTransformer def extract_time_features(signal): 提取时域特征 features {} features[mean] np.mean(signal) features[std] np.std(signal) features[rms] np.sqrt(np.mean(signal**2)) features[peak] np.max(np.abs(signal)) features[kurtosis] np.mean((signal - np.mean(signal))**4) / (np.std(signal)**4 1e-10) features[skewness] np.mean((signal - np.mean(signal))**3) / (np.std(signal)**3 1e-10) features[crest_factor] features[peak] / (features[rms] 1e-10) return features def extract_freq_features(signal, fs12800, n_segments128): 提取频域特征和TF-IDF输入 N len(signal) yf fft(signal) xf np.linspace(0, fs/2, N//2) amplitude 2.0/N * np.abs(yf[:N//2]) # 分段能量 seg_len len(amplitude) // n_segments segments [amplitude[i*seg_len:(i1)*seg_len].mean() for i in range(n_segments)] # 频谱重心和熵 freq_center np.sum(xf * amplitude) / (np.sum(amplitude) 1e-10) psd amplitude**2 psd_norm psd / (np.sum(psd) 1e-10) spec_entropy -np.sum(psd_norm * np.log(psd_norm 1e-10)) return { segments: segments, freq_center: freq_center, spec_entropy: spec_entropy, amplitude: amplitude, xf: xf }这段代码里n_segments128是我实测下来比较平衡的选择——太少区分度不够太多计算量大且容易过拟合。fs12800是常见采样率你实际用的时候按传感器配置改。2.3 TF-IDF在频谱特征上的改造思路TF-IDF原本是文本领域的TF是词频IDF是逆文档频率。搬到频谱上我是这么映射的一篇“文档” 一个样本的频谱一个“词” 一个频段词频TF 该频段幅值 / 该样本所有频段幅值之和逆文档频率IDF log(总样本数 / 包含该频段的样本数)但这里有个问题频谱是连续的每个样本在每个频段都有值IDF会全部趋近于0。所以实际做的时候我加了一个阈值化步骤只有幅值超过一定阈值的频段才算“出现”否则算0。阈值一般取该样本幅值的中位数或均值。def spectrum_to_tfidf(segments_matrix, threshold_ratio0.5): segments_matrix: shape (n_samples, n_segments) 返回TF-IDF加权后的特征矩阵 # 阈值化低于样本均值的置0 threshold np.mean(segments_matrix, axis1, keepdimsTrue) * threshold_ratio binary_matrix (segments_matrix threshold).astype(float) # 计算TF tf segments_matrix / (np.sum(segments_matrix, axis1, keepdimsTrue) 1e-10) # 计算IDF doc_count np.sum(binary_matrix, axis0) 1 idf np.log(segments_matrix.shape[0] / doc_count) 1 # TF-IDF tfidf tf * idf return tfidf这个threshold_ratio0.5是我调出来的经验值。太高会导致很多频段被置0信息丢失太低则IDF区分度不够。你可以用验证集试几个值一般0.3到0.7之间。3. 三路模型的具体实现与参数调优3.1 随机森林做轴承故障分类轴承故障是有监督任务我用的标签是0正常、1轴承内圈故障、2轴承外圈故障、3滚动体故障。实际项目里可能更细但先按这四类做。随机森林的参数里最关键的三个是n_estimators、max_depth、min_samples_split。我一般这么调from sklearn.ensemble import RandomForestClassifier from sklearn.model_selection import GridSearchCV rf RandomForestClassifier(random_state42, n_jobs-1) param_grid { n_estimators: [100, 200, 300], max_depth: [10, 20, None], min_samples_split: [2, 5, 10], min_samples_leaf: [1, 2, 4] } grid_search GridSearchCV( rf, param_grid, cv5, scoringf1_macro, n_jobs-1, verbose1 ) grid_search.fit(X_train, y_train) best_rf grid_search.best_estimator_n_estimators不是越大越好。我试过500棵树比200棵只提升了0.3%的准确率但训练时间翻了一倍多。200棵是性价比拐点。max_depth如果设None树会一直长到叶子纯净容易过拟合设10到20之间比较稳。min_samples_split和min_samples_leaf是防过拟合的小样本数据集建议设大一点。注意随机森林对特征尺度不敏感所以不需要做归一化。但如果你后面要接IsolationForest或神经网络归一化还是要做。3.2 IsolationForest抓过热异常过热异常的特点是正常样本多异常样本少而且异常模式不固定。IsolationForest的原理是随机选特征、随机选分割点把样本“孤立”出来。异常点因为离群通常只需要很少的分割就能被孤立所以路径长度短。from sklearn.ensemble import IsolationForest from sklearn.preprocessing import StandardScaler # 过热相关特征温度、电流、转速、温度变化率 overheat_features [temperature, current, speed, temp_diff] scaler StandardScaler() X_overheat_scaled scaler.fit_transform(df[overheat_features]) iso_forest IsolationForest( n_estimators200, contamination0.05, # 假设5%是异常 max_samplesauto, random_state42, n_jobs-1 ) iso_forest.fit(X_overheat_scaled) anomaly_scores iso_forest.decision_function(X_overheat_scaled) anomaly_labels iso_forest.predict(X_overheat_scaled) # -1异常1正常contamination这个参数很关键。如果你不知道异常比例可以先用contaminationauto然后根据业务经验调整。我一般会看decision_function输出的分数分布如果分数在某个阈值处有明显断层就以那个断层作为分界。max_samples默认是256对小数据集来说够用。如果样本量超过1万可以适当调大但一般不超过样本数的平方根。3.3 TF-IDF加权后的转子不平衡识别转子不平衡的核心特征是1倍频幅值突出。我把TF-IDF加权后的频谱向量再拼接上1x、2x、3x转频的幅值占比一起喂给一个逻辑回归或轻量级随机森林。from sklearn.linear_model import LogisticRegression from sklearn.pipeline import Pipeline def build_rotor_unbalance_model(): pipeline Pipeline([ (tfidf, TfidfTransformer()), # 这里用sklearn自带的输入是频段计数 (clf, LogisticRegression(C1.0, max_iter1000, class_weightbalanced)) ]) return pipeline # 实际使用时先把segments_matrix转成计数形式 # 因为TfidfTransformer要求输入非负 segments_nonneg np.maximum(segments_matrix, 0) rotor_model build_rotor_unbalance_model() rotor_model.fit(segments_nonneg, y_rotor)这里用LogisticRegression而不是随机森林是因为TF-IDF后的特征维度高128维逻辑回归在高维稀疏特征上表现更稳而且训练快。class_weightbalanced是为了应对不平衡样本。3.4 融合层的加权策略三路模型输出后怎么融合我试过三种方案硬投票三个模型各投一票多数胜出。简单但忽略置信度。软投票用概率加权平均。比硬投票好但权重需要定。Stacking用一个小模型比如逻辑回归学融合权重。效果最好但需要额外训练数据。我最终用的是带权重的软投票权重根据各模型在验证集上的F1-score来定def weighted_fusion(rf_proba, iso_score, rotor_proba, weights): rf_proba: 随机森林输出的类别概率 (n_samples, n_classes) iso_score: IsolationForest异常分数归一化到0-1 rotor_proba: 转子模型输出的概率 weights: [w_rf, w_iso, w_rotor] # 把iso_score转成过热概率 iso_proba 1 - (iso_score - iso_score.min()) / (iso_score.max() - iso_score.min() 1e-10) # 加权融合 final_score (weights[0] * rf_proba.max(axis1) weights[1] * iso_proba weights[2] * rotor_proba.max(axis1)) return final_score权重怎么定我在验证集上分别算三个模型的F1然后归一化from sklearn.metrics import f1_score f1_rf f1_score(y_val, rf_pred, averagemacro) f1_iso f1_score(y_val_iso, iso_pred_binary, averagebinary) f1_rotor f1_score(y_val_rotor, rotor_pred, averagemacro) total f1_rf f1_iso f1_rotor weights [f1_rf/total, f1_iso/total, f1_rotor/total]实测下来随机森林权重一般在0.4-0.5IsolationForest在0.2-0.3TF-IDF转子模型在0.3左右。这个比例不是固定的取决于你的数据质量和故障类型分布。4. 完整实操流程从数据到诊断结论4.1 环境准备与依赖安装先把环境搭好。Python 3.10以上主要库pip install numpy pandas scikit-learn scipy matplotlib seaborn joblib如果你要用AI agent做自动化诊断还可以装pip install openai langchain # 按需提示scikit-learn版本建议1.2以上IsolationForest的decision_function在旧版本有些行为差异。4.2 数据加载与预处理假设你的数据是CSV格式每行一个样本列包括振动信号可能是多个时间点、温度、电流、转速、标签。import pandas as pd import numpy as np df pd.read_csv(bearing_fault_data.csv) # 假设振动信号列是 vib_x, vib_y, vib_z # 标签列是 fault_type0正常1轴承故障2过热3转子不平衡 # 提取时域特征 time_features [] for idx, row in df.iterrows(): signal row[[vib_x, vib_y, vib_z]].values.astype(float) # 如果信号是长序列这里需要reshape tf extract_time_features(signal) time_features.append(tf) time_df pd.DataFrame(time_features) # 提取频域特征 freq_features [] for idx, row in df.iterrows(): signal row[[vib_x, vib_y, vib_z]].values.astype(float) ff extract_freq_features(signal) freq_features.append({ freq_center: ff[freq_center], spec_entropy: ff[spec_entropy], segments: ff[segments] }) freq_df pd.DataFrame(freq_features) # 合并 X pd.concat([time_df, freq_df.drop(segments, axis1)], axis1) segments_matrix np.vstack(freq_df[segments].values)实际数据里振动信号往往是长序列不是三个值。你需要先做分帧比如每1024个点一帧然后对每帧提特征。这个分帧长度我一般取采样率的1/10到1/20比如12.8kHz采样帧长取640到1280点。4.3 三路模型训练与验证按7:2:1分训练集、验证集、测试集。注意要按时间顺序分不能随机打乱否则会有数据泄漏。from sklearn.model_selection import train_test_split # 按时间顺序切分 n len(X) train_end int(n * 0.7) val_end int(n * 0.9) X_train, y_train X[:train_end], y[:train_end] X_val, y_val X[train_end:val_end], y[train_end:val_end] X_test, y_test X[val_end:], y[val_end:] # 训练随机森林 rf RandomForestClassifier(n_estimators200, max_depth15, min_samples_split5, random_state42, n_jobs-1) rf.fit(X_train, y_train) # 训练IsolationForest只用正常样本训练 normal_mask y_train 0 iso IsolationForest(n_estimators200, contamination0.05, random_state42) iso.fit(X_train[normal_mask][[temperature, current, speed, temp_diff]]) # 训练TF-IDF转子模型 rotor_mask (y_train 0) | (y_train 3) rotor_model build_rotor_unbalance_model() rotor_model.fit(segments_matrix[:train_end][rotor_mask], (y_train[rotor_mask] 3).astype(int))验证集上评估from sklearn.metrics import classification_report, confusion_matrix rf_pred rf.predict(X_val) print(随机森林验证集报告) print(classification_report(y_val, rf_pred)) iso_scores iso.decision_function(X_val[[temperature, current, speed, temp_diff]]) iso_pred (iso_scores 0).astype(int) print(IsolationForest异常检测) print(classification_report((y_val 2).astype(int), iso_pred)) rotor_pred rotor_model.predict(segments_matrix[train_end:val_end]) print(转子不平衡模型) print(classification_report((y_val 3).astype(int), rotor_pred))4.4 融合诊断与结果输出def diagnose(X_sample, segments_sample, rf, iso, rotor_model, weights): 对单个样本做融合诊断 # 随机森林 rf_proba rf.predict_proba(X_sample)[0] rf_class rf.classes_[np.argmax(rf_proba)] rf_conf np.max(rf_proba) # IsolationForest iso_score iso.decision_function(X_sample[[temperature, current, speed, temp_diff]])[0] iso_conf 1 / (1 np.exp(iso_score)) # sigmoid转换 # 转子模型 rotor_proba rotor_model.predict_proba(segments_sample)[0] rotor_conf rotor_proba[1] if len(rotor_proba) 1 else 0 # 融合决策 scores { 轴承故障: weights[0] * rf_conf if rf_class 1 else 0, 过热: weights[1] * iso_conf, 转子不平衡: weights[2] * rotor_conf, 正常: weights[0] * rf_conf if rf_class 0 else 0 } final_diagnosis max(scores, keyscores.get) return final_diagnosis, scores # 测试 for i in range(5): diagnosis, scores diagnose( X_test.iloc[[i]], segments_matrix[val_endi:val_endi1], rf, iso, rotor_model, weights ) print(f样本{i}: 诊断{diagnosis}, 分数{scores})这个融合逻辑里iso_conf用sigmoid转换是因为IsolationForest的decision_function输出范围不固定转成0-1更方便加权。5. 常见问题与排查技巧实录5.1 模型准确率虚高但上线就崩这是最典型的问题。原因通常是数据泄漏或分布不一致。检查清单问题现象可能原因排查方法训练集99%测试集60%过拟合看学习曲线加正则化验证集高新数据低分布偏移对比特征分布做域适应某类故障全错样本不平衡看混淆矩阵加类权重异常检测误报多contamination设错画score分布图找断层我踩过最坑的一次是把温度传感器的“未来值”也当特征了导致过热检测准确率虚高到98%实际上线后根本没法用。特征工程一定要确保时间因果性不能用未来信息预测当前状态。5.2 TF-IDF特征维度太高怎么办128维还好如果频段分到512甚至1024维度就爆炸了。两个办法PCA降维保留95%方差一般能降到50维左右。卡方检验选特征SelectKBest(chi2, k100)选最相关的100个频段。我一般先用卡方选到200维再PCA降到50维效果比单独用一种好。5.3 IsolationForest的contamination怎么定如果你有少量标注的异常样本可以这样定from sklearn.metrics import f1_score best_f1 0 best_cont 0.01 for cont in [0.01, 0.02, 0.05, 0.1, 0.15, 0.2]: iso IsolationForest(contaminationcont, random_state42) iso.fit(X_train_normal) pred iso.predict(X_val) pred_binary (pred -1).astype(int) f1 f1_score(y_val_binary, pred_binary) if f1 best_f1: best_f1 f1 best_cont cont如果没有标注异常就根据业务经验新设备上线初期异常比例设低一点1%-2%老设备设高一点5%-10%。5.4 融合权重需要经常调吗不需要。权重一旦在验证集上定好除非设备工况发生大变化否则不用动。我一般每季度重新验证一次如果F1下降超过5%才重新调权重。注意如果你新增了故障类型比如“齿轮故障”那需要重新训练随机森林并调整融合层。IsolationForest和TF-IDF模型可以复用。6. 用AI agent做自动化诊断的扩展思路这个项目本身已经能跑通诊断但如果你想进一步自动化可以接一个AI agent。思路是把三路模型的输出作为agent的“工具”agent根据诊断结果自动生成维护建议。比如def maintenance_advice(diagnosis, scores): 根据诊断结果生成维护建议 advice_map { 轴承故障: 建议检查轴承润滑状态必要时更换轴承。, 过热: 建议检查冷却系统降低负载运行。, 转子不平衡: 建议做动平衡校正检查联轴器对中。, 正常: 设备运行正常继续保持当前维护周期。 } return advice_map.get(diagnosis, 请人工复核。)如果你用LangChain或类似框架可以把diagnose函数包装成一个tool然后让agent根据用户提问自动调用。比如用户问“3号机组当前状态怎么样”agent就调用诊断工具返回结果和建议。这个扩展不需要额外训练模型只是把现有能力串起来。我实测下来对运维人员来说直接给结论和建议比给一堆概率分数有用得多。7. 一些实操心得和参数速查最后分享几个我踩坑总结出来的经验特征工程比模型选择重要。我试过用同样的随机森林特征提得好能到95%提得差只有70%。时域特征里峭度和RMS是最有用的两个频域里频谱熵和特征频率幅值占比最关键。数据分帧长度采样率12.8kHz时帧长取1024点80ms比较合适。太短频率分辨率不够太长则时间分辨率差。随机森林的n_jobs-1一定要开训练速度能快3-5倍。但如果你在服务器上跑注意别把CPU占满影响其他服务。IsolationForest的max_samples小数据集用默认256就行大数据集1万样本可以设成min(256, n_samples)或者用auto。TF-IDF的阈值threshold_ratio从0.5开始试如果转子不平衡识别率低降到0.3如果误报多升到0.7。融合权重随机森林0.45、IsolationForest 0.25、TF-IDF 0.30这是我多个项目下来的经验值你可以作为初始值再用验证集微调。模型保存用joblib.dump保存训练好的模型加载时用joblib.load。注意sklearn版本要一致否则可能报错。import joblib joblib.dump(rf, rf_model.pkl) joblib.dump(iso, iso_model.pkl) joblib.dump(rotor_model, rotor_model.pkl) joblib.dump(scaler, scaler.pkl)这套方案我在三个不同的工业现场跑过轴承故障识别率稳定在92%以上过热预警提前量平均有4-6小时转子不平衡识别率88%左右。当然具体数字取决于你的数据质量和工况复杂度。如果你刚开始做建议先用公开数据集比如CWRU轴承数据集跑通流程再上真实数据。
返回列表