跳到内容

第 13 卷 / 第 4 章 / 第 2 课

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,样本数相同且至少有五个不同团队。它检查分组隔离,尚未训练模型。

python
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 可评估多个时期:

text
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。把整个对象交给交叉验证函数后,各折会分别拟合自己的中位数和缩放参数。

python
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 的操作纪律 ​

text
访问者与用途受限
评估脚本和主指标预先冻结
只返回必要聚合,避免逐样本反复调试
每次访问记录模型、数据和理由
测试后变更必须重新建立评估边界
测试分布与部署变化时重新取样

测试集不是永恒标准。随着团队反复公开 benchmark 成绩,组织知识也会对它过拟合。

常见误区 ​

  • 拆成三份就没有泄漏:切分单位和所有 fit 步骤同样重要。
  • stratify 解决分布问题:它只约束指定标签比例。
  • 随机种子固定就可靠:可能只是复现一次偶然切分。
  • test 只看不训练所以可多看:人的选择也会过拟合。

练习 ​

  1. 对重复任务日志比较行随机和 GroupKFold 分数。
  2. 为 30 天 label window 设计时间 split 与 gap。
  3. 演示全数据 feature selection 与 fold 内选择的差异。
  4. 为测试集建立访问日志和“何时需要新 test”规则。

小结 ​

评估可信度来自隔离真实未知,而不是文件名叫 test.csv。时间、实体、空间和所有数据学习步骤都要与部署目标一致。

下一课用学习曲线和误差分解诊断欠拟合、过拟合与分布变化,并选择对症控制。

Built with VitePress | Software Systems Atlas