147 lines
4 KiB
Python
147 lines
4 KiB
Python
"""
|
||
06_sead_details.py – Tiefenanalyse der Ensemble-Entscheidungen
|
||
==============================================================
|
||
Visualisiert pro Lauf:
|
||
- Oben: Entwicklung der SEAD-Gewichte (w_i) pro Detektor
|
||
- Unten: Einzel-Anomalie-Scores (s_i) und aggregierter Gesamt-Score
|
||
- Hintergrund: Ground-Truth Injektionsfenster
|
||
"""
|
||
|
||
import sys
|
||
|
||
import matplotlib.dates as mdates
|
||
import matplotlib.pyplot as plt
|
||
import numpy as np
|
||
import pandas as pd
|
||
|
||
sys.path.insert(0, ".")
|
||
from config import *
|
||
|
||
DETECTORS = ["MAD", "RRCF-fast", "RRCF-mid", "RRCF-slow", "COPOD"]
|
||
|
||
|
||
def _to_utc(ts):
|
||
ts = pd.Timestamp(ts)
|
||
return ts.tz_localize("UTC") if ts.tzinfo is None else ts.tz_convert("UTC")
|
||
|
||
|
||
def plot_sead_internals(run_key: str, windows_df: pd.DataFrame):
|
||
print(f" Analysiere SEAD-Internals für {run_key}...")
|
||
|
||
try:
|
||
anom = load_anomalies(run_key)
|
||
except Exception as e:
|
||
print(f" Fehler: {e}")
|
||
return
|
||
|
||
if anom.empty:
|
||
return
|
||
|
||
anom["timestamp"] = anom["timestamp"].apply(_to_utc)
|
||
inj = windows_df[windows_df["run"] == run_key].copy()
|
||
|
||
fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(16, 10), sharex=True)
|
||
|
||
colors = plt.cm.tab10(np.linspace(0, 1, len(DETECTORS)))
|
||
det_color_map = {det: colors[i] for i, det in enumerate(DETECTORS)}
|
||
|
||
# --- Subplot 1: SEAD Gewichte ---
|
||
for det in DETECTORS:
|
||
col = f"{det}_weight"
|
||
if col in anom.columns:
|
||
ax1.plot(
|
||
anom["timestamp"],
|
||
anom[col],
|
||
label=f"w_{det}",
|
||
color=det_color_map[det],
|
||
lw=1.5,
|
||
)
|
||
|
||
ax1.axhline(
|
||
0.2, color="gray", linestyle=":", alpha=0.6, label="Gleichgewicht (0.2)"
|
||
)
|
||
ax1.set_ylabel("SEAD Gewicht $w_i$")
|
||
ax1.set_ylim(0, 0.7)
|
||
ax1.set_title(f"SEAD Ensemble Dynamik: {run_key}")
|
||
ax1.legend(loc="upper right", ncol=3, fontsize=9)
|
||
|
||
# --- Subplot 2: Anomalie Scores ---
|
||
for det in DETECTORS:
|
||
col = f"{det}_score"
|
||
if col in anom.columns:
|
||
ax2.plot(
|
||
anom["timestamp"],
|
||
anom[col],
|
||
color=det_color_map[det],
|
||
lw=0.8,
|
||
alpha=0.5,
|
||
linestyle="--",
|
||
)
|
||
|
||
ax2.plot(
|
||
anom["timestamp"],
|
||
anom["score"],
|
||
color="black",
|
||
lw=2.0,
|
||
label="Aggregierter Score (Pipeline)",
|
||
)
|
||
|
||
ax2.axhline(
|
||
0.5,
|
||
color="red",
|
||
linestyle="--",
|
||
lw=1.0,
|
||
alpha=0.7,
|
||
label="Alarm-Schwellenwert (0.5)",
|
||
)
|
||
ax2.set_ylabel("Anomalie Score $s_i$")
|
||
ax2.set_ylim(0, 1.1)
|
||
ax2.set_xlabel("Zeit [UTC]")
|
||
ax2.legend(loc="upper right", fontsize=9)
|
||
|
||
# --- Ground Truth Shading ---
|
||
for _, row in inj.iterrows():
|
||
t0, t1 = _to_utc(row["t_inj"]), _to_utc(row["t_stop"])
|
||
for ax in [ax1, ax2]:
|
||
ax.axvspan(t0, t1, color="red", alpha=0.08, zorder=0)
|
||
if ax == ax1:
|
||
ax.text(
|
||
t0,
|
||
ax.get_ylim()[1] * 0.95,
|
||
row["scenario"],
|
||
rotation=90,
|
||
color="darkred",
|
||
fontsize=7,
|
||
va="top",
|
||
alpha=0.6,
|
||
)
|
||
|
||
for ax in [ax1, ax2]:
|
||
ax.xaxis.set_major_formatter(mdates.DateFormatter("%H:%M"))
|
||
ax.grid(True, alpha=0.2)
|
||
|
||
plt.tight_layout()
|
||
save_fig(f"06_sead_details_{run_key}")
|
||
|
||
|
||
def main():
|
||
print_section("06 – SEAD Gewichte & Scores Analyse")
|
||
|
||
try:
|
||
windows_df = pd.read_csv(OUTPUT_DIR / "injection_windows.csv")
|
||
except:
|
||
print(" Fehler: injection_windows.csv nicht gefunden.")
|
||
return
|
||
|
||
sample_runs = ["full_cycle_run1", "high_bw_run1", "high_iops_run1"]
|
||
for rk in sample_runs:
|
||
if rk in RUNS:
|
||
plot_sead_internals(rk, windows_df)
|
||
|
||
print(
|
||
f"\n→ SEAD Detail-Plots unter {OUTPUT_DIR}/06_sead_details_*.png gespeichert."
|
||
)
|
||
|
||
|
||
if __name__ == "__main__":
|
||
main()
|