【Bug已解决】Evaluation protocol uses best validation score as reported metric and inflates results 解决方案

【Bug已解决】Evaluation protocol uses best validation score as reported metric and inflates results 解决方案
【Bug已解决】Evaluation protocol uses best validation score as reported metric and inflates results 解决方案一、现象长什么样你在 PEFT 的 benchmark 里看到某个方法的“准确率”特别高但复现不出来。细看评估协议发现# 伪代码可疑的评估协议 best_val max(val_scores) # 训练过程中验证集最好的一次 report best_val # 直接把“验证集最好分”当成方法的效果报告或者# 用验证集选了最优 epoch/超参却把该 epoch 的验证分当作测试分报告 test_score val_scores[argmax(val_scores)] # 这是验证集不是测试集根因是评估协议把“验证集上的最好分数”当成了“方法的效果”来报告而按规范验证集只用于选 epoch/超参最终效果必须用独立测试集在“验证集最优对应的那个 checkpoint”上评估。直接报告 best val score 会虚高inflated——因为你在验证集上挑了最好的那次它必然高于平均且不等于测试集表现。本文讲清正确的评估协议。二、背景机器学习评估的金标准训练集拟合参数验证集dev选超参、选最佳 epoch、早停——只用于决策不直接报告为效果测试集test只在最终、用验证集选定 checkpoint/超参后评估一次作为方法效果的估计。常见错误协议错误 A报告max(val_scores)作为效果。这等于“在验证集上挑最好的一次当成绩”虚高且不可比别人用测试集你用验证集最好分母不同。错误 B用验证集选了最优 epoch却报告该 epoch 的验证分而非测试分。正确应是“用该 epoch 的 checkpoint 去测测试集”。错误 C在验证集上做超参搜索后把搜索中最好的验证分报告等于在验证集上调参并报告过拟合验证集。正确的协议不inflatefor epoch: train() val evaluate(val_set) # 只用于选 epoch if val best_val: best_val val save_checkpoint() # 存“验证最优”checkpoint test evaluate(test_set, best_checkpoint) # 用验证最优 checkpoint 测测试集 report(test) # 报告的是测试集分数不是 best_val三、根因根因 A把验证集当测试集报告最直接。max(val)是验证集分却当方法效果虚高且不可比。根因 B选了最优 epoch 但测的是验证而非测试用验证集选 epoch 是对的但报告时用了验证分而非“该 checkpoint 的测试分”逻辑断链。根因 C超参搜索在验证集上最后报告最好验证分等于在验证集上调参并报告验证集被“污染”。根因 D多次早停/重跑取最好多次运行取验证集最好的一次报告进一步 inflate。根因小结正确验证集只决策选 epoch/超参测试集才报告错误报告max(val)或“最优 epoch 的验证分”会 inflate 结果修复存验证最优 checkpoint用它评估独立测试集报告测试分。四、最小可运行复现下面脚本演示“错误协议报告 best val虚高”vs“正确协议报告 best checkpoint 的 test 分”import random def simulate_training(n_epochs10): # 模拟训练过程中 val 和 test 分数test 始终略低于 val且独立 random.seed(0) val, test [], [] for e in range(n_epochs): base 0.6 0.03 * e random.uniform(-0.02, 0.02) val.append(base) test.append(base - random.uniform(0.01, 0.03)) # test 略低且独立 return val, test def wrong_protocol(val, test): # 错误直接报告 max(val) return max(val) def correct_protocol(val, test): # 正确用 val 最优 epoch 的 checkpoint 测 test best_epoch val.index(max(val)) return test[best_epoch] def demo(): val, test simulate_training() wrong wrong_protocol(val, test) correct correct_protocol(val, test) print(f错误协议报告(max val): {wrong:.3f}) print(f正确协议报告(best epoch 的 test): {correct:.3f}) print(f虚高量: {wrong - correct:.3f} (应 0说明错误协议 inflate)) if __name__ __main__: demo()运行后错误协议报告的分明显高于正确协议量化展示“inflate”的程度。五、解决方案第一层最小直接修复把评估协议改成“验证集决策、测试集报告”best_val -float(inf) best_state None for epoch in range(epochs): train_one_epoch(model, loader) val_score evaluate(model, val_loader) # 只用于选 epoch if val_score best_val: best_val val_score best_state {k: v.detach().clone() for k, v in model.state_dict().items()} # 用验证最优 checkpoint 测测试集这才报告 model.load_state_dict(best_state) test_score evaluate(model, test_loader) print(报告测试集分数:, test_score) # 不报告 best_val关键纪律best_val只用来选 checkpoint绝不打印成“效果”最终report(test_score)且测试集在整个实验里只评估一次避免测试集泄漏。六、解决方案第二层结构性改进6.1 封存测试集防止泄漏# 测试集在超参搜索/早停期间完全不可见 TEST_LOCKED True def evaluate_test(model, test_loader): assert TEST_LOCKED, 测试集只能在最终评估一次 return evaluate(model, test_loader)6.2 多次运行取测试均值而非验证最好# 多次 seed 跑报告测试集均值±标准差不是验证最好 test_scores [run_once(seeds) for s in [0, 1, 2]] report (statistics.mean(test_scores), statistics.pstdev(test_scores))6.3 报告协议写进实验卡PROTOCOL { selection: val (best epoch), report: test at best-val checkpoint, test_evals: 1, seeds: 3, }七、解决方案第三层断言 / CI 守护import pytest def test_report_uses_test_not_val(): val, test simulate_training() reported correct_protocol(val, test) assert reported in test, 报告分数必须来自测试集 assert reported not in val or reported max(val), 不应直接报告 best val def test_val_only_for_selection(): # 守护best_val 不出现在最终 report 字段 report {metric: test_accuracy, value: 0.82} assert val not in report[metric] def test_test_evaluated_once(): calls {n: 0} def evaluate_test(model, loader): calls[n] 1 return 0.8 evaluate_test(None, None) assert calls[n] 1, 测试集应只评估一次 def test_no_inflation(): val, test simulate_training() wrong wrong_protocol(val, test) correct correct_protocol(val, test) assert wrong correct, 错误协议不应低于正确否则示例失效CI 跑这四条评估协议的正确性被守住。八、排查清单评估协议 inflate 结果时查报告的是测试集还是验证集必须报告测试集分数不是max(val)。验证集只用于决策吗选 epoch/超参不打印成效果。最优 epoch 测的是测试还是验证应用最优 checkpoint 测测试集。超参搜索在验证集上吗搜索后报告的是测试集在该超参下。测试集只评估一次吗防止泄漏/多次挑最好。多次运行取测试均值吗报告 test 均值±标准差而非验证最好。CI 守护报告字段了吗断言metric来自 test 且只评一次。九、小结“Evaluation protocol uses best validation score as reported metric and inflates results” 是评估协议把验证集最好分当方法效果报告导致结果虚高正确金标准验证集只用于选 epoch/超参最终效果用独立测试集在验证最优 checkpoint 上评估一次错误max(val)或“最优 epoch 的验证分”直接报告inflate 且不可比第一层存验证最优 checkpoint用它评估测试集并报告测试分验证分只用于选不用于报第二层封存测试集防泄漏、多次 seed 取测试均值、协议写进实验卡第三层pytest 守护“报告来自测试集 验证只决策 测试只评一次 不 inflate”。一句话评估协议别把max(val)当效果报告验证集只用来选最优 checkpoint最终必须用它去测独立测试集并报告测试分否则结果虚高且不可比。