很多新手都会遇到一种特别诡异的场景:训练过程中损失值降得很漂亮,验证集上的指标也说得过去,结果一到测试集上就原形毕露。你以为是模型过拟合,正则化、Dropout、早停轮番上阵,折腾半天也没多大改善。其实问题往往不在模型,而在你对训练数据集和测试数据集的理解与操作上。
这个系列写到第10篇,终于到了我认为整个AI入门阶段最容易被轻视、却又最容易埋雷的环节。很多教程会把训练集、测试集当成一个"理所当然"的概念,划分完就开训,但实际做项目时你会发现,数据集的构建方式、划分策略、使用边界,几乎决定了你最终模型的上限。这篇文章我会把训练集和测试集从原理到实操掰开揉碎讲清楚,包括怎么采集清洗数据、几种划分策略分别适用什么场景、测试集污染是怎么悄悄发生的,以及如何用测试结果反过来指导数据迭代。内容适合正在用YOLO、分类网络或任何监督学习框架训练自己数据集的人,无论你是刚入门的小白,还是已经跑通几个开源项目的进阶玩家,这篇都能帮你少踩几个大坑。
1. 训练集和测试集到底在解决什么问题
1.1 一个直观但容易误导的类比
很多人喜欢把训练集比作"习题册",把测试集比作"期末考试卷"。这个类比基本方向没错,但有一个关键点被忽略:考试的目的是检验你学会了知识,而不是检验你背下了某道题的答案。如果习题册和考卷用的是同一批题目,你考了满分也不能说明能力,只能说明记忆力好。
模型训练也是这个道理。训练集是让模型去"学"的数据,测试集是检验它"学没学会"的数据。如果两者混在一起,或者划分不干净,模型就可能不是在"学习规律",而是在"背答案"。这就是所谓的过拟合——表面上训练指标非常好,一到真实场景就崩。
我以前带过一个实习生,训练一个二分类模型,拿到的准确率高达99%。结果部署到线上,面对真实用户数据,准确率直接掉到72%。后来排查发现,他把整个数据集做了增强处理之后才划分训练测试集,导致测试集里出现了大量和训练集几乎重复的增强图片。模型的"好成绩"完全是背答案背出来的。
1.2 为什么不能简单套用固定比例
网上随便一搜,最常见的数据集划分建议就是"7:3"或者"8:2"。这个经验值有一定道理,但如果你真在任何场景下都套用,大概率会出问题。
划分比例真正要权衡的是两方面:训练集越大,模型见到的样本越丰富,学得越充分;但测试集也不能太小,否则评估结果不稳定,你对模型真实水平的判断就不可靠。
给你一个直观的例子:假设你只有100张图片,按8:2划分,测试集只有20张。这20张里哪怕只有一张被误分,准确率就掉了5个百分点。你用这个结果去判断模型好坏,误差范围大得离谱。但如果你的数据集有100万张图片,哪怕只拿出5%做测试集,也有5万张,评估结果就非常稳定。
我的建议是:样本量很小的时候,先别急着留固定的测试集,优先考虑交叉验证(后面会细讲);样本量足够大时,测试集也未必需要绝对比例很大,够用就行。关键是要让测试集的样本量能支撑你对模型做可靠判断。
1.3 验证集:训练集和测试集之间不可或缺的第三者
严格来说,一个完整的训练流程会涉及三个数据集合:训练集、验证集、测试集。很多人会在入门阶段忽略验证集,直接把训练集当验证集用,这是另一个常见的坑。
三个集合的分工是这样的:
- 训练集:用来更新模型的参数(权重)。
- 验证集:用来做模型选择、调超参数、判断何时早停。
- 测试集:只在最终评估时使用一次,用来模拟模型面对完全未见过的数据时的表现。
用考试类比:训练集是日常作业,验证集是模拟考,测试集才是真正的高考。你可以根据模拟考成绩调整复习策略,但如果你根据高考成绩去调整复习方向,那显然是来不及的,而且这行为本身就不合理。
实际操作中,很多人会拿测试集反复调参,今天测一下改改学习率,明天测一下换换网络结构,测了十几次终于"调"出了一个好结果,然后宣布模型精度很高。这本质上已经是在拿测试集当验证集用了,最终的测试结果严重虚高,放到真实场景一定会打折。正确做法是:用训练集训练,用验证集调参,模型定型后再用测试集做一次最终体检。
2. 如何构建一份能真正训练出好模型的数据集
2.1 数据量:"多少张才够"没有标准答案,但有经验值
不管是YOLO目标检测还是图像分类,被问到最多的问题就是"我到底要准备多少张图片?"这个问题的标准答案只有一个:看任务复杂度。但如果你需要一些可参考的经验值,我可以给你一组我自己实践下来还算靠谱的基准。
对于图像分类任务,如果类别之间的差异非常明显(比如猫和狗、汽车和自行车),每个类别有500张以上原始图片,配合合理的数据增强,通常能训练出一个可用的模型。如果类别差异细微(比如不同品种的狗、不同型号的零件),每个类别建议准备2000张以上。对于目标检测任务,因为每个目标还需要精确的标注框,复杂度更高,一般来说每个类别至少要有1000个标注实例,各种尺度、角度、遮挡情况都要覆盖到。
这里要引入一个核心概念:数据集的多样性,比绝对数量更重要。你有1万张几乎一模一样的猫的照片,不如有1000张不同背景、不同光线、不同姿态、不同遮挡程度的猫的照片。模型真正学到的是类别之间的边界,而这个边界是靠各种变化条件下的样本共同支撑起来的。
2.2 数据清洗:把"脏数据"挡在训练之前
很多开源数据集直接下载下来就能用,但自己构建数据集时,最花时间的往往不是采集,而是清洗。这里说的清洗包括三类工作。
第一类是去除错误样本。标注框对不齐、类别标错、同一张图里目标被遮挡超过一半导致标注框严重偏离——这些样本不但让模型学不到正确特征,还会干扰训练稳定性。检查方法很简单但很耗时:用标注工具逐张过一遍,或者用训练好的模型辅助预标注后再人工修正。
第二类是处理重复和近重复样本。从视频里抽帧的话,连续几十帧可能只有微小的位置变化。这些高度相似的样本本质上等效于一个样本,反复出现还会让模型对特定帧产生过拟合。抽帧时建议每隔N帧抽一次,或者按帧间差异度去重。
第三类是类别平衡问题。真实场景的数据往往呈现长尾分布:少数类别拥有大量样本,大部分类别样本稀少。直接拿这种数据训练,模型会严重偏向头部的类别。处理方法有三种:对少数类别过采样(重复采样或做增强)、对多数类别欠采样、或者在损失函数上给少数类别更高权重。我的经验是,先做欠采样去掉一些冗余的多数类样本,再对少数类做有针对性的增强,效果通常比单纯靠损失函数权重更好。
2.3 数据增强:一条必须画在训练集内部的线
数据增强是扩充数据集的利器,尤其在小数据集上几乎能翻倍提升模型表现。但这里有一条铁律:增强只能应用在训练集上,测试集必须保持原始数据的分布。
很多入门资源会把数据增强讲成"一种提升模型泛化能力的手段",但很少有人提醒:增强后的图片和原始图片在像素层面存在很强的相关性。如果在划分训练测试集之前就做了增强,增强后的图片很可能同时出现在训练集和测试集里,这会导致测试结果虚高。
我前文提到的实习生案例里,就是先用了随机翻转、随机裁剪、色彩抖动做了5倍增强,然后再划分数据集。测试集里充满训练集图片的"变体",模型相当于提前见过了考试题。正确的做法是:先把原始数据按策略划分为训练集和测试集,再在训练流程中实时对训练集做增强(也就是在线增强)。例如YOLO系列训练时使用的Mosaic、MixUp、HSV扰动等,都是实时作用在训练数据上的,不会污染测试集。
2.4 标注质量:决定模型能力上限的隐形因素
如果说数据是模型的天花板,那标注质量就是支撑这个天花板的柱子。标注不准确的数据,轻则让模型收敛变慢,重则让模型学到错误模式。
我见过一个比较典型的例子:一个项目要检测流水线上的瓶盖缺陷,标注时把"瓶盖表面轻微划痕"和"瓶盖正常"归为一类的边界搞得非常模糊,不同标注员的判断标准还不统一。训练出来的模型在测试集上表现尚可,但上线后几乎每天都有漏检或者误报,因为生产环境中的光线、角度变化让那些模糊边界问题集中暴露了出来。
所以,如果条件允许,建议至少让两个人独立标注同一批样本,然后计算标注一致性(比如用Cohen's Kappa系数或者检测场景下对比IoU),不一致的地方再让第三个人复核。这会很费时间,但对数据集质量带来的提升是值得的。即便你一个人做标注,也要给自己定一套明确的标注规范:什么情况算遮挡、什么程度算缺陷、类别边界在哪里,然后严格执行。
3. 数据集划分的三种策略:按场景选,不是按习惯选
3.1 随机划分:最常用,但未必最适合你
随机划分就是直接把全量数据随机打散后按比例切分。实现简单,代码一行就能搞定。但在有些场景下,随机划分会给你一个虚假的信心。
最典型的就是视频抽帧数据。假设你要做一个车辆检测模型,从一段城市道路监控视频里抽了5000帧作为数据集。如果随机划分训练集和测试集,那么测试集里会有大量和训练集高度相似的连续帧。比如视频第100帧在训练集,第99帧和第101帧很可能就在测试集里。三帧画面上车辆位置只移动了几个像素,背景几乎一样。模型在测试集上的表现当然会很好,但换个路段、换个时间,可能立刻原形毕露。
这种问题的本质是样本不独立。视频帧之间存在强时序相关性,随机划分破坏了"训练集和测试集要来自独立分布"这一前提。
3.2 按视频序列或时间戳划分:评估泛化能力的正解
如果你的数据天然带有时间或序列属性,比如视频帧、股票K线、传感器时序数据,正确的做法是按序列切分,而不是按单帧随机切分。
具体操作上,你可以把整个视频序列按时间切成两段:前80%的帧作为训练集,后20%的帧作为测试集。或者按视频ID划分:如果有多个不同场景的视频,确保同一个视频的所有帧只会出现在其中一个集合里。
这样做的好处是,测试集里的样本和训练集样本不再存在直接的"邻居关系",考验的是模型真正学到了目标物体的通用特征,而不是记住了某个特定画面的背景信息。代价是测试难度变大了,指标通常会比随机划分低几个点——但这才更接近模型上线的真实表现。
如果你在用Scikit-learn,可以用GroupShuffleSplit这类支持组划分的工具,按视频ID分组切分。如果用的是PyTorch或自定义流程,手动按组切也不复杂,核心就是保证分组逻辑在划分前就确定好。
3.3 按场景或域划分:检验模型的真实鲁棒性
还有一种更严苛的划分方式:让训练集和测试集来自不同的数据分布。比如训练集里全是白天晴天的街景,测试集里放夜间、雨天、雾天的图像。或者训练集来自A城市的道路,测试集来自B城市的道路。
这种划分策略不会让模型指标好看,甚至会很难看,但它能回答一个关键问题:"我的模型能不能在真实场景中落地?"这本质上是在做领域泛化能力的检验。
如果你做的项目要部署到不可控的真实环境,我非常建议用这种方式多测几次。哪怕最终指标不够理想,你也知道了模型的短板在哪里:它是对光照变化敏感,还是对背景环境变化敏感,抑或对不同角度拍摄的目标敏感。带着这些信息再去补充数据,远比盲目堆样本量高效得多。
3.4 小数据集的保底方案:K折交叉验证
当你的数据量很小,留出固定测试集太奢侈时,K折交叉验证几乎是唯一靠谱的选择。
做法是把数据分成K份(常用5或10),每次取其中1份作为验证集,其余K-1份作为训练集,训练K次,每次验证集都不同,最终把K次验证结果取平均。这样每个样本都参与过训练也参与过验证,对模型真实水平的估计更稳定。
使用交叉验证时有两点提示。第一,如果数据不独立(比如来自同一视频序列),分组时仍然要按组逻辑切分(比如GroupKFold),不能直接随机分K份,否则问题依旧。第二,交叉验证适合在小数据集上做模型选型和参数调优,等确定了最终模型和超参数后,再用全部数据重新训练一次,供部署使用。
4. 测试集污染的几种常见路径:你以为你测了,其实你在骗自己
4.1 信息泄露:测试集绝对不能参与任何决策
测试集污染这个词听起来很专业,说穿了就一句话:测试集中的任何信息,包括数据本身、数据分布、评估结果,都不能反过来影响你的建模决策。
实际项目里最常见的污染路径有三个。
第一种是拿测试集反复调超参数。很多人在训练时发现指标不理想,就改一下学习率、换一下网络深度,然后立刻在测试集上测。每测一次,测试集的信息就泄露一次。测几十次之后,模型可能已经在隐式地"记住"了测试集的分布特征。这时候测试集作为"未知数据"的模拟意义就消失了。
第二种是在测试集上做"观察后处理"。比如发现测试集里某个类别经常被误分,就针对性地调整分类阈值甚至后处理规则。这依然是让测试集影响了你的决策。
第三种更加隐蔽:用整个数据集做标准化或归一化。如果你的归一化参数(均值和方差)是全量数据算出来的,那测试集的信息已经悄悄流入了训练流程。正确做法是只在训练集上计算归一化参数,然后用这些参数去转换验证集和测试集。
4.2 先增强后切分:很多视频教程最爱犯的错
我前面反复提到这一点,因为它实在是太常见了。不少教程为了让"效果看起来更好",会先对整个数据集做增强扩充,然后才划分训练测试集。这样确实能把测试集指标做得非常漂亮,但本质上模型在测试集上看到的大部分图片都是训练集图片的"换皮"版本,评估结果严重失真。
一个判断方法很简单:在测试集上随机挑几张增强后的图片,看看能不能在训练集里找到对应的原图。如果能找到,那这个划分方案就有问题。
4.3 标注风格不一致:测试集标注和训练集标注来自不同的人
标注质量对测试结果的影响比很多人想象中大。如果训练集和测试集是不同的人标注的,而两人的标注风格差异明显,比如一个人喜欢把检测框画得紧一些,另一个人画得松一些,那模型在测试集上的mAP可能会受到额外干扰。
处理思路有两种:一种是在划分前统一标注风格,由同一个人或同一套规则复核全量标注;另一种是用IoU阈值适当的后处理(比如NMS参数调整)来消化标注风格差异,但这属于治标不治本。最稳妥的办法还是,在数据划分前制定清晰的标注规范,并在全量数据上执行同一套标准。
4.4 数据去重遗漏:互联网数据集的经典陷阱
很多人会选择从互联网上爬图片来构建数据集。这时候一个特别容易踩的坑是重复样本:同一张图片可能被不同网站以不同文件名、不同尺寸保存,爬下来之后如果不做去重,它可能既出现在训练集又出现在测试集。
处理办法是在划分前做感知哈希(Perceptual Hash)去重,或者更简单一点,用图片文件的MD5值先做一层去重,再用相似度计算做第二层去重。如果数据量比较大,可以先粗后细:MD5去掉完全相同的,再用感知哈希去掉视觉上几乎一样的。
5. 用测试集做评估的正确姿势:指标怎么选、结果怎么读
5.1 别让"准确率"骗了你:从精度、召回率到F1
分类任务中最常用的指标是准确率(Accuracy),但它有一个致命缺陷:在类别不平衡时严重失真。假设99%的样本是A类,1%是B类,模型全部预测为A类,准确率是99%,看起来非常好,可这个模型对B类完全无效。
正确做法是同时关注精确率(Precision)、召回率(Recall)和两者的调和平均F1值。精确率回答的是"模型预测为正类的样本里,有多少预测对了",召回率回答的是"所有真实的正类样本里,模型找回来了多少"。
实际场景里,精确率和召回率往往此消彼长。以缺陷检测为例:如果你更在意漏检(正类没有被找出来),就优先提高召回率;如果你更在意误报(把正常品判为缺陷),就优先提高精确率。具体需要通过调整分类阈值来平衡,这时候绘制的PR曲线(Precision-Recall Curve)能帮你直观地看到不同阈值下的权衡。
5.2 目标检测场景:mAP不是唯一的答案
如果你在用YOLO或Faster R-CNN这类检测模型,官方输出的指标里最显眼的是mAP。但mAP只是参考,它不是全部。我建议你看评估结果时分三步走。
第一步,看不同IoU阈值下的AP变化。mAP@0.5和mAP@0.5:0.95是两种常见口径。前者只要求预测框和真实框有50%的重叠就算正确,后者要求更严格,从50%到95%的多个阈值取平均。如果你的mAP@0.5不错但mAP@0.5:0.95很差,说明你的定位精度不够,框的位置大体对但不够精细。
第二步,按类别看AP。mAP是所有类别的平均,但平均数字好看不代表每个类别都好。很可能某一个大类别的AP很高拉高了平均值,另一个稀缺类别的AP低得吓人。按类别拆开才能发现模型的"偏科"问题。
第三步,结合PR曲线看置信度阈值。测试集上的PR曲线可以告诉你哪些类别容易混淆,以及不同置信度阈值下精度和召回率的实际情况。这对接下来的阈值设定和后处理策略非常重要。
5.3 从测试结果反推数据问题:测试集不只是用来打分的
测试集最容易被低估的价值是诊断功能。当测试结果不理想时,不要急着调模型结构,先回到数据层面检查几件事。
如果整体指标差,先看测试集里是否有训练集覆盖不到的新场景、新角度、新光照条件,这往往意味着你的训练集多样性不够。如果某个类别指标特别差,先数一下这个类别在训练集中有多少样本,如果严重不足,优先补这个类别的数据。如果误报严重,去看看误报样本长什么样,很可能是背景干扰或者类别边界定义模糊导致。
我自己的习惯是,每次测试集评估结束后,把错误样本按类别、按原因分组保存,截图整理成一份错误分析文档。这份文档比测试报告本身更有价值,它能直接指导下一轮的数据迭代方向。
5.4 测试集客观性:一旦模型定型,尽量只测一次
最后再强调一个原则性问题:测试集只应该在你认为模型已经定型后使用一次。如果测完发现还有提升空间,可以回去改模型、补数据,但测试集本身不应该成为你反复迭代的目标。
如果你在测试集上反复测试并持续改进,最终你得到的模型其实已经间接拟合了测试集。这个时候测试集的评估结果只是"最后一次实验的记录",而不是"模型泛化能力的无偏估计"。这就像一个学生反复做同一套模拟卷,做到最后每次都满分,但这并不能证明他掌握了所有知识点,只能证明他把这套卷子背熟了。
实际操作中,我一般会把数据集拆成训练集、验证集、测试集三部分后,把测试集"锁起来"——平时完全不去碰它,只在最终评估时解开。如果模型要发布或者要做技术决策时,再用测试集做最终检验。
回到我开头提到的那个现象——训练和验证表现很好,测试集上一测就崩。现在你应该知道,这种情况大概率不是模型的锅,而是数据集操作流程出了问题。要么是划分策略不对(随机划分了有序列相关性的数据),要么是信息泄露(增强数据混进了测试集、全量标准化、用测试集调参),要么是数据本身有问题(标注风格不一致、类别分布恶劣)。
我自己做了这么多年AI项目,最大的体会是:大部分人愿意在模型结构上花时间研究各种网络、各种注意力机制,却不太愿意认真打磨数据。但实战中你最终会发现,模型结构带来的提升可能只有一两个百分点,而数据集的质量、划分的严谨程度,动辄就是十个点以上的差距。尤其是当你开始用真实业务数据替代公开数据集做训练时,训练集和测试集的管理就直接决定了项目能不能从"实验室精度"走到"线上可用"。
下一次当你准备训练自己的数据集时,从采集、清洗、标注一路走到划分,每一步都多问一句:这一步操作会不会让测试集不再"干净"?如果答案是否定的,那你的测试结果就值得信任。后续这个系列我会接着讲数据标注工具的选择、以及如何用测试集的评估结果快速定位数据问题,如果你也正在被数据集折腾,可以先从这篇文章里的几个检查点入手排查一遍。