Improvements! And another figure.
This commit is contained in:
Binary file not shown.
|
After Width: | Height: | Size: 84 KiB |
@@ -0,0 +1,125 @@
|
||||
"""
|
||||
Geometric picture of the ROLL TPR@FPR objective.
|
||||
|
||||
Upper panel: score PDFs for both classes; shaded area under the positive-class
|
||||
PDF to the left of τ equals the ROLL loss F̂₁(τ).
|
||||
Lower panel: CDFs showing how τ is read from F̂₀ and how the loss and TPR
|
||||
are read from F̂₁.
|
||||
|
||||
Run from the thesis root:
|
||||
nix develop --command python3 content/method/figures/roll_principle.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"
|
||||
|
||||
np.random.seed(3)
|
||||
|
||||
# --- Parameters ------------------------------------------------------------
|
||||
mu0, sig0 = 0.0, 1.00 # negative class N(0, 1)
|
||||
mu1, sig1 = 1.5, 1.00 # positive class N(1.5, 1) — intentional overlap
|
||||
alpha = 0.20 # target FPR
|
||||
|
||||
# τ = (1-α)-quantile of the negative class → F̂₀(τ) = 1-α → FPR = α
|
||||
tau = norm.ppf(1 - alpha, mu0, sig0)
|
||||
F0_tau = norm.cdf(tau, mu0, sig0) # = 1 - alpha
|
||||
F1_tau = norm.cdf(tau, mu1, sig1) # ROLL loss
|
||||
tpr = 1.0 - F1_tau
|
||||
|
||||
# --- Sample points for positive class (rug) --------------------------------
|
||||
rug = np.random.normal(mu1, sig1, 35)
|
||||
|
||||
# --- Score axis ------------------------------------------------------------
|
||||
x = np.linspace(-3.6, 5.0, 900)
|
||||
|
||||
# --- Palette ---------------------------------------------------------------
|
||||
C_NEG = "#4477AA"
|
||||
C_POS = "#CC6633"
|
||||
C_TAU = "#333333"
|
||||
C_LOSS = "#7799BB"
|
||||
|
||||
# ── Figure ─────────────────────────────────────────────────────────────────
|
||||
fig, (ax1, ax2) = plt.subplots(
|
||||
2, 1, figsize=(5.5, 4.8), sharex=True,
|
||||
gridspec_kw={"height_ratios": [1.25, 1.0]},
|
||||
)
|
||||
fig.subplots_adjust(hspace=0.07)
|
||||
|
||||
# ══ Top: PDF ══════════════════════════════════════════════════════════════
|
||||
|
||||
pdf0 = norm.pdf(x, mu0, sig0)
|
||||
pdf1 = norm.pdf(x, mu1, sig1)
|
||||
|
||||
ax1.plot(x, pdf0, color=C_NEG, linewidth=1.8, label=r"Negative class ($y=0$)")
|
||||
ax1.plot(x, pdf1, color=C_POS, linewidth=1.8, label=r"Positive class ($y=1$)")
|
||||
ax1.axvline(tau, color=C_TAU, linestyle="--", linewidth=1.3,
|
||||
label=r"Threshold $\tau$")
|
||||
|
||||
# Shade: area under positive PDF to the left of τ = loss = F̂₁(τ)
|
||||
x_left = x[x <= tau]
|
||||
ax1.fill_between(x_left, norm.pdf(x_left, mu1, sig1),
|
||||
color=C_LOSS, alpha=0.30, label=r"Loss $= \hat{F}_1(\tau)$")
|
||||
|
||||
# Rug plot
|
||||
rug_y = np.full_like(rug, -0.006)
|
||||
ax1.scatter(rug, rug_y, color=C_POS, s=18, marker="|",
|
||||
zorder=3, alpha=0.65, clip_on=False)
|
||||
|
||||
ax1.set_ylabel("PDF", fontsize=9)
|
||||
ax1.set_yticks([])
|
||||
ax1.set_ylim(bottom=-0.025)
|
||||
ax1.legend(fontsize=7.5, loc="upper right", framealpha=0.88)
|
||||
|
||||
# ══ Bottom: CDF ═══════════════════════════════════════════════════════════
|
||||
|
||||
cdf0 = norm.cdf(x, mu0, sig0)
|
||||
cdf1 = norm.cdf(x, mu1, sig1)
|
||||
|
||||
ax2.plot(x, cdf0, color=C_NEG, linewidth=1.8, label=r"$\hat{F}_0$ (neg.)")
|
||||
ax2.plot(x, cdf1, color=C_POS, linewidth=1.8, label=r"$\hat{F}_1$ (pos.)")
|
||||
ax2.axvline(tau, color=C_TAU, linestyle="--", linewidth=1.3)
|
||||
|
||||
# Horizontal reference lines
|
||||
ax2.axhline(F0_tau, color=C_NEG, linestyle="-.", linewidth=0.9, alpha=0.7)
|
||||
ax2.axhline(F1_tau, color=C_POS, linestyle="-.", linewidth=0.9, alpha=0.7)
|
||||
|
||||
# Dots at the intercepts
|
||||
ax2.plot(tau, F0_tau, "o", color=C_NEG, markersize=5, zorder=4)
|
||||
ax2.plot(tau, F1_tau, "o", color=C_POS, markersize=5, zorder=4)
|
||||
|
||||
# Left-side y-axis labels for the intercepts
|
||||
x_min = x[0]
|
||||
ax2.text(x_min - 0.15, F0_tau, rf"$1-\alpha$",
|
||||
fontsize=8, color=C_NEG, va="center", ha="right")
|
||||
ax2.text(x_min - 0.15, F1_tau, r"$\hat{F}_1(\tau)$" + "\n(loss)",
|
||||
fontsize=7.5, color=C_POS, va="center", ha="right")
|
||||
|
||||
# TPR annotation: double-headed arrow on the right + label
|
||||
x_bracket = tau + 1.9
|
||||
ax2.annotate("", xy=(x_bracket, 1.0), xytext=(x_bracket, F1_tau),
|
||||
arrowprops=dict(arrowstyle="<->", color="black",
|
||||
lw=0.8, mutation_scale=10))
|
||||
ax2.text(x_bracket + 0.12, (1.0 + F1_tau) / 2,
|
||||
f"TPR\n= {tpr*100:.0f}%",
|
||||
fontsize=7.5, va="center", ha="left", color="black")
|
||||
|
||||
ax2.set_ylabel("CDF", fontsize=9)
|
||||
ax2.set_xlabel(r"Score $f_\theta(\mathbf{x})$", fontsize=9)
|
||||
ax2.set_yticks([0.0, 0.25, 0.50, 0.75, 1.0])
|
||||
ax2.set_yticklabels(["0", "0.25", "0.5", "0.75", "1"], fontsize=7.5)
|
||||
ax2.set_xlim(x[0] - 0.3, x[-1])
|
||||
ax2.legend(fontsize=7.5, loc="upper left", framealpha=0.88)
|
||||
|
||||
for ax in (ax1, ax2):
|
||||
ax.tick_params(labelsize=8)
|
||||
ax.spines[["top", "right"]].set_visible(False)
|
||||
|
||||
fig.savefig(f"{OUT_DIR}/roll_principle.pdf", bbox_inches="tight")
|
||||
fig.savefig(f"{OUT_DIR}/roll_principle.png", bbox_inches="tight", dpi=180)
|
||||
print(f"Saved to {OUT_DIR}/roll_principle.{{pdf,png}}")
|
||||
print(f"tau={tau:.3f} F0(tau)={F0_tau:.3f} F1(tau)={F1_tau:.3f} TPR={tpr:.3f}")
|
||||
@@ -251,6 +251,23 @@ of the two populations, turning a TPR@FPR objective into a FPR@TPR one. Because
|
||||
two objectives are therefore interchangeable at the implementation level, all derivations
|
||||
and implementation details below are given for \eqref{eq:roll-tpr-at-fpr} only;
|
||||
\eqref{eq:roll-fpr-at-tpr} follows by the same transformation applied to the inputs.
|
||||
\Cref{fig:roll-principle} provides a geometric view of \eqref{eq:roll-tpr-at-fpr}.
|
||||
|
||||
\begin{figure}[H]
|
||||
\centering
|
||||
\includegraphics[width=0.85\textwidth]{content/method/figures/roll_principle.png}
|
||||
\caption{Geometric picture of the ROLL TPR@FPR objective. \textit{Upper}: score PDFs
|
||||
for the negative (blue, $y=0$) and positive (orange, $y=1$) classes. The operating
|
||||
threshold $\tau$ (dashed) is placed at the $(1-\alpha)$-quantile of the negative
|
||||
class, so that exactly $\alpha$ fraction of negative scores exceed it ($\operatorname{FPR}=\alpha$).
|
||||
The shaded area under the positive-class PDF to the left of $\tau$ is
|
||||
$\hat{F}_1(\tau)$ --- the ROLL loss we minimize. Tick marks along the bottom represent
|
||||
observed positive-class scores. \textit{Lower}: corresponding CDFs $\hat{F}_0$ (blue)
|
||||
and $\hat{F}_1$ (orange). Reading horizontally at height $1-\alpha$ on $\hat{F}_0$
|
||||
gives $\tau$; reading $\hat{F}_1(\tau)$ off $\hat{F}_1$ at the same $\tau$ gives the
|
||||
loss; the distance from $\hat{F}_1(\tau)$ to $1$ is the resulting TPR.}
|
||||
\label{fig:roll-principle}
|
||||
\end{figure}
|
||||
|
||||
We now derive the gradient of \eqref{eq:roll-tpr-at-fpr} with respect to $f_\theta(\mathbf{x}_i)$.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user