训练集、验证集、测试集怎么划分?YOLOv8数据泄漏避坑指南
发布时间:2026/10/2 5:08:37 锦皓数字建站

做目标检测或者训练自己的数据集的朋友应该都经历过这种让人抓狂的时刻用YOLOv8训练自己的数据集训练集loss一路降得漂漂亮亮最后用训练好的权重一测验证集mAP却低得离谱或者反过来验证集上分数很高一部署到实际场景就完全不是那么回事。很多人第一反应是“代码写错了”“数据标错了”“学习率没调好”于是开始疯狂调参。但我见过的大多数情况问题都出在一个更基础的地方训练集、验证集、测试集这三个集合的职责没理清楚尤其是验证集到底为什么存在、该拿来干什么很多人其实是一笔糊涂账。这篇文章就把这件事彻底讲明白。我会先用一个容易理解的类比把三个集合的分工讲清楚再重点解释为什么非要有验证集不可然后结合“YOLOv8训练自己的数据集”这类常见实操场景给出划分数据集的可行方案最后聊聊数据泄漏和交叉验证——这些才是你真正动起手来最容易踩坑的地方。1. 考试逻辑训练集、验证集、测试集各自管什么1.1 训练集教材、练习册还有刷题的错题本训练集是模型学习的直接素材。模型在训练集上做前向传播、计算损失、反向传播更新权重本质上就是学生对着课本和练习册反复刷题。训练集里的每一张图片、每一个标注框、每一段文本都是模型用来“归纳规律”的原材料。这里有个很多人没意识到的问题深度学习模型的容量非常大尤其是现在动辄上亿参数的神经网络。一个容量足够大的模型理论上可以把训练集里的样本“背”下来而不是真正理解其中的规律。这就是为什么训练集loss低根本不值得高兴——你只看到模型在熟悉的题上考得好却不知道它在陌生题上会不会直接交白卷。1.2 验证集模拟考试用来判断该不该调整备考策略验证集不参与模型的梯度更新但它参与你的决策过程。你用它来观察模型在“没做过的新题”上的表现然后根据这个表现决定学习率该调大还是调小网络深度要不要改数据增强策略要不要换正则化系数怎么设训练到什么轮次该停每一次你根据验证集的结果调整了某个超参数其实都是在做一次“模型选择”。验证集的价值就在于它可以被反复看、反复用用来指导你在训练过程中的各种决定而不会污染最终评估的公正性。1.3 测试集最终高考考完就封卷测试集在整个训练和调参过程中都应该被“雪藏”。它只在最后用来评估最终模型的泛化能力做完一次评估之后这个分数才有了真正的意义。测试集的原则是能不看就不看全程最多碰一次。三者的区别可以用下面这张表说清楚集合参与梯度更新参与模型选择使用频率核心目的训练集是间接影响每个batch都在用学习模型参数验证集否是每个epoch或每隔几轮选择超参数与模型结构测试集否否全程只碰一次评估最终泛化能力1.4 三个集合背后是同一个核心问题泛化我们要训练模型最终目标不是让它记住训练集而是让它对没见过的数据也能有好的表现这个能力叫“泛化”。训练集负责教会模型验证集负责在过程中帮你纠偏测试集负责给你一个诚实的评分。三者缺一不可但很多教程只讲了“要划分数据”没讲清楚为什么这样划分导致新手把验证集当成测试集用或者干脆不要验证集最后只能靠“玄学调参”。2. 没有验证集你的模型会变成“背题机器”这一节回答标题里那句“为什么要使用验证集”。我先说结论验证集存在的根本原因是让你有一个可以在训练过程中反复查看成绩、又不会让最终评估失效的“试错窗口”。没有它你会陷入两种尴尬境地。2.1 训练集loss持续走低模型却在过拟合深度神经网络拟合能力极强训练集上loss降到0.01都不稀奇。你监控训练集loss会发现它一路上涨的情况很少绝大多数时候都稳定下降。但过拟合的本质是模型把训练样本里的噪声、标注误差、个别特例也当成了规律。最典型的图像就是训练loss不断下降验证loss却在某个epoch之后开始反弹——这条“V型曲线”是过拟合的标志。如果没有验证集你根本看不见这条曲线只会看着训练loss继续傻等到训练结束最后拿到的模型在真实场景里一塌糊涂。2.2 只有训练集和测试集时你会被迫“偷看答案”假设你的数据只分成训练集和测试集两堆。现在你想调学习率该看什么指标训练集loss不能说明问题唯一有参考价值的就是测试集。于是你只能去测试集上跑一下发现效果不好回去改超参数再跑一次测试集。跑完之后又觉得哪里不对再改再测。这个过程重复十次之后你的模型已经在测试集上“学习”了十次。你每次根据测试结果做的调整都把测试集的信息间接注入到了模型选择里。测试集不再是未知的考卷它的统计特征已经被你摸透了。这在机器学习里叫“测试集污染”test set contamination结果就是测试分数虚高部署上线立刻翻车。2.3 验证集真正管的是“选择”测试集管的是“报告”一句话总结训练集决定模型“长什么样”验证集决定模型“选哪个”测试集负责报告模型“到底行不行”。更直白一点验证集是你训练过程中可以反复查看的模拟考成绩单测试集是最终高考成绩单。高考前你可以考无数次模拟但正式高考只有一次而且你得等到真正上考场那天才知道自己真实水平。如果提前偷看了高考题那这次成绩就完全失去参考价值了。这一点在做early stopping早停时体现得最明显。没有验证集你不知道该在哪个epoch保存权重有了验证集你可以在验证loss开始上升的瞬间把模型停下来保存“验证集上表现最好”的那份权重而不是最后一个epoch的权重。YOLOv8训练时默认保存的best.pt和last.pt就是这个逻辑——best.pt是基于验证集指标挑选出来的。3. 划分数据集实操YOLOv8训练自己的数据怎么切前面讲的是原理这一节落到具体的实操。既然热搜词里大量是“yolov8/yolov5/yolo11训练自己的数据集”“mmrotate训练dota数据集”这种目标检测场景我就以检测任务为例其他任务思路完全一样。3.1 先定划分比例再谈其他划分比例没有绝对标准取决于数据量数据规模建议划分说明上万张以上98% / 1% / 1%验证集和测试集各几百张就足够稳定几千到一万张8:1:1 或 9:0.5:0.5常规做法几百到一千张尽量多留训练数据验证和测试各取10%~20%再少就要上交叉验证几百张以下不建议用固定划分直接用K折交叉验证见第5节对目标检测任务划分的单位是“整张图片”不是“标注框”。有些人统计完标注框数量后想把同一张图里的框拆到不同集合这绝对是错误做法——同一张图的信息泄漏到了两个集合验证指标会虚高。3.2 分层划分保证每个集合都有足够的每一类样本如果你的数据里某个类别只有几十张随机划分很可能导致这个类别的样本全部跑到训练集验证集里一个都没有。后果是验证集上这类目标的指标波动极大甚至因为缺失而无法计算。解决办法是分层抽样stratified split。对单标签分类任务直接按类别标签分层对YOLO这种多标签检测任务按“每张图片包含的类别组合”做分层。下面这个脚本可以直接用import os import random import shutil from collections import defaultdict from sklearn.model_selection import train_test_split random.seed(42) IMG_DIR datasets/images LBL_DIR datasets/labels OUT_DIR datasets/split # 读取每张图包含的类别返回类别组合字符串 def stratify_key(img_name: str) - str: label_path os.path.join(LBL_DIR, img_name.replace(.jpg, .txt)) classes set() if os.path.exists(label_path): with open(label_path, r, encodingutf-8) as f: for line in f: parts line.strip().split() if parts: classes.add(parts[0]) return -.join(sorted(classes)) if classes else empty images [f for f in os.listdir(IMG_DIR) if f.endswith(.jpg)] keys [stratify_key(img) for img in images] # 第一次切分分出测试集 train_val, test train_test_split( images, test_size0.1, random_state42, stratifykeys ) # 第二次切分训练集和验证集 train, val train_test_split( train_val, test_size0.1 / 0.9, random_state42, stratify[stratify_key(img) for img in train_val] )切分完成后把文件复制到YOLO要求的目录结构里同时记得把标注文件一并复制并生成对应的train.txt/val.txt/test.txt路径列表。3.3 同分布铁律按场景切而不是随机切这是目标检测数据划分里最坑、也最容易被忽略的一条。如果你在做视频抽帧数据集或者用无人机、卫星影像切片做检测比如DOTA数据集那类大图切tile的操作同一段视频、同一张大图切出来的若干小图画面内容高度相似。这时候如果直接随机划分train和val里会出现大量“同源”图片模型在val上的表现会虚高得可怕因为它其实已经“见过”这些场景了。正确做法是先按“场景”分组再保证同一个场景的所有图片只进入一个集合。视频数据按视频片段分组切片数据按原始大图分组。mmrotate这类旋转检测框架处理DOTA数据时尤其要注意这一点——DOTA官方提供的是大图你切成小图后如果按小图随机分基本等于数据泄漏。3.4 验证集和测试集在训练过程中的处理差异还有一个实操细节数据增强只加在训练集上。验证集和测试集只做resize、normalize这类必要预处理不要做随机翻转、随机裁剪、mosaic这些增强。原因很简单验证集和测试集要代表“真实世界的数据分布”而真实世界的图片不会自带随机翻转和mosaic。如果你想评估模型在增强策略下的表现那就把增强当成超参数在训练集上做消融实验而不是去增强验证集。有些人为了“让验证结果更好看”给验证集也加上测试时增强TTA这在对比实验里是允许的但你必须明确记录否则最终分数没有可比性。4. 验证集分数虚高的元凶数据泄漏的隐蔽形态很多朋友训练时遇到“验证集mAP很高部署到现场就崩”的情况第一反应是模型不行其实更常见的原因是数据泄漏。数据泄漏不一定是“train和val图片重复”这种肉眼可见的低级错误它有很多隐蔽形态。4.1 泄漏的常见来源第一类预处理泄漏。你计算归一化用的均值和方差时使用了全部数据包括验证集和测试集。这样验证集/测试集的统计信息就被编码进了模型输入属于标准的泄漏。正确做法是只用训练集计算均值和方差然后把它固定下来应用到所有集合。第二类特征选择泄漏。如果你在做传统机器学习或特征工程在划分数据之前就对全量数据做了特征筛选那最终评估结果也会虚高。特征选择的依据来自测试集这等于偷看了答案。第三类相似样本泄漏。train和val里存在大量重复或近似重复的图片。比如同一场景的不同角度、同一物体的不同帧这些图片视觉上不完全一样但模型很容易“记住”它们的共同特征导致val分数虚高。第四类手动整理数据时造成的泄漏。很多人习惯先把所有图片按类别归档、人工清洗然后再划分。清洗过程中如果人工把“看起来像的”分到一起或者为了凑数量把某个类别的图片从别处复制过来都会造成隐性重复。4.2 怎么排查你的划分是否泄漏我常用的排查手段有三个一是计算训练集和验证集之间的图片相似度。可以用感知哈希pHash或者简单的像素差来判断是否有重复或近似重复图片发现相似度超过阈值的样本对就要检查是否跨集合了。二是对比每个集合的类别分布。如果某个类别在训练集占比30%在验证集占比只有2%那你需要检查划分过程是否出了问题或者数据本身类别不平衡。三是观察训练过程中的loss曲线。正常情况下训练集loss应该略低于验证集loss。如果你发现两个loss曲线几乎完全重合、验证loss下降速度和训练loss一样快并且全程没有差距这往往意味着验证集和训练集过于相似存在泄漏风险。4.3 另一个让人翻车的方向验证集不等于真实场景有时候划分没问题、也没泄漏但模型上线还是崩原因在于验证集的“来源”和真实部署场景不一致。比如你用行车记录仪白天视频做训练和验证部署时却要处理夜间场景那验证集再准确也没用。所以要尽量让测试集甚至验证集的采集条件贴近真实的部署环境。这属于领域漂移domain shift问题没有统计公式能帮你解决只能靠你对业务场景的理解来设计数据采集方案。我见过不少项目模型效果差不是训练问题而是从一开始的验证集就选错了“考试范围”。5. 数据太少怎么办K折交叉验证与嵌套交叉验证数据量少的时候固定划分会让每一份数据都很珍贵。如果总共只有500张图按7:2:1划分训练集只有350张验证集100张测试集50张——验证集和测试集都小得可怜指标波动会非常大你根本分不清调参的效果是变好了还是噪声。5.1 K折交叉验证的原理K折交叉验证把数据分成K份每次拿K-1份训练1份做验证轮流做K次最后把K次验证指标平均。这样每一份数据都既当过训练数据也当过验证数据模型的选择和评估都更稳定。from sklearn.model_selection import KFold kf KFold(n_splits5, shuffleTrue, random_state42) for fold, (train_idx, val_idx) in enumerate(kf.split(images)): train_imgs [images[i] for i in train_idx] val_imgs [images[i] for i in val_idx] # 用 train_imgs 训练val_imgs 验证记录指标 # fold_metrics.append(val_mAP)注意两点一是要设置shuffleTrue和random_state保证每次划分可复现二是如果类别不平衡优先用StratifiedKFold做分层交叉验证。5.2 目标检测中用交叉验证的代价交叉验证不是免费的。5折交叉验证意味着同样的训练要跑5次训练时间直接变成5倍。对于YOLOv8这种需要GPU跑几十分钟甚至几个小时的模型这个成本很现实。所以我的建议是数据量在几千张以上直接固定划分就够了没必要交叉验证数据量只有几百张交叉验证的价值远大于额外训练时间的成本数据量大但某一类特别少可以对类别做分层固定划分再决定是否交叉验证。5.3 嵌套交叉验证兼顾模型选择和评测如果你既要调超参数又要得到诚实的测试分数在数据量很小的情况下可以使用嵌套交叉验证nested cross-validation。外层循环负责评估内层循环负责模型选择。外层每一折都把数据分成训练验证两部分内层在训练部分上再做一次K折挑出最好的超参数然后用这个超参数在外层的验证部分上测试。嵌套交叉验证的代价更高不适合日常训练但它能给出一个相对无偏的模型性能估计。做学术实验、写论文、做算法选型调研时这招非常有用。6. 我踩过的坑关于验证集的几条血泪经验这一节不讲理论只讲我在实际项目中犯过的错误希望你能绕开。6.1 坑一不按类别分层验证集指标上下乱跳有一次做工业质检分类数据集里有一个瑕疵类别只占5%。我当时直接随机划分8:2结果划分完才发现这个瑕疵类别在验证集里只有3张图。训练过程中验证准确率一会儿95%一会儿70%看起来像模型不稳定实际是验证集样本太少了。后来改成按类别分层划分曲线立刻稳定下来。从那以后任何数据集划分我都会先统计每个类别的数量确认分布一致再动手。6.2 坑二视频抽帧直接随机划分做视频目标检测时我从监控视频里每5秒抽一帧攒了三千多张图然后随机划分训练集和验证集。训练时验证mAP高达0.9心里挺高兴结果部署到另一段完全不同的视频上mAP直接掉到0.4场面非常难看。后来排查才发现同一镜头的相邻帧被同时分到了训练集和验证集验证指标根本不诚实。最后改成按视频片段分组保证同一场景的帧只进入一个集合才得到真实可用的模型。6.3 坑三用了测试集结果调参还不自知早期做项目时为了赶进度我一边调参一边拿测试集测效果。当时没意识到这样做有什么问题只觉得“测试分数一直有提升项目进展很顺利”。后来看了不少资料才反应过来那部分测试集已经被我污染了。项目交付时我重新标了一批新数据当测试集分数比之前低了七八个点幸好发现得早。现在我的铁律是测试集一旦划定任何人不得以任何理由去看测试集的结果除非是最终评估。6.4 我现在固定下来的工作流总结一下我现在每接一个训练任务划分数据时都会按这个流程走先按场景/视频/原始大图分组保证同组数据不进不同集合统计每个类别的数量用分层抽样做第一次划分切出测试集并锁死从剩余数据里再分层划分出验证集只在训练集上计算归一化参数训练过程中只盯训练集loss和验证集指标测试集全程不看保存模型只用验证集上表现最好的权重而不是最后一个epoch的权重最终评估时测试集只跑一次记录结果后不再改动。这套流程看起来朴素但能挡住绝大多数数据划分和验证集使用上的坑。说实话模型结构、损失函数、数据增强这些东西很多开源框架已经帮你优化得很好了真正拉开项目落地差距的往往就是你对待验证集和测试集的严谨程度。
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。