简介:这是一套面向故障诊断与剩余使用寿命预测(RUL)研究的开源框架源码,采用Python与Jupyter Notebook实现,适合具备一定机器学习基础的高校研究者、工程师及工业预测维护方向的开发者使用。框架聚焦轴承与涡扇发动机两类典型设备,提供从数据预处理、退化特征提取到端到端寿命预测与故障诊断的完整实验示例,便于快速验证算法思路。资源包共125个文件,以115个Python源文件为主体,辅以5个ipynb交互式实验笔记、2个文本说明、1份Word设计文档及开源许可证与配置文件,压缩包约2.56MB,目录组织清晰,便于按模块查阅与二次开发。目前已有355人学习下载。读者可借助其中的轴承退化特征分析、阶段划分、涡扇发动机端到端RUL预测等示例,理解框架整体结构与实验流程,并在此基础上替换数据集或模型,开展自己的预测维护研究。
1. 从一台设备突然宕机说起:RUL-Framework 到底解决什么问题
产线上某台关键设备毫无征兆地停了,事后翻看历史数据,其实振动和温度曲线在两周前就已经开始漂移,只是没人能从几百个测点里把这点异常捞出来。这类场景催生了故障诊断与剩余使用寿命预测(RUL)的需求:不光要知道现在坏没坏,还要知道还能撑多久。基于 Jupyter Notebook 的 RUL-Framework 就是干这件事的 Python 框架,它把数据接入、特征提取、健康指标构建、退化建模、寿命预测串成一条可交互的流水线。适合设备运维工程师、做预测性维护的算法同学,以及想用 Python 快速验证 RUL 思路的开发者。你不需要先搭一套重型平台,一个 Notebook 就能把从原始传感器数据到剩余寿命曲线的链路跑通。
2. RUL-Framework 的骨架:数据流、模型层与 Notebook 交互怎么串起来
2.1 为什么选 Jupyter Notebook 做框架载体
传统 RUL 项目往往把数据处理、模型训练、评估拆成多个脚本,改一个参数要来回跳文件,调试一次等半天。Notebook 的单元格执行模式天然适合做探索性分析:你可以先加载一段传感器数据看一眼波形,再决定用哪种退化特征,接着在同一页面里训练模型并画出预测曲线。RUL-Framework 把这种交互性固化下来,每个功能模块对应一个可独立运行的单元格块,既保留了脚本的可复现性,又降低了试错成本。
从工程角度看,Notebook 还有一个隐性好处:它强制你把中间结果可视化。RUL 预测最怕黑匣子,模型说还剩 30 个周期,你得能看到健康指标是怎么一步步退化过来的。框架里每个阶段都留了绘图钩子,这不是装饰,是排查问题的后悔药。
2.2 框架的四个核心层与数据流转
RUL-Framework 的骨架可以拆成四层,数据从下往上流:
| 层级 | 职责 | 典型输入 | 典型输出 |
|---|---|---|---|
| 数据接入层 | 读取传感器时序、对齐时间戳、切分训练/测试 | CSV、MAT、数据库查询结果 | 统一格式的 DataFrame |
| 特征工程层 | 时域/频域特征提取、健康指标构建 | 原始振动/温度/电流信号 | 退化特征矩阵 |
| 模型层 | 退化建模与 RUL 回归 | 特征矩阵 + 寿命标签 | 训练好的模型对象 |
| 评估与可视化层 | 预测曲线、误差指标、置信区间 | 模型输出 + 真实值 | 图表与指标报告 |
数据流转的关键在于健康指标的定义。常见做法是用均方根、峰峰值、峭度等时域指标,或者小波包分解后的频带能量。框架不强制你用哪一种,但要求每个健康指标都能追溯到原始信号,否则后面 RUL 偏了根本查不出是哪一步出的问题。
2.3 最小可运行环境搭建与依赖安装
先确保 Python 环境干净。我一般用 conda 建独立环境,避免和系统里的包打架:
conda create -n rul-framework python=3.8 conda activate rul-framework pip install numpy pandas scipy scikit-learn matplotlib jupyter如果要用深度学习版本的退化模型,再补一条:
pip install torch torchvision装完后启动 Notebook:
jupyter notebook默认保存路径通常是当前终端所在目录,想固定到某个工程目录,可以在启动前cd过去,或者改 Jupyter 配置文件里的notebook_dir。这一步看着简单,但路径没设对,后面读数据文件时相对路径会把你绕晕。
2.4 用 Python 跑通第一个 RUL 预测单元
下面这段代码演示从特征矩阵到 RUL 预测的最小闭环,用的是随机森林做退化回归,适合作为基线:
import numpy as np import pandas as pd from sklearn.ensemble import RandomForestRegressor from sklearn.model_selection import train_test_split from sklearn.metrics import mean_squared_error # 模拟退化特征:3 个传感器通道,200 个时间步 np.random.seed(42) n_steps = 200 time = np.arange(n_steps) feature_1 = 0.5 * time + np.random.normal(0, 2, n_steps) # 缓慢上升的退化趋势 feature_2 = np.sin(time / 20) * 10 + np.random.normal(0, 1, n_steps) feature_3 = np.exp(time / 100) + np.random.normal(0, 0.5, n_steps) X = np.column_stack([feature_1, feature_2, feature_3]) # 构造 RUL 标签:假设总寿命 200 步,当前步的剩余寿命为 200 - t y = n_steps - time # 切分训练集与测试集,时间序列不能随机打乱 split_idx = int(n_steps * 0.7) X_train, X_test = X[:split_idx], X[split_idx:] y_train, y_test = y[:split_idx], y[split_idx:] model = RandomForestRegressor(n_estimators=100, max_depth=8, random_state=42) model.fit(X_train, y_train) y_pred = model.predict(X_test) rmse = np.sqrt(mean_squared_error(y_test, y_pred)) print(f"测试集 RMSE: {rmse:.2f}")逻辑说明:这里用三个模拟通道分别代表线性退化、周期波动和指数退化,覆盖常见的传感器行为。RUL 标签按线性递减构造,是最简单的退化假设。切分时按时间顺序切,不能train_test_split默认随机打乱,否则未来信息会泄漏到训练集,RMSE 会好看得离谱但实际部署就翻车。
参数说明:n_estimators控制树的数量,100 是基线值,数据量大可以加到 300;max_depth限制树深,8 层在几百个样本量级下不容易过拟合;random_state固定随机种子,保证每次跑结果一致,方便对比不同特征组合的效果。
3. 故障诊断模块:从原始信号到健康指标的落地路径
3.1 时域与频域特征怎么选、怎么算
故障诊断的第一步是把原始振动或电流信号转成能反映退化趋势的数字。时域特征里,均方根(RMS)对整体能量变化敏感,峭度对冲击成分敏感,峰峰值适合捕捉突发异常。频域特征则要看故障特征频率,比如轴承外圈故障会在特定频率处出现边带。
下面这段代码同时算时域和频域特征:
from scipy.stats import kurtosis, skew from scipy.fft import fft, fftfreq def extract_features(signal, fs=1000): """从一段时序信号中提取时域与频域特征""" features = {} # 时域特征 features['rms'] = np.sqrt(np.mean(signal ** 2)) features['kurtosis'] = kurtosis(signal) features['skewness'] = skew(signal) features['peak_to_peak'] = np.max(signal) - np.min(signal) # 频域特征 n = len(signal) yf = fft(signal) xf = fftfreq(n, 1 / fs)[:n // 2] magnitude = 2.0 / n * np.abs(yf[:n // 2]) features['freq_centroid'] = np.sum(xf * magnitude) / np.sum(magnitude) features['freq_std'] = np.sqrt(np.sum(((xf - features['freq_centroid']) ** 2) * magnitude) / np.sum(magnitude)) return features逻辑说明:rms反映信号能量水平,退化后期通常单调上升;kurtosis对早期微弱冲击敏感,但容易受噪声干扰,适合做辅助指标;freq_centroid是频谱重心,当高频故障成分增多时它会往右移。这些特征拼在一起,比单一指标更能刻画退化过程。
参数说明:fs是采样频率,必须和实际数据一致,否则频域特征全错。n // 2取半边谱是因为实信号频谱对称,只保留正频率部分。
3.2 健康指标构建与退化起始点判定
有了特征,下一步是构建一个能单调反映退化的健康指标。常见做法是对多个特征做归一化后加权融合,或者用主成分分析取第一主成分。退化起始点判定则可以用阈值法:当健康指标连续多个时间步超过基线均值加三倍标准差时,认为退化开始。
from sklearn.preprocessing import MinMaxScaler def build_health_indicator(feature_df): """多特征融合为单一健康指标""" scaler = MinMaxScaler() normalized = scaler.fit_transform(feature_df) # 简单等权融合,实际项目可按相关性调整权重 hi = normalized.mean(axis=1) return hi def find_degradation_start(hi, window=10, threshold=3): """基于滑动统计判定退化起始点""" baseline = hi[:window] mu, sigma = baseline.mean(), baseline.std() for i in range(window, len(hi)): if hi[i] > mu + threshold * sigma: return i return None逻辑说明:MinMaxScaler把不同量纲的特征拉到 0 到 1 之间,避免量级大的特征主导融合结果。等权融合是最省事的做法,如果某个特征和 RUL 的相关性明显更高,可以手动调权重。find_degradation_start用前window个点做基线,后续点超过阈值就标记为退化起点,这个方法对缓变退化有效,对突变故障需要换用变化率检测。
参数说明:window取 10 到 30 比较常见,太小基线不稳,太大则退化起点会往后漂。threshold设 3 对应正态分布下约 99.7% 的置信区间,数据噪声大可以放宽到 4。
3.3 把诊断结果接回 RUL 预测的接口设计
故障诊断的输出不能只停在“有没有故障”,还要给 RUL 模块提供退化阶段标签。框架里我一般设计一个中间数据结构,包含当前健康指标值、退化起始点索引、以及距离起始点的步数。RUL 模型把“距离退化起始点的步数”作为一个强特征输入,比直接用原始时间步更有效,因为不同设备的绝对时间尺度不一样,但退化后的相对步数有可比性。
这个接口设计的关键是解耦:诊断模块只负责输出健康指标和阶段标签,RUL 模块只负责根据这些标签做回归。两边通过一个字典或 DataFrame 传递,改一边不影响另一边。很多项目把诊断和预测揉在一个函数里,后期想换模型时牵一发动全身,血泪经验。
4. 剩余使用寿命预测:模型选型、训练策略与评估指标
4.1 从统计模型到深度学习的选型对比
RUL 预测的模型大致分三类:统计退化模型、机器学习回归、深度学习序列模型。统计模型比如维纳过程、伽马过程,适合退化机理清楚、数据量少的场景,优点是能给出置信区间,缺点是拟合复杂退化模式能力有限。机器学习回归里随机森林和梯度提升树是稳妥的基线,特征工程做得好就能出不错的结果。深度学习方面,LSTM 和 Transformer 适合长序列建模,但需要更多数据和调参精力。
| 模型类型 | 数据需求 | 可解释性 | 置信区间 | 适用场景 |
|---|---|---|---|---|
| 维纳过程 | 低 | 高 | 有 | 退化机理明确、样本少 |
| 随机森林 | 中 | 中 | 需额外构造 | 特征工程成熟、快速基线 |
| LSTM | 高 | 低 | 需额外构造 | 长序列、复杂退化模式 |
| Transformer | 高 | 低 | 需额外构造 | 多传感器融合、大数据量 |
我一般先用随机森林跑一个基线,看 RMSE 和预测曲线形状。如果曲线明显滞后或超前,再考虑换 LSTM 捕捉时序依赖。直接上深度学习容易陷入调参泥潭,最后发现是特征没做好。
4.2 训练集构造中的时间窗口与标签对齐
RUL 预测的训练集构造有个容易翻车的地方:标签对齐。假设设备总寿命 500 个周期,你在第 100 个周期采样,对应的 RUL 是 400。但如果你用滑动窗口取了一段 50 步的序列作为输入,标签应该对应窗口末尾时刻的 RUL,而不是窗口起始时刻。这个细节搞错,模型学出来的东西完全不对。
def create_sequences(features, rul_labels, window_size=50): """滑动窗口构造序列样本,标签对齐窗口末尾""" X, y = [], [] for i in range(len(features) - window_size): X.append(features[i:i + window_size]) y.append(rul_labels[i + window_size - 1]) # 对齐窗口最后一步 return np.array(X), np.array(y)逻辑说明:features[i:i + window_size]取连续window_size步作为输入序列,rul_labels[i + window_size - 1]取窗口最后一步对应的 RUL 作为标签。这样模型看到一段退化过程后,预测的是这段过程结束时的剩余寿命,符合实际部署时的推理逻辑。
参数说明:window_size取 30 到 100 之间,取决于退化过程的快慢。退化慢的设备可以用大窗口,退化快的小窗口更灵敏。窗口太小模型看不到趋势,太大则样本数减少且引入过多历史信息。
4.3 RMSE、MAE 与 PHM 评分函数的取舍
评估 RUL 预测不能只看 RMSE。RMSE 对大误差敏感,但 RUL 场景里“预测偏晚”比“预测偏早”危险得多:偏晚意味着你以为还能跑,结果设备提前挂了。PHM 评分函数对偏晚预测施加指数惩罚,更贴近工程实际。
def phm_score(y_true, y_pred): """PHM 评分函数,对预测偏晚施加更大惩罚""" diff = y_pred - y_true score = np.where(diff < 0, np.exp(-diff / 13) - 1, np.exp(diff / 10) - 1) return np.sum(score)逻辑说明:diff < 0表示预测 RUL 小于真实 RUL,即预测偏早,惩罚系数 13;diff >= 0表示预测偏晚,惩罚系数 10。分母越小惩罚越重,所以偏晚的代价更大。这个函数来自 PHM 挑战赛的经典定义,工程上可以直接用。
参数说明:13 和 10 这两个系数是经验值,可以根据设备安全等级调整。安全要求高的场景把偏晚的惩罚系数降到 8 甚至更低,让模型宁可早报。
5. 避坑与排查:RUL-Framework 落地时最容易翻车的五个地方
5.1 数据泄漏:测试集信息混进训练集
现象:离线评估 RMSE 很低,上线后预测一塌糊涂。原因:构造特征时用了全局统计量,比如对整段数据做归一化再切分,测试集的均值和方差信息泄漏到了训练阶段。解决:所有统计量只能从训练集计算,然后应用到测试集。用fit_transform在训练集上,transform在测试集上,顺序不能反。
5.2 健康指标非单调:退化曲线来回震荡
现象:健康指标画出来上下波动,找不到明确的退化趋势。原因:原始信号噪声太大,或者融合权重不合理导致某个噪声敏感特征主导了指标。解决:先对每个特征做滑动平均平滑,再融合;检查各特征与时间的相关性,把相关性低甚至为负的特征剔除。峭度这类对冲击敏感的特征在噪声环境下要慎用。
5.3 标签构造错误:RUL 从第一个样本就开始递减
现象:模型在设备早期就预测剩余寿命很短。原因:构造 RUL 标签时假设从第一个采样点就开始退化,但实际设备可能运行了很久才进入退化阶段。解决:先用健康指标判定退化起始点,只对退化起始点之后的数据构造 RUL 标签,退化前的数据标记为“健康”或给一个固定的最大 RUL 值。
5.4 Notebook 内存溢出:长时间序列全量加载
现象:跑着跑着内核挂了,提示内存不足。原因:把几百兆甚至几个 G 的传感器数据全部读进 DataFrame,再做特征提取。解决:分块读取,用生成器逐段处理;或者先把特征提取结果存成 parquet 文件,后续直接读特征矩阵。Notebook 里可以用del及时释放不再用的大变量,配合gc.collect()。
5.5 模型过拟合:训练集 RMSE 极低但测试集崩了
现象:训练集 RMSE 个位数,测试集 RMSE 几百。原因:模型太复杂,或者特征维度远大于样本数。解决:先降特征维度,用相关性分析或 PCA 筛掉冗余特征;模型侧加正则化,随机森林限制max_depth,LSTM 加 dropout;增加训练数据量,或者用交叉验证选超参而不是盯着单次划分的结果调。
6. 让 RUL 预测从 Notebook 走向可复现:三个我常用的验证习惯
第一个习惯是固定随机种子并记录每次实验的配置。Notebook 里很容易随手改个参数就跑,跑完忘了改了什么。我一般会在第一个单元格里用一个字典存所有超参,训练前打印出来,结果存到带时间戳的文件夹。这样一周后回头看还能复现。
第二个习惯是画预测曲线而不是只看数字。RMSE 相同的情况下,预测曲线是整体平移还是尾部翘起,对应的工程含义完全不同。整体平移可能是标签对齐偏了,尾部翘起可能是模型对退化加速阶段拟合不足。下面这段代码把真实 RUL 和预测 RUL 画在一起:
import matplotlib.pyplot as plt plt.figure(figsize=(10, 4)) plt.plot(y_test, label='真实 RUL', linewidth=2) plt.plot(y_pred, label='预测 RUL', linestyle='--') plt.xlabel('时间步') plt.ylabel('剩余使用寿命') plt.legend() plt.title('RUL 预测对比') plt.tight_layout() plt.show()第三个习惯是留一段“从未参与训练”的数据做最终验证。很多人把测试集反复用来调参,调到最后测试集也过拟合了。我会从最早的数据里切一段出来,训练和调参全程不碰,最后只跑一次。这次结果才是真正能拿去汇报的数字。
这三个习惯看着笨,但能帮你避开“Notebook 里效果很好、换台机器就复现不了”的尴尬。RUL 预测本身就有不确定性,工程上能做的就是让每次改进都有迹可循。希望帮到你。
本文还有配套的精品资源,点击获取