预测性维护中的数据标注:这是一门手艺活

预测性维护中的数据标注:这是一门手艺活
预测性维护中的数据标注这是一门手艺活去年夏天我在一个石化企业的压缩机监测项目里跟甲方的设备工程师吵了一架。事情是这样的我们的模型报了一台压缩机的轴承内圈故障置信度87%。甲方工程师看了一眼说这设备运行得好好的你们模型瞎报。我坚持把数据拉出来看发现振动频谱里确实有BPFI内圈故障特征频率的谐波成分只是幅值不高。两周后那台压缩机轴承真的烧了停机损失80万。事后复盘问题出在标注上。那台压缩机在故障前2周的数据到底该标成正常还是早期故障甲方之前标的是正常因为设备确实还在转。但模型学的是振动特征不是设备转没转。说白了预测性维护的标注不是简单的打标签而是在划定一条模糊的边界。今天我把这些年踩过的坑摊开聊。一、标注什么不是故障/正常这么简单很多人以为数据标注就是给样本贴个0或1的标签。但在预测性维护里这远远不够。我们团队在2024年初给山东一家做风电齿轮箱的厂商做项目最后定的标注体系是四层结构标签层级含义举例健康等级整体状态评估健康/亚健康/轻度退化/严重退化/故障故障模式具体故障类型内圈剥落/外圈裂纹/滚动体磨损/保持架断裂故障程度严重度量级轻微(0-30%)/中度(30-70%)/严重(70%)时间标签距故障发生的时间距故障T-30天/T-7天/T-1天/故障时刻有意思的是甲方一开始只愿意标正常/故障两类。我说你们想做的不是坏了告诉我而是提前告诉我什么时候会坏。如果不标时间标签模型永远学不到退化趋势只能做个二分类器那跟阈值报警有什么区别最后甲方被说服了但代价是标注工作量翻了4倍。一个熟练的标注工程师一天只能标完大概2小时的振动数据采样率10kHz也就是7200万条原始数据点。二、谁来标设备工程师 vs 数据科学家这是一个老大难问题。设备工程师懂机器但看不懂频谱数据科学家会看频谱但不知道这个幅值到底算不算异常。我们在2023年底的一个项目里试过双盲标注——让设备工程师和数据科学家分别独立标注同一批数据然后算一致性。结果惨不忍睹Kappa系数只有0.31连中等一致都算不上。分歧最大的地方是亚健康状态。设备工程师觉得设备还能跑标正常数据科学家看到频谱里有微弱的边频带想标轻度退化。两边都没错只是标准不一样。我们的解决方案是引入标注指南Annotation Guideline 定期校准会议。# 标注一致性检查脚本 # 用 Cohens Kappa 评估多标注者之间的一致性 from sklearn.metrics import cohen_kappa_score import numpy as np # 三位标注者对100条样本的标注结果 # 0健康, 1亚健康, 2轻度退化, 3严重退化, 4故障 annotator_a [0,0,1,2,0,1,2,3,4,0, ...] # 设备工程师 annotator_b [0,0,0,1,0,0,1,2,4,0, ...] # 数据科学家 annotator_c [0,1,1,2,0,1,2,3,4,1, ...] # 第三方专家 kappa_ab cohen_kappa_score(annotator_a, annotator_b) kappa_ac cohen_kappa_score(annotator_a, annotator_c) kappa_bc cohen_kappa_score(annotator_b, annotator_c) print(f工程师 vs 数据科学家: Kappa {kappa_ab:.3f}) print(f工程师 vs 第三方专家: Kappa {kappa_ac:.3f}) print(f数据科学家 vs 第三方专家: Kappa {kappa_bc:.3f}) # Kappa 0.4 说明一致性差需要重新对齐标注标准 if kappa_ab 0.4: print(⚠️ 一致性过低建议召开标注校准会议)踩坑提醒别用简单的投票法解决分歧——三个外行投票结果还是外行。我们的做法是Kappa低于0.4时把分歧样本拉出来现场听声音、看频谱、查维修记录三方一起定标准。这个过程很痛苦但标出来的数据质量天差地别。三、半自动标注让模型帮你标标注成本太高了必须想办法偷懒。我们用的路子是半自动标注先用一个粗糙的模型预标人工只审核和修正。2024年3月我们在一个水泥厂的回转窑监测项目里实现了这套流程。具体做法第一步用无监督方法做初筛。from sklearn.ensemble import IsolationForest from sklearn.preprocessing import StandardScaler import numpy as np # 提取的振动特征RMS、峰峰值、峭度、频带能量比等 features extract_features(vibration_signals) # shape: (n_samples, 16) # 标准化 scaler StandardScaler() features_scaled scaler.fit_transform(features) # Isolation Forest 检测异常 contamination 设为5%经验值 iso_forest IsolationForest( contamination0.05, random_state42, n_estimators200 ) outlier_labels iso_forest.fit_predict(features_scaled) # -1 异常, 1 正常 # 把异常样本挑出来优先人工标注 anomaly_indices np.where(outlier_labels -1)[0] print(f初筛出 {len(anomaly_indices)} 条疑似异常样本需要人工审核)第二步人工只标边界样本。Isolation Forest 筛出来的异常大部分确实是异常但模型置信度高的自动过只审置信度边缘的。我们设了一个阈值模型输出概率在0.3-0.7之间的样本必须人工审0.7以上或0.3以下的自动标。# 假设我们有一个粗糙的预训练模型 probas rough_model.predict_proba(features_scaled) # 只审核不确定的样本 uncertain_mask (probas.max(axis1) 0.7) (probas.max(axis1) 0.3) uncertain_indices np.where(uncertain_mask)[0] print(f需要人工审核: {len(uncertain_indices)} 条) print(f自动标注: {len(features) - len(uncertain_indices)} 条)这套流程下来人工标注工作量减少了大概70%而且因为优先标了最难的样本模型学得反而更快。注意这里有个细节Isolation Forest 的 contamination 参数不能瞎设。我们一开始按故障率1%设的结果漏了一大堆早期退化样本。后来改成5%召回率上去了但误标也多了——这就需要人工审核来兜底。没有完美的自动标注只有省人工的自动标注。四、时间对齐标注里最隐蔽的坑很多人忽略了时间对齐的问题。振动数据、温度数据、电流数据、维修记录——这些时间戳如果不统一标注就全是错的。2024年6月我们在一个钢厂的项目里栽了大跟头。振动传感器的时间是UTCSCADA系统的时间是本地时间东八区维修记录里的时间是工人手写的时间经常写错。结果我们标的一条故障前3天的样本实际上可能是故障前2小时也可能是故障后1天。更狠的是有些系统的时间戳不是绝对时间而是设备运行时间——从开机开始计时。如果设备中间停机了运行时间和 wall-clock time就对不上。我们的解决方案是写一个时间对齐检查脚本from datetime import datetime, timezone, timedelta import pandas as pd def check_timestamp_alignment(vib_df, scada_df, maintenance_df): 检查多数据源的时间对齐情况 issues [] # 1. 检查时区 for name, df in [(振动, vib_df), (SCADA, scada_df)]: if df[timestamp].dt.tz is None: issues.append(f{name}数据缺少时区信息) # 2. 检查时间范围重叠 vib_range (vib_df[timestamp].min(), vib_df[timestamp].max()) scada_range (scada_df[timestamp].min(), scada_df[timestamp].max()) overlap_start max(vib_range[0], scada_range[0]) overlap_end min(vib_range[1], scada_range[1]) if overlap_start overlap_end: issues.append(振动和SCADA数据没有时间重叠) else: overlap_days (overlap_end - overlap_start).days print(f时间重叠区间: {overlap_days} 天) # 3. 检查维修记录时间是否在数据范围内 for _, row in maintenance_df.iterrows(): maint_time pd.to_datetime(row[time]) if maint_time vib_range[0] or maint_time vib_range[1]: issues.append(f维修记录时间 {maint_time} 超出振动数据范围) return issues # 使用示例 issues check_timestamp_alignment(vibration_data, scada_data, maintenance_log) if issues: print(发现时间对齐问题:) for issue in issues: print(f - {issue}) else: print(✅ 时间对齐检查通过)踩过这个坑的人都懂时间对不上后面所有工作都是白干。我们那个钢厂项目因为时间对齐问题返工了整整一周。五、标注质量评估别等模型训完了才发现标错了标注质量怎么量化我们用的是标注一致性 模型反向验证的双保险。第一层多人标注一致性。前面说的Kappa系数低于0.4就返工。第二层模型反向验证。用标好的数据训一个简单模型比如随机森林然后看预测概率和标注标签的吻合度。如果模型对某个样本的预测概率极低比如标了故障但模型输出0.01那就把这个样本拉出来重新审。from sklearn.ensemble import RandomForestClassifier from sklearn.model_selection import cross_val_predict # 用简单模型做交叉验证预测 clf RandomForestClassifier(n_estimators100, random_state42) pred_probas cross_val_predict(clf, X, y, cv5, methodpredict_proba) # 找出模型强烈反对的标注样本 for i in range(len(y)): true_label y[i] predicted_prob pred_probas[i, true_label] # 如果模型认为该标签的概率 0.3说明标注可能有误 if predicted_prob 0.3: print(f样本 {i}: 标注{true_label}, 模型置信度{predicted_prob:.3f}) print( → 建议重新审核此样本的标注)这个方法的逻辑是如果简单的随机森林都觉得某个标注不对劲那大概率真的不对劲。当然也有可能是样本本身就很难但无论如何值得人工再看一眼。写在最后预测性维护的数据标注本质上是在把工程师的经验结构化。一个好的标注体系比一个好的模型更重要——因为模型可以重新训标错了的数据会一路把模型带偏。最后说两句标注标准必须写下来不能靠口头传。新加入的标注员必须先看指南再标数据。定期重新校准标注者的标准会漂移尤其是一个项目做了3个月以上的时候。别省标注的钱花1万块标好数据比花10万块训一个被脏数据带偏的模型划算得多。上个月和西门子的一个工程师聊过这个话题他说他们内部有个说法Garbage in, garbage out在预测性维护里不是警告是定律。深以为然。