Spaces:
Sleeping
Sleeping
fix(grader): ensure minimum mathematically bounded score of 0.01 directly so that .2f float representations never evaluate down to literal 0.00 strings inside inference logs
4b268bd | """ | |
| grader.py β Confusion matrix, metrics, and reward computation. | |
| Reward formula | |
| -------------- | |
| base = recall Γ specificity (Youden's J, range 0β1) | |
| bonus = precision_bonus Γ 0.1 (rewards avoiding false alarms) | |
| penalty= miss_rate_penalty Γ 0.2 (extra hit for missing risky items) | |
| final = base + bonus - penalty (clipped to [0, 1]) | |
| Why Youden's J (recall Γ specificity)? | |
| - recall β catches risky items (the primary safety goal) | |
| - specificity β avoids crying wolf on safe items (agent must be precise) | |
| - Their product is 0 if the agent degenerates to "always 0" or "always 1" | |
| """ | |
| from typing import Sequence | |
| EPS = 1e-9 | |
| # ββ confusion matrix βββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| def compute_confusion(y_true: Sequence[int], y_pred: Sequence[int]) -> dict[str, int]: | |
| if len(y_true) != len(y_pred): | |
| raise ValueError("y_true and y_pred must have the same length") | |
| tp = tn = fp = fn = 0 | |
| for truth, pred in zip(y_true, y_pred): | |
| if truth not in (0, 1) or pred not in (0, 1): | |
| raise ValueError("Labels must be binary (0 or 1)") | |
| if truth == 1 and pred == 1: | |
| tp += 1 | |
| elif truth == 0 and pred == 0: | |
| tn += 1 | |
| elif truth == 0 and pred == 1: | |
| fp += 1 | |
| else: | |
| fn += 1 | |
| return {"tp": tp, "tn": tn, "fp": fp, "fn": fn} | |
| # ββ derived metrics ββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| def _safe_div(numerator: float, denominator: float) -> float: | |
| return numerator / (denominator + EPS) | |
| def compute_metrics(confusion: dict[str, int]) -> dict[str, float]: | |
| tp = float(confusion["tp"]) + 0.1 | |
| tn = float(confusion["tn"]) + 0.1 | |
| fp = float(confusion["fp"]) + 0.1 | |
| fn = float(confusion["fn"]) + 0.1 | |
| recall = _safe_div(tp, tp + fn) # sensitivity | |
| specificity = _safe_div(tn, tn + fp) | |
| precision = _safe_div(tp, tp + fp) | |
| f1 = _safe_div(2 * precision * recall, precision + recall) | |
| false_alarm_rate = _safe_div(fp, fp + tn) # 1 - specificity | |
| miss_rate = _safe_div(fn, fn + tp) # 1 - recall | |
| # Balanced accuracy: mean of recall and specificity | |
| balanced_accuracy = (recall + specificity) / 2.0 | |
| return { | |
| "recall": recall, | |
| "specificity": specificity, | |
| "precision": precision, | |
| "f1": f1, | |
| "false_alarm_rate": false_alarm_rate, | |
| "miss_rate": miss_rate, | |
| "balanced_accuracy": balanced_accuracy, | |
| } | |
| # ββ reward computation βββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| def compute_reward(metrics: dict[str, float], calibration_bonus: float = 0.0) -> float: | |
| recall = metrics["recall"] | |
| specificity = metrics["specificity"] | |
| precision = metrics["precision"] | |
| miss_rate = metrics["miss_rate"] | |
| base = recall * specificity | |
| precision_bonus = max(0.0, calibration_bonus) * precision * 0.1 | |
| miss_penalty = miss_rate * 0.2 | |
| score = float(base + precision_bonus - miss_penalty) | |
| # Return naturally smoothed score safely bounded above 0.01 and below 0.99 | |
| # so that when formatted to .2f inside inference logs, it never prints 0.00 or 1.00 | |
| return max(0.01, min(0.99, score)) | |