YOLOv5三类别水果检测数据集:从标注到训练全流程解析

📅 发布时间:2026/8/27 1:43:52
YOLOv5三类别水果检测数据集:从标注到训练全流程解析 简介目标检测作为计算机视觉的核心任务其模型性能高度依赖于训练数据的质量与组织方式。在工业质检、智能农业等场景中高质量数据集往往需要遵循严谨的标注规范与目录结构。YOLO格式采用归一化坐标表示目标框要求图片与标签文件名一一对应并通过data.yaml配置类别信息。数据集的构建涉及图片采集、标注工具使用、清洗校验、训练集与验证集划分等关键环节尤其要避免数据泄漏并确保类别分布均衡。以苹果、橘子、梨三类别水果检测数据集为例从标注到训练全流程的实践能帮助初学者快速掌握YOLOv5的训练闭环并迁移到真实业务场景。合理的数据管理策略包括版本迭代与体检脚本是提升模型精度的基础保障。 做目标检测的朋友不管你最后是搞工业质检、自动驾驶还是智慧农业入门的第一个坎几乎都是数据集。尤其是刚接触YOLOv5那会儿总想找一份“干净、规范、能直接跑起来”的数据集练手而不是上来就面对一堆格式混乱、标注缺漏、目录结构随意的烂摊子。水果目标检测苹果、橘子、梨这个选题看起来简单实际上是特别适合拿来理解整个YOLOv5训练闭环的经典场景。三类目标形状有相似也有差异颜色有区分也有干扰正好用来踩一遍数据标注、格式整理、训练配置、结果评估的完整流程。这篇文章把一份完整的三类别水果目标检测数据集按YOLOv5标准目录组织含训练集、验证集从底层逻辑到实战操作拆开讲清楚。你看了之后既能直接用这份数据跑通训练也能举一反三把自己手里的业务数据整理成同样的格式。适合刚入门目标检测的初学者也适合想规范数据管理流程的开发者参考。1. 三类别水果检测的选题逻辑与YOLOv5目录结构的硬性要求1.1 为什么是苹果、橘子、梨而不是更复杂的场景大多数YOLOv5教程用的都是COCO数据集或者网上爬的车辆行人数据但说实话对新手并不友好。COCO类别多、标注繁杂一张图里几十个目标跑一次训练光数据预处理就够折腾。而苹果、橘子、梨三种水果是一个“难度刚好”的中间档位。先说难度在哪里。苹果的常见颜色是红色和绿色橘子的典型颜色是橙色而梨通常是黄绿色三者之间存在明显的颜色重叠——青苹果和橘子在某些光照下颜色很接近梨和青苹果在色彩特征上也会互相干扰。此外苹果和橘子都是近球形形状特征高度相似只靠“圆不圆”根本分不开必须依赖颜色、纹理、反射光泽等细粒度特征。这就让模型不能靠单一特征“偷懒”必须真正学到多种特征的组合。同时三种水果的尺寸跨度不小有的小橘子只有几个像素有的苹果占了大半个画面这又天然模拟了真实业务里的小目标和中大目标共存的情况。这个设计恰好覆盖了目标检测里最常见的几个核心难点类间相似、类内差异、尺度变化、遮挡重叠。用这份数据练出来的模型迁移到其他检测任务上泛化能力是有保障的。如果你拿一份全是“完美正面、单一背景、统一光照”的数据集来学训练过程很顺但一上真实场景就原形毕露。水果数据集的意义就在于用最低的成本让你体会到真实数据的“不完美”。1.2 YOLOv5数据集目录格式到底在要求什么很多同学下载数据集后习惯性用tree命令看一眼目录发现结构好像是那么回事但训练时一报错就懵了。这里把YOLOv5的目录格式硬性要求列清楚对照着检查就行了。一份标准的YOLOv5检测数据集目录结构应该是这样的fruits-dataset/ ├── images/ │ ├── train/ │ │ ├── apple_001.jpg │ │ ├── apple_002.jpg │ │ ├── orange_001.jpg │ │ └── ... │ └── val/ │ ├── apple_val_001.jpg │ └── ... ├── labels/ │ ├── train/ │ │ ├── apple_001.txt │ │ ├── apple_002.txt │ │ └── ... │ └── val/ │ ├── apple_val_001.txt │ └── ... ├── data.yaml └── README.md核心要求只有两条但这两条踩坑的人无数第一images和labels必须同级并列不能把标签放到图片目录里面也不能把它们放到数据集根目录之外。train.txt、val.txt这类文件列表只在老版本YOLOv3/v4里是必须的YOLOv5已经不需要了它直接通过遍历images/train和images/val目录来寻找对应的标签文件。第二图片和标签的文件名必须完全一致不含扩展名。图片叫apple_001.jpg标签就必须叫apple_001.txt。后缀名不限.jpg、.jpeg、.png都行但主文件名必须一点不差。YOLOv5在训练时通过把图片路径中的images替换为labels、把图片后缀替换为.txt来定位标签文件所以只要主名对不上这个样本直接跳过并告警。1.3 每个txt标签文件内部的“归一化坐标”是怎么算的标签文件不是随便写的每一行对应图片中的一个目标格式为class_id x_center y_center width height注意这里的x_center y_center width height全部是归一化坐标取值在0到1之间而不是像素坐标。计算公式是x_center bbox_x_center_pixel / image_width y_center bbox_y_center_pixel / image_height width bbox_width_pixel / image_width height bbox_height_pixel / image_height举个例子一张640×480的图片中一个苹果的检测框左上角像素坐标是(200, 150)右下角是(420, 350)。那么bbox_x_center_pixel (200 420) / 2 310 bbox_y_center_pixel (150 350) / 2 250 bbox_width_pixel 420 - 200 220 bbox_height_pixel 350 - 150 200归一化得到x_center 310 / 640 0.484375 y_center 250 / 480 0.520833 width 220 / 640 0.343750 height 200 / 480 0.416667对应的标签行就是0 0.484375 0.520833 0.343750 0.416667这里class_id从0开始计数所以0对应苹果1对应橘子2对应梨。这个顺序一旦在data.yaml里确定了就不能随意更改否则训练和推理时的类别语义就乱了。我自己习惯在标注前就确定好类别顺序写进data.yaml后就不再变动因为中途改类别索引是一件极其头痛的事会导致已经标注好的所有标签文件都要重新映射。2. 从图片来源到标注完成构建这份水果数据集的完整流程2.1 原始图片采集数量、质量与多样性怎么同时兼得这份数据集总共包含约2000张图片训练集1600张验证集400张。三类水果分布比较均匀苹果约680张、橘子约660张、梨约660张。不是每张图只含一种水果很多图片里同时出现两种或三种水果所以图片数量不是简单的类别数量相加。采集图片时我遵循了三个原则这也是做任何检测数据集都通用的基本原则一是场景多样性。同一种水果拍摄背景不能单一。桌面、果园、超市货架、白色盘子、木制砧板、塑料包装袋等都要覆盖到。因为目标检测模型非常“吃”背景信息如果训练集里全是白底图验证集一换成木桌mAP可能直接掉10个点以上。背景也就是深度学习里说的“上下文”在检测任务中扮演的角色远超很多初学者想象。二是光照多样性。自然光、室内白炽灯、夜间闪光灯、强逆光、侧面光照都要有。特别是逆光条件下苹果和橘子的颜色饱和度会明显下降边缘会发暗这对模型学习“颜色并非恒定特征”很有帮助。真实场景中光照永远是变化的训练时见过足够多的光照条件推理时才能对光照变化不敏感。三是姿态与尺度多样性。水果摆正、歪放、堆叠、被遮挡、距离镜头远近不同都要尽量覆盖。尤其是遮挡这是目标检测里最难的场景之一。一个苹果被另一个苹果挡住一半检测框应该怎么画模型只有见过足够多的遮挡样本才能学会合理地预测被遮挡目标的完整范围。采集方式上可以用手机拍摄也可以用网络爬虫抓取公开图片但要注意版权和合规问题。最省事靠谱的办法是自己买几种水果在办公桌、厨房、阳台等不同环境拍摄花一个下午就能拍一两百张高质量的原始图片。然后配合数据增强手段把每张原始图生成多个变体相当于数据量翻了好几倍。2.2 使用LabelImg进行标注效率和规范之间的平衡标注工具我用的是LabelImg它是目标检测领域最常用的开源标注工具之一支持PascalVOC和YOLO两种输出格式。它的使用流程比较简单打开图片目录、加载预设类别、画框、保存。安装方式pip install labelimg或者从GitHub拉源码运行git clone https://github.com/HumanSignal/labelImg.git cd labelImg pyrcc5 -o libs/resources.py resources.qrc python labelImg.py启动后左侧有一个“Create RectBox”按钮点击后就可以在图片上拖动鼠标画矩形框。画完后会弹出类别选择框输入对应类别即可。这里强烈建议提前在data/predefined_classes.txt文件中定义好类别列表apple orange pear这样标注时只需要点选不会出现输入法切来切去导致类别名称不一致的问题。标注时有几个细节需要特别注意这些都是后续训练时直接踩过的坑一是标注框要尽量贴合目标边缘不能框得太大或太小。框太大会把背景噪声带进正样本框太小会截断目标边缘导致模型学到的特征不完整。经验是对于规则形状的水果框的四边应该刚好贴着水果的最外侧边缘留出1到2像素的余量即可。对于有明显茎部比如苹果蒂、梨蒂的情况茎部如果和果实相连可以包含在框内如果茎部特别长且伸出去很远则不包含在内因为检测目标相对的是“果实主体”。二是单张图里多个目标互相重叠时每个目标都要单独画框即使某个目标被遮挡了90%只要还能辨认出来就应该标注。这里面有一个原则目标是否“可辨认”取决于人眼能否判断出它是什么类别。如果只有一个几像素的色块作为人根本无法判断那是个苹果还是橘子那就不标如果能看到一部分轮廓和颜色能判断出是梨那就标。这直接决定了模型对遮挡目标的召回率。三是标注过程中要定期保存。LabelImg默认每标注一张图就保存一次但如果你手动修改了某个框的位置记得手动按CtrlS保存不然后续训练时标签和图片对应不上又要回头排查。使用LabelImg时推荐开启“自动保存模式”即标注完一张图后自动保存标签文件并自动跳转到下一张图。这样能把标注速度提升30%以上。在LabelImg的View菜单中勾选Auto Saving再配合Next Image的快捷键字母D整个标注流程就能非常丝滑。2.3 标注数据清洗与一致性检查不要直接拿原始标注去训练标注完成后一定不要直接拿去训练。必须做一轮数据一致性检查。这一步很多初学者会跳过直到训练时发现loss异常偏高、mAP诡异才回过头来排查标注问题。真到那一步排查成本远高于提前检查。检查内容包括三个层面第一图片与标签文件是否一一对应。用脚本遍历图片目录和标签目录找出没有对应标签的图片、没有对应图片的标签。使用Python脚本可以快速实现import os from pathlib import Path img_dir Path(images/train) label_dir Path(labels/train) img_files {p.stem for p in img_dir.glob(*.jpg)} label_files {p.stem for p in label_dir.glob(*.txt)} missing_label img_files - label_files missing_image label_files - img_files print(缺标签的图片:, len(missing_label)) print(缺图片的标签:, len(missing_image))第二标签内容是否符合格式规范。检查标签文件中的每一行是否包含5个数值类别id是否在0-2范围内归一化坐标是否都在0到1之间。坐标值如果大于1或者小于0说明标注框超出了图片边界需要修正。第三使用可视化脚本把标注框画回图片上人工抽查。这一步最直观只要快速浏览100张左右的可视化结果就能发现大部分标注问题。包括框的位置偏移、类别标错、框尺寸不合理等。可视化可以用OpenCV实现import cv2 import numpy as np def draw_yolo_boxes(image_path, label_path, class_names): img cv2.imread(image_path) h, w img.shape[:2] with open(label_path) as f: for line in f.readlines(): parts line.strip().split() cls_id int(parts[0]) x_c, y_c, bw, bh map(float, parts[1:]) x1 int((x_c - bw / 2) * w) y1 int((y_c - bh / 2) * h) x2 int((x_c bw / 2) * w) y2 int((y_c bh / 2) * h) cv2.rectangle(img, (x1, y1), (x2, y2), (0, 255, 0), 2) cv2.putText(img, class_names[cls_id], (x1, y1 - 5), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 0, 255), 2) return img把抽查出的问题样本重新标注或剔除确保数据质量后再进入下一步。这一步投入的时间一定会在训练时加倍省回来。坏数据训练出的模型不仅指标差而且错误是“隐性”的——它不会报错只会安安静静地输出一个让你误以为模型能力不足的mAP。3. 训练集与验证集划分不是随手split就算完了3.1 划分比例怎么定才合理82还是91训练集与验证集的划分比例是这个数据集的核心问题之一。我最终采用的是82的比例1600张训练、400张验证因为总数据量2000张不算大验证集占比太低会导致评估结果抖动剧烈占比太高则训练数据不足模型学不到位。你可以根据数据总量调整如果总量超过5000张可以适当把验证集比例降到15%左右如果总量不足1000张建议用K折交叉验证而不是简单的一次性划分否则验证集的结果不具有统计意义。另外如果后续准备做模型调优或超参数搜索最好再单独留出一部分“测试集”完全不参与训练过程只在最终评估时使用一次。但对于这份数据集的定位——一个入门级的、可直接用于YOLOv5训练的三类别数据集——训练集和验证集的划分已经够用。你可以把验证集当作调优参考在最终评估时单独再收集一小批新图片作为测试。3.2 按文件级还是按目录级划分这是个数据泄漏问题划分训练集和验证集时最忌讳的一件事就是“数据泄漏”。所谓数据泄漏是指验证集中的图片和训练集中的图片相似度过高导致验证集不能真实反映模型的泛化能力。在水果数据集上数据泄漏的典型场景是同一颗苹果从不同角度连拍了5张照片其中3张分到了训练集2张分到了验证集。因为拍摄角度、光照、背景都极为相似模型在训练时已经见过这颗苹果的“几乎完全一样”的样子验证时再看到它相当于开卷考试分数虚高。这个问题最好的解决办法是在划分之前按“数据来源”分组。如果这批照片来自10次不同场景的拍摄那就按场景划分选择一个或两个场景的全部图片作为验证集其余场景作为训练集。而不是把所有图片打乱后随机抽取20%作为验证集。这样划分验证集才真正模拟了“模型从未见过的新场景”。在实际构建中我采用的方法是把同一次拍摄序列比如同一时间、同一背景、连续拍摄的图片视为一个分组然后在分组级别上做随机划分。这避免了同一场景的连续帧被拆散到两个集合中。实现逻辑不复杂import os import random import shutil from pathlib import Path random.seed(42) # 假设按拍摄批次命名文件前缀如 batch1_apple_001.jpg img_dir Path(raw_images) train_img_dir Path(dataset/images/train) val_img_dir Path(dataset/images/val) train_label_dir Path(dataset/labels/train) val_label_dir Path(dataset/labels/val) # 按批次前缀分组 batches {} for img_path in img_dir.glob(*.jpg): prefix img_path.name.split(_)[0] # 如 batch1 batches.setdefault(prefix, []).append(img_path) all_files [] for prefix, paths in batches.items(): all_files.append(paths) # 每个batch作为一个整体 random.shuffle(all_files) split_idx int(len(all_files) * 0.8) train_batches all_files[:split_idx] val_batches all_files[split_idx:] for batch in train_batches: for img_path in batch: shutil.copy(img_path, train_img_dir / img_path.name) # 假设标签在 raw_labels 目录中 label_path Path(raw_labels) / (img_path.stem .txt) shutil.copy(label_path, train_label_dir / (img_path.stem .txt))还有一种思路是直接对每个类别的图片数量做分层抽样保证训练集和验证集中三个类别的比例大致相同。对这份数据集来说因为三个类别在图片总数上已经是均匀分布的直接按批次分组也不会破坏类别平衡。如果类别分布很不均匀就需要先按类别分层再在每一层内部划分。3.3 验证集的作用不只是看指标它还是训练过程中的“裁判”验证集在YOLOv5训练过程中扮演的角色很多人理解得不够透彻。它不只是在训练结束后用来评估一次而是在每个epoch结束后都会在当前模型上推理一遍计算验证集的loss和mAP然后决定是否保存当前权重。YOLOv5的默认机制是只要验证集上的mAP比之前最好的结果更好就保存一份新的best.pt。所以验证集的质量直接决定了你最终拿到的最佳模型是否真的好。验证集的图片质量如果和训练集存在系统性差异比如全是单一背景、全是完美光照那么训练过程中的模型选择就会被带偏。你可能在训练完看日志发现best.pt是在第120个epoch保存的但它在真实场景上的实际表现还不如第80个epoch的last.pt。这个现象在行业里叫“验证集过拟合”根本原因就是验证集不能代表目标分布。因此在构建这份水果数据集时验证集的图片分布尽量贴近真实使用场景。我没有把验证集做成“只放干净背景的单果图”而是真实地放入了大量遮挡、堆叠、混合类别的图片。这样做会让验证mAP数值不是那么“好看”但模型部署到实际环境后的表现会稳定得多。宁可分数低一点也要分数真实。4. 训练前的数据体检标签体检脚本与data.yaml的编写细节4.1 一键运行的数据完整性检查脚本在做完划分后、正式训练前我建议你无论用什么数据集都跑一遍下面这个数据体检脚本。它能在几分钟内完成对数据集完整性的全面扫描提前暴露绝大多数会导致训练异常或结果偏差的问题。import os from pathlib import Path data_root Path(fruits-dataset) def check_split(split): img_dir data_root / images / split label_dir data_root / labels / split if not img_dir.exists(): print(f[ERROR] 图片目录不存在: {img_dir}) return if not label_dir.exists(): print(f[ERROR] 标签目录不存在: {label_dir}) return img_files list(img_dir.glob(*.jpg)) list(img_dir.glob(*.png)) list(img_dir.glob(*.jpeg)) label_files list(label_dir.glob(*.txt)) img_stems {p.stem for p in img_files} label_stems {p.stem for p in label_files} img_without_label img_stems - label_stems label_without_img label_stems - img_stems if img_without_label: print(f[WARN] {split} 中以下图片缺少标签: {len(img_without_label)}张) if label_without_img: print(f[WARN] {split} 中以下标签缺少图片: {len(label_without_img)}个) # 检查标签内容格式 bad_labels [] out_of_range [] invalid_cls [] for label_file in label_files: with open(label_file) as f: lines f.readlines() for line in lines: parts line.strip().split() if len(parts) ! 5: bad_labels.append(label_file.name) continue try: cls_id int(parts[0]) vals [float(v) for v in parts[1:]] except ValueError: bad_labels.append(label_file.name) continue if cls_id not in [0, 1, 2]: invalid_cls.append(label_file.name) if any(v 0 or v 1 for v in vals): out_of_range.append(label_file.name) if bad_labels: print(f[ERROR] {split} 格式错误的标签文件: {len(bad_labels)}个) if invalid_cls: print(f[ERROR] {split} 包含非法类别id的文件: {len(invalid_cls)}个) if out_of_range: print(f[ERROR] {split} 包含越界坐标的文件: {len(out_of_range)}个) # 统计每个类别的目标数 cls_count {0: 0, 1: 0, 2: 0} total_boxes 0 for label_file in label_files: with open(label_file) as f: for line in f.readlines(): parts line.strip().split() if len(parts) 5: cls_id int(parts[0]) if cls_id in cls_count: cls_count[cls_id] 1 total_boxes 1 print(f[INFO] {split} 总图片数: {len(img_files)}) print(f[INFO] {split} 总标注框数: {total_boxes}) print(f[INFO] {split} 类别0(苹果)出现次数: {cls_count[0]}) print(f[INFO] {split} 类别1(橘子)出现次数: {cls_count[1]}) print(f[INFO] {split} 类别2(梨)出现次数: {cls_count[2]}) check_split(train) check_split(val)这个脚本输出的信息分几个层次文件缺失WARN级别、格式错误ERROR级别、类别分布统计INFO级别。文件缺失会导致样本被静默跳过格式错误会直接导致训练脚本崩溃类别分布不均衡会影响模型的收敛速度和最终精度。我实际使用的过程中通过这些检查发现过手误把一张梨的标签类别写成了3非法类别id因为总类别只有0、1、2导致训练脚本直接抛出AssertionError还有一张图片因为压缩软件问题JPEG头损坏OpenCV读不出来但文件仍然存在导致数据加载阶段报错“无法解码图像”。4.2 data.yaml写法绝对路径、相对路径还是纯目录名data.yaml是连接数据集与训练脚本的桥梁写错它训练根本跑不起来。我先给出这份数据集使用的配置文件然后逐个字段解释其含义和注意事项。# data.yaml path: ./fruits-dataset # 数据集根目录相对于当前工作目录 train: images/train # 训练集图片目录相对于path val: images/val # 验证集图片目录相对于path nc: 3 # 类别数 names: [apple, orange, pear] # 类别名称与标注时的类别顺序一致YOLOv5在解析这个文件时会拼接出完整的图片目录路径{path}/{train}和{path}/{val}。然后遍历这两个目录下的所有图片并从标签目录中寻找对应的标注文件。关于path字段存在三种写法坑也不一样写法一绝对路径path: /home/user/data/fruits-dataset。这种方式最稳妥但换机器后必须修改不然就会报“数据集路径不存在”的错误。写法二相对路径path: ./fruits-dataset。这种方式要求你在运行训练命令时当前工作目录必须包含fruits-dataset这个文件夹。如果脚本在别处调用就会因为找不到路径而报错。写法三留空直接把train和val写成完整路径比如train: /home/user/data/fruits-dataset/images/train。这种方式可以工作但不符合YOLOv5的推荐习惯也不便于后续对数据集目录做迁移。我推荐写法一在单机训练时最稳定。如果你是在团队协作或多机训练则应该用相对路径或环境变量来避免硬编码路径。另外很多人不知道的是data.yaml中的names列表顺序必须与标注过程中的类别id顺序完全对应。如果你在LabelImg中预设的类别顺序是apple, orange, pear那么在data.yaml中也要保持这个顺序。如果不一致你训练出来的模型会在推理时把“橘子”识别成“梨”而且根本不会有任何报错提示。4.3 验证集是否需要单独的data.yaml有的做法是给训练集和验证集分别写一个data.yaml比如train.yaml和val.yaml。对于YOLOv5我建议不要这样做。一份data.yaml同时指定train和val目录是官方设计且推荐的方式。训练过程中模型会在每个epoch结束时自动在val目录上推理并计算指标无需额外设置。如果你确实需要在同一份数据上做多次不同的划分实验比如5折交叉验证那可以按折数生成多个yaml文件比如fold1.yaml、fold2.yaml等。但对于单个训练任务一个data.yaml就够了。我见过有人把splits目录下的多个yaml文件混在一起结果跑训练时加载了错误的配置文件损失了好几个小时训练时间这个属于低级错误配置文件的命名和保存原则就是“一训练任务一配置文件”。5. 用这套数据集跑通YOLOv5训练环境、命令与超参的实战调整5.1 环境安装版本匹配CUDA、PyTorch、Python三者不能各说各话在开始训练之前先把环境准备好。YOLOv5官方对Python版本的要求是3.8以上建议3.9或3.10实测3.8也能跑但个别依赖可能需要降级。PyTorch版本建议1.10以上与CUDA的对应关系可以在PyTorch官网查到。我这里给出一个实测稳定的组合供参考Python 3.9PyTorch 2.0.1 CUDA 11.8torchvision 0.15.1cuDNN 8.6.0显卡驱动 ≥ 450.80.02CUDA 11.8要求安装依赖时建议用虚拟环境隔离不污染系统Python环境。使用conda创建环境是最省心的方法conda create -n yolov5 python3.9 conda activate yolov5 pip install torch2.0.1 torchvision0.15.1 --index-url https://download.pytorch.org/whl/cu118 pip install -r requirements.txt克隆YOLOv5官方仓库并切换到稳定分支git clone https://github.com/ultralytics/yolov5.git cd yolov5 git checkout v7.0这里建议锁定版本因为YOLOv5不同版本之间的代码差异较大超参数配置文件的格式也不完全一样。用v7.0这个版本配合上面这套环境稳定性好社区资料多遇到问题排查起来也容易。5.2 训练命令逐参数拆解不只是“跑起来”还要知道每个参数的作用训练命令是整篇技术文章的核心。先给出一份实测可用的完整命令然后逐个参数解释它是什么、为什么这样设置。python train.py \ --data ../fruits-dataset/data.yaml \ --weights yolov5s.pt \ --img 640 \ --batch-size 16 \ --epochs 100 \ --model yolov5s.yaml \ --name fruits_exp \ --cache逐参数来分析--data指定数据集的配置文件路径也就是上一节写的data.yaml。注意路径的写法如果yaml文件中path用了绝对路径这里就用相对路径指向yaml文件也可以如果yaml中path是相对路径那么这里的位置会影响相对路径的解析。最省心的做法是yaml里用绝对路径命令行里也用绝对路径。--weights yolov5s.pt预训练权重文件。这是很多初学者困惑的地方我是从零开始训练还是用预训练权重答案很明确用预训练权重。yolov5s.pt是在COCO数据集上预训练好的模型权重包含了大量通用视觉特征边缘、纹理、颜色块等。以它为起点训练自定义数据集收敛速度更快、精度更高。不用预训练权重从零开始训练2000张图片的数据量远远不够模型很容易过拟合欠拟合两头摇摆。如果网络环境不允许下载权重文件可以用--weights yolov5s.yaml代替表示从零开始训练但效果通常差不少不推荐。--img 640输入图片的像素尺寸。YOLOv5会把训练图片统一缩放到640×640再输入网络。你的原始图片可能尺寸不一YOLOv5会自动做letterbox处理即等比缩放并填充灰色边框保持长宽比不变。为什么不直接拉伸因为拉伸会改变物体形状比例导致苹果变成“椭圆”对形状特征的学习带来干扰。640这个值也是一个均衡选择太小则小目标信息丢失严重太大则显存占用翻倍、训练速度急剧下降。有显卡条件的话可以试--img 1280但需要的显存大约呈平方关系上升。--batch-size 16批大小。在显存允许的范围内批大小越大梯度的方差越小训练越稳定。但16是一个比较保守的值在8GB显存如RTX 3060 Laptop上跑YOLOv5s640输入、默认参数16的batch差不多是极限。如果显存不足可以减到8或4但学习率可能也需要相应调整。如果你发现训练时loss在初期震荡很大一个可能的原因就是batch太小。--epochs 100训练轮数。100轮对2000张图的数据集来说是一个合适的量级。YOLOv5默认的早停机制patience参数默认100会在验证集指标连续100轮没有提升时自动停止训练。但我建议不要依赖这个默认值而是观察训练曲线来决定是否提前停止这个后文会详细讲。--model yolov5s.yaml模型结构配置文件。这里需要理解--weights和--model的关系。--model指定网络结构它决定了模型有几层、每层多少通道、什么结构--weights指定初始权重值。当你使用预训练权重时YOLOv5会根据--model定义的类别数自动替换最后的检测头所以即使COCO是80类、你的数据是3类预训练权重中的特征提取部分也能被充分利用。网络结构文件里的nc会在读取data.yaml后被覆盖所以yolov5s.yaml里的nc: 80不需要手动改。--cache是否把图片缓存到内存中。这个参数在数据规模不大时非常有用。YOLOv5默认每个epoch都从硬盘重新读取图片2000张图读起来其实很快但如果你用的是机械硬盘每次读取都要几十秒100个epoch就浪费了一个多小时。加上--cache后第一次读取会把图片和标签缓存到内存中后续epoch直接从内存读取训练速度提升非常明显。缺点是需要足够的内存空间2000张640×640的图片约占2到3GB内存一般机器都能承受。5.3 超参数初始化文件hyp.scratch-low.yaml默认配置到底改还是不改YOLOv5的超参数配置文件定义了学习率、动量、权重衰减、数据增强强度等训练参数。官方提供的hyp.scratch-low.yaml是默认配置适用于大多数场景。对这份水果数据集我的建议是前两轮训练先用默认配置跑不要动它。原因很简单如果默认配置下的结果不佳你至少知道问题出在数据或模型结构上而不是超参数如果你一上来就改了超参数结果反而变差你就不知道是哪一步出了问题。什么时候需要调整超参数看训练过程的具体表现如果loss收敛速度过慢可以适当提高初始学习率lr0从0.01调到0.02如果loss在前几十个epoch抖动剧烈、不下降则降低学习率到0.005如果验证集mAP在训练后期持续震荡不收敛可以调低数据增强强度参数比如hsv_h、hsv_s、fliplr等。需要特别注意的是YOLOv5的自动学习率调度机制。train.py会使用LinearLR和CosineAnnealingLR的组合在训练过程中动态调整学习率。所以即使你完全不修改学习率相关的超参数训练后期的实际学习率也会很低帮助模型稳定收敛。大部分情况下默认超参数配合足够多的epoch已经能取得不错的结果。6. 训练结果解读与常见坑排查曲线、mAP和各种“看起来正常但实际有问题”的情况6.1 训练日志与results.png怎么看不是在“看”而是在“读”训练开始后YOLOv5会在runs/train/fruits_exp/目录下生成一组文件。其中最重要的有三个results.png、weights/best.pt、weights/last.pt。results.png包含多个子图包括train/box_loss、train/obj_loss、train/cls_loss、val/box_loss、val/obj_loss、val/cls_loss、metrics/precision、metrics/recall、metrics/mAP0.5、metrics/mAP0.5:0.95。这些曲线串联起来就是训练过程的完整“心电图”。关键是怎么读这些曲线正常情况训练loss曲线随着epoch增加稳步下降然后趋于平缓验证loss曲线先下降中途可能出现小幅度回升但整体趋势是向下的mAP曲线持续上升最终趋于稳定。可疑情况训练loss持续下降但验证loss从某个epoch开始不再下降甚至上升这是过拟合的典型信号。此时应提前停止训练并选择验证loss最低点的模型权重通常就是best.pt。另一种常见情况是验证loss在训练后期出现周期性“锯齿”一个可能的原因是验证集太小导致评估结果波动另一个可能是batch size太小训练不稳定。还有一个容易忽略的指标是metrics/precision和metrics/recall。如果precision非常高接近1但recall很低0.5以下说明模型预测得很保守只在自己的高置信度区域输出检测框漏掉了不少真实目标。如果recall高但precision低说明模型倾向于把背景或错误类别也预测为目标。水果检测任务中比较理想的状态是precision和recall都在0.85以上且两者差距不超过5个百分点。6.2 混淆矩阵与PR曲线单靠mAP会掩盖什么问题mAP是一个聚合指标它把precision和recall综合成了一个数这对比较不同模型很方便但会掩盖一些关键的局部问题。你需要额外查看confusion_matrix.png和PR_curve.png这两个可视化文件。混淆矩阵能告诉你苹果被误判成橘子的比例是多少橘子被漏检的比例是多少背景被误判成梨有多少如果苹果被误判成橘子的比例超过5%说明这两个类别的特征区分度不够可能需要检查标注时是否有大量“青苹果”被错误地归入橘子或梨。如果背景被误判的比例较高则说明标注框可能偏大把过多背景区域包含进来了。PR曲线显示的是不同置信度阈值下precision和recall的trade-off。曲线下的面积就是AP。如果某一类别的PR曲线出现“直角形”recall很低时precision就急剧下降说明模型的置信度校准有问题可能需要在推理时调整置信度阈值。YOLOv5默认的置信度阈值是0.25如果你的PR曲线显示0.25对应的点远非最优可以在推理时用--conf-thres参数调整。对于这个三类别水果数据集我训练完成后得到的mAP0.5约0.93mAP0.5:0.95约0.81。不同类别的AP从高到低依次是梨、苹果、橘子。橘子AP偏低的原因很直接——橘子颜色与背景木桌、纸箱在某些光照下的对比度不够且橘子表面反光容易造成纹理特征不稳定。这是天然的光学问题不是标注或代码bug。6.3 数据增强缺失与标签错误的经典症状训练中遇到的一些问题表面上看是“玄学”实际上都有固定的症状和对应原因。以下几条是我做这个水果数据集时实际踩过或观察过的列出来供你排查时对照症状一训练loss下降很快前5个epoch就降到很低但验证mAP很低0.3以下。这通常出现在使用预训练权重但训练数据类别与预训练分布差异很大的情况下。模型只是在“死记硬背”训练集并没有学到真正的泛化特征。解决方法是检查训练集和验证集的分布是否一致以及数据增强是否过强导致模型学不到稳定的特征。症状二训练loss不下降甚至在前10个epoch内持续上升。先查学习率是否过大再查标签文件是否有误。在这里要特别强调一个容易忽略的问题data.yaml中的nc类别数是否和标注文件的类别id一致。如果你标注文件中出现了类别id3但nc: 3只接受0、1、2训练就会报错。如果没报错但loss不降也要检查是否有一些标签文件是空文件没有标注任何目标。空标签在YOLOv5中会作为纯背景样本参与训练如果这种样本过多会严重干扰模型的学习甚至在训练日志中产生大量“WARNING: No labels found”的提示。症状三训练和验证的loss一直在下降但mAP却在一个低水平横盘。这时可以拉出val_batch0_pred.jpg看看模型在验证集第一张图上的预测结果。如果预测框位置大致正确但类别全错很可能是names列表顺序与标注类别顺序不一致。如果预测框完全乱飘可能是输入图片尺寸与标注归一化坐标不匹配比如原图是600×800标注时却按640×640的归一化坐标保存。在跑完100轮训练后我还额外做了一个小实验把训练过程中每10轮的best.pt分别拿来做推理对比。结果发现第60轮保存的权重在验证集上的mAP已经接近最终结果的95%而后40轮主要是在精调边界框的回归精度。这说明对于这种中等复杂度、数据量适中的检测任务训练到60到80轮时已经基本收敛没必要盲目加大epoch数。这个发现也帮助我在后续做其他数据集时能更合理地设置训练轮数节省了不少算力。7. 推理实测与扩展思考这份数据集还能怎么用7.1 用导出后的权重做实时推理不只是看结果还要看推理速度训练完成后用best.pt做推理是最直观的验收方式。这里推荐先用detect.py批量跑验证集图片观察检测效果python detect.py \ --weights runs/train/fruits_exp/weights/best.pt \ --source ../fruits-dataset/images/val \ --conf-thres 0.25 \ --iou-thres 0.45 \ --save-txt \ --save-conf这个命令会逐张处理验证集图片把检测结果保存到runs/detect/exp/目录同时用--save-txt导出检测框坐标、用--save-conf导出置信度。你可以把导出的txt文件和标注标签做对比定量计算模型在验证集上的实际表现而不仅仅依赖训练过程中统计的mAP。如果打算把模型部署到实际应用比如做一个水果分拣小工具可以使用export.py把模型导出为TorchScript或ONNX格式这样推理速度更快、部署方式更灵活python export.py \ --weights runs/train/fruits_exp/weights/best.pt \ --include onnx \ --img 640导出ONNX后可以用ONNX Runtime做CPU推理。在普通i5处理器上输入一张640×640的图片单次推理耗时大约30到50毫秒基本可以做到25FPS以上的实时检测。如果对速度有更高要求可以换成YOLOv5n或YOLOv5s的轻量版本或者使用TensorRT在NVIDIA GPU上做加速。7.2 从这份数据集延伸出去的三个方向一份数据集的价值不在于本身“能跑通”而在于它能不能支撑你做更多实验和项目扩展。基于这三类别水果数据集至少有三个有价值的延伸方向第一个方向是类别扩展。把苹果、橘子、梨的数据集扩展为包含香蕉、葡萄、西瓜等多类别水果数据集。方法也很简单沿用相同的标注流程和目录结构新增类别在data.yaml的names中追加名称然后把新类别的图片和标签放入对应目录。需要特别注意的是类别id的顺序一旦确立新类别只能追加在末尾不能在中间插入。否则所有已有标签的类别id全部要重新映射。第二个方向是数据增强的极端验证。可以故意用极强/极弱的数据增强配置分别训练两个模型对比它们的验证mAP和训练loss曲线直观感受数据增强对模型泛化能力的影响。这个实验对理解YOLOv5的hyp.scratch-low.yaml中每个增强参数的含义非常有帮助。第三个方向是模型结构对比。在同一数据集上分别用YOLOv5n、YOLOv5s、YOLOv5m、YOLOv5l训练比较它们在mAP、推理速度、模型大小三个维度上的差异。这类实验做完之后你对“什么时候该用轻量模型、什么时候该上大模型”会有一个非常清晰直观的认识。以这份水果数据集为例YOLOv5s已经能达到0.93的mAP0.5YOLOv5m可能只会提升到0.94到0.95但推理速度和显存占用都会成倍增加。大多数实际应用场景中YOLOv5s这样的“小模型”才是性价比更高的选择。7.3 数据集的持续迭代与版本管理一份能长期使用的数据资产最后聊一个容易被忽视但非常重要的点数据集不是一次性产物而是需要持续迭代的数据资产。训练第一版模型后你一定会发现模型在某个场景下表现不佳比如强逆光下苹果漏检、绿色橘子被误判成梨。这时候正确的做法不是去调参数而是去补充这些失败场景的图片重新标注更新数据集然后做增量训练。为了方便迭代每个数据集版本都应该有清晰的版本号和管理记录。我习惯在数据集根目录下放一个CHANGELOG.md记录每个版本的时间、变更内容和原因。对标注文件做修改时用脚本批量操作并保留修改日志不要直接在原文件上手动改。因为一旦出现标注错误没有日志的话根本找不到问题源头。目录结构上也可以预留一个archive/目录存放旧版本数据集避免直接覆盖导致历史数据丢失。如果你有足够版本管理意识可以用Git LFS来维护数据集这样训练实验的复现和回溯会方便很多。对于个人项目一份数据集的迭代记录能让你在几个月后重新打开项目时快速回忆起当初为什么这样做、改了什么、效果如何省下的时间远超记录所花的时间。做目标检测数据集这件事技术和工具都不是最大的门槛真正考验人的是你对细节的态度。一个框标注得是否贴合、训练验证集划分是否避免数据泄漏、数据体检脚本是否跑过、data.yaml的类别顺序是否一致——这些细节单独看都微不足道但它们叠加起来直接决定了模型最终的精度上限。希望这份三类别水果数据集的完整拆解能帮你少走一些我当年走过的弯路。本文还有配套的精品资源点击获取