|
|
1 month ago | |
|---|---|---|
| models | 1 month ago | |
| README.md | 1 month ago | |
| __init__.py | 1 month ago | |
| config.py | 1 month ago | |
| data_loader.py | 1 month ago | |
| detect.py | 1 month ago | |
| labeler.py | 1 month ago | |
| train.py | 1 month ago |
基于机型的风机运行异常检测系统,支持数据打标、模型训练、推理检测、结果入库全流程。
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)
pip install pyarrow scikit-learn joblib pandas numpy
编辑 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/sSTATUS_MODEL_SPECIAL_RULES:按机型添加特殊打标规则,如某机型切入风速不同在 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
测点缺失的检测器会自动跳过,不影响其他检测器训练。
检测默认读取前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 点
...
结果写入 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()
# 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 保存机型级自适应统计阈值,训练时生成,推理时直接加载,避免推理阶段重新读取全量数据。
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_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 计算打标阈值
| 字段 | 来源 | 用途 |
|---|---|---|
| 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 锁冲突。
训练和预测均只使用有效发电区间(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可选) | 单点偏离正常散点云,捕捉风能利用率偏低;温度特征反映空气密度影响 |
IsolationForest 替代原 DBSCAN,原生支持 fit/predict 分离,日推理数据量小时更稳定,且自带 anomaly score。不使用 wind_dir(存在与 yaw_err 数据互换的风险),仅依赖 yaw_ang 和 twist_ang。
| 检测器 | 算法 | 特征 | 检测目标 |
|---|---|---|---|
| StaticYawDetector | IsolationForest | yaw_ang、短窗口滚动均值/标准差(2小时,12点)、长窗口滚动均值(12小时,72点) | 偏航角瞬时偏离(短窗口)及持续慢漂移(长窗口) |
| CableTwistDetector | IsolationForest | twist_ang、绝对值、变化率 | 扭缆角度超限或变化异常(未及时解缆) |
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 | 三桨叶最小值、均值、极差 | 最小桨距角偏离正常范围 |
| 检测器 | 算法 | 特征 | 检测目标 |
|---|---|---|---|
| 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.py的init_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 | {...} | 机型特殊打标规则 |