Files
thesis/content/method/figures/roll_aoc_roc.py
T

97 lines
3.7 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
ROC curve illustration for ROLL-AOC.
Shows how ROLL-AOC converts the single-threshold ROLL into AUC optimization:
- ROC curve traced by sweeping threshold τ
- 10 evenly-spaced FPR sample points (as ROLL evaluates in miniature)
- Green shaded region = AUC (area under curve, what we maximize)
- Red shaded region = AOC = 1 − AUC (area over curve, the loss we minimize)
- Vertical segments = per-threshold loss 1 − TPR(αᵢ), one per sampled FPR
Run from the thesis root:
nix develop --command python3 content/method/figures/roll_aoc_roc.py
"""
import numpy as np
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
from scipy.stats import norm
OUT_DIR = "content/method/figures"
# --- Parameters (same distributions as roll_principle.py) ------------------
mu0, sig0 = 0.0, 1.00
mu1, sig1 = 1.5, 1.00
# --- ROC curve -------------------------------------------------------------
# Sweep τ from high to low → FPR rises from 0 to 1
tau_grid = np.linspace(norm.ppf(0.9999, mu0, sig0),
norm.ppf(0.0001, mu0, sig0), 3000)
fpr_curve = 1.0 - norm.cdf(tau_grid, mu0, sig0)
tpr_curve = 1.0 - norm.cdf(tau_grid, mu1, sig1)
# Pin endpoints so the fill covers the full unit square correctly
fpr_full = np.concatenate([[0.0], fpr_curve, [1.0]])
tpr_full = np.concatenate([[0.0], tpr_curve, [1.0]])
auc = float(np.trapezoid(tpr_full, fpr_full))
# --- 10 sample FPR points: αᵢ = i/11 for i = 1…10 ------------------------
n_samples = 10
alpha_s = np.array([(i / (n_samples + 1)) for i in range(1, n_samples + 1)])
tpr_s = np.interp(alpha_s, fpr_full, tpr_full)
# --- Palette (matches roll_principle.py) -----------------------------------
C_POS = "#CC6633" # ROC curve (positive-class colour)
C_GREEN = "#44AA77" # AUC fill
C_RED = "#BB3333" # AOC fill / vertical segments
C_DOT = "#333333" # sample dots
# ── Figure ─────────────────────────────────────────────────────────────────
fig, ax = plt.subplots(figsize=(4.8, 4.4))
# Random-classifier diagonal
ax.plot([0, 1], [0, 1], color="#AAAAAA", linestyle="--",
linewidth=0.9, zorder=1, label="Random classifier")
# AUC — green fill under ROC curve
ax.fill_between(fpr_full, 0, tpr_full,
color=C_GREEN, alpha=0.25, zorder=2,
label=rf"AUC $\approx$ {auc:.2f} (maximize)")
# AOC — red fill above ROC curve
ax.fill_between(fpr_full, tpr_full, 1.0,
color=C_RED, alpha=0.22, zorder=2,
label=r"AOC $= 1-\mathrm{AUC}$ (minimize)")
# ROC curve itself
ax.plot(fpr_full, tpr_full, color=C_POS, linewidth=2.0, zorder=3,
label="ROC curve")
# Per-threshold loss segments: vertical from TPR(αᵢ) up to 1
for a_i, t_i in zip(alpha_s, tpr_s):
ax.plot([a_i, a_i], [t_i, 1.0],
color=C_RED, linewidth=1.0, alpha=0.55, zorder=4)
# Sample dots on the ROC curve
ax.scatter(alpha_s, tpr_s,
color=C_DOT, s=35, zorder=5,
label=r"Sampled $\alpha_i$ (ROLL evaluations)")
# --- Axes / labels ---------------------------------------------------------
ax.set_xlabel(r"FPR $(\alpha)$", fontsize=9)
ax.set_ylabel(r"TPR", fontsize=9)
ax.set_xlim(0.0, 1.0)
ax.set_ylim(0.0, 1.0)
ax.set_aspect("equal")
ax.tick_params(labelsize=8)
ax.spines[["top", "right"]].set_visible(False)
ax.legend(fontsize=7.5, loc="lower right", framealpha=0.88)
fig.tight_layout()
fig.savefig(f"{OUT_DIR}/roll_aoc_roc.pdf", bbox_inches="tight")
fig.savefig(f"{OUT_DIR}/roll_aoc_roc.png", bbox_inches="tight", dpi=180)
print(f"Saved to {OUT_DIR}/roll_aoc_roc.{{pdf,png}}")
print(f"AUC = {auc:.4f} AOC = {1-auc:.4f}")