留出法 (Hold-out) 原理与实现

交叉验证家族里最基础的方法,就是留出法。我刚开始做机器学习时,最先接触的就是它。说白了,就是把手头的数据集按比例切成两块:一块用来训练模型,另一块用来评估效果。

什么是留出法

留出法的操作特别简单。你手里有 N 个样本,先随机打乱,然后按某个比例(常见 7:3 或 8:2)分成训练集和测试集。训练集用来拟合模型,测试集用来算准确率、F1 等指标。

核心思想: 训练集和测试集互不重叠,保证评估结果的独立性。

我在项目中遇到过一种情况:数据量只有几千条,留出 30% 做测试,结果测试集太小,指标波动很大。后来我改用分层抽样才缓解了这个问题。嗯,留出法的随机性是个双刃剑——简单,但不够稳。

留出法的数学定义

设数据集 D = {(x₁,y₁), (x₂,y₂), …, (xn,yn)}。按比例 α 划分训练集 Dtrain 和测试集 Dtest,满足:

  • Dtrain ∪ Dtest = D
  • Dtrain ∩ Dtest = ∅
  • |Dtrain| = ⌊α·n⌋,|Dtest| = n – ⌊α·n⌋

你想想看,如果数据本身有类别分布偏斜,随机切分很可能让测试集里某个类别样本数为零。那评估结果就毫无意义了。所以很多工程师做留出法时,会用分层抽样保证类别比例一致。

完整流程(附 SVG 结构图)

下面这张图把留出法的执行步骤画清楚了。我个人习惯用这种流程图来跟团队对齐实验方案,避免理解偏差。

留出法执行流程 原始数据集 D 随机打乱 + 可选分层抽样 训练集 D_train 比例 α(如 0.8) 测试集 D_test 比例 1-α(如 0.2) 训练模型 进行评估 训练好的模型 f̂ 评估指标(准确率等) 注意:留出法仅做一次切分,评估结果受随机划分影响较大。 数据量较小时建议使用 K 折交叉验证替代。

Python 代码实现

用 scikit-learn 实现留出法非常方便。model_selection 里的 train_test_split 就是干这个的。不过我习惯自己加一步分层参数,防止某些极端情况。

from sklearn.model_selection import train_test_split
from sklearn.datasets import load_iris
from sklearn.tree import DecisionTreeClassifier
from sklearn.metrics import accuracy_score

# 加载数据
iris = load_iris()
X, y = iris.data, iris.target

# 留出法切分:训练80%,测试20%
X_train, X_test, y_train, y_test = train_test_split(
    X, y, 
    test_size=0.2,       # 测试集比例
    random_state=42,     # 固定随机种子,保证可重复
    stratify=y           # 分层抽样,保持类别比例
)

# 训练模型
clf = DecisionTreeClassifier(random_state=42)
clf.fit(X_train, y_train)

# 评估
y_pred = clf.predict(X_test)
acc = accuracy_score(y_test, y_pred)
print(f"测试集准确率: {acc:.3f}")

输出结果大概长这样:

测试集准确率: 0.967

经验之谈: random_state 一定要固定。我曾经在对比不同模型时忘记设置种子,结果每次跑出来指标都不一样,根本没法公平比较。另外,如果数据有类别不平衡,stratify=y 能救命。

留出法的优缺点

很多人觉得留出法太简单不值得用。其实在数据量足够大(比如几十万条以上)时,留出法完全够用,而且计算成本低。我总结了一张表,便于你对比:

优点 缺点
实现简单,几行代码搞定 评估结果方差大,对随机切分敏感
计算速度快,适合大数据 数据利用率低,大部分样本只用于训练
可重复性好(固定随机种子后) 不适用于小样本场景(<1000 条时不稳定)
直观易懂,适合快速实验 无法有效估计模型泛化性能的稳定性

避坑指南

我曾经在做一个医疗影像分类项目时,直接用默认的 train_test_split,没有做分层。结果测试集里某个罕见类别一个样本都没有,准确率高达 99%,但实际部署后模型在那个类别上直接崩了。所以,下面这几个坑一定要避开:

  • 不要忘记分层 – 分类问题一定要设 stratify=y,回归问题可以不做。
  • 不要随意改比例 – 测试集太小(<10%)会导致评估不可靠,太大(>40%)又会浪费训练数据。7:3 或 8:2 是比较稳妥的。
  • 不要在训练前看到测试集 – 数据预处理(标准化、PCA 等)一定要先 fit 在训练集上,再 transform 测试集。我曾经看到有人直接对整个数据集做归一化,那本质上就泄露了测试分布。

重要: 留出法仅仅是一次性划分,不能替代更稳定的交叉验证。如果你在写论文或做模型选型,建议至少用 5 折交叉验证来报告最终指标。留出法更适合探索性分析和快速原型验证。

何时选用留出法?

总结一下我的个人经验:

  • 数据量 > 10 万条,且分布相对均衡 → 留出法够用,省钱省时间
  • 数据量 < 2000 条 → 不要用留出法,改 K 折或留一法
  • 你需要快速比较几个模型初步效果 → 留出法配固定种子,半小时搞定
  • 正式发布模型前 → 必须用交叉验证或多次留出法重复评估