[机器学习]Kaggle:CV、Public LB and Private LB
在很多竞赛平台(比如Kaggle)上,测试集确实可以反复提交来获得分数。但这里有一个关键的陷阱:如果你根据多次测试集的分数来调整模型或超参数,本质上是在用测试集训练,会导致模型对测试集过拟合——最终分数很好看,但换一批新数据就会崩溃。
🔍 为什么通常“禁止”反复用测试集调参?
· 信息泄漏:每次提交都在从测试集中“学习”模式。比如你发现某次提交分数低,于是改参数再试,这相当于把测试集当成了验证集。
· 泛化风险:反复优化测试集分数后,模型会“记住”测试集中的特定噪声,失去对新数据的预测能力。竞赛中这叫 “leaderboard overfitting”,常见于靠后提交反超、但最终换用Private Test Set时翻车的情况。
📌 什么时候可以反复提交?
· 有限次数:Kaggle每天只允许3-5次提交,并且有Public/Private榜——Public榜分数本身就是用来给你“探索”的,但最终排名由Private榜决定(通常只评一次)。
· A/B测试或固定测试集:如果你明确知道未来数据分布与当前测试集完全一致(比如公司内部的固定留出集),且不打算再更新,那反复调参是可行的。但严格的研究规范依然要求测试集只用一次。
✅ 正确的做法
在超参数调优(W&B Sweeps)阶段:
· 用验证集或交叉验证来指导搜索,不用Public LB来训练模型。
· 搜索结束后,用最佳参数在训练集+验证集上重新训练最终模型。
· 最后在测试集上只跑一次,记录最终分数。
· 从Public Leaderboard分数中反推验证集性能,但不要直接改模型超参数。
· 用Public分数作为参考,但最终模型选择依然基于本地交叉验证。
总之:原则上测试集只用一次,竞赛的多次提交本质是“公开验证集”。
公开测试集分数不理想时,千万不要直接对着公开分数改参数(否则会过拟合到公开测试集)。正确做法是:
1️⃣ 先诊断:是验证集就低,还是只有公开测试集低?
情况 诊断 对策
验证集分数也低 模型欠拟合或数据问题 加强特征工程、增大模型容量、延长训练
验证集分数高,公开测试集低 过拟合到验证集 / 数据分布不一致 简化模型、增加正则、检查验证集采样是否与测试集同分布
2️⃣ 系统化调整步骤(按优先级排序)
✅ 第一步:强化交叉验证
· 把单次 train_test_split 换成 5折交叉验证,用平均分作为Sweep的优化目标。这能帮你判断分数波动是偶然还是真实问题。
✅ 第二步:回到特征工程(树模型最敏感)
· 检查缺失值处理:是否引入了噪音?试试更精细的分组填充(比如按Pclass+Title中位数填充年龄,比全局中位数好)。
· 检查类别编码:无序类别(如Embarked)用One‑Hot;有序类别(如Pclass)用整数即可。不要对高基数特征(如Ticket)直接编码,应提取模式(如是否有字母前缀)。
· 构造交叉特征:例如Age*Pclass、Fare_per_person = Fare / (SibSp+1),树模型能自动利用这些组合。
· 删除无意义特征:PassengerId、Name(已提取Title后可以删掉)、Ticket(除非提取出有用部分)。
✅ 第三步:调整超参数搜索空间
· 不要只调常用参数:为树模型增加正则化参数的搜索范围:
· 随机森林:min_samples_split、min_samples_leaf、max_features(推荐sqrt或log2)
· XGBoost:eta、subsample、colsample_bytree、gamma、reg_alpha、reg_lambda
· LightGBM:learning_rate、num_leaves、min_child_samples、subsample、colsample_bytree、reg_alpha、reg_lambda
· 扩大搜索范围:如果之前的范围较窄(比如max_depth只试3~5),放开到2~15试试看。
✅ 第四步:尝试模型集成
· 单模型调到头后,用 Bagging(多个随机森林取平均)或 Stacking(用树模型的预测作为新特征训练第二层模型)往往能提分。
✅ 第五步:检查数据泄露(公开测试集特有的坑)
· 你是否在特征工程中使用了整个训练集的信息(如全局均值填充)?如果公开测试集的时间或分布与训练集不同,这可能导致分数下降。改用基于训练集内部的统计(如分组中位数)更安全。
3️⃣ 实战建议:用W&B对比实验
1. 创建一个新的Sweep,把上述改进(如新特征、新参数范围)作为实验组,与旧配置同时运行。
2. 在W&B项目里按 val_acc 排序,选最好的几组,再用相同超参数跑一次全训练集(不划分验证集),最后预测公开测试集。
3. 如果公开测试集分数依然低于预期,可以考虑对公开测试集做同样的特征工程后,检查其分布是否与训练集有明显差异(例如年龄分布、缺失率)。如有差异,需要针对测试集做适配(如用训练集的统计量填充测试集,而不是重新计算测试集的中位数)。
---
一句话总结:不要迷信公开测试集分数,它只是参考。回到本地交叉验证,系统化地做特征工程和超参数调优,最后在公开测试集上跑一次即可。
概念解释
· 公开排行榜 (Public LB):在比赛进行时可见,通常只使用了测试集中较小的一部分(例如20%-30%)来计算分数。它让你能了解模型的大致水平,但更接近一个“热身赛”的结果。
· 私有排行榜 (Private LB):在比赛截止后才会揭晓。它基于测试集中剩余且一直保密的大部分数据来计算最终分数。奖项的最终归属完全由私有排行榜来决定。
设置私有排行榜的核心目的,就是评估模型的泛化能力 (Generalization),确保获奖模型真正学到了数据中的内在规律,而不仅仅是“背”下了公开测试集的答案。
🌊 什么是“排行榜地震”?
有时你会发现,比赛结束后最终排名和之前看到的公开排名发生了巨大变化,这种现象被称为“排行榜地震”。在历史比赛中,甚至出现过公开排行榜第1485名的队伍,最终凭借其模型强大的泛化能力一跃成为冠军的例子。这正是验证私有测试集重要性的典型例子。
🧭 实战建议:如何应对双排行榜?
1. 专注本地验证:调优时,请信任你自己精心设计的、更科学的本地交叉验证 (Cross-Validation) 分数,而不是公开排行榜的分数。
2. 勿盲目追榜:不要为了追求公开排行榜的高分而反复提交“试答案”,这是一种捷径但通常效果适得其反。
在刚才的对话中提到的 CV,指的是 交叉验证 (Cross-Validation),这是一种评估模型泛化能力的统计方法。
📌 简单理解
交叉验证就是多次划分训练集和验证集,反复训练和验证,最后取平均分数。它能告诉你:你的模型在不同的数据子集上表现是否稳定,而不是碰巧在一个验证集上分数高。
🧪 最常用的方法:K折交叉验证 (K-Fold CV)
1. 把训练数据随机分成 K 份(通常 K=5 或 10)。
2. 每次拿出其中 1 份作为验证集,其余 K-1 份作为训练集。
3. 重复 K 次,每次都换一个验证集。
4. 最后计算 K 次验证分数的平均值 作为该模型的最终评估。
✅ 为什么用它?
· 更可靠:单次划分可能运气好(验证集简单)或运气差(验证集难),K折平均后更接近真实水平。
· 数据利用率高:每个样本都被用作过验证,适合小数据集(如泰坦尼克号只有891条)。
· 减少过拟合风险:当你在公开排行榜分数低时,先用交叉验证确认是模型真差还是只是验证集选得不好。
💡 在 W&B Sweeps 中怎么用?
把训练函数里的 train_test_split 改成 KFold 循环:
```python
from sklearn.model_selection import KFold
kf = KFold(n_splits=5, shuffle=True, random_state=42)
scores = []
for train_idx, val_idx in kf.split(X_train):
X_tr, X_val = X_train.iloc[train_idx], X_train.iloc[val_idx]
y_tr, y_val = y_train.iloc[train_idx], y_train.iloc[val_idx]
model.fit(X_tr, y_tr)
scores.append(accuracy_score(y_val, model.predict(X_val)))
wandb.log({"cv_mean_acc": np.mean(scores)})
```
这样你优化的是 5折平均准确率,比单次验证集分数更可信。
⚠️ 注意
· CV 会增加计算量(K 倍时间),但对小数据集完全值得。
· 如果数据有时序(如股票预测),不能用随机 K 折,要用 时间序列交叉验证。
关注我,及时获取后续更新QA.
