你在做微调或训小模型时,最容易翻车的不是调参,而是切数据这一步:随手随机切,结果验证集分数虚高,上线就掉。这篇按数据分布来讲切分方法——什么数据该分层、什么该按时间切、什么必须整组隔离,切完还要做哪几项检查。全程只给判断标准和动作顺序,具体比例和数字到站内相应页面核对。
做微调或者训练小模型的时候,最容易翻车的往往不是调参,而是切数据这一步。很多人习惯把数据集随手随机切一刀,训完一看验证集分数挺漂亮,上线就拉胯。原因很简单:随机切分假设了样本独立、分布一致,而真实业务数据这两条基本都不满足。
这篇只讲一个角度——按数据分布来切训练集、验证集和测试集。什么数据该分层、什么该按时间切、什么必须整组隔离、切完再做哪几项检查,按顺序往下走。具体比例和数字不在这里写死,去站内页面核对当时的参考值。
先想清楚:切分切的是分布,不是文件
很多人把切分理解成把文件按百分比分三堆,其实你切的是数据分布。三个集合各自回答不同问题:训练集用来学参数,验证集用来选超参和决定什么时候早停,测试集只在方案定下来之后用一次,用完就封存。如果一开始没想清楚这个分工,后面怎么切都是错的。
- 随机切分只在数据同分布、样本相互独立时才成立,业务数据里这两条经常同时不满足
- 先找出数据的天然分组:一个用户、一台设备、一家门店、一个批次,通常就是最小的不可拆单元
- 切分方式要跟着最终评估口径走:线上按用户维度打分,训练时就不能按行为行维度切
- 把切分脚本和随机种子一起存档,换机器、换同事都能复现同一份切分
类别不均衡就分层切
分类任务里少数类占比很低的时候,纯随机切分会让某些集合几乎不包含少数类,指标一会儿高一会儿低,其实全是抽样噪声。分层切分的思路是让每个集合的类别比例尽量贴近整体,代价是实现上多一步,收益是评估结果稳定得多。
- 统计标签分布先算出每个类别的样本量和占比,标出占比明显偏低的那几类,确认哪些是必须保住比例的。
- 按标签分层切用支持分层参数的切分函数,把标签列传进去。多标签任务可以只按主要标签分层,或者把标签组合成字符串再分层。
- 检查比例偏差切完分别统计训练、验证、测试三个集合的类别占比,和整体占比逐项对比,偏差大的类别要重新抽。
- 连续目标怎么处理回归任务别硬套分类分层,可以先把目标值分箱,再按箱分层抽样,或者直接用分位数抽样保证覆盖高值和低值。
有个不适用的情况要提前说清:当样本量本身已经很大、类别分布也比较均匀的时候,分层带来的收益很有限,反而增加一层实现复杂度。这时候用固定种子的随机切分就够了,把精力放到后面的泄漏检查上更划算。
有时间轴就按时间切,别打乱
时序数据、用户行为日志、交易流水这类数据,一旦随机切分,等于拿未来的数据去预测过去。线下验证分数会明显高于线上,你越是调参越会往错误方向走。正确做法是按时间点切:较早的一段做训练,中间一段做验证,最近的一段做测试。
| 数据类型 | 切分方式 | 常见坑 |
|---|---|---|
| 交易流水、行为日志 | 按时间点先后切三段 | 随机打乱导致未来信息进训练集 |
| 有明显周期性的业务 | 切点对齐业务周期,别用固定天数硬切 | 切在周期中间,验证集不具代表性 |
| 线上要滚动更新模型 | 滚动窗口做多次切分验证 | 只做一次切分,看不出稳定性 |
| 无时间依赖的数据 | 不必强行按时间切 | 为了按时间切而切,浪费数据 |
如果线上模型会定期更新,单次切分不够用。更贴近实际的做法是滚动窗口:每次往前推一个窗口,重切一次训练和验证,看多轮下来的指标波动。波动小的方案,上线之后通常也更稳。反过来,如果数据本身没有时间依赖,比如静态图像分类、独立的文本分类,就不必强行按时间切。
同源样本必须整组切走
同一个用户的多条记录、同一台设备的多次上报、同一篇文章切出来的多个片段、同一张原图裁剪出的多份样本,这些都是同源样本。如果它们被拆到训练集和验证集两边,模型在验证集上看到的其实是训练时见过的「亲戚」,分数自然虚高。这就是最常见的组泄漏。
- 以分组 ID 为单位整体分配,不要以行为单位分配,分组 ID 可以是用户、设备、店铺、文档
- 近重复样本要在切分之前处理:文本先做相似度去重,图片先做感知哈希比对,合并掉再进切分流程
- 先切分后增强,过采样和生成样本只允许在训练集内部做,否则同一份样本的变体会洒进三个集合
- 切完拿分组 ID 反查一遍,确认没有同一个 ID 同时出现在两个集合里
# 思路示意:按分组 ID 切分,而不是按行切分
# groups = df['user_id']
# 用支持 groups 参数的切分函数,把 groups 传进去
# 切完检查:set(train.user_id) & set(valid.user_id) 应为空集这里要强调顺序:去重一定要在切分之前完成。如果先把数据切成三份,再在训练集里做去重,那验证集和测试集里的重复样本依然存在,评估结果照样不可信。判断标准很简单,切完之后随便抽几条验证集样本,去训练集里做相似度检索,能搜出高度相似的,就说明还没处理干净。
切之前先过一遍泄漏清单
切分动作本身不复杂,真正难的是保证信息没有从验证集、测试集漏进训练集。下面三类泄漏按顺序排查,能在建模之前挡掉大部分问题。
- 目标泄漏
- 某个特征在真实预测时根本拿不到,或者是由标签衍生出来的,比如用结案时间预测是否结案。
- 预处理泄漏
- 归一化、缺失值填充、特征选择、词表构建这些统计量只能在训练集上算,再应用到验证集和测试集。
- 增强泄漏
- 过采样或生成样本只能在训练集内做,先切分后增强,不能先增强后切分。
- 列特征可用性对着每一个特征问一句:真实预测那一刻,这个值拿得到吗?拿不到的、由标签算出来的,直接剔除。
- 把预处理管道拆开确认所有拟合类步骤只在训练集上 fit,验证和测试只做 transform,检查代码里有没有对全量数据 fit 的地方。
- 检查增强顺序确认过采样、数据增强发生在切分之后,且作用范围限定在训练集内。
- 做交叉相似度抽查从验证集抽样本去训练集里检索相似项,有高度重合的就回到去重环节重做。
比例怎么定,切完怎么验
比例没有一个通用答案,它取决于数据总量和类别稀有度。数据越少,留出来的验证集越需要谨慎,否则评估结果基本靠运气。常见任务该留多少、大概的参考区间,到 /training 页面按你的任务规模和数据类型对一遍,别直接照搬别人的数字;部署相关的口径差异可以顺带看 /models/deploy。
| 检查项 | 怎么看 | 不通过怎么办 |
|---|---|---|
| 标签分布 | 三个集合的类别占比与整体逐项对比 | 偏差大的类别重新抽样或调整分层参数 |
| 特征分布 | 关键特征的均值、分位数跨集合对比 | 漂移明显就检查切分是否混入不同来源数据 |
| 重复样本 | 验证集样本去训练集做相似度检索 | 有高度重合就回到去重环节重做 |
| 分组隔离 | 分组 ID 集合之间交集为空 | 有交集就改用分组切分重新分配 |
把这四项检查写进切分脚本,每次数据更新自动跑一遍,比人工肉眼比对可靠。切分逻辑固定成可复现的随机种子和一段脚本,数据变了重跑同一套逻辑,别让人手工拖文件。想横向比不同切分方案的效果,可以到 /benchmarks 看评测口径是怎么定义的,先统一口径再比。
什么时候该重新切分
切分不是一次性工作,有几类情况出现时,旧的切分就失效了,继续用只会得出误导性的结论。
- 数据来源换了渠道,或者样本构成明显变化,旧切分的分布假设已经站不住
- 业务口径调整过标注规则,老样本和新样本的标签含义不一致
- 同一个测试集上反复比较了很多方案,它实际上已经变成了验证集,需要重新划一份留存集
- 线上指标和线下指标长期背离,先怀疑切分方式,再怀疑模型结构或超参
最后一点尤其值得注意:测试集被反复使用之后就不再是测试集了。判断标准是看你在同一份测试集上比较过多少轮方案,如果每一轮都拿它做决策,就该重新留一份从没碰过的数据。定位线下线上偏差时,可以对照 /models 里的模型资料和 /benchmarks 的评测口径说明,先确认两边测的是不是同一件事,再回头查切分。
下一步把上面四类泄漏检查和分组隔离写进你的切分脚本,跑一遍现有数据看有没有中招;比例和参考区间到站内对应页面核对当时的数字。→常见问题
训练集、验证集、测试集到底各干什么用?
训练集用来调模型参数,验证集用来选超参和决定什么时候停,测试集只在方案定下来后跑一次给出最终结论。三者不能混用角色,尤其是测试集反复看,就等于把它变成了验证集。概念辨析可以对照站内已有的含义解析类文章。
数据量很小的时候还要留测试集吗?
样本很少时留出独立测试集会让每份数据都太薄,评估波动大于方案差异。更稳的做法是用交叉验证,把验证结果取平均,最后只在真正上线前留一小份完全不碰的数据做终检。小样本下比例怎么定,到 /training 页面看当时的建议。
随机切分有什么问题?
随机切分假设样本独立同分布。只要数据里有时间先后、同一用户多条记录、同一原文的多个片段,随机切就会让训练集和验证集互相「串味」,线下分数虚高、上线掉分。这类数据要按时间切或按分组切。
怎么判断自己是不是发生了数据泄漏?
常见信号是:验证分数异常高、和测试分数差距大、删掉某个特征后效果断崖式下跌。排查顺序是先查跨集合重复样本,再查预处理是否在全集上做过拟合,最后查有没有用到预测时拿不到的特征。
切分比例有标准答案吗?
没有。比例取决于总量、类别稀有度和任务容错度。少量数据更依赖交叉验证,数据充足时留一份够稳定的评估集即可。具体区间的参考值到 /training、/models/deploy 页面按你的场景核对,不要直接抄别人的数字。