在训练机器学习模型时,我们常常关心一个问题:模型在未见过的新数据上表现如何?这时,K折交叉验证(K-Fold Cross-Validation
)就发挥了作用。它提供了一种评估模型泛化能力的方式,帮助我们避免模型在某一份数据上表现好,而在真实场景中表现不稳定。
1 原理
K折交叉验证是一种将数据集划分为K个子集的策略。模型在K轮中每次使用其中一个子集做验证,其余K-1个子集用于训练。这样每个数据点都至少被验证一次,能有效评估模型在不同数据分布下的表现。
1.1 介绍
K折交叉验证 vs 训练测试划分(Train-Test Split)
传统的训练-测试划分方法将数据划分为两部分,例如80%用于训练,20%用于测试。这种方法虽然简单快捷,但评估结果容易受到划分方式的影响,导致高方差。
K折交叉验证则能减少这种方差风险,因为它通过多轮验证来更全面地评估模型。下图展示了5折交叉验证的流程:将整个数据集平均划分为5份,每次选取其中一份作为测试集,其余四份作为训练集,重复5轮训练和评估,确保每个子集都被用作一次测试集。最终对5个模型的评估结果取平均,得到更稳定、全面的模型性能估计。
为什么使用K折交叉验证?
- 更稳定:不依赖于一次性的数据划分。
- 更全面:每个样本都参与训练和验证。
- 更抗过拟合:可发现某一数据划分下的过度拟合问题。
如何选择合适的K?
- K = 2 K=2 K=2或 3 3 3:适合资源有限或快速估计。
- K = 5 K=5 K=5或 10 10 10:实践中最常见的选择,平衡计算成本与评估稳定性。
- K = 20 K=20 K=20:评估更细致,但计算代价大,若子集太小可能导致高方差。
1.2 其他交叉验证方法
方法名 | 适用场景 | 描述 | 推荐使用情况 |
---|---|---|---|
标准K折 | 回归 & 分类 | 将数据均分为K份,轮流做测试集 | 适用于样本平衡的数据集 |
分层K折(Stratified ) | 分类为主 | 每一折中类别分布与整体相同 | 适合类别不均的数据集 |
留一法(LOOCV ) | 小数据集 | 每次留一个样本测试,剩余训练 | 精度高但计算开销大 |
留P法(Leave-P-Out ) | 小数据集 | 每次留出P个样本做测试 | 用于分析小样本变动影响 |
分组K折(Group K-Fold ) | 有分组结构的数据 | 保证同一组不被划分到不同集合 | 数据非独立样本(如用户) |
分层分组K折 | 有分组且类别不均 | 保持分组完整且类别比例一致 | 最复杂但最稳定,适用于真实世界复杂数据 |
方法名 | 适用场景 | 描述(做法 + 原理) | 推荐使用情况 |
---|---|---|---|
标准K折(KFold ) | 回归 & 分类 | 将数据随机打乱后平均分为K份,每一份轮流作为测试集,其余作为训练集。**每个样本恰好验证一次、训练K-1次。**适合数据分布均匀的情况。 | 适用于样本量适中、类别分布均衡的场景,如房价预测、图片分类等。 |
分层K折 (Stratified KFold ) | 分类为主 | 和标准K折类似,但每一折中类别的比例与整个数据集保持一致,比如整体正负样本比例是8:2,那每一折也会尽量接近这个比例。 | 适合类别不平衡的分类任务(如欺诈检测、医疗诊断),否则标准KFold会造成某些折全是某一类。 |
留一法 (Leave-One-Out, LOOCV ) | 小数据集 | 每次只留一个样本作为测试,其余所有样本用来训练。一共做n次(样本数量为n),结果更稳定但开销大。 | 适合数据很少但要求精度高的情况,比如医学研究中的小样本病人数据。 |
留P法(Leave-P-Out ) | 小数据集 | 和LOOCV类似,每次留出P个样本做测试,其余做训练。P可以是2、3等。更灵活控制测试集大小,适合观察数据敏感性。 | 主要用于模型鲁棒性分析、小数据实验设计,不常用于大规模评估。 |
分组K折 (Group KFold ) | 分组结构数据 | 每组数据具有相同特征(如来自同一用户),该方法会确保同一组不会同时出现在训练和测试集中,防止“数据泄漏”。 | 适合有“组”概念的数据(如多条记录属于一个用户),用于推荐系统、个性化模型等。 |
分层分组K折 (Stratified Group KFold ) | 分组且类别不均 | 结合分层K折和分组K折:**保持每折中类别比例一致,同时同一组数据不被拆分。**这是目前最复杂也最稳定的做法。 | 适用于真实世界复杂数据,如多用户、多类别医疗图像、教育数据,尤其适合深度学习前的数据准备。 |
不同的交叉验证方法,是为了解决以下几个数据结构问题:
问题类型 | 举例 | 推荐方法 |
---|---|---|
类别不平衡 | 正负样本比例严重失衡 | StratifiedKFold |
数据量很少 | 医疗、金融中仅有几十个样本 | LOOCV , Leave-P-Out |
数据成组 | 一个用户下有多个行为记录 | GroupKFold |
有组也有类别不均 | 多用户 + 多类别的教育/推荐/医疗数据 | StratifiedGroupKFold |
2 K-Fold代码实现
我们将使用 scikit-learn
库中的California Housing
数据集,这是基于1990年美国人口普查中加州地区的房屋信息整理而成的。其目标变量MedHouseVal
表示各区域的中位数房价,数据集中包含8个特征,分别为地区中位收入、房屋平均年龄、每户平均房间数、每户平均卧室数、总人口数、每户平均居住人数、地区纬度和经度,反映了加州各地的居住和人口特征。
- 加载数据集
from sklearn.datasets import fetch_california_housing
data = fetch_california_housing()
- 准备特征与标签
import pandas as pd
X = pd.DataFrame(data.data, columns=data.feature_names)
y = data.target
- 设置KFold验证器
from sklearn.model_selection import KFold
k = 5
# 将数据划分为5个折叠
kf = KFold(n_splits=k, shuffle=True, random_state=42)
(1)shuffle=True
:在划分之前先对数据进行随机打乱。这一步非常重要,尤其是当数据原本有顺序(比如地理位置、时间序列)时,否则容易导致某一折分布不均。
假设你有一个按收入排序的数据集,样本从低收入到高收入排列:
[低, 低, 低, 中, 中, 中, 高, 高, 高]
如果你直接将这个数据集分为 3 折:
- Fold 1:低, 低, 低
- Fold 2:中, 中, 中
- Fold 3:高, 高, 高
每一折都只包含一种收入水平,不具备整个数据的代表性。模型在某一折上训练得到的规律,可能在另一折完全无效,导致评估不准确、方差大和结果不稳定
(2)random_state=42
:设置随机种子,确保每次运行时划分方式一致,实现结果可复现性。42是一个常见的固定值(可以设为任意整数)。
- 初始化模型
from sklearn.linear_model import LinearRegression
model = LinearRegression()
- 执行交叉验证并评估R²得分
from sklearn.model_selection import cross_val_score
scores = cross_val_score(model, X, y, cv=kf, scoring='r2')
参数设置如下:
参数 | 含义 |
---|---|
model | 你要评估的模型对象,比如 LinearRegression() |
X | 特征变量(DataFrame或NumPy数组) |
y | 目标变量(标签) |
cv=kf | 指定交叉验证的划分方式,这里传入KFold 对象kf (即5折) |
scoring='r2' | 指定使用的评分指标,这里是回归任务常用的 R 2 R^2 R2 分数(决定系数) |
返回值scores
是一个数组,包含每一折交叉验证的评估分数;
- 计算平均R²分数
import numpy as np
average_r2 = np.mean(scores)
print(f"R² Score for each fold: {[round(score, 4) for score in scores]}")
print(f"Average R² across {k} folds: {average_r2:.2f}")
输出如下:
R² Score for each fold: [np.float64(0.5758), np.float64(0.6137), np.float64(0.6086), np.float64(0.6213), np.float64(0.5875)]
Average R² across 5 folds: 0.60
平均 R 2 R^2 R2分数为0.60,意味着模型能解释数据中约60%的方差。在实际回归任务中,0.60是一个中等偏上的结果,表示模型有一定预测能力,但仍有提升空间。
3 总结
K折交叉验证是一种比传统训练测试划分更稳定、可信的模型评估方法。它可以最大程度降低模型对某一特定划分的依赖,从而提升模型在真实场景下的表现力和稳定性。