简介Pascal-5i 数据集是面向小样本学习研究的标准化评估资源源自经典的 PASCAL VOC 物体识别数据集将原始 20 个类别划分为基础类与新型类支撑小样本学习中 5 类 1 样本与 5 类 5 样本两种典型任务设定。它适合计算机视觉研究者、算法工程师以及刚入门小样本学习的学生用于验证原型网络、元学习、度量学习等算法在极少量标注样本下的分类与泛化表现。整个资源包约 151KB共 6 个文件包含 Python 读取脚本、可运行的示例笔记本、依赖文件清单、说明文档和许可证其中读取脚本负责数据加载与类别划分示例展示基本调用方式依赖清单帮助搭建运行环境可直接用于复现小样本分类流程省去手动整理原始数据的步骤。目前已有 1444 人浏览学习资源体量小但结构清晰既能为初学者提供任务定义、数据划分逻辑与基线代码参考也能成为研究者对比不同小样本方法性能的便捷基准。1. pascal-5i 是什么少样本语义分割绕不开的评测基准训练数据永远不够、新类随时出现、标注成本居高不下——在工业现场和学术实验里少样本语义分割的“能不能用”从来不是模型一个问题而是评测口径首先要对齐。pascal-5i 就是把 PASCAL VOC 2012 的 20 个语义类别切成 4 组、每 5 类一组的标准协议跑少样本分割的人基本都拿它当统一试卷。它解决的问题很具体模型只能从几张带标注的新类图上学会分割这一类评测时就看这几张能不能撑住一整类泛化。适合正在做新类快速适配、视觉质检打样、或复现少样本分割论文的工程师和研究者。2. 划分逻辑20 类如何变成 4 组 5 类它真正在测什么2.1 PASCAL VOC 的 20 类与 4 个 fold 切片pascal-5i 里的“5i”指的是 5 个新类novel classesi 表示第几组总共 4 组也就是 4 个 fold。每个 fold 选出 5 个类作为 novel其余 15 个类作为 base训练阶段只允许使用 base 类的有标注像素测试阶段才轮到 novel 类。四个 fold 覆盖全部 20 类任何一类都有机会被当作“从没见过的新类”这样评测结果不会偏向某一类视觉特征。Foldnovel测试类base训练类0aeroplane, bicycle, bird, boat, bottlebus, car, cat, chair, cow, dining table, dog, horse, motorbike, person, potted plant, sheep, sofa, train, tv1bus, car, cat, chair, cowaeroplane, bicycle, bird, boat, bottle, dining table, dog, horse, motorbike, person, potted plant, sheep, sofa, train, tv2dining table, dog, horse, motorbike, personaeroplane, bicycle, bird, boat, bottle, bus, car, cat, chair, cow, potted plant, sheep, sofa, train, tv3potted plant, sheep, sofa, train, tvaeroplane, bicycle, bird, boat, bottle, bus, car, cat, chair, cow, dining table, dog, horse, motorbike, person这个列表看起来只是分类学但它是整个数据协议的骨架。下载数据集后第一步永远是核对这张表而不是直接加载模型。很多复现代码把 fold 写死在配置文件里一旦前一个项目改了类顺序后一个项目拿去跑novel 类里混进了几个本该在 base 里的类指标立刻失真。2.2 每个 fold 里训练和测试分别发生了什么以 fold 0 为例模型在训练阶段只能看到 15 个 base 类的像素比如 bus、car、cat。训练方式不是常规的“给一张图出全图分割”而是构造小样本任务episode从 base 类里随机抽几个类每类抽 1 或 5 张带标注图作为支撑集support set再抽一张查询图query image让模型看支撑集后去分割查询图里对应类的像素。这个流程在训练时反复跑让模型学会“用支撑信息做分割”这个能力。测试阶段只把 base 类换成当前 fold 的 5 个 novel 类连支撑集的图和查询图也全部换成包含这些新类的图像。模型的参数在测试时不再更新纯粹看它能不能靠 1 张或 5 张标注图理解“这 5 个陌生类长什么样”。所以 pascal-5i 测的不是“模型见过多少类”而是“模型对没见过的东西能多快建立概念”。标注里一个常见装法是测试时把 novel 类的像素从训练集的掩码里抠掉再喂给模型这样更贴近真实场景。2.3 为什么按类划分而不是按图划分有人第一次接触这个数据集时问过一个问题为什么不直接把 VOC 的 train/val 按图片切开训练图归训练、测试图归测试答案很简单切图不切类所有 20 类在训练阶段都被模型见过测试时 no class is new这就不再是少样本学习而是普通语义分割换了个壳子。按类划分才能保证测试类的像素在训练期完全不可见顶多是在某些训练图里作为背景出现而规范做法是把这些像素设成 ignore防止模型偷看。按类划分也带来一个实际后果不同 fold 的难度不均匀。fold 3 的 novel 类包含 sofa、train、tv 这类结构规整、背景相对干净的目标而 fold 1 的 cow、chair 经常跟人和遮挡物搅在一起类内形态差异更大。所以论文里报平均分时通常报告“四个 fold 的 novel 类 mIoU 均值”单看某一个 fold 的数字没有说服力这个口径在复现时一定要跟原作者对齐。2.4 支撑集和查询集的采样限制协议本身对支撑集的来源没有硬性画线但行内约定至少有三条支撑集图像和查询集图像不能来自同一张图片支撑集和查询集里不能出现带相同标注实例的“连拍帧”每个 episode 里 5 个 novel 类要么全部出现、要么按固定顺序轮空。这些限制是为了防止视觉重复泄漏——比如同一只猫在两张图里几乎同姿态支撑图给了你正确答案查询图等于开卷考。我在实际工程里一般会把采样逻辑写成一个独立模块输入只传当前 fold 的 novel 类名列表和图像索引输出支撑集、查询集和对应的掩码路径。这样换 fold、换 shot 数都不用碰主训练循环。采样模块里再固定随机种子后面跑消融实验时改的是网络结构而不是数据分布对比才成立。3. 拿到手的第一步下载、目录结构和最小验证脚本3.1 需要准备哪些数据文件做 pascal-5i 数据准备底料是 PASCAL VOC 2012 的官方数据但少样本分割社区几乎默认再配一份语义边界数据集 SBD 的扩充标注。VOC 官方 trainval 约 2900 张图像SBD 扩充后训练图像约 1 万张出头。很多已发表结果都是在“VOC 官方数据 SBD 扩充”上训练的如果只用官方训练集复现出来的 novel 类 mIoU 普遍会掉几个点。数据内容是否必需JPEGImages所有原始图像必需SegmentationClassVOC 官方 20 类分割掩码必需ImageSets/Segmentation/train.txt, val.txt官方数据划分必需SegmentationClassAugSBD 扩充掩码复现论文指标时强烈建议3.2 把数据整理成标准目录结构各个开源实现使用的目录名称略有差异但都不脱这个骨架图像目录、掩码目录、划分列表目录。我的习惯是先统一成下面的结构再写代码后续加载逻辑只要写一次。mkdir -p pascal5i/{JPEGImages,SegmentationClass,SegmentationClassAug,ImageSets/Segmentation} # 解压 VOC 原始包后把图像和掩码放进来 # 再用官方提供的 SBD 数据补充 SegmentationClassAug ls pascal5i/ImageSets/Segmentation/ # 确认 train.txt / val.txt 存在 head -n 3 pascal5i/ImageSets/Segmentation/train.txttrain.txt 里每行是一个不带扩展名的图像 ID比如2008_000008。算法通过这个 ID 拼接出 JPEGImages 里的 .jpg 和 SegmentationClass 里的 .png。拆开来看每个字节都很简单但项目一多经常有人把 train.txt 和 val.txt 混着用导致支撑集里混入评估图像这里建议写好后先跑一次脚本检查图像和掩码一一对应避免后续训练中途才发现缺文件。3.3 掩码的类索引与 ignore 约定VOC 掩码图的像素值不是单纯 0~200 表示背景1~20 分别对应 20 个语义类255 表示边界或不参与训练的像素。处理时千万不要把 255 直接改成 0那样会把 hard boundary 变成背景参与损失计算。少样本分割里有一个更重要的约定当前 fold 的 novel 类像素即使在训练图像里出现了也要把它们改成 255。因为模型训练时如果见过这些像素的类别标签测试时“新类”就不新了。import numpy as np def filter_novel_mask(mask, novel_class_ids): # novel_class_ids: 当前 fold 的 5 个 novel 类 id 集合 filtered mask.copy() for cls_id in novel_class_ids: filtered[mask cls_id] 255 # 标记为 ignore不参与损失 return filtered这段代码里的关键决策是“255 而非 0”。设为 0 会让模型把 novel 类像素当成背景去学习挤压背景类的特征空间设为 255 在交叉熵损失里直接被跳过等于告诉模型“这块区域我没有答案你别猜”。训练 dataloader 里对每个 batch 都要执行一次这个过滤而且必须在数据增强、归一化之后用原始掩码做顺序颠倒会导致边缘像素的类号错位。3.4 用 10 行脚本验证协议装好了数据整理完不要急着上模型先写一个最小检查脚本把“类划分对不对、训练图里有没有 novel 类、掩码能否正常读取”一次性验证掉。这个脚本跑完只需几秒但能挡掉大量后期排查。import os import numpy as np from PIL import Image VOC_CLASS_NAMES [ aeroplane,bicycle,bird,boat,bottle, bus,car,cat,chair,cow, dining table,dog,horse,motorbike,person, potted plant,sheep,sofa,train,tv] fold 0 novel_ids {VOC_CLASS_NAMES.index(c)1 for c in [aeroplane,bicycle,bird,boat,bottle]} with open(pascal5i/ImageSets/Segmentation/train.txt) as f: ids [line.strip() for line in f if line.strip()] for img_id in ids[:20]: mask np.array(Image.open( fpascal5i/SegmentationClass/{img_id}.png)) leak set(np.unique(mask)) novel_ids if leak: print(novel 类出现在训练掩码中需要过滤:, img_id, leak)脚本逻辑很简单取前 20 个训练图像检查掩码里是否出现当前 fold 的 novel 类 id出现就说明过滤步骤没有生效。这一步在项目开始前跑一遍能确保之后所有实验都建立在同一份数据上而不是“某一天 SBD 掩码读取失败导致部分图像缺失”的隐形偏差。4. 避坑pascal-5i 上最容易翻车的五个细节4.1 用全量 VOC 类别训练导致指标虚高现象novel 类 mIoU 突然比已发表结果高出 3~4 个点甚至逼近正常监督训练的分数。原因训练脚本没有过滤 novel 类像素模型在训练阶段已经见过测试类的标签所谓小样本能力变成了一部分记忆能力。解决在 dataloader 里强制设置 ignore 规则并把过滤函数输出打印到日志确认每次训练启动时 novel 类在训练集的有效像素数为 0。4.2 缺少 SBD 扩充导致复现结果偏低现象自己辛苦复现novel 类 mIoU 比论文低 4~6 个点基础设置一模一样也救不回来。原因少样本分割的主流实现默认使用 SBD 扩充数据训练图像数量接近官方数据的三倍多没有扩充集相当于少了大量背景多样性和类内形态。解决准备 SegmentationClassAug 并加入训练加载路径同时确认 SBD 掩码的类别索引映射到 VOC 协议后一致因为不同渠道下载的 SBD 掩码在像素值和背景定义上存在不一致这点对最终结果影响很大。4.3 支撑集和查询集来自同一图像或同一场景现象1-shot 分数看起来异常高但换一组采样 seed 后剧烈波动。原因支撑图里的目标实例和查询图里的目标是同一段视频或同一场景的连续帧模型实际上不需要泛化只需要把支撑图里的视觉特征“平移”到查询图上。解决采样时记录图像来源序列同一来源的图像不能同时出现在支撑集和查询集里更严格一点在读取图像文件名时按前缀判断是否属于同一组场景。4.4 测试 seed 不固定导致结果不可复现现象同一套权重、同一个测试脚本两次运行得到的 mIoU 差 1.5 个点甚至影响论文表格里的排名。原因1-shot 支撑集是随机采样的不固定 seed 时每个 episode 的支撑图都不一样而少数困难 episode 对均值影响很大。解决测试入口固定随机种子在脚本里写死随机数生成器和 numpy 的 seed同时记录 episode 索引方便两个版本的结果逐 episode 对比。4.5 支撑图和查询图尺寸处理不一致现象训练指标正常但可视化结果里分割边界沿着固定方向错位或者小目标整体消失。原因支撑集图像缩放后没有保留原始长宽比或者查询图在做归一化时用了与支撑图不同的 resize 方式导致特征图的空间位置对应不上。解决把支撑集和查询图的预处理写成同一个函数resize 统一为(H, W)且使用相同插值方式如果是原图分辨率不统一就先把支撑图 resize 到查询图同一尺度再进网络。这个坑在“先提取支撑原型、再跑查询图推理”的两段式代码里尤其常见。5. 把结果变成可对比的指标评估协议与 mIoU 计算5.1 支撑集采样逻辑怎么写才能复现评估阶段支撑集的采样直接影响最终数字任何一点随机性都要锁死。常见做法是每个 fold 固定用同一个随机数生成器按顺序生成 episode 里每张支撑图的图像索引然后缓存到本地文件。这样即使代码结构改动只要缓存文件不变新代码和老代码结果就能对齐。import random def make_support_set(novel_ids, shot, seed, image_ids): # novel_ids: 当前 fold 的 novel 类 id 列表 # shot: 1 或 5支撑图数量 rng random.Random(seed) pool [i for i in image_ids if has_novel_class(i, novel_ids)] return rng.sample(pool, shot)这里有一个容易被忽略的点rng.sample与rng.shuffle在相同 seed 下产生的序列并不相同代码版本之间如果改动过采样方式即使 seed 不变生成的 episode 也会变。所以我的做法是把每个 episode 的支撑图 ID 落盘成一个 JSON 文件测试时只读文件不现场采样。这个习惯能避免很多“同一个 seed 结果却不同”的悬案。5.2 每个类的 IoU 与四个 fold 的汇总方式语义分割里类别的 IoU 计算并不复杂但要小心三类像素GT 里是 255 的 ignore 像素不能参与交集预测里多余的前景像素要进并集某些 novel 类在查询图里完全没有出现时该类的 IoU 计 0 还是跳过协议里通常是计 0否则会高估模型表现。def compute_class_iou(pred, gt, cls, ignore255): pred_m (pred cls) gt_m (gt cls) inter (pred_m gt_m).sum() union (pred_m | gt_m).sum() return inter / union if union 0 else 0.0汇总时先算每个 fold 内 5 个 novel 类的平均 IoU再对 4 个 fold 取平均得到最终 novel mIoU。有些论文还会报 base 类 mIoU 和两者的调和平均用来观察模型在“记住旧类”和“适应新类”之间的平衡。复现时先确认对方报的是哪种口径不要拿“只报 novel 类”的数字去跟“报全部 20 类”的数字比。5.3 和已发表结果对齐前必须确认的三件事确认点影响方向检查方式是否使用 SBD 扩充训练数据不使用时 novel mIoU 可能低 3 点以上看训练日志里的图像总数是否为官方 train 的约 7 倍测试 episode 总数与采样 seed100 个 episode 和 1000 个 episode 的方差明显不同固定 seed按同一 episode 文件比对两个模型的输出掩码的 ignore 设置novel 类像素未过滤会导致指标虚高单独统计训练集里 novel 类像素数量应为 0这三件事本质上是同一个问题指标的可信度。pascal-5i 的数值本身不是一个绝对能力的度量而是一个“在固定协议下的相对比较”。只要协议里的任何一个环节没对齐你的数字和别人的数字就不能放进同一张表格里。我见过有人因为漏了 SBD 扩充连续两周在调模型结构实际上只是数据量不如别人方向完全跑偏。6. 用 pascal-5i 做自己的实验一个快速验证技巧很多人在 pascal-5i 上踩的第一个跟头是“从头跑一个完整 fold 太慢”。其实可以先用 10 个 episode 的调试模式把流程跑通。做法是在训练脚本里加一个debugTrue参数训练集只取 50 张图每个 fold 的支撑集固定生成 10 个 episodeepoch 数砍到 1/10整个流程几分钟内跑完。这个模式不是为了看指标而是确认数据流、损失、反向传播、评估脚本全程无断点再把规模放大。另一个值得养成的习惯是建立一个“小模型基线”。不要一上来就堆复杂模块先把你手头最朴素的语义分割模型接到 pascal-5i 协议上跑出 base 类 mIoU 和 novel 类 mIoU记住这个数。后面任何改动的收益上限都是相对这个基线算的。如果一个小改动连 base 类 mIoU 都往下掉说明它不是针对小样本问题而是把原有分割能力伤到了这种改动没有继续优化的价值。遇到指标突然波动时不要急着调参。我会把每个 episode 的支撑图、查询图、预测结果和 GT 横向拼成一张图连续看 20 个 episode。很多时候问题根本不在模型而是某一张支撑图里目标极小、几乎被背景淹没导致原型被背景 feature 稀释或者查询图里的目标与支撑图里的目标从另一个角度拍摄特征完全不同。这些小样本特有的边界案例光看平均数永远发现不了。我自己的习惯是每套实验固定一个 seed 清单比如 0、1、2跑三个 seed 取均值同时把每个 seed 的结果单独记录。模型改动如果只在某一个 seed 上变好换一个 seed 就没有了那大概率是采样运气。跑 pascal-5i 这几年最大的教训就是永远不要用一个 seed 的结果下结论也不要相信一次运行的 mIoU 比论文高就在模型设计上狂喜。希望帮到你。本文还有配套的精品资源点击获取