37 lines
1.4 KiB
Python
37 lines
1.4 KiB
Python
"""Threshold-independent and threshold-dependent verification metrics."""
|
|
|
|
import numpy as np
|
|
from numpy.typing import ArrayLike
|
|
from sklearn.metrics import det_curve, roc_curve
|
|
|
|
|
|
def verification_metrics(labels: ArrayLike, scores: ArrayLike) -> dict[str, object]:
|
|
"""Compute FAR/FRR curves, ROC, DET and the approximate EER.
|
|
|
|
Labels use 0 for forgery and 1 for genuine. Scores are lower for more
|
|
genuine-looking samples.
|
|
"""
|
|
y = np.asarray(labels, dtype=np.int8)
|
|
values = np.asarray(scores, dtype=np.float64)
|
|
if y.ndim != 1 or values.ndim != 1 or len(y) != len(values) or len(y) == 0:
|
|
raise ValueError("labels and scores must be non-empty 1D arrays of equal length")
|
|
if not np.isin(y, [0, 1]).all() or len(np.unique(y)) != 2:
|
|
raise ValueError("labels must contain both classes, encoded as 0=forgery and 1=genuine")
|
|
|
|
fpr, tpr, roc_thresholds = roc_curve(y, -values)
|
|
false_accept, false_reject, det_thresholds = det_curve(y, -values)
|
|
fnr = 1.0 - tpr
|
|
crossing = int(np.argmin(np.abs(fpr - fnr)))
|
|
return {
|
|
"far": fpr,
|
|
"frr": fnr,
|
|
"roc_fpr": fpr,
|
|
"roc_tpr": tpr,
|
|
"roc_thresholds": -roc_thresholds,
|
|
"det_fpr": false_accept,
|
|
"det_fnr": false_reject,
|
|
"det_thresholds": -det_thresholds,
|
|
"eer": float((fpr[crossing] + fnr[crossing]) / 2.0),
|
|
"eer_threshold": float(-roc_thresholds[crossing]),
|
|
}
|