4.2 数据切分、交叉验证与泄漏:评估集要模拟模型将面对的未知
如果同一任务被日志拆成几十行,随机切分可能让训练集和测试集各持一部分。模型认出任务 ID 和固定模式就能得高分,却没有回答“新任务上怎样”。测试数据换了文件夹,未必就成了陌生人。
切分不是固定的 60/20/20 配方。它是一场部署模拟:模型未来要面对新记录、新时间、新队伍还是新站点,决定了隔离单位。
本课目标
- 给 training、validation/CV 和 test 分配不同职责;
- 根据独立、分组、时间和空间结构选择切分;
- 把 preprocessing、feature selection 和 calibration 放入正确边界;
- 识别反复查看测试集造成的适应性过拟合。
1. 三类数据职责
- training:拟合模型和预处理参数;
- validation/CV:选择特征、算法、超参数、阈值和停止点;
- test:方案冻结后估计整个选择流程的泛化表现。
如果根据 test 结果改变模型、特征或论文叙事,test 已参与选择。此时需要新的独立评估数据,或明确标注探索性结果;事后在同一批已看过的数据上套 nested CV,不能抹掉人的选择。Nested CV 应在选择流程开始前设计。
固定百分比不是目标。样本小可用交叉验证,时间变化快可能用多个 rolling windows,测试集还要足够覆盖关键群体与罕见失败。
2. 随机切分的适用条件
随机行切分近似假设样本可交换,且部署目标与当前分布相似。分类可 stratify label 以减少类别比例波动,但:
- 不保证所有重要交叉群体平衡;
- 不处理同一实体重复;
- 不模拟未来漂移;
- 不能修复选择偏差。
随机种子只保证某次伪随机过程可复现,不证明结果稳健。可重复多次切分报告分布。
3. Group split
同一用户、设备、队伍、病人或文档的多行高度相关,应按泛化目标把完整 group 放在同一 fold。
下面是 Python 3 + scikit-learn 片段(partial),依赖 NumPy 数组 X、y、team_id,样本数相同且至少有五个不同团队。它检查分组隔离,尚未训练模型。
from sklearn.model_selection import GroupKFold
cv = GroupKFold(n_splits=5)
for train_idx, valid_idx in cv.split(X, y, groups=team_id):
assert set(team_id[train_idx]).isdisjoint(team_id[valid_idx])若一人属于多个家庭/站点,group 定义可能重叠,需要连接分量或更高层隔离。只按单个字段 group 可能仍有泄漏。
4. 时间切分
预测未来时,训练应早于验证。Rolling/expanding window 可评估多个时期:
train Jan–Mar → validate Apr
train Jan–Apr → validate May
train Jan–May → validate Jun还要设置:
- label maturation:训练样本标签已完整观察;
- feature/label gap:防止重叠窗口泄漏;
- embargo:金融等重叠事件场景隔离邻近期;
- late-arriving data:历史回放只使用当时可用版本。
例如在 4 月 1 日预测未来,标签观察窗为 30 天,则 3 月 31 日任务的完整标签此时尚不存在,不能拿它训练。若标签还要延迟两天才落库,成熟边界也要相应前移。简单按任务时间切最后 20%,不会自动处理这些可用时间。
5. 空间与站点泛化
地理邻近会造成空间相关。留出相邻网格的随机点可能过于容易,可采用区域 block 和缓冲带。
6. Preprocessing 必须在 fold 内 fit
会从数据学习参数的步骤都属于模型选择过程:
- imputation、scaling、encoding;
- feature selection、PCA;
- target encoding;
- resampling/SMOTE;
- calibration 和 threshold selection。
下面的 partial 片段构造预处理和模型的 Pipeline。把整个对象交给交叉验证函数后,各折会分别拟合自己的中位数和缩放参数。
from sklearn.pipeline import Pipeline
from sklearn.impute import SimpleImputer
from sklearn.preprocessing import StandardScaler
from sklearn.linear_model import LogisticRegression
pipeline = Pipeline([
("impute", SimpleImputer(strategy="median")),
("scale", StandardScaler()),
("model", LogisticRegression(max_iter=1000)),
])交叉验证 pipeline,每折会重新 fit 前处理。先对全表标准化/筛选再 CV,即使没直接看 label,也让验证折影响转换;目标编码和监督选择泄漏更严重。预处理边界见 scikit-learn 的泄漏示例。
7. 重采样只能在训练折
对少数类 oversampling、undersampling 或 synthetic sampling 应在每个训练 fold 内做。先 SMOTE 再切分会让一个原样本及其合成邻居跨 fold,显著乐观。
评估集应保留部署时自然分布。若业务会改变采样/筛选,还需用权重或单独目标分布评估。
8. Nested cross-validation
同一组 CV folds 既选超参数又报告最佳分数,会产生选择乐观。Nested CV:
- inner loop 选择 pipeline 和超参数;
- outer loop 评估整个选择过程。
成本较高,但小数据、候选多且需要可靠比较时很重要。最终模型可在全部开发数据上按选定流程重训,独立 test 仍只使用一次。
9. 数据去重不能跨越 test 边界偷看
先对全数据做近重复检测可能利用 test 内容来修改 training。正确流程取决于目标:
- 全局来源层去除真正重复采集,可在切分前,规则与原因要固定;
- 语义近重复/相似图像应先分 group,再按 group 切;
- 根据 test 表现发现并删除“麻烦样本”属于污染。
版本、哈希、来源 ID 和去重簇应保留以审计。
10. Test set 的操作纪律
访问者与用途受限
评估脚本和主指标预先冻结
只返回必要聚合,避免逐样本反复调试
每次访问记录模型、数据和理由
测试后变更必须重新建立评估边界
测试分布与部署变化时重新取样测试集不是永恒标准。随着团队反复公开 benchmark 成绩,组织知识也会对它过拟合。
常见误区
- 拆成三份就没有泄漏:切分单位和所有 fit 步骤同样重要。
- stratify 解决分布问题:它只约束指定标签比例。
- 随机种子固定就可靠:可能只是复现一次偶然切分。
- test 只看不训练所以可多看:人的选择也会过拟合。
练习
- 对重复任务日志比较行随机和 GroupKFold 分数。
- 为 30 天 label window 设计时间 split 与 gap。
- 演示全数据 feature selection 与 fold 内选择的差异。
- 为测试集建立访问日志和“何时需要新 test”规则。
小结
评估可信度来自隔离真实未知,而不是文件名叫 test.csv。时间、实体、空间和所有数据学习步骤都要与部署目标一致。
下一课用学习曲线和误差分解诊断欠拟合、过拟合与分布变化,并选择对症控制。