
简介一套面向YOLOv3猫狗检测任务的精选数据集源头取自COCO官方验证集只保留猫和狗两个类别专用于目标检测模型的训练与效果验证适合深度学习和计算机视觉初学者、研究者及高校学生开展实战项目。压缩包内共包含两千五百六十八张jpg原图、两千五百六十八个xml标签和两千五百六十九个txt标签前者记录边界框详细坐标与类别名称后者为简洁的YOLO格式标注可直接供主流框架读取相互对照能帮助理解不同标注规范差异。整个资源包约三百零六兆字节文件命名沿用官方图像编号便于按索引定位样本。目前已有超过一千七百人次在CSDN浏览学习。借助该数据集可完整体验图像预处理、模型训练、超参数调整到平均精度评估等流程直观感知多尺度检测、深层残差网络和先验框机制对猫狗识别效果的影响是入门目标检测和开展课程设计的有力支撑。1. 这个 coco-val2014-cat_dog-2568.zip 不是开箱即用而是一个半成品数据集我拿到“coco-val2014-cat_dog-2568.zip”的第一反应是解压出来就能跑 YOLO 训练结果第一次就翻车了。这个压缩包名字把四件事拼在一起coco-val2014 表示源数据来自 COCO 2014 验证集cat_dog 表示只要猫狗两类2568 表示筛选后的图像或标注数zip 只是容器。它的价值在于省掉你自己从 instances_val2014.json 里按类别过滤的工作让做 YOLO 猫狗检测的人拿到一个体积可控的子集。但它不是一解压就能 train 的成品很多人栽在标签格式、类别 id 和数据集划分上。它更适合用在 YOLOv5/v8 的快速验证、预训练微调和训练流程测试而不是直接当生产级数据集。2. 先验货再解压看懂命名、检查 zip 结构、处理伪加密2.1 coco-val2014-cat_dog-2568 四个字段分别代表什么coco 指的是 COCO 数据集val2014 是 2014 年发布的验证集版本。注意 COCO 还有一个 val2017两者图片编号规则一样但标注细节和类别分布不同混合使用时很容易出错。cat_dog 表示这个子集只保留猫和狗两个类。2568 这个数字更要小心它可能是有猫狗标注的“图像数量”也可能是“标注框数量”。一张图可以有多个猫框或狗框所以先确认这个数字的含义会直接影响你后续对图片数和标签数的判断。我之前处理过一个类似子集包文件名写着 2568解压后 images 里有 2568 张图但 labels 里有 2761 个 txt就是因为一张图对应多个目标而打包者把“目标数”写进了文件名。这种不一致不是数据损坏只要你理解了命名口径后续训练就不会惊慌。这个子集的另一个隐含问题是它来自 val2014而一般 YOLO 训练习惯是 train 和 val 分开。如果你拿整个 val2014 子集去做训练那么“训练集”和“验证集”在时间上可能都是同一批拍摄片段存在帧级重复后面我会专门讲这个坑。2.2 Linux/Windows 上验包文件清单、完整性、伪加密先别急着解压。在 Linux 上用 unzip 看压缩包内容unzip -l coco-val2014-cat_dog-2568.zip | head -40 unzip -t coco-val2014-cat_dog-2568.zip第一条命令列出前 40 个文件第二条做完整测试。如果输出末尾有 “No errors detected in compressed data”说明压缩包本身没坏。Windows 上可以打开 PowerShell 用tar -tf或者直接用 7-Zip 的“测试”按钮。不要用系统自带右键解压它不会告诉你哪一帧损坏。接下来是很多人遇到过的“伪加密”。现象很诡异压缩包没设密码但双击要求输入密码或者用 zipfile 读取时z.infolist()里每一项的flag_bits 0x1都是 1。伪加密的原理是ZIP 格式在 local file header 和 central directory 中都记录了一个“加密标志位”。某些压缩软件在生成或分享文件时错误地把这个标志位置为了 1但压缩数据本身没有真正加密。所以它并不是真的设了密码。常见做法是用 7-Zip 打开如果能在不输密码的情况下看到文件列表就基本确定是伪加密这时直接把文件从压缩包里拖出来或者重新压缩成不带密码的新 zip是最省心的后悔药。如果你想自动化检查可以用 Pythonimport zipfile with zipfile.ZipFile(coco-val2014-cat_dog-2568.zip) as z: for info in z.infolist(): print(info.filename, bool(info.flag_bits 0x1))看到True不代表一定有密码但如果你确认分享者没有设密码就要走伪加密处理流程。我不建议手工去改 zip 二进制标志位容易把整个包改坏把文件拖出来重新打包虽然多花几分钟但不会翻车。2.3 判断 zip 内是 COCO 原生格式还是已经转好的 YOLO 格式验完包之后用zipinfo -1或unzip -l看目录结构这个决定你后面走哪条路。常见的结构有两种COCO 原生格式images/val2014/*.jpg加annotations/instances_val2014.json。这种不能直接训练必须把 json 转成 YOLO 的 txt 格式。YOLO 半成品格式images/*.jpg加labels/*.txt有的还自带train.txt和val.txt。这种可以直接校验并写入 data.yaml。最坑的是“混搭”images 目录下是原图labels 目录下却只有一个空文件夹真正的标注还在 json 里。所以不要只看有没有 labels 目录要抽查一个 txt 的内容find labels -name *.txt | head -5 | xargs -I {} sh -c echo --- {}; head -3 {}YOLO 格式的每一行应当是class_id x_center y_center width height四个坐标值都是 0 到 1 之间的小数。如果看到两三百的大数字说明这是原生的 COCO bbox 坐标不能直接训练。另外class_id必须是连续整数如果看到17或18说明打包者还没做类别映射这在 COCO 子集里几乎一定会踩到。3. 把 COCO 格式的 cat/dog 转成 YOLO 格式过滤、映射、划分一次讲清3.1 为什么必须把 COCO 的 category_id 映射到 0/1在 COCO val2014 的标注里猫的 category_id 是 17狗是 18中间还有很多不相干的类别比如斑马是 24、长颈鹿是 25。COCO 的类别编号是稀疏的并不是从 0 开始连续排列。而 YOLO 训练时nc2意味着模型把类别预测看成两个互斥的输出节点class_id 必须经过映射变成 0 和 1。如果你直接把“17”写进 YOLO 标签会出现两类问题。第一模型输出的类别数变成 19 个训练时还会警告 “class 17 out of range” 或直接忽略这些标签。第二就算能跑验证时 mAP 会被错误类别数拉低最终推理出来的猫狗标签也是错位的。常见映射是{17: 0, 18: 1}也就是 cat 对应 0dog 对应 1。这个顺序要和后续 data.yaml 里的names: [cat, dog]完全一致不能互换。3.2 用 Python 从 instances_val2014.json 生成 YOLO 标签假设压缩包解压后是 COCO 原生格式你需要自己写转换脚本。下面这段脚本是我常用的做法按图片维度遍历标注过滤猫狗生成 YOLO txt并把对应图片复制出来。import json import shutil from pathlib import Path ann_path Path(annotations/instances_val2014.json) img_src Path(images/val2014) img_dst Path(cat_dog/images) label_dst Path(cat_dog/labels) CAT_DOG_MAP {17: 0, 18: 1} with open(ann_path) as f: coco json.load(f) img_id_to_file {img[id]: img[file_name] for img in coco[images]} img_id_to_size {img[id]: (img[width], img[height]) for img in coco[images]} ann_by_img {} for ann in coco[annotations]: cid ann[category_id] if cid not in CAT_DOG_MAP: continue ann_by_img.setdefault(ann[image_id], []).append(ann) selected sorted(ann_by_img.keys()) print(selected images:, len(selected)) img_dst.mkdir(parentsTrue, exist_okTrue) label_dst.mkdir(parentsTrue, exist_okTrue) for img_id in selected: fname img_id_to_file[img_id] w, h img_id_to_size[img_id] lines [] for ann in ann_by_img[img_id]: x, y, box_w, box_h ann[bbox] x_center (x box_w / 2) / w y_center (y box_h / 2) / h norm_w box_w / w norm_h box_h / h lines.append( f{CAT_DOG_MAP[ann[category_id]]} {x_center:.6f} {y_center:.6f} {norm_w:.6f} {norm_h:.6f} ) (label_dst / fname.replace(.jpg, .txt)).write_text(\n.join(lines)) shutil.copy2(img_src / fname, img_dst / fname)这段代码有四个关键点。第一COCO 的 bbox 是[x, y, width, height]左上角像素坐标不是中心点所以要先加一半宽高再除以原图宽高才是 YOLO 需要的归一化中心点。第二分母用的是原图宽高不是缩放后的尺寸如果把准备输入模型的 640 当作分母就会得到一批错误的超小框。第三一张图同时有猫狗时会追加多行标注不会互相覆盖。第四脚本最后只复制有猫狗标注的图片避免空图干扰训练。上面脚本没有过滤无标签图片但把“无标签图片”和“无标注图片”区分开很重要。如果某张 jpg 在 COCO 里存在但没有任何猫狗框这个脚本不会复制它这正是我们想要的行为。反过来如果 labels 里有 txtimages 里却找不到对应 jpg训练时 YOLO 会报“No labels found in ...”这时候需要做一次交集校验。3.3 划分数据集和生成 data.yaml因为原始数据来自 val2014如果你只想做快速流程测试可以按 9:1 随机划分。这里我给一个简单的划分脚本import random from pathlib import Path random.seed(20240605) selected_ids selected # 来自上面脚本的图片 id 列表 random.shuffle(selected_ids) split int(len(selected_ids) * 0.9) train_ids selected_ids[:split] val_ids selected_ids[split:] for name, id_list in [(train, train_ids), (val, val_ids)]: with open(fcat_dog/{name}.txt, w) as f: for img_id in id_list: f.write(fcat_dog/images/{img_id_to_file[img_id]}\n)注意这里我用random.seed固定随机种子确保两次生成的文件列表一致。如果你复制了数据集到新机器最好把生成的train.txt和val.txt一起打包而不是在每台机器上重新划分。划分完生成cat_dog.yamlpath: ./cat_dog train: train.txt val: val.txt nc: 2 names: [cat, dog]path指向根目录train和val是相对路径的 txt 文件每行一个图片路径。这里最容易被忽略的是YOLOv5 读取 data.yaml 时会先用path拼上train再读取 txt 里的每一行作为图片路径。如果你的path写的是绝对路径txt 里也写绝对路径会重复拼接导致路径错误。常见做法是path写相对路径txt 里也写相对路径保持一致。在使用这个配置训练前我用一个非常小的模型跑 1 个 epoch 来检查路径和标签是否读取正常python train.py --data cat_dog/cat_dog.yaml --weights --img 640 --batch 8 --epochs 1如果标签有问题日志里会直接报Image sizes为 0 或Found 2568 images的数量不对。这个阶段发现问题比训练 50 个 epoch 后才发现要划算得多。4. 跑通 YOLO 训练预训练权重、超参数和第一次验证4.1 选哪个 YOLO 版本和怎么拿预训练权重对于 2568 张猫狗图这种小型数据集我一般选 YOLOv5s 或 YOLOv8n。YOLOv5 生态稳定训练命令和参数文档都很全YOLOv8n 模型更小部署时方便。只做快速验证时不要一上来就选 v5x 或 v8x参数量大会让小数据集很快过拟合而且训练时间翻好几倍。预训练权重优选用官方 release 里提供的yolov5s.pt或yolov8n.pt。下载后先看一眼文件大小通常是十几兆到几十兆如果只有几百 KB大概率下载被中断或拿到了错误文件。网络环境受限时可以先把权重下载到本地再在训练命令里用绝对路径指定不要依赖自动下载这样还能在训练前手动检查文件完整性。训练命令我一般这样写cd yolov5 python train.py \ --data ../cat_dog/cat_dog.yaml \ --weights yolov5s.pt \ --img 640 \ --batch 32 \ --epochs 50 \ --workers 4 \ --project ../runs \ --name cat_dog_run参数含义--img是输入到网络的边长图像会按比例缩放到 640×640--batch是每个 GPU 上的批大小显存不够就改成 16--epochs对这个小数据集 50 轮足够再多容易过拟合--workers是数据加载线程数Windows 上超过 4 容易报 DataLoader worker 崩溃Linux 可以开到 8。--project和--name把日志输出到独立目录方便和下一次实验对比。如果机器内存不够可以加--cache disk把图片以缓存形式存在磁盘上。这个参数比每 epoch 重新读图快很多但会多占用和数据集等大的磁盘空间。4.2 看训练曲线和混淆矩阵别被指标骗了训练结束后runs/cat_dog_run目录下会有results.png、confusion_matrix.png和每轮的 mAP 曲线。我通常先看results.png里两件事训练 loss 是否持续下降验证集 mAP 是否在某个 epoch 后开始不再上升。如果 mAP 在 epoch 20 左右就平台期后面只是 loss 缓慢波动说明数据集太小模型已经饱和再训 50 轮也没有意义。confusion_matrix.png里行是真实类别列是预测类别主对角线越亮越好。常有人发现这个矩阵每一行加起来的数量不等于该类别真实样本数就开始怀疑贴错标签。YOLO 的混淆矩阵统计的是预测框与真实框在 IoU 阈值下的匹配结果没有匹配上的预测会进入背景列还有一些真实目标因为置信度低或遮挡被忽略所以行和列的总和不唯一是正常现象不是 bug。真正要注意的是如果猫这一行有大量目标被分到狗那一列那大概率不是训练问题而是类别映射反了需要回到数据转换阶段检查。4.3 遇到 bn 崩溃和 loss 为 NaN 时怎么办YOLO 训练中 bn 崩溃是个高频坑现象是第一个 epoch 正常第二个 epoch 开始 loss 突然变成 NaN日志里还会出现torch.nn.functional.batch_norm相关的警告。BN 层在 batch 很小时统计均值方差不稳定如果学习率又调得很大梯度一步跨太远很容易让 BN 层的 running stats 崩掉模型之后全都是 NaN。解决方法是先用小学习率和大 batch 兜底python train.py \ --data ../cat_dog/cat_dog.yaml \ --weights yolov5s.pt \ --batch 32 \ --lr0 0.001 \ --nbs 64 \ --img 640 \ --epochs 50--nbs是名义 batch sizeYOLOv5 内部会用它来重新缩放学习率。如果显存只能放--batch 16但想模拟 batch 64 的优化效果就写--batch 16 --nbs 64这个参数在小数据集上非常好用。另外标签里有 NaN 也会触发同样现象。所以我在训练前会跑一小段脚本检查所有 txtfrom pathlib import Path bad [] for p in Path(cat_dog/labels).glob(*.txt): for line_no, line in enumerate(p.read_text().splitlines(), 1): parts line.split() if len(parts) ! 5: bad.append((p, line_no, line)) continue try: x, y, w, h map(float, parts[1:]) except ValueError: bad.append((p, line_no, line)) continue if not (0 x 1 and 0 y 1 and 0 w 1 and 0 h 1): bad.append((p, line_no, line)) print(bad lines:, len(bad)) for item in bad[:20]: print(item)这个脚本只看第 2 到第 5 列因为第 1 列是类别 id。如果发现 w 或 h 等于 0说明标注 box 退化成了线段YOLO 在 anchor 匹配时会出问题也会间接导致 loss 波动。5. coco-val2014-cat_dog-2568 避坑记录我踩过的 5 个具体坑5.1 zip 伪加密双击要密码却没有密码现象压缩包明明公开分享双击却要求输入密码用 7-Zip 打开又能看到文件列表只是无法主动解压。原因打包工具的加密标志位被错误置位实际数据没有被加密这就是 zip 伪加密。解决用 7-Zip 打开全选文件直接拖到本地文件夹释放或者右键重新压缩为新的 zip不设置任何密码。重新压缩会丢失原文件的压缩率优势但能一劳永逸。如果你曾经用 Python 的zipfile去读这种包会得到RuntimeError: File is encrypted这时候先想到“伪加密”三个字别傻傻去跑密码字典。5.2 图片数和标签数对不上现象解压后 images 目录有 2570 张图labels 目录却只有 2568 个 txt训练时日志显示Found 2568 images ...实际图片数并不匹配。原因文件名里的 2568 可能是“有猫狗标注的图像数”但打包者复制图片时没有按标签过滤把一些原图中存在但没有任何猫狗目标的图也放了进来或者反过来缺失了一小部分图片。解决以 labels 为准做一次图片集合与标签集合的交集。我用 Python 找差异from pathlib import Path image_names {p.name for p in Path(cat_dog/images).glob(*.jpg)} label_stems {p.stem for p in Path(cat_dog/labels).glob(*.txt)} missing_label_images [name for name in image_names if Path(name).stem not in label_stems] print(缺标签的图片数量:, len(missing_label_images))如果差异只有几张直接删除没有标签的图片不要试图为它们生成空标签。YOLO 对空标签的图片训练时会跳过但数据加载时容易产生数量对不上的问题。5.3 类别 id 没有映射训练出来猫狗互换现象训练 loss 正常mAP 也能到 0.7但推理时猫的框全标成 dog狗的框标成 cat。原因labels 里写的是 COCO 原始 category_id17和18没有映射到0和1。YOLO 从数据里读出类别数量后在 NMS 阶段把 class 17 和 class 18 当作额外类别最终输出到names时错位。解决写一个小脚本把第一列从 COCO id 映射回连续 id。跑之前记得备份原文件from pathlib import Path import shutil backup Path(labels_backup) backup.mkdir(exist_okTrue) for p in Path(cat_dog/labels).glob(*.txt): shutil.copy2(p, backup / p.name) lines p.read_text().splitlines() out [] for line in lines: parts line.split() if not parts: continue if parts[0] 17: parts[0] 0 elif parts[0] 18: parts[0] 1 else: continue out.append( .join(parts)) p.write_text(\n.join(out))把映射和 names 顺序绑定后最好再随机选 3 张图用 OpenCV 画框看一眼标注类别是不是和图片内容一致。这一步其实花不了几分钟但能救回一整个训练周期。5.4 把 val2014 又当训练集又当验证集现象训练时验证 mAP 很高在完全没有见过的测试图片上一测立刻掉到低得离谱。原因这个子集本来就来自 COCO val2014val2014 里同一场景的连续帧很多比如一只猫在相邻两秒被拍了 5 张照片。如果随机划分 train/val同一时刻的“兄弟帧”可能一边在训练、一边在验证相当于模型已经见过验证数据。解决如果你只是为了复现流程或测试环境建议直接把 2568 张全部当独立验证集不要训练。如果必须训练和验证至少要按image_id做时间窗口划分而不是随机按图片名散切。一个粗糙但有效的做法是把图片按image_id排序后每间隔 10 张取 1 张进验证集这样能最大限度把连续帧留在同一侧。5.5 把 COCO 像素坐标原样写进 txtloss 直接爆炸现象训练刚开始就报AssertionError或者 loss 在 50 步内变成 NaN打开 labels 里的 txt看到0 123 456 78 95这样的大数字。原因YOLO 需要归一化的中心点坐标COCO 的 bbox 是像素坐标的[x, y, width, height]。如果只做了类别映射不改坐标模型的输出空间和标签空间完全不匹配。解决按照 3.2 节的脚本重新生成标签。生成后快速抽查有没有越界值awk {if ($2 1 || $3 1 || $4 1 || $5 1) print} cat_dog/labels/*.txt | head -20如果这条命令有输出说明还有标签没有归一化。注意 awk 里第 1 列是 class_id第 2 到第 5 列才是 x、y、w、h。这个检查在 Windows 上可以在 Git Bash 或 WSL 里跑不要直接用 PowerShell 的 awk。6. 把这个 2568 子集用出高信噪比的三种方法第一个方法是把它当成固定回归测试集。每当你改了一个 YOLO 模型结构、调了 anchor、加了注意力模块不要急着上大 COCO 全集先在cat_dog.yaml上跑同一个val.py记录每次 best.pt 的 mAP0.5 和 mAP0.5:0.95再做横向对比。因为数据集只有 2568 张一轮验证也就几分钟比用几十万图片的完整 COCO 来得快。这个数据集最稳定的价值是提供一个“可复现的基线”而不是一个更强的训练源。第二个方法是分开统计 cat 和 dog 的 AP不要只看总 mAP。YOLOv5 的val.py加--save-json会输出详细的 per-category AP天然就是分开的。我在一个小实验里发现dog 的 AP 往往比 cat 高 5 个点以上原因是 COCO val2014 原始分布里 dog 的样本更多而且狗在画面里的尺度变化比较集中。看到这种差异后可以尝试在 data.yaml 里给 dog 类更高的cls损失权重或者用简单的过采样缓解不平衡。如果不分开统计总 mAP 会把偏低的那类盖过去问题就被掩盖了。第三个方法是冻结 backbone 微调。对 2568 张图这种规模从随机初始化开始训练几乎必然过拟合从 YOLOv5s 预训练权重开始微调才是常态。我一般先跑 20 轮冻结前 10 层只让 head 去适应猫狗两类目标第 20 轮之后解冻全部层再把学习率降到 0.0001 跑完全部 epoch。命令可以写python train.py \ --data cat_dog/cat_dog.yaml \ --weights yolov5s.pt \ --freeze 10 \ --lr0 0.001 \ --batch 32 \ --epochs 30这里有个教训一开始我也拿这种“子集包”直接全量训练结果模型在验证集上很漂亮一到真实场景就翻车。后来养成“先验数据再改格式最后可视化”的习惯基本不再踩这个坑。希望帮到你。本文还有配套的精品资源点击获取