第一次训模型,很多人卡在同一个地方:数据到手就随手切一刀,训练跑得挺顺,上线一看效果对不上。问题往往不在模型,而在划分这一步——切错了,验证集和测试集早就被污染,你看到的指标是虚的。这篇只讲怎么用代码把数据集切成训练、验证、测试三份,每份该切多少、什么时候不能随机切、随机种子和泄漏需要查什么,给一套能直接照着改的顺序。
第一次训模型,很多人卡在同一个地方:数据到手就随手切一刀,训练跑得挺顺,上线一看效果对不上。问题往往不在模型,而在划分这一步——切错了,验证集和测试集早就被污染,你看到的指标是虚的。
这篇只讲怎么用代码把数据集切成训练、验证、测试三份,每份该切多少、什么时候不能随机切、随机种子和泄漏需要查什么。关于各家模型和接口能力的差异,可以到站内的模型资料库对照,这里只谈切分代码本身。
先搭一个能跑的最小划分脚本
最稳的做法是分两步切:先切出测试集并锁进抽屉,再从剩下的里切出验证集。这样测试集从头到尾没参与过调参,最后报出来的指标才可信。如果一次切成三份,很容易在反复调参时顺手把测试集也看了一遍,等于白留。
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 重叠检查,以及统计分布检查。两者都过,再去写训练循环。
- 查 ID 重叠把三份的样本 ID、用户 ID、文档 ID 分别取集合求交集,非空就说明切法有问题,回头改切分逻辑
- 查统计分布比较训练集和测试集的标签分布、关键字段均值,差异过大先怀疑切法而不是怀疑模型
- 卡死预处理顺序标准化、归一化、缺失值填充、编码只能在训练集上 fit,再 transform 到验证和测试;反过来做就是典型的预处理泄漏
- 查文本近重复文本和问答类数据额外查一遍近似重复,同一段话换个标点分到两边也是泄漏
如果你用的是自带评估集的数据加载方式,也要确认自己外层的切分和数据集内置的切分是谁生效,两者叠加容易出现验证集里混进训练样本的情况。相关工具的用法可以到站内工具目录里对照。
把划分固定下来:种子、版本、日志
划分只切一次是不够的,还得让这次切分能被复现、被追溯。团队里最常见的乱象是每个人本地各切一次,指标对不上时谁也不知道差异来自模型还是来自数据。解决办法是把划分结果当成一份产物管起来。
- 固定随机种子的同时,把三份 ID 列表落盘成索引文件,训练直接读文件,不再重复随机
- 每次训练日志记三件事:数据版本、划分脚本版本、随机种子,指标异常先核对这三项
- 划分脚本改动当成一次数据变更,改了就要重跑全部评估,不能只补跑一部分
- 把划分文件放进版本管理,别让每个人本地各切一次
验证这套机制是否生效,可以做个简单测试:换台机器、换个人、按日志里的三项信息重跑一遍,看三份 ID 是否完全一致。对不上就说明固定得不彻底。
下一步划分搞定后,下一步是选一个成本可控的模型接口跑通基线。站内价格追踪页有各家接口的当前价格和变动记录,去核对当时的数字再决定用哪家。→常见问题
训练集、验证集、测试集比例到底设多少?
没有通用数字。数据量小的时候,验证和测试各自留出足够做出稳定判断的样本量就行;数据量大时测试集占比可以压得更小。与其背比例,不如先看你的样本总量和标签分布,切完打印出来核对,数量太少就说明比例不合适,需要扩数据或者改评估方式。
为什么不能用测试集调参?
一旦你根据测试集的表现反复调模型、调提示词、调超参,测试集就变成了第二个验证集,它给出的分数不再代表模型在没见过的数据上的表现。正确做法是调参和早停只看验证集,测试集留到最后跑一次,跑完就锁住不再回头改。
随机切分总是报错或者标签分布不均怎么办?
先看是不是样本量太小,某些类别本来就少;其次检查有没有加分层参数,分类任务建议按标签分层。如果是时间序列或存在分组关系,随机切本身就不适用,应该改成按时间或按实体 ID 切。改完再看三份的标签分布是否接近。