ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

工业设备监控数据分析预测:从特征工程到模型实战

工业设备监控数据分析预测:从特征工程到模型实战 简介这是一份面向机器学习初学者、数据分析师及工业设备运维人员的实战资源聚焦工业设备监控数据的探索、分析与故障预测。压缩包内含 11 个文件包括约 716.52 KB 的原始数据集 equipment_anomaly_data.csv、9 个 Python 源代码和 1 个 readme 说明压缩包整体约 310 KB所有脚本均手工整理无语法错误可直接运行。源代码覆盖数据可视化、逻辑回归、随机森林、SVM/KNN 对比、98% 准确率的三特征故障预测、异常检测、维护与性能分析以及基于 TensorFlow 的 ANN 设备监控等典型任务并应用了 SMOTE 不平衡处理、Optuna/GridSearchCV 超参数调优、特征相关性分析等方法。读者可通过运行代码完整复现数据清洗、可视化、建模与评估流程作为课程设计、毕业设计或 Kaggle 类竞赛的练手项目也很有价值目前已有 62 人浏览/学习这份资料。整体内容完整、结构清晰便于按任务分步练习。1. 工业设备监控数据集的分析预测从哪一步开始设备还没报警产线先停了这是工业设备监控最常见的窘境。值班人员盯着温度、振动、电流曲线看往往只能等阈值触发才动手而阈值触发时故障通常已经进入不可逆阶段。标题里这份“工业设备监控数据集分析预测实例”对应的正是这一类问题手里有一批传感器读数和运行状态记录要把“什么时候会坏、坏成哪一类、还有多少安全运行时间”提前预测出来。这套流程不是某个特定行业的专属方案泵机、压缩机、电机、风机、机床主轴都在用同一套方法论。9个源代码的价值不在于数量而在于它通常覆盖了从数据清洗、特征构造、模型训练到预测评估的完整链路。对刚接触设备预测的人来说先搞清楚数据里有什么、能预测什么再动手写代码远比直接跑模型重要。2. 先读数据再定任务故障分类与剩余寿命预测的选择2.1 监控数据里有什么传感器字段与事件标签工业设备监控数据集常见的形态是若干台设备的历史运行记录按时间戳排列成表格。拿到的压缩包里如果是一批 CSV列通常是这几类时间戳、设备编号、多个传感器读数温度、振动加速度、电流、压力、转速、工况参数负载率、运行模式以及一个表示设备健康状态的结果字段。状态字段的表达方式有两种离散标签正常、退化、故障类型A/B/C和连续数值剩余寿命 RUL单位通常是循环次数或小时。先花 10 分钟做一次字段盘点能避开后面 90% 的坑。看时间戳是否连续、设备是否同时运行、传感器有没有大量空值、状态字段的取值分布是否均衡。很多数据集来自试验台设备之间的退化路径差异很大如果某台设备只运行了很短时间就故障它的样本对模型的影响会被放大。2.2 从业务问题到预测目标分类、回归还是健康因子拿到监控数据后第一件事不是选模型而是把业务问题翻译成机器学习问题的形式。同样一份设备数据可以有三个完全不同的预测目标对应不同的标签构造方式。任务类型预测目标标签怎么构造典型输出异常检测当前状态是否偏离正常人工标注正常/异常或无监督打分异常分数/二分类概率故障分类故障属于哪一类维修记录给出故障代码各类别概率剩余寿命预测还能运行多久用故障时刻减去当前时刻RUL 数值小时/循环如果数据里有明确的故障时间点剩余寿命预测通常信息量最大也是工业界最想要的答案。但它的标签构造有讲究退化是渐进的距离故障很远的时候 RUL 标签并不稳定常见做法是对 RUL 做截断比如把超过 100 个周期的标签统一设成 100避免模型在早期阶段被不精确的大数值带偏。如果数据里只有故障类型没有时间点那就退而求其次做分类任务。2.3 用代码给数据摸底下面这段代码不依赖具体字段名拿到任何一张监控数据表都能用它会帮你快速回答三个问题数据长什么样、缺失严重吗、每台设备运行了多久。import pandas as pd import numpy as np def probe_monitoring_data(df, time_col, device_col, sensor_cols): # 时间戳统一成 datetime方便后续按时间切片 df[time_col] pd.to_datetime(df[time_col]) # 按设备统计生命周期长度判断不同设备样本量是否悬殊 life df.groupby(device_col)[time_col].agg([min, max, count]) life[span_hours] (life[max] - life[min]).dt.total_seconds() / 3600 # 缺失率只统计传感器列避免把设备编号也统计进去 missing df[sensor_cols].isnull().mean().sort_values(ascendingFalse) print(整体样本量:, len(df)) print(设备数量:, df[device_col].nunique()) print(\n各设备运行时长(小时):) print(life[span_hours].describe()) print(\n传感器缺失率最高的5列:) print(missing.head(5)) return life, missing # 使用示例df 为原始监控表sensors 为传感器列名列表 life, missing probe_monitoring_data( df, time_coltimestamp, device_coldevice_id, sensor_cols[temp, vibration_x, vibration_y, current, pressure] )逻辑说明先解析时间戳再按设备编号分组统计生命周期最后单独看传感器列的缺失率。把时间统一成 datetime 格式是后续所有时间窗口操作的前提按设备统计生命周期能直接发现数据集中是否存在“某台设备样本极少”的失衡问题缺失率排序则用来决定哪些列适合直接丢弃、哪些列需要插值。参数说明time_col和device_col按实际 CSV 列名传入sensor_cols只放数值型传感器列不要把设备编号、状态标签这类离散列传进去否则缺失率统计没有意义。如果发现某列缺失率超过 30%优先考虑删除而不是插值工业传感器一旦长时间失效插值出来的数据会误导模型。3. 特征工程决定预测上限滚动窗口、频谱与切分方法3.1 为什么原始读数不能直接喂模型刚开始做设备预测的人最容易犯的错是拿当前时刻的温度、振动值直接当特征输入模型。单个时刻的读数信息量非常有限传感器噪声大、工况切换导致均值漂移、故障往往表现为趋势变化而不是瞬间突变。轴承磨损早期振动幅值可能只上升 5%单看一个点根本看不出来但看过去半小时的振动能量谱趋势就清晰了。因此特征工程的核心思路是用过去一段时间的统计量代替当前瞬时值把“点”变成“段”。滚动窗口均值能平滑噪声滚动标准差能捕捉波动加剧滚动极差能体现冲击滚动斜率能反映退化速率。这套组合拳在设备监控里几乎是通用的。另外一个反直觉的结论是并不是窗口越长越好。窗口太长会把近期变化稀释掉预测滞后反而严重窗口长度要根据采样频率和设备退化速度来定振动类信号通常用 5 到 30 分钟的窗口。3.2 滚动窗口统计特征均值、标准差、斜率与区间下面的函数把原始传感器列扩展成一组滚动特征覆盖了均值、波动、最值和趋势四个维度。def build_rolling_features(df, value_cols, window30, min_periods10): roll df[value_cols].rolling(windowwindow, min_periodsmin_periods) df_feat pd.DataFrame(indexdf.index) for col in value_cols: df_feat[f{col}_mean_{window}] roll[col].mean() df_feat[f{col}_std_{window}] roll[col].std() df_feat[f{col}_min_{window}] roll[col].min() df_feat[f{col}_max_{window}] roll[col].max() # 斜率用一阶差分近似比多项式拟合更快更稳 df_feat[f{col}_slope_{window}] df[col].diff().rolling( windowwindow, min_periodsmin_periods ).mean() # 峰值因子最大值与均值的比值对冲击类故障敏感 df_feat[f{col}_crest_{window}] ( df_feat[f{col}_max_{window}] / (df_feat[f{col}_mean_{window}] 1e-6) ) return df_feat # 使用示例temperature 和 vibration_z 是传感器列名 feat build_rolling_features(df, value_cols[temperature, vibration_z], window30) df pd.concat([df, feat], axis1)逻辑说明每个传感器列生成 6 个派生特征。std捕捉波动增大这个早期退化信号slope用一阶差分均值的窗口平均来近似变化趋势比拟合多项式快一个数量级在数据量大的时候差别明显crest峰值因子是振动分析里的经典指标正常工况下峰值与均值比值稳定出现冲击故障时该值会突然拉高。参数说明window30表示 30 个采样点具体数值由采样频率决定——如果每小时采样一次30 代表 30 小时对大多数机械退化过程偏短如果每 10 秒采样一次30 代表 5 分钟适合早期故障检测。min_periods10表示窗口内至少有 10 个有效值才计算避免数据开头大量 NaN。生产环境里我会把window设成 5 分钟、30 分钟、2 小时三档分别捕捉不同退化阶段模型效果通常比单窗口好 10% 以上。3.3 频域特征振动信号绕不开的频谱变换温度、压力这类缓变信号用滚动统计就够但振动信号不一样。轴承故障的特征频率集中在特定频段时域上看起来只是噪声变大频域里却能清楚看到故障特征频率及其倍频。这时候需要对振动信号做频谱变换。import numpy as np def add_fft_features(df, signal_col, sample_rate_hz1.0, n_components10): # 按固定长度切段做 FFT取主要频段的能量占比作为特征 seg_len 256 values df[signal_col].values n_seg len(values) // seg_len if n_seg 0: return df # 只取每个片段的前 n_components 个频点能量 fft_feats [] for i in range(n_seg): seg values[i * seg_len:(i 1) * seg_len] spectrum np.abs(np.fft.rfft(seg - seg.mean())) ** 2 total_energy spectrum.sum() 1e-8 # 归一化并截取低频段主要成分 norm_spec spectrum[:n_components] / total_energy fft_feats.append(norm_spec) # 把片段特征对齐回原始 DataFrame 的末尾片段 feat_names [f{signal_col}_fft_{i} for i in range(n_components)] fft_df pd.DataFrame(fft_feats, columnsfeat_names) fft_df.index df.index[-n_seg:] return fft_df逻辑说明代码把信号按 256 个点切段对每段做快速傅里叶变换取前 10 个频点的归一化能量作为特征。归一化是为了消除幅值量纲影响让不同工况下的特征可比较。低频段能量占比上升往往对应轴承磨损或转子不对中这类早期故障。参数说明sample_rate_hz要跟数据采集频率一致如果传感器是 1kHz 采样FFT 后每个频点代表约 3.9Hz滚动轴承的外圈故障特征频率通常在 100Hz 附近n_components至少取 50 才能覆盖到。如果数据是温度这类缓变信号跳过频域特征直接使用时域滚动特征即可。这里给出的 FFT 特征是片段级对齐训练时要注意与标签的时间轴对齐最稳妥的做法是只把故障发生前 30% 区段的数据用于训练。3.4 时序切分随机 shuffle 在设备预测里是灾难图像任务里随机打乱训练集没问题因为每张图片是独立样本。设备监控数据是强时序的今天的样本和明天的样本高度相关随机打乱等于让模型偷看未来。正确做法是按时间顺序切分验证集并且要求验证集的时间段完整晚于训练集。from sklearn.model_selection import TimeSeriesSplit tscv TimeSeriesSplit(n_splits5, gap2) for fold, (train_idx, val_idx) in enumerate(tscv.split(X)): # 时间序列切分每一折验证集都在训练集之后 X_train, X_val X.iloc[train_idx], X.iloc[val_idx] y_train, y_val y.iloc[train_idx], y.iloc[val_idx] # 每个 fold 里训练集是逐步累积的模拟真实“用过去预测未来” print(ffold {fold}: train {X_train.index[0]} - {X_train.index[-1]} | val {X_val.index[0]} - {X_val.index[-1]})逻辑说明TimeSeriesSplit的切分方式是训练集不断累积、验证集始终在时间上靠后。gap2在训练集末尾和验证集开头之间留出 2 个样本的间隔防止相邻样本因滚动窗口特征重叠而产生信息泄漏。泄漏的典型表现是验证集指标异常漂亮部署后掉点严重。要点如果你的数据集包含多台设备切分时优先按设备切同一台设备的数据不要同时出现在训练集和验证集里。否则模型可能只是记住了某台设备的个体特征而不是学到了通用的退化规律。按设备切分后样本量会变少这时更体现出 716KB 这种小数据集适合跑通流程、不适合追求极致精度的现实。4. 三套模型跑通预测XGBoost 基线、LSTM 与 Transformer 对比4.1 为什么先跑 XGBoost 而不是直接上深度学习设备预测项目里我的习惯永远是先跑一个 XGBoost 当基线。原因不是深度学习不好而是工业数据的样本量通常不够大深模型在小数据上容易过拟合而 XGBoost 对特征工程友好、训练快、能给出特征重要性方便反推数据链路是否合理。训练一个基线模型只要分钟级得到的 MAE 或 RMSE 数值就是后面所有复杂模型的参照系。超参数建议值调整说明n_estimators500配合早停防止过拟合learning_rate0.05调小则需要更多树一般 0.01~0.1 之间max_depth5设备特征数量少深度太大容易过拟合subsample0.8每棵树随机采样 80% 样本colsample_bytree0.8每棵树随机采样 80% 特征early_stopping_rounds50验证集连续 50 轮无提升则停止import xgboost as xgb from sklearn.metrics import mean_absolute_error model_xgb xgb.XGBRegressor( n_estimators500, learning_rate0.05, max_depth5, subsample0.8, colsample_bytree0.8, random_state42 ) model_xgb.fit( X_train, y_train, eval_set[(X_val, y_val)], early_stopping_rounds50, verbose20 ) y_pred model_xgb.predict(X_val) print(MAE:, mean_absolute_error(y_val, y_pred)) # 特征重要性排序用来判断哪些传感器特征真正起作用 importance sorted( zip(X_train.columns, model_xgb.feature_importances_), keylambda x: x[1], reverseTrue )[:10] for name, score in importance: print(f{name}: {score:.4f})逻辑说明训练时传入了验证集并开启早停模型在验证集指标连续 50 轮不提升后停止既能防止过拟合又能省时间。MAE 的单位与剩余寿命标签一致比如预测结果平均偏差 7.3 个周期比 RMSE 更直观。特征重要性排序能一眼看出滚动均值、峰值因子哪个更有效据此调整特征组合后续模型的效果会差不少。注意如果特征重要性里某个传感器列占到 40% 以上要怀疑是不是特征泄漏了比如传感器读数直接参与计算了标签。正常的多传感器工业数据重要性分布通常相对分散。4.2 用 PyTorch 写一个能跑的 LSTM 预测模块序列模型处理设备退化时序数据有天然优势它能自动从窗口数据中提取时间依赖关系不需要像 XGBoost 那样手工构造滞后特征。下面给出一个在工业预测场景里常用的最小可跑结构。import torch import torch.nn as nn class DeviceLSTM(nn.Module): def __init__(self, n_features, hidden_size64, num_layers2, dropout0.2): super().__init__() self.lstm nn.LSTM( input_sizen_features, hidden_sizehidden_size, num_layersnum_layers, batch_firstTrue, dropoutdropout ) self.head nn.Sequential( nn.Linear(hidden_size, 32), nn.ReLU(), nn.Linear(32, 1) ) def forward(self, x): # x: (batch, seq_len, n_features) out, _ self.lstm(x) # 取最后一个时间步的输出作为序列表示 last_step out[:, -1, :] return self.head(last_step).squeeze(-1) # 使用示例seq_len 由前面滚动窗口长度决定 model_lstm DeviceLSTM(n_featuresX_train.shape[1], hidden_size64)逻辑说明输入形状是 (batch, seq_len, n_features)batch_firstTrue让第一个维度是批次。模型取 LSTM 最后一个时间步的隐状态接一个小的全连接回归头。取最后一个时间步是最常见的做法因为设备预测要求的是“基于到目前为止的全部信息预测未来”最后一个隐状态已汇聚了整个序列的信息。关键参数hidden_size64控制记忆容量工业时序特征通常只有几十维64 足够再大只会增加过拟合风险num_layers2表示堆叠两层 LSTM两层能学到更高层的时间抽象但三层以上在几千样本的小数据集上收益很小dropout0.2只对层间连接生效是防止深模型过拟合的关键。训练时要记得对输入做标准化并且只用训练集的均值和标准差来转换验证集否则验证集指标会虚高。4.3 把 Transformer 搬到设备时序上要注意的不是架构而是配参Transformer 在时序预测上的优势是能直接建模任意两个时间步之间的依赖关系不像 LSTM 需要逐步传递。但中小规模设备数据集上直接套用标准 Transformer 容易过拟合。常见的“transformer 预测 python 代码”翻车原因集中在三个地方忘记加位置编码、d_model 设得过大、dropout 设为零。下面这段代码给出一个适配小型数据集的精简配置。import torch.nn as nn import math class PositionalEncoding(nn.Module): def __init__(self, d_model, max_len500): super().__init__() pe torch.zeros(max_len, d_model) position torch.arange(0, max_len, dtypetorch.float).unsqueeze(1) div_term torch.exp(torch.arange(0, d_model, 2).float() * (-math.log(10000.0) / d_model)) pe[:, 0::2] torch.sin(position * div_term) pe[:, 1::2] torch.cos(position * div_term) self.register_buffer(pe, pe.unsqueeze(0)) def forward(self, x): return x self.pe[:, :x.size(1)] class SmallDeviceTransformer(nn.Module): def __init__(self, n_features, d_model64, nhead4, num_layers2, dim_feedforward128, dropout0.1): super().__init__() self.input_proj nn.Linear(n_features, d_model) self.pos_enc PositionalEncoding(d_model) encoder_layer nn.TransformerEncoderLayer( d_modeld_model, nheadnhead, dim_feedforwarddim_feedforward, dropoutdropout, batch_firstTrue ) self.encoder nn.TransformerEncoder(encoder_layer, num_layersnum_layers) self.head nn.Linear(d_model, 1) def forward(self, x): # x: (batch, seq_len, n_features) x self.input_proj(x) x self.pos_enc(x) # 位置编码是必须的否则顺序信息丢失 x self.encoder(x) # 用最后一个时间步做预测 return self.head(x[:, -1, :]).squeeze(-1) model_trm SmallDeviceTransformer(n_featuresX_train.shape[1])逻辑说明先通过线性层把原始特征投影到d_model维空间加上位置编码表示时间顺序再经过两层 TransformerEncoder取最后一个时间步的输出接回归头。对比 LSTM 的差异Transformer 没有循环结构位置编码是唯一携带顺序信息的地方不能省略。参数说明d_model64对设备数据足够标准 NLP 任务里 d_model 普遍是 512但工业时序特征维度通常 20~5064 维表示已经充分nhead4表示多头注意力分裂成 4 个头每个头关注不同时间尺度的模式dim_feedforward128是前馈网络宽度不需要设成 512dropout0.1是很多小数据集上过拟合的分水岭不要设成 0。相比 LSTM同数据量下 Transformer 通常需要多 30% 的训练轮数才能收敛可以用早停控制轮数上限。4.4 训练对比的常见坑位随机种子、归一化泄漏与预测滞后三套模型之间的公平对比要提前固定四件事随机种子、训练/验证切分、特征集、归一化方式。随机种子不固定每次跑的 MAE 波动可能比模型之间的差异还大归一化泄漏前面提过是隐蔽性最强的错误。还有一点容易被忽略设备预测模型在验证集上表现极好往往是因为模型学会了“预测值等于最近观测值”——退化缓慢的早期阶段这个策略几乎不犯错但它没有预测能力。判断模型是否真的学到退化趋势把验证集上的真实 RUL 和预测 RUL 画成曲线看预测曲线是否在趋势转折点有明显滞后。滞后 3 个周期以内可以接受滞后超过 10 个周期说明特征或模型没有捕捉到退化加速信号需要回到特征工程环节调整窗口参数。5. 用滚动预测把模型变成在线监控的小工具模型训练完不是终点设备监控场景里真正有用的是在线推理每隔一个采样周期用最近一个窗口的数据预测未来一段时间的设备状态。这个环节有三个实用技巧。技巧一是用固定长度的双端队列维护最新传感器读数避免每次重新读取整个历史数据文件。在内存里保留窗口长度的数据滚动更新推理延迟可以压到毫秒级。from collections import deque class RollingPredictor: def __init__(self, model, scaler, feature_builder, window30): self.model model self.scaler scaler self.feature_builder feature_builder self.window window # deque 长度固定为 window满了自动弹出旧数据 self.buffer deque(maxlenwindow) def update(self, sensor_row): # sensor_row: dict包含温度、振动等原始读数 self.buffer.append(sensor_row) if len(self.buffer) self.window: return None # 数据不足先不预测 # 用缓冲区内数据构造滚动特征并推理 frame pd.DataFrame(list(self.buffer)) feat self.feature_builder(frame) feat_scaled self.scaler.transform(feat.iloc[[-1]]) return self.model.predict(feat_scaled)[0] predictor RollingPredictor(model_lstm, scaler, build_rolling_features, window30) # 新数据到达时调用 r predictor.update(new_row)r 就是当前 RUL 预测值技巧二是对回归输出做“连续确认”再触发告警避免单次预测抖动导致误报。具体做法连续 3 个预测周期 RUL 均低于阈值时才触发维护工单这个思路在工业界比单点阈值可靠得多。技巧三是性能评估不要只看 MAE重点算“预测提前量”——模型预测的故障时刻比真实故障时刻早多少。提前量不足反而比误差大更危险。保存模型时把 scaler、特征列顺序、窗口长度一起打包压进 joblib 文件部署环境直接解包恢复避免分别传递导致字段错位。这类压缩包项目里通常已经按“数据探索、特征工程、基线模型、序列模型、评估对比、模型保存”拆好了脚本照着这个清单逐项检查自己的实现是否完整比追求单个模型的精度天花板更有价值。本文还有配套的精品资源点击获取
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进