4、基于SCADA数据的异常检测:特征工程、阈值设定、孤立森林算法实战

各位同行,大家好。今天咱们聊点实在的——怎么用SCADA数据做异常检测。

说实话,我在风场干了快十年,见过太多“事后诸葛亮”的运维。齿轮箱都冒烟了,报警才响。这能叫智能运维吗?我个人习惯是,把功夫花在数据上,让算法替我们“盯”着机组。

这一章,我就把压箱底的经验掏出来。从特征工程到阈值设定,再到孤立森林实战,咱们一步步走通。

4.1 特征工程:别让算法“吃”垃圾

做异常检测,最怕什么?最怕数据本身就有问题。你想想看,传感器漂移、通讯中断、停机维护……这些脏数据喂进去,再牛的算法也得翻车。

第一步:清洗数据

我一般先做三件事:

  • 剔除停机段:发电机有功功率 < 10kW 的,直接扔掉。机组都没转,谈何异常?
  • 处理缺失值:连续缺失超过5分钟,整段删除。短时间缺失,用前后均值填充。
  • 去野点:风速超过切出风速、转速超过额定值1.2倍……这些明显不合理的数据,必须干掉。
我的小技巧: 别用全局阈值。比如风速,不同季节、不同机位,分布差异很大。我习惯按月份、按机位分组做3σ剔除。

第二步:构造特征

原始SCADA数据就那么几个点:风速、转速、功率、温度。但异常往往藏在“关系”里。

我常用的特征有三类:

  • 物理特征:叶尖速比(λ = 叶尖线速度/风速)、桨距角变化率、发电机转速与风速的偏差。
  • 统计特征:过去10分钟的温度均值、方差、最大值、最小值。滑动窗口,我一般设10分钟,太长反应慢,太短噪声大。
  • 残差特征:用正常数据训练一个功率曲线模型(比如随机森林),然后算实际功率与预测功率的差值。这个差值,就是异常的信号。
重点: 残差特征是我最看重的。有一次,某台机组齿轮箱温度一直正常,但功率残差突然变大。我判断是变桨系统有卡涩,导致发电效率下降。拆开一看,果然。

4.2 阈值设定:别拍脑袋,要拍数据

阈值怎么设?很多新手直接取3σ。但风电机组的运行工况变化很大,一个固定阈值根本不够用。

动态阈值法

我推荐用“滑动窗口+百分位数”。比如:

  • 取过去7天同一时段的数据(比如每天10:00-11:00)
  • 计算该时段内某个特征(比如齿轮箱温度)的95%分位数
  • 当前值超过这个分位数,就报警

这样做的好处是,阈值会随着季节、风速变化自动调整。夏天温度高,阈值自动上浮;冬天温度低,阈值自动下降。

注意: 百分位数不能设得太高。99%分位数虽然误报少,但漏报风险大。我一般用95%作为预警线,99%作为报警线。

多维度阈值

单一特征报警,可能是误报。但如果多个特征同时越界,那基本就是真异常了。我习惯设一个“投票机制”:

  • 温度特征报警:+1分
  • 振动特征报警:+1分
  • 功率残差报警:+1分
  • 总分 ≥ 2,才触发告警

这一招,能把误报率降低70%以上。我曾经用这个规则,成功过滤掉了大量因风速突变导致的“假报警”。

4.3 孤立森林算法实战:让算法“孤立”异常

孤立森林(Isolation Forest)是我最喜欢的异常检测算法。为什么?因为它快,而且不需要假设数据分布。

算法原理(一句话)

异常点“鹤立鸡群”,容易被孤立。算法随机切分数据,异常点通常只需要很少的切分次数就能被孤立出来。切分次数越少,异常得分越高。

代码实战

下面是我在项目里用的一段代码,直接可以跑:

import pandas as pd
import numpy as np
from sklearn.ensemble import IsolationForest
import matplotlib.pyplot as plt

# 加载数据(假设已经清洗和特征工程)
data = pd.read_csv('scada_clean.csv')
features = ['wind_speed', 'rotor_speed', 'gen_temp', 'power_residual']

# 训练模型
model = IsolationForest(
    n_estimators=100,        # 树的数量,我一般设100
    max_samples='auto',      # 每棵树采样比例
    contamination=0.05,      # 预期异常比例,我设5%
    random_state=42
)
model.fit(data[features])

# 预测:-1为异常,1为正常
data['anomaly'] = model.predict(data[features])
data['score'] = model.decision_function(data[features])

# 输出异常点
anomalies = data[data['anomaly'] == -1]
print(f"发现 {len(anomalies)} 个异常点")
参数调优: contamination 这个参数很关键。设小了,漏报多;设大了,误报多。我一般先用无标签数据跑一遍,看异常得分分布,再根据业务容忍度调整。

结果可视化

光有数字不够,得画出来看。我习惯画一个“异常得分分布图”:

plt.figure(figsize=(10, 6))
plt.hist(data['score'], bins=50, alpha=0.7, label='正常')
plt.hist(anomalies['score'], bins=20, alpha=0.7, label='异常')
plt.axvline(x=0, color='red', linestyle='--', label='决策边界')
plt.xlabel('异常得分')
plt.ylabel('频次')
plt.legend()
plt.show()

得分越低,越可能是异常。如果得分分布有明显的“双峰”,说明模型效果不错。如果所有点挤在一起,那就要检查特征工程了。

4.4 知识体系总览

为了让大家看得更清楚,我画了一张图,把整个流程串起来:

SCADA异常检测知识体系 数据预处理 清洗 · 去野点 · 补缺失 特征工程 物理特征 · 统计特征 · 残差特征 阈值设定 动态阈值 · 多维度投票 孤立森林算法 训练 · 预测 · 异常得分 结果输出 异常点列表 · 可视化 · 告警 反馈优化 误报分析 · 参数调优 · 模型迭代 反馈闭环

这张图把整个流程串起来了。从数据预处理开始,到特征工程、阈值设定、孤立森林,最后输出结果。别忘了,还有一个反馈闭环——把误报和漏报分析清楚,反过来优化特征和参数。

4.5 避坑指南

最后,分享几个我踩过的坑:

  • 别用全部数据训练:我曾经用一整年的数据训练模型,结果模型把季节变化当成了异常。正确做法是,只用正常工况的数据训练。
  • 孤立森林不是万能的:如果异常点聚集在一起(比如整片风场同时限功率),孤立森林效果会变差。这时候需要结合其他方法。
  • 阈值要定期更新:机组老化后,正常运行的参数范围会漂移。我建议每季度重新校准一次阈值。
总结一句话: 异常检测不是一锤子买卖。特征工程是基础,阈值设定是艺术,孤立森林是工具。三者缺一不可。

好了,这一章就到这里。下一章咱们聊聊怎么用LSTM做时序预测,提前发现轴承退化。到时候见。


专注资料整理