Download scripts/make_figures.py from Nabidnur/AIP-AD: direct link, hf CLI and curl.
- Browser
- Download file 8.79 kB
-
https://huggingface.co/Nabidnur/AIP-AD/resolve/main/scripts/make_figures.py
- Command line
-
hf download hf://Nabidnur/AIP-AD/scripts/make_figures.py
-
curl -L -o make_figures.py https://huggingface.co/Nabidnur/AIP-AD/resolve/main/scripts/make_figures.py
8.79 kB
| #!/usr/bin/env python3 | |
| """Publication-grade figures for the AIP-AD experiments.""" | |
| from __future__ import annotations | |
| import json | |
| import os | |
| import sys | |
| import matplotlib | |
| matplotlib.use("Agg") | |
| import matplotlib.font_manager as fm | |
| for f in ("/usr/share/fonts/truetype/chinese/NotoSansSC-Regular.ttf", | |
| "/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf"): | |
| if os.path.exists(f): | |
| fm.fontManager.addfont(f) | |
| import matplotlib.pyplot as plt | |
| import numpy as np | |
| plt.rcParams["font.sans-serif"] = ["DejaVu Sans", "Noto Sans SC"] | |
| plt.rcParams["axes.unicode_minus"] = False | |
| plt.rcParams["figure.dpi"] = 150 | |
| plt.rcParams["savefig.bbox"] = "tight" | |
| HERE = os.path.dirname(os.path.abspath(__file__)) | |
| ROOT = os.path.dirname(HERE) | |
| RES = os.path.join(ROOT, "results") | |
| FIG = os.path.join(RES, "figures") | |
| os.makedirs(FIG, exist_ok=True) | |
| sys.path.insert(0, ROOT) | |
| def style_ax(ax): | |
| ax.spines["top"].set_visible(False) | |
| ax.spines["right"].set_visible(False) | |
| ax.spines["left"].set_color("#cbd5e1") | |
| ax.spines["bottom"].set_color("#cbd5e1") | |
| ax.tick_params(colors="#475569", labelsize=8) | |
| ax.grid(True, linestyle="--", linewidth=0.5, alpha=0.2) | |
| ax.set_axisbelow(True) | |
| C_MAX = "#94a3b8" | |
| C_RAW = "#0891b2" | |
| C_ACL = "#7c3aed" | |
| C_RL = "#dc2626" | |
| C_AIP = "#f59e0b" | |
| with open(os.path.join(RES, "results.json")) as f: | |
| R = json.load(f) | |
| cats = [c for c in R["fixed_strategies"]["max"] if c != "macro"] | |
| table = R["fixed_strategies"] | |
| acl_table = R["acl_strategies"] | |
| arb = R["grpo_arbiter"] | |
| def grouped_bars(ax, src, names, colors, title, arbiter_line=None): | |
| x = np.arange(len(cats) + 1) # last slot = MACRO | |
| w = 0.8 / len(names) | |
| for i, n in enumerate(names): | |
| v = [src[n][c] for c in cats] + [src[n]["macro"]] | |
| ax.bar(x + i * w, v, w, label=n, color=colors[i], edgecolor="white", linewidth=0.4) | |
| if arbiter_line is not None: | |
| ax.axhline(arbiter_line, color=C_RL, ls="--", lw=1.6) | |
| ax.text(0.15, arbiter_line + 0.004, f"GRPO arbiter {arbiter_line:.3f}", | |
| color=C_RL, fontsize=8, weight="bold") | |
| ax.set_xticks(x + w * (len(names) - 1) / 2) | |
| ax.set_xticklabels(cats + ["MACRO"], rotation=20, ha="right", fontsize=8) | |
| ax.set_ylim(0.5, 1.0) | |
| ax.set_ylabel("Sample-level AUROC") | |
| ax.set_title(title, fontsize=10, weight="bold") | |
| style_ax(ax) | |
| ax.legend(fontsize=8, ncol=len(names), loc="lower left", bbox_to_anchor=(0, 1.01), frameon=False) | |
| fig, axes = plt.subplots(1, 2, figsize=(13, 4.4), constrained_layout=True) | |
| grouped_bars(axes[0], table, ["max", "mean", "median"], [C_MAX, C_RAW, C_RAW], | |
| "RAW camera agents — paper protocol (max) loses to robust/mean pooling") | |
| grouped_bars(axes[1], acl_table, ["max", "median", "trimmed"], [C_MAX, C_ACL, C_ACL], | |
| "ACL-calibrated agents — robust poolers recover; arbiter tops all", | |
| arbiter_line=arb["per_category"]["macro"]) | |
| fig.savefig(os.path.join(FIG, "fig1_benchmark.png")) | |
| plt.close(fig) | |
| # --- fig2: radar of strategies (ACL) --- | |
| names = ["max", "mean", "median", "top2", "trimmed", "aip_d05", "aip_c03", "aip_mt01", "aip_mt025", "aip_wt05"] | |
| ang = np.linspace(0, 2 * np.pi, len(cats), endpoint=False).tolist() | |
| ang += ang[:1] | |
| fig, ax = plt.subplots(figsize=(5.8, 5.4), subplot_kw=dict(polar=True), constrained_layout=True) | |
| pal = ["#64748b", "#0891b2", "#7c3aed", "#059669", "#65a30d", "#f59e0b", "#ea580c", "#db2777", "#be123c", "#dc2626"] | |
| for n, c in zip(names, pal): | |
| v = [acl_table[n][c] for c in cats] | |
| v += v[:1] | |
| ax.plot(ang, v, label=n, color=c, linewidth=1.2) | |
| ax.fill(ang, v, color=c, alpha=0.04) | |
| av = [arb["per_category"][c] for c in cats] | |
| av += av[:1] | |
| ax.plot(ang, av, label="GRPO arbiter", color="#dc2626", linewidth=2.4, linestyle="--") | |
| ax.set_xticks(ang[:-1]) | |
| ax.set_xticklabels(cats, fontsize=8) | |
| ax.set_ylim(0.5, 1.0) | |
| ax.set_title("Fusion strategies on ACL-calibrated agents (S-AUROC)", fontsize=10, weight="bold", pad=18) | |
| ax.legend(fontsize=6.5, loc="upper right", bbox_to_anchor=(1.34, 1.12)) | |
| fig.savefig(os.path.join(FIG, "fig2_radar.png")) | |
| plt.close(fig) | |
| # --- fig3: continual learning + online hedge --- | |
| fig, axes = plt.subplots(1, 2, figsize=(11.5, 4.2), constrained_layout=True) | |
| for ci, run in enumerate(R["continual"]): | |
| xs = np.arange(1, len(run["curve"]) + 1) | |
| c = ["#2563eb", "#dc2626"][ci] | |
| axes[0].plot(xs, [q["macro_sauroc_all6"] for q in run["curve"]], "o-", color=c, | |
| label=f"order {ci+1} (all 6 cats)") | |
| axes[0].plot(xs, [q["macro_sauroc_seen"] for q in run["curve"]], "o--", color=c, alpha=0.45, | |
| label=f"order {ci+1} (seen cats)") | |
| for src, lab, col in ((acl_table["max"], "static max-pool (paper protocol)", C_MAX), | |
| (acl_table["median"], "static median (ACL)", C_ACL)): | |
| axes[0].axhline(np.mean([src[c2] for c2 in cats]), color=col, ls=":", lw=1.5) | |
| axes[0].text(1.05, np.mean([src[c2] for c2 in cats]) + 0.003, lab, color=col, fontsize=7) | |
| axes[0].set_xticks(range(1, len(cats) + 1)) | |
| axes[0].set_xlabel("stream step (category)") | |
| axes[0].set_ylabel("macro S-AUROC") | |
| axes[0].set_title("Continual learning of the GRPO arbiter (solid: all cats, dashed: seen)", fontsize=9, weight="bold") | |
| axes[0].legend(fontsize=7, loc="lower left", bbox_to_anchor=(0, 1.02), ncol=2, frameon=False) | |
| style_ax(axes[0]) | |
| hc = R["online_hedge"] | |
| xs = np.arange(1, len(hc) + 1) | |
| axes[1].plot(xs, [h["macro_sauroc_all"] for h in hc], "o-", color=C_AIP, label="online Hedge (operator feedback)") | |
| axes[1].axhline(np.mean([table["max"][c] for c in cats]), color=C_MAX, ls=":", lw=1.5, label="static max-pool (raw)") | |
| axes[1].set_xticks(xs) | |
| axes[1].set_xticklabels([h["after"] for h in hc], rotation=20, ha="right", fontsize=8) | |
| axes[1].set_ylabel("macro S-AUROC (cumulative)") | |
| axes[1].set_title("Online learning: multiplicative-weights fusion", fontsize=9, weight="bold") | |
| axes[1].legend(fontsize=8, loc="lower left", bbox_to_anchor=(0, 1.02), frameon=False) | |
| style_ax(axes[1]) | |
| fig.savefig(os.path.join(FIG, "fig3_continual.png")) | |
| plt.close(fig) | |
| # --- fig4: policy behaviour --- | |
| fig, axes = plt.subplots(1, 2, figsize=(11.5, 4.2), constrained_layout=True) | |
| hist = arb["train_history"] | |
| ep = [h["epoch"] for h in hist] | |
| axes[0].plot(ep, [h["policy_entropy"] for h in hist], "-", color=C_RL) | |
| axes[0].set_xlabel("GRPO epoch") | |
| axes[0].set_ylabel("policy entropy (nats)") | |
| axes[0].set_title("Policy specialization during GRPO training", fontsize=9, weight="bold") | |
| style_ax(axes[0]) | |
| P = np.array(hist[-1]["mean_probs"]) | |
| snames = ["max", "mean", "median", "top2", "trimmed", "aip_d05", "aip_c03", "aip_soft2", | |
| "aip_mt01", "aip_mt025", "aip_mt05", "aip_mt075", "aip_wt05", "aip_wt075"] | |
| order = np.argsort(P) | |
| axes[1].barh(np.array(snames)[order], P[order], color=C_RL, alpha=0.85) | |
| axes[1].set_xlabel("mean policy weight") | |
| axes[1].set_title("Learned policy over pooling strategies (after ACL)", fontsize=9, weight="bold") | |
| style_ax(axes[1]) | |
| axes[1].grid(axis="x", linestyle="--", linewidth=0.5, alpha=0.2) | |
| fig.savefig(os.path.join(FIG, "fig4_policy.png")) | |
| plt.close(fig) | |
| # --- fig5: why dissent is information (swarm statistics, ACL scale) --- | |
| gaps_ok, gaps_ng, top1_ok, top1_ng = [], [], [], [] | |
| for cat in cats: | |
| ev = np.load(os.path.join(RES, "scores", f"{cat}.npz.eval.npz")) | |
| vs = ev["view_scores"].astype(np.float64) | |
| y = ev["view_labels"].max(1) | |
| srt = np.sort(vs, axis=1) | |
| for i in np.where(y == 0)[0]: | |
| gaps_ok.append(srt[i, -1] - srt[i, -2]) | |
| top1_ok.append(srt[i, -1]) | |
| for i in np.where(y == 1)[0]: | |
| gaps_ng.append(srt[i, -1] - srt[i, -2]) | |
| top1_ng.append(srt[i, -1]) | |
| fig, axes = plt.subplots(1, 2, figsize=(11.5, 4.2), constrained_layout=True) | |
| axes[0].hist(gaps_ok, bins=48, alpha=0.62, label=f"normal (n={len(gaps_ok)})", color=C_MAX, density=True) | |
| axes[0].hist(gaps_ng, bins=48, alpha=0.62, label=f"anomalous (n={len(gaps_ng)})", color=C_RL, density=True) | |
| axes[0].set_xlabel("dissent: top-1 minus top-2 per-view score") | |
| axes[0].set_ylabel("density") | |
| axes[0].set_title("The dissenting 'liar' view carries the anomaly signal", fontsize=9, weight="bold") | |
| axes[0].legend(fontsize=8, loc="lower left", bbox_to_anchor=(0, 1.02), frameon=False) | |
| style_ax(axes[0]) | |
| axes[1].hist(top1_ok, bins=48, alpha=0.62, label="normal", color=C_MAX, density=True) | |
| axes[1].hist(top1_ng, bins=48, alpha=0.62, label="anomalous", color=C_RL, density=True) | |
| axes[1].set_xlabel("top-1 per-view score (max report)") | |
| axes[1].set_title("Max report alone is weakly separated", fontsize=9, weight="bold") | |
| axes[1].legend(fontsize=8, loc="lower left", bbox_to_anchor=(0, 1.02), frameon=False) | |
| style_ax(axes[1]) | |
| fig.savefig(os.path.join(FIG, "fig5_dissent.png")) | |
| plt.close(fig) | |
| print("figures written to", FIG) | |
| for f in sorted(os.listdir(FIG)): | |
| print(" ", f, os.path.getsize(os.path.join(FIG, f))) | |