训练验证测试集

训练 loss 很低就算学会了?真正的考验是面对没见过的数据。

知识课 · 约 20 分钟 · 更新于 2026-06-08

一个让人不安的思考实验

设想一个特殊的"模型":

它 不学任何模式 ,只是把训练集中所有的 对原样存进哈希表。 预测时:如果输入命中表中的 , 就返回对应的 ; 否则返回任意值。

这个模型在训练集上的 loss 是 0 ——百分百正确。

但是——如果给它一条训练集没见过的新数据,它几乎只能瞎猜。 这个模型 没有真正"学到"任何规律 , 只是把答案 背 下来了。

这暴露出一个核心问题: 训练 loss 低,并不意味着模型真的"学会"了 。

真正的目标:泛化能力

机器学习的目标,从来都不是"在训练集上表现好", 而是 在 没见过 的数据上表现好 —— 这个能力叫做 泛化 (Generalization)。

训练 loss 只是 泛化能力的一个粗糙代理 , 我们需要更可靠的衡量方式。

数据集三划分

既然要衡量 "在没见过的数据上表现好不好" ,最直接的办法就是—— 从一开始就 留出一部分数据 ,不让模型看到, 训练完之后再拿出来考它。这就是数据集划分最朴素的动机。

但只留一份"没见过的数据"还不够。训练过程中, 我们除了在 更新参数 ,还在不停地 选超参数 —— 网络多深、学习率多大、正则多强……这些选择本身也需要"用没见过的数据" 来评估好坏。如果直接拿 最终考试的那份数据 来做选择, 就相当于 反复偷看答案 ,那份数据也就不再"没见过"了—— 最后报出的成绩就会 偏乐观,甚至虚高 。

所以"没见过的数据"得再 细分一份 : 一份用于训练过程中的 反复评估和调参 , 另一份 完全留到最后一刻 ,只用来给出最终的客观成绩。 这就自然地引出了实践中通用的 三划分 ——训练集、验证集、测试集,各有明确分工:

训练集(Train)

用来更新参数。模型直接"看到"这些数据,并据此调整 和 。

验证集(Validation)

训练过程中 定期评估 泛化能力,调整超参数(学习率、 网络深度、正则强度等)。模型不在验证集上更新参数,但 超参选择会被它驱动 。

测试集(Test)

最终一次性评估 ,给出"模型上线后大概能做到多好"的客观估计。 训练全程对测试集守口如瓶, 只在最后一刻使用一次 。

下面这张图把整个流程串起来:原始数据切成三份后, 训练集、验证集、测试集分别接入 训练循环 、 调超参 、 最终评估 三个环节, 各自的角色一目了然——

实践中的划分比例随 数据规模 而变,常见做法如下:

  • 中小数据集(千~十万条) :通常训练集取 60% ~ 80% ,验证集取 10% ~ 20% ,测试集取 10% ~ 20% (常见组合如 70 / 15 / 15、60 / 20 / 20 等)。
  • 大型数据集(百万级以上) : 98 / 1 / 1 也够用——1% 已经是上万条样本,足够稳定估计。
  • 极端小样本 :改用 交叉验证(K-fold) 替代固定划分 (进阶话题,本课不展开)。

PyTorch 中的数据划分

PyTorch 内置一个 random_split 函数, 把一个 Dataset 按比例随机切分。下面跟着每一步看这套 API 在内存里到底发生了什么:

改造训练循环:同时跟踪 train_loss 与 val_loss

每个 epoch 结束后, 都 在验证集上算一次 loss—— 我们既能看模型在训练集上的拟合情况,也能看它在没见过的数据上的表现:

观察:双曲线告诉我们什么?

训练几十个 epoch 后,把 train_loss 和 val_loss 同图画出来—— 大多数情况下,曲线呈下面这种典型形态:

  • 初期 :train_loss 与 val_loss 一起下降——模型在学习
  • 中期 :两条曲线达到接近的最低点——这是 最佳泛化点
  • 后期 :train_loss 继续下降,但 val_loss 开始上升 —— 模型在"死记训练集",泛化反而变差