control_params.py 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192
  1. """
  2. Module 4: 运行参数异常检测
  3. 算法: IsolationForest
  4. - 相比 OneClassSVM 对高维稀疏特征更稳定,训练速度更快
  5. - 两个检测器特征维度较高(电气侧多测点),IF 更合适
  6. 子检测器:
  7. A. PowerQualityDetector - 功率质量异常
  8. 测点: p_active, theory_p_active, p_reactive, grid_freq,
  9. grid_ia/ib/ic, grid_ua/ub/uc
  10. 检测: 三相不平衡、功率因数偏低、频率偏差、理论/实际功率偏差
  11. B. OperationStateDetector - 运行状态异常
  12. 测点: p_active, gen_spd, pitch_ang_act_1/2/3, twist_ang
  13. 检测: 转速-功率-桨距角-扭缆整体运行状态偏离正常模式
  14. """
  15. import pandas as pd
  16. import numpy as np
  17. from sklearn.ensemble import IsolationForest
  18. from sklearn.preprocessing import StandardScaler
  19. import joblib
  20. from pathlib import Path
  21. from config import (
  22. ISO_CONTAMINATION, ISO_RANDOM_STATE, ISO_N_ESTIMATORS,
  23. COL_P_ACTIVE, COL_ROTOR_SPD,
  24. COL_PITCH_ACT_1, COL_PITCH_ACT_2, COL_PITCH_ACT_3,
  25. COL_TWIST_ANG,
  26. )
  27. _GRID_CURR = ["grid_ia", "grid_ib", "grid_ic"]
  28. _GRID_VOLT = ["grid_ua", "grid_ub", "grid_uc"]
  29. # ── A. 功率质量检测器 ──────────────────────────────────────────────────────────
  30. class PowerQualityDetector:
  31. """
  32. 特征工程:
  33. - 理论/实际功率偏差比 (p_diff_ratio)
  34. - 功率因数近似 (p_active / sqrt(p_active^2 + p_reactive^2))
  35. - 三相电流不平衡度 (std/mean)
  36. - 三相电压不平衡度 (std/mean)
  37. - 电网频率偏差 (grid_freq - 50)
  38. 所有测点均为可选,存在则纳入特征,缺失则跳过。
  39. 至少需要 p_active + 任意一个辅助测点。
  40. """
  41. def __init__(self, contamination: float = ISO_CONTAMINATION):
  42. self.scaler = StandardScaler()
  43. self.model = IsolationForest(
  44. n_estimators=ISO_N_ESTIMATORS,
  45. contamination=contamination,
  46. random_state=ISO_RANDOM_STATE,
  47. )
  48. def _features(self, df: pd.DataFrame) -> pd.DataFrame:
  49. feat = {}
  50. # 理论/实际功率偏差比
  51. if "theory_p_active" in df.columns and COL_P_ACTIVE in df.columns:
  52. denom = df["theory_p_active"].replace(0, np.nan)
  53. feat["p_diff_ratio"] = (df[COL_P_ACTIVE] - df["theory_p_active"]) / denom
  54. # 功率因数近似(需要有功+无功)
  55. if COL_P_ACTIVE in df.columns and "p_reactive" in df.columns:
  56. apparent = np.sqrt(df[COL_P_ACTIVE] ** 2 + df["p_reactive"] ** 2)
  57. feat["power_factor"] = df[COL_P_ACTIVE] / apparent.replace(0, np.nan)
  58. # 三相电流不平衡度
  59. curr_cols = [c for c in _GRID_CURR if c in df.columns]
  60. if len(curr_cols) >= 2:
  61. curr = df[curr_cols]
  62. mean_c = curr.mean(axis=1).replace(0, np.nan)
  63. feat["curr_imbalance"] = curr.std(axis=1) / mean_c
  64. # 三相电压不平衡度
  65. volt_cols = [c for c in _GRID_VOLT if c in df.columns]
  66. if len(volt_cols) >= 2:
  67. volt = df[volt_cols]
  68. mean_v = volt.mean(axis=1).replace(0, np.nan)
  69. feat["volt_imbalance"] = volt.std(axis=1) / mean_v
  70. # 频率偏差
  71. if "grid_freq" in df.columns:
  72. feat["freq_dev"] = df["grid_freq"] - 50.0
  73. if not feat:
  74. return pd.DataFrame()
  75. return pd.DataFrame(feat, index=df.index).replace([np.inf, -np.inf], np.nan).dropna()
  76. def fit(self, df: pd.DataFrame) -> "PowerQualityDetector":
  77. feat = self._features(df)
  78. if feat.empty or len(feat.columns) < 2:
  79. raise ValueError("功率质量特征不足(至少需要 p_active + 一个辅助测点)")
  80. X = self.scaler.fit_transform(feat)
  81. self.model.fit(X)
  82. return self
  83. def predict(self, df: pd.DataFrame) -> pd.DataFrame:
  84. out = pd.DataFrame({"anomaly": False, "score": np.nan}, index=df.index)
  85. feat = self._features(df)
  86. if feat.empty:
  87. return out
  88. X = self.scaler.transform(feat)
  89. out.loc[feat.index, "anomaly"] = self.model.predict(X) == -1
  90. out.loc[feat.index, "score"] = self.model.score_samples(X)
  91. return out
  92. def save(self, path: Path):
  93. joblib.dump(self, path)
  94. @classmethod
  95. def load(cls, path: Path) -> "PowerQualityDetector":
  96. return joblib.load(path)
  97. # ── B. 运行状态综合检测器 ──────────────────────────────────────────────────────
  98. class OperationStateDetector:
  99. """
  100. 特征工程:
  101. - p_active(有功功率)
  102. - gen_spd(发电机转速)
  103. - 三桨叶实际桨距角均值、不一致度(std)
  104. - twist_ang(扭缆角度)
  105. - 功率/转速比(反映转矩状态)
  106. - 桨距角均值 × 转速(协调特征)
  107. 所有测点均为可选,p_active 为必须项。
  108. """
  109. def __init__(self, contamination: float = ISO_CONTAMINATION):
  110. self.scaler = StandardScaler()
  111. self.model = IsolationForest(
  112. n_estimators=ISO_N_ESTIMATORS,
  113. contamination=contamination,
  114. random_state=ISO_RANDOM_STATE,
  115. )
  116. def _features(self, df: pd.DataFrame) -> pd.DataFrame:
  117. if COL_P_ACTIVE not in df.columns:
  118. return pd.DataFrame()
  119. feat = {COL_P_ACTIVE: df[COL_P_ACTIVE]}
  120. if COL_ROTOR_SPD in df.columns:
  121. feat[COL_ROTOR_SPD] = df[COL_ROTOR_SPD]
  122. spd_safe = df[COL_ROTOR_SPD].replace(0, np.nan)
  123. feat["p_per_spd"] = df[COL_P_ACTIVE] / spd_safe
  124. # 三桨叶特征
  125. act_cols = [c for c in [COL_PITCH_ACT_1, COL_PITCH_ACT_2, COL_PITCH_ACT_3]
  126. if c in df.columns]
  127. if act_cols:
  128. pitch_df = df[act_cols]
  129. feat["pitch_mean"] = pitch_df.mean(axis=1)
  130. if len(act_cols) >= 2:
  131. feat["pitch_std"] = pitch_df.std(axis=1)
  132. if COL_ROTOR_SPD in df.columns:
  133. feat["pitch_x_spd"] = feat["pitch_mean"] * df[COL_ROTOR_SPD]
  134. if COL_TWIST_ANG in df.columns:
  135. feat[COL_TWIST_ANG] = df[COL_TWIST_ANG]
  136. result = pd.DataFrame(feat, index=df.index)
  137. return result.replace([np.inf, -np.inf], np.nan).dropna()
  138. def fit(self, df: pd.DataFrame) -> "OperationStateDetector":
  139. feat = self._features(df)
  140. if feat.empty or len(feat.columns) < 2:
  141. raise ValueError("运行状态特征不足(至少需要 p_active + 一个辅助测点)")
  142. X = self.scaler.fit_transform(feat)
  143. self.model.fit(X)
  144. return self
  145. def predict(self, df: pd.DataFrame) -> pd.DataFrame:
  146. out = pd.DataFrame({"anomaly": False, "score": np.nan}, index=df.index)
  147. feat = self._features(df)
  148. if feat.empty:
  149. return out
  150. X = self.scaler.transform(feat)
  151. out.loc[feat.index, "anomaly"] = self.model.predict(X) == -1
  152. out.loc[feat.index, "score"] = self.model.score_samples(X)
  153. return out
  154. def save(self, path: Path):
  155. joblib.dump(self, path)
  156. @classmethod
  157. def load(cls, path: Path) -> "OperationStateDetector":
  158. return joblib.load(path)