8.3 超参数搜索与最终测试:搜索预算越大,越容易过拟合验证过程
模型工坊一夜跑了两千组参数,第二天挑出 validation 第一名。阿花没有立刻庆祝:“如果两千次都盯着同一块验证数据,总会有一个因为运气排在前面。”
超参数搜索是一种模型选择过程。候选越多、反馈越频繁,validation 越像训练数据;最终 test 或 outer CV 必须评估整套选择流程,而不是只评估获胜模型的训练结果。
本课目标
- 区分模型参数、超参数与决策参数;
- 设计有尺度、条件和预算的搜索空间;
- 正确使用 grid/random/adaptive search;
- 把 pipeline、early stopping、calibration 和 threshold 放入验证边界;
- 冻结选择后只用 test 做最终评估。
1. 哪些东西都算“从数据选择”
- model parameters:训练 loss 学到的系数、树分裂、神经网络权重;
- hyperparameters:正则强度、树深、learning rate、representation 选择;
- decision parameters:分类阈值、top-$K$、拒绝区间;
- procedure choices:特征窗口、缺失策略、metric、ensemble 规则。
后三类只要根据 validation 结果调整,就属于模型选择。手工“凭经验改两次”与自动 RandomizedSearchCV 在统计上没有本质区别,都消耗验证信息。
2. 先写搜索目标和预算
搜索前固定:
- primary selection metric/utility;
- secondary guardrails(延迟、校准、公平、内存);
- splitter 和随机种子策略;
- 最大 candidate 数、总 fit 数和时限;
- 失败/NaN 的处理;
- 最小有意义改进;
- test 何时解封。
否则团队会在看到结果后换指标、扩大空间、删除失败折,最终 best score 无法解释。
3. 搜索空间要尊重参数尺度
正则强度、learning rate 常跨多个数量级,适合 log-uniform;深度、邻居数是离散整数;某些参数只在特定模型/solver 下有效。
不合理网格:
C = [1, 2, 3, 4, 5]若合理范围可能从 $10^{-4}$ 到 $10^2$,线性小网格几乎没探索尺度。先用宽的对数空间找区域,再在冻结预算内细化;不要看到 test 后再扩范围。
Conditional space 也很重要:只有 penalty="elasticnet" 时才搜索 l1_ratio;不同 booster/grow policy 对应不同参数。把无效组合塞进笛卡尔网格既浪费计算,也容易产生失败候选。
4. Grid 与 Random Search
Grid search 枚举笛卡尔积,适合候选很少、每个值有明确意义的空间。维度一多,组合数相乘。
Random search 在固定 n_iter 下从分布采样:
- 搜索预算与维度解耦;
- 连续参数能访问更多不同取值;
- 结果依赖参数分布和随机种子;
- 可随预算追加候选,但追加规则应预先定义。
Random 不等于盲目。分布上下界仍是强先验,应记录采样值和失败情况。
5. 一个 Pipeline 内的 Group-aware Random Search
from scipy.stats import loguniform
from sklearn.impute import SimpleImputer
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import RandomizedSearchCV, StratifiedGroupKFold
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler
pipeline = Pipeline([
("impute", SimpleImputer(strategy="median")),
("scale", StandardScaler()),
("model", LogisticRegression(max_iter=3_000)),
])
inner_cv = StratifiedGroupKFold(
n_splits=5,
shuffle=True,
random_state=42,
)
search = RandomizedSearchCV(
pipeline,
param_distributions={
"model__C": loguniform(1e-4, 1e2),
"model__class_weight": [None, "balanced"],
},
n_iter=40,
scoring={
"neg_log_loss": "neg_log_loss",
"average_precision": "average_precision",
},
refit="neg_log_loss",
cv=inner_cv,
random_state=42,
n_jobs=-1,
return_train_score=True,
)
search.fit(X_development, y_development, groups=development_group)
test_probability = search.predict_proba(X_test)[:, 1]best_score_ 对 neg_log_loss 是“越大越好”的负损失,不要把负号忘掉。多指标搜索只按 refit 指定的 metric 选最终 estimator;其他指标用于 guardrail/诊断,不会自动做多目标优化。
项目启用 metadata routing 或升级 sklearn 后,groups/额外元数据传递方式可能变化,应固定版本并测试每折 group 隔离。
6. Best Candidate 常只是噪声赢家
cv_results_ 应至少检查:
- 各折与均值/标准差;
- train–validation gap;
- fit/score 时间;
- failed/NaN candidates;
- 参数边界是否总胜出;
- top candidates 差异是否小于抽样噪声;
- 不同时间/group 的排名是否翻转。
当多个候选性能相近,可用 one-standard-error 类规则或预先定义的容差,选择更简单、更快、更稳定的模型。最高第四位小数不一定值得更高延迟。
7. 多目标选择要明确优先级
真实系统常要求:
在 validation log loss 不劣于 baseline 1% 的候选中,
选择 p99 延迟最低且群体 recall 均达到门槛的模型。这不是把多个 metric 取平均。可使用 constrained selection、Pareto frontier 或 refit=callable,但规则要在 test 前冻结。模型、阈值和容量还可能共同决定结果,最好直接评估最终 decision policy。
8. Adaptive/Bayesian Search 的代价
Bayesian optimization 根据历史 trial 建 surrogate/acquisition 决定下一组参数;successive halving 用少量资源初筛,再给少数候选更多资源。
它们可节省计算,但没有免除验证偏差:
- 早期低资源排名可能不能预测完整训练排名;
- 学习曲线可交叉,过早淘汰慢热候选;
- adaptive trial 更紧密地使用同一 validation;
- 并行 trial、失败恢复和随机性影响复现;
- sklearn 的 halving search 在当前版本仍可能是 experimental API。
记录每个 trial 的 code/data/version、资源、seed、metric 与终止原因。
9. Early Stopping 需要第三层训练边界
在一个 inner fold 内评估 boosting candidate 时:
- outer/inner validation 不能直接作为每棵模型的 early-stopping data 后又无修正地报告同一分数;
- 可从 inner-train 再切 fit/early-stop 子集,或使用能正确 cross-fit rounds 的自定义流程;
- 选定轮数后如何 refit 全开发集,要预先规定;
- 绝不能用最终 test 控制停止。
通用 SearchCV 未必自动把每折 validation 传给第三方库的 eval_set。如果代码把同一个全局 X_valid 塞进每个 fit,可能产生跨折泄漏。需要自定义 estimator/callback 或分阶段方案,并写测试验证 indices。
10. Calibration 与 Threshold 也要嵌套
一个完整 binary system 可能依次选择:
- representation 与 model hyperparameters;
- calibration mapping;
- decision threshold。
若都在同一 validation 上反复优化,结果偏乐观。可选设计:
- 训练/调参、校准、阈值各有独立数据段;
- cross-fitting 生成 out-of-fold scores,再拟合 calibration/threshold;
- 将完整 meta-estimator 放入 outer CV;
- 最终独立 test 评估冻结 pipeline。
阈值调优不改变原始 score 的 ROC/PR 排序曲线,但会改变 hard predictions 和 utility。Calibration 若是严格单调映射也不改变排名;有限数据与非严格变换下仍要实测。
11. Parallel Search 的工程陷阱
n_jobs=-1 加上模型内部多线程会 oversubscribe CPU。候选并行还可能复制数据,导致内存峰值和 OOM。
控制:
- 只在搜索层或 estimator 一层并行;
- 设置
pre_dispatch/线程环境; - 记录峰值 RSS、fit time 和能耗;
- 给每个 trial 超时与失败分类;
- 不把 OOM candidate 当作普通低分静默丢弃。
可部署性是 selection guardrail,不是获胜后才检查。
12. 最终测试与重训协议
一种清楚的 holdout 流程:
- 锁定 test snapshot 与评估脚本;
- 在 development data 完成所有搜索、特征、校准、阈值与代码选择;
- 冻结 pipeline、依赖、seed policy 和 primary metric;
- 解封 test,生成一次 predictions;
- 报告 point estimate、合适单位的 uncertainty、切片与失败案例;
- 根据预设 release gate 决策。
若 test 失败后改模型,可以继续开发,但必须承认原 test 已成为开发证据。需要新的未来窗口、外部数据或严格记录的 sequential testing 方案恢复可信最终评估。
最终决定后是否在 train+validation 上 refit,要看 calibration/threshold 如何交叉拟合以及 test 之后是否仍需估计性能。不能先把 test 也加入训练,再用旧 test 分数代表新模型。
常见误区
- 手调参数不算搜索:只要看 validation 反馈调整,就在消耗它。
- GridSearchCV 的 best score 是泛化无偏估计:它是被选择出的最大 CV 分数,通常乐观。
- Random search 天然更科学:分布、预算和验证结构仍决定质量。
- Early stopping 自动防过拟合:它本身就是 validation-driven 选择。
- SearchCV 会自动处理第三方
eval_set:通常需要显式设计和测试。
练习
- 把线性网格改为 log-uniform 搜索,并画出实际采样值。
- 从
cv_results_找出分数相近但延迟/复杂度不同的候选。 - 实现 outer GroupKFold + inner RandomizedSearchCV,比较 best inner score 与 outer score。
- 画出 boosting 的 fit/early-stop/inner/outer/test 数据边界。
- 写一份包含 calibration、threshold 和最终 test 的冻结协议。
小结
超参数搜索扩大了候选范围,也扩大了对 validation 的适应。可靠流程要预先固定目标、空间、预算与 split,把所有 learned choices 放入 inner loop,并用独立 test 或 outer CV 评估整套选择过程。模型第一名只是候选,冻结后的决策系统才是交付物。
下一章进入神经网络。训练规模和非凸优化都会增加,但评估原则不变:数据边界、目标、early stopping 和最终 test 仍必须彼此隔离。