刚上手建模的人最容易在这里翻车:数据只有一份,随手切完就开训,结果调参调到测试集上,最后报出来的效果自己都不敢信。这篇不背定义,讲的是三份数据的分工边界、切分顺序、常见比例区间,以及切完之后该怎么复查有没有串味。具体数字以你自己数据的规模和去站内模型库对照为准。
刚上手做模型的人,最容易在数据切分这一步翻车:手里就一份数据,随手切三块,训完调、调完再训,最后报出来的效果自己都不敢信。问题不在模型,在于三份数据的边界没划清。这篇不讲定义,只讲每份数据各干什么、按什么顺序切、比例怎么定,以及切完之后怎么复查有没有串味。
先把一句话记住:训练集让模型学,验证集让你做决定,测试集只负责最后报一次成绩。你基于哪份数据的表现做了决定,那份数据就已经参与训练过程了。具体规模该留多少、指标怎么对照,可以到 /rankings 看当时的口径和数据,不要照搬别人的数字。
三份数据各管一件事,别混着用
训练集是模型唯一允许反复看的那份数据,模型在这上面错得越多改得越多。验证集是给你自己看的,用来选超参、定迭代轮次、挑特征组合。测试集则只在模型彻底定稿后跑一次,跑完就封存,用来对外报成绩。三者的区别不是数据本身,而是你什么时候、出于什么目的去碰它。
- 训练集
- 模型可以反复看,用它更新参数;看多少遍都不算作弊
- 验证集
- 你拿它做选择,比如换超参、停在第几轮、丢哪个特征
- 测试集
- 定稿后跑一次,用来对外报成绩;跑完不许再回头改模型
- 判断有没有用错的标准很简单:如果某个决定是「看了某份数据表现之后才做的」,那这份数据就已经进了训练流程,不能再当最终成绩的依据。
- 验证集是过程指标,会随着你调参上下波动,这是正常的;测试集是结论指标,只应出现一次。
- 如果发现自己反复去看测试集结果并据此调整,那它实际上已经变成第二个验证集,报出来的数字偏乐观。
- 想对模型效果有个相对可信的判断,可以把验证集上的表现和最终测试结果并排看,差距大到不合理就说明切分或流程有问题。
切分顺序和比例:先切测试,再切验证
推荐顺序是先留出测试集并封存,再从剩下的部分里划验证集。反过来先切验证再切测试,容易因为两次切分的边界漂移,让两份数据分布对不上。切之前最好固定一次随机种子并把切分脚本存下来,保证每一步都可复现。
- 封存测试集从全量数据里先划走一份只做最终评估,把它放到单独目录或单独字段标记,流程里不读它。
- 从剩余数据里切验证集在剩下这份上再划一份做验证,其余留作训练。两步都记录随机种子和样本 ID。
- 检查三份数据的标签分布把每份的标签比例拉出来并排比一眼,差异明显就换分层抽样或按时间切。
- 写进切分脚本一起跑把切分逻辑固化成可重跑的命令,换数据时只换输入路径,避免每次手动切。
比例没有标准答案,主要看数据规模。数据量很小的时候,测试留一成到两成、验证留一成到两成比较常见,其余都给训练;数据量到了几十万条以上,验证和测试各留很小一部分通常也够用。真要定具体数字,建议结合你任务的指标波动情况到 /tools/speed-test 跑一下看当前规模下的稳定性。
| 数据情况 | 切分做法 | 注意点 |
|---|---|---|
| 样本数很少(几百到几千) | 训练占大头,验证和测试各留小份,配合交叉验证 | 别把测试切得太碎,留够能触发一次完整评估的量 |
| 常规规模(几万到几十万) | 训练为主,验证和测试各留固定比例 | 固定随机种子,保证可复现 |
| 类别严重不平衡 | 按标签分层抽样 | 每类在验证和测试里都要有代表样本 |
| 时序数据 | 按时间先后切:早的训、中间的验、最晚的测 | 不能打乱顺序,否则未来信息会漏进训练 |
| 存在分组结构(同用户、同设备) | 按组整体切,同组样本只出现在一份里 | 否则同一对象既训又测,等于开卷考试 |
时序和分组这两条要特别小心。时序任务随机打乱,会把未来信息漏进训练集,线下效果好看上线就崩。涉及同一用户、同一设备、同一文档的样本,要整组切到一起,不能让同一个对象同时出现在训练和测试里,否则模型等于提前见过答案。
切完先做一遍串味检查,再开训
切分本身只是第一步,切完之后至少要做几项检查,确认三份数据之间没有互相泄露。很多看起来效果很好的模型,问题都出在这里,而不是模型本身有多强。检查不用很复杂,按下面的顺序过一遍就行,花不了多少时间。
- 查重复:训练集和验证、测试之间做一次近似去重,完全重复和近重复(同一份内容改几个字)都算泄露。
- 查分布:把三份数据的标签分布、关键字段分布并排比一眼,差异明显说明切分方式有问题。
- 查预处理顺序:归一化、填充缺失、编码的统计量只能用训练集算,再套到验证和测试上;先在全量上算完再切,就已经泄露了。
- 查 ID 交叉:用样本或对象 ID 做一次集合交集,训练和测试之间不该有共享 ID。
- 留底线:测试集先别动,等模型和超参全部定稿再跑一次,跑完记录结果,不要再回头改。
预处理顺序这条最容易被忽略,也最隐蔽。均值、方差、编码映射这些东西,只要是在全量数据上算出来的,验证和测试的统计信息就通过预处理环节进入了训练。正确做法是先切分,所有统计量只在训练集上拟合,然后 transform 到验证和测试上。
数据量小怎么办:几种能落地的替代做法
数据量小是很多入门项目的常态,硬切三份确实会让每一份都薄得没法用。这时候不是放弃切分,而是换一种更省数据的方式,同时把结论里的话说诚实。
- 交叉验证
- 把训练数据轮着分块,每块当一次验证,最后取平均,适合只有几百到几千条的场景
- 留一法
- 每次只留一个样本做验证,适合样本极少但训练开销能接受的场景
- 验证测试合并
- 数据实在撑不起三份时可先合并,但要在结论里写明这是乐观估计
- 先判断交付目标只是内部看趋势,可以用交叉验证代替固定验证集;要对外报数或上线,测试集不能省。
- 选一个省数据的方案交叉验证或留一法选一个,把每一折的分数分布记下来,看方差大不大。
- 保留一份干净的测试哪怕只有几百条,也单独留出一份只跑一次,宁可小也不能脏。
- 在结论里写清口径说明验证用了哪种方式、测试跑了多少次,让人知道这个数字的置信度大概什么样。
有一种情况可以不用太纠结测试集:任务本身是探索性的,没有明确的上线判定标准,那严格封存测试集的意义有限,重点应该放在验证集的稳定性上。但只要涉及对外交付或上线决策,测试集的干净程度就不能打折。想了解常见的评估口径和踩坑记录,可以到 /sites 对照看看别人是怎么做的。
下一步把你现在的切分脚本跑一遍,把三份数据的标签分布和 ID 交集打出来,对照上面清单确认没有串味,再开始训练。→常见问题
验证集和测试集到底差在哪,能不能用同一份?
能合并,但代价是最后报出来的成绩偏乐观,因为你在调参过程中已经反复看过它了,相当于提前偷看答案。分开的价值就在于那份测试集从头到尾没被你动过,它给出的数字才是对新数据效果的诚实估计。如果只是内部评估趋势、不上线,合并一份也能接受,但结论要标注清楚。
数据太少,切完训练集只剩几百条,还怎么分?
优先考虑交叉验证而不是硬切三份:让每一块数据轮流当一次验证,取多次结果的平均,比一次性切分稳定得多。测试集可以留得很薄甚至暂时不留,但要明确这是阶段性的做法。等数据补上来之后,再按先切测试、后切验证的顺序重做一遍,别一直沿用临时方案。
随机切分看起来最简单,为什么有时候不能这么做?
两类情况要避开随机切:一是数据带时间顺序,随机切会让模型提前看到未来信息,线下效果好但上线崩;二是同一个用户、文档或设备对应多条记录,随机切会把同一对象的样本分到训练和测试两边,等于泄题。这两种情况分别要用按时间切和按组切,切完再复查一遍分布是否一致。