划分数据集为训练集、验证集和测试集代码

接入与开发

给模型训练新手讲清数据集划分的代码写法与常见坑点,覆盖分层抽样、时间切分、随机种子和泄漏自查。

第一次训模型,很多人卡在同一个地方:数据到手就随手切一刀,训练跑得挺顺,上线一看效果对不上。问题往往不在模型,而在划分这一步——切错了,验证集和测试集早就被污染,你看到的指标是虚的。这篇只讲怎么用代码把数据集切成训练、验证、测试三份,每份该切多少、什么时候不能随机切、随机种子和泄漏需要查什么,给一套能直接照着改的顺序。

第一次训模型,很多人卡在同一个地方:数据到手就随手切一刀,训练跑得挺顺,上线一看效果对不上。问题往往不在模型,而在划分这一步——切错了,验证集和测试集早就被污染,你看到的指标是虚的。

这篇只讲怎么用代码把数据集切成训练、验证、测试三份,每份该切多少、什么时候不能随机切、随机种子和泄漏需要查什么。关于各家模型和接口能力的差异,可以到站内的模型资料库对照,这里只谈切分代码本身。

先搭一个能跑的最小划分脚本

最稳的做法是分两步切:先切出测试集并锁进抽屉,再从剩下的里切出验证集。这样测试集从头到尾没参与过调参,最后报出来的指标才可信。如果一次切成三份,很容易在反复调参时顺手把测试集也看了一遍,等于白留。

from sklearn.model_selection import train_test_split\n\n# 第一步:切出测试集,锁起来不再动\nX_tmp, X_test, y_tmp, y_test = train_test_split(\n    X, y, test_size=0.2, random_state=42, stratify=y)\n\n# 第二步:从剩下的里切出验证集\nX_train, X_val, y_train, y_val = train_test_split(\n    X_tmp, y_tmp, test_size=0.25, random_state=42, stratify=y_tmp)\n\n# 切完立刻打印,异常在这步能看出来\nprint(len(X_train), len(X_val), len(X_test))
  • 比例只给思路:数据量小的时候,验证和测试各留一份明显够用的量;数据量大时测试集占比可以压小,够评估粒度就行,别背固定数字
  • 固定 random_seed,并且把切分脚本单独存成文件,下次复现同一份划分不用靠记忆
  • 切完立刻打印三份的样本数、标签分布和字段形状,数量或分布明显异常先回头查切法
  • 测试集落盘后加个只读标记,日常训练流程里只读不写,避免手滑改动

什么时候不能随机切

随机切不是万能公式,以下三种场景里随手 shuffle 会直接把评估结果做废。判断标准很简单:如果测试集里的一条样本,和训练集里的某条样本来自同一个原始对象,或者时间上晚于训练集,这次随机切就不适用。

数据类型切法不这么切会怎样
类别不均衡的分类数据按标签 stratify 分层切少数类可能整类落进验证集,指标无意义
时间序列、日志、行为数据按时间先后切,训练用早段、测试用最近段随机打乱相当于让模型看到未来信息
同一用户/文档/会话多行样本按实体 ID 分组切,同一 ID 只进一份同源数据分居两侧,等于变相作弊

分层抽样在 scikit-learn 里就是给 train_test_split 传 stratify 参数,注意第二步要传 y_tmp 而不是原始 y,否则分层基准对不上。时间切分则是先按时间字段排序,再用切片或 groupby 取前中后三段,不要用随机函数。

  • 分类且样本不均衡:加 stratify,切完核对每个类别在三份里的占比
  • 时间相关:按时间排序后切,训练在前、验证居中、测试在最近
  • 有实体分组字段:按 group 切,保证同一群组不跨集合
  • 文本近重复多:先做去重或近重复合并,再考虑切分

切完必须做的泄漏自查

泄漏是划分环节最常见的隐形坑,表现为线下指标漂亮、线上立刻回落。切完不要马上开训,先花几分钟做两轮检查:行级 ID 重叠检查,以及统计分布检查。两者都过,再去写训练循环。

  1. 查 ID 重叠把三份的样本 ID、用户 ID、文档 ID 分别取集合求交集,非空就说明切法有问题,回头改切分逻辑
  2. 查统计分布比较训练集和测试集的标签分布、关键字段均值,差异过大先怀疑切法而不是怀疑模型
  3. 卡死预处理顺序标准化、归一化、缺失值填充、编码只能在训练集上 fit,再 transform 到验证和测试;反过来做就是典型的预处理泄漏
  4. 查文本近重复文本和问答类数据额外查一遍近似重复,同一段话换个标点分到两边也是泄漏

如果你用的是自带评估集的数据加载方式,也要确认自己外层的切分和数据集内置的切分是谁生效,两者叠加容易出现验证集里混进训练样本的情况。相关工具的用法可以到站内工具目录里对照。

把划分固定下来:种子、版本、日志

划分只切一次是不够的,还得让这次切分能被复现、被追溯。团队里最常见的乱象是每个人本地各切一次,指标对不上时谁也不知道差异来自模型还是来自数据。解决办法是把划分结果当成一份产物管起来。

  • 固定随机种子的同时,把三份 ID 列表落盘成索引文件,训练直接读文件,不再重复随机
  • 每次训练日志记三件事:数据版本、划分脚本版本、随机种子,指标异常先核对这三项
  • 划分脚本改动当成一次数据变更,改了就要重跑全部评估,不能只补跑一部分
  • 把划分文件放进版本管理,别让每个人本地各切一次

验证这套机制是否生效,可以做个简单测试:换台机器、换个人、按日志里的三项信息重跑一遍,看三份 ID 是否完全一致。对不上就说明固定得不彻底。

下一步划分搞定后,下一步是选一个成本可控的模型接口跑通基线。站内价格追踪页有各家接口的当前价格和变动记录,去核对当时的数字再决定用哪家。

常见问题

训练集、验证集、测试集比例到底设多少?

没有通用数字。数据量小的时候,验证和测试各自留出足够做出稳定判断的样本量就行;数据量大时测试集占比可以压得更小。与其背比例,不如先看你的样本总量和标签分布,切完打印出来核对,数量太少就说明比例不合适,需要扩数据或者改评估方式。

为什么不能用测试集调参?

一旦你根据测试集的表现反复调模型、调提示词、调超参,测试集就变成了第二个验证集,它给出的分数不再代表模型在没见过的数据上的表现。正确做法是调参和早停只看验证集,测试集留到最后跑一次,跑完就锁住不再回头改。

随机切分总是报错或者标签分布不均怎么办?

先看是不是样本量太小,某些类别本来就少;其次检查有没有加分层参数,分类任务建议按标签分层。如果是时间序列或存在分组关系,随机切本身就不适用,应该改成按时间或按实体 ID 切。改完再看三份的标签分布是否接近。

本文依据各平台公开文档与官方公告整理,只做中文归纳;具体做法、参数与限制以官方文档为准。