2. 留出法与简单交叉验证的实现

说到模型评估,最直白的做法就是——把手头数据切一块出来当“考卷”,剩下的当“课本”。留出法就是这么干的。我刚开始做机器学习项目时,第一个想到的评估方式就是这个。但很快我就发现,单凭一次“考试”成绩定模型生死,风险其实不小。

2.1 留出法:一次切分,一次评估

留出法的逻辑很简单:从原始数据中随机抽取一部分作为测试集(hold-out set),剩下的作为训练集。常见的切分比例是 70/30 或 80/20。如果是分类问题,最好做分层抽样(stratified split),让两个集中的类别比例尽量一致。

实现步骤:

  1. 打乱数据集(保证随机性)
  2. 按比例切分
  3. 在训练集上训练模型
  4. 在测试集上计算评估指标

用 scikit-learn 两行就能搞定:

from sklearn.model_selection import train_test_split

X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=0.3, random_state=42, stratify=y
)

random_state 控制随机种子,stratify 保证类别比例。嗯,这里面有个坑——随机种子不同,结果可能差很多。我去年帮一个团队排查模型性能波动问题,最后发现就是他们每次跑代码忘记固定种子,导致测试集划分变了,报告里的准确率跳了 5%。

⚠ 避坑指南:我曾经在一个回归任务上只用一次留出法,结果测试集恰好包含了几个极端值,模型表现极差。后来才发现是随机种子没固定,换了个种子评分立刻正常。所以,永远不要只依赖单一一次划分来下结论。

2.2 简单交叉验证:重复留出,降低运气成分

留出法只有一次切分,结果太依赖“手气”。那好办——多切几次,取平均。这就是简单交叉验证(也叫重复留出法,Repeated Hold-Out)。

做法:

  1. 重复 N 次留出法
  2. 每次随机切分(允许样本重叠)
  3. 记录每次的评估得分
  4. 计算均值和标准差

你看,这样一来,即便某次切分特别差,其他几次也能把结果拉回来。我个人的习惯是 N 取 10 或 20,太少缺乏统计意义,太多计算量吃不消。

代码实现:

import numpy as np
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score

scores = []
n_repeats = 20

for i in range(n_repeats):
    X_train, X_test, y_train, y_test = train_test_split(
        X, y, test_size=0.3, random_state=i
    )
    model.fit(X_train, y_train)
    y_pred = model.predict(X_test)
    scores.append(accuracy_score(y_test, y_pred))

mean_score = np.mean(scores)
std_score = np.std(scores)

print(f"平均准确率: {mean_score:.4f} ± {std_score:.4f}")

注意这里我用了 random_state=i 让每次划分不同。如果数据集比较大,建议加上 stratify=y。你想想,如果分类任务里某一类样本很少,随机切分很容易把这类样本全分到测试集或训练集去,导致评估不可信。

为什么不是 k 折交叉验证? 简单交叉验证每次划分是独立随机的,样本可以重复出现在不同轮次的训练集或测试集中。而 k 折是严格不重叠的。简单交叉验证的优势在于实现极其简单,且能通过增加重复次数来逼近真实性能。缺点也很明显——每次切分的训练集/测试集比例不一定相同,且样本利用率不高。

2.3 留出法与简单交叉验证的对比

对比维度 留出法 (Hold-Out) 简单交叉验证 (Repeated Hold-Out)
执行次数 1 次 N 次(通常 10~30)
结果可靠性 低,依赖单次划分 较高,提供均值±标准差
计算开销 极低 中等(N 倍留出法)
样本重叠 无(单次) 允许重叠,可同一样本多次出现
适用场景 数据量极大、快速验证 小/中等数据集、需要稳定估计

💡 我的经验:如果你时间紧,先用留出法跑一次看看趋势;正式汇报或调优时,至少用简单交叉验证重复 10 次。有一次我在客户现场演示模型效果,只做了单次留出,结果某个类别的准确率特别低,客户当场质疑。回去补了 20 次重复交叉验证,发现那只是偶然偏差——所以现在我对自己交付的模型,都习惯输出“均值±标准差”,客户反而觉得更专业。

2.4 实现流程总览

下面这张图总结了留出法和简单交叉验证的核心流程。左边是单次切分,右边是多次切分后聚合结果。

留出法与简单交叉验证工艺流程对比 留出法 vs 简单交叉验证流程对比 原始数据集 训练集 (70%) 测试集 (30%) 训练模型 预测与评估 一个评分 简单交叉验证(N次重复) • 重复左侧步骤 N 次 • 每次随机种子不同 • 计算 N 个评分的均值和标准差 右箭头表示流程方向,虚线表示进入重复循环

2.5 什么时候该用留出法,什么时候改用重复留出?

我列几个实际场景,你自己判断:

  • 数据量超级大(百万级以上):留出法就够了,单次切分已经足够稳定,重复计算太浪费资源。
  • 数据量中等(几千到几十万):强烈建议用简单交叉验证重复 10~20 次,结果更可信。
  • 数据量很小(几百条):留出法和简单交叉验证都不太够用,这时候我们后面会讲的 k 折交叉验证更合适。

⚠ 注意:简单交叉验证虽然重复多次,但每次划分是独立的,样本可能在不同轮次中重复出现在测试集或训练集。这会导致评估结果之间不独立,统计推断时要注意。不过对于实际工程来说,只要报告均值和标准差,已经比单一留出好很多了。

最后说一句:留出法是最简单的起点,简单交叉验证是它的升级版。但两者都有一个共同缺点——它们都没有充分利用所有数据做训练。下一类方法(k 折)才是更主流的做法。不过打好基础总是没错的,你先把这两种掌握了,后续学新方法会快很多。