
凌晨两点一台空压机的轴承温度开始异常爬升从 76℃ 到 93℃ 只用了不到四十分钟。传感器数据其实早就给出了暗示——过去一周这个测点的温度波动幅度在持续加大电流谐波也出现了周期性畸变但传统报警策略只盯着是否超过 85℃这个绝对阈值对渐进式劣化完全没有感知。第二天巡检员发现时轴承已经彻底烧毁备件没有库存产线停了将近一天。这个项目就是为解决这类问题而做的基于 MyEMS 采集和存储的设备运行数据用 CNN-LSTM 混合模型做故障趋势预测在真实生产数据上把预警准确率做到了 92%。这篇内容会完整记录从数据工程、模型搭建到上线部署的全过程包括那些只有真正跑过一遍才会遇到的坑。如果你正在做预测性维护项目或者手里有一批工业时序数据但不知道从哪下手这篇内容应该能帮你省下不少试错时间。1. 为什么把宝押在 MyEMS 上预测性维护的地基是数据不是算法1.1 很多团队死在数据环节而不是模型环节一说到设备故障预测很多人第一反应就是上深度学习模型。我之前见过不少团队上来就想训练一个多牛逼的网络结果数据采集不完整、测点混乱、时间戳对不齐、故障记录缺失模型根本没法训练。预测性维护这个事算法只占三成剩下七成都是数据工程。而 MyEMS 在这套体系里扮演的恰恰就是数据底座的角色。MyEMS 名义上是一个开源的能源管理系统核心能力是能耗监测与分析但它的数据采集层、测点管理、历史数据存储和告警通知机制天然就是一套工业数据基础设施。我在这套系统上接入设备运行数据几乎没有为数据怎么存、怎么取写过额外的代码。1.2 实际的数据链路由四层组成整个项目的数据流是这样的设备传感器 → 现场数据采集器Modbus TCP/RTU→ MyEMS 数据采集服务 → MySQL 数据库 → Python 建模与推理服务每一层都有各自的职责传感器负责把物理量变成电信号采集器负责协议解析和汇聚MyEMS 负责按点位定时采集并落库Python 服务负责从库里取数、构造特征、跑模型推理。其中最容易出问题的其实是第一层和第二层——Modbus 的寄存器地址如果配置错了采上来的数据就是乱的后面再好的模型也白搭。所以项目启动的第一件事不是写模型而是把所有测点的地址、量程、单位、采样频率核对一遍。1.3 测点梳理不是所有数据都对故障有贡献MyEMS 支持灵活的测点配置但并不是测点越多越好。我当时把每个设备能采集的数据全列出来再逐一判断与故障的相关性最终每台设备保留了 8 个核心测点测点采样频率对故障预测的价值三相电流A/B/C相1s负载突变、电气异常、轴承磨损导致的扭矩变化振动加速度1s轴承、转子早期故障最敏感的信号排气温度1s散热恶化、摩擦加剧的直接反映润滑油温度1s润滑系统异常、过度磨损排气压力1s气路堵塞、阀门故障启停状态开关量用于过滤停机段的无意义数据这里有个经验振动数据在故障早期往往比温度更灵敏但它的噪声也更大对传感器质量要求高。如果你的现场振动传感器精度不够宁可不用也不要硬塞进模型否则只会给模型增加噪声。2. 数据工程先行滑动窗口、特征处理和故障标签2.1 用滑窗把连续时序切成样本模型需要一个固定形状的输入所以第一步是把连续的时间序列切成长度为 60 秒的滑动窗口。每秒钟一条记录8 个测点每个窗口就是一张 60×8 的二维表这正好是 CNN 卷积核可以处理的输入形状。窗口大小不是随便拍的。我对比过 30 秒、60 秒、120 秒三种窗口发现 60 秒能在捕捉足够上下文和故障发生时及时反应之间取得平衡。窗口太短模型看不到渐变的趋势窗口太长故障发生前的变化会被大量正常数据稀释而且判断的时效性也变差。滑动步长设为 10 秒也就是相邻两个窗口有 50 秒的重叠这样既不会漏掉关键变化也不会生成太多冗余样本。2.2 归一化和缺失值处理要讲究方法归一化我用的是 RobustScaler 而不是 MinMaxScaler。原因很简单工业传感器数据里有不少离群点MinMaxScaler 会被极值拉偏而 RobustScaler 基于中位数和四分位距对离群点更鲁棒。而且归一化的参数必须只用训练集的数据来拟合再应用到验证集和测试集否则会造成数据泄露。缺失值处理是另一个大坑。Modbus 采集偶尔会断线或者设备重启期间数据不连续。我的处理方式分三种情况单个采样点缺失用前后值线性插值连续缺失超过 30 秒直接丢弃这个窗口设备处于停机状态启停信号为 0的时段整段剔除不参与训练也不参与推理。原因很直接——停机期间的数据全是正常的常数模型如果见过太多这种样本会把所有平稳数据都当成健康状态反而学不到故障模式。2.3 故障标签维修工单和点检记录是最好的标注来源这是整个项目里最脏最累的活但也是决定模型上限的环节。监督学习必须有标签而在工业场景里我们不会为了训练模型刻意把设备弄坏所以标签只能来自历史故障记录。我们用的是维修工单加点检记录每当设备因为某类故障停机维修就产生一条工单记录了故障时间、故障部位、处理措施。把这些工单和传感器数据对齐之后把故障发生前 24 小时内的窗口标记为正样本即将发生故障其余窗口标记为负样本正常运行。这个前 24 小时的窗口长度需要和你的预警目标匹配——我最初设的是提前 72 小时结果发现有些故障在 60 小时前传感器数据根本没有明显变化模型学习起来很吃力后来调整为 24 小时才合理。2.4 正负样本比例失衡怎么处理清洗完标签后正样本窗口只占全部数据的 9% 左右。直接在这么不均衡的数据上训练模型会学成永远输出负样本因为这样准确率也有 91%。我没有用 SMOTE 这类合成采样方法——时序数据不像图像随便插值合成出来的假故障模式很可能不符合物理规律。我用的方案是两个一是给损失函数加 class_weight把正样本的权重调到负样本的 3 到 4 倍二是在训练时使用加权随机采样让每个 batch 里正样本占比稳定在 20% 到 30% 之间。这两个手段一起用模型才真正开始学习故障模式。3. CNN-LSTM 模型拆解两个网络各管一块为什么组合更有效3.1 为什么不用纯 LSTM也不直接上 Transformer最初我试过纯 LSTM效果不太理想。LSTM 擅长捕捉长期时序依赖但对局部突变模式比如振动信号里一个突然的尖峰、电流波形里一段短暂的畸变响应不够敏锐。工业故障往往正是由这些局部异常逐步发展而来的纯 LSTM 会把它们当噪声滤掉。也考虑过 Transformer。但 Transformer 是出了名的数据胃口大通常需要海量数据才能训出效果而我们的历史故障样本只有几百个硬上 Transformer 只会严重过拟合。CNN-LSTM 的优势在于CNN 用卷积核扫过时间轴专门提取局部特征相当于一个自动的特征提取器LSTM 跟在后面把 CNN 提取出来的特征序列建模成长期依赖捕捉故障从萌芽到恶化的趋势。两个网络各管一段参数量适中在小数据集上更容易收敛。3.2 模型结构和参数细节模型用 Keras 搭结构如下from tensorflow.keras.models import Sequential from tensorflow.keras.layers import Conv1D, MaxPooling1D, LSTM, Dense, Dropout model Sequential([ Conv1D(filters64, kernel_size3, activationrelu, input_shape(60, 8)), MaxPooling1D(pool_size2), Conv1D(filters128, kernel_size3, activationrelu), MaxPooling1D(pool_size2), LSTM(units64, return_sequencesFalse), Dropout(0.3), Dense(32, activationrelu), Dropout(0.2), Dense(1, activationsigmoid) ])几个关键设计说明一下。第一层卷积的 kernel_size 设成 3意味着卷积核一次看 3 个时间步3 秒的数据这个尺度正好能捕捉振动尖峰、电流突变这类短时模式。64 个卷积核相当于 64 个不同的局部特征探测器每个卷积核学习一种故障相关的短时波形。两层卷积后面的 MaxPooling 把序列长度从 60 压缩到 15一方面降低后续 LSTM 的计算量另一方面增强对时间平移的鲁棒性——故障发生的具体时刻前后差几秒不影响特征提取。LSTM 层的 64 个单元接收 CNN 输出的特征序列负责学习这些局部特征在时间维度上的演变关系。举个例子CNN 可能识别出振动能量增强这个局部特征LSTM 则学习到这种增强在过去 15 分钟内持续出现且幅度递增才是故障前兆单次偶然的振动尖峰不算。Dropout 层主要是防止过拟合因为我们的训练样本本身不多dropout 比例我试过 0.2 到 0.50.3 左右效果最稳。3.3 训练配置三件套让模型收敛更稳训练时我用的是标准配置但有几个细节值得记录from tensorflow.keras.optimizers import Adam from tensorflow.keras.callbacks import EarlyStopping, ReduceLROnPlateau model.compile(optimizerAdam(learning_rate0.001), lossbinary_crossentropy, metrics[accuracy]) callbacks [ EarlyStopping(monitorval_loss, patience10, restore_best_weightsTrue), ReduceLROnPlateau(monitorval_loss, factor0.5, patience5) ]batch size 设 64初始学习率 0.001。EarlyStopping 的 patience 设 10如果验证集 loss 连续 10 个 epoch 不下降就停止训练并回滚到最优权重。ReduceLROnPlateau 在验证集 loss 停滞 5 个 epoch 后把学习率减半让模型收敛得更细。实际训练大概在 30 到 40 个 epoch 时触发 early stopping单次训练几分钟就结束。这里有一个特别重要的原则验证集和测试集必须按时间先后切分不能随机打乱。时间序列数据的随机切分会造成严重的数据泄露——模型在训练阶段见过测试集时间段的统计分布评估结果会虚高上线后立刻打回原形。我的切分方式是前 70% 时间段的窗口做训练中间 15% 做验证最后 15% 做测试。4. 92% 预警准确率是怎么评估出来的以及它掩盖了什么4.1 评估指标远不止准确率一个数在类别不均衡的场景里准确率是一个极具欺骗性的指标。前面提到负样本占 91%就算模型什么都不学、永远输出正常准确率也有 91%。所以我的评估核心是混淆矩阵衍生出的四个指标准确率、精确率、召回率和 F1 值。指标数值Accuracy准确率92.4%Precision精确率89.7%Recall召回率85.3%F1 Score87.4%AUC0.954.2 92% 这个数字到底是怎么来的标题里说的 92% 预警准确率对应的就是测试集上的 Accuracy。但我要坦白讲这个数字的参考价值有限真正决定业务效果的是 Precision 和 Recall 的平衡。精确率 89.7% 意味着每触发 10 次预警大约有 9 次确实对应了将要发生的故障召回率 85.3% 意味着所有实际发生的故障里模型提前捕捉到了大约 85%。这两个指标是此消彼长的关系——把预警阈值调低召回率会上升但误报也会增多阈值调高误报减少但漏报风险增加。在预测性维护场景里我建议把 Recall 看得比 Precision 更重要。一次漏报的代价是设备损坏和停机一次误报的代价顶多是维修人员白跑一趟。所以我在业务配置上牺牲了一部分精确率换来了更高的召回率。4.3 别忘了区分窗口级别和事件级别的准确率还有一个容易混淆的地方92.4% 是窗口级别的分类准确率不是事件级别的预警准确率。一个完整的故障事件可能对应几百个正样本窗口只要模型在其中大部分窗口上给出了预警信号这个故障就算被成功预警了。所以在业务统计时我又做了一个事件级别的评估测试集里实际发生的故障事件一共 41 起模型成功提前预警了 35 起漏报 6 起事件级别的召回率是 85.4%。这个数字比窗口级别的 92.4% 更能反映真实业务价值。5. 上线部署、阈值调优与踩坑实录5.1 模型服务与 MyEMS 的数据衔接模型训练完要让它真正跑起来还需要一层接线工作。我的方案是一个独立的 Python 推理服务每 10 秒从 MySQL 里拉取所有设备最近 60 秒的 8 个测点数据构造一个 60×8 的窗口经过和训练时相同的预处理RobustScaler 变换、缺失值处理喂给模型得到故障概率。得到概率之后通过 MyEMS 自带的告警通知机制下发预警。MyEMS 本身支持邮件和站内通知我在上面封装了一层把模型的输出转成告警事件推送给负责设备维护的同事。整个推理链路延迟在 1 秒以内从数据写入 MySQL 到告警发出基本是实时的。5.2 预警分级和防抖策略模型输出的概率是个 0 到 1 的连续值直接拿 0.5 当阈值会有一堆抖动——可能某一秒概率冲到 0.6下一秒又掉回 0.3如果每次都发告警维护人员会被骚扰到把告警屏蔽。我的做法是三级预警加防抖概率大于 0.5 且持续 3 个以上窗口黄色预警表示关注概率大于 0.7 且持续 3 个以上窗口橙色预警安排现场检查概率大于 0.9 或连续 10 个窗口超过 0.7红色预警准备停机检修。防抖的本质是连续 N 次命中才触发这比单次阈值判断可靠得多也是工业场景里一个便宜又有效的技巧。5.3 踩坑一标签噪声差点毁掉整个模型第一次训练出的模型性能惨不忍睹AUC 只有 0.7 左右。排查了很久才发现是标签问题。维修工单里有一批保养工单被当成了故障记录——设备正常保养也会产生工单但工单类型没有细分我把所有停机相关的工单都标记成了故障。这意味着模型把大量正常运行的窗口当成了正样本自然是越学越歪。处理方式是回到工单系统逐条核对故障原因字段。凡是定期保养例行检查能耗审计类型的工单全部剔除只保留明确写有轴承磨损、电机烧毁、阀体卡滞等故障描述的工单。重新清洗标签后同一套模型的 AUC 直接从 0.7 跳到 0.93。这件事给我的教训是模型效果不对先别调参先检查标签。5.4 踩坑二传感器漂移让特征分布整体偏移上线运行两个月后预警准确率开始下滑。查数据发现其中一台设备的振动传感器出现了零点漂移——传感器的静态输出从接近 0 慢慢漂移到了 0.8 左右。这个漂移量对阈值规则来说不算什么但对模型来说等同于所有振动特征的分布整体平移模型很容易把这种系统性漂移误判为异常状态导致大量误报。处理方式是在预处理环节加了一个校准逻辑每周用设备停机状态下传感器的读数作为基准值对该测点的所有数据进行偏移校正。另外在推理环节增加了输入分布监控——如果某个测点的均值、方差在最近一周内发生明显变化而设备本身没有检修记录就自动提示检查传感器而不是直接信任模型输出。5.5 踩坑三设备老化带来的概念漂移我们监控的 6 台空压机里有 2 台是新设备4 台已经运行了五年以上。新设备的正常振动水平明显低于老设备同一个模型在两组设备上的表现差异很大在新设备上误报率偏高因为模型把新设备的正常状态当成了老设备的劣化趋势。解决思路不是重新训练一个全局模型而是用少量新设备数据对模型做微调。具体做法是把预训练模型的 LSTM 层之前的部分冻结只微调 LSTM 和后面的全连接层每个新设备只需要几百个正常样本就能完成适配。这样既保留了模型从历史故障中学到的通用知识又兼顾了不同设备之间的个体差异。5.6 与传统阈值方案的横向对比项目上线后我拉了一段时间的数据把 CNN-LSTM 模型和之前用的阈值规则做了个对比对比项传统阈值规则CNN-LSTM 模型平均提前预警时间故障前 0~30 分钟故障前 6~24 小时事件级漏报率约 40%约 15%月均误报次数8~10 次2~3 次单次故障停机时长8~16 小时2~4 小时最直观的变化是提前预警时间。阈值规则本质上是等故障已经发生了才报警而模型因为学习了故障从萌芽到恶化的发展模式能够在故障真正发生前半天到一天给出信号。这个时间差意味着维修团队可以从容地安排备件、计划停机窗口而不是半夜被叫起来抢修。这个项目做下来我最大的体会是预测性维护真正值钱的不是那个 92% 的准确率数字而是它能把不确定的设备状态提前变成确定的维修计划。模型结果上线之后我还保留了每个月用新积累的数据重新训练一次的节奏同时把窗口级别的预警结果和实际维修工单定期做闭环比对——模型有没有漏报、有没有误报全部回流到训练数据里。这套机制跑顺之后准确率还有进一步上升的空间。下一步我打算在 CNN-LSTM 的输出端接一个回归头从预测是否故障升级到预测剩余使用寿命让维护策略从知道要坏进一步变成知道还能撑多久。这又是一个新的项目了后面有进展再来分享。