☰
工业设备故障诊断:随机森林、IsolationForest与TF-IDF融合实战
2026/10/17 9:34:33 网站建设 项目流程

工业设备故障诊断这件事,我做了快六年,从最早靠老师傅拿听棒贴着轴承箱听异响,到后来用振动频谱仪一条条看边频带,再到现在把特征工程和机器学习模型串成一条流水线。说实话,轴承故障、过热、转子不平衡这三类问题,占了旋转机械非计划停机的七成以上,而且它们的振动特征在频域上经常互相重叠,单靠一个模型很难同时压住漏报和误报。这篇东西就是把我自己搭的一套融合诊断方案完整拆开讲——用Python把随机森林、IsolationForest和TF-IDF三个看似不搭界的东西捏在一起,分别处理有标签的故障分类、无标签的异常检测和文本工单的特征提取,最后输出一个能直接落地的诊断结论。

如果你手头有振动数据、有历史维修工单,但不知道怎么把它们变成可用的模型输入,或者你已经试过单一模型但效果总差一口气,那这篇内容应该能帮你省掉不少试错时间。我会把每个模型为什么选、参数怎么定、特征怎么对齐、融合层怎么设计,全部按实际代码和踩坑经验讲清楚,不绕弯子。

1. 三类故障的振动特征到底长什么样

在动手写任何代码之前,得先把物理对象搞清楚。轴承故障、过热和转子不平衡,这三者在振动信号上的表现有本质区别,如果连这个都分不清,后面特征工程就是瞎猜。

1.1 轴承故障的冲击性与高频共振

轴承局部损伤(比如外圈剥落、内圈点蚀、滚动体裂纹)在运转时会产生周期性的冲击脉冲。这个冲击会激发轴承座和传感器安装位置的高频固有频率,形成所谓的“高频共振带”。在时域上,你会看到一串等间隔的冲击衰减波形;在频域上,高频段(通常2kHz到10kHz)会出现共振峰,而低频段则出现故障特征频率及其谐波。

轴承故障特征频率的计算依赖转速和轴承几何参数。外圈故障频率BPFO、内圈故障频率BPFI、滚动体故障频率BSF、保持架故障频率FTF,这四个公式我在下面列一下,实际项目中直接封装成函数调用:

def bearing_fault_frequencies(n, d, D, theta, fr): """ n: 滚动体数量 d: 滚动体直径 (mm) D: 节径 (mm) theta: 接触角 (度) fr: 轴转频 (Hz) """ import math theta_rad = math.radians(theta) bpfo = (n / 2) * fr * (1 - (d / D) * math.cos(theta_rad)) bpfi = (n / 2) * fr * (1 + (d / D) * math.cos(theta_rad)) bsf = (D / (2 * d)) * fr * (1 - ((d / D) * math.cos(theta_rad)) ** 2) ftf = (fr / 2) * (1 - (d / D) * math.cos(theta_rad)) return bpfo, bpfi, bsf, ftf

实测中我发现,轴承早期故障的冲击能量很弱,直接做FFT经常被背景噪声淹没。这时候包络解调(Hilbert变换取包络再做谱分析)比直接频谱有效得多。包络谱上出现的BPFO或BPFI峰值,才是判断轴承故障的硬证据。

1.2 过热故障的宽频抬升与趋势性

过热和轴承故障不一样,它往往不是某个特定频率的突增,而是整个高频段的能量缓慢抬升,同时伴随润滑油粘度下降导致的阻尼变化。在时域上,过热表现为振动有效值(RMS)的持续爬升,但峰值因子(Crest Factor)反而可能下降,因为冲击成分被高温软化后的材料吸收了。

我处理过热数据时,最看重的三个指标是:高频段(5kHz以上)能量占比、RMS的滑动趋势斜率、以及温度传感器与振动RMS的相关系数。如果温度上升的同时高频能量占比也在涨,基本可以锁定过热。这里有个坑:环境温度变化会干扰判断,所以必须做温度归一化,或者直接用温升速率而不是绝对温度。

1.3 转子不平衡的1倍频主导

转子不平衡的特征非常明确:1倍转频(1X)的振幅显著高于其他频率成分,而且径向振动通常大于轴向振动。如果1X幅值占总振动能量的60%以上,同时2X、3X幅值很低,那基本就是不平衡。但要注意区分“不平衡”和“转子弯曲”或“联轴器不对中”,后两者会在2X甚至3X上也有明显峰值。

实际项目中,我遇到过电机转子不平衡和轴承外圈故障同时存在的情况。这时候1X和BPFO会同时出现在频谱上,如果只用一个二分类模型,很容易把复合故障误判成单一故障。这也是我后来坚持做多模型融合的直接原因——不同模型对不同类型的特征敏感度不一样,融合之后容错率会高很多。

2. 为什么选随机森林、IsolationForest和TF-IDF做融合

很多人一上来就想用深度学习,LSTM、CNN往振动信号上怼。我不否认深度学习在特定场景下效果好,但工业现场的数据量往往不够,而且标注成本极高。一个轴承从正常到失效可能跑几个月,但真正有价值的故障样本可能就几十条。这种数据条件下,树模型和统计方法反而更稳。

2.1 随机森林负责有标签的故障分类

随机森林在我的方案里承担的是“监督分类器”的角色。输入是手工提取的时域和频域特征,输出是故障类别(正常、轴承故障、过热、转子不平衡)。选它的理由很直接:对缺失值不敏感、不需要特征归一化、能输出特征重要性、在小样本上不容易过拟合。

我用的特征集大概有四十多维,包括时域的均值、方差、峭度、峰值因子、裕度因子,频域的1X幅值、2X幅值、BPFO/BPFI幅值、高频能量占比,以及包络谱的峰值统计。随机森林的n_estimators我一般设200到500,max_depth控制在8到12之间,再深就会记住训练集的噪声。

from sklearn.ensemble import RandomForestClassifier rf = RandomForestClassifier( n_estimators=300, max_depth=10, min_samples_leaf=3, max_features='sqrt', class_weight='balanced', random_state=42, n_jobs=-1 )

class_weight='balanced'这个参数很关键。工业数据里正常样本远多于故障样本,不加权的话模型会倾向于把所有东西都判成正常。我试过不加权,召回率直接掉到0.6以下,加上之后能拉到0.85以上。

2.2 IsolationForest负责无标签的异常检测

IsolationForest的定位是“兜底”。现场经常出现一种情况:设备出现了某种从未记录过的异常,但随机森林因为没见过这类样本,会把它硬分到某个已知类别里,导致误判。IsolationForest不需要标签,它通过随机切分特征空间来孤立异常点,对未知类型的异常更敏感。

我通常把IsolationForest的contamination参数设在0.05到0.1之间,意思是假设5%到10%的数据是异常的。这个值不能拍脑袋定,得看历史数据里异常比例大概是多少。如果设得太高,正常波动会被误报;设得太低,真正的早期故障又抓不住。

from sklearn.ensemble import IsolationForest iso = IsolationForest( n_estimators=200, contamination=0.08, max_samples='auto', random_state=42, n_jobs=-1 )

实际部署时,我会把IsolationForest的输出作为一个“异常分数”而不是硬标签。分数越低表示越异常,然后把这个分数和随机森林的预测概率一起送进融合层。

2.3 TF-IDF处理维修工单和报警文本

TF-IDF在这个方案里容易被忽略,但它其实很重要。工业现场有大量的文本信息:维修工单、报警日志、操作员备注。这些文本里藏着很多振动数据反映不出来的信息,比如“上次加脂后异响消失”、“环境温度过高导致跳机”之类的上下文。

我用TF-IDF把工单文本向量化,然后训练一个简单的逻辑回归或者SVM做文本分类,输出“文本侧故障概率”。这个概率和振动侧的概率做加权融合,能显著提升复合故障的识别率。比如振动模型判断是轴承故障,但工单文本里频繁出现“温度报警”,融合层就会提高过热类别的权重。

from sklearn.feature_extraction.text import TfidfVectorizer from sklearn.linear_model import LogisticRegression tfidf = TfidfVectorizer( max_features=5000, ngram_range=(1, 2), min_df=2, max_df=0.9 ) text_clf = LogisticRegression( C=1.0, class_weight='balanced', max_iter=1000 )

ngram_range=(1, 2)是为了捕捉“轴承 异响”这样的二元词组,单用一元词会丢失很多语义信息。min_df=2过滤掉只出现一次的词,避免噪声特征。

3. 从原始振动信号到模型输入的特征流水线

特征工程是整个方案里最耗时间也最决定上限的部分。模型选得再好,特征不对齐也是白搭。我这条流水线跑了三年多,迭代了十几个版本,下面讲的是目前最稳定的一版。

3.1 时域特征的提取与窗口选择

时域特征我固定提取12个:均值、标准差、RMS、峰值、峰峰值、峭度、偏度、峰值因子、裕度因子、波形因子、脉冲因子、方差。这些特征的计算不复杂,但窗口长度的选择很讲究。

窗口太短,统计量不稳定;窗口太长,故障的瞬态特征会被平均掉。我的经验是:对于转速1500rpm左右的设备,窗口长度取0.5秒到1秒比较合适,对应大约12到25个转周期。如果转速更低,窗口要相应加长。

import numpy as np def time_domain_features(signal): abs_signal = np.abs(signal) mean_val = np.mean(signal) std_val = np.std(signal) rms = np.sqrt(np.mean(signal ** 2)) peak = np.max(abs_signal) peak_to_peak = np.max(signal) - np.min(signal) kurtosis = np.mean((signal - mean_val) ** 4) / (std_val ** 4 + 1e-12) skewness = np.mean((signal - mean_val) ** 3) / (std_val ** 3 + 1e-12) crest_factor = peak / (rms + 1e-12) clearance_factor = peak / (np.mean(np.sqrt(abs_signal)) ** 2 + 1e-12) shape_factor = rms / (np.mean(abs_signal) + 1e-12) impulse_factor = peak / (np.mean(abs_signal) + 1e-12) return { 'mean': mean_val, 'std': std_val, 'rms': rms, 'peak': peak, 'peak_to_peak': peak_to_peak, 'kurtosis': kurtosis, 'skewness': skewness, 'crest_factor': crest_factor, 'clearance_factor': clearance_factor, 'shape_factor': shape_factor, 'impulse_factor': impulse_factor, 'variance': std_val ** 2 }

峭度这个特征我要单独说一句。正常轴承的振动信号接近高斯分布,峭度在3左右;一旦出现冲击性故障,峭度会迅速升高到5以上。但峭度对早期故障的敏感度有限,通常要等到故障发展到中期才会明显抬升。所以我会同时看包络谱的峰值,两者互补。

3.2 频域与包络谱特征的构造

频域特征我分三块:直接FFT的幅值谱、Hilbert包络谱、以及高频段能量占比。直接FFT用来抓1X、2X、3X这些转频成分,判断不平衡和对中问题;包络谱用来抓BPFO、BPFI这些轴承故障特征频率;高频能量占比用来判断过热和整体劣化趋势。

from scipy.fft import fft, fftfreq from scipy.signal import hilbert def frequency_domain_features(signal, fs, fr): n = len(signal) yf = fft(signal) xf = fftfreq(n, 1 / fs)[:n // 2] amplitude = 2.0 / n * np.abs(yf[:n // 2]) def amp_at_freq(target_freq, tolerance=2.0): idx = np.argmin(np.abs(xf - target_freq)) mask = np.abs(xf - target_freq) <= tolerance return np.max(amplitude[mask]) if np.any(mask) else 0.0 feat_1x = amp_at_freq(fr) feat_2x = amp_at_freq(2 * fr) feat_3x = amp_at_freq(3 * fr) envelope = np.abs(hilbert(signal)) env_yf = fft(envelope - np.mean(envelope)) env_amp = 2.0 / n * np.abs(env_yf[:n // 2]) high_freq_mask = xf > 2000 high_freq_ratio = np.sum(amplitude[high_freq_mask] ** 2) / (np.sum(amplitude ** 2) + 1e-12) return { '1x_amp': feat_1x, '2x_amp': feat_2x, '3x_amp': feat_3x, '1x_ratio': feat_1x / (np.sum(amplitude) + 1e-12), 'high_freq_ratio': high_freq_ratio, 'env_peak': np.max(env_amp), 'env_mean': np.mean(env_amp) }

包络谱的峰值位置要和轴承故障特征频率比对。我一般会计算BPFO、BPFI、BSF、FTF以及它们的2倍频、3倍频处的包络幅值,作为独立特征送进模型。这样随机森林就能学到“BPFO处包络峰值高”对应轴承外圈故障。

3.3 特征对齐与缺失值处理

多源特征拼在一起的时候,最容易出问题的是时间对齐。振动特征是按窗口算的,文本特征是按工单时间戳算的,温度特征是按传感器采样周期算的。如果不对齐,融合层拿到的就是错位的信息。

我的做法是:以振动窗口的时间戳为基准,向前向后各找最近的一条温度记录和一条工单记录,用时间差作为权重做加权平均。如果时间差超过阈值(比如温度超过5分钟、工单超过24小时),就标记为缺失,用该特征的中位数填充。

注意:缺失值填充一定要在训练集上计算中位数,然后应用到验证集和测试集,否则会造成数据泄漏。我见过有人直接在全体数据上算中位数,交叉验证分数虚高,上线后直接崩掉。

4. 三个模型的融合策略与权重分配

单模型跑通之后,融合层才是真正拉开差距的地方。我试过投票法、 stacking、贝叶斯融合,最后稳定下来的是“概率加权+规则兜底”的方案。

4.1 概率输出的校准与对齐

随机森林输出的predict_proba是投票比例,不是真正的概率,分布往往偏向0和1两端。IsolationForest输出的是异常分数,范围在-1到1之间,需要先做归一化再转成概率。文本分类器的概率相对准一些,但类别体系可能和振动侧不一致。

我的处理方式是:对随机森林做Platt缩放(用逻辑回归拟合分数到概率的映射),对IsolationForest做min-max归一化后取1 - normalized_score作为正常概率,对文本分类器直接使用predict_proba。三者的类别顺序必须严格对齐,我一般用固定的类别列表['normal', 'bearing', 'overheat', 'unbalance'],所有模型输出都按这个顺序排列。

from sklearn.calibration import CalibratedClassifierCV rf_calibrated = CalibratedClassifierCV( rf, method='sigmoid', cv=5 ) rf_calibrated.fit(X_train, y_train)

校准之后,随机森林的概率输出会平滑很多,融合时不会因为某个模型过度自信而带偏整体判断。

4.2 加权融合的权重确定

权重不能拍脑袋定。我用的是验证集上的网格搜索,以宏平均F1为目标,三个权重的搜索范围是0.1到0.8,步长0.05,约束是三个权重之和为1。跑一遍大概几分钟,找到最优组合后固定下来。

实际跑下来,我这边的最优权重通常是:随机森林0.55、IsolationForest 0.25、TF-IDF文本0.20。随机森林权重最高是因为它直接监督训练,对已知故障类型判别最准;IsolationForest权重次之,负责捕捉未知异常;文本权重最低,但关键时刻能提供上下文修正。

模型权重范围典型最优值主要贡献
随机森林0.40-0.700.55已知故障分类
IsolationForest0.15-0.350.25未知异常检测
TF-IDF文本0.10-0.300.20上下文修正

4.3 规则兜底与冲突消解

加权融合之后,还会出现一些明显不合理的结论。比如振动侧三个模型都指向“正常”,但温度特征显示温升速率异常高,这时候纯靠概率融合可能会漏报过热。所以我加了一层规则兜底:

  • 如果温升速率超过阈值且高频能量占比上升,强制提升过热类别概率至少0.3。
  • 如果1X幅值占比超过0.6且2X、3X很低,强制提升不平衡类别概率至少0.3。
  • 如果包络谱在BPFO或BPFI处峰值超过背景3倍,强制提升轴承故障类别概率至少0.3。

这些规则不是拍脑袋写的,是从历史误报案例里总结出来的。每一条规则背后都有至少五个真实案例支撑。

提示:规则兜底的概率提升不要设得太高,0.3左右比较合适。设太高会压制模型本身的判断,设太低又起不到兜底作用。这个值需要在验证集上反复调。

5. 实测中的误报漏报与排查链路

模型上线之后,最怕的不是效果差,而是效果不稳定。我遇到过好几次“训练集F1 0.95,上线一周后掉到0.7”的情况。下面把几个典型的排查过程完整还原,方便你遇到类似问题时对照。

5.1 传感器松动导致的全局误报

有一次,模型连续三天把一台正常运行的风机判成轴承故障。我第一反应是模型漂移了,但检查训练数据分布没发现异常。后来去现场看,发现加速度传感器的磁吸底座松了,导致信号里混入了大量低频晃动成分,峭度和峰值因子都异常升高,模型自然判成故障。

排查链路是这样的:先看特征重要性,发现峭度和峰值因子的贡献突然变大;再回看原始时域波形,发现低频大幅晃动;最后现场确认传感器松动。紧固传感器后,误报消失。

这件事之后,我在特征流水线里加了一个“传感器健康检查”步骤:如果低频段(<10Hz)能量占比超过总能量的30%,就标记传感器可能松动,暂时抑制故障报警。

5.2 工况变化引起的特征漂移

另一台设备,模型在负载稳定时表现很好,但一到变负载工况就频繁误报。原因是变负载时转速波动,1X频率跟着漂移,而我的频域特征是按固定频率点取的幅值,频率一漂就取不准。

解决办法是改用阶次分析(Order Analysis),把频率轴转换成转速的倍数,这样1X永远在阶次1的位置,不受转速波动影响。实现上需要同步采集转速信号,或者从振动信号里提取转速(用瞬时频率估计)。改完之后,变负载工况下的误报率从15%降到了3%以下。

5.3 文本特征引入的噪声

TF-IDF文本分类器上线后,有一段时间把大量正常工单判成故障。查下来发现,维修工单里经常出现“检查轴承”、“轴承正常”这样的描述,TF-IDF把“轴承”这个词的权重拉得很高,导致只要工单里出现“轴承”就被判成轴承故障。

解决方法是引入否定词处理:在文本预处理阶段,检测“正常”、“无异常”、“未见异常”等否定词,如果否定词出现在故障关键词附近,就把该关键词的TF-IDF权重降权。同时扩充停用词表,把“检查”、“查看”、“确认”这类操作动词过滤掉。

def preprocess_text(text): negation_words = ['正常', '无异常', '未见异常', '良好'] fault_keywords = ['轴承', '过热', '不平衡', '异响', '振动大'] for neg in negation_words: for kw in fault_keywords: if neg in text and kw in text: text = text.replace(kw, '') return text

这个处理很粗糙,但实测有效。更精细的做法是用依存句法分析判断否定词的作用范围,但工业场景下工单文本通常很短,粗糙处理已经够用。

6. 把诊断模型封装成可调用的AI Agent

模型跑通之后,下一步是让它能被人用起来。我把它封装成了一个简单的AI Agent,接收自然语言查询,返回诊断结论和依据。这样现场工程师不用懂Python,直接问“三号风机现在什么状态”就能拿到答案。

6.1 Agent的输入输出设计

Agent的输入是设备ID和查询时间范围,输出是结构化的诊断报告,包括:当前状态(正常/轴承故障/过热/不平衡)、置信度、关键证据(哪些特征触发了判断)、以及建议措施。

class DiagnosisAgent: def __init__(self, rf_model, iso_model, text_model, feature_pipeline): self.rf = rf_model self.iso = iso_model self.text_clf = text_model self.pipeline = feature_pipeline def diagnose(self, device_id, time_range): vib_features = self.pipeline.extract_vibration(device_id, time_range) text_features = self.pipeline.extract_text(device_id, time_range) rf_proba = self.rf.predict_proba([vib_features])[0] iso_score = self.iso.score_samples([vib_features])[0] iso_proba = self._normalize_iso(iso_score) text_proba = self.text_clf.predict_proba([text_features])[0] fused = 0.55 * rf_proba + 0.25 * iso_proba + 0.20 * text_proba fused = self._apply_rules(fused, vib_features) return self._format_report(fused, vib_features)

6.2 与PLC和SCADA系统的对接思路

Agent本身是Python服务,要跟现场PLC和SCADA对接,最稳妥的方式是通过OPC UA或者Modbus TCP读取实时数据,通过REST API把诊断结果推送给上层系统。我不建议Agent直接写PLC,诊断系统只读不写,避免误动作。

实际部署时,Agent跑在边缘计算盒子上,每5分钟拉一次最新数据,跑完诊断后把结果写入本地数据库,同时通过MQTT推送到监控大屏。这样即使网络断了,本地诊断也不受影响。

6.3 技能扩展与多设备管理

一个Agent管一台设备太浪费,我把它扩展成多设备管理:每个设备维护独立的特征缓存和模型状态,Agent根据设备ID路由到对应的处理逻辑。新增设备时只需要注册设备信息和对应的模型路径,不用改代码。

技能扩展方面,我预留了插件接口。比如后面想加“齿轮箱故障诊断”技能,只需要实现一个符合接口的诊断类,注册到Agent的技能列表里就行。这样整个系统是可生长的,不会因为加功能而推倒重来。

7. 数据准备与代码结构的实操建议

最后这部分讲点实在的。很多人模型调得很好,但代码结构一团糟,换台机器就跑不起来。我把自己的项目结构分享一下,你可以直接抄。

7.1 目录结构与依赖管理

diagnosis_project/ ├── data/ │ ├── raw/ # 原始振动、温度、工单数据 │ ├── processed/ # 特征工程后的数据 │ └── models/ # 训练好的模型文件 ├── src/ │ ├── features/ # 特征提取模块 │ ├── models/ # 模型训练与融合 │ ├── agent/ # Agent服务 │ └── utils/ # 工具函数 ├── configs/ # 配置文件 ├── notebooks/ # 实验记录 ├── tests/ # 单元测试 └── requirements.txt

依赖管理我用requirements.txt固定版本,关键包包括:scikit-learn、scipy、numpy、pandas、joblib。版本一定要锁死,我吃过亏——scikit-learn从0.24升到1.0之后,IsolationForest的contamination默认行为变了,导致线上模型输出偏移。

7.2 模型持久化与版本控制

模型训练完用joblib保存,文件名带上日期和版本号,比如rf_model_20250115_v3.joblib。每次上线新模型,旧模型不删,保留至少三个版本,方便回滚。

import joblib from datetime import datetime version = datetime.now().strftime('%Y%m%d') joblib.dump(rf_calibrated, f'models/rf_model_{version}_v3.joblib') joblib.dump(iso, f'models/iso_model_{version}_v3.joblib') joblib.dump(tfidf, f'models/tfidf_{version}_v3.joblib')

特征流水线的参数也要一起保存,包括窗口长度、采样率、特征列表、归一化参数。这些参数和模型是绑定的,换一个模型但用错流水线参数,结果会完全不对。

7.3 上线前的验证清单

模型上线前,我固定跑一遍验证清单,缺一项都不发版:

  • 在独立测试集上,宏平均F1不低于0.85,各类别召回率不低于0.75。
  • 在最近一个月的数据上,误报率不超过5%,漏报率不超过3%。
  • 特征分布与训练集对比,PSI(群体稳定性指标)小于0.1。
  • 单次诊断耗时不超过2秒,满足实时性要求。
  • 模型文件和流水线参数版本匹配,能完整复现训练结果。

这套清单帮我拦住了至少三次有问题的上线。有一次测试集F1达标,但最近一个月数据上误报率到了12%,查下来是设备换了新轴承,振动特征分布变了,重新训练后才通过。

7.4 持续迭代的触发条件

模型不是上线就完事了。我设了几个触发重新训练的条件:误报率连续三天超过8%、漏报率连续一周超过5%、或者设备大修后振动特征发生显著变化。触发后自动拉取最近三个月数据重新训练,人工审核后上线。

这套机制跑了一年多,模型效果一直维持在可用水平。工业场景的数据分布变化比互联网慢,但一旦变化就是结构性的,定期重训比频繁调参有效得多。

最后分享一个小技巧:如果你手头没有足够的故障样本,可以用迁移学习。先在公开的轴承数据集(比如凯斯西储大学数据集)上预训练随机森林,然后用自己设备的少量样本做微调。我试过,在只有20条故障样本的情况下,微调后的模型比从头训练的F1高了将近0.15。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询