20160227 异常检测代码算法

Xmia 9eed84e8f6 Initial commit: anomaly detection module 1 month ago
models 9eed84e8f6 Initial commit: anomaly detection module 1 month ago
README.md 9eed84e8f6 Initial commit: anomaly detection module 1 month ago
__init__.py 9eed84e8f6 Initial commit: anomaly detection module 1 month ago
config.py 9eed84e8f6 Initial commit: anomaly detection module 1 month ago
data_loader.py 9eed84e8f6 Initial commit: anomaly detection module 1 month ago
detect.py 9eed84e8f6 Initial commit: anomaly detection module 1 month ago
labeler.py 9eed84e8f6 Initial commit: anomaly detection module 1 month ago
train.py 9eed84e8f6 Initial commit: anomaly detection module 1 month ago

README.md

风机异常检测项目

基于机型的风机运行异常检测系统,支持数据打标、模型训练、推理检测、结果入库全流程。


目录结构

anomaly_detection/
├── config.py             # 路径 & 超参配置(所有可调参数集中在此)
├── data_loader.py        # parquet 读取,测点缺失容错
├── labeler.py            # 状态打标模块(运行/限功率/停机/传感器异常)
├── train.py              # 训练入口(按机型)
├── detect.py             # 推理入口(逐点结果写数据库,9个检测器并行)
├── saved_models/         # 训练后模型自动保存至此(按机型子目录)
│   └── {model_name}/
│       ├── wind_power_curve.pkl
│       ├── wind_power_scatter.pkl
│       ├── yaw_static.pkl
│       ├── yaw_twist.pkl
│       ├── pitch_regulation.pkl
│       ├── pitch_coord.pkl
│       ├── pitch_min.pkl
│       ├── ctrl_power_quality.pkl
│       ├── ctrl_op_state.pkl
│       └── model_stats.pkl       # 训练时保存的统计值,推理时直接加载
└── models/
    ├── wind_power.py     # 风速-功率异常(PowerCurve: 分段z-score逐点 / Scatter: IsolationForest)
    ├── yaw.py            # 偏航系统异常(IsolationForest,含短/长窗口滚动特征)
    ├── pitch.py          # 变桨系统异常(LOF自适应 / IsolationForest)
    └── control_params.py # 风机运行状态综合异常(IsolationForest)

使用流程

Step 1. 安装依赖

pip install pyarrow scikit-learn joblib pandas numpy

Step 2. 配置路径和参数

编辑 config.py,至少修改以下三项(标有 TODO):

# parquet 数据根目录,目录结构: {PARQUET_ROOT}/{model_name}/{farm_name}/{turbine_name}.parquet
PARQUET_ROOT   = Path("/your/data/path")

# 模型保存目录,训练后按机型自动创建子目录
MODEL_SAVE_DIR = Path("/your/model/path")

# 结果数据库,默认 SQLite 文件路径,生产环境可替换为数据库连接字符串
DB_PATH        = "/your/result.db"

parquet 目录结构要求:

PARQUET_ROOT/
└── EN156-3300/          ← 机型名称(model_name)
    └── 某风场/           ← 风场名称(farm_name)
        ├── 001.parquet  ← 风机名称(turbine_name = 001)
        └── 002.parquet

parquet 文件需包含 data_time 列(时间戳),检测时按此列过滤目标日期数据。

其他可选调整(均有默认值,不改也能运行):

  • ISO_CONTAMINATION:预期异常比例,默认 0.01(1%),数据质量差时可适当调大
  • WIND_VALID_MIN/MAX:有效发电风速区间,默认 3~25 m/s
  • STATUS_MODEL_SPECIAL_RULES:按机型添加特殊打标规则,如某机型切入风速不同

Step 3. 训练模型

anomaly_detection/ 目录下执行:

# 查看所有可用机型
python train.py --list

# 训练所有机型
python train.py

# 只训练指定机型
python train.py --model EN156-3300

训练过程输出示例:

==================================================
开始训练机型: EN156-3300
  [自适应统计] 额定: 3300.0kW | 基准桨距: 1.23°
  [训练数据] wind_power_curve: 运行85000 + 传感器异常可用1200 = 86200 行
  [风速功率] 功率曲线模型已保存
  ...
机型 EN156-3300 训练完成,模型保存至: /your/model/path/EN156-3300

测点缺失的检测器会自动跳过,不影响其他检测器训练。

Step 4. 运行检测

检测默认读取前1天的数据(按 data_time 列过滤):

# 检测所有机型(昨天数据)
python detect.py

# 只检测指定机型
python detect.py --model EN156-3300

# 指定检测日期
python detect.py --date 2026-02-24

检测过程输出示例:

结果数据库: /your/result.db
检测日期:   2026-02-24

机型 EN156-3300: 共 50 台风机,检测日期 2026-02-24
  检测: 某风场 / 001
  某风场/001: 总144 | 运行108 | 传感器异常6 | 停机/限功率30
    [wind_power_curve] 检测 110 点,异常 2 点
    [wind_power_scatter] 检测 110 点,异常 5 点
    [yaw_static] 检测 108 点,异常 3 点
    ...

Step 5. 查看结果

结果写入 DB_PATH 指定的 SQLite 数据库,表名 anomaly_points,每行对应一个时间戳×检测器:

import sqlite3
import pandas as pd

conn = sqlite3.connect("/your/result.db")

# 查看某台风机某天的所有异常点
df = pd.read_sql("""
    SELECT data_time, detector, result_type, is_anomaly, anomaly_score, anomaly_label
    FROM anomaly_points
    WHERE turbine_name = '001'
      AND data_time LIKE '2026-02-24%'
      AND is_anomaly = 1
    ORDER BY data_time
""", conn)

# 统计某天各风机异常点数(按检测器)
df_summary = pd.read_sql("""
    SELECT model_name, farm_name, turbine_name, detector,
           COUNT(*) AS total_points,
           SUM(is_anomaly) AS anomaly_count,
           ROUND(1.0 * SUM(is_anomaly) / COUNT(*), 4) AS anomaly_ratio,
           MIN(anomaly_score) AS min_score
    FROM anomaly_points
    WHERE data_time LIKE '2026-02-24%'
      AND result_type = 'anomaly_detection'
    GROUP BY model_name, farm_name, turbine_name, detector
    ORDER BY anomaly_ratio DESC
""", conn)

# 查看传感器异常点
df_sensor = pd.read_sql("""
    SELECT data_time, farm_name, turbine_name, detector, anomaly_label
    FROM anomaly_points
    WHERE result_type = 'sensor_anomaly'
      AND data_time LIKE '2026-02-24%'
    ORDER BY turbine_name, data_time
""", conn)

conn.close()

Step 6. 重新训练(模型调优)

# config.py

# 调整异常比例(数据中实际异常点多时调大)
ISO_CONTAMINATION = 0.02

# 调整有效风速区间(切入风速不同的机型)
WIND_VALID_MIN = 2.5

# 调整打标阈值
STATUS_CURTAIL_PITCH_OFFSET = 5.0  # 限功率判定更严格

重新训练会覆盖已有模型文件,无需手动删除。


数据打标逻辑

打标由 labeler.py 实现,状态优先级从高到低:

优先级 状态 判断条件
1(最高) 传感器异常-xxx异常 任意传感器异常列为 True
2 停机 功率值正常且 ≤ max(10kW, 额定×0.5%)
3 限功率 功率在 [2%, 95%] 额定区间 且 桨距角 > 基准+3°
4(默认) 运行 其余所有点

传感器异常类型:

异常列 含义
d_val_power 功率值越界(> 额定×1.25 或 < 额定×-0.1)
d_val_wind 风速值越界(> 75 或 < -2 m/s)
d_val_pitch 变桨值越界(> 105° 或 < -10°)
d_val_spd 转速值越界(> 历史最大×1.25)
d_val_torque 扭矩值越界(> 历史最大×1.25 或 < -2000)
d_logic_wind_pwr 风速-功率逻辑悖论(风速<0.1 但功率>100kW)
d_logic_torque_pwr 转速-扭矩逻辑悖论(有功率但扭矩接近0)

所有阈值均在 config.py 中可配置,支持按机型添加特殊规则(STATUS_MODEL_SPECIAL_RULES)。


model_stats.pkl 存储与调用逻辑

model_stats.pkl 保存机型级自适应统计阈值,训练时生成,推理时直接加载,避免推理阶段重新读取全量数据。

训练时(train.py)

load_model_type()          ← 加载机型全量 parquet 数据
    │
    ▼
get_model_statistics()     ← 计算自适应统计值
    │   p_max_observed  = p_active 的 99.5% 分位数(近似额定功率)
    │   baseline_pitch  = 20%~60% 额定功率区间内 pitch_ang_act_1 的中位数(基准桨距角)
    │   spd_limit       = gen_spd 的 99.9% 分位数(转速上限基准)
    │   torque_limit    = actual_torque 的 99.9% 分位数(扭矩上限基准)
    │
    ▼
joblib.dump(stats, model_dir / "model_stats.pkl")   ← 保存至机型模型目录

推理时(detect.py)

detect_model_type()
    │
    ├── model_stats.pkl 存在
    │       YES → joblib.load(stats_path)          ← 直接加载,毫秒级
    │       NO  → load_model_type() + get_model_statistics()
    │              (回退方案,建议重新训练生成 pkl)
    │
    ▼
detect_turbine()
    │
    ▼
label_dataframe(df_raw, model_stats, model_name)   ← 用 stats 计算打标阈值

stats 字段用途

字段 来源 用途
p_max_observed p_active 99.5% 分位数 功率越界判断(×1.25上限、×-0.1下限)、停机阈值(×0.5%)、限功率区间(×2%~95%)
baseline_pitch 中载区间桨距角中位数 限功率判断(基准桨距角 + 3° 偏移)
spd_limit gen_spd 99.9% 分位数 转速越界判断(×1.25上限)
torque_limit actual_torque 99.9% 分位数 扭矩越界判断(×1.25上限)、转速-扭矩逻辑悖论判断

训练数据筛选规则

每个检测器只使用与自身相关的干净数据:

  • 运行 数据:全部纳入训练
  • 传感器异常 数据:该检测器关心的测点无异常时纳入训练
  • 停机 / 限功率:不参与训练
DETECTOR_SENSOR_COLS = {
    "wind_power_curve":   ["d_val_wind", "d_val_power", "d_logic_wind_pwr"],
    "wind_power_scatter": ["d_val_wind", "d_val_power", "d_logic_wind_pwr"],
    "yaw_static":         [],   # 无关联传感器列,传感器异常数据全部纳入
    "yaw_twist":          [],
    "pitch_regulation":   ["d_val_pitch"],
    "pitch_coord":        ["d_val_pitch", "d_val_spd", "d_val_power"],
    "pitch_min":          ["d_val_pitch"],
    "ctrl_power_quality": ["d_val_power"],
    "ctrl_op_state":      ["d_val_power", "d_val_spd", "d_val_pitch"],
}

检测数据筛选逻辑

检测时同样先对当天数据打标,再按状态分流处理:

当天数据(parquet)
    │
    ▼ label_dataframe()
    ├── 停机 / 限功率 ──────────→ 跳过,不输出任何记录
    │
    ├── 运行         ──────────→ 直接送入模型预测
    │                            result_type = anomaly_detection
    │
    └── 传感器异常
            │
            ├── 该检测器关心的测点有异常?
            │       YES → 直接标记为传感器异常
            │              result_type = sensor_anomaly, is_anomaly = 1
            │              anomaly_label = 传感器异常-xxx异常
            │
            └── 该检测器关心的测点无异常(或无关联测点)
                    └──→ 送入模型预测
                           result_type = anomaly_detection

每个检测器关心的传感器列(DETECTOR_SENSOR_COLS)决定了传感器异常数据的走向:

检测器 关联传感器列 传感器异常数据处理
wind_power_curve d_val_wind, d_val_power, d_logic_wind_pwr 相关列有异常→sensor_anomaly;否则→模型预测
wind_power_scatter d_val_wind, d_val_power, d_logic_wind_pwr 同上
yaw_static (无) 全部传感器异常数据→模型预测
yaw_twist (无) 全部传感器异常数据→模型预测
pitch_regulation d_val_pitch 相关列有异常→sensor_anomaly;否则→模型预测
pitch_coord d_val_pitch, d_val_spd, d_val_power 相关列有异常→sensor_anomaly;否则→模型预测
pitch_min d_val_pitch 相关列有异常→sensor_anomaly;否则→模型预测
ctrl_power_quality d_val_power 相关列有异常→sensor_anomaly;否则→模型预测
ctrl_op_state d_val_power, d_val_spd, d_val_pitch 相关列有异常→sensor_anomaly;否则→模型预测

9个检测器通过 ThreadPoolExecutor 并行执行,主线程统一批量写入数据库,避免 SQLite 锁冲突。


检测模块说明

1. 风速-功率异常(wind_power.py)

训练和预测均只使用有效发电区间(WIND_VALID_MIN=3.0 ~ WIND_VALID_MAX=25.0 m/s)数据,排除切入/切出段噪声。

检测器 算法 特征 检测目标
PowerCurveDetector z-score(逐点,分段阈值) 按风速分箱统计 (mean, std),逐点计算功率偏离度 功率曲线偏移或单点功率异常;低风速段阈值放宽(4σ),中风速(3σ),高风速段收紧(2.5σ)
ScatterDetector IsolationForest (wind_spd, p_active, p_active/wind_spd³, ambient_temp可选) 单点偏离正常散点云,捕捉风能利用率偏低;温度特征反映空气密度影响

2. 偏航系统异常(yaw.py)

IsolationForest 替代原 DBSCAN,原生支持 fit/predict 分离,日推理数据量小时更稳定,且自带 anomaly score。不使用 wind_dir(存在与 yaw_err 数据互换的风险),仅依赖 yaw_angtwist_ang

检测器 算法 特征 检测目标
StaticYawDetector IsolationForest yaw_ang、短窗口滚动均值/标准差(2小时,12点)、长窗口滚动均值(12小时,72点) 偏航角瞬时偏离(短窗口)及持续慢漂移(长窗口)
CableTwistDetector IsolationForest twist_ang、绝对值、变化率 扭缆角度超限或变化异常(未及时解缆)

3. 变桨系统异常(pitch.py)

LOF 的 n_neighbors 自适应调整:max(5, min(20, len(train)//50)),避免小样本时退化。PitchCoordDetector 过滤转速 < 5 rpm 的点,避免低转速时功率/转速比噪声放大。

检测器 算法 特征 检测目标
PitchRegulationDetector LOF (novelty=True) 三桨叶设定值-实际值偏差、三桨叶不一致度(std)、变桨速度均值/不一致度(可选) 桨距角调节偏差过大或三桨叶不同步
PitchCoordDetector LOF (novelty=True) 三桨叶均值/不一致度、rotor_spd、p_active 及衍生比值(仅 rotor_spd > 5 rpm 的点) 变桨-转速-功率协调关系异常
MinPitchDetector IsolationForest 三桨叶最小值、均值、极差 最小桨距角偏离正常范围

4. 风机运行状态综合异常(control_params.py)

检测器 算法 特征 检测目标
PowerQualityDetector IsolationForest 实际/理论功率偏差比、功率因数、三相电流/电压不平衡度、频率偏差 电气侧功率质量异常(三相不平衡、频率偏差、功率因数偏低)
OperationStateDetector IsolationForest p_active、gen_spd、三桨叶均值/不一致度、twist_ang、功率/转速比 机械侧运行状态整体偏离正常模式

检测输出逻辑

每个时间戳 × 检测器输出一行,result_type 区分两类:

result_type 触发条件 is_anomaly anomaly_score anomaly_label
anomaly_detection 运行数据 + 传感器异常中测点干净的数据,经模型预测 0 或 1 模型输出分数 NULL
sensor_anomaly 传感器异常数据中,该检测器关心的测点有异常 恒为 1 NULL 传感器异常-xxx异常

停机 / 限功率数据不输出任何记录。

所有检测器的 predict 输出与输入数据等长(保留原始索引),特征缺失的点输出 anomaly=False, score=NaN


结果数据库表结构

表名:anomaly_points

字段 类型 说明
data_time TEXT 数据时间戳(来自 parquet 的 data_time 列)
model_name TEXT 机型名称
farm_name TEXT 风场名称
turbine_name TEXT 风机名称
detector TEXT 检测器名称
result_type TEXT anomaly_detection / sensor_anomaly
is_anomaly INTEGER 1=异常,0=正常
anomaly_score REAL 异常分数(越低越异常;sensor_anomaly 时为 NULL)
anomaly_label TEXT 传感器异常标签(anomaly_detection 时为 NULL)
detect_time TEXT 本次检测运行时间

索引:(turbine_name, data_time),加速按风机+时间查询。删除旧记录时同时匹配 farm_name,防止不同风场同名风机互删。

当前使用 SQLite,生产环境可在 detect.pyinit_db_bulk_insert 中替换为 PostgreSQL/MySQL。


异常分数说明

anomaly_score 来自各算法的 score_samples() 方法,含义是"该点属于正常分布的程度",越低越异常

算法 检测器 分数范围 说明
z-score wind_power_curve (-∞, +∞) 功率偏离 bin 均值的标准差倍数;低风速段 |z|>4、中风速 |z|>3、高风速 |z|>2.5 判异常
IsolationForest wind_power_scatter, yaw_static, yaw_twist, pitchmin, ctrl* 约 [-0.5, 0.5] 基于平均路径长度,< -0.1 开始值得关注
LOF pitch_regulation, pitch_coord 约 (-∞, -1] 局部离群因子取负,-1 表示完全正常

实际使用时以 is_anomaly=1 为主要判断依据,anomaly_score 用于排序优先级(分数越低 → 越需要优先排查)。


配置参数说明

所有参数集中在 config.py

参数 默认值 说明
ISO_CONTAMINATION 0.01 IsolationForest/LOF 预期异常比例
ISO_N_ESTIMATORS 100 IsolationForest 树数量
WIND_BIN_WIDTH 0.5 风速分箱宽度(m/s)
WIND_VALID_MIN 3.0 有效发电风速下限(m/s)
WIND_VALID_MAX 25.0 有效发电风速上限(m/s)
STATUS_POWER_UPPER_RATIO 1.25 功率越界上限倍数
STATUS_CURTAIL_PITCH_OFFSET 3.0 限功率桨距角偏移量(°)
STATUS_MODEL_SPECIAL_RULES {...} 机型特殊打标规则