| """Generate baseline-vs-trained reward plots from eval rollouts. |
| |
| Usage: |
| python scripts/make_plots.py --rollouts eval_outputs/real_baseline/rollouts.jsonl --out eval_outputs/real_baseline |
| """ |
| from __future__ import annotations |
|
|
| import argparse |
| import json |
| import sys |
| from pathlib import Path |
|
|
|
|
| def main(): |
| ap = argparse.ArgumentParser() |
| ap.add_argument("--rollouts", required=True) |
| ap.add_argument("--out", required=True) |
| args = ap.parse_args() |
|
|
| import matplotlib |
| matplotlib.use("Agg") |
| import matplotlib.pyplot as plt |
|
|
| rows = [json.loads(l) for l in Path(args.rollouts).read_text(encoding="utf-8").splitlines() if l.strip()] |
| by_policy_scenario: dict[tuple[str, str], list[float]] = {} |
| for r in rows: |
| by_policy_scenario.setdefault((r["policy"], r["scenario_id"]), []).append(r["cumulative_reward"]) |
|
|
| policies = sorted({p for p, _ in by_policy_scenario}) |
| scenarios = sorted({s for _, s in by_policy_scenario}) |
|
|
| fig, ax = plt.subplots(figsize=(10, 5)) |
| width = 0.8 / max(1, len(policies)) |
| for i, p in enumerate(policies): |
| means = [] |
| stds = [] |
| for s in scenarios: |
| vals = by_policy_scenario.get((p, s), []) |
| if vals: |
| m = sum(vals) / len(vals) |
| v = sum((x - m) ** 2 for x in vals) / max(1, len(vals) - 1) |
| means.append(m) |
| stds.append(v ** 0.5) |
| else: |
| means.append(0) |
| stds.append(0) |
| x = [j + i * width - 0.4 + width / 2 for j in range(len(scenarios))] |
| ax.bar(x, means, width=width * 0.95, yerr=stds, capsize=2, label=p) |
| ax.set_xticks(range(len(scenarios))) |
| ax.set_xticklabels(scenarios, rotation=20, ha="right") |
| ax.set_ylabel("Cumulative episode reward") |
| ax.set_title("OpsGuard: cumulative reward by policy × scenario") |
| ax.axhline(0, color="gray", linestyle="--", linewidth=0.8) |
| ax.legend(loc="upper left", fontsize=9) |
| ax.grid(axis="y", alpha=0.3) |
| plt.tight_layout() |
| out_path = Path(args.out) / "reward_by_policy.png" |
| plt.savefig(out_path, dpi=130) |
| print(f" wrote {out_path}") |
|
|
| fig2, ax2 = plt.subplots(figsize=(10, 5)) |
| spam_recall_by = {} |
| for r in rows: |
| if r["n_spam_total"] == 0: |
| continue |
| spam_recall_by.setdefault((r["policy"], r["scenario_id"]), []).append( |
| r["n_spam_caught"] / r["n_spam_total"] |
| ) |
| for i, p in enumerate(policies): |
| means = [] |
| for s in scenarios: |
| vals = spam_recall_by.get((p, s), []) |
| means.append(sum(vals) / len(vals) if vals else 0.0) |
| x = [j + i * width - 0.4 + width / 2 for j in range(len(scenarios))] |
| ax2.bar(x, means, width=width * 0.95, label=p) |
| ax2.set_xticks(range(len(scenarios))) |
| ax2.set_xticklabels(scenarios, rotation=20, ha="right") |
| ax2.set_ylabel("Spam recall (caught / total)") |
| ax2.set_title("OpsGuard: spam recall by policy × scenario") |
| ax2.set_ylim(0, 1.05) |
| ax2.legend(loc="upper left", fontsize=9) |
| ax2.grid(axis="y", alpha=0.3) |
| plt.tight_layout() |
| out_path2 = Path(args.out) / "spam_recall.png" |
| plt.savefig(out_path2, dpi=130) |
| print(f" wrote {out_path2}") |
|
|
|
|
| if __name__ == "__main__": |
| main() |
|
|