尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

少样本目标检测入门:pascal-5i 数据集全解析

少样本目标检测入门:pascal-5i 数据集全解析 简介Pascal-5i小样本学习数据集为研究者提供了基于PASCAL VOC构建的标准评测环境专攻5-way 1-shot与5-way 5-shot场景适合从事小样本图像分类、原型学习、元学习等方向的研究人员和学生使用。数据集将原始20类划分为基础类与新类别前者用于训练基础知识后者用于评估模型仅凭少数样本识别新类的能力能够清晰检验算法的泛化性能。整个资源包共6个文件约151KB包含说明文档md、示例脚本ipynb、Python读取代码py、依赖清单txt以及许可证等结构简洁。配合现成的读取脚本和示例notebook读者可从零开始加载数据、查看图像与掩码省去自行解析PASCAL VOC标注的繁琐过程直接开展原型网络或元学习对比实验。目前已有1443人学习使用对于入门小样本学习或复现论文的实验环境搭建都是高性价比的起点。 月底调模型的时候我从一个朋友那收到一个消息他说手头有个“pascal-5i数据集”半天没跑通问我是哪一年的VOC。我当时就明白这就是很多刚上手少样本目标检测few-shot object detection的人第一道坎。pascal-5i 不是什么新拍的数据集也不是某个人的“私藏”它是从经典 PASCAL VOC 里按照固定规则切出来的一个基准benchmark专门用来验证模型“只看几张带框标注的图能不能学会一个新类别”。这篇文章就把这件事讲透pascal-5i 的来历、四个 fold 怎么划分、K-shot 到底按图算还是按框算、怎么从零手写 split 脚本以及训练和评估中那些论文不会告诉你的坑。适合刚入坑小样本检测的研究生也适合想复现 baseline 结果的工程师。1. Pascal-5i 到底是什么少样本目标检测的“出场设置”1.1 从 PASCAL VOC 20 类说起PASCAL VOC 是计算机视觉里最有“资历”的目标检测数据集之一它本身包含20个类别aeroplane, bicycle, bird, boat, bottle, bus, car, cat, chair, cow, diningtable, dog, horse, motorbike, person, pottedplant, sheep, sofa, train, tvmonitor。大家一般会把 VOC2007 和 VOC2012 搭配着用2007 的 trainval 做训练2007 的 test 做测试2012 的 trainval 做额外训练数据。两个版本的类别定义完全一致这也是后续各种变体能够基于它做切分的前提。我第一次看 Pascal-5i 时第一反应也是“这不就是 VOC 吗”后来才意识到它的关键不在数据本身而在“怎么切”。pascal-5i 仍然使用这20个大类但把它们拆成了“基类”base classes和“新类”novel classes。基类拥有完整的大量标注新类在训练阶段只保留极少量标注样本。这种设计模拟的是现实中的一个常见矛盾某些类别你手上数据很多某些类别刚刚出现、只有几张图但你又希望检测器能快速学会它们。所以 pascal-5i 不是一个孤立的数据集它是一个“协议”。你下载的还是原始的 VOC 图片和 XML 标注要跑 pascal-5i关键是一套你认可的“划分规则”和“采样规则”。不同论文、不同开源代码对 fold 的划分可能有细微差别这才是很多人最终没复现出论文数据的原因。1.2 “5i”这两个字符怎么拆解“5”指的是 few-shot 设定中的 shot 数也就是 K。最常用的设定是 1-shot 和 5-shot。这儿的 shot 含义非常容易踩坑后面我会专门讲。“i”指的是第几个 fold数据划分常见变量名是 split 0 到 split 3或者 split 1 到 split 4。因为一次切分带有偶然性只在一个划分上做实验不能说明任何问题所以大家约定俗成地做多次划分把结果平均才算是比较可信的结论。pascal-5i 里的 i就是提醒你“别只跑一个划分”。这套命名方式最早可以追溯到语义分割领域的 PASCAL-5i 基准。后来少样本目标检测的研究者们沿用了这一套思路把 VOC 的 20 类拆成多个划分每个划分里的 novel 类都不相同这样每个类都有机会成为“少数类”模型不能在某个特定类别组合上过拟合。所以你在读论文时经常看到 “average over 4 splits” 这类描述指的就是在四个划分上分别训练、分别测试最后取平均结果。1.3 为什么少样本检测需要这种“人造基准”目标检测的数据标注成本远高于图像分类。分类只需要告诉模型“图里有什么”检测必须告诉它“东西在哪个位置”也就是要画 bounding box。如果某个新类别只有三五张图用传统检测流程去训练很容易过拟合到背景、光线、角度这些无关因素上。pascal-5i 的价值在于它把所有模型放在同一个起跑线上大家都是同样的20类同样的 K-shot 标注量同样的 base 类预训练数据。这样一来论文之间能比较的就不再是“谁的数据集更好”而是“谁能在同样的小样本条件下学到更通用的特征”。这是它和普通“数据集”最大的不同它更像一套标准化考题。2. 四个 Fold 怎么划分的类别取舍与实验协议2.1 常见划分示例与“以代码库为准”的出处不同代码库对四个 fold 的类别划分细节略有不同我先把最常见的一组列出来供参考这套划分在 Meta R-CNN、TFA、DeFRCN 等不少工作里都能看到fold 1split 0novel {bird, bus, cow, motorbike, sofa}fold 2split 1novel {aeroplane, bottle, cow, horse, tvmonitor}fold 3split 2novel {boat, cat, motorbike, sofa, tvmonitor}fold 4split 3novel {cat, chair, cow, dog, pottedplant}划分novel 类每个类提供 K-shot基类数量fold 1bird, bus, cow, motorbike, sofa15fold 2aeroplane, bottle, cow, horse, tvmonitor15fold 3boat, cat, motorbike, sofa, tvmonitor15fold 4cat, chair, cow, dog, pottedplant15注意fold 2 和 fold 3 里都出现了 cow、motorbike、sofa、tvmonitor这说明不同划分之间并不要求“完全互斥”。有的刚入门的同学以为四个 fold 是硬切四份novel 类不能重复这完全是误解。pascal-5i 关注的是模型在“任意指定 K-shot novel 类”上的泛化能力不是让类的集合干干净净不重叠。更重要的是既然各个开源库之间存在微调版本差异你在任何实验开始前都必须确认自己使用的常量表。我看过太多实验结果对不上最后发现是别人 repo 里 fold 划分的类别顺序跟你理解的不一样。不要盲目照抄论文附录里的表格以你跑的那份代码里的pascal_voc.py或voc_split.py中的定义为最终标准。2.2 shot 的定义按实例还是按图片这是最常见的误区K-shot 中的 shot 绝大多数情况下指的是“标注实例数”也就是 bounding box 的数量而不是图片数量。这一点新手特别容易搞错因为分类领域的 few-shot 通常天然按图片数算一张图就是一个样本。到了检测领域一张图里可能同时出现三只猫、五个人那么这张图能提供的“样本数”就不是1而是对应类别框的数量。举个例子5-shot 设定下如果“猫”这个类别只有一张图片但这一张图里恰好有 5 只猫5 个标注框那么它就直接满足了这个类的 5-shot 限额。如果你按“5张图”来采样你会多拿一些本来不该出现的训练图这相当于偷偷增加了数据量最终结果会比论文偏高也不公平。构建 split 时你需要逐类解析 XML 里的object数量直到某一类的累计框数达到 K 才停止添加该类的图片。对于既包含 base 类又包含 novel 类的图片处理方式要格外小心通常的做法是整张图都会进入 support 集但在训练时只会保留 novel 类的标注框base 类框要不要保留取决于你用的训练框架。有些方法为了避免泄漏会直接把 base 类框过滤掉有些方法则会保留当作上下文。这个选择要在实验记录里写明白不然别人复现时又会对不上。2.3 实验协议base 预训练、novel 微调、四折平均绝大多数基于 pascal-5i 的算法走的是“两阶段”路线第一阶段用全部 base 类训练一个常规检测器比如 Faster R-CNN第二阶段冻结或微调部分权重用 K-shot 的 novel 数据做小样本适配。这是检测任务和分类任务一个显著差异因为检测模型包含 proposal 生成、RoI 特征提取、分类与回归多个模块你不能像图像分类那样简单地把新类别挂到分类头上就完事。在完整实验中你需要对每个 fold 都做同样的流程基类预训练、抽取对应 novel 的 K-shot、微调、在 VOC2007 test 上评估。最后把四个 fold 的结果平均上报的是“4-split 平均 mAP”而且惯例上会单独报告 novel 类上的 mAP因为这才是小样本能力的核心体现。只看 20 类整体 mAP 会被 base 类结果稀释掩盖真实差距。3. 手把手构建自己的 Pascal-5i从 VOC 下载到 split 脚本3.1 下载与目录结构第一步当然是拿到原始 VOC 数据。虽然名叫“pascal-5i 数据集”但你并不需要找专门的下载链接只需要去官网下载 VOC2007 和 VOC2012。常见目录结构如下VOCdevkit/ ├── VOC2007/ │ ├── Annotations/ │ ├── ImageSets/ │ └── JPEGImages/ └── VOC2012/ ├── Annotations/ ├── ImageSets/ └── JPEGImages/如果你用的是第三方库比如很多少样本检测的开源代码它们已经提供了自动化下载脚本。但我的建议是手动确认一下 Annotations 里的 XML 文件数量和 JPEGImages 里的图片数量是否对得上。因为后处理脚本通常会全量扫描这两个目录缺失一个文件就可能导致整个类别实例计数出错而且这种错误肉眼很难发现。3.2 解析 XML、筛选 K-shot 实例的核心逻辑构建 split 的核心功能就两件事统计类别实例数、按 K-shot 限制挑选图片。下面这个简化版 Python 脚本能说明大部分逻辑import os import random import xml.etree.ElementTree as ET VOC_CLASSES [ aeroplane, bicycle, bird, boat, bottle, bus, car, cat, chair, cow, diningtable, dog, horse, motorbike, person, pottedplant, sheep, sofa, train, tvmonitor ] novel_classes {bird, bus, cow, motorbike, sofa} base_classes [c for c in VOC_CLASSES if c not in novel_classes] K_SHOT 5 random.seed(0) def parse_annotation(xml_path): tree ET.parse(xml_path) root tree.getroot() instances [] for obj in root.findall(object): name obj.find(name).text if name not in novel_classes: continue difficult int(obj.find(difficult).text) if difficult: continue bndbox obj.find(bndbox) box [ int(round(float(bndbox.find(xmin).text))), int(round(float(bndbox.find(ymin).text))), int(round(float(bndbox.find(xmax).text))), int(round(float(bndbox.find(ymax).text))) ] instances.append((name, box)) return instances xml_dir VOCdevkit/VOC2012/Annotations all_xmls [f for f in os.listdir(xml_dir) if f.endswith(.xml)] random.shuffle(all_xmls) shot_collect {cls_name: [] for cls_name in novel_classes} for xml_file in all_xmls: instances parse_annotation(os.path.join(xml_dir, xml_file)) if not instances: continue for cls_name, box in instances: if len(shot_collect[cls_name]) K_SHOT: continue shot_collect[cls_name].append((xml_file, box)) if all(len(v) K_SHOT for v in shot_collect.values()): break这段代码里有两个关键点。第一difficult样本建议过滤掉这是 VOC 官方的惯例difficult 表示该目标本身极难辨认通常不符合小样本训练的预期不排除会让结果不稳定。第二采样前必须random.seed并且把采样得到的图片列表保存成文件。因为四个 fold 之间、不同 K 值之间都需要可复现后面对比实验或别人复现都依赖这个文件。3.3 类别重映射与避免基类泄漏拿到 K-shot 图片后你不应该直接把原始标注喂给检测器。因为检测器的分类头在 base 预训练阶段只有 15 类你要么在第二阶段扩展分类头增加 novel 类要么把标注重映射成“base novel 统一的新索引”。这两条路对应不同的微调策略前者常见于把 novel 类当作增量类处理后者常见于统一 fine-tune。很多框架默认你会在数据加载器里完成重映射如果你没有做模型输出的类别维度会对不上训练直接崩。基类泄漏是另一个隐蔽问题。假设一张 K-shot 图片里既有 novel 类“猫”又有 base 类“人”。从最简单的角度讲这张图包含了人的完整标注如果在微调阶段你把人的 ground truth 也拿来训练就等于让模型在 novel 场景中额外复习了 base 类。这对检测器的整体性能未必是坏事但它会混淆实验结论novel mAP 的提升到底是因为学到了“猫”的通用特征还是因为在少数几张图里同时强化了“人”的检测能力所以在写实验说明时最好明确一个原则novel 微调阶段只使用 novel 类的框参与 loss 计算。4. 训练与评测中绕不开的细节mAP、种子与数据增强4.1 评估指标novel 类 mAP 才是主线pascal-5i 的测试阶段通常是用 VOC2007 test 作为评估集你会对测试图片上的目标类别做预测然后按标准 mAP 流程计算。对于小样本检测大家最看重的是 novel 类的 mAP0.5。原因很直接base 类有几百上千张训练图检测器在 base 上的性能主要反映“常规训练”能力不是小样本能力。只有在 novel 类上拉开差距才能说明算法在小样本条件下的泛化性能。你可能会在网上看到不同的汇报方式有的论文报mAP_novel有的报mAP_all还有的报mAP_base。做实验时我的建议是三个都算一遍上报时以 novel 为主、另外两个作为辅助参考。因为只报 novel 类可能掩盖模型在 base 类上的灾难性遗忘比如某些方法在 novel 上提了 2 个点但 base 掉了 8 个点这在实际部署中根本不可用。4.2 两阶段微调的主流范式从复现角度讲R101-C4 骨架的 Faster R-CNN 配合余弦学习率衰减、在 K-shot 上微调若干个 epoch到今天依然是性价比最高的 baseline 之一。NVIDIA 的 TFA 工作非常透彻地验证了这个思路它不需要复杂的元学习模块先用 base 类预训练再在 novel 类上微调分类头和 bbox 回归头就已经能超过很多花哨的元学习算法。一个我反复使用的微调策略是第一阶段base用正常的 1x 或 2x schedule第二阶段novel把学习率降到原来的 1/10 左右同时加大 weight decay训练轮数不用多。小样本阶段你要是沿用大学习率几个 batch 下来 loss 就飞了。并且在微调阶段做数据增强时必须警惕“增强过头”小样本场景下随机裁剪、水平翻转有帮助但过强的 color jitter 反而会把本来就是少数类的特征进一步扭曲。4.3 影响结果稳定性的三个隐藏因素第一个是随机种子。很多人做 few-shot 实验时只设定一次全局随机种子但真正影响结果的是构建 K-shot 时的采样种子、数据加载时的 shuffle 种子以及并行训练时数据加载器的 worker 种子。我的习惯是全链路固定并且在代码开头用日志打印出每个环节的 seed 值。第二个是 base 预训练模型的选择。你用同一个算法、同一个 K-shot但 base 预训练权重不同novel 性能可能差 1 到 2 个点。社区里有些代码会公布他们训练好的 base 模型权重建议第一次复现时直接用官方权重搭起完整流程后再考虑自己训练 base。第三个是“微调多少轮”。few-shot 场景下模型非常容易在 support 集上快速过拟合训练损失下降很快但测试 mAP 可能已经拐头向下了。我自己的做法是每 500 步保存一次 checkpoint在验证集上选点。不要轻易相信“最后一个 epoch 最好”。5. 我踩过的坑与给新手的检查清单5.1 看似简单但很致命的配置错误先分享两个真实遇到的坑。第一次跑 pascal-5i 时我下载的 VOC 数据里混入了一些损坏的 JPEG 图片解码时报Corrupt JPEG data警告。我一开始觉得无伤大雅结果某个 novel 类的实例数统计一直差几个后来才发现对应 XML 挺好但图片文件根本打不开。从那以后我写了个脚本逐张验证图片能否正常解码再进 split 流程。第二个坑是类别索引偏移。VOC 官方没有提供统一的“从 0 到 19”的类别 ID 表很多代码库自己写了一套。如果你把一个 repo 里生成的 K-shot 标注文件直接拿到另一个 repo 里用类别 ID 很可能对不上出现“模型把猫预测成牛”这类完全错位的结果。解决办法很简单每次用新代码库都先打印一遍类别索引表确认 novel 类的 ID 和该库内部定义一致。第三个坑容易被忽略你在构建 K-shot 时可能会把同一张图片同时选入 novel 类的 support 集和测试集。如果这张图来自 VOC2007 test或者来自 VOC2012 但被某个库混进了评估集那测试结果会虚高模型可能只是在记忆图片而不是泛化。必须做好集合去重检查。5.2 如何判断模型是真的学会了这些新类小样本检测里有个常见现象模型在 novel 类的 mAP 上看起来不错但一可视化就露馅它其实是在“猜”。判断模型有没有真正学会新类的通用做法有两个。第一个做法是做一次“反向验证”。你把 novel 类 K-shot 里的图片隐藏一半只保留 K/2 的 shot看看 mAP 是否随之下降。如果 mAP 几乎不动说明模型主要靠 base 预训练特征硬撑并没有从 support 集中获取有效信息这个模型在真实小样本场景里不太可靠。第二个做法是画 PR 曲线而不是只看 mAP 数字。小样本下 PR 曲线通常抖动厉害但你可以关注 recall 的极限位置。如果模型在 novel 类上的最高 recall 只有 50%即使 AP 被调得还行说明 proposal 阶段就已经漏掉了大量新类目标。很多小样本检测模型的瓶颈不在分类头而在区域提议网络根本没有把新类目标提出来。5.3 一个让实验对比更有说服力的习惯做研究时把四个 fold 的结果分别记录下来非常有必要。很多论文只报平均 mAP但如果你看过每个 fold 的具体结果会发现某些 fold 的性能明显偏低。一个能够诊断模型偏置的习惯是打印每个 novel 类的 AP而不是只打印总平均。比如一个方法在 fold 1 上 AP 全是 30在 fold 2 上靠“bottle”刷到 50平均下来看着不错但实际挑类别。这个信息只有拆到单类才能发现。最后分享一个小技巧如果你在跑基线实验建议把每个 fold 的 K-shot 标注文件单独存成 JSON 或 pickle不要每次都重新采样。一来能保证多个方法在完全相同的 support 集上进行公平对比二来能省掉不少调试时间。我吃过一次亏两个算法“官方代码”各自采样了一套 support 集结果微小差距根本分不清是算法原因还是数据原因。统一 K-shot 文件之后实验结论一下子清晰多了。本文还有配套的精品资源点击获取
返回列表