model_predict.py 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108
  1. """Model prediction as a *reference* for fault segments (no verdict).
  2. For each fault segment produced by the rule algorithm, slide a 3-hour valid-run
  3. window ending at every valid hour of the segment and score it with the existing
  4. XGBoost model. Only aggregate probabilities are reported (mean / max / min);
  5. the model never decides whether a fault exists.
  6. Training is untouched: this module only reuses the same window builder and
  7. feature functions so the scored features match training exactly.
  8. """
  9. from __future__ import annotations
  10. import json
  11. from pathlib import Path
  12. import joblib
  13. import numpy as np
  14. from . import paths, windowing
  15. WINDOW_HOURS = 3
  16. MODEL_FIELDS = ["模型异常概率均值", "模型异常概率最大值", "模型异常概率最小值"]
  17. NOTE_FIELD = "模型预测参考"
  18. _BUNDLE_CACHE: dict[str, tuple] = {}
  19. def _assets(kind: str):
  20. if kind == "pressure":
  21. from models.generalized import features_pressure
  22. from models.generalized.build_datasets import pressure_features
  23. return features_pressure, pressure_features
  24. from models.vibration import features_vibration
  25. from models.vibration.build_datasets import vibration_features
  26. return features_vibration, vibration_features
  27. def load_bundle(kind: str):
  28. if kind in _BUNDLE_CACHE:
  29. return _BUNDLE_CACHE[kind]
  30. model_dir = paths.PRESSURE_MODELS if kind == "pressure" else paths.VIBRATION_MODELS
  31. model_path = model_dir / f"{kind}_model.joblib"
  32. meta_path = model_dir / f"{kind}_metadata.json"
  33. if not model_path.is_file() or not meta_path.is_file():
  34. pipeline = "generalized" if kind == "pressure" else "vibration"
  35. raise RuntimeError(
  36. f"未找到 {kind} 模型产物:{model_dir}。请先运行 "
  37. f"python models/{pipeline}/run_pipeline.py --window-hours 3"
  38. )
  39. bundle = joblib.load(model_path)
  40. metadata = json.loads(meta_path.read_text(encoding="utf-8"))
  41. _BUNDLE_CACHE[kind] = (bundle, metadata)
  42. return bundle, metadata
  43. def _window_index(hourly_rows: list[dict], kind: str) -> dict[str, list[tuple[str, dict]]]:
  44. _, feature_fn = _assets(kind)
  45. samples = windowing.build_samples(hourly_rows, kind, [], WINDOW_HOURS, feature_fn)
  46. by_unit: dict[str, list[tuple[str, dict]]] = {}
  47. for sample in samples:
  48. by_unit.setdefault(sample["unit"], []).append((sample["end_hour"], sample))
  49. for series in by_unit.values():
  50. series.sort()
  51. return by_unit
  52. def _probability(bundle, feature_module, sample) -> float:
  53. names = bundle["feature_names"]
  54. features = feature_module.generalized_features(sample, bundle["unit_baselines"])
  55. matrix = np.asarray([[features.get(name) for name in names]], dtype=float)
  56. matrix = bundle["imputer"].transform(matrix)
  57. return float(bundle["model"].predict_proba(matrix)[0, 1])
  58. def annotate(kind: str, hourly_rows: list[dict], segments: list[dict]) -> dict:
  59. """Add model probability aggregates + note to each segment in place."""
  60. bundle, _metadata = load_bundle(kind)
  61. feature_module, _feature_fn = _assets(kind)
  62. by_unit = _window_index(hourly_rows, kind)
  63. scored = 0
  64. for segment in segments:
  65. unit = segment.get("机组")
  66. start, end = segment.get("开始小时"), segment.get("结束小时")
  67. probabilities = []
  68. if start and end:
  69. for end_hour, sample in by_unit.get(unit, []):
  70. if start <= end_hour <= end:
  71. probabilities.append(_probability(bundle, feature_module, sample))
  72. if probabilities:
  73. mean = float(np.mean(probabilities))
  74. high = float(np.max(probabilities))
  75. low = float(np.min(probabilities))
  76. segment["模型异常概率均值"] = round(mean, 6)
  77. segment["模型异常概率最大值"] = round(high, 6)
  78. segment["模型异常概率最小值"] = round(low, 6)
  79. segment[NOTE_FIELD] = (
  80. f"模型预测参考:{WINDOW_HOURS}小时窗口异常概率 "
  81. f"均值{mean:.4f}/最大{high:.4f}/最小{low:.4f}({len(probabilities)}次)"
  82. )
  83. scored += 1
  84. else:
  85. segment[NOTE_FIELD] = "模型预测参考:无可用3小时窗口,数据不足"
  86. return {"kind": kind, "segments": len(segments), "scored": scored}
  87. __all__ = ["WINDOW_HOURS", "MODEL_FIELDS", "NOTE_FIELD", "load_bundle", "annotate"]