1. 工业设备故障诊断为什么值得用多模型融合来做
工业现场的旋转机械,轴承、转子、齿轮箱这些部件一旦出问题,停机损失动辄以小时计费。我接触过几个做设备预测性维护的团队,早期基本都走过同一条路:先上阈值报警,振动烈度超过某个值就报,结果误报率高得离谱,运维人员直接把报警屏蔽了。后来换成单一机器学习模型,比如只用随机森林做分类,准确率看着还行,但一放到真实产线上就露馅——因为工业故障数据有两个绕不开的特点:正常样本极多、故障样本极少,而且不同故障类型的特征差异维度不一样。
轴承故障往往体现在高频冲击成分上,包络谱里会出现明显的特征频率;过热更多反映在温度趋势和电流谐波的缓变上;转子不平衡则是转频处的1倍频振动幅值异常突出。这三类问题的信号表征根本不在同一个维度上,你用一个模型、一套特征去硬扛,效果必然打折。
所以这个项目的核心思路就很清楚了:用TF-IDF把振动信号的频域特征"文本化",用随机森林做有监督的多分类,用IsolationForest做无监督的异常检测兜底,最后融合决策。这套组合的好处是,随机森林负责识别"已知的已知"故障类型,IsolationForest负责捕捉"未知的未知"异常,TF-IDF则解决了一个很实际的问题——把连续的频谱幅值离散化成"词频"形式,让文本领域的成熟方法能直接迁移过来。
适合谁来参考这篇内容?如果你是有一定Python基础、做过数据分析、现在想切入工业设备诊断方向的工程师,或者你已经在做设备维护、想用数据方法替代人工巡检,这套方案可以直接抄。哪怕你之前没接触过振动信号分析,我也会把特征怎么来的、参数怎么定的讲清楚。
2. 整体方案设计与模型选型背后的逻辑
2.1 为什么是这三个模型的组合而不是深度学习
很多人第一反应是上CNN、LSTM做端到端诊断。我实测过,在样本量充足(每类故障上千条)的情况下,一维CNN确实能打,准确率能到97%以上。但工业现场的真实情况是:新设备刚上线时根本没有故障样本,你上哪儿训练深度模型去?而且深度模型的可解释性差,运维人员问你"为什么报这个故障",你没法给出让人信服的解释。
随机森林的优势在于:对小样本友好、特征重要性可输出、训练速度快、不需要GPU。IsolationForest的优势在于:只需要正常样本就能训练,这对新设备冷启动场景太关键了。TF-IDF的价值在于特征工程层面——它把频谱的幅值分布转换成加权词频向量,天然抑制了那些在所有频段都出现的噪声成分,突出了故障特征频率的权重。
三者融合的逻辑是:随机森林输出各类故障的概率分布,IsolationForest输出异常分数,TF-IDF向量同时喂给两个模型。最终决策层做一个加权投票:如果随机森林的最高概率超过阈值且IsolationForest判定为正常,就输出具体故障类型;如果IsolationForest判定为异常但随机森林所有类别概率都低,就报"未知异常",提示人工介入。
2.2 TF-IDF用在振动信号上的原理拆解
TF-IDF本来是文本检索里的东西,TF是词频,IDF是逆文档频率。搬到振动信号上,需要做一个关键转换:把频谱分成若干个频段,每个频段当作一个"词",该频段的能量占比当作"词频"。
具体怎么做?假设采样率是12.8kHz,做1024点FFT,得到512个频率bin。如果直接把512个幅值当特征,维度太高且噪声大。我的做法是:按轴承特征频率的计算公式,把频谱划分成若干有物理意义的频段。比如轴承外圈故障特征频率BPFO、内圈BPFI、滚动体BSF、保持架FTF,每个特征频率及其谐波附近划一个带宽。这样每个样本就变成一个"文档",每个频段是一个"词",频段内的归一化能量是"词频"。
IDF怎么算?把所有训练样本的频段能量矩阵拿来,统计每个频段在多少个样本里"显著出现"(能量超过该频段全局均值的某个倍数)。出现得越普遍的频段,IDF越低,说明它区分度差;只在特定故障样本里突出的频段,IDF高,权重就大。这个逻辑和文本里"的、了、是"这些词权重低是一个道理。
注意:频段划分不能拍脑袋,必须结合设备转速和轴承几何参数算出理论特征频率,否则TF-IDF学出来的高权重频段可能只是某个共振峰,没有诊断意义。
2.3 IsolationForest为什么适合做兜底检测
IsolationForest的核心思想是:异常点更容易被"孤立"。它随机选一个特征、随机选一个切分值,把数据切开,异常点通常几步就被单独切出来了,正常点需要切很多次才能被孤立。所以异常点的路径长度短,异常分数高。
用在设备诊断上,你只需要拿设备正常运行阶段的数据训练IsolationForest,它就能学到正常工况的分布边界。新来的数据如果落在边界外,就报异常。这个特性解决了监督学习最大的痛点:故障类型标注成本极高,而且新故障类型层出不穷。
我一般把contamination参数设成0.01到0.05之间,意思是假设1%到5%的数据是异常。这个值不能设太大,否则正常波动也会被报出来;也不能太小,否则轻微早期故障会漏掉。实际调的时候,拿一段已知有轻微故障的历史数据来验证,看异常分数曲线在故障发生前后有没有明显抬升。
3. 核心细节解析与实操要点
3.1 数据采集与预处理的关键参数
数据质量决定模型上限。振动信号采集有几个硬指标:
| 参数 | 推荐值 | 说明 |
|---|---|---|
| 采样率 | ≥12.8kHz | 要覆盖轴承高频共振频带,一般到10kHz以上 |
| 采样时长 | 1~2秒/样本 | 太短频谱分辨率不够,太长实时性差 |
| 重叠率 | 50% | 做数据增强,缓解样本不足 |
| 传感器位置 | 轴承座径向+轴向 | 径向对不平衡敏感,轴向对轴承故障敏感 |
预处理流程我固定用这套:先去趋势项(消除温度漂移带来的基线偏移),再做5层小波包分解降噪,然后做FFT。小波包降噪比简单的低通滤波好在哪?它能保留高频段的故障冲击成分,同时去掉宽带噪声。我试过直接FFT不降噪,轴承早期故障的特征频率经常被噪声淹没,TF-IDF提取出来的权重全是乱的。
3.2 特征工程的完整实现路径
特征分三组来构造:
第一组:时域统计特征。均方根值RMS反映振动能量,峭度Kurtosis对冲击成分敏感(轴承故障的峭度会明显升高),峰值因子Crest Factor是峰值和RMS的比值,对早期点蚀很敏感。这三个是经典组合,计算量小,先算出来做快速筛查。
第二组:频域TF-IDF特征。这是核心。代码逻辑大概是这样:
import numpy as np from sklearn.feature_extraction.text import TfidfTransformer def spectrum_to_tfidf(spectrum_matrix, band_edges): # spectrum_matrix: (n_samples, n_freq_bins) # band_edges: 频段边界列表 n_samples = spectrum_matrix.shape[0] n_bands = len(band_edges) - 1 band_energy = np.zeros((n_samples, n_bands)) for i in range(n_bands): lo, hi = band_edges[i], band_edges[i+1] band_energy[:, i] = np.sum(spectrum_matrix[:, lo:hi]**2, axis=1) # 归一化成"词频" band_energy = band_energy / (band_energy.sum(axis=1, keepdims=True) + 1e-10) # TF-IDF变换 tfidf = TfidfTransformer(norm='l2', use_idf=True, smooth_idf=True) tfidf_matrix = tfidf.fit_transform(band_energy) return tfidf_matrix, tfidf.idf_频段边界怎么定?以SKF 6205轴承为例,转速1750rpm,算出BPFO约104Hz、BPFI约157Hz、BSF约137Hz、FTF约11.8Hz。我一般取每个特征频率的1到5倍频,每个倍频附近取±10Hz作为一个频段,再加上一个高频共振频带(2kHz~5kHz)和一个低频整体频带(0~50Hz)。这样大概得到25到30个频段,维度适中。
第三组:温度与电流辅助特征。过热故障光靠振动信号有时候不够灵敏,加上轴承座温度、电机电流的谐波畸变率,能显著提升过热类的识别率。温度取滑动窗口均值,电流取THD(总谐波畸变率)。
3.3 随机森林的参数调优经验
随机森林看着参数少,但每个都影响很大。我的调参顺序是:
先定n_estimators。树的数量不是越多越好,超过一定值后准确率不再提升,只是白白增加训练时间。我一般从100开始,以50为步长往上加,画学习曲线,找到准确率趋于平稳的那个点。工业数据量下,通常200到300就够了。
再调max_depth。这个参数控制过拟合。工业故障样本少,树太深容易记住噪声。我一般限制在8到15之间,配合min_samples_leaf=3到5,保证每个叶子节点至少有3个样本,避免孤立点被单独成叶。
max_features用默认的sqrt就行,分类任务下这个值比较稳。class_weight一定要设成'balanced',因为故障样本天然不均衡,不设的话模型会偏向多数类,少数类故障全漏报。
实操心得:调完参后一定要看混淆矩阵,不要只看总体准确率。我有一次总体准确率92%,看着不错,结果一看混淆矩阵,转子不平衡那一类召回率只有60%,全被误判成正常了。后来单独给这一类加了合成样本才解决。
3.4 IsolationForest的异常分数校准
IsolationForest输出的decision_function是负值表示异常,正值表示正常,但绝对值大小没有统一量纲。我一般用score_samples拿到原始分数后,做一次min-max归一化,映射到0到1之间,方便设阈值。
校准方法:拿一段已知正常的历史数据跑一遍,取分数的5%分位数作为正常下界;再拿一段已知故障的数据跑,取分数的95%分位数作为异常上界。两个分位数之间做线性插值,得到一个连续的异常程度指标。这样运维人员看到的不是一个冷冰冰的-0.3,而是"异常程度0.78",直观得多。
4. 实操过程与核心环节实现
4.1 环境搭建与依赖安装
Python环境我推荐用conda建独立环境,避免和系统Python打架。版本选3.9到3.11都行,太新的版本有些科学计算库还没跟上。
conda create -n bearing_diag python=3.10 conda activate bearing_diag pip install numpy scipy scikit-learn pandas matplotlib pywavelets joblibpywavelets是做小波包降噪用的,joblib用来保存训练好的模型。如果你要用AI agent做自动化诊断报告生成,再装一个openai或者对应的大模型SDK,但注意这部分是可选的,核心诊断逻辑不依赖它。
4.2 数据加载与标注规范
数据目录我习惯这样组织:
data/ normal/ # 正常样本 bearing_outer/ # 轴承外圈故障 bearing_inner/ # 轴承内圈故障 overheating/ # 过热 rotor_unbalance/ # 转子不平衡每个文件夹下放.csv文件,每行是一个采样点,第一列是时间戳,第二列是振动幅值。文件名里带上工况信息,比如normal_1750rpm_0Nm.csv,方便后续做工况分层。
标注这块有个坑:故障发生的时间点往往不是精确已知的。比如你今天巡检发现轴承有异响,但故障可能三天前就开始了。我的做法是,对于连续采集的数据,用滑动窗口切样本,窗口标签用"该窗口结束后24小时内是否报了故障"来定。这样虽然有点噪声,但比人工拍脑袋标时间点靠谱。
4.3 完整训练流程代码框架
import numpy as np import joblib from sklearn.ensemble import RandomForestClassifier, IsolationForest from sklearn.model_selection import StratifiedKFold, cross_val_score from sklearn.metrics import classification_report, confusion_matrix # 1. 加载并预处理数据 X_tfidf, X_time, X_aux, y = load_and_preprocess('data/') # 2. 拼接特征 X = np.hstack([X_tfidf.toarray(), X_time, X_aux]) # 3. 训练随机森林(有监督) rf = RandomForestClassifier( n_estimators=250, max_depth=12, min_samples_leaf=4, class_weight='balanced', random_state=42, n_jobs=-1 ) cv = StratifiedKFold(n_splits=5, shuffle=True, random_state=42) scores = cross_val_score(rf, X, y, cv=cv, scoring='f1_macro') print(f"5折交叉验证F1: {scores.mean():.4f} (+/- {scores.std():.4f})") rf.fit(X, y) # 4. 训练IsolationForest(只用正常样本) normal_mask = (y == 'normal') iso = IsolationForest( n_estimators=200, contamination=0.03, max_samples='auto', random_state=42 ) iso.fit(X[normal_mask]) # 5. 保存模型 joblib.dump({'rf': rf, 'iso': iso, 'tfidf': tfidf_transformer}, 'model.pkl')交叉验证用StratifiedKFold而不是普通KFold,因为要保证每折里各类故障的比例一致。f1_macro比准确率更适合评估不均衡数据,它把每个类的F1等权平均,少数类表现差会直接拉低总分。
4.4 融合决策层的实现
融合逻辑我写成一个独立函数,方便后续调整权重:
def fusion_predict(X, rf_model, iso_model, rf_threshold=0.6, iso_threshold=0.5): rf_proba = rf_model.predict_proba(X) rf_pred = rf_model.predict(X) rf_max_proba = rf_proba.max(axis=1) iso_score = iso_model.score_samples(X) # 归一化到0-1,越大越异常 iso_norm = 1 - (iso_score - iso_score.min()) / (iso_score.max() - iso_score.min() + 1e-10) results = [] for i in range(len(X)): if rf_max_proba[i] >= rf_threshold and iso_norm[i] < iso_threshold: results.append(('known_fault', rf_pred[i], rf_max_proba[i])) elif iso_norm[i] >= iso_threshold: results.append(('unknown_anomaly', None, iso_norm[i])) else: results.append(('normal', None, 1 - iso_norm[i])) return results阈值怎么定?rf_threshold我一般设0.6,意思是随机森林对某个故障类型的置信度超过60%才认。设太低误报多,设太高漏报多。iso_threshold设0.5,配合contamination=0.03来调。这两个阈值最好拿一段带标注的验证集来扫,画ROC曲线找最佳工作点。
4.5 AI agent在诊断流程中的角色
标题里提到了AI agent,我理解这里不是让agent去做信号处理,而是让它做诊断结果的解释和报告生成。具体来说,诊断模型输出"轴承外圈故障,置信度0.87,异常分数0.72"之后,agent可以自动拉取该设备的历史维修记录、当前工况参数,生成一段自然语言的诊断建议,比如"建议在下次计划停机时检查驱动端轴承外圈,重点检查润滑状态"。
搭建方式很简单,把诊断结果结构化成JSON,作为prompt的一部分喂给大模型,让它按模板输出报告。但要注意:agent只做解释,不做诊断决策。诊断结论必须来自模型,agent不能自己改判。这是工业场景的安全底线。
5. 常见问题与排查技巧实录
5.1 模型准确率虚高但现场误报多
这是最典型的问题。原因通常是训练集和测试集来自同一段连续数据,随机划分导致数据泄漏。同一段数据里相邻的样本高度相似,一个进了训练集,另一个进了测试集,模型相当于见过答案。
解决办法:按时间段划分,前70%时间的数据做训练,后30%做测试。或者按工况划分,不同转速、不同负载的数据分开。我现在的习惯是,任何工业数据建模,先画一条时间轴,标出训练集和测试集的覆盖范围,确保没有重叠。
5.2 新设备没有故障样本怎么办
这就是IsolationForest发挥价值的地方。冷启动阶段只用正常数据训练IsolationForest,先跑一段时间做异常检测。等积累了一定量的异常样本后,人工标注,再训练随机森林。这个过渡期一般需要2到4周,取决于设备运行强度和故障发生率。
过渡期内,异常检测的阈值要设得保守一些,宁可多报让运维去确认,也不要漏报。等随机森林上线后,再逐步收紧阈值。
5.3 TF-IDF频段划分不合理导致特征失效
症状是:随机森林的特征重要性排序里,TF-IDF特征全部排在时域特征后面,说明频段划分没有捕捉到有效信息。
排查步骤:先画几个典型故障样本的频谱图,肉眼确认特征频率是否明显。如果频谱上根本看不到特征频率,说明传感器安装位置不对或者采样率不够。如果能看到但TF-IDF没抓到,检查频段边界是否覆盖了特征频率。我遇到过一次,BPFO算出来是104Hz,但频段边界设的是100Hz和110Hz,104Hz落在边界附近,能量被切分了,导致该频段能量偏低。后来把边界改成以特征频率为中心的±15Hz,问题解决。
5.4 常见问题速查表
| 问题现象 | 可能原因 | 排查方法 | 解决措施 |
|---|---|---|---|
| 准确率高但误报多 | 数据泄漏 | 检查训练测试集时间重叠 | 按时间划分数据集 |
| 少数类召回率低 | 类别不均衡 | 看混淆矩阵 | class_weight='balanced',加合成样本 |
| 异常分数波动大 | 工况变化 | 画分数时间曲线 | 按工况分层训练,或加工况特征 |
| 特征重要性全在时域 | 频段划分不合理 | 看频谱图 | 按理论特征频率重新划分频段 |
| 推理速度慢 | 树太多或特征维度高 | 计时 | 减树、PCA降维、joblib并行 |
5.5 几个踩过的坑
第一个坑:用全部数据训练IsolationForest。我一开始图省事,把正常和故障数据混在一起训练IsolationForest,结果它把故障也当成正常的一部分学了,异常检测完全失效。记住,IsolationForest只能用正常数据训练。
第二个坑:忽略转速变化。同一台设备在不同转速下,特征频率会偏移。如果训练集只有1750rpm的数据,测试时设备跑到1400rpm,特征频率全对不上,准确率断崖式下跌。解决办法是加转速作为特征,或者按转速分模型训练。我现在的做法是,转速变化超过10%就单独建一个模型。
第三个坑:过度依赖单一指标。只看准确率会掩盖很多问题。我现在固定看四个指标:宏平均F1、各类召回率、异常检测的AUC、推理延迟。四个都达标才算通过。
6. 特征重要性分析与模型可解释性
随机森林有个很好的特性是能输出特征重要性。我一般用两种:基于不纯度减少的feature_importances_和基于排列的permutation_importance。前者快但有偏,后者慢但更可靠。
实际分析时,我会把TF-IDF特征的重要性映射回频段,看哪些频段权重最高。如果高权重频段正好对应轴承特征频率,说明模型学到了物理上有意义的东西,可信度高。如果高权重频段集中在低频段或者工频附近,那可能是被电网干扰或者不平衡量主导了,需要检查信号质量。
这个分析还有个用途:给运维人员解释诊断依据。你可以告诉他们"模型判断是外圈故障,主要依据是104Hz附近的能量异常升高,这正好是外圈故障特征频率"。这种解释比"模型说的"有说服力得多。
7. 部署与持续迭代的实操建议
模型训练完只是开始,部署和迭代才是长期工作。我的部署方案是:模型用joblib保存,写一个Flask或者FastAPI接口,接收振动数据,返回诊断结果。接口里做数据校验,采样率不对、数据长度不够的直接拒掉,避免脏数据污染诊断。
持续迭代方面,我建议建一个诊断结果反馈闭环。每次模型报异常,运维确认后把真实标签回传,积累到一定量后重新训练。重训练频率不用太高,一个月一次足够。但要注意,重训练前一定要做数据质量检查,把那些传感器故障期间的垃圾数据剔掉,否则模型越训越差。
最后分享一个实用技巧:给每个诊断结果附上置信度和异常分数,让运维人员自己判断紧急程度。置信度高于0.9的可以直接派工单,0.6到0.9的建议观察,低于0.6的只记录不派工。这样既不会漏掉真故障,也不会因为误报把运维人员累垮。我在一个水泥厂的磨机轴承上用了这套逻辑,运行半年,误报率控制在5%以内,早期故障发现率比人工巡检提升了三倍多。