AdverScan / adverscan /evaluation /threshold_analysis.py
charanyellanki's picture
initial AdverScan implementation — adversarial example detector with threshold analysis
b95a555
Raw
History Blame Contribute Delete
3.14 kB
"""
Operating-point tooling: ROC/PR thresholds and false-positive budget sweeps.
These utilities summarize how detector logits translate into deployment trade-offs without
embedding themselves inside the training loops.
"""
from __future__ import annotations
from dataclasses import dataclass
from typing import Iterable
import numpy as np
from numpy.typing import NDArray
from sklearn.metrics import precision_recall_curve, roc_curve
@dataclass(slots=True)
class RocSummary:
"""Container for ROC evaluation outputs."""
fpr: NDArray[np.floating]
tpr: NDArray[np.floating]
roc_thresholds: NDArray[np.floating]
def build_roc(
labels: NDArray[np.integer],
scores: NDArray[np.floating],
) -> RocSummary:
"""
Convenience wrapper emitting ``RocSummary`` envelopes.
Positive class ``1`` should correspond to adversarial samples.
"""
curve = roc_curve(labels, scores)
fpr_np: NDArray[np.floating] = curve[0].astype(np.float32, copy=False)
tpr_np: NDArray[np.floating] = curve[1].astype(np.float32, copy=False)
thr_np: NDArray[np.floating] = curve[2].astype(np.float32, copy=False)
return RocSummary(fpr=fpr_np, tpr=tpr_np, roc_thresholds=thr_np)
@dataclass(slots=True)
class PrecisionRecallSweep:
"""Precision-recall envelopes + threshold ladders."""
precision: NDArray[np.floating]
recall: NDArray[np.floating]
pr_thresholds: NDArray[np.floating]
def build_precision_recall_sweep(labels: NDArray[np.integer], scores: NDArray[np.floating]) -> PrecisionRecallSweep:
"""Produce precision/recall grids using sklearn internals."""
p, r, thr = precision_recall_curve(labels, scores)
return PrecisionRecallSweep(
precision=p.astype(np.float32, copy=False),
recall=r.astype(np.float32, copy=False),
pr_thresholds=thr.astype(np.float32, copy=False),
)
def fp_budget_analysis(
fpr_curve: Iterable[float],
target_fp_rates: Iterable[float],
) -> dict[float, tuple[float | None, float]]:
"""
Map desired false-positive rates to nearest ROC operating points.
Parameters
----------
fpr_curve
ROC false-positive probabilities monotonic increasing iterable.
target_fp_rates
Iterable of desired budgets ``tau`` with ``tau`` in ``[0,1]``.
Returns
-------
dict
Lookup ``desired_fp_rate`` -> ``(matched_fpr_observed | None, index_of_closest)``
"""
fps = np.asarray(list(fpr_curve), dtype=np.float32)
lookups: dict[float, tuple[float | None, float]] = {}
if fps.size == 0:
for target in target_fp_rates:
lookups[float(target)] = (None, float(np.nan))
return lookups
ordered = fps.copy()
for target in target_fp_rates:
pos = np.searchsorted(np.sort(ordered), target, side="left")
if pos >= ordered.size:
lookups[float(target)] = (None, float("nan"))
continue
distances = np.abs(ordered - target)
argmin_idx = np.argmin(distances)
matched = float(ordered[argmin_idx])
lookups[float(target)] = (matched, float(argmin_idx))
return lookups