显著性图引导的幻觉样本:小样本学习的数据增强新思路
小样本学习折腾久了你会发现最头疼的问题往往不是模型跑得太慢而是手里每类就那几十张图怎么调都像在“螺蛳壳里做道场”。最近重读《Few-shot Learning via Saliency-guided Hallucination of Samples》这篇论文触动我的是它没有把重心放在修改损失函数或设计元学习策略上而是直接回答了一个我想了很久的问题能不能在数据端“凭空”造出更多有效的样本论文给出的方案很朴素——先用一个已经训练好的分类网络把图像中真正决定类别判断的显著性区域saliency map提取出来再把这个区域移植到另一张图像里合成一批带正确标签的幻觉样本hallucinated samples。这个思路放在今天不算惊艳但它让我第一次意识到小样本场景里的数据增强不是简单翻转、裁剪就能糊弄过去的关键是要把“语义”一起搬过去。1. 小样本学习的老难题:不是模型不够强,而是数据本身太单薄1.1 数据少带来的一系列连锁问题小样本学习的设定说起来很“残忍”训练时每个新类别只给你几到几十张带标注的图测试时还要在完全没见过的新类别上做分类。很多人第一反应是“模型太小了容量不够”但实际跑过几个基准就会发现问题往往出在数据分布上。一个分类器在训练阶段见过的样本越少它对类别边界的估计就越不稳定。假设把一张 224×224 的图像展开成特征空间里的一个点本质上是在一个高维空间里做线性划分。支撑集只有 1 张或 5 张图时这个空间的“锚点”太少模型很容易找到一条在训练集上完美、但稍微挪一点点就翻车的判别面。这也是为什么小样本任务里经常见到训练准确率接近满分、测试准确率却很低的情况——过拟合不是因为你调参不够细心而是样本量根本撑不起你想要的决策边界。更麻烦的是小样本任务通常采用“预训练 微调”的范式先在数据充足的基类集合上训练一个通用特征提取器再把这个特征提取器搬到新类别上微调。基类上训得好不代表迁移后好因为新类别只有极少样本微调阶段本质上是在“高维空间中用几个点去拟合一个未知分布”几乎必然会过拟合。1.2 常规增强手段为什么“治标不治本”说到数据不足很多人会立刻想到随机翻转、随机裁剪、颜色抖动这些常规增强手段。这些方法确实有效但它们有一个共同的局限它们都是在“同一张图像本身的邻域”里做变换。你翻转一张猫的图片得到的还是一只猫你把它颜色调深一点它仍然只是一只猫。模型从这些变换里学到的是这张图在平移、旋转、颜色扰动下的不变性而不是“猫”这个类别在不同背景、不同姿态、不同组合下的泛化性。举个例子如果你手里只有一张“草地上坐着猫”的图片常规增强能造出“草地上的猫往左歪头”“草地上的猫颜色偏暗”之类的样本但永远造不出“厨房里的猫”“沙漠里的猫”。模型因此很容易学到一种偷懒的决策方式看到绿色像素就倾向判断为猫而不是真正去理解猫本身的形状纹理特征。在小样本场景里这种“背景捷径”非常致命因为测试阶段的新类别图像背景分布和训练时几乎不可能完全一致。Mixup、CutMix 这类基于样本混合的增强方法往前走了一大步它们确实引入了跨样本的信息。但它们的混合区域是纯随机的没有语义约束CutMix 把一张图的某个矩形块贴到另一张图上很可能把猫的头切掉一半、把另一张图的背景贴上另一半。对普通训练集来说这种带噪声的样本可以增强鲁棒性对样本极度稀缺的小样本任务来说这种“噪声”会进一步污染本来就不够清晰的类簇结构。所以一个自然的问题就是能不能让增强过程具备语义感知既然分类器自己其实知道“图像的哪个区域对类别判断最重要”为什么不让它来指导我们造样本这正是这篇论文的出发点。2. 核心机制拆解:显著性图引导的类间样本组合2.1 显著性图怎么算:用分类器的梯度当“免费探测器”论文的第一个关键思想是把已经训练好的基类分类器当作一个免费的显著性探测器。给定一张输入图像和一个训练好的分类器我们对模型输出的某类得分做反向传播得到该得分对输入像素的梯度。梯度的绝对值越大说明这个像素的变化对类别得分的影响越剧烈也就是该像素在分类决策中的重要性越高。用公式表示大概是S | ∂y_c / ∂I |其中 y_c 是模型对类别 c 的得分I 是输入图像。通常会对三通道的梯度取最大值或均值把结果归一化到 [0,1] 区间就得到一张和原图同尺寸的显著性图。这里有个很容易忽略的细节计算梯度时用“预测得分最高的类别”和用“真实标签对应的类别”结果差别很大。论文实际用的是分类器输出的类别得分我个人复现时也倾向于用模型预测的那个类别而非真实类别——因为我们要提取的正是“模型认为最能区分该类别的区域”而预测类别往往就是模型最有信心的判断。如果基类分类器训练得足够好两者差异不会太大但在某些样本上用真实标签反而会提取到不太稳定的区域。2.2 从显著性图到二值掩码:阈值、插值和区域搬运拿到显著性图后下一步是把连续的热力图变成二值掩码。做法通常是在归一化后的显著性图上取一个阈值 τ大于 τ 的像素置为 1其余置为 0。这样一来掩码所覆盖的区域就是模型认为“对分类最关键的像素集合”。掩码生成之后就是真正的“搬运”步骤。假设我们有两个样本source我们希望最终合成样本属于的类别和 target提供背景上下文的样本。我们把 source 的显著性区域像素直接复制到 target 图像的对应位置上合成的新样本拥有 target 的背景和上下文但关键判别区域来自 source。新样本的标签取 source 的标签。下面是我复现时用框架写的一个极简版本import torch def compute_saliency(model, img, label): # img shape: [C, H, W], 需要梯度 img img.clone().requires_grad_(True) out model(img.unsqueeze(0)) # 用预测类别得分,更接近模型真实的判别依据 pred_label out[0].argmax() score out[0, pred_label] model.zero_grad() score.backward() # 对三通道梯度取绝对值,再按通道取最大值 saliency img.grad.abs().max(dim0)[0] saliency saliency / (saliency.max() 1e-8) return saliency.detach() def hallucinate(source, target, saliency, tau0.5): # source/target: [C, H, W] mask (saliency tau).float().unsqueeze(0) # [1, H, W] # 如果涉及尺寸变化,用最近邻插值 # mask F.interpolate(mask, sizetarget.shape[1:], modenearest) new_img source * mask target * (1 - mask) return new_img阈值 τ 的选择很讲究。τ 太高搬运区域太小合成样本几乎等于 target 原图增强效果不明显τ 太低把目标区域的大块背景也搬过去了合成样本的“目标类别”会混入大量噪声。论文里的消融实验也证实了这一点后面我会专门展开。2.3 为什么“不自然的样本”反而更有效第一次看到生成的样本时我的第一反应是“这图也太怪了”一只猫的头出现在一片荒地的纹理里或者一辆车的轮廓嵌在室内场景中。正常的机器学习直觉告诉我们训练数据和测试数据应该服从同一分布这些“拼贴”出来的样本看起来严重偏离自然图像分布怎么会对分类有用关键在于小样本任务里的测试数据本身就可能很“怪”。测试集里的新类别图像背景各异如果模型在训练时只见过“自然背景下的目标”它就学不会在陌生背景下提取目标特征。幻觉样本恰恰提供了一类刻意制造的正样本目标类别语义被保留、上下文被替换模型被迫放弃依赖背景捷径转而认真学习目标类别本身的纹理和形状。这个机制可以类比成我们人类学习认字如果小时候只见过同样的教科书字体换种手写体可能就不认识了但如果老师把同一个字用各种奇怪的字体、颜色、背景展示给你你对这个字的认知反而更稳固。幻觉样本做的就是这件事——它把“类别语义”和“上下文语境”解耦逼模型抓住真正重要的判别信息。在评估阶段这个逻辑同样成立。测试时我们手里有少量支撑样本如果直接拿去算原型向量结果很容易被单张样本的偶发性背景干扰。把支撑样本也做一遍幻觉增强相当于每个类别都获得了大量“同一语义、不同背景”的样本原型向量的估计会稳定得多。3. 训练-评估全流程:从基类分类器到幻觉样本的四个阶段3.1 阶段一:在基类上训练一个“分类能力探测器”整个方法的前提是有一个对“什么是判别区域”有良好判断力的分类器。这个分类器不需要花哨常见的小规模图像分类网络结构就够用。关键是在基类集合上把它训练到充分收敛因为显著性图的质量直接取决于特征提取器是否已经学到了语义层面的知识。基类集合通常包含几十个类别每个类别样本数量充足。我在复现时会把分类器的输出层设计成和基类类别数一致的全连接层训练时就用常规的交叉熵损失没有加入特别的辅助损失。训练轮数宁多勿少因为稀疏小样本任务对特征质量很敏感一个半吊子的预训练特征提取器会让后面的显著性图完全不可用。这里有个需要注意的点预训练分类器的准确率并不是唯一指标。某些分类错误区域在梯度上可能呈现出不稳定的响应但整体上只要分类器在基类上超过一个合理的准确率阈值显著性图的质量就足够用于样本幻觉。我在实验里发现与其追求极致的分类准确率不如关注特征图本身是否具备清晰的语义边界——后者对后续合成步骤的影响更大。3.2 阶段二:批量生成幻觉样本有了训练好的分类器第二阶段就是批量构造幻觉样本。具体做法我在前面已经给出了核心函数但在完整流程里还需要解决两个工程问题怎么采样配对以及生成多少样本。配对策略上最简单的方式是随机采样两个不同类别的样本一个作为 source一个作为 target。需要尽量避免 source 和 target 同属一个类别因为同类别配对生成出来的样本和原图差异太小本质退化为一种“局部置换”增强没有跨类别语义迁移的效果。更好的做法是维护一个类别索引每次随机选一个类别作为 source再从剩余类别里随机采样 target。生成数量方面我建议按类别扩增比例的思路来设置。比如原本每个类只有 5 张支撑样本你可以为每张真实样本生成 3 到 5 个幻觉样本让支撑集扩充到 20 到 30 个。数量太少方差不够数量太多边际收益会下降而且训练时间会明显拉长。论文里的实验也显示幻觉样本数量从 0 开始增加时准确率上升很快但到某个临界点后曲线趋于平缓。3.3 阶段三:用真实样本和幻觉样本共同微调分类器生成完样本后接下来的训练阶段很有意思——不是单独在幻觉样本上训练而是把真实样本和幻觉样本混合在一起共同微调整个分类器。这样做的目的是让模型在真实分布和增强分布之间找到一个平衡点而不是过度适应合成数据。训练目标仍然是交叉熵损失标签按 source 类别设置。我习惯把幻觉样本的比例控制在 30% 到 50% 之间。比例太低模型学不到增强带来的泛化收益比例太高模型会被合成样本的“不自然外观”带偏甚至忘记真实样本的细节特征。微调时要控制训练轮数。小样本场景下的规律是微调过早模型还没吸收新类别的判别信息微调过晚模型就开始死记硬背那几张某实验里我试过直接把微调轮数翻倍结果准确率不仅没提升反而掉了好几个点——教训就是样本越少越要小心别过拟合。3.4 阶段四:评估阶段也做一次“临时增强”很多增强方法只在训练阶段使用评估时恢复原样回原样测试。但论文给我的一个重要启发是评估阶段的小样本数据增强同样有效甚至在某些设定下比训练阶段的增强收益更大。具体做法是在测试某个新类别任务时先用预训练分类器对支撑集样本计算显著性图并生成幻觉样本然后用扩充后的支撑集计算原型向量或训练一个简单的分类器最后对查询集样本做预测。由于支撑集样本量本来就只有几个扩充后的支撑集能让类别原型的估计方差大幅降低。我自己复现时在 5-way 1-shot 设定下仅这个测试期增强就能带来接近一个百分点的提升。评估阶段还有个小细节查询集样本不需要做幻觉增强因为你不能利用未知样本的真实标签去生成样本。但如果查询集样本是图片做常规的翻转、裁剪增强来投票仍然可以叠加到这个框架上。4. 实验规律:在标准基准上的增益与消融观察4.1 在常用小样本基准上的效果观察论文在标准的 5-way 1-shot 和 5-way 5-shot 设定下做了验证。从实验报告中能看到一个很典型的规律5-way 1-shot 的提升幅度明显大于 5-way 5-shot。这其实不难理解。5-way 1-shot 的支撑集每个类别只有一张图类别原型的估计方差极大幻觉增强相当于用多个合成样本来平滑这个估计收益自然非常明显。到了 5-way 5-shot每类已经有五张真实样本原型估计本身相对稳定增强带来的额外信息增益就会变小。如果把这套增强模块加到一个朴素的预训练 微调基线上1-shot 任务的准确率能提升两到三个百分点左右。这个幅度的提升在公开基准上已经相当可观因为同一时期的很多先进方法花费大量精力修改训练目标换来的也只是几个点的进步。4.2 消融实验揭示的三个规律关于阈值对结果的影响消融实验的结论很清晰阈值存在一个最优区间。阈值太低比如 0.1 到 0.2 的时候显著性区域占据图像的大部分面积合成样本和“把两张图直接叠加”几乎没区别类别语义被严重稀释阈值太高比如 0.8 以上搬运的区域太小样本多样性不足增强效果趋近于零。另一个规律是显著性图的来源层级。用网络浅层特征计算的梯度更关注边缘、颜色等局部纹理用深层特征计算的梯度更能反映语义级的物体部件信息。论文实验显示深层特征驱动的显著性图在后续增强上效果更好。这符合直觉因为深层特征已经收敛到了“类别概念”层面。还有一个关于幻觉样本数量的观察。生成样本数量太少起不到作用但数量达到一定程度后继续增加收益会边际递减。我在某次实验里看到过这种情况从每类 10 个幻觉样本增加到 30 个时准确率还在缓慢上升但增加到 50 个以上不仅训练时间翻倍准确率反而出现了微小下降。这可能是因为同类别生成的幻觉样本之间高度相关过多的合成样本会让模型反复看到相似的拼贴模式反而束缚了它的泛化能力。4.3 与各类小样本方法的兼容性这篇论文的方法本质上是一个“即插即用”的增强模块它不依赖特定的训练范式可以很自然地叠加到当时主流的几类方法上。和原型网络这一类的基于度量的方法结合时思路最顺畅先用幻觉增强扩充支撑集再用扩充后的支撑集计算类别原型最后做最近邻分类。和匹配网络这类注意力模型结合时幻觉样本可以作为额外的支撑集输入让注意力机制在更丰富的上下文中计算相似度。即便是优化型的小样本方法比如通过几步梯度更新来适配新任务的元学习范式幻觉增强同样可以作用在支撑集的任务构造阶段。由于它不改动损失函数、不改动网络结构所以接入成本很低。我当时在复现时是先把增强模块加到最简单的基线上确认收益后再把它和其它方法做组合对比。5. 复现笔记:几个容易踩的坑和可选改造方向5.1 复现最容易踩的五个坑第一个坑是显存和反向传播。计算显著性图需要对输入图像求梯度如果一不小心在批处理时把整个 batch 的梯度都打开了显存占用会成倍增加。我的做法是计算显著性图时用单样本或者极小的 mini-batch算完立刻 detach 释放梯度图后续的幻觉样本生成过程完全不需要梯度信息。第二个坑是掩码插值方式。当显著性图和原图尺寸不一致时要对掩码做缩放这时候务必用最近邻插值不要用双线性插值。双线性会把二值掩码变成带有中间灰度的模糊掩码导致搬运区域出现半透明的混合带新样本会出现明显的边界融合伪影。用最近邻插值可以保持掩码清晰样本看起来更接近真实拼贴。第三个坑是不要把随机增广叠加得过分。有些复现者喜欢在生成幻觉样本之后再叠加随机裁剪、随机颜色抖动结果增广强度过大很容易把搬运过去的显著性区域破坏掉相当于你辛苦搬过去的“语义核心”又被随机噪声洗掉了。我的经验是幻觉增强之后最多做一次轻度的随机水平翻转其他重增广留给常规训练分支。第四个坑与采样配对方式有关。如果代码里用随机 batch 采样经常会出现 source 和 target 来自同一类别的情况。这会让大量生成的幻觉样本退化成“背景微调”严重浪费计算资源。我在实现时专门写了一个类级别采样的逻辑保证每个幻觉样本都来自两个不同类别。第五个坑是 BatchNorm 统计量的漂移问题。训练好的分类器里如果有 BatchNorm 层在计算显著性图和生成样本时如果输入分布差异过大统计量可能出现漂移。实际复现中如果模型是在基类上充分训练过的这个问题不太明显但如果在微调阶段大量使用幻觉样本推荐把这部分样本单独过一个前向以稳定 BatchNorm 的统计量。5.2 如果让我动手改,我会这么做论文的原始实现是一个很好的起点但它还有不少可以继续挖掘的空间。比如显著性图的计算除了基于梯度的方式还可以用 CAM 类的类激活映射来替代。梯度方式和 CAM 方式各有优劣梯度方式更精细但需要反向传播CAM 方式速度快、实现简单但空间分辨率通常不如显著图细腻。如果你需要在大规模数据上快速批量生成幻觉样本建议用 CAM 做初筛再用梯度方式细化关键区域。另一个改造方向是把二值掩码换成软掩码。硬掩码的边界在合成样本上非常明显模型有可能学到“哪里边缘突兀哪里就是关键区域”这样的捷径。给掩码加一圈高斯过渡让搬运区域和背景之间平滑融合可以在视觉上更接近真实图像某些实验里能提高鲁棒性。如果你正在做分类之外的小样本任务比如小样本目标检测或小样本分割这个思路也完全可以迁移过去显著性图可以替换为检测框或分割掩码搬运粒度从像素级变成区域级。本质上都是“把关键语义区域从源样本搬到上下文不同的目标样本上”核心思想是一致的。结尾的几句大实话如果你只是想快速给现有小样本模型加一个增强模块我的建议是别一上来就做花哨的版本。先用最朴素的预训练分类器生成显著性图阈值设在让搬运区域大约占图像面积四分之一到三分之一的位置训练和评估阶段同时用跑出结果再决定要不要引入 CAM、软掩码这些变体。大多数情况下朴素版本已经能带来相当可观的收益真正麻烦的反而是数据配对、阈值调节这些工程细节。我在复现过程中还有一个体会测试阶段的增强比想象中更重要。很多人辛辛苦苦在训练阶段造了几百张幻觉样本却忘了评估时只用那几张原始支撑样本等于把最后的“冲刺”机会白白浪费了。先试着在测试阶段给支撑集扩充到十几张,再回头调训练阶段的生成数量你会发现性价比完全不同。