KFold、StratifiedKFold 与重复交叉验证
回归常用 KFold,分类用分层折保持类别比例,重复 CV 检查随机划分敏感性。
学习前需要掌握
背景与问题
同一个模型换一种划分器,实际上可能在回答另一个泛化问题。随机留出新行、留出患者、按时间向前预测,不能只因为都叫“交叉验证”就互换。
划分器首先匹配数据依赖结构,再考虑类别比例、折数与计算预算。折数更多不自动意味着估计更真实。
概念定义
KFold 不保证类别比例;StratifiedKFold 近似保持 y 分布;RepeatedStratifiedKFold 使用多组随机折。
直观理解
普通分组只均分人数,分层分组还尽量让每组类别配比相似。
核心原理
KFold 按行分成若干互斥验证块,shuffle 决定是否先打乱。StratifiedKFold 尽量维持各类比例,减少某折缺类造成的计算问题,但不会识别同一实体或时间泄漏。
ShuffleSplit 反复随机抽取训练验证集合,同一样本可能多次验证,也可能从未验证。RepeatedStratifiedKFold 则重复多轮分层 K 折;这些额外分数能观察划分敏感性,但不构成同样数量的独立实验。
GroupKFold 限制组不跨训练验证,TimeSeriesSplit 限制时间方向。若同时存在群组和时间关系,可能需要组合或自定义索引,不能通过增加 shuffle 来解决。
K 受最少类别数量、独立组数与训练预算约束。分层分类时少数类样本过少会使折内支持不足;近似保持比例只是工程约束,不是更可靠置信区间的保证。应保存实际索引并检查每折样本与类别分布。
数学表达
核心表达
5/10 折是常见起点,最终由数据量、少数类和计算成本决定。
变量含义
变量定义与本页概念定义一致,代码中的同名变量保持相同语义。
计算步骤
- 1识别任务类型
- 2检查最少类别数
- 3选折数
- 4决定 shuffle/seed
代码对应位置
示例 1检查重复分层划分的数量与类别支持:完整构造不平衡类别,检查每行在重复分层划分中被验证的次数。
案例:两轮 3 折不等于 6 份独立数据
构造 18 行,其中负类 12 行、正类 6 行。两轮 3 折分层划分一共生成 6 个训练验证对,每次验证 6 行,包含负类 4 行与正类 2 行。
每轮所有行各验证一次,两轮后每行验证两次,训练数据仍大量重叠。可以用它观察随机划分波动,但不能声称样本量扩成了原来的两倍,也不能把这些索引直接交给要求每行恰好验证一次的 cross_val_predict。
计算与实现步骤
- 识别任务类型
- 检查最少类别数
- 选折数
- 决定 shuffle/seed
- 必要时重复
- 报告所有分数
代码实现
示例 1
检查重复分层划分的数量与类别支持
example_01.py用途:完整构造不平衡类别,检查每行在重复分层划分中被验证的次数。
import numpy as np
from sklearn.model_selection import RepeatedStratifiedKFold
X = np.arange(18).reshape(-1, 1)
y = np.array([0] * 12 + [1] * 6)
cv = RepeatedStratifiedKFold(n_splits=3, n_repeats=2, random_state=42)
splits = list(cv.split(X, y))
counts = np.zeros(len(y), dtype=int)
for train, valid in splits:
counts[valid] += 1
assert np.bincount(y[valid]).tolist() == [4, 2]
print("split_count:", len(splits))
print("first_train_valid:", (len(splits[0][0]), len(splits[0][1])))
print("validations_per_row:", sorted(set(counts.tolist())))代码解析
解析始终位于完整代码下方,并按实际代码段逐项对应。
输入数据与任务
完整构造不平衡类别,检查每行在重复分层划分中被验证的次数。
X- 完整特征矩阵,每行一个样本、每列一个特征。
y- 目标标签或连续目标值。
Step 1 · 1–2 行
导入当前步骤需要的数值计算、预处理、模型或评价工具。依赖集中写在代码开头,便于复现。
import numpy as np
from sklearn.model_selection import RepeatedStratifiedKFoldStep 2 · 4–14 行
按学习率和梯度更新参数。步长过小会缓慢,过大会越过最低点并震荡或发散。
X = np.arange(18).reshape(-1, 1)
y = np.array([0] * 12 + [1] * 6)
cv = RepeatedStratifiedKFold(n_splits=3, n_repeats=2, random_state=42)
splits = list(cv.split(X, y))
counts = np.zeros(len(y), dtype=int)
for train, valid in splits:
counts[valid] += 1
assert np.bincount(y[valid]).tolist() == [4, 2]
print("split_count:", len(splits))
print("first_train_valid:", (len(splits[0][0]), len(splits[0][1])))
print("validations_per_row:", sorted(set(counts.tolist())))预期输出或运行结果
本地 scikit-learn 1.8.0 的核对输出: split_count: 6 first_train_valid: (12, 6) validations_per_row: [2]
常见错误 · 4 条
- 默认 shuffle=True 就适合一切任务,忽略组和时间依赖。
- 少数类不足仍盲目提高折数,或只检查总样本数不检查独立组数。
- 把重复 CV 的折数当成独立实验数,夸大不确定性估计的精度。
- 确认样本轴、特征轴以及 X 与 y 的第一维完全对应。
实际应用
- 普通分类回归
常见错误
- 默认 shuffle=True 就适合一切任务,忽略组和时间依赖。
- 少数类不足仍盲目提高折数,或只检查总样本数不检查独立组数。
- 把重复 CV 的折数当成独立实验数,夸大不确定性估计的精度。
推荐学习资料
参考库不会生成虚假资源或无效外部链接。