☰
工业故障诊断落地实战:物理约束与不平衡样本下的实时部署框架
2026/10/14 14:07:52 网站建设 项目流程

做工业故障诊断项目,最难受的不是模型精度不够,而是模型在测试集上F1刷到98%,一上现场就崩。去年我们给一套离心压缩机做振动监测,甲方提了三个硬性要求:必须把物理约束加进算法、故障样本只有正常样本的百分之一、模型要在边缘网关上一个窗口周期内给出结果。这三个要求凑在一起,就成了“物理约束+不平衡样本+实时部署”的完整框架。这篇文章把我们在落地过程中的设计思路、关键代码、踩过的坑全部摊开讲,给正在做类似项目的朋友做个参考。

先说结论:这类项目的核心难点从来不在某一个点上,而是三个约束互相打架。物理约束限制了模型自由度,不平衡样本逼着你别信准确率,实时部署又砍断了你“上大模型慢慢跑”的后路。只有把三者当成一个整体来设计,才不会陷入“实验室调参一时爽,现场验收火葬场”的循环。

1. 先拆需求:三个关键词到底在解决什么问题

1.1 为什么纯数据驱动的故障诊断在现场会翻车

很多人对深度学习模型有过高预期,觉得数据量够了就能自动学到故障规律。但工业信号和自然图像有本质区别:训练数据往往只覆盖少数几种工况,现场设备转速、负荷、温度稍有变化,输入分布就变了,模型立刻从“高精度”变成“乱猜”。

机器学习模型擅长插值,不擅长外推。训练集里如果有90%是50%负荷下的振动数据,模型大概率只在附近工况有效。这时候物理约束的作用就体现出来了:轴承故障特征频率不会因为数据分布变化而消失,振动能量不会因为换了一个班组就违背守恒关系。物理约束本质上给模型画了一条常识边界,让输出结果不会跑到物理上不可能的区域。

我见过不少团队在诊断项目中完全忽略物理信息,只靠卷积神经网络硬学。结果就是测试集表现良好,现场遇到一个稍微不同的转速区间,模型开始疯狂误报或者漏报。设备工程师过来问一句“你这个报警依据是什么”,你连解释的抓手都没有。

1.2 工业故障样本的“九死一生”才是常态

不平衡样本不是“少数类占10%”这种温和场景。真实产线上,一个月采集800万个振动窗口,人工确认的故障样本可能只有120条,其中每种故障类型再细分下去,部分类别可能只有十几条。

这种情况下用准确率评估模型就是自欺欺人。假设正常样本占99.9%,模型什么都不学,把所有样本判为正常,准确率还有99.9%。但现场要的不是这个数字,而是“漏掉一条故障记录,可能损失整条产线”。

更麻烦的是,故障样本本身还带有很强的工况偏置,比如故障数据几乎都来自某个特定转速段,模型很容易学会“按转速分类”而不是“按故障分类”。这就是为什么很多人用不平衡数据集训练后,验证集指标还行,部署下来一塌糊涂。

1.3 实时部署不是“服务器上跑个接口”

工业现场所谓实时,和我们平时理解的Web接口延迟还不一样。它是硬实时约束:振动传感器以20480Hz采样,每个窗口1024点,相当于50毫秒就来一批数据,算法必须在这个窗口周期之内完成推理、判定、结果输出。一旦超时,数据堆积,系统就失去实时性。

部署环境通常只是一台4到8核的工业CPU网关,或者一块Jetson级别的嵌入式板卡,不允许你上一张高功耗大显卡。再加上现场网络可靠性有限,数据传输、预处理、推理、告警推送是串联链路上的每一环,任何一段慢了,整体周期就崩了。

一个典型的部署需求清单大概长这样:

参数典型值备注
采样率20k ~ 50k Hz振动加速度传感器常用区间
窗口长度1024 ~ 4096点对应约50 ~ 200ms
推理延迟预算小于窗口时长需要留出预处理余量
数据传输MQTT / OPC UA现场网络可能丢包
边缘算力4~8核CPU或Jetson不适合重模型

设计框架的时候,先把这个需求清单摆出来,后面所有模型选择和优化都得对着它来。

2. 物理约束的设计与落地

2.1 先把物理先验写成代码:滚动轴承特征频率

物理约束听起来抽象,落地的时候往往是从一个公式开始的。滚动轴承不同部件故障时,振动信号会在特定频率处出现冲击成分,这些频率可以由轴承几何参数和转速精确计算。

以滚动轴承为例,几个关键的特征频率计算方式如下:

项目公式含义
外圈故障频率 BPFOn/2 * fr * (1 - d/D * cosα)滚动体通过外圈损伤点
内圈故障频率 BPFIn/2 * fr * (1 + d/D * cosα)滚动体通过内圈损伤点
滚动体故障频率 BSFD/(2d) * fr * (1 - (d/D)² * cos²α)滚动体自转故障
保持架故障频率 FTFfr/2 * (1 - d/D * cosα)保持架旋转频率

其中n是滚动体数量,fr是转频,d是滚动体直径,D是节径,α是接触角。这些参数不一定每次都能从图纸拿到,但可以用包络谱上的峰值位置反推验证。一个正常的轴承,包络谱上不会出现明显的BPFO及其倍频成分;一旦外圈出现点蚀,包络谱在BPFO、2倍频、3倍频处会出现清晰的谱峰。

我们把这部分逻辑封装成一个独立模块,叫“物理先验检查器”:

def compute_bearing_frequencies(n_rollers, rpm, d, D, alpha): fr = rpm / 60.0 cos_alpha = math.cos(alpha) bpfo = n_rollers / 2 * fr * (1 - d / D * cos_alpha) bpfi = n_rollers / 2 * fr * (1 + d / D * cos_alpha) bsf = D / (2 * d) * fr * (1 - (d / D) ** 2 * cos_alpha ** 2) return {"BPFO": bpfo, "BPFI": bpfi, "BSF": bsf, "FTF": fr / 2 * (1 - d / D * cos_alpha)}

这里有一个容易忽略的点:特征频率公式给的是理论值,实际运行中转频可能有微小波动,所以检查时不能只看单独一个频率点,而要看理论值附近一个小频带内的能量,通常取±3Hz或者±1%相对带宽。我见过有人直接取精确频率点上的幅值做特征,结果转速一波动,特征就失效了。

2.2 物理信息注入模型的三级方案

物理约束和深度学习模型结合,我把它分成了三个级别,实际项目中可以组合使用。

级别一:物理特征作为模型输入。把包络谱特征频率处的能量、边带能量、RMS趋势、峭度等物理指标和原始窗口的频域特征拼接在一起,作为模型输入。这个方式最简单,也最不容易翻车。物理特征提供了强先验,模型只需要学会如何组合这些特征,而不是从零学习什么是故障。

级别二:物理正则化损失。在训练损失中增加一项物理约束,使得模型输出概率尽量贴近物理先验的判断。比如当物理先验根据谱峰能量判断“大概率正常”时,模型输出故障概率不能太高。理想情况下写成:

L = L_task + λ * L_phys

其中L_task是分类损失,L_phys是物理偏差损失。这里有个教训:λ不宜设太大,否则模型会完全不学习数据信息,只跟着物理规则走,泛化性反而变差。我们最终设的λ在0.1到0.3之间,用验证集调优。

级别三:推理阶段后置校验。模型输出概率之后,物理检查器作为“守护者”再复核一次。如果模型给出高故障概率,但包络谱上特征频率处没有明显能量峰,就把输出概率往下压。反过来,如果包络谱有明显故障特征,但模型概率偏低,可以提升报警等级,同时提示“物理特征异常”。

我们最终推荐的是级别一加级别三,级别二作为辅助。原因很简单:工业现场需要可解释的结论,后置校验可以直接对照谱图给工程师解释“为什么报警”,而纯模型输出没法做这个事。

def physics_guard(model_prob, spectrum, rpm, params, thresholds): freq_map = compute_bearing_frequencies(...) bpfo_energy = band_energy(spectrum, freq_map["BPFO"], width=3.0) if model_prob > 0.8 and bpfo_energy < thresholds["low_energy"]: return min(model_prob, 0.35) # 模型说故障但物理不支持,降级 return model_prob

这里要注意后置校验的尺度:它的作用是拦截明显荒谬的输出,不是替代模型。工业现场的故障本来就有早期微弱期,物理特征不明显,模型也许能从统计特征中发现隐患,后置校验不能一刀切把这类样本全部压死。

2.3 现场工况边界的物理约束

除了频率特征以外,工况边界是另一种物理约束。设备有额定转速范围、负荷范围、温度范围。模型训练时覆盖的工况范围是有限的,现场运行如果越界,模型就应该主动声明“超出我的能力域”,而不是硬给一个置信度很高的结论。

我们实现了一个简单的域检查器:实时采样转速、负荷、温度等工况参数,与训练集覆盖的区间对比。如果某个参数超出训练区间的5%以上,就给结果打上“out-of-domain”标签,并在告警界面提示“当前工况未覆盖,结果仅供参考”。这个功能看起来不炫酷,但能避免很多误报。

3. 不平衡样本的处理与模型训练

3.1 先处理数据:重采样、增强、伪样本

不平衡样本处理有三个层面,需要按顺序做。

数据层面,最常用的重采样方法包括SMOTE、ADASYN、欠采样等。但我要提醒一句:工业信号不要直接在原始波形上做SMOTE。振动信号是时域波形,直接线性插值制造出来的样本在物理上是无意义的,模型学到的是“插值伪影”。正确的做法是先提取特征(频域特征、统计特征、包络谱特征),在特征空间做SMOTE,或者在频谱幅度上做合成。

信号增强方面,我们验证过几种有效方法:加高斯白噪声、小幅时间轴缩放、幅值调制、窗口Mixup。其中Mixup需要注意,工业信号混合两个不同状态窗口会产生“中间态”,这个中间态在物理上可能不存在,所以只能作为一种正则化手段,增强量要控制。我们实际用的增强组合是:加噪(概率0.3)、时间缩放(概率0.2)、Mixup(概率0.2)。

机理仿真生成伪样本也是一个值得考虑的方向。借助轴承动力学模型、转子动力学模型,可以生成不同故障程度、不同工况下的仿真信号。这类样本物理含义明确,特别适合作为预训练数据,让模型先学会基本映射,再用真实数据微调。但是仿真数据和真实数据的域差异是客观存在的,不能指望仿真样本替代真实样本。

3.2 算法层面:代价敏感学习与阈值移动

数据层面处理完之后,算法层面还有两招。

一招是代价敏感损失,最常用的是focal loss。它的核心思想是让模型更关注难分类样本,减少容易分类的正常样本对损失的压制。参数设置上,γ取2.0,α根据正负样本比例调整。工业场景中,我建议不要只看单一指标调参,要同时看故障类召回率(漏报)和正常类误报率。

另外一个更简单、经常被忽略的方法:阈值移动。很多项目训练完模型直接拿0.5当判定阈值,忽略了一个事实——数据不平衡时,最优阈值往往不在0.5。训练完模型后,在验证集上搜索让业务指标最优的阈值,这个操作几乎零成本,却能带来显著提升。

# 训练完成后做阈值搜索 thresholds = np.arange(0.1, 0.9, 0.05) best_t, best_score = 0.5, 0 for t in thresholds: pred = (prob >= t).astype(int) # 在验证集上计算漏报率与误报率的加权得分 score = business_metric(y_true, pred, weight_fn) if score > best_score: best_t, best_score = t, score

这里还有一个关键点:无论是focal loss还是阈值移动,都要先固定验证集划分,否则阈值会过拟合。后面会专门讲划分陷阱。

3.3 评估指标与数据集划分陷阱

不平衡数据评估,最忌讳看准确率。我建议固定关注四类指标:故障召回率(Recall)、误报率(False Alarm Rate)、F1、G-mean。其中G-mean = sqrt(正常类召回率 * 故障类召回率),对两类数据都比较均衡。现场最关心的是故障类召回率,因为漏掉一次故障可能造成重大损失。

指标适用场景注意事项
准确率类别均衡时不平衡下完全失真
F1类别不均衡对少数类变化敏感
G-mean要求两类性能均衡适合故障诊断
故障召回率现场最关心漏报代价大

数据集划分是更大的坑。工业连续采样信号具有时间相关性,同一个时间段内、同一次运行状态下的前后窗口高度相似。如果用随机划分,同一个连续信号段的前半个窗口分到训练集、后半个窗口分到验证集,相当于模型在考试时看到了“邻居的答案”,验证指标虚高,部署现场一测就崩。

正确做法是划分时以设备为单位或者以连续时间块为单位。比如一共有10台设备,用8台的完整数据训练,2台的数据验证;或者按时间顺序,前70%时间段训练,中间15%验证,最后15%测试。这样划分出来的指标才真正代表模型面对新设备、新时间段时的表现。

4. 工程化组织:数据、训练、测试的框架选型

4.1 用PyTorch搭建数据管线与训练流程

工业信号数据量虽然大,但结构相对简单,用PyTorch的标准数据管线足够。Dataset类负责从原始波形目录加载数据,做切片、特征提取、标准化,DataLoader负责批量加载和多进程并行。一个容易踩的坑是num_workers设置不当导致CPU打满、GPU饥饿。经验值是:CPU核数的一半到三分之二,配合prefetch_factor=4左右效果比较稳定。

训练配置用YAML文件管理,而不是在代码里写死。窗口长度、采样率、特征选择、物理正则系数、类权重全部放配置里。这样每次实验有迹可循,不会出现“昨天还能复现,今天忘记改了哪个超参”的情况。实验记录可以用MLflow,也可以自己维护实验日志表,关键是把每个配置对应的模型指标、数据切分版本、特征版本记录下来。

4.2 用pytest守住数据处理的底线

这个项目里,pytest不是用来测模型的,而是用来测数据处理函数的。工业信号处理函数链路上容易出错的地方太多了:重采样导致时间轴对不上、FFT窗口没有加窗函数导致频谱泄漏、标准化参数不一致导致特征漂移。每一点都可能让模型在训练时看着还行,部署后完全失灵。

我们给每个关键函数都写了单测,比如特征频率计算、包络谱提取、标准化器、数据切分逻辑。部署上线前跑一遍全量测试,确保环境变更没有破坏数据链路。

pytest tests/ -k "signal or dataset or standardization"

用跑完的结果确认数据链路一致,再谈训练模型。这比模型结构本身重要得多。

4.3 代码结构别贪多,分层清晰最重要

很多团队写这类项目喜欢把训练、推理、API服务全堆在一个脚本里,前期方便,后期维护非常痛苦。我们最终采用的代码结构很简单:

project/ ├── configs/ # YAML配置 ├── data/ # Dataset、预处理、数据增强 ├── features/ # 特征提取、包络谱、物理指标 ├── models/ # 网络结构、损失函数 ├── physics/ # 特征频率计算、物理检查器 ├── tests/ # pytest测试 ├── deploy/ # ONNX导出、推理服务 └── train.py # 训练入口

没有引入微服务,也不需要云端编排,目录分层就是最简单的“框架”。如果只是部署一套诊断系统,微服务只会增加运维负担。后台告警平台如果要用成熟框架做MES集成,可以单独拆一个Web服务,但实时推理管线没必要跟着一起拆。

5. 实时部署框架的完整实践

5.1 从PyTorch到ONNX再到TensorRT的模型加速路线

训练阶段用PyTorch完全没问题,部署阶段就要换引擎了。我们的标准流程是:PyTorch训练 → 导出ONNX → 在目标设备上用ONNX Runtime或TensorRT推理。导出ONNX时要注意几个细节:输入输出名称固定、动态轴只在batch维度开、不要用Python自定义算子。

torch.onnx.export( model, dummy_input, "fault_diagnosis.onnx", input_names=["window"], output_names=["prob"], dynamic_axes={"window": {0: "batch"}, "prob": {0: "batch"}}, opset_version=13 )

量化方面,FP16通常是振动信号模型的甜点。INT8虽然能把模型压得更小、推理更快,但振动信号动态范围大、故障特征往往藏在微妙幅度差异里,直接量化容易掉点。如果一定要INT8,需要用真实故障样本做校准集,不能拿随机噪声或者正常信号来校准。我们实测过一个一维CNN模型,FP16精度几乎无损,INT8在某些故障类别上召回率掉了5个百分点,最终没敢上INT8。

5.2 流式推理的并发设计与延迟预算计算

工业振动采样是源源不断的,推理不能是“凑齐一批处理一批”的批处理模式,而是流式的。一块环形缓冲区保存最近N个窗口的数据,采集线程持续写入,推理线程按固定节奏取出最新窗口做推理。

# 流式推理循环伪代码 buffer = RingBuffer(capacity=10) last_processed = 0 while running: new_data = collector.read_new_samples() buffer.push(new_data) if buffer.latest_window_id() > last_processed: window = buffer.get_window(last_processed) t0 = time.perf_counter() prob = ort_session.run(None, {"window": window[None, ...]}) latency_ms = (time.perf_counter() - t0) * 1000 if latency_ms > latency_budget: metrics.record_timeout() result = physics_guard(prob, window, rpm, ...) alarm_channel.push(result) last_processed += 1

这里有一个实际计算例子。采样率20480Hz,窗口1024点,窗口时长约50ms。采集线程每50ms产生一个新窗口,推理线程必须在这个周期内完成推理,否则延迟会不断累积。假设模型推理25ms,物理检查器5ms,总耗时30ms,能留出20ms余量处理抖动。这个预算比例是安全的。如果推理超过80ms,就得考虑减小模型、量化、或者降低窗口重叠率。

延迟预算的计算公式很简单:总周期 = 采集窗口时长 + 处理时间 + 传输时间。只要处理时间明显小于窗口时长,系统就能稳定运行。我建议在部署环境里专门写一个延迟监控,打点记录每次推理耗时,超过预算就报警,否则延迟问题是“不跑三天根本看不出来”的暗雷。

5.3 可靠性设计:看门狗、影子模式、数据留存

实时部署里最怕的不是模型效果差,而是推理进程悄悄死掉。模型挂了几小时,系统静默,看起来“没有报警”,实际已经失去监控能力。我们给部署框架加了一个看门狗:推理主循环每隔5秒写一次心跳文件,外部守护进程检查心跳,如果超时未更新,就自动重启推理进程,同时把告警切换到后备规则引擎。

新模型上线不要直接接管报警。先跑影子模式,模型计算结果只记录不对外发送,与现有规则引擎结果做对比。跑一到两周,确认模型报警和人工确认结果一致率达标后,才切到主动报警。这一步能避开绝大多数“模型训练时正常,部署后乱报”的问题。

告警数据留存也必须有策略:平时只保存特征和模型输出,一旦触发报警,把触发前后各5秒的原始波形保存下来。这样事后分析有据可查。振动信号每秒20万个采样点,如果全量保存,一块1TB硬盘几天就满了,所以必须“平时丢,报警存”。

6. 常见问题与排查技巧实录

6.1 训练时指标很好,部署后“失灵”怎么办

这个问题十有八九出在三个环节:数据泄露、预处理不一致、工况分布漂移。

数据泄露是划分不当造成的,解决方法见3.3,按设备或时间块重新划分。预处理不一致是因为训练时用了离线全量标准化,部署时用了实时累计标准化,两边均值和方差不一样。解决办法是训练时就把标准化参数固定下来,部署时加载同样的参数。工况分布漂移则需要域检查器提示,超出训练域时不要直接采信模型结果。

给一个排查清单:先确认采样率是否一致,再确认特征提取参数是否一致,再确认输入顺序是否一致,最后看模型输出的概率分布是否和训练验证集分布接近。这四项检查完,大部分“部署失灵”都能定位。

6.2 推理延迟抖动与进程卡死

推理延迟偶尔高一下,不算大事;持续抖动甚至卡死,多半和资源竞争有关。工业网关上往往同时跑着数据采集、HMI、MES上报等一堆程序,CPU抢占严重。

我们实测有效的处理手段包括:给推理线程绑CPU核心,设置较高的线程优先级,关闭动态设备调频;尽量避免推理引擎动态申请显存或堆内存,预先分配好缓冲区;TensorRT的推理引擎固定输入尺寸,不要用动态shape,动态shape会显著增加延迟不稳定。

如果进程彻底卡死,先看是不是死锁:检查环形缓冲区读写两侧的操作是否都释放了锁。工业场景里最常犯的错误是采集线程持锁等待用户中断,推理线程等锁超时导致整个流程停摆。解决方式是缓冲区操作简洁化,不加冗余锁。

6.3 误报太多,现场对系统失去信任怎么办

工业现场比漏报更致命的问题其实是误报。误报多了,操作员就会习惯性忽略所有报警,真故障来了也错过。我们把报警设计成了三级体系:提示、预警、报警。轻量级规则和模型输出都满足条件时才发报警,只有物理特征明显且模型高置信度时才进入最高级报警。中间状态只发提示,不打扰现场人员。

物理检查器在这里也起到重要作用。模型给出高故障概率,但物理特征依据不足时,自动降级为“预警”而不是“报警”,并把原因写清楚:“模型发现统计特征偏离正常,但频谱中未见明显故障特征频率成分,建议关注。”这种输出方式,现场工程师看了是认可的,比单纯一个红色弹窗有说服力得多。

最后分享一点个人体会

这类框架项目的本质,不是训练一个高精度模型,而是搭一套让现场工程师敢关掉人工盯屏的系统。我自己的经验是:从项目一开始就要把设备工程师拉到一起,确认报警的可信度要求、现场的工况变化范围、告警的响应流程,这些信息对框架设计的影响比模型结构大得多。

另外,系统里放一个“传感器自检通道”是有奇效的,如果探头断了线、卡了涩,直接输出“传感器异常”而不是“设备故障”。这一个功能比把F1从0.95提升到0.98更有价值。让系统知道什么时候该承认自己不可用,是工业现场对AI最大的信任来源。这一点,在很多论文里看不到,但每一个做过现场交付的人都会懂。

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

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

立即咨询