交叉验证与数据划分策略
一句话定义
交叉验证把数据反复划分成「训练 + 验证」组合(k 折 = 每折轮流当验证集),用 k 次验证误差的均值与波动来估计泛化性能,是超参数选择与模型比较的标准协议。
为什么重要
「模型好不好」的唯一严肃答案是样本外表现。单次留出评估受划分运气影响大,k 折 CV 把每条样本都用上验证,方差显著更小。它还是防数据泄漏的枢纽概念:预处理的拟合范围、调参的嵌套结构都围绕「信息只能从训练流向验证」这条铁律展开。
前置知识
偏差-方差分解与过拟合 的过拟合与验证误差概念。
核心概念
- 留出法(holdout):一次划分训练/测试(常 8:2),快但方差大。
- k 折交叉验证:等分 k 份,轮流验证;R̂cv = 1k∑j=1k R̂j。
- 分层抽样(stratify):每折保持类别比例一致,分类任务默认应该用。
- 留一法(LOO):k = n,无偏但方差大、代价高。
- 时序划分 / TimeSeriesSplit:训练集必须全部早于验证集,禁止「用未来预测过去」。
- 组划分(GroupKFold):同一组(同一用户/病人)的样本不许跨训练与验证。
- 嵌套 CV:外层估性能、内层调参,防止「调参信息渗入性能估计」。
直观类比
只用一场模拟考(holdout)判断学生水平,碰巧考到会的就高估、考到不会的就低估;k 折 CV 相当于用 k 场不同套卷的平均分评估——每个知识点都被考过一次。但注意:如果按 k 场考试的平均分「挑学生」(选超参),这个平均分就不再是无偏成绩,需要再参加一场全新期末考(测试集)——这正是外层测试集与嵌套 CV 存在的理由。
原理与机制
k 折 CV 的误差估计 R̂cv = 1k∑j 1nj∑i ∈ Fj L(yi, f-j(xi)),其中 f-j 是去掉第 j 折训练的模型。它与真泛化误差的偏差来源:每折训练集只有 (1 - 1/k)n 个样本,模型比「全量训练版」略差,故估计略偏悲观(LOO 偏差最小但方差最大)。标准三区协议:测试集锁进保险柜只开一次;训练区内部用 CV 做一切决策(选模型、调参、选阈值);最终用训练区全量重训、在测试集上报告。重复 CV(RepeatedKFold,换种子重复 r 次)进一步压评估方差,代价线性增长。划分方式必须反映数据结构:时间序列乱折 = 时间泄漏;用户级数据乱折 = 组泄漏,两者都是 kp-025 的重灾区。
公式与推导
k 折 CV 估计:
选超参数 λ 时在网格上对每个候选重复上式并取最小:λ̂ = argmin_λ R̂cv(λ)。若再用同一个 R̂cv(λ̂) 报告性能,会因「在多个候选里挑了最好的」而系统性偏乐观(选择偏差)——修法即嵌套 CV:内圈选参、外圈评分。
图示
数据 5 折 (分层) 测试集 ◫ 永远只碰一次
折1 [测试][训练][训练][训练][训练] → 模型1 → 误差 e₁
折2 [训练][测试][训练][训练][训练] → 模型2 → 误差 e₂
折3 [训练][训练][测试][训练][训练] → 模型3 → 误差 e₃
折4 [训练][训练][训练][测试][训练] → 模型4 → 误差 e₄
折5 [训练][训练][训练][训练][测试] → 模型5 → 误差 e₅
CV 估计 = mean(e₁..e₅) ± std实例或案例
分层 k 折与 RepeatedKFold 的稳定性对比:
from sklearn.datasets import load_breast_cancer
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import (cross_val_score, KFold,
StratifiedKFold, RepeatedKFold)
import numpy as np
X, y = load_breast_cancer(return_X_y=True)
m = RandomForestClassifier(n_estimators=200, random_state=0)
plain = KFold(n_splits=5, shuffle=True, random_state=0)
strat = StratifiedKFold(n_splits=5, shuffle=True, random_state=0)
rep = RepeatedKFold(n_splits=5, n_repeats=10, random_state=0)
for name, cv in [("普通5折", plain), ("分层5折", strat), ("分层5折×10", rep)]:
s = cross_val_score(m, X, y, cv=cv)
print(f"{name}: 均值={s.mean():.4f} 标准差={s.std():.4f}")重复 CV 的标准差明显更小——模型比较时结论更不易被划分运气翻转。
常见误区
- 在整个数据集上先做标准化/特征选择再 CV:预处理信息跨折泄漏,评估虚高(正确姿势见 kp-025 与 Pipeline)。
- 调参和报告共用同一个 CV 分数:选择偏差,必须嵌套或留出独立测试集。
- 分类任务用不打乱、不分层的折:类别分布失衡时某折可能几乎没正类,误差估计剧烈抖动。
- 时序数据用
shuffle=True的 KFold:直接构造时间机器,线上指标必然跳水。
与其他知识点的关系
超参数调优:网格、随机与贝叶斯搜索 的所有调参器都把 CV 当内燃机;数据泄漏与常见评估陷阱 的核心防御就是把全部预处理塞进 Pipeline 走 CV;分类评估指标:混淆矩阵、Precision/Recall 与 ROC-AUC 的指标在每折上计算再汇总;端到端机器学习工作流与 Pipeline 把本节协议封装成标准工作流。
自测题
- k 折 CV 为什么比单次留出更稳?偏差方向如何?
- 要点:每条样本都参与验证、评估是 k 次平均,方差更小;每折训练样本少于全量,估计略偏悲观(k 越大偏差越小、计算越贵)。
- 什么数据结构必须改变划分策略?
- 要点:时间依赖(TimeSeriesSplit 前向链)、组聚集(GroupKFold 整组迁移)、类别不平衡(StratifiedKFold)。
- 为什么调参后必须用独立测试集或嵌套 CV?
- 要点:在同一评估信号上反复选择会利用其噪声,产生乐观偏置;外层未参与选择的评估才是泛化的无偏估计。
延伸阅读
Hastie 等《The Elements of Statistical Learning》第 7.10 节;周志华《机器学习》第 2.2 节。