观文听傑

返回

上一篇中,我们把训练写成经验风险最小化:模型不断调整参数,让训练样本上的平均损失下降。

但这立刻带来一个问题:同一批数据既参与学习,又负责给模型打分,这个分数还能代表未来表现吗?

通常不能。模型可能记住训练样本,研究者也可能在反复试验中“记住”测试集。今天只讲透一件事:怎样划分数据,才能得到可信的泛化评估。

01 三份数据不是简单切三刀#

监督学习中,三类数据承担不同职责:

数据允许做什么不允许做什么
训练集(Training Set)学习权重、偏置和预处理统计量代表最终泛化成绩
验证集(Validation Set)选择模型、学习率、正则化强度、停止轮次参与参数拟合
测试集(Test Set)所有方案确定后做一次最终验收反复查看并据此修改方案

这里要区分两类参数:

  • Parameter(模型参数):由训练算法直接学到,如神经网络权重 WW
  • Hyperparameter(超参数):训练前或训练外做出的选择,如学习率、树深度、隐藏层宽度。

训练集学习参数,验证集帮助选择超参数,测试集估计最终方案面对未知数据时的表现。

原始数据

   ├── 训练集 ──► 拟合参数 ───────────────┐
   │                                      │
   ├── 验证集 ──► 比较方案、选择超参数 ───┤ 循环
   │                                      │
   └── 测试集 ──► 最终一次评估 ◄──────────┘

                   方案冻结后才使用
text

02 用十个样本看懂“选择偏差”#

假设只有 10 个按编号排列的样本:

样本编号: 1  2  3  4  5  6  7  8  9  10
划分结果: T  T  T  T  T  T  V  V  E   E
text
  • T:6 个训练样本。
  • V:2 个验证样本。
  • E:2 个测试样本。

现在训练三个模型:

模型训练误差验证误差测试误差
A:简单0.200.22未查看
B:中等0.080.12未查看
C:复杂0.000.30未查看

训练误差会偏爱复杂模型 C,但验证误差提示 B 更可能泛化。因此我们选择 B,冻结全部方案,最后才查看测试集。

假设 B 的测试误差是 0.15,这就是当前最可信的泛化估计。

如果不满意 0.15,又回去增加特征、换模型,直到测试误差降到 0.08,会怎样?你已经针对这两个测试样本完成了人工调参。即使模型权重没有直接用测试集训练,人的决策过程也对测试集发生了拟合

这叫 Selection Bias(选择偏差):尝试的方案越多,越容易偶然找到一个在固定测试集上表现很好的方案。

交互判断:测试集只看了一次,但看完后改了数据增强,算泄漏吗?

算。数据增强是训练方案的一部分,你根据测试结果改变它,测试信息便参与了方案选择。应重新准备未被查看的测试集,或明确承认原测试集已降级为验证集。

03 数据泄漏到底泄漏了什么?#

Data Leakage(数据泄漏):训练或模型选择阶段使用了在真实预测时不可能获得的信息,导致离线指标过于乐观。

它不只意味着“把测试标签放进训练集”。更隐蔽的情况是:在划分数据前,对全部样本计算均值、做特征选择或填补缺失值。

一个可以手算的标准化泄漏#

训练数据只有两个值:

Xtrain=[0,2]X_{train}=[0,2]

测试数据只有一个值:

Xtest=[100]X_{test}=[100]

标准化公式是:

z=xμσz=\frac{x-\mu}{\sigma}

其中 μ\muσ\sigma 分别是均值与标准差,它们不是固定常量,而是需要从数据中估计的预处理参数

正确做法只用训练集:

μtrain=1,σtrain=1\mu_{train}=1,\qquad \sigma_{train}=1

因此:

Xtrain=[1,1],Xtest=[99]X'_{train}=[-1,1],\qquad X'_{test}=[99]

测试值 100 是明显的分布外样本,这个事实被保留下来。

如果先合并全部数据再标准化:

μall=0+2+1003=34\mu_{all}=\frac{0+2+100}{3}=34

此时训练样本的变换方式已经受测试样本 100 影响。模型在训练时虽然没看到测试标签,却提前获得了测试分布的信息。这就是泄漏。

关键规则只有一句:

任何需要通过 fit 学习统计量的步骤,都只能在训练数据上 fit;验证集和测试集只能 transform

04 随机划分并不总是正确#

train_test_split 很方便,但它默认随机打乱样本。只有当样本近似独立同分布时,随机划分才合理。

IID(Independent and Identically Distributed,独立同分布):样本彼此近似独立,并来自同一个概率分布。

下面三类任务需要更谨慎:

时间序列:必须尊重时间方向#

用 2026 年数据训练,却随机把部分 2025 年样本放进测试集,可能让模型间接“看到未来”。合理划分是:

过去 ─────────────────────────────► 未来
[       Train       ][Validation][ Test ]
text

同一主体有多条记录:按组划分#

医学数据中,同一患者的多张影像高度相似。如果其中一些进入训练集、另一些进入测试集,模型可能识别患者而不是疾病。应让同一患者只属于一个集合。

分类比例失衡:考虑分层#

若正样本只有 5%,纯随机划分可能让小验证集几乎没有正样本。**Stratified Split(分层划分)**会尽量保持各集合的类别比例。

数据结构推荐方法关键约束
普通表格 IID 数据随机或分层划分固定随机种子
时间序列按时间向前划分训练不能使用未来
患者、用户、设备多记录Group Split同一组不能跨集合
小样本交叉验证仍保留独立测试集

05 从原则落到 sklearn 代码#

下面以二分类为例。输入输出形状是:

X: [num_samples, num_features]
y: [num_samples]

model.predict(X_test): [num_test_samples]
model.predict_proba(X_test): [num_test_samples, 2]
text

先划分,再做任何需要拟合的预处理#

最终比例是 60% 训练、20% 验证、20% 测试。

  • random_state=42:让划分可复现,不表示 42 有特殊统计意义。
  • stratify=y:让类别比例尽量一致。
  • test_size:可以是比例,也可以是样本数量。

用 Pipeline 把防泄漏规则写进代码#

Pipeline.fit(X_train, y_train) 会先让 StandardScaler 只在训练集上计算均值和标准差,再把变换后的训练数据交给分类器。调用 predict(X_val) 时,Pipeline 复用训练阶段保存的统计量,不会在验证集上重新拟合。

确定 C、特征和模型结构后,常见的最终流程是用训练集与验证集重新拟合一次,再在测试集上验收:

final_model = make_pipeline(
    StandardScaler(),
    LogisticRegression(C=best_c, max_iter=1000),
)
final_model.fit(X_train_val, y_train_val)

test_pred = final_model.predict(X_test)
test_accuracy = accuracy_score(y_test, test_pred)
print("final test accuracy:", test_accuracy)
python

测试指标应与模型版本、数据版本、划分规则和随机种子一起记录。

06 小数据怎么办?#

数据很少时,单个验证集的结果可能高度依赖“恰好分到了哪些样本”。这时可以使用 Cross-Validation(交叉验证):把训练数据分成 KK 份,轮流用一份验证、其余训练,最后汇总 KK 次结果。

Fold 1: [Val][Train][Train][Train][Train]
Fold 2: [Train][Val][Train][Train][Train]
Fold 3: [Train][Train][Val][Train][Train]
Fold 4: [Train][Train][Train][Val][Train]
Fold 5: [Train][Train][Train][Train][Val]
text

交叉验证替代的是固定验证集,不是独立测试集。所有超参数确定后,测试集仍只用于最后评估。

07 真正做项目时的检查清单#

  1. 先定义预测时刻。 问清楚模型做预测时,哪些字段确实已经存在。
  2. 先划分,后预处理。 缺失值填补、标准化、PCA、特征选择都遵守这一顺序。
  3. 按真实部署单位划分。 患者、用户、设备、场站和时间都可能是隔离单位。
  4. 保留数据划分清单。 保存样本 ID,而不只是保存随机种子。
  5. 不要盯着单一指标。 同时观察均值、方差、类别指标和置信区间。
  6. 测试集设访问边界。 大型项目中可由独立脚本或负责人执行最终测试。

08 什么时候仍会失败?#

即使数据完全无泄漏,测试结果仍可能失真:

  • 测试集太小,估计方差很大。
  • 测试数据与上线流量不是同一分布。
  • 数据在时间上发生 Distribution Shift(分布漂移)
  • 指标与业务目标不一致,例如只追求准确率却忽视少数类召回率。
  • 反复发表和比较结果,公共测试集逐渐被整个社区“过拟合”。

因此,正确划分不是终点,而是可信实验的最低条件。

09 今天真正需要记住什么?#

  1. 训练集学习参数,验证集选择方案,测试集只做最终验收。
  2. 看过测试结果并据此修改方案,就已经对测试集发生了拟合。
  3. 标准化、填补缺失值和特征选择也会学习参数,必须只在训练集上 fit
  4. 时间、患者、用户等结构决定划分方式,随机切分不是万能答案。
  5. Pipeline 能把预处理与模型绑定,降低交叉验证和部署中的泄漏风险。

10 思考题与小练习#

练习 1:找出泄漏

某项目先对全部 10,000 个样本做 PCA,再划分训练集和测试集。问题在哪里?PCA 的主轴使用了测试集分布。正确做法是先划分,再只用训练集拟合 PCA;使用 Pipeline 能在交叉验证的每一折中正确执行。

练习 2:应该随机划分吗?

你要用过去 30 天的传感器数据预测下一小时是否故障。应按时间划分,训练使用更早数据,验证和测试使用更晚数据;随机划分会把未来状态混入训练。

练习 3:计算两次切分比例

第一次留下 20% 测试集,第二次从剩余 80% 中留下 25% 验证集。最终训练、验证、测试比例是多少?答案是 60%、20%、20%。

相关工作#

11 下一篇预告#

有了可信的数据边界,下一步才能讨论“模型究竟应该优化什么”。下一篇将聚焦损失函数:为什么评价指标是准确率时,分类模型通常不直接优化准确率;以及一个可优化的损失需要具备哪些性质。

为什么不能用测试集调模型?一次看懂训练、验证与数据泄漏
https://zwjcode.cn/blog/train-validation-test-split
作者
发布于 2026年8月18日
版权协议 CC BY-NC-SA 4.0
评论加载似乎遇到了问题,请尝试刷新页面。