5.6 交叉验证完全指南
交叉验证是机器学习中最基本的评估技术之一。它的核心思想很简单:不要只用一次划分来评估模型,多次划分取平均,结果更可靠。
5.6.1 为什么需要交叉验证
单次训练/测试划分的问题:
- 测试集恰好全是简单样本 → 模型看起来比实际好
- 测试集恰好全是困难样本 → 模型看起来比实际差
- 数据量少时,测试集太小,评估结果方差大,不可靠
- 你无法知道模型是在”学规律”还是”刚好这套划分效果好”
交叉验证通过多次划分和平均,解决了这些问题。每个数据点都有机会被测试,评估结果是对模型泛化能力的更可靠估计。
5.6.2 K-Fold 交叉验证的详细步骤
步骤 1:把数据集随机打乱,分成 K 等份(每份大小尽量相等)。
步骤 2:进行 K 轮训练和评估:
- 第 i 轮:用第 i 份作为验证集(测试集),其余 K-1 份合并作为训练集
- 训练模型,在验证集上评估,记录得分 s_i
步骤 3:最终得分 = K 轮得分的平均值。也可以计算标准差来衡量模型稳定性。
K=5 的完整示例(假设 1000 条数据,分成 5 份,每份 200 条):
第1轮: [训练: 份2+份3+份4+份5 (800条)] [测试: 份1 (200条)] → 得分 0.85
第2轮: [训练: 份1+份3+份4+份5 (800条)] [测试: 份2 (200条)] → 得分 0.83
第3轮: [训练: 份1+份2+份4+份5 (800条)] [测试: 份3 (200条)] → 得分 0.87
第4轮: [训练: 份1+份2+份3+份5 (800条)] [测试: 份4 (200条)] → 得分 0.84
第5轮: [训练: 份1+份2+份3+份4 (800条)] [测试: 份5 (200条)] → 得分 0.86
最终得分 = (0.85 + 0.83 + 0.87 + 0.84 + 0.86) / 5 = 0.85 标准差 = 0.016(得分波动小,模型稳定)
这个 0.85 比单次划分可靠得多。它不仅给出了”模型有多好”的估计,还通过标准差给出了”这个估计有多可靠”。
5.6.3 K 值的选择
K 值的选择是一个偏差-方差权衡:
K 太小(如 K=2):
- 训练数据只有一半,模型可能学不好(欠拟合)→ 评估结果偏悲观(高偏差)
- 但每次训练的数据量小,训练快
- 不推荐,除非数据量极大
K 太大(如 K=N,即留一法):
- 训练数据几乎等于全部数据,模型学得很好 → 评估结果可能偏乐观(低偏差)
- 但每轮训练数据几乎一样,评估结果高度相关 → 方差大
- 计算量巨大,需要训练 N 次
- 不推荐,除非数据量极小(N < 100)
K=5 或 K=10:工业界经验值。
- 在偏差和方差之间取得良好平衡
- 计算成本可接受
- 大多数研究论文使用 K=5 或 K=10
- K=10 比 K=5 略好(更多训练数据),但计算量翻倍
- 当数据量小于 1000 时,K=10 更合适;数据量大于 10000 时,K=5 足够
5.6.4 Stratified K-Fold:保持类别比例
普通的 K-Fold 随机划分,可能导致某一折中正例特别多或特别少——这对不平衡数据是灾难。
Stratified K-Fold:在划分时保持每一折的类别比例与原始数据一致。如果原始数据正例占 10%,每一折的正例也占约 10%。
为什么重要:假设正例只占 1%,普通 K-Fold 可能让某一折完全没有正例,那一折的评估结果完全没有意义。Stratified K-Fold 保证每一折都有正例。
在 Scikit-learn 中:
from sklearn.model_selection import StratifiedKFold, cross_val_score
skf = StratifiedKFold(n_splits=5, shuffle=True, random_state=42)
scores = cross_val_score(model, X, y, cv=skf, scoring='f1')
2026 年的建议:对于分类问题,几乎总是使用 Stratified K-Fold 而不是普通 K-Fold。shuffle=True 在划分前打乱数据,random_state=42 保证结果可复现。
5.6.5 留一法(LOOCV, Leave-One-Out Cross-Validation)
做法:每次留 1 个样本作为测试集,其余 N-1 个样本作为训练集。重复 N 次。
优点:
- 训练数据几乎等于全部数据(N-1 个),偏差最小
- 不受随机划分影响,结果完全确定(没有随机性)
缺点:
- 计算量巨大:需要训练 N 次模型,N=1000 就是 1000 次
- 方差大:每轮训练数据几乎一样,评估结果高度相关
- 对于大模型(如神经网络),完全不可行
适用场景:数据量极小(N < 100)且模型训练很快(如线性回归、KNN)。2026 年,LOOCV 在深度学习时代几乎不被使用,但它在统计学和传统机器学习中仍有理论价值。
5.6.6 时间序列交叉验证
时间序列数据不能随机打乱——过去的数据训练,未来的数据测试。否则会造成”用未来预测过去”的数据泄露。
TimeSeriesSplit(滚动预测):
- 第 1 轮:用前 20% 的数据训练,预测第 21-30% 的数据
- 第 2 轮:用前 30% 的数据训练,预测第 31-40% 的数据
- 第 3 轮:用前 40% 的数据训练,预测第 41-50% 的数据
- …以此类推
关键原则:训练集的时间必须早于测试集的时间。永远不可以用”未来”的数据训练模型来预测”过去”。
Scikit-learn 实现:
from sklearn.model_selection import TimeSeriesSplit
tscv = TimeSeriesSplit(n_splits=5)
for train_idx, test_idx in tscv.split(X):
X_train, X_test = X[train_idx], X[test_idx]
# 训练和评估
适用场景:股票价格预测、天气预测、销售预测、任何有明确时间顺序的数据。
5.6.7 Group K-Fold:防止数据泄露
当数据中有”组”结构时,同一个组的数据不能同时出现在训练集和测试集中。
典型场景:
- 医疗数据:同一个病人的多次检查记录必须在同一折中——不能让模型在训练时看到病人的一些检查,测试时又看到同一个病人的其他检查。这会导致数据泄露(模型通过记住病人特征而不是学习病理特征来作弊)
- 用户行为数据:同一个用户的多次行为必须在同一折中
- 图片数据:同一场景的多张图片必须在同一折中
Group K-Fold:确保同一个 group 的所有样本要么全在训练集,要么全在测试集。
from sklearn.model_selection import GroupKFold
gkf = GroupKFold(n_splits=5)
for train_idx, test_idx in gkf.split(X, y, groups=patient_ids):
# groups 参数指定每个样本属于哪个组
5.6.8 嵌套交叉验证:用于模型选择
普通的交叉验证用于评估一个固定模型的性能。但如果你要同时选择模型(如选 SVM 还是随机森林)和调超参数,就需要嵌套交叉验证。
外层循环:评估模型的泛化性能(K 折) 内层循环:在训练数据上做超参数调优(K 折)
外层循环(5 折):
for 每一折:
训练集(外层)→ 内层循环(5 折):
在训练集上做超参数搜索
选出最优超参数
用最优超参数在训练集上训练
在测试集(外层)上评估
为什么需要嵌套:如果不用嵌套,你在整个数据集上选超参数,然后用同样的数据评估——评估结果会偏乐观,因为你用测试集”指导”了超参数选择。
代价:嵌套交叉验证的计算量是 K_outer × K_inner × 超参数组合数——非常大。对于大模型(如神经网络),嵌套交叉验证通常不可行,一般用单独的验证集来做超参数选择。
2026 年的实践:嵌套交叉验证主要用于传统机器学习(数据量小于 10000 时)。深度学习时代,通常用固定的 train/val/test 划分,在 val 上选超参数,在 test 上做最终评估。
Scikit-learn 一行代码完成 K-Fold:
from sklearn.model_selection import cross_val_score
scores = cross_val_score(model, X, y, cv=5, scoring='accuracy')
print(f"平均分: {scores.mean():.3f} (+/- {scores.std() * 2:.3f})")
← 上一节:5.5 混淆矩阵深度解析 | 下一节:5.7 特征工程实战 →
关联5
知识图谱局部图谱 · 点节点跳转