ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

低样本视觉识别新思路:特征收缩与幻觉合成

低样本视觉识别新思路:特征收缩与幻觉合成

第一次看到这篇论文标题时,我就被两个动词抓住了:Shrinking和Hallucinating。前者是把特征往类内均值收缩,后者是主动造出原本不存在的特征样本——一收一放,方向相反,却指向同一个目标:Low-shot Visual Recognition(低样本视觉识别)。在深度学习视觉模型已经非常成熟的今天,仍然有一类问题让人头疼:某个新类别只给你三五张图,怎么让模型认得出?这篇论文的思路很有意思:与其只盯着“数据不够就硬造数据”,不如先把手头已有的特征空间整理干净,再在这个干净空间里合成本来没有的特征。读完之后我最大的感受是,它不追求花哨的架构,而是把两个朴素想法用得非常扎实,既适合入门者理解少样本学习的痛点,也适合做工程的人直接借鉴思路。

这篇笔记会按照我的阅读习惯来写:先把问题拆清楚,再讲方法模块各自在干什么,接着用实验视角复盘论文设计,最后聊聊这套思路在工程里能怎么用、有哪些坑。中间会穿插我自己的理解和一些复现时容易踩的细节,希望能帮你省点时间。

1. 问题为什么要这么拆:少样本识别的难点在哪

1.1 低样本问题的真正瓶颈不是“样本少”这一个因素

很多人第一次接触low-shot recognition会觉得,问题不就是数据少吗,多生成几张图不就行了?但真正动手做过就会明白,数据量只是表面。更深层的矛盾在于,新类别出现时,模型从基类学到的特征空间并不一定适合新类别。

举个不太严谨但很好理解的例子:一个模型在猫狗图片上训练得很好,特征空间里“毛茸茸”“尖耳朵”这类属性被组织得清清楚楚。这时突然来一个全新类别——比如“机器狗”,支持集里只有3张图。虽然机器狗和真狗长得很像,但模型在基类训练时从没遇到过“金属质感”“关节结构”这些维度,现有的特征空间也没有为这些新维度留出余地。于是那3张图映射到特征空间后,可能就被挤进某个既有类别的领地,分类器直接判错。

这就是论文里反复强调的域偏移问题:训练基类和测试新类不是同一个分布。样本少只是放大这个问题的导火索,真正要解决的是如何让特征表示对新类别更友好。常见的元学习思路是想办法“快速适应”,度量学习思路是“用原型代表类别”,数据增强思路是“补全样本分布”。这篇论文的切入点则是另一条路:既对已有特征做主动收缩,又在收缩后的空间中合成新特征。它没有选择“学一个更好的特征提取器”,而是选择“在特征层面直接动手脚”。

1.2 为什么“收缩特征”和“幻觉特征”要同时做

先说Shrinking。如果新类的支持集样本太少,那么每个类算出的原型(类内特征均值)会很不稳定,因为几个样本的均值方差很大。而基类样本多,类内均值相对可靠。论文的一个关键观察是:基类特征虽然可分,但类内仍存在不必要的散布。这个散布会把新类样本挤得很难受。所以想法很直接:把训练阶段见过的所有特征向各自的类内均值方向收缩一点,让每个类在特征空间里更紧凑。类内紧凑了,类与类之间的边界就更干净,新类的几个样本就不会被基类的“尾部特征”干扰。

再说Hallucinating。新类只有几张图,哪怕特征空间已经整理干净,可用信息还是少。所以要在特征空间中主动生成一些看起来合理的样本。怎么生成?比较朴素的做法是拿新类原型加上随机扰动,造出一堆“虚拟特征”。这样做的好处是很稳定,完全不依赖额外的生成模型,训练开销小,也不会出现生成图像那种明显的伪影。

关键是两者要搭配。如果只做收缩,新类仍然只有几张图,模型很容易死记;如果只做幻觉,在乱糟糟的特征空间里乱造样本,合成的特征可能落在分类边界上甚至落在其他类别区域里,反而教坏分类器。所以论文的逻辑是一套组合拳:先把地图收拾清楚,再放心大胆地补充路线样本。我在自己的小实验里也验证过,单独加高斯噪声合成特征的效果非常不稳定,但搭配特征收缩之后,合成样本的有效性明显提升。这个协同效应是全文最核心的贡献。

2. 方法内部拆解:Shrinking与Hallucinating各自在做什么

2.1 先把特征搬到超球面上:统一尺度是前提

论文方法的第一步和我见过的不少度量学习工作是类似的:将骨干网络输出的特征做L2归一化,映射到一个单位超球面上。这点看起来是细节,实际影响非常大。特征不归一化的时候,不同类别的特征向量长度可能差异很大,分类头很容易依赖“向量的模长”来分类,而这个模长信息往往带有域偏差。归一化之后,所有特征都在同一尺度上,相似度可以通过余弦距离表示,收缩和扰动这些操作也会更可控。

在超球面上操作听起来抽象,但直观理解就是把所有点搬到同一个球壳上,每个类别在球面上占一片区域。Shrinking就等价于把这片区域往球面上的某个中心点收拢;Hallucinating则是在中心点附近随机走几步。如果没有归一化这一步,收缩的尺度没法统一,扰动的方差也不知道该给多大,整个方法会变得非常敏感。所以我在复现这类方法时,第一步一定是确认特征是否已经归一化,这一步错位,后面所有操作都会出问题。

2.2 Shrinking:让特征靠向类内原型

Shrinking模块的核心逻辑可以用一个很简洁的形式表达:给定一个特征向量f和它所属类别的原型μ,收缩后的特征f' = normalize(f + k * (μ - f))。这里的k是收缩系数,取值在0到1之间。k=0表示完全不收缩,f'就是原始特征;k=1表示直接缩到原型点上,类内方差变成0。实际使用时k会取一个中间值,让类内紧凑的同时保留一定的个体差异。

这个公式可以有两种操作方式。一种是对所有基类训练特征都做一遍收缩,再拿去训练分类头;另一种是对新类支持集特征做收缩,得到更稳定的新类原型。论文的价值恰恰在于把这套思想延伸到了新类推理阶段:先用基类中学到的收缩方式处理支持集,得到质量更高的原型,再做分类。这比我最初以为的“只在训练时增强数据”多了一层作用。

我的经验是,k值的大小需要结合特征空间的类内方差来定。类内方差大的数据集,k可以适当调大;类内已经很紧凑的数据集,k调太大反而会让所有特征挤成一团,丢失个体信息。更稳妥的做法是可以像论文讨论的那样,把k当作可学习的参数,让模型自己判断收缩幅度。但如果是快速验证,固定k=0.2到0.3往往就有效果。

2.3 Hallucinating:给新类补充训练样本

Hallucinating模块的目标非常直白:给定新类的一个或者几个支持样本,合成更多的训练特征。最简单的实现是假设特征围绕原型服从高斯分布,于是可以直接写h = μ_c + ε,其中ε是均值为0的高斯噪声。噪声的方差σ²需要根据特征空间的类内标准差来估计,不能拍脑袋定。一个常见的替代方法是先收集基类每个类别的类内方差,再把方差统计量迁移到新类上,这个思路在很多同样讨论特征幻觉的工作里也出现过。

真正值得注意的问题是:合成多少样本,以及怎么控制噪声的幅度。合成太少没效果,合成太多会让分类器过度信任某些虚假区域,反而损害泛化能力。我在实际实验中发现,对每个新类合成几十个特征样本就足够,翻到几百个并不会带来更大提升,甚至在某些数据集上会有轻微下降。噪声幅度方面,我习惯先让合成特征和原始支持特征保持在超球面上距离比较近的范围,再逐步放宽,避免一开始就把训练数据搞得过于发散。

另外,论文讨论的幻觉不是无脑加噪声。它把“特征空间中的语义方向”也考虑进去,尝试让合成特征落在有意义的区域上。这一点我觉得值得做笔记——同样是合成,随便加点高斯噪声和加在有语义的方向上,最终分类器的边界质量完全不同。读到这里时我曾经想过,能不能直接用生成对抗网络来造特征?可以,但成本高、训练不稳定。用随机特征幻觉的好处是快、稳、可复现,代价是特征分布过于理想化。

2.4 两模块如何协同:先收缩、再扩展,推理阶段有取舍

整体流程用一句话概括:先用Shrinking把已有的特征空间整理得更紧凑,再在整理好的空间里用Hallucinating生成新类样本,最后用增强后的支持集训练一个简单的分类器。Shrinking负责把路标摆正,Hallucinating负责多造几条练习路径,两者面向的是不同瓶颈:一个管判别性,一个管多样性。

需要特别强调的是推理阶段的取舍。Shrinking在训练和推理阶段都可以使用,因为它的作用对象是“已经存在的特征”;Hallucinating则只用于训练阶段扩充数据,推理时不需要生成任何虚拟样本,分类器直接对真实测试特征打分。搞清楚这个区别很重要,不少人误以为这类方法是“生成一堆假样本一起来分类”,其实最终使用的分类器是普通的线性分类头,幻觉只是训练策略。

如果你准备自己动手复现,我建议先把流程分成三段来理解:第一段训练骨干网络和基类分类头,得到可用的特征提取器;第二段冻结特征提取器,对基类和新类的特征做Shrinking处理;第三段用Hallucinating补充新类训练数据,训练最终的分类器。这样模块之间解耦清晰,调试起来也更容易定位问题。

3. 实验设计与评估视角:怎么读才能真正看出门道

3.1 数据协议先分清:standard low-shot 和 generalized low-shot 差别很大

读论文实验部分,最忌讳上来就看准确率数字。这类少样本论文通常会在两种不同的评估协议下测试,含义完全不同。第一种是standard low-shot classification,也就是经典的few-shot分类:测试时只需要在若干个新类内做分类,每个新类给1个或5个支持样本,评估时也只测这些新类。第二种是generalized low-shot classification:模型不仅要对新类分类,还要能识别所有基类和新类;类别总量更大,分布更不平衡,难度也高不少。

这两种协议下,方法的表现趋势经常不一致。有的方法在standard协议下很猛,到了generalized场景就崩,因为模型把所有注意力都用在了区分少数新类上,忘了自己原本还会的基类。论文之所以有意义,往往在于它能在更接近实际应用的generalized场景下保持稳定。读实验部分时,我习惯先把数据集和协议列一个对照表:数据集是什么,标准评估还是广义评估,每个类几个shot,骨干网络是什么。如果一篇论文连这些问题都交代不清楚,后面的数字再好看也不可信。

少样本领域常见的公开基准,想数还是能数出几个,比如小规模的MiniImageNet或CUB这类数据集,5-way 1-shot和5-way 5-shot是标配。一般论文会报告多次随机采样的平均准确率和95%置信区间。只报一个点估计不报置信区间,误差范围可能非常大,尤其是1-shot场景,几个样本的随机性足以让结果波动好几个点。

3.2 准确率之外还要看什么

精度是最直接的指标,但不能只看它。一个方法能不能落地,还要看它对支持集选择的稳定性。少样本实验通常要跑很多轮,每轮重新随机采样支持集。方差大的方法即使平均精度稍微高一点,也不一定可靠。读论文时我会额外留意:它的实验做了多少次随机采样,置信区间是多宽,和基线方法的区间是否重叠。置信区间重叠的话,几点的提升在统计上未必显著。

另一个容易被忽略的点是骨干网络的选择。同样的改进,在一个很弱的主干上能提升10个点,在一个很强的预训练主干上可能只剩1个点。论文如果使用了大规模预训练模型,提升空间会被压缩,方法本身的普适性也要打个问号。读的时候要问一句:这个改进是依赖特定主干的,还是换到普通骨干也有稳定收益?

消融实验也是必看部分。好的消融不是简单把模块摘掉看看掉几个点,而是能告诉你每个设计选择对什么环节起作用。比如只加Shrinking时,standard和generalized场景下分别掉多少;只加Hallucinating时,1-shot比5-shot提升更明显还是相反。从这些信息里你能判断方法在新类增量极端少时的适应力。

3.3 消融实验要盯着哪几个问题

我读这篇文章的消融时会特别关注三个问题。第一个问题是:Shrinking和Hallucinating的收益是否正交?如果两者叠加的收益接近各自收益之和,说明它们解决的是不同瓶颈,组合合理;如果叠加后收益小于其中某一个单独贡献,说明有冗余,那就要重新考虑设计。第二个问题:收缩系数k的敏感性。一个方法如果对超参数极其敏感,那在换到新数据集时大概率要重新调很久,工程价值会打折扣。论文如果展示了k在一定范围内都能保持稳定,那说明这个机制是鲁棒的。第三个问题:在归一化空间里操作,和在原始欧氏空间里操作,差距有多大?这个对比能揭示超球面假设是否真的必要,也能帮你判断这套思路能不能迁移到非归一化特征上。

看消融实验还有个实用技巧:把消融结果和2.4节提到的方法流程对照着看。哪个模块在哪个阶段起作用,消融表都能告诉你。我通常会把消融结果抄到本子上,在旁边批注“如果我去掉这个,我的场景还能不能受益”,这样做笔记才是自己的。

4. 从论文到工程实践:这套思路能迁移到哪些场景

4.1 特征收缩最适合的场景:类内变量大的小众分类

Shrinking这个思想不局限于少样本。凡是遇到“类内样本看起来差距很大”的分类任务,都可以借鉴:模型把每个类在特征空间里摊得很开,边界互相重叠,分类结果不稳定。比如在一些细粒度分类里,同一种鸟在不同姿态、不同光线下特征差异巨大,类间边界非常模糊。如果先按类内原型对训练特征做一次收缩,分类器更容易学到“核心特征”,而不是被姿态和光照带来的边缘特征带偏。

我在自己的模拟实验里也尝试过类似的思路:在没有做任何额外数据增强的情况下,只对训练集特征做轻微收缩,某些类内方差大的类别分类精度就能提升一两个点。原因是收缩相当于给特征加了一个隐式的类内正则,它削弱了极端样本对分类边界的影响。当然,过分收缩会让特征退化为中心点集合,损失个体差异性,所以收缩系数要控制在一个比较小的范围。

4.2 特征幻觉最常见的落地姿势:新类冷启动

聊到工程落地,特征幻觉最对口的场景就是冷启动。很多实际系统会遇到“模型已经上线跑得好好的,突然要加一类新东西,只给了三五张样例图片”的情况。传统做法是把新类图片加入训练集重新微调,但基类数据量大,微调成本高;而且新类样本太少,容易把新分类头训偏。用特征幻觉的思路,可以保留原有特征提取器不动,只针对新类样本做特征扩充,再在这个轻量分类头上做增量学习。

这个方案对系统架构的侵入非常小:不需要重新训练整个视觉模型,只加了一个小分类头;不需要额外的图像生成服务,只做向量层面的运算;上线后如果新类样本逐步积累,还能替换掉最初合成的特征,平滑过渡。我在实践中比较推荐先把支持集特征做Shrinking得到更稳定的原型,然后以原型为中心合成少量特征,最后用逻辑回归这类简单分类器完成增量识别。整个流程在CPU上也能跑得很轻快,适合作为线上兜底方案。

4.3 两种机制的适用范围与风险

任何方法都有代价。特征收缩的本质假设是类内特征近似单峰分布,也就是每个类只有一个聚集中心。如果某个类天然是多模态分布,比如“手机”这个类包含各种品牌、各种外观,一个原型根本代表不了整个类,强行收缩反而会把各个模态的边缘信息抹掉。遇到这种类别,按子类做收缩会更好,但这已经超出了原始方法的范畴。

特征幻觉的风险在于,它生成的样本分布只能覆盖原型附近的区域,无法凭空产生原型之外的信息。新类支持集只有一张图时,原型本身就不稳定,再加噪声只能在这个“不准的中心”附近乱转。这时候更靠谱的做法是融入外部知识或者利用基类中相近类别的先验信息,而不是单纯依赖合成。读这篇论文时,要时刻记住它是“基于特征分布假设”的方法,成熟的工程系统不可能只用这一招,但它完全可以作为整体方案里一个稳定且轻量的环节。

5. 阅读记录与复现心得

5.1 论文里最值得玩味的三个设计选择

第一个是“不训生成模型,直接加噪声”。读到这里我一开始是怀疑的,因为图像域里直接加噪声几乎不会产生好结果,为什么特征域就行?后来想明白,特征是高度语义化的表示,骨干网络已经帮我们过滤掉了大量像素级噪声,在特征空间里加适度扰动,相当于在语义近邻之间插值。这比在像素空间里合成图像要安全得多。

第二个是“收缩之后再来扩展”。这种先做减法再做加法的顺序很有启发性。如果先扩展后收缩,合成样本很可能被后一步收缩拉到一起,相当于白做;如果只扩展不收缩,合成样本又会被杂乱的类内分布带偏。先减后加,顺序本身就包含了很深的对特征分布的判断。

第三个是模块之间高度解耦。它没有把Shrinking和Hallucinating绑在某个复杂架构里,而是像两个独立积木,可以单独使用,可以替换实现,甚至可以只取其中一个迁移到其他任务。这种设计让方法的可复用性大大提升,并不是所有论文都考虑到了这一点。

5.2 最容易误读的三个点

误读一:“Shrinking就是正则化”。正则化通常作用于损失函数或网络权重,鼓励某种全局性质;Shrinking是对特征表示本身做主动重映射,把特征向量向类原型方向移动,本质上是在改变优化目标的数据分布。虽然两者都有“限制模型自由度”的效果,但作用层级不同,不能混为一谈。

误读二:“Hallucinating就是数据增强”。传统数据增强在图像空间做旋转、裁剪、颜色扰动,保留语义的同时改变几何外观。特征幻觉则完全跳过图像,直接在特征向量上生成数据。它生成的样本不是“原图的另一种形态”,而是“类内特征的一种模拟”。因此它没有传统数据增强那种视觉可解释性,也不需要关心生成样本是否像真图。

误读三:“方法只对1-shot有效”。从设计动机来看,Shrinking对5-shot同样有用,因为再多的支持样本也仍然存在噪声和域偏移;Hallucinating的收益在5-shot下会变小,因为真实样本多了,合成的边际价值下降,但不代表模块可以删掉。把两者分开评估,才能理解各自的适用区间。

5.3 复现时容易踩的坑

我在自己写代码复现时踩过不少坑,挑几个最值得说的。

第一个坑:忘了在收缩之后重新归一化。特征从单位超球面被收缩到原型方向后,向量长度会发生变化,如果后面还要计算余弦相似度,必须再做一次L2归一化。这个细节容易漏,漏掉之后精度会掉很多。

第二个坑:对测试数据也做了收缩或幻觉。推理时要记住,Shrinking只能用在支持集或者训练阶段,Hallucinating绝对不能用在测试集上。如果测试样本也被收缩到某个原型附近,等于人为把所有类别都“挤压”进了相似区域,分类器反而分不清。这是数据泄漏的一种隐性形式。

第三个坑:支持集的均值原型计算不够稳。当每个类只有1到2个样本时,直接用特征算均值,噪声很大。更稳妥的方式是结合基类中的类内方差信息,对原型做一次平滑处理,或者先对支持集特征做收缩再算均值,我试下来效果都优于直接用原始特征均值。

第四个坑:噪声方差设成全局固定值。不同类别的类内方差差别很大,全局固定值会导致有的类合成样本太发散,有的类太集中。如果不想逐个类估计方差,至少可以按基类平均方差来定,再预留一个缩放系数调参空间。

写在最后的一点个人感受

整篇读下来,我最大的收获不是那两个模块本身,而是作者对“特征空间”的态度:特征不是看一眼就完的产出物,它是可以继续加工的中间表示。这个观念让我后续处理很多问题时都多了一个选项——遇到数据不够,不只是想着换网络、加数据、调损失,而是先想想能不能在特征层面做点文章。Shrinking和Hallucinating只是两个具体工具,但这种“对特征做工程”的思路,价值比方法本身更持久。如果你也正在做增量学习、新类识别,或者被少样本问题反复折磨,我建议你花一个下午把这篇论文的流程图用自己的话重新画一遍,再在代码里跑个小实验验证一下收缩系数的影响。走一遍之后,你对低样本视觉识别的理解会扎实很多。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进