机器学习进阶(6):训练集、验证集、测试集到底怎么分?模型为什么不能一边学一边偷看答案

很多人刚开始学机器学习的时候,都会有一个特别自然的想法:

既然数据这么宝贵,那我干脆把所有数据都拿去训练,不是更好吗?

这个想法看上去很合理,但真正做模型的时候,这往往是最容易把自己骗到的一步。

因为机器学习里真正重要的,从来都不是模型能不能把手头这批数据“做对”,而是它面对没见过的新数据时,还能不能做得对。

这就是为什么,我们总要把数据分成训练集、验证集、测试集。

这三个名字看起来像是流程上的术语,但它们背后其实对应的是机器学习里特别核心的一件事:
你得想办法让模型接受一次真正公平的考试。


1. 机器学习不是让模型背题,而是看它会不会做新题

我们先别急着讲训练集、测试集这些词,先把问题本身想清楚。

假设你现在做的是一个“预测学生能不能通过考试”的小模型。
你手上有 1000 个学生的数据,里面包括:

  • 每天学习多久
  • 平时作业完成情况
  • 课堂出勤率
  • 最后是否通过考试

你训练了一个模型,然后发现它在这 1000 个学生上表现很好,准确率 98%。

这听起来是不是很厉害?

不一定。

因为这里面有一个很关键的问题:
这 1000 条数据,到底是模型训练时看过的,还是没看过的?

如果这些数据模型在训练阶段全都见过,那这个 98% 很可能说明的不是“模型真的学会了规律”,而只是“模型把这些题记住了”。

你可以把它想象成这样:

  • 训练阶段:老师把 100 道题的题目和答案都发给学生
  • 测试阶段:老师又拿同样那 100 道题来考试
  • 学生考了 98 分

这当然不能说明他真的学得很好。
最多只能说明,他对这套题已经很熟了。

机器学习也是一样。

我们真正想知道的,是模型能不能处理训练时没见过的新样本
也就是说,我们想检验的是它有没有泛化能力


2. 训练集到底是干什么的

训练集这个名字其实挺直白的。

它就是:

专门拿来让模型学习的那部分数据。

模型在训练集上会不断做这些事:

  • 用当前参数做预测
  • 计算预测结果和真实结果的误差
  • 用损失函数衡量自己错了多少
  • 通过梯度下降之类的方法调整参数
  • 再预测、再调整、再更新

也就是说,训练集是模型真正“刷题”的地方。

如果你前面已经学过线性回归、逻辑回归、损失函数、梯度下降,那么这里就很好理解了:

训练集就是那批让模型不断试错、不断调参数的数据。

所以训练集的作用很明确:

它负责教模型。


3. 测试集是干什么的,为什么它必须“晚点再看”

测试集的作用,和训练集刚好不一样。

它不是拿来训练的,而是拿来检查模型到底学得怎么样的。

你可以把它理解成真正的“期末考试”。

这个考试有一个特别重要的前提:

考试题不能在平时训练的时候提前泄露。

为什么?

因为一旦模型训练时提前接触过测试集,测试集就失去了意义。
你最后算出来的准确率、误差、AUC 这些数字,也就没法真实反映模型面对新数据时的能力了。

所以测试集的原则非常简单,但也非常重要:

训练阶段不能碰测试集。

这里的“不能碰”,不只是说不能拿测试集去直接训练模型。
很多更隐蔽的问题,后面也会讲到,比如:

  • 用测试集结果反复改模型
  • 用整份数据一起做标准化
  • 让测试集的信息提前进入预处理流程

这些也都算“偷看答案”。


4. 那验证集又是干什么的

很多人学到训练集和测试集的时候,会觉得已经够了。

毕竟逻辑上好像很简单:

  • 一部分数据拿去训练
  • 一部分数据拿去测试

这不就完了吗?

但真实建模过程通常不是“训练一次,结束”。

你一般会不断试很多版本,比如:

  • 逻辑回归效果一般,要不要试试 KNN
  • KNN 里的 KKK 取 3 还是 5
  • 特征要不要再多加两列
  • 学习率要不要调小一点
  • 模型是不是太复杂了
  • 正则化强度是不是该改改

问题来了:

你根据什么来判断这些选择哪个更好?

很多初学者会下意识地说:
“那我每改一次,就去看测试集表现不就行了吗?”

这正是最容易踩的坑之一。

因为你一旦反复盯着测试集结果改模型,测试集就不再是“最终考试”了,它变成了你调模型时的参考依据。
这样一来,测试集的信息也会被你一点点“用掉”。

这时候,验证集就有用了。

验证集的作用是:

在训练过程中,帮你比较不同方案,决定哪个模型、哪个参数更合适。

所以三者的分工,其实可以很清楚地说成一句话:

  • 训练集:让模型学习
  • 验证集:帮你做选择
  • 测试集:做最终评估

这个分工一旦理解了,后面很多流程都会顺很多。


5. 一个很像现实考试的类比

如果你觉得上面的说法还是有点抽象,可以把这三个集合理解成一个很普通的备考过程。

训练集

就是你平时做的练习题。

你可以在上面犯错、改错、总结规律,甚至把一道题做很多遍都没关系。
因为它本来就是拿来练的。

验证集

就是模拟考试。

你用它来看看自己最近到底学得怎么样,也拿它来决定接下来要不要调整复习方式。

比如:

  • 最近阅读题做得差,是不是要多练阅读
  • 计算题失分多,是不是要把基础再补一补

测试集

就是正式考试。

正式考试的意义就在于,它应该尽量独立、尽量客观。
你不能每次模拟考完都去改正式考试题本身。

这个类比其实非常贴近机器学习的真实流程。

因为训练模型,本来就是一个不断练习、不断调整、最后做客观评估的过程。


6. 数据一般怎么分

最常见的分法有两种。

一种是直接三分:

  • 训练集 60%
  • 验证集 20%
  • 测试集 20%

或者:

  • 训练集 70%
  • 验证集 15%
  • 测试集 15%

另一种更常见的实际做法是:

先把测试集留出来,然后再从训练部分里切验证集。

比如:

  1. 先拿 80% 做训练+验证,20% 做测试
  2. 再从前面那 80% 里切出一部分做验证

这么做的好处是逻辑更清晰:
先把“最后考试”的那部分题封存起来,后面训练和调参都不要碰它。

到底是 6:2:2,还是 7:1.5:1.5,没有绝对标准。
真正要看的是:

  • 你的数据总量够不够
  • 任务复杂不复杂
  • 你对评估稳定性的要求高不高

数据量特别大的时候,怎么分都相对宽松一点。
但如果你的数据本来就不多,那这个问题就会变得更敏感。


7. 数据少的时候,为什么不能死板地硬切三份

这个问题特别现实。

假设你手头只有 300 条数据。
你要是硬切成:

  • 180 条训练
  • 60 条验证
  • 60 条测试

那很可能每一部分都不够用。

训练集太小,模型学不充分。
验证集和测试集太小,评估结果又容易波动。

这种情况下,你就会发现:
模型今天这个分法好一点,明天那个分法又差一点,结果很不稳定。

这也是为什么在小数据场景里,人们经常会用到交叉验证

交叉验证你后面可以单独写一篇,现在这里先保留一个基本认识就够了:

数据少的时候,不是不能评估,而是不能太机械。

机器学习很多时候不是照着固定模板切数据,而是要根据数据规模来决定更合适的评估方式。


8. 为什么大多数情况下要随机划分

再说一个很多人一开始不太会注意的点:
划分数据时通常要随机打乱。

为什么?

因为真实数据常常是有顺序的,而这种顺序未必是你想要的。

比如你有一份用户数据,前半部分刚好都是老用户,后半部分刚好都是新用户。
如果你直接按前 80% 和后 20% 来切,那训练集和测试集的分布就可能差很多。

再比如你在做房价预测,数据是按地段或者面积排序过的。
你不打乱直接切,最后就很可能出现:

  • 训练集主要是某一类房子
  • 测试集主要是另一类房子

这时候你的评估结果就会很奇怪,因为训练和测试已经不太像是从同一个总体里来的了。

所以在绝大多数普通任务里,都会先随机打乱,再划分数据。

这样做的目的不是“形式规范”,而是尽量让训练集、验证集、测试集都能代表整体数据分布。


9. 但有一类任务不能乱打乱:时间序列

这里有一个非常重要的例外。

如果你的任务和时间顺序强相关,比如:

  • 预测明天的销量
  • 预测下周的气温
  • 预测股票未来价格
  • 预测设备未来会不会故障

这时候你就不能把数据随便打乱。

因为真实场景里,模型永远是:

用过去的数据,预测未来的结果。

你要是把未来的数据打乱到训练集里,再拿过去的数据做测试,那等于模型提前知道了未来信息。
这在现实里根本不可能发生。

所以时间序列数据的划分思路通常是:

  • 用较早时间的数据做训练
  • 用中间时间的数据做验证
  • 用更晚时间的数据做测试

也就是说,时间序列里最重要的不是“随机”,而是“尊重时间顺序”。

这一点特别值得强调。
很多初学者会本能地拿 train_test_split 一切了之,但在时间相关任务里,这么做很可能直接把实验做歪。


10. 数据泄漏,到底是在“漏”什么

一说到数据划分,就绕不开一个特别关键的词:数据泄漏

这个词听起来好像很严重、很技术,但其实意思很朴素:

本来不该在训练阶段知道的信息,被模型提前知道了。

这就叫数据泄漏。

最常见的泄漏方式大概有这几种:

1)直接拿测试集去调模型

你每改一次参数,就去看测试集表现。
久而久之,你的模型其实是在“适配测试集”。

2)先用全量数据做预处理,再切训练测试

比如整份数据一起算均值、方差,再做标准化。
表面上你没有直接拿测试集训练,但测试集的信息已经进入了预处理过程。

3)特征里偷偷带着“未来信息”

比如你想预测用户会不会流失,但特征里放了“未来 7 天是否还登录”。
这显然不合理,因为你在真实预测时根本拿不到这个信息。

4)重复样本或强相关样本同时分到训练和测试

模型表面上好像没见过测试样本,但实际上训练集里已经有几乎一样的东西了。

数据泄漏最麻烦的地方在于:
它不会让你的结果变差,反而会让你的结果异常地好看

所以很多时候,模型分数好得离谱,并不是什么惊喜,而是警报。


11. 一个特别常见的泄漏例子:标准化顺序搞反了

这个坑真的非常常见。

很多人刚开始写代码,会这么干:

scaler.fit_transform(X)

先对整个数据集做标准化,然后再把它切成训练集和测试集。

看起来好像没什么问题,反正只是做个缩放嘛。

但问题在于,标准化要计算:

  • 均值
  • 标准差

而你在整份数据上做这件事时,测试集的信息其实已经被用进来了。

也就是说,模型虽然没直接“看答案”,但它已经提前知道了测试集的一些统计特征。
这同样属于数据泄漏。

正确顺序应该是:

  1. 先划分训练集、验证集、测试集
  2. 在训练集上 fit
  3. 再把同样的变换应用到验证集和测试集

这里看起来只差一个顺序,但本质差很多。


12. 用 Python 看一个标准的数据划分流程

下面用一个很简单的小例子演示一下,怎么切训练集、验证集、测试集。

import pandas as pd
from sklearn.model_selection import train_test_split

# 构造一个简单数据集
data = pd.DataFrame({
    "hours": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12],
    "homework": [50, 55, 58, 60, 62, 65, 70, 72, 78, 82, 88, 92],
    "pass": [0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1]
})

X = data[["hours", "homework"]]
y = data["pass"]

# 第一步:先留出测试集
X_train_val, X_test, y_train_val, y_test = train_test_split(
    X, y, test_size=0.2, random_state=42, stratify=y
)

# 第二步:再从训练部分里切出验证集
X_train, X_val, y_train, y_val = train_test_split(
    X_train_val, y_train_val, test_size=0.25, random_state=42, stratify=y_train_val
)

print("训练集大小:", len(X_train))
print("验证集大小:", len(X_val))
print("测试集大小:", len(X_test))

这里有两个点值得注意。

random_state

它的作用是让随机划分结果可复现。
不然你每次运行代码,切分结果都可能不一样,调试起来会很乱。

stratify=y

这个参数的意思是按标签分层抽样。

为什么要分层?

因为有些分类任务里,正负样本比例本来就不平衡。
如果你纯随机切,很可能会出现:

  • 训练集里正样本很多
  • 测试集里正样本很少

或者反过来。

这样评估结果就不稳定。
分层抽样可以尽量让训练、验证、测试三部分里的类别比例保持一致。

这在分类任务里非常常用。


13. 一个完整而靠谱的建模顺序,通常是什么样

把前面的东西串起来,一个比较规范的流程大概是这样:

第一步:划分数据

先把测试集留出来,再切训练集和验证集。

第二步:只在训练集上确定预处理规则

比如:

  • 缺失值怎么填
  • 标准化用什么均值和方差
  • 类别编码映射怎么建

这些规则都应该主要根据训练集来确定。

第三步:在训练集上训练模型

让模型去学习参数。

第四步:在验证集上比较方案

看看:

  • 哪个模型更合适
  • 参数该怎么调
  • 特征组合要不要改

第五步:最后才看测试集

测试集最好是最后才用,而且不要来回反复看。

这套流程看上去有点“规矩很多”,但它真正保护的是一件很重要的事:

你最后拿到的结果,尽量是真实可信的。


14. 为什么有些模型训练集表现很好,一到测试集就掉下来

这个现象,你后面一定会经常碰到。

比如:

  • 训练集准确率:99%
  • 验证集准确率:85%
  • 测试集准确率:83%

初学者看到这种情况,第一反应往往是:
“是不是测试集太难了?”

有可能,但更常见的解释是:

模型在训练集上学得太死了。

它不仅学到了真正有用的规律,连训练数据里的噪声、偶然模式、局部细节也都记住了。
所以一旦换到新数据上,它就开始掉分。

这就是后面你很快会讲到的:过拟合

而训练集、验证集、测试集的划分,本身就是帮助你识别这个问题的重要手段。

如果你只有训练集,那你可能一直以为模型很强。
只有把新数据拿出来测,问题才会暴露出来。


15. 这件事真正重要的,不是术语,而是思维方式

说到底,训练集、验证集、测试集这几个词,本身不难记。

真正重要的是你慢慢建立这样一种判断方式:

模型的价值,不在于它把旧数据做得多漂亮,而在于它面对新数据时还能不能稳住。

一旦你接受这个判断标准,很多后续问题都会自然起来:

  • 为什么不能只看训练误差
  • 为什么不能拿测试集反复调参数
  • 为什么要小心数据泄漏
  • 为什么评估方式和模型本身一样重要

讲到这里,其实第六篇最想讲的东西就已经出来了:
不是“怎么切数据”这件事有多神秘,而是机器学习从一开始就必须防着自己被“假象”骗到。

下一篇刚好就接这个问题往下走。
因为数据已经切好了,那接下来就会遇到另一个更现实的问题:

模型表现到底该怎么看?

比如准确率 95%,听起来很高。
但这个 95%,到底说明模型真的好,还是只是看起来还行?
第七篇就讲这个。

Logo

DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。

更多推荐