VETime / evaluation /protocol.py
Lantian72's picture yyyang0's picture
Restore VETime evaluation and document the model (#1)
88b1f73
Raw History Blame Contribute Delete
3.63 kB
"""Fixed-prediction evaluation entry points for the published Test_TSB script."""
import numpy as np
import torch
from sklearn.metrics import precision_recall_fscore_support
from .basic_metrics import basic_metricor, generate_curve, improved_cardinality_fn
def get_metrics(score, labels, slidingWindow=100, pred=None, version="opt", thre=250):
"""Evaluate scores without selecting thresholds from ground-truth labels.
Respect supplied predictions. Otherwise use mean(score) + 3 * std(score),
matching Test_TSB.py. Remove labels == -1 before calibration and evaluation.
Undefined ranking/event metrics are NaN, rather than a fabricated zero.
"""
score = np.asarray(score, dtype=float)
labels = np.asarray(labels)
if score.ndim != 1 or labels.shape != score.shape:
raise ValueError("Scores and labels must be one-dimensional with equal lengths.")
if pred is not None:
pred = np.asarray(pred)
if pred.shape != score.shape:
raise ValueError("Predictions must have the same shape as scores.")
valid = labels != -1
score, labels = score[valid], labels[valid]
if not len(score) or not np.isfinite(score).all():
raise ValueError("At least one finite, unpadded score is required.")
if not np.isin(labels, [0, 1]).all():
raise ValueError("Labels must be 0, 1, or -1 for padding.")
labels = labels.astype(int)
if pred is None:
pred = score > (score.mean() + 3 * score.std())
else:
pred = pred[valid]
if not np.isin(pred, [0, 1]).all():
raise ValueError("Predictions must be binary.")
pred = pred.astype(int, copy=True)
if int(slidingWindow) != slidingWindow or slidingWindow < 1:
raise ValueError("slidingWindow must be a positive integer.")
grader = basic_metricor()
precision, recall, f1, _ = precision_recall_fscore_support(
labels, pred, average="binary", zero_division=0
)
adjusted = grader.adjustment(labels, pred.copy())
pa_p, pa_r, pa_f, _ = precision_recall_fscore_support(
labels, adjusted, average="binary", zero_division=0
)
t_p, t_r = grader.ts_precision_and_recall(
torch.as_tensor(labels), torch.as_tensor(pred),
recall_cardinality_fn=improved_cardinality_fn,
weighted_precision=True,
)
t_f = 2 * t_p * t_r / (t_p + t_r) if t_p + t_r else 0.0
auc_roc = auc_pr = vus_roc = vus_pr = float("nan")
if len(np.unique(labels)) == 2:
auc_roc = grader.metric_ROC(labels, score)
auc_pr = grader.metric_PR(labels, score)
*_, vus_roc, vus_pr = generate_curve(
labels, score, int(slidingWindow), version=version, thre=thre
)
aff_f = aff_p = aff_r = float("nan")
if labels.any():
if pred.any():
aff_f, aff_p, aff_r = grader.metric_Affiliation(labels, score, preds=pred)
else:
aff_f, aff_p, aff_r = 0.0, float("nan"), 0.0
return {key: float(value) for key, value in {
"AUC-PR": auc_pr, "AUC-ROC": auc_roc,
"VUS-PR": vus_pr, "VUS-ROC": vus_roc,
"Standard-F1": f1, "Standard-Precision": precision, "Standard-Recall": recall,
"PA-F1": pa_f, "PA-Precision": pa_p, "PA-Recall": pa_r,
"Affiliation-F": aff_f, "Affiliation-P": aff_p, "Affiliation-R": aff_r,
"F1_T": t_f, "Precision_T": t_p, "Recall_T": t_r,
}.items()}
def get_metrics_pred(score, labels, pred, slidingWindow=100):
"""Evaluate a fixed prediction vector through the same public protocol."""
return get_metrics(score, labels, slidingWindow=slidingWindow, pred=pred)