简介Pascal-5i数据集是面向小样本学习研究的标准化数据集源自PASCAL VOC将20个类别划分为基础类与新类支持5-way 1-shot和5-way 5-shot任务适合需要验证原型网络、元学习或知识迁移方法的科研人员与算法工程师使用。压缩包内共6个文件约151KB包含Markdown说明文档、Python读取脚本、Jupyter示例程序以及依赖清单等从环境配置到数据加载与实验演示均有覆盖结构紧凑。已有1444人学习下载说明这一数据集在小样本学习社区中具有一定关注度。借助其中的数据划分与读取工具使用者可以跳过繁琐的数据预处理直接聚焦算法设计与效果对比还能基于示例程序快速复现基础实验适合用于开题验证、论文复现以及课程设计等场景。1. 从 pascal-5i 说起小样本学习评测绕不开的基准某开发者第一次复现小样本学习分割论文时跑完一个 splitmIoU 和论文差了 12 个点。折腾两天后发现问题不在模型而在数据划分他把支持集和查询集的图像混在了一起。pascal-5i 是小样本学习few-shot learning领域曝光率最高的语义分割基准它没有独立的下载链接而是基于 PASCAL VOC 2012 和 SBD 标注衍生出来的评测协议。核心是一个「5 类一组」的划分方案20 个物体类被切成 4 个 split每个 split 中 5 类作为查询类、15 类作为支持类用来验证模型见过某类 1 张或 5 张标注图之后的分割能力。适合正在做小样本分割研究、准备对比 SOTA 方法、或者只是想跑通一个可信 baseline 的从业者。2. 数据集的底子VOC 2012、SBD 与四个 split 的划分规则2.1 目录结构与标注格式PASCAL-5i 不是一个会随论文打包下载的文件它是「协议 两套数据集的组合体」。协议指定用哪些图像做 support、哪些做 query底层标注来自 PASCAL VOC 2012 devkit 和 SBDSemantic Boundaries Dataset。复现之前先把这些东西装进脑子VOC 的语义分割标注是 8-bit 彩色 PNG用调色板palette存储背景是 0物体类从 1 到 20边界是 255SBD 的标注是以类别为中心的边界标注集在 VOC 2012 的 trainval 图之外还多了一批新图它的 cls 掩码是单通道 PNG像素值直接就是 0背景到 20类别同样把 255 作为未知区域。目录结构如下以从官网解压后的默认路径为例目录内容标注格式VOCdevkit/VOC2012/JPEGImages约 1.7 万张原图JPGVOCdevkit/VOC2012/SegmentationClassVOC 语义分割标注palette PNGVOCdevkit/VOC2012/ImageSets/Segmentationtrain.txt / val.txt 等列表txtSBD/dataset/imgSBD 图像含 VOC 没有的图JPGSBD/dataset/clsSBD 单通道语义掩码PNG需要特别留意VOC2012 的 trainval 一共 2913 张有分割标注SBD 额外提供了这 2913 张的改进标注和 5585 张新图的标注因此 SBD 的 cls 目录文件比 VOC 的 SegmentationClass 多出不少。绝大多数 PASCAL-5i 实现用 SBD 的 train 部分作为训练时的支持图像来源用 VOC 2012 的 val 作为查询图像来源这就是为什么你下载的每个 repo 都会同时要求这两份数据。2.2 四个 split 的类别划分谁是支持类谁是查询类PASCAL-5i 的核心协议是把 VOC 的 20 个类别切成 4 个互斥的 split每个 split 包含 5 个查询类。划分方式是固定的对比实验必须按同一张表来split查询类5 类支持类剩余 15 类fold0aeroplane, bicycle, bird, boat, bottle其余 15 类fold1bus, car, cat, chair, cow其余 15 类fold2diningtable, dog, horse, motorbike, person其余 15 类fold3pottedplant, sheep, sofa, train, tvmonitor其余 15 类这里的「查询类」就是在该 split 的测试中需要被分割的类别训练阶段模型不允许看到查询类的标注只能在支持图像里看到支持类的掩码。每个 split 之间类别不重叠保证了「跨类泛化」的测试可信度。有的论文把 fold3 称为 fold4甚至在 4 个 split 之外再加自定义划分报告结果时务必标明自己用的是哪几个否则复现时数值对不上第一反应常是「模型写错了」实际只是划分表不同。2.3 支持图像与查询图像的来源边界理解 PASCAL-5i 的第二个关键点在于「图像来源」而不是「类别来源」。训练时 support 图像通常从 SBD 的 train 列表里取query 图像从 VOC 2012 的 val 里取验证时 support 和 query 都从 val 里取。这意味着同一张图在整个实验里扮演的角色要提前定好不能既当支持又当查询。遵守这个边界的原因是防止信息泄露如果支持图像和查询图像来自同一张图模型在测试时见过同一场景的标注mIoU 会显著虚高。我见过某开发者把 SBD 的 trainval 全部塞进支持集又用 VOC val 做查询导致 fold 结果比论文还高 8 个点这不是模型强是泄露。跑实验前先把每个 split 的 support 图清单和 query 图清单导出成 txt各自去重再检查交集是否为空这一步值得做成固定流程。SBD 的 train 和 val 数量也常被忽略SBD train 有 8498 张、val 有 2857 张VOC 2012 train 1464 张、val 1449 张。有的实现为了图省事只用 VOC train 构建支持类索引类别样本量少一半训练出来的模型在验证时 mIoU 会偏低 3-5 个点。复现任何比较实验前先确认对方用的训练支持图像源是 SBD 还是 VOC这直接决定了你的实验能不能对齐。3. 下载与预处理把 VOC 2012 与 SBD 对齐成可直接读取的数据集3.1 数据获取与目录检查PASCAL-5i 的安装需要手动获取两套数据集并放到同一目录下。常见做法是建一个 data 目录下载 VOC 2012 devkit 压缩包和 SBD 的 dataset 压缩包解压后保持默认目录名不变。下载完成后先做一次目录体检find . -maxdepth 3 -type d | sort正常情况应该能看到 VOCdevkit/VOC2012/JPEGImages、VOCdevkit/VOC2012/SegmentationClass、SBD/dataset/img、SBD/dataset/cls 这几个关键目录。这时候不要急着写训练代码先确认两件事。第一VOC 的 mask 文件数和 SBD 的 mask 文件数是否完整缺少标注会在训练中断断续续报错难以排查。第二JPEGImages 里全部是 JPG 后缀SBD/img 里也都是 JPG但有些镜像下载的包会把图片转成 PNG格式不一致会在图像解码时报错。检查文件数用 find 命令find VOCdevkit/VOC2012/JPEGImages -name *.jpg | wc -l find SBD/dataset/cls -name *.png | wc -l比较官方公布的数量数量对不上就去检查下载包是否完整。这一步做得好后面能省很多「图片读不出来」的调试时间。如果从网盘或别人分享的镜像下载解压后务必确认两级目录名PASCAL 相关代码里路径写死成 VOCdevkit/VOC2012 的情况很常见目录名一变所有相对路径全部失效。3.2 统一标注格式从彩色掩码到 class id两套数据的标注格式不同VOC 的 mask 是 palette PNG用 PIL 读取后得到索引数组SBD 的 cls 是单通道 PNG读取后直接是类别数值。为了让训练代码只认一种格式我的习惯是把两份 mask 全部统一成 uint8 的 npy 缓存只保留类别索引。import numpy as np from PIL import Image from pathlib import Path def voc_mask_to_id(path): img Image.open(path).convert(P) return np.array(img, dtypenp.uint8) def sbd_mask_to_id(path): arr np.array(Image.open(path), dtypenp.uint8) if arr.ndim 3: arr arr[..., 0] # SBD 偶见三通道存储取第一通道 return arr def unify_mask(mask): mask mask.copy() mask[mask 255] 255 # ignore 区域统一为 255 return mask # 批量缓存示例 root Path(./data) out_dir root / SegmentationClass_npy out_dir.mkdir(exist_okTrue) for p in (root / VOCdevkit/VOC2012/SegmentationClass).glob(*.png): mask voc_mask_to_id(p) np.save(out_dir / f{p.stem}.npy, unify_mask(mask))逻辑说明VOC 的 P 模式图像np.array 拿到的就是像素索引0 是背景、1-20 是类别、255 是未标注边界SBD 的 cls 本身也是 0-20少数文件存在 255。统一处理后所有 mask 的语义就是「0 背景1-20 类别255 忽略」。参数表里唯一需要留意的值是 255 的忽略编号它必须和损失函数里的 ignore_index 一致。统一成 npy 后训练时的读取开销也会下降PIL 每次加载 PNG 需要解码调色板npy 直接 np.load 即可。缓存放在 SegmentationClass_npy 目录原目录保留不改动。这个预处理脚本看起来不起眼但它是整个复现流程里最值得保留的一段代码后期换数据集只需要改读取函数主训练代码一行不用动。3.3 生成 split 对应的图像列表PASCAL-5i 实验里几乎每个 repo 都会生成自己的 split 列表格式无非是 txt每一行是「图像相对路径 类别」。为了避免采样时反复扫描全量数据浪费时间按照类别建立一张「类别到图像列表」的映射表最实用。import numpy as np from collections import defaultdict def build_class_index(mask_dir, img_dir): class_to_images defaultdict(list) for npy_path in sorted(mask_dir.glob(*.npy)): mask np.load(npy_path) classes np.unique(mask) classes classes[(classes 0) (classes 255)] for c in classes: name npy_path.stem class_to_images[int(c)].append(str(img_dir / f{name}.jpg)) return class_to_images逻辑说明读取每张图的 mask找出它包含的所有物体类别把图路径登记到对应类别的桶里。class_to_images 的 key 是 1-20 的类别编号value 是该类别出现的所有图像路径列表。后续构建 episode 时不再扫描 mask只需要查表速度提升明显。参数说明sorted 保证多进程下顺序稳定过滤掉 255 是因为 ignore 不能参与分类计数考虑到 1-20 类别编号与 VOC 类别名的映射建议同时生成一个 id2name 字典方便可视化时在图像上打印类别名。生成列表后按 fold 导出成文件也是家常便饭def save_split(class_to_images, fold_classes, out_file): with open(out_file, w) as f: for c in fold_classes: for img_path in class_to_images[c]: f.write(f{img_path} {c}\n)这个文件在验证时用来统计「每个查询类有多少张可采样图」。一个常见现象是 bottle、pottedplant 这类小类别在 VOC val 里只有 30 张左右带标注的图采样支持图像时很容易反复抽到同一批造成验证结果方差增大。把类别样例数打印出来就能知道该不该固定随机种子、该用多少个 episode 做评估而不是盲从论文里写的固定数值。4. 构建 few-shot episoden_way、n_shot 采样逻辑与 batch 组织4.1 episode 采样的核心流程few-shot 分割的训练单元不是一张图而是一个 episode。一个 episode 通常由一个查询图query和一组支持图support组成支持图提供「当前任务的目标类别外观」查询图是模型需要实际分割的图。以 PASCAL-5i 的标准做法为例训练时随机从支持类里抽 n_way 个类别每个类别抽 n_shot 张支持图查询图指定为其中一个类别。整体流程如下第一步从训练用支持类里随机选出 n_way 个类别。第二步对每个类别在 class_to_images 里随机选出 n_shot 张支持图和 1 张查询图查询图不能和该 episode 的任何支持图重复。第三步将支持图裁剪后与对应 mask 在通道维拼接作为一个分支的输入查询图与查询 mask 作为另一个分支的监督目标。第四步模型从支持图像上学到目标类别的嵌入再在查询图上预测。这里最容易搞错的是「支持类别」和「查询类别」的关系。训练阶段支持类和查询类来自同一个类别集合——模型要学习的是「从支持图中提取目标类特征并应用到查询图」这个能力而不是学习某个固定类别的参数。因此训练时各类别不断随机组合验证时再把 fold 里的 5 个查询类单独拿出来测。理解这一点才能理解为什么 PASCAL-5i 的划分会让每个 fold 的查询类逻辑上互斥。4.2 一个可以直接跑通的 PyTorch Sampler下面这段是一个可运行的 episode 采样器骨架核心是上面的类桶查表输出每个 batch 的样本索引import random from torch.utils.data import Sampler class EpisodeSampler(Sampler): def __init__(self, class_to_images, train_classes, n_way, n_shot, episodes): self.c2i class_to_images self.train_classes train_classes # 训练阶段所有支持类 self.n_way n_way # 每个 episode 的类别数 self.n_shot n_shot # 每类支持图数量 self.episodes episodes # 总 episode 数 def __iter__(self): for _ in range(self.episodes): # 1. 随机选 n_way 个类别 ways random.sample(self.train_classes, self.n_way) episode_files [] for cls in ways: images self.c2i[cls] # 2. 每类取 n_shot 支持图 1 查询图 picked random.sample(images, self.n_shot 1) support picked[:self.n_shot] query picked[self.n_shot:] for sp in support: episode_files.append((sp, cls, support)) episode_files.append((query[0], cls, query)) yield episode_files逻辑说明外层循环控制总 episode 数内层对每个类别取 n_shot1 张图最后一张当查询图。yield 返回的是一个元组列表每个元组包含图像路径、类别编号、角色标记。Dataset 拿到这些元组后按角色读取图像并返回对应张量。这样设计的好处是采样逻辑和图像读取解耦后续想改成 2-way、增加负样本类别桶只需要改这一个 Sampler。参数说明n_way 在训练时常见取 1 或 2取值太大会让支持信息溢出模型要同时区分多个类别n_shot 常见取 1 或 5论文里的 1-shot 和 5-shot 结果必须分开报告episodes 控制训练步数一般与迭代次数对齐。如果用的是分布式训练这个 Sampler 需要重写为每张卡独立采样否则多卡会拿到完全相同的 episodebatch 意义消失。4.3 关键参数怎么调shot、分辨率与增强支持图分辨率是 few-shot 分割里最容易影响结果的参数之一。常用做法是先把图片缩放到 321×321 或 473×473再随机裁剪到训练尺寸验证阶段用整图缩放加 padding。分辨率太低时bottle、pottedplant 这类小物体的掩码很容易缩成一个像素点类别信息几乎消失。我一般先开启 dataset 的可视化打印几组 episode 看看支持图 mask 里目标像素的最小占比如果某个类目标区域不足 100 像素先考虑提升输入分辨率而不是改模型。参数常见取值对结果的影响n_shot1 / 55-shot 通常比 1-shot 高 8-12 个点 mIoU输入分辨率321 / 473 / 512分辨率太低时小物体类别直接失效增强强度翻转 缩放 颜色抖动过强增强会导致小目标掩码被裁掉ignore_index255与预处理和损失函数不一致会出现 NaN数据增强方面few-shot 任务比普通分割更依赖颜色与形状不变性。常用组合是随机水平翻转、随机缩放、颜色抖动但对 query 的增强不应改变类别的相对位置。注意SBD 的 mask 是严格对齐原图的增强时必须对图像和 mask 用同一套随机参数翻转方向、缩放尺度、裁剪区域都要一致。做法是把 mask 也当作图像送入变换用 torchvision.transforms 里的 functional 统一处理不要分别设置随机种子否则会出现 mask 和图像错位的翻车现场。增强的强度建议从弱到强很多小样本分割论文的结果差异就来自增强策略翻车后先关掉增强再对比确定基线可信后再逐步加增强。支持分支的梯度还有一个细节训练时支持图只需要输出特征不需要像素级梯度反向传播时对支持分支 detach 一次显存能省下接近三分之一结果几乎不变。5. 踩坑记录PASCAL-5i 复现中的五个常见问题5.1 现象SBD 掩码加载后类别整体错位现象训练第一轮 loss 正常但验证时 mIoU 只有个位数打印预测图发现预测的类别和真实类别总是差一个固定偏移比如真实是 car模型预测成 bus。原因SBD 的类别索引在某些实现里不是 0-20而是遵循 SBD 自身的数据集约定用 1-20 对应 PASCAL 的 1-20但下载到的版本里有的文件实际存的是 1-20、背景是 255。读取时如果直接沿用 VOC 的「0 背景」假设就会把背景类和物体类整体偏移。解决在预处理阶段用 3.2 节的 unify_mask打印一张图 mask 的 np.unique 结果确认类别集合是 0-20 加 255而不是 1-21 或者 255 之外还有 254。把 mask 统计脚本跑一遍再进入训练能避免大多数这类问题。5.2 现象mIoU 比论文高太多怀疑数据泄露现象某开发者跑完 fold0mIoU 比文献高 7 个点第一反应是模型更强仔细检查后发现支持图像列表和查询图像列表有交集。原因构建 class_to_images 时用了全部 trainval 图像没有区分 train 和 val。等于有些查询图在支持集里也出现过模型其实见过测试场景的标注结果虚高。解决class_to_images 必须按数据来源分区。训练支持图像只从 SBD train 构建验证支持图像和查询图像都从 VOC val 构建两个列表必须做一次交集校验。写一个断言len(set(support_list) set(query_list)) 0每次启动训练前执行成为固定习惯。5.3 现象mask 全部是背景loss 变成 NaN现象训练几轮后 loss 突然变成 NaN检查代码逻辑没找到问题打印查询 mask 后发现整张图只有一个类别 0。原因随机采样时选到了「目标类别有效像素极少」的图像缩小分辨率后目标区域被完全吞掉mask 退化为全背景。解决在采样后加一步过滤统计每组 episode 中每个类别 mask 的非零像素数少于阈值比如 50 像素就重新采样。另外检查损失函数是否对空目标做了保护常见做法是让 loss 在 target 全为背景时返回 0但更推荐直接过滤采样因为它不会掩盖真实边界样本的分布。5.4 现象四个 fold 之间的 mIoU 差距非常大现象fold1 的 mIoU 45fold3 只有 25看上去像某个 split 的实现 bug。原因PASCAL-5i 的四个 fold 测试类别天然不平衡。fold3 里包含 diningtable、pottedplant 这类小物体和屋内场景支持图中目标占比普遍偏低分割难度本来就大fold0 里的 aeroplane、bus 这类大物体更容易被预测准。解决报告结果时必须逐个 split 写不能只报一个平均。对比方法时固定同一套 fold 顺序先确认别人的实验用了哪几个 split再对齐评估的 episode 数量与随机种子。如果发现 fold 之间差异超过 10 个点这通常不是实现 bug而是数据偏移属于小样本分割的常态。5.5 现象支持集 15 张图一起前向显存直接溢出现象训练时 batch size 设为 1把支持图像拼接后输入显存还是爆了报 CUDA out of memory。原因支持图和查询图在同一个 batch 里一起前向支持图数量等于 n_way×n_shot加上高分辨率输入显存占用翻倍。有些实现为了简单把支持图和查询图在 batch 维拼接这会让显存占用变成普通分割的数倍。解决显存紧张时把支持图和查询图分两次前向支持图计算结果保存下来再与查询图特征做交互。或者缩小输入分辨率用 321×321 而不是 473×473 跑通实验验证结果后再调大。还有一个做法是把支持图分支的梯度截断只对支持分支 detach因为支持图像只需要输出特征不需要像素级梯度。6. 评估与验证mIoU 计算、可视化抽检与样本量统计6.1 mIoU 计算口径PASCAL-5i 的评估指标是 mIoU但计算细节写不对会直接影响结果可信度。常见口径是对每个查询类分别统计所有测试 episode 的预测掩码和真实掩码计算该类别的 IoU最后求 5 个类的平均也有实现是先算每个 episode 的 mIoU 再平均两种口径在类别样本不均衡时差异明显。建议采用「按类别汇总像素再算 IoU」的口径因为它不受每类测试图像数量影响和多数论文报告一致。import numpy as np def compute_miou(pred, gt, num_classes21, ignore_index255): ious [] for c in range(1, num_classes): p (pred c) g (gt c) inter (p g).sum() union (p | g).sum() if union 0: ious.append(inter.item() / union.item()) return np.mean(ious)逻辑说明只计算 1-20 类的 IoU背景和 ignore 不参与评估union 为 0 说明该类别在当前 episode 中未出现直接跳过。参数说明num_classes 设为 21 是因为含背景实际参与平均的是 20 个物体类这一步要和验证集协议保持一致。如果某个查询类在所有 episode 中从未被预测到mIoU 会被拉低这是正常现象不必当作代码 bug。6.2 可视化抽检与样本量统计评估之后我有一个长期保留的习惯把每个 episode 的支持图、查询图、真实掩码和预测掩码拼成一张画布保存下来跑 100 个 episode 做人工抽检。这套做法的价值在于数据划分、mask 统一、采样逻辑存在的 bug 几乎都会在可视化里暴露支持图和查询图重叠会出现同一场景的重复图像mask 错位会看到预测类别与颜色严重不符支持图的类别外观不对会导致可视化图像里出现奇怪的颜色。import cv2 import numpy as np def visualize_episode(support_imgs, query_img, pred, gt, save_path): h, w query_img.shape[:2] canvas np.zeros((h * 2, w * (1 len(support_imgs)), 3), dtypenp.uint8) for i, s_img in enumerate(support_imgs): canvas[:h, i*w:(i1)*w] s_img canvas[h:, 0:w] query_img canvas[h:, w:2*w] pred canvas[h:, 2*w:3*w] gt cv2.imwrite(save_path, canvas)这段是把所有图放到同一张画布上横向排支持图纵向排查询图、预测和真实掩码。参数说明pred 和 gt 需要先转成可视化用的 3 通道伪彩色最简单的方法是把 class id 乘以 20 作为像素值虽然颜色不标准但能看出大致轮廓保存路径按 fold 和 episode 编号组织方便回查。除了可视化还要定期运行一个类别样本量统计脚本打印每个类在 val 里有多少张图这个数字决定了评估 episode 的总数设置。从那以后我每次换数据集、改划分、调采样逻辑都强制自己先跑一轮 20 个 episode 的可视化抽检再开始训练。这套「可视化 交集校验 类别样本统计」的检查流程比任何调试器都能更快定位数据问题。PASCAL-5i 的数据规模不大但协议的细节密度一点不低希望上面的拆解和踩坑记录能帮你少走几趟弯路希望帮到你。本文还有配套的精品资源点击获取