朋友前两天问我,有没有一套现成的猫狗检测数据集,能把YOLO从数据准备到训练部署这条路完整跑通。他说不想一上来就看Coco那种上百GB的大项目,只想要一个干净、能快速验证的小数据集。我当时就把手头整理的一套4300张YOLO宠物识别数据集推给了他,顺便写了个小小的使用说明。折腾下来发现,这套数据对入门目标检测、做课程设计、甚至跑通一条自己的检测落地流程,都挺合适。这篇就当边整理边记录,聊清楚这套数据长什么样、YOLO格式怎么处理、训练时该盯哪些指标、以及我踩过的几个坑。
如果你是第一次接触目标检测,或者看了一堆YOLO教程但没找到合适的数据练手,这套4300张的宠物识别数据集可以让你把训练、评估、导出整个链路走通。训练端对显卡要求也不高,我用一张6GB显存的卡跑yolov8n,从零开始到出模型大概40多分钟,换成yolov8s大约一个多小时,完全在可接受范围内。
1. 数据集的整体定位与组成
1.1 为什么是4300张,而不是4万张?
很多刚接触目标检测的人有个误区,觉得数据集越大模型越强,上来就想找几十万张图的那类大规模数据集。但大数据的量级对新手并不友好:下载耗时、存储占空间、标注格式复杂、训练周期长,最要命的是出问题时很难定位,到底是数据问题、训练参数问题,还是模型结构问题,排查起来一头雾水。
这套4300张的猫狗检测数据集,定位就是“终端验证”。4300张这个量级,对YOLO系模型来说已经足够训练出一个效果不错的检测器:它不追求刷SOTA,而是让你在几小时内跑完一整套训练流程,看清楚每一环的作用。我做样本筛选时重点保证了类别的平衡和场景的多样性,而不是一味堆数量。
从实际测试来看,4300张足够yolov8n收敛到mAP@0.5 0.97以上的水平。如果要验证一个小改动,比如把backbone换成轻量化版本、调一下数据增强策略,你很快就能看到对比结果。但如果是4万张,一次训练可能要跑大半天,迭代效率反而不如这个小而干净的数据集。
1.2 数据分布与类别构成
这套数据集的构成大致如下:总共4300张图片,标注框总数在7000个左右,平均每张图大约1.6个目标。类别共两类,猫(cat)和狗(dog),对应的YOLO类别索引是cat=0、dog=1。图片涵盖室内、室外、街道、公园、宠物医院等不同场景,光照条件比较杂,姿态也够多样化,有正面、侧面、俯拍,甚至还有小部分遮挡样本。
划分上我按6:2:2拆成了训练集、验证集和测试集:训练集约2580张,验证集约860张,测试集约860张。这样的比例下,每类目标在三个集合里的数量分布也比较一致,不会出现训练集里猫很多、验证集里却全是狗这种尴尬情况。
图片的分辨率跨度比较大,小的有400x400左右,大的可以到1900x1200。YOLO训练时会统一缩放,所以分辨率差异问题不大,但这也提醒一点:如果你的训练集里目标框大小差异很大,预处理阶段就要注意,小目标框占比过高的图片在resize之后会变得更难检。这套数据里猫因为体型相对小,经常在画面里占的比例不大,训练时如果小目标框没处理好,猫的AP会明显低于狗。
1.3 这套数据集从哪来,筛选做了什么
构成这套数据集的主要来源是公开可用的图像素材、部分公开竞赛数据子集,以及我自己收集后重新标注的图片。汇总之后,我做了几轮筛选:
第一轮去重,用感知哈希把内容高度相似、甚至同一张图的不同压缩版本都过滤掉了。目标检测数据集最怕重复,重复样本会让模型在测试时虚高,明明没有学到泛化能力,评估结果却很好看。
第二轮去模糊和低质量,人眼看着都糊的图,标注出来也没意义。这里我人工扫了两遍,删掉了过暗、过曝、严重运动模糊、水印遮挡严重的图。
第三轮是修正标注,原始素材里有些标注工具的产物并不规范:有的是Pascal VOC格式,有的是坐标越界的框,有的框把猫头截了一半还硬标成完整目标。我统一转成YOLO格式并做了坐标裁剪,坐标值控制在0到1之间,防止训练时bbox_loss出现异常。
2. YOLO标注格式拆解:从图片到txt的过程
2.1 YOLO txt标注的底层逻辑
YOLO的标注格式非常简单直接:每张图片对应一个同名的txt文件,放在labels目录下。txt里的每一行代表一个目标框,格式是五个值:
class_id center_x center_y width height注意,这里的center_x、center_y、width、height都是归一化坐标,取值在0到1之间,是用像素坐标除以图片宽高得到的。举个例子,一张宽800、高600的图片里有一只猫,目标框左上角像素坐标是(200, 150),宽300,高250,那么归一化之后:
- center_x = (200 + 300/2) / 800 = 0.4375
- center_y = (150 + 250/2) / 600 = 0.4583
- width = 300 / 800 = 0.375
- height = 250 / 600 = 0.4167
这行标注就是0 0.4375 0.4583 0.375 0.4167。
理解了这条计算逻辑,你就知道为什么很多人说“YOLO格式很简单”:它把目标框的位置和大小完全压缩成了四个相对值,跟图片分辨率无关。同一张图不管你怎么resize,归一化坐标都不用改,这就是YOLO训练时能直接吃不同尺寸图片的原因。
但简单不代表不会出错,最容易翻车的点是:很多人手工修改txt时把center_x和center_y写成了左上角坐标,或者把width和height写成了右下角坐标。这类错误不跑一个可视化脚本很难发现,一旦存在,模型训练时的回归目标就是错的,损失下降也不正常。
2.2 类别编号与data.yaml的对应关系
YOLO训练时用data.yaml文件来声明数据路径和类别名称,你需要保证names列表的顺序跟txt里的class_id完全一致。我这套数据集的data.yaml长这样:
train: /path/to/catdog_dataset/images/train val: /path/to/catdog_dataset/images/val nc: 2 names: ['cat', 'dog']cat在前,所以在所有标注文件里,猫的class_id就是0,狗的class_id就是1。如果你把names写成['dog', 'cat'],那模型学到的0号类就是狗,1号类就是猫,到最后推出来结果全反了,而且光看训练loss你可能根本发现不了,因为loss照样会降。
这类错我见过太多次,尤其在网上找数据集时,别人给出的names顺序和txt里的id不一定对得上。稳妥做法是拿到数据集后先随机抽几个txt文件看第一列数字,再对应图片里目标的真实类别,确认无误再开训。
2.3 标注文件的完整性检查
下载完数据集后,第一步永远先写脚本检查标注文件,而不是直接开始训练。我常用的检查逻辑就三点:每个txt文件是否存在、是否为空、坐标值是否在合法范围。
import os from pathlib import Path img_dir = Path('catdog_dataset/images/train') label_dir = Path('catdog_dataset/labels/train') missing = [] empty = [] invald = [] for img_path in img_dir.glob('*.jpg'): label_path = label_dir / (img_path.stem + '.txt') if not label_path.exists(): missing.append(str(img_path)) continue lines = label_path.read_text().strip().splitlines() if not lines: empty.append(str(img_path)) continue for line in lines: parts = line.split() if len(parts) != 5: invald.append(str(img_path)) break cls, cx, cy, w, h = int(parts[0]), float(parts[1]), float(parts[2]), float(parts[3]), float(parts[4]) if not (0 <= cx <= 1 and 0 <= cy <= 1 and 0 <= w <= 1 and 0 <= h <= 1): invald.append(str(img_path)) break print('missing:', len(missing), 'empty:', len(empty), 'invalid:', len(invald))这个脚本跑完,如果有missing,大概率是标签文件放错目录或者命名不一致;如果有empty,说明这张图标注时被跳过或者导出失败;如果有invalid,就是转换过程出了问题,坐标越界或格式错乱。这些问题不解决就开训,轻则类别学错,重则训练直接崩。
还有一个常见情况:某些图片有多个目标,但txt里只有一行。这种多半不是漏标,而是这张图本来就只有一只猫。也有少数是标注人员漏看了角落里的狗头,最终后果就是模型对“画面边缘目标”的召回率偏低。所以我建议,如果时间和精力允许,最好写个可视化脚本把标注框画出来,人眼扫一遍比什么都靠谱。
3. 训练前的准备:目录结构划分与校验
3.1 标准目录结构与存放建议
YOLO训练不关心你到底把数据放在哪个盘哪个目录,但要求目录结构清晰、稳定。我用的是最终版本结构:
catdog_dataset/ ├── images/ │ ├── train/ │ ├── val/ │ └── test/ ├── labels/ │ ├── train/ │ ├── val/ │ └── test/ └── data.yaml这里有个细节:images和labels是平级目录,不是“train里再放images和labels”这种结构。原因很简单,data.yaml里train和val只需要指向images下的子目录,而ultralytics框架会自动在同一级找labels,比如你指向images/train,它就去labels/train找同名txt。如果你把labels塞进images目录里面,就得额外改配置,麻烦还容易错。
测试集这次没在data.yaml里声明,因为我训练时val已经承担了验证和挑选最优权重的任务。test目录留到最后导出模型、做最终效果评估时用,这样能更真实地反映模型在没见过图片上的表现。
3.2 训练集与验证集的划分策略
我拿到标注好的图片后,不是简单按文件名排序后前60%分给train、中间20%分给val、最后20%分给test,而是先按图片id做乱序洗牌,再按比例切分。这样的好处是,相近时间点采集的同场景图片不会全部挤在同一集合里。
更讲究一点的做法是分层抽样,让cat和dog的框数量在train、val、test里都保持大致相同的比例。写个小脚本统计合并信息后划分:
import random from pathlib import Path random.seed(42) img_paths = list(Path('catdog_dataset/images').glob('*.jpg')) random.shuffle(img_paths) total = len(img_paths) train_split = int(total * 0.6) val_split = int(total * 0.8) train_paths = img_paths[:train_split] val_paths = img_paths[train_split:val_split] test_paths = img_paths[val_split:]如果你打算从零开始标注自己的数据,建议一开始就按这个流程走,不要前期随便堆一起,到最后划分的时候才发现重复图片横跨train和val,白折腾一场。
3.3 可视化检查:把标注画出来再决定要不要修
这一步是我个人体会最深的习惯。只看txt数字完全感觉不到问题,你需要把每个标注框画回图片上,看框是不是紧贴着猫和狗的身体。用OpenCV写个小工具:
import cv2 img = cv2.imread('catdog_dataset/images/train/000001.jpg') h, w = img.shape[:2] with open('catdog_dataset/labels/train/000001.txt') as f: for line in f: cls, cx, cy, bw, bh = map(float, line.split()) x1 = int((cx - bw / 2) * w) y1 = int((cy - bh / 2) * h) x2 = int((cx + bw / 2) * w) y2 = int((cy + bh / 2) * h) color = (0, 255, 0) if int(cls) == 0 else (0, 0, 255) cv2.rectangle(img, (x1, y1), (x2, y2), color, 2) cv2.imshow('check', img) cv2.waitKey(0)这类检查我一般随机抽50张来看,如果发现大量框跑偏,就说明标注质量有问题,先修数据再谈训练。如果是个别框偏了,修改txt后重新检查即可。
4. YOLO训练实操:从配置到启动
4.1 环境配置与预训练权重下载
训练环境用ultralytics这个库就够了,安装很省事:
pip install ultralytics如果你还想顺手看训练指标曲线,可以加装tensorboard,不过我在实际使用中用得不多,ultralytics自带的results可视化已经够用。
预训练权重是训练能不能快速收敛的关键因素。从零训练一个YOLO模型不是不行,但需要更多的epochs和数据量,对电脑性能要求也高。使用官方预训练权重,相当于模型已经学过了通用特征,你再拿猫狗数据微调一下,很快就能收敛。
用yolov8n.pt做一个示例:
yolo detect train data=catdog_dataset/data.yaml \ model=yolov8n.pt \ epochs=100 \ imgsz=640 \ batch=16 \ device=0如果是第一次运行并且网络环境正常,它会自动把预训练权重下载到本地。如果下载卡住,也可以手动从官方描述地址下载对应pt文件,放到脚本目录下再执行。这里要提醒一下:预训练权重的名字要和model参数保持一致,你写的是yolov8n.pt,就不该放yolov8s.pt进去。
4.2 编写data.yaml的几个经典错误
data.yaml写错是最常见的训练失败原因,我把这些年在不同机器上遇到的问题集中列一下:
第一,路径写成了相对路径,但运行训练命令时工作目录不对。比如你把data.yaml放在catdog_dataset/下,却从项目根目录执行命令,相对路径就失效了。我的习惯是直接写绝对路径,或者用Path(__file__).parent生成路径再拼进去,一劳永逸。
第二,train和val指向了同一批图片。有些人想省事,直接把全部图片放train,val也指向train。这样训练时会看验证集loss做早停,但验证集信息已经泄露给了训练过程,指标虚高,不具备参考意义。
第三,nc和names对不上。比如声明了nc: 2,names里却写了三个名字。框架不一定会报错,但训练和预测阶段编号会乱。这个错误隐蔽性高,首次训练前务必打印出来看一眼。
4.3 训练命令与超参数选择
YOLO训练时最重要的几个超参数是imgsz、batch、epochs,以及device。不同显卡能力下,这些参数的选择差异很大。先看一下模型体量对比:
| 模型 | 参数量 | 输入尺寸 | 单卡训练耗时参考 | 精度基准 |
|---|---|---|---|---|
| yolov8n | 3.2M | 640 | 40-50分钟 | 较低 |
| yolov8s | 11.2M | 640 | 60-90分钟 | 中等 |
| yolov8m | 25.9M | 640 | 2-3小时 | 较高 |
我刚开始用6GB显存的卡跑这套数据集时,batch设成32直接OOM,后来降到16才顺利跑完。batch太小又会导致梯度震荡,训练loss的曲线看起来一上一下,很不稳定。对这套数据来说,batch=16配合默认学习率0.01,效果已经很稳了。
imgsz的选择也要讲究,虽然有auto模式,但我建议固定640,这也是YOLO系列的经典输入尺寸。你花时间研究模型结构前,先记住:更大的imgsz会带来更高的精度,但显存和推理时间都会上升。4300张的数据集,640和960的区别在小目标检测上会有一点体现,但总体差别不算大,入门阶段用640就好。
epochs方面,100是一个比较安全的中间值。我从实践来看,yolov8n从预训练权重开始,大约40个epoch时mAP就开始稳定,70个epoch之后基本收敛。100个epoch能让你看到过拟合阶段的曲线走势,对理解训练过程有好处。如果你追求效率,50个epoch也够用。
4.4 训练过程中如何判断是否正常
训练启动后,终端会实时刷新loss和指标,很多新手盯着这些数字心里发慌,不知道什么算正常。我简单描述一下正常的过程:
前10个epoch,box_loss和cls_loss会从1到2的高位快速下降,P(精确率)和R(召回率)可能上下波动,mAP50也还没起来,这是模型在快速学习目标位置和类别特征的阶段。20到50个epoch,loss下降变缓,mAP稳步上涨,这时候训练已经进入稳定期。过了70个epoch后,如果训练集loss继续下降,但验证集指标不再提升,甚至开始掉头,那就是过拟合的苗头。
训练中遇到loss变成NaN,绝大部分情况是梯度爆炸或者学习率设置不当。YOLO系列的默认学习率对大多数数据集是安全的,如果你自己调了学习率或者改了优化器,遇到NaN第一件事是把学习率降回去。另外,batch太小但学习率不变,也会偶尔出现数值不稳定,可以适当调大batch或调低学习率。
还有一类问题是输出里没报错,但loss一直在某个高数值附近震荡。这种情况我会先去查数据:标签是否有越界、类别id是否有错、验证集是否太小导致波动过大。数据没问题再回头调超参数。
5. 评估指标与部署实战
5.1 mAP不可能不看的三处细节
训练结束后的验证结果有 precision、recall、mAP50、mAP50-95几项核心指标。简单解释下:mAP50是IoU阈值为0.5时的平均精度均值,反应的是“框大致框对了就算对”的成绩;mAP50-95则把IoU阈值从0.5到0.95每隔0.05取一次,求平均,要求严格得多。
在这套猫狗数据集上,yolov8n大概能跑到mAP50 0.97左右,mAP50-95则在0.80上下。如果你看到mAP50很高但mAP50-95偏低,说明模型的定位精度一般,框的位置不够准。这种情况通常可以调高imgsz,或者换yolov8s以上模型来改善。
评估阶段还要看每个类别的AP,不是只看均值。比如cat和dog的AP分别是多少,如果dog明显高于cat,那多半是数据不平衡或者猫的样本难度偏高。这时候不要去动模型,先去补猫的难例数据,效果来得更快。
5.2 混淆矩阵的正确看法
YOLO训练结束后会生成混淆矩阵图,横轴是预测类别,纵轴是真实类别,每个单元格显示的是该组合所占比例。对于二分类的猫狗检测,你主要看三块:真实猫被预测成猫的比例、真实狗被预测成狗的比例、以及真实目标被漏检的比例(背景那一类)。
很多人第一次看混淆矩阵时有个疑惑:为什么所有列加起来不等于100%?这是因为YOLO的混淆矩阵计算方式里包含了背景或者说“漏检”这一类,并且不同类别数量不平衡时,归一化的基准也不一样。简单说,你不用纠结“总合不唯一”,只要关注主对角线上的值够不够高,以及某个类别被大比例错分成另一个类别的问题。比如如果猫被识别成狗的比例有15%,那就说明这两个类别在特征空间里区分度不够,要么加数据,要么改模型分辨率。
5.3 导出ONNX并在CPU上跑识别
训练完best.pt之后,你还需要把它导出成通用的ONNX格式,这样部署时就不依赖PyTorch环境,用OpenCV也能跑:
yolo export model=runs/detect/train/weights/best.pt format=onnx执行完会得到一个best.onnx文件。用OpenCV加载并执行推理的方式很直接:
import cv2 import numpy as np net = cv2.dnn.readNetFromONNX('best.onnx') img = cv2.imread('test.jpg') blob = cv2.dnn.blobFromImage(img, 1/255.0, (640, 640), swapRB=True, crop=False) net.setInput(blob) outputs = net.forward()这里outputs的形状是[1, 84, 8400],其中84的构成是4个坐标信息加上2个类别分数(80个类别时才是80,这里因为是2类所以是2),8400是640x640输入下所有检测点数量。拿到输出后做解析、过滤低置信度的框、再跑NMS,就能得到最终检测框。这套流程跑在CPU上,单张图大约50-80ms,虽然不快,但做验证够了。
5.4 常见问题与排查速查表
我把这套数据集训练部署过程中最容易碰到的问题整理成了表格,遇到问题先对着查,比自己瞎试强:
| 现象 | 可能原因 | 解决建议 |
|---|---|---|
| 训练loss变NaN | 学习率过高或batch过小 | 降低learning rate,或增大batch,必要时换float32 |
| mAP50很高,mAP50-95低 | 判定框定位不准、小目标漏检 | 增大imgsz,或换用yolov8s以上模型 |
| cat类别AP显著低于dog | 猫样本量不足,或目标框普遍偏小 | 补充猫的难例数据,检查小目标样本占比 |
| 测试集上框偏了半个身位 | 标注框本身就不够贴边 | 可视化检查标注,修正框边界 |
| ONNX推理速度很慢 | 输入尺寸太大,或CPU算力弱 | 降低imgsz,或使用int8量化 |
| 检测结果把猫认成狗 | 类别特征学习不充分,严重样本不均衡 | 平衡数据,增加混淆样本,必要时提高分辨率 |
这些坑我在不同项目里反复遇到过,最快的排错方式永远是从数据开始查,而不是先怀疑模型结构。
6. 数据集还能怎么玩:扩展思路与个人体会
6.1 把猫狗二分类扩展成品种多分类
这套数据集是二分类结构,但你已经有了完整的标注格式和训练流程,扩展到更多类别其实顺理成章。比如想把“英短、橘猫、边牧、金毛”这些品种都识别出来,理论上有两种路线:一是直接给每张图重新标注品种标签,二是先用现成模型做伪标签,人工复核后再加入训练。
伪标签这条路线我在一个动物识别的项目里验证过,确实能大幅节省标注时间。先用当前的最佳模型对一批新图做预测,把高置信度的检测结果直接转成标注候选,再人工过滤掉那些置信度低或明显标错的样例。不过对新手我建议还是先人工标注,毕竟你对YOLO格式和训练流程还不熟,直接上伪标签容易引入噪声,回头排查问题又多一层变数。
6.2 把检测模型迁移到邻近任务
猫狗检测练出来的模型初始化权重,可以迁移到其他检测任务上,比如车辆检测、玩具检测、商品计数。做法很简单:训练时用best.pt作为预训练权重,data.yaml换成新数据集的路径和类别数。因为模型已经学会了通用的边缘、纹理、目标边界等特征,迁移后收敛速度往往比从yolov8n.pt开始还快。
我在一次实验里用这套宠物数据训练出的权重,去初始化一个“开关闭合检测”模型,效果让我有点意外,只训练了30个epoch就达到了原来80个epoch的精度。这说明,在相似分布的数据之间迁移,确实能省下不少训练时间。
6.3 踩过坑之后的几点个人体会
展开这套数据集的完整使用流程之后,最后说几句题外话。
第一句:不要迷信测试集数字。训完看mAP50到0.97,你觉得模型很厉害,但把它放到真实场景里,灯光暗一点、猫跑起来产生运动模糊、狗被挡了一半,效果可能立刻打七折。所以我习惯在训练完再加一个“真实环境验证”,拿手机拍几段视频在电脑上跑一跑,看看误检和漏检都发生在什么画面上。这个方法虽然土,但比任何指标都实在。
第二句:小数据集的过拟合无处不在。4300张看起来不少,但目标检测模型尤其吃数据,尤其当场景相对单一时,模型很容易在验证集上表现好、泛化能力却不尽如人意。解决思路不是马上堆数据,而是把数据增强用起来。YOLO系列的增强策略已经很完善,你只要别手动关掉那些增强,过拟合的概率就会下降很多。
第三句:标注质量是数据集的灵魂。一张标错框的图,会让模型在一个错误的方向上修正自己。如果你打算用这套数据集的实际流程去整理自己的数据,请一定在训练前做可视化检查,这个步骤永远不要省。我自己就因为偷懒跳过可视化检查,结果训出一个频繁把背景误检成猫的模型,排查了两天才发现是标注图里有个目标框放在了一只玩具猫上,而那只玩具猫和真猫长得确实太像了。
说到这,我最后再分享一个小技巧:在做模型迭代时,把每次训练生成的confusion_matrix.png截图存下来,按日期归档。时间长了你会发现,数据集的演进历史就是这些混淆矩阵不断变化的历史,哪个类容易混、哪个类还在漏,一眼就能看出来。那套4300张的宠物识别数据集,能带你把整个链路走通,就已经完成它的使命了。剩下的事,是你拿着这个基础,往自己的场景里不断补数据、调阈值、做评估循环。目标检测这条路十万八千里,搞懂数据、跑通训练、会看指标,才算真正迈出了第一步。