资讯动态

pascal-5i数据集小样本分割实战:fold划分、episodic采样与避坑指南

发布时间:2026/10/21 18:54:04 来源:尧图企业网站定制
简介Pascal-5i 数据集面向小样本学习方向的研究者与算法工程师基于经典 PASCAL VOC 的 20 个类别通过采样策略划分基础类别与新类别支持 5-way 1-shot 与 5-way 5-shot 场景用于评估模型在极少样本下的泛化与适应能力。资源包共 6 个文件压缩后约 151KB包含 py 读取脚本、ipynb 示例笔记、md 说明文档、txt 依赖清单及 license 等覆盖数据加载、实验演示与使用说明等环节便于快速接入原型学习、元学习或知识迁移等方法的训练与评测流程。目前已有 1444 人学习下载。借助配套脚本与示例读者可省去数据解析与划分的重复工作直接复现小样本分类实验并对照 F1 分数、one-vs-rest 混淆矩阵等评估方式分析模型表现适合作为入门与对比实验的标准化起点。1. pascal-5i数据集为什么你的小样本分割模型总在5-way 1-shot上翻车如果你正在做小样本语义分割大概率绕不开 pascal-5i 数据集。它不是什么新数据集而是把 PASCAL VOC 2012 重新切分后得到的一套标准小样本评测协议。很多人第一次跑 5-way 1-shot 时都会遇到同一个反直觉现象训练集上 mIoU 冲到 60 多换到测试的 novel 类别直接掉到 20 出头。问题往往不在模型结构而在你根本没搞清楚 pascal-5i 的 fold 划分逻辑和 episodic 采样规则。这套数据集解决的核心问题是让不同论文的小样本分割结果能在同一套类别划分和采样协议下比较。它适合两类人一是刚入门小样本分割、需要一套可复现 baseline 的工程师二是已经跑通但指标对不上、需要排查数据管线的人。下面从划分逻辑讲到落地采样再到避坑和验证全部按能直接抄作业的方式写。2. pascal-5i 的 fold 划分与 episodic 采样先把协议吃透再写模型2.1 四个 fold 到底怎么切、为什么不能自己乱切pascal-5i 的全称里那个 “5i” 指的是 5 个类别一组、共 4 个 fold 的交叉验证协议。PASCAL VOC 2012 分割任务原本有 20 个前景类加 1 个背景类pascal-5i 把这 20 个类分成 4 组每组 5 个类。每个 fold 里15 个类作为 base训练时可见5 个类作为 novel测试时才出现。四个 fold 轮流让不同的 5 类当 novel保证每个类都有机会被当作新类评测。这里第一个坑就来了很多人以为 base 和 novel 是随机切的其实不是。常见的做法是固定划分比如 fold-0 的 novel 类是 aeroplane、bicycle、bird、boat、bottlefold-1 是 bus、car、cat、chair、cow以此类推。你如果自己随机切指标和论文完全没法比。我一般会直接把划分写死在配置文件里避免每次跑实验时采样逻辑引入随机性。# pascal_5i_split.py # 固定 pascal-5i 四个 fold 的类别划分类别名对应 PASCAL VOC 2012 的 20 个前景类 PASCAL_5I_SPLITS { fold0: { novel: [aeroplane, bicycle, bird, boat, bottle], base: [bus, car, cat, chair, cow, diningtable, dog, horse, motorbike, person, pottedplant, sheep, sofa, train, tvmonitor] }, fold1: { novel: [bus, car, cat, chair, cow], base: [aeroplane, bicycle, bird, boat, bottle, diningtable, dog, horse, motorbike, person, pottedplant, sheep, sofa, train, tvmonitor] }, fold2: { novel: [diningtable, dog, horse, motorbike, person], base: [aeroplane, bicycle, bird, boat, bottle, bus, car, cat, chair, cow, pottedplant, sheep, sofa, train, tvmonitor] }, fold3: { novel: [pottedplant, sheep, sofa, train, tvmonitor], base: [aeroplane, bicycle, bird, boat, bottle, bus, car, cat, chair, cow, diningtable, dog, horse, motorbike, person] } }这段配置的关键在于novel 和 base 的并集必须恰好是全部 20 个类且两两不重叠。参数上你只需要改 fold 名来切换评测目标。逻辑说明训练阶段只允许用 base 类的标注数据novel 类的图像可以出现但不能用其分割标签测试阶段才用 novel 类的 support 集和 query 集。如果你在训练时不小心把 novel 类的 mask 也喂进去了指标会虚高这就是典型的协议违规。2.2 episodic 采样5-way 1-shot 的一个 episode 长什么样小样本分割的评测单位是 episode。一个 episode 包含一个 support set 和一个 query set。5-way 1-shot 的意思是从当前 fold 的 novel 类里随机抽 5 个类每个类给 1 张带标注的 support 图像再给若干张 query 图像通常每类若干张要求模型根据 support 的分割结果去分割 query。这里第二个坑是采样粒度。常见做法是每个 episode 的 5 个类不重复support 图像从该类中随机选一张query 图像从剩余图像中选。但有些实现会把 support 和 query 混在一起导致同一张图既当 support 又当 query指标直接失真。我一般会在 Dataset 类里显式维护一个已用图像集合确保 support 和 query 不重叠。import random from collections import defaultdict class EpisodicSampler: 按 pascal-5i 协议采样 episode保证 support 与 query 不重叠 def __init__(self, novel_classes, image_dict, n_way5, n_shot1, n_query5): self.novel_classes novel_classes # 当前 fold 的 5 个 novel 类 self.image_dict image_dict # {class_name: [img_id, ...]} self.n_way n_way self.n_shot n_shot self.n_query n_query def sample_episode(self): # 1. 从 novel 类中不放回抽 n_way 个类 selected random.sample(self.novel_classes, self.n_way) support, query [], [] for cls in selected: imgs self.image_dict[cls][:] random.shuffle(imgs) # 2. 前 n_shot 张做 support紧接着 n_query 张做 query if len(imgs) self.n_shot self.n_query: raise ValueError(f{cls} 图像不足无法构成 episode) support.append((cls, imgs[:self.n_shot])) query.append((cls, imgs[self.n_shot:self.n_shot self.n_query])) return support, query参数说明n_way 固定为 5 对应 5-wayn_shot 设 1 就是 1-shot设 5 就是 5-shotn_query 常见取 5 或 10取决于你的显存和评测稳定性。逻辑上support 和 query 的切分必须在同一个类内完成且不能跨类复用图像。如果你发现同一张图在两个 episode 里反复出现检查一下 image_dict 是否被原地修改了。2.3 训练时 base 类怎么用、novel 类怎么隔离训练阶段的标准做法是在 base 类上做 episodic 训练或者做普通的语义分割预训练再元学习。无论哪种novel 类的分割标签绝对不能进入梯度更新。常见做法是在 DataLoader 层面加一个过滤器只保留 base 类的样本。def build_base_loader(all_samples, base_classes, batch_size4): 只保留 base 类的样本用于训练novel 类样本直接丢弃 base_samples [s for s in all_samples if s[class_name] in base_classes] # 注意这里不能简单按类别过滤还要保证每张图的 mask 只包含 base 类 # 如果一张图里同时有 base 和 novel 类常见做法是把 novel 类像素置为 ignore_index return base_samples这里有个容易忽略的点PASCAL VOC 的一张图里可能同时出现 base 类和 novel 类。如果你只按图像级过滤novel 类的像素仍然会参与 loss 计算。正确做法是在生成 mask 时把 novel 类对应的像素值改成 255ignore_index让损失函数跳过这些位置。这个细节在多数论文里不会写但不做的话指标会偏高。3. 从 VOC 原始标注到 pascal-5i 可训练格式转换脚本与四个边界坑3.1 目录结构怎么摆、文件命名怎么对齐PASCAL VOC 2012 的原始结构是 JPEGImages、SegmentationClass、ImageSets/Segmentation 三件套。pascal-5i 本身不改变图像和标注文件只改变你读取时的类别映射和采样逻辑。所以转换工作的核心是建立类别名到连续 id 的映射并生成每个 fold 的 base/novel 列表。常见做法是保留原始 PNG 标注在代码里做 remap。我一般会生成一个 json 文件记录每个 fold 的类别 id 映射避免每次训练时重新算。import json import numpy as np from PIL import Image # PASCAL VOC 2012 的 21 类含背景原始 id 映射 VOC_CLASSES [ background, aeroplane, bicycle, bird, boat, bottle, bus, car, cat, chair, cow, diningtable, dog, horse, motorbike, person, pottedplant, sheep, sofa, train, tvmonitor ] def build_remap(fold_name, split_info): 为指定 fold 生成 0..N 的连续 id 映射背景固定为 0 remap {0: 0} # 背景 # base 类从 1 开始编号 for idx, cls in enumerate(split_info[base], start1): remap[VOC_CLASSES.index(cls)] idx # novel 类接着编号但训练时会被 ignore for idx, cls in enumerate(split_info[novel], startlen(split_info[base]) 1): remap[VOC_CLASSES.index(cls)] idx return remap def convert_mask(mask_path, remap, ignore_index255): 把原始 mask 按 remap 转换未映射的类别置为 ignore_index mask np.array(Image.open(mask_path)) out np.full_like(mask, ignore_index, dtypenp.uint8) for src, dst in remap.items(): out[mask src] dst return out参数说明remap 的 key 是原始 VOC idvalue 是训练时用的连续 id。ignore_index 通常设 255和多数分割框架的默认值一致。逻辑上背景必须保留为 0否则模型会把背景当普通类处理。如果你发现训练 loss 一直不降先检查 remap 里有没有漏掉某个类。3.2 四个边界坑图像级过滤、像素级过滤、类别不平衡、评测脚本第一个坑是图像级过滤不等于像素级过滤前面已经提过。第二个坑是类别不平衡base 类里 person 和 car 的像素量远大于其他类直接训练会让模型偏向大头类。常见做法是在 loss 里加类别权重或者对稀有类做过采样。第三个坑是评测脚本很多开源实现的 mIoU 计算方式不一致有的按类平均有的按像素平均。pascal-5i 的标准是按类平均且只统计 novel 类。第四个坑是 support 集的标注质量PASCAL VOC 的标注本身有噪声1-shot 时如果抽到一张标注很差的 support整个 episode 的指标都会崩。我一般会在采样时加一个简单的质量过滤比如 support 的前景像素占比低于 1% 就重新抽。def is_valid_support(mask, min_ratio0.01): support 前景像素占比过低时判为无效重新采样 fg (mask 0).sum() total mask.size return (fg / total) min_ratio这个过滤不是论文里的标准操作但在实际复现时能明显降低 episode 间的方差。参数 min_ratio 我一般设 0.01设太高会导致采样失败率上升设太低起不到过滤作用。4. 避坑与排查pascal-5i 复现时最常见的 5 个翻车现场4.1 指标虚高novel 类标签泄漏进训练现象训练时 mIoU 正常测试 novel 类时高得离谱甚至超过 base 类。原因训练时没有把 novel 类像素置为 ignore_index或者 DataLoader 里混入了 novel 类的样本。解决在 mask 转换阶段显式检查 novel 类 id 是否被置为 255并在训练循环里打印每个 batch 的 unique 值确认没有 novel 类 id 出现。4.2 指标虚低support 和 query 图像重叠现象测试指标远低于论文报告值且不同 episode 之间波动极大。原因采样时 support 和 query 从同一个图像池里抽导致同一张图既当 support 又当 query。解决在 EpisodicSampler 里维护已用图像集合或者直接在采样前把图像列表打乱后按顺序切分确保不重叠。4.3 训练不收敛remap 后类别 id 不连续现象loss 震荡或直接 NaN。原因remap 时跳过了某些 id导致类别数对不上模型输出通道数。解决打印 remap 的 value 集合确认是 0 到 N 的连续整数且 N 等于模型输出通道数减一。4.4 评测结果和论文对不上mIoU 计算方式不一致现象同样的模型和权重你的 mIoU 比论文低 5 到 10 个点。原因有的实现按像素平均有的按类平均还有的把背景也算进去了。解决统一按类平均只统计 novel 类的 5 个类背景不计入。可以在评测脚本里加一行打印每个类的 IoU方便定位是哪个类拖后腿。4.5 显存爆炸episodic 采样时 batch 内图像尺寸不一致现象训练到一半 OOM。原因PASCAL VOC 图像尺寸不统一episodic 采样时一个 batch 里混了不同尺寸的图。解决在 Dataset 里统一 resize 到固定尺寸比如 473x473 或 513x513并同步 resize mask用最近邻插值避免引入新类别。5. 用 1-shot 结果反推数据管线是否健康一个可复现的验证技巧最后一章讲一个我常用的验证方法不要一上来就训大模型先用一个极简的 prototype 分割器跑通全流程用 1-shot 的指标反推数据管线有没有问题。具体做法是对每个 episode把 support 的前景像素取平均得到一个 prototype 向量然后对 query 的每个像素算余弦相似度阈值化后得到分割结果。这个方法不需要训练纯推理几分钟就能跑完一个 fold。import torch import torch.nn.functional as F def prototype_segment(support_feat, support_mask, query_feat, threshold0.5): support_feat: [N_shot, C, H, W] support_mask: [N_shot, H, W]值为 0/1 query_feat: [C, H, W] 返回 query 的二值分割结果 # 1. 对 support 的前景像素做平均得到 prototype fg_feats [] for i in range(support_feat.shape[0]): mask support_mask[i].unsqueeze(0) # [1, H, W] fg support_feat[i] * mask # 只保留前景 fg_feats.append(fg.sum(dim(1, 2)) / (mask.sum() 1e-6)) proto torch.stack(fg_feats).mean(dim0) # [C] # 2. 对 query 每个像素算余弦相似度 C, H, W query_feat.shape q query_feat.permute(1, 2, 0).reshape(-1, C) # [H*W, C] q F.normalize(q, dim1) p F.normalize(proto.unsqueeze(0), dim1) # [1, C] sim (q p.T).reshape(H, W) # [H, W] # 3. 阈值化得到二值 mask return (sim threshold).float()参数说明threshold 一般取 0.5但实际用的时候可以扫 0.3 到 0.7看哪个值在验证集上最稳。逻辑上这个方法的指标肯定不如训练过的模型但如果你的数据管线有问题它的指标会异常低比如 novel 类 mIoU 低于 10那就说明 support 和 query 的类别对不上或者 mask 转换错了。我一般会先用这个方法跑一遍 fold-0确认 mIoU 在 30 左右再开始训正式模型。如果低于 20先回去查第 3 章的 remap 和采样逻辑别急着调模型。还有一个习惯每次换 fold 或换 backbone我都会固定随机种子跑 3 个 episode看指标的方差。如果方差超过 5 个点说明采样不稳定需要加 support 质量过滤或者增大 n_query。这个习惯帮我省了很多后悔药因为小样本分割的指标对采样极其敏感不控制方差的话你根本分不清是模型变好了还是抽到了简单 episode。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑