MyEMS+CNN-LSTM:设备预测性维护从零到92%准确率的实战
2026/9/14 5:25:26 网站建设 项目流程

接到值班同事电话的时候是早上六点半,三号空压机报警停机。我到现场打开柜门,轴承已经烧得发蓝,润滑油碳化成一圈黑渣。事后算账:非计划停机八小时,后段注塑产线全部等料,损失够买两台新电机。那会儿我就在想,要是设备能提前一天告诉我“我要坏了”,这一夜就不用熬了。

那个项目之后,我开始认真把预测性维护往生产环境里落。平台用的是 MyEMS 这个开源能源管理系统,模型选的是 CNN-LSTM 组合,最终在空压机和注塑机的故障预警上跑出了 92% 的准确率。注意,我这里说的是“准确率”,不是“召回率”也不是“F1 分数”,这几个数字在生产场景里差别很大,后面我会专门拆开讲。

这篇内容不是概念科普,是我把从数据采集、标签制作、模型训练到 MyEMS 告警闭环的完整流程,按实战顺序重新走了一遍。适合已经在做设备数据采集、手里攒了一堆历史数据但不知道怎么变成预警能力的工程师;也适合刚接触预测性维护、想知道 CNN-LSTM 到底怎么落地的朋友。先泼一盆冷水:模型不是最难的,数据链路才是。

1. 为什么把 MyEMS 当成预测性维护的数据底座

1.1 MyEMS 在预测性维护里到底承担什么角色

MyEMS 在国内的能源管理圈子里不陌生,很多工厂用它做能耗计量、水电风气数据汇总、能效报表。它是一个开源项目,后端用 Python 写采集器,支持 Modbus、M-Bus、DL/T 645、OPC UA 等一堆协议,数据落到 MySQL 里,前端带一套还算能看的管理界面。

但很少有人把它跟预测性维护联系起来。我刚开始也没想用它,第一反应是自己写一个数据采集加存储的系统。后来被现实教育了:Modbus 轮询要写,断线重连要写,仪表的点位表要维护,历史数据要分表归档,告警要对接钉钉和短信——这些需求 MyEMS 已经做过了,而且做得比我临时写的稳定得多。

所以我给它的定位是:预测性维护的“数据底座”。它负责三件事:第一,从 PLC、电表、传感器里按周期采集数据;第二,把数据可靠地存进数据库,保留足够长的历史周期;第三,提供告警规则和消息推送通道,模型算出的故障概率也要通过它发出去。简单说,跟设备通信和报警的脏活累活它全包了,我只需要把注意力放在特征和模型上。

1.2 数据链路搭建:从设备到模型需要哪些环节

我的试点对象是一台 150kW 的空压机和一台注塑机,再往后来加了一台冷水机组。先看数据从哪来:

  • 变频器、PLC 里的运行参数:通过 Modbus TCP 读取,包括三相电流、排气压力、转速、运行时间。
  • 轴承温度、电机温度:走 4-20mA 模拟量接入 PLC,再被 MyEMS 采集到。
  • 振动信号:这是最麻烦的一路。原始振动加速度信号采样率 20kHz,不能直接进 MySQL,否则一天就是几个 GB。我在边缘侧做了统计降维,每 1 分钟算一次振动加速度的 RMS、峰峰值和峭度,这三个统计量作为特征写进数据库。

MyEMS 的采集周期我设的是 1 分钟一次。为什么不是秒级?因为对于轴承磨损、润滑不良这类慢慢发展的故障,分钟级数据完全够用,而且能大幅降低存储压力和模型输入的复杂性。真正几秒钟就发生的变化(比如电缆瞬间烧断)不在预测性维护的范围内,那是保护装置该管的事。

存储层面有个细节:MySQL 里建一张特征宽表,每一行是一个时间点,字段是振动 RMS、峰值、峭度、温度、电流、压力、转速这些。MyEMS 自己的历史表我不动,从它的接口定时把原始采集值拉出来,落到一张独立的predict_features表里。这样模型读取数据时不会影响 MyEMS 本身的性能。

1.3 为什么不用自研平台,而是“借用”MyEMS

很多人会觉得,MyEMS 是能源管理系统,硬拿来搞预测性维护会不会不伦不类?我的经验是:选择平台看它能不能解决 80% 的重复工作,而不是看它原本的定位是什么。MyEMS 的采集器框架、点位管理、告警模块都是成熟可复用的,我要扩展现有协议也方便。自己从零写一套设备接入层,至少要两三个月,而且还得天天伺候断线重连和数据丢包问题。

这套链路跑了一年,我自己的体感就是:预测性维护项目里真正的技术债不在模型层,而在数据接入和运维层。MyEMS 帮我省掉了最枯燥的部分,让我有余力去迭代模型,这就够了。

2. CNN-LSTM 模型结构设计:把波形变成故障概率

2.1 为什么是 CNN + LSTM,而不是纯 LSTM 或 Transformer

先说结论:对于工业设备故障预测这个场景,CNN-LSTM 是性价比最高的组合,没有之一。纯 LSTM 能捕捉时间依赖,但对局部波形特征不敏感,特别是轴承早期故障那种“每转一圈产生一次冲击”的周期信号,纯 LSTM 很容易把这些特征淹没在长序列里。Transformer 在 NLP 和大模型领域很强,但在设备故障预测上需要海量数据支撑,而且推理和训练的资源开销在工业现场有点奢侈,我用 8GB 显存的显卡训练都嫌瓦数高。

CNN-LSTM 的思路是分工协作。一维卷积层在时间维度上滑动,提取局部波形特征——可以理解成它负责“听音色”,识别振动信号里的冲击脉冲、温度曲线的斜率突变;LSTM 层负责“听节奏”,记住前 8 个小时里这些特征是怎么演化的,判断“这种发展轨迹像不像一次故障前兆”。

生产环境里,这个结构还有一个好处:特征提取和时序建模是分开的,调试时如果发现某个故障类型检不出来,可以先看卷积层提取的特征合不合理,再调 LSTM 的参数,定位问题方便得多。

2.2 网络结构与参数:两层卷积加一层 LSTM 的取舍

模型的输入形状是(480, 10),480 是时间步长度,10 是特征数量(振动 RMS、峰值、峭度、轴承温度、电机温度、三相电流、压力、转速)。网络结构用 Keras 写出来是这样的:

from tensorflow.keras.models import Sequential from tensorflow.keras.layers import Conv1D, MaxPooling1D, LSTM, Dense, Dropout, Flatten model = Sequential([ Conv1D(64, kernel_size=8, activation='relu', padding='same', input_shape=(480, 10)), MaxPooling1D(pool_size=2), Conv1D(128, kernel_size=5, activation='relu', padding='same'), MaxPooling1D(pool_size=2), LSTM(128, return_sequences=False), Dropout(0.3), Dense(64, activation='relu'), Dense(1, activation='sigmoid') ]) model.compile(optimizer='adam', loss='binary_crossentropy', metrics=['accuracy'])

参数选择不是拍脑袋,是反复试出来的:

  • 第一层卷积核 64 个、尺寸 8,负责捕捉短时冲击;第二层卷积核 128 个、尺寸 5,进一步抽象模式。两层卷积提取的特征已经够用,加到三层之后验证集 F1 反而下降,典型的过拟合。
  • LSTM 单元数为什么取 128?因为我输入特征维度是 10,卷积输出后特征图维度约 128,LSTM 单元数跟输入维度匹配效果最好,再大会在有限的故障样本上严重过拟合。
  • padding='same'是必须的,否则卷积操作会缩短时间步,导致后续 LSTM 输入的序列信息被截断。数据量不大,卷积层不设 stride,靠池化降维就够了。

2.3 滑动时间窗:480 步长与 60 步滑动的实战逻辑

模型不能对整段历史数据一次性预测,它需要一个“时间窗口”的概念。我设置的逻辑是:每个时间点取最近 480 分钟的数据(因为每条特征记录是 1 分钟一条,480 步就是 8 小时),预测这个时间点往后 4 小时内发生故障的概率;窗口每 60 分钟滑动一次,也就是每 1 小时产出一个预测结果。

480 这个数字是怎么定的?统计了历史故障数据后发现,从振动峭度开始异常爬升到轴承真正失效,中间大约有 6 到 20 小时。窗口太短(比如 60 步)模型看到的只是异常发生后的突变,噪声大,几乎预测不准;窗口太长(比如 1440 步)早期异常特征会被后续长时间的正常数据稀释,反而“钝化”。480 步刚好覆盖一次完整的故障演化过程。

60 步滑动意味着每 1 小时推理一次,既不会产生太高的计算负载(单次推理在 GPU 上不到 20ms,CPU 上也就几百毫秒),又能保证故障一旦有苗头,一小时之内就能被发现。窗口重叠率高达 87.5%,这会增加样本之间的相关性,所以我在训练时对训练集做了“样本去重”:两条滑窗样本起始点相隔不足 30 分钟的只保留一条,降低模型的过拟合风险。

3. 数据准备与故障标签:92% 准确率的地基

3.1 我实际采集了哪些数据,频率和精度如何

这里把我最终用于模型的特征列全列出来,方便你对照自己的设备做调整:

特征来源精度/范围说明
振动 RMS (mm/s)振动加速度计积分0.01整体振动能量
振动峰值 (mm/s)振动加速度计积分0.01捕捉瞬时冲击
峭度边缘计算统计0.01轴承早期损伤敏感指标
轴承温度 (°C)4-20mA 进 PLC0.1摩擦升温
电机温度 (°C)4-20mA 进 PLC0.1过载/绝缘老化
三相电流 (A)Modbus 读取0.1负荷变化
排气压力 (MPa)Modbus 读取0.01工况识别
转速 (rpm)Modbus 读取1工况识别与停机过滤

振动信号是旋转设备故障预测的关键,尤其是峭度这个指标,我在第一个模型里没有加它,后来加了以后召回率直接提了十几个百分点。峭度描述的是波形分布“尖峰”的程度,轴承早期出现点蚀时,每转一圈会产生一个冲击脉冲,峭度会显著上升。这个特征不需要维护成本,边缘计算脚本每 1 分钟算一次就行。

3.2 故障标签怎么来:维修工单和点检记录的价值

预测性维护最尴尬的事是:没有故障样本。设备天天正常跑,深度学习模型拿什么学“故障长什么样”?

我的做法是去档案室和运维系统里翻旧账。过去一年里,这台空压机经历过 6 次轴承故障、3 次电机绝缘老化和 2 次叶轮不平衡。每次维修都有工单记录,记录了故障类型、发生时间、处理措施。我把这些记录一条条对照 MyEMS 里的历史曲线,找到故障发生的确切时间点,然后往前推 4 小时,把这段时间内的数据标成“即将故障(1)”,其余时间标为“正常(0)”。

为什么是提前 4 小时,不是 24 小时不是 1 小时?因为时间太长样本覆盖的工况太复杂,模型学习到的是“与其故障无关的长期波动”;时间太短又失去了预警意义。4 小时是跟维保团队一起定的,他们反馈说“提前四小时知道,备件更换、人员调度都来得及”。

这步做完,总共得到约 48 万条分钟级样本,其中正样本(故障前 4 小时内)约 2.2 万条,占比只有 4.6%。这个不平衡比例意味着:模型只要把所有样本都预测成“正常”,准确率就有 95.4%——这也是为什么我后面不断强调不要只盯着准确率看。

3.3 类别不平衡处理:SMOTE、类别权重与时间序列划分

4.6% 的正样本比例,直接训练会出问题。我试过三种处理方式:

  1. 随机降采样正常样本,把正负比例降到 1:3。这个方法简单,但丢掉了大量正常工况信息,模型对正常状态的“了解”不够,容易把没见过的正常工况误判成故障。
  2. SMOTE 过采样,在特征空间里人工合成少数类样本。对表格数据有效,但我的数据是带时间序列结构的,SMOTE 合成出来的“故障样本”在时间连续性上不合理,加上以后验证集 F1 反而下降。
  3. 保留全部样本,用类别权重(class_weight)补偿。最终选了这个方案。
total = len(y_train) positive = int(y_train.sum()) negative = total - positive class_weight = {0: 1.0, 1: negative / positive}

这样算下来正样本权重约 14.8,模型每次更新时误判一个正样本的代价是误判负样本的 14.8 倍,自然会把注意力往少数类上倾斜。配合早停(EarlyStopping)和验证集上的 F1 监控,训练过程稳定,不会出现检验集指标暴涨、测试集翻车的现象。

3.4 最容易翻车的数据泄露问题

这点必须单独拎出来讲,因为 90% 的人第一次做时序预测都会掉进这个坑,我也不例外。数据泄露有两种典型情况:

第一种:先做 MinMaxScaler 标准化,再划分训练集和测试集。这样一来,测试集的均值、极值已经参与到了缩放参数的拟合里,模型相当于提前“见过”了测试集的信息,验证时分数虚高。正确做法是先在训练集上fit标准化器,保存下来,再分别 transform 训练集、验证集和测试集。

第二种:滑窗样本之间的时间重叠。相邻两个样本共用大部分时间点,如果随机划分训练集和测试集,同一条故障演化过程会同时出现在两边,模型不是“预测故障”而是“背答案”。正确的切分方式是按时间顺序:用前 70% 时间内的数据做训练集,中间 15% 做验证集,最后 15% 做测试集,确保测试集里的故障样本在时间上完全晚于训练集。

4. 训练与调优:从 78% 到 92% 的真实过程

4.1 第一版模型失败在哪里

第一版模型我用了纯 LSTM,输入只挑了温度、电流、压力三个常规参数,没上振动信号。训练完之后在测试集上的准确率有 94%,看着不错,但一看混淆矩阵就露馅了:F1 只有 0.37,召回率 0.28,基本上等于“从不报警”。为什么准确率虚高?还是因为正样本只占 4.6%,模型学了个偷懒的策略——全部预测成正常,准确率就能拿到 95%。

这轮失败让我明白两个事:第一,没有振动特征,温度电流只能反映设备整体负荷变化,对轴承早期故障根本不敏感;第二,评价指标必须用 F1 和召回率,准确率在这种类别极度不平衡的场景里就是摆设。

4.2 振动特征带来的转折

把振动 RMS、峰值、峭度加进特征集,重新训练同样的纯 LSTM,召回率从 0.28 提到了 0.61。提升是显著的,但还不够。继续分析误报样本后发现,模型对“振动突然变大但持续时间不长”的事件特别敏感,比如车间另一台设备启动时引起的管道共振,会被误判为故障前兆。这只是特征层面的问题,需要模型能区分“瞬时扰动”和“持续恶化”——这正是序列建模的强项,也是我决定加入卷积层的原因。

换成 CNN-LSTM 之后,验证集 F1 第一次突破了 0.75。卷积层把局部波形模式编码成更高阶的特征,LSTM 再学习这些特征在时间轴上的演变规律,误报明显减少。

4.3 从 F1 分数到 92% 准确率的关键调整

之后又做了一系列调优,我挑几个效果最明显的记录一下:

  • 使用类别权重后,正样本的召回率从 0.61 提到 0.78,精确率没有明显下降。
  • 把 LSTM 单元数从 256 降到 128,并加 Dropout(0.3),验证集 F1 从 0.78 提到 0.82。单元数少了,模型容量变小,反而减少了过拟合。
  • 使用更大 batch size(128)配合 Adam 默认学习率,训练更稳定。一开始用的是 32,训练损失震荡明显。
  • 早停条件设为验证集 F1 连续 8 个 epoch 不提升就停止,避免过拟合。

最终测试集的结果是这样的:

模型精确率召回率F1说明
LSTM(无振动特征)0.450.280.34基本不报警
LSTM(含振动特征)0.700.610.65有预警能力但误报多
CNN-LSTM(类别权重调优)0.890.920.90阈值 0.5
CNN-LSTM(实际部署阈值 0.63)0.940.850.89误报率更低

92% 的准确率来自第三行:阈值 0.5 时,模型预测正确的样本占总样本的 91.8%,近似 92%。但在实际部署时我没有用 0.5,用的 0.63,因为对工厂来说误报也是有成本的。后面单独讲。

4.4 92% 准确率应该怎么理解:阈值校准与业务权衡

上面表格里藏着一个关键信息:调阈值改变的不是模型的“能力”,而是“行为”。

模型输出的是一 个 0 到 1 之间的故障概率,默认大于 0.5 就算故障。但在正样本占比只有 4.6% 的场景里,0.5 这个阈值太激进,会把不少临界状态的正常样本划进故障。我在验证集上画了 PR 曲线,发现阈值为 0.63 时 F1 仍然维持在 0.89,但精确率从 0.89 升到了 0.94,意味着误报率几乎减半。代价是召回率从 0.92 降到 0.85,也就是说 100 个真实故障里有 15 个可能漏报,15 个里大多是演化速度极快的突发型故障,提前预警的意义本身就有限。

所以对外汇报时可以说“预警准确率 92%”,但作为工程负责人,心里要清楚这个数字背后的权衡。如果你想追求高召回率,就把阈值调低,代价是值班群每天多几条误报;如果你想让它“每次报警都有价值”,阈值要往上调,接受一定漏报。没有绝对正确的阈值,只有符合现场容忍度的阈值。

5. 把模型接入 MyEMS:从预测结果到值班室告警

5.1 模型推理服务的工程封装

训练好的模型不能躺在 Jupyter Notebook 里,得让它按时跑、能容错、出了问题能自查。我用 TensorFlow 的 SavedModel 格式导出模型,写了一个简单的 Flask 推理服务,接口逻辑是这样的:

  1. 定时任务每 1 小时触发一次,从 MySQL 的predict_features表里取最近 480 条特征记录。
  2. 加载保存好的 scaler,对特征做同样的标准化。
  3. 将矩阵 reshape 成(1, 480, 10)输入模型,得到故障概率。
  4. 将概率写回一张predict_result表,同时推送到 MyEMS 的虚拟设备点位。

推理服务我用 systemd 守护进程托管,异常退出自动拉起。每个预测结果都记录模型版本号和时间戳,方便后续回溯:如果某次效果变差,能知道是哪个版本的模型产出的结果。

5.2 用 MyEMS 自带的告警模块完成闭环

模型产出的故障概率如果不接告警通道,那这套系统只有一半价值。MyEMS 正好有现成的告警管理模块,我的做法是:在系统里新建一个“虚拟设备”,点位名称叫predict_fault_prob,类型选模拟量。定时任务把预测结果写入这个点位对应的数据表,MyEMS 的规则引擎就能像对待温度、电流一样对待这个故障概率。

告警规则的配置很简单:

  • predict_fault_prob > 0.63时触发“故障预警”告警;
  • 连续 3 次检测都满足条件才正式通知,避免瞬时抖动;
  • 告警级别设为“高”,通知组包括设备工程师、值班主管和维保班长;
  • 推送渠道走 MyEMS 集成的企业微信机器人,方便手机端确认和回复。

为什么偏要借道 MyEMS 而不是自己写个钉钉推送?因为 MyEMS 的告警管理里自带确认流程、通知记录存储、告警抑制逻辑,还能跟其他能耗告警统一在一个平台里查。独立写一个推送脚本,第五次告警的时候你就会开始怀念这种集中管理。

5.3 预警复核面板与特征解释

告警发出以后,值班同事会在 MyEMS 前端看到一个“预测性维护监控”仪表板,展示最近 8 小时的特征曲线和模型输出的概率曲线。我特意加了几行文字说明,把模型告警时最主要的三条特征变化列出来。比如:

预测故障概率升至 0.87(阈值 0.63),主要异常:振动峭度升至 12.5(正常<4),轴承温度由 68°C 升至 74°C,电流三相不平衡度 3.2%。

这种可解释性很重要。一线工程师信不信这套系统,取决于他收到告警之后能不能快速验证。有一次振动峭度明显升高但温度还没起来,维保班长半信半疑去测了测轴承座,温度枪打上去确实比平时高了两度,他才真正信了这个模型。

6. 半年实战踩坑记录:这些坑比模型本身更有价值

6.1 时间窗穿越:最隐蔽的假高分

前面说过标准化泄露,这里再讲一个更隐蔽的版本:第一次建模时我把所有历史数据一次性生成了滑窗样本,然后随机划分训练集和测试集,测试集 F1 一度冲到 0.97。当时还以为是模型太强,结果上现场一周内发了 30 多条误报。原因就是测试集和训练集里有大量重叠时间段的样本,模型相当于“背过答案”。

修复方法是先按时间分成三段,再分别生成滑窗样本,并且保证测试集的故障窗口起始时间比训练集晚至少 48 小时。改完以后测试集 F1 从 0.97 掉到 0.90,这才是真实水平。

6.2 工况漂移:夏冬季数据差异导致的误报

第一年夏天结束,天气转凉,冷水机组开始出现一连串莫名告警。排查后发现:模型在训练时没有见过冬季的低环境温度工况,冷却水进水温度整体降低,导致模型把“温度偏低”这种从没见过的状态当成了异常。设备本身一点问题没有。

解决思路:一是把环境温度作为附加特征加入模型输入,让模型学会区分“冷却水温下降因为环境降温”和“冷却水温下降因为换热器结垢”;二是每年按滚动周期重新训练一次模型,加入新季节的数据。目前模型每季度微调一次,用近 6 个月的数据重新训练,效果稳定。

6.3 数据断线、停机和毛刺:清洗策略

工业现场的数据不像公开数据集那么干净,我在这个项目里遇到的数据问题里最典型的有三类:

  • 通信毛刺:Modbus 偶发读回一个异常跳变值,比如电流瞬间从 30A 跳到 200A。处理方式是中值滤波,窗口 5 分钟,剔除孤立尖峰。
  • 计划停机:设备关机时转速为 0,温度和振动特征全部归零。这些“停机正常”的数据如果不过滤,模型会把“零值”学成“正常状态”,等设备开机瞬间特征突变,反而误报。处理方式是:转速低于额定转速 30% 的时间段,直接不生成滑窗样本。
  • 传感器断线:如果某个特征列连续 10 分钟以上没有新值,标记该时间点为缺失,而不是沿用上一次的值。缺了就直接丢弃该样本,绝不往前填充,否则模型会学到“历史值重复出现”这种假象。

6.4 预测概率抖动与告警风暴抑制

模型输出的概率在故障边界附近会出现抖动,比如 0.58、0.64、0.57、0.66 这样来回跳,如果不处理,告警一会儿触发一会儿恢复,值班群会被刷屏。我在输出端做了两重抑制:

  1. 对预测概率做 5 次指数移动平均(EMA),平滑突变。
  2. 连续 3 次(即连续 3 小时)预测值超过阈值才正式触发告警。

这么处理后,误报刷屏的现象消失了,代价是真实的故障预警大约会延迟 1 到 2 小时。对轴承磨损这类演化周期以天计的故障来说,这点延迟完全可以接受。

现在这套系统已经在我们厂里跑了快一年。十二次真实故障里,模型提前预警了十一次,唯一漏掉的一次是电机电缆接头瞬间烧断——物理过程几秒钟就完成了,这种故障本来就该由电气保护装置处理,不是预测性维护的菜。回看整个过程,我的真实体会有三点:预测性维护先解决数据连续性问题,再谈模型精度;92% 这种数字要拆开看,背后的召回率和误报率才是业务真正关心的;最后,模型一定要跟维修执行形成闭环,否则报一百次警也没人理你,还是白搭。

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

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

立即咨询