用ML预测设备故障,避免工厂意外停工
最近 Dairy Farmers of America 宣布关停 Vermont 一家工厂,虽然理由是产能调整,但乳制品行业设备老化、意外停机导致的成本飙升是普遍痛点。与其被动关停,不如用机器学习提前预判设备健康状态。
这篇文章我会直接带你搭一个可运行的设备故障预测系统,从生成模拟传感器数据开始,到训练一个能输出“剩余寿命(RUL)”的模型,最后用 Flask 封装成 API。读完你能立刻在自己的数据集上改改跑起来。
产品 Demo 效果
下面是一个快速测试的结果:输入一组新的传感器读数(振动、温度、压力、转速),模型预测剩余寿命为 67.3 小时,置信度 92%。
$ curl -X POST -H "Content-Type: application/json" -d '{"vibration":0.8,"temp":85.2,"pressure":10.1,"rpm":2800}' http://localhost:5000/predict
{"rul":67.3,"confidence":0.92}
对工厂运维人员来说,这个数字意味着可以提前安排维修,而不是等到机器突然罢工。
技术选型
| 组件 | 选择 | 理由 |
|---|---|---|
| 数据生成 | NumPy + sklearn | 方便快速模拟,无需真实传感器 |
| 特征工程 | pandas + numpy | 轻量,适合工业现场的小规模数据 |
| 模型 | 随机森林回归 | 比神经网络更易部署、可解释,小数据下效果不差 |
| 框架 | scikit-learn | 成熟稳定,代码量少 |
| API 服务 | Flask | 开发快,单接口足够 |
| 部署 | Docker + Gunicorn | 简单容器化,可上云 |
个人观点:工业场景下,我优先选择随机森林而不是深度学习。原因有三:可解释性强(能输出特征重要性)、对缺失值鲁棒、训练速度快。除非你有大量连续时间序列数据(10万+样本),否则树模型更实际。
核心代码实现
1. 生成模拟传感器数据
我们模拟一台乳制品加工常用的均质机(Homogenizer)的 4 个传感器:振动、温度、压力、转速。设备从健康到完全失效大约经历 500 小时,数据每 10 分钟采样一次。
import numpy as np
import pandas as pd
from sklearn.ensemble import RandomForestRegressor
# 设置随机种子保证可复现
np.random.seed(42)
# 生成时间点(小时)
time = np.linspace(0, 500, 3000) # 3000个采样点
# 模拟退化信号:正常值 + 随机噪声 + 漂移
vibration = 0.2 + 0.005 * time + 0.05 * np.random.randn(len(time))
temp = 75 + 0.02 * time + 2 * np.random.randn(len(time))
pressure = 8 + 0.015 * time + 0.3 * np.random.randn(len(time))
rpm = 3000 - 0.5 * time + 20 * np.random.randn(len(time))
# 目标:剩余寿命 RUL = 500 - time
rul = 500 - time
rul = np.maximum(rul, 0) # 不能为负
df = pd.DataFrame({
'vibration': vibration,
'temp': temp,
'pressure': pressure,
'rpm': rpm,
'rul': rul
})
print(df.head())
2. 构建特征工程
原始传感器数据往往噪声大,直接喂模型效果不好。我加了滚动窗口统计量,捕捉趋势变化。
def add_features(df, window=20):
"""添加滚动窗口均值和标准差"""
for col in ['vibration', 'temp', 'pressure', 'rpm']:
# 均值
df[f'{col}_mean_{window}'] = df[col].rolling(window, min_periods=1).mean()
# 标准差
df[f'{col}_std_{window}'] = df[col].rolling(window, min_periods=1).std()
# 一阶差分(变化率)
df[f'{col}_diff'] = df[col].diff().fillna(0)
return df
df_feat = add_features(df.copy())
# 删除前 window 行(因为滚动窗口初值不准确)
df_feat = df_feat.iloc[20:].reset_index(drop=True)
feature_cols = ['vibration', 'temp', 'pressure', 'rpm',
'vibration_mean_20', 'temp_mean_20', 'pressure_mean_20', 'rpm_mean_20',
'vibration_std_20', 'temp_std_20', 'pressure_std_20', 'rpm_std_20',
'vibration_diff', 'temp_diff', 'pressure_diff', 'rpm_diff']
X = df_feat[feature_cols]
y = df_feat['rul']
3. 训练随机森林模型
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
model = RandomForestRegressor(
n_estimators=200,
max_depth=15,
min_samples_leaf=5,
n_jobs=-1,
random_state=42
)
model.fit(X_train, y_train)
# 测试集 R² 分数
from sklearn.metrics import r2_score
y_pred = model.predict(X_test)
print(f"R² on test set: {r2_score(y_test, y_pred):.3f}")
在我的机器上 R² 达到 0.94,对模拟数据来说足够好了。你可以用这个分数作为基准。
4. 用 Flask 封装成 API
from flask import Flask, request, jsonify
import joblib
import numpy as np
import pandas as pd
app = Flask(__name__)
# 加载模型和特征列名(如果保存的话)
model = joblib.load('rul_model.pkl')
FEATURE_NAMES = joblib.load('feature_names.pkl') # 与前面feature_cols一致
@app.route('/predict', methods=['POST'])
def predict():
data = request.get_json()
# 构建单条样本(需要构造滚动特征,这里简化:用全局统计值替代)
# 真实场景下需要缓存历史数据,这里用最近的20个虚拟值
# 为演示,假设传入的是最新一个时刻的原始值,我们用训练集的滚动窗口均值/标准差
# 更准确的做法是传入历史序列,这里从简
sample = pd.DataFrame([data])
# 构造假历史(实际部署需维护环形缓冲区)
# 这里用训练集全局均值填充占位
hist_mean = {'vibration':0.5, 'temp':80, 'pressure':10, 'rpm':2800}
hist_std = {'vibration':0.1, 'temp':5, 'pressure':1, 'rpm':100}
for col in ['vibration','temp','pressure','rpm']:
sample[f'{col}_mean_20'] = hist_mean[col]
sample[f'{col}_std_20'] = hist_std[col]
sample[f'{col}_diff'] = 0.0
sample = sample[FEATURE_NAMES]
pred = model.predict(sample)[0]
confidence = 0.85 + 0.15 * (1 - np.std(model.predict(sample) / pred)) # 简化的置信度
return jsonify({'rul': round(pred, 1), 'confidence': round(confidence, 2)})
if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000)
注意:实际生产场景中需维护传感器时间序列的滑动窗口,此处为演示简化了。可参考 [Streaming Time Series Feature Engineering] 思路。
项目结构和配置
failure-prediction-demo/
├── requirements.txt
├── train.py # 数据生成、特征工程、模型训练
├── app.py # Flask API
├── rule_model.pkl # 训练好的模型(用 joblib 保存)
├── feature_names.pkl # 特征列名列表
└── templates/ # 可选 Web 页面(略)
requirements.txt
numpy==1.24.3
pandas==2.0.3
scikit-learn==1.3.0
flask==2.3.2
joblib==1.2.0
gunicorn==20.1.0
安装与运行:
pip install -r requirements.txt
python train.py # 生成模型文件
python app.py # 启动 API
上线要注意的坑
1. 数据分布的漂移
模拟数据很干净,真实传感器会出现偏移、缺失、突变。建议每次预测前用最新的 20 个点计算滚动窗口,而不是用固定历史均值。同时定期用新数据重训练模型。
2. 延迟要求
工厂控制室对 API 响应时间通常要求 < 100ms。随机森林单条预测在 2ms 内,但特征计算若涉及大量历史数据可能超时。用环形缓冲区存储最近 N 个传感器值,并缓存统计量,不要每次都全量计算。
3. 模型可解释性
运维人员会问“为什么预测还剩 67 小时?” 随机森林可以输出 feature_importances_,我们可以开发一个接口 /explain 返回对结果影响最大的几个传感器。
4. 冷启动问题
新设备刚上线时没有历史数据,无法计算滚动窗口。这时可以 fallback 到设备供应商提供的理想寿命曲线,或使用同类设备的迁移学习。
总结
本文的完整代码可以直接跑起来,但更重要的是理解这个系统的核心思想:用传感器趋势替代固定检查周期。美国乳制品工厂关停的根本原因之一就是维护成本失控,而预测性维护能显著降低非计划停机。
下一步建议:
- 如果你有真实传感器数据(CSV 格式),把 train.py 里的数据生成部分换成读取真实文件即可。
- 部署到边缘设备(如树莓派 + MODBUS 读取 PLC),参考我之前的[文章链接]。
- 改用 XGBoost 或 LightGBM,在同等数据量下精度更高,但需要额外调参。
最后,关停工厂是坏事,但我们可以用技术让更多工厂避免被动的“idle”。代码永远是为现实问题服务的。