Reject eval_metric='roc_auc' with tune_decision_thresholds=True - #1153
Open
LeoGrin wants to merge 2 commits into
Open
Reject eval_metric='roc_auc' with tune_decision_thresholds=True#1153LeoGrin wants to merge 2 commits into
LeoGrin wants to merge 2 commits into
Conversation
The threshold search never optimized ROC AUC. Per class it scores thresholded 0/1 predictions, and the ROC AUC of a single operating point is the area under (0,0) -> (FPR,TPR) -> (1,1), which equals (TPR + TNR) / 2 -- balanced accuracy. So eval_metric='roc_auc' silently optimized balanced accuracy, and produces threshold vectors bit-identical to eval_metric='balanced_accuracy'. Nor can the pairing be repaired by passing probabilities to the objective instead: ROC AUC does not depend on the threshold, so the search would score every candidate identically and return the midpoint of a full-width plateau. The metric is threshold-invariant by definition. The applied reweighting cannot help it either. At predict time thresholds are used as divisors (probas / t, renormalized). For binary targets that is a strictly monotone transform of the positive-class probability (q1 = p1*t0 / (t1*(1-p1) + t0*p1), dq1/dp1 = t0*t1/(...)^2 > 0), so the ranking and therefore ROC AUC are provably unchanged. For multiclass the renormalizer mixes in other classes and the ranking does move, but only barely: injecting a deliberate per-class distortion spanning 0.37x-2.7x shifted macro OvR AUC by +0.0011 on covtype and +0.0000 on digits, and a coordinate search aimed directly at held-out macro OvR AUC gained +0.0002 and -0.0000 on test. That search was validated by a positive control, recovering 110% of the injected headroom on covtype, so the null result is not optimizer weakness. Temperature calibration is left enabled for this metric. It optimizes log loss regardless of eval_metric, so there is nothing roc_auc-specific about that path; its warning is reworded to say so rather than claiming ROC AUC is independent of the tunings, which was inaccurate -- the reweighting is applied inside _predict_proba and measurably lowered macro OvR AUC (-0.0128, losing on 7 of 8 seeds on covtype). Unrelated to FinetunedTabPFNClassifier.eval_metric='roc_auc', which scores real probabilities for checkpoint selection and is a correct use of the metric. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Threshold tuning under
eval_metric='roc_auc'never optimized ROC AUC. It silently optimized balanced accuracy, and separately degraded the metric the user asked for.The search scores thresholded 0/1 predictions:
With a binary score vector the ROC curve has a single interior point, so the area is
FPR·TPR/2 + (1−FPR)(TPR+1)/2 = (TPR + TNR)/2— exactly balanced accuracy. The two metrics are therefore the same objective, and the search returns bit-identical thresholds:Why this is rejected rather than repaired
Passing probabilities instead would be worse. ROC AUC does not depend on the threshold, so the objective would be constant across the grid, every candidate would score identically, and
select_robust_optimal_thresholdwould return the midpoint of a full-width plateau regardless of the data.Thresholds cannot improve a ranking metric. At predict time the values are applied as divisors (
probas / t, renormalized). For binary targets that is a strictly monotone transform of the positive-class probability:so the ranking, and therefore ROC AUC, is provably unchanged. Verified identical to 10 decimal places across thresholds from (0.5, 0.5) to (0.01, 0.99).
For multiclass the ranking does move, but negligibly. The renormalizer mixes in other classes, so
q_iis not monotone inp_i. HoweverD = Σⱼwⱼpⱼis a convex combination of the weights, so it varies only mildly across rows and the reranking is second order. Measured:The search was validated by a positive control — a known per-class distortion was injected, and the optimizer recovered 110% of the resulting headroom on covtype (4/4 seeds). So the null result reflects absence of signal, not a weak optimizer. Caveat: digits' AUC is ~0.997, at the ceiling, so its control was uninformative; the validation rests on covtype.
Meanwhile the reweighting is applied inside
_predict_proba, and measurably lowers macro OvR AUC: −0.0128 on covtype, losing on 7 of 8 seeds.Scope — only one case changes
eval_metrichas exactly one functional effect: selecting the threshold-tuning objective.find_optimal_temperaturetakes nometric_nameand always optimizes log loss.tuning_config(the default)tuning_configwith neither flagcalibrate_temperatureonlytune_decision_thresholds=TrueValueErrorThe error message names both escape routes:
eval_metric='balanced_accuracy'to keep the previous behaviour exactly, ortune_decision_thresholds=False.The old warning was misleading
Wrong on three counts: the reweight is applied inside
_predict_probaso AUC is not independent of it; the effect is not neutral but negative; and it never mentions the metric substitution, which is the important part. It read as "harmless, optional cleanup." The temperature-calibration warning is reworded to state the accurate fact — that calibration optimizes log loss regardless ofeval_metric.Not affected
FinetunedTabPFNClassifier(eval_metric='roc_auc')is a different parameter on a different class. It scoresroc_auc_score(y_val, probabilities)withmulti_class='ovr'for checkpoint selection — a correct use of the metric, deliberately untouched.Tests
The test asserting the removed warning is replaced by two: one asserting the raise for
tune_decision_thresholds, one asserting the calibration path still warns and still fits. Fulltest_classifier_interface.py: 163 passed, 63 skipped.Relation to #1152
Independent, different files (
classifier.pyhere,inference_tuning.pythere). #1152 fixes classes with no holdout support, whereroc_aucwas one of several affected metrics; it stands on its own forlog_loss(crashes on 8/8 seeds on real data) andf1/balanced_accuracy. Best merged after #1152, since landing this first makes some of that PR'sroc_aucmotivation unreachable through the classifier path.🤖 Generated with Claude Code