小样本学习这几年在图像领域的热度一直没下去过,朋友圈里聊 Few-shot Learning 的人越来越多,但从论文到工程落地之间的距离,远比大多数人想象的大。这个方向解决的不是普通分类问题,而是当某个新类别只出现几张甚至一张标注图时,模型还能不能快速识别。图像分类、目标检测、工业视觉、医疗辅助诊断等场景里,新类别永远在出现,标注成本永远不够,所以这几年围绕小样本的研究越来越像是“现实世界识别的最后一块拼图”。这篇内容既聊研究现状,也聊我实际踩过的坑,适合刚接触小样本学习的同学,也适合已经在跑普通分类任务、打算往这个方向转的工程师。读完你会理解方法为什么在换代、评测为什么重要、以及一个符合当前现状的可落地基线到底长什么样。
1. 图像小样本学习到底在解决什么问题
1.1 小样本问题的本质
先把问题说清楚。小样本学习在图像领域最常见的评测形式是 N-way K-shot,也就是每次从新类别集合里随机抽 N 个类别,每个类别只给 K 张标记图像作为支持集,再从这些类别中抽出未标记的查询集去验证识别结果。5-way 5-shot 的意思就是每次随机抽 5 个类别,每类只提供 5 张标注图,模型要在这种几乎“信息饥饿”的条件下把查询集的图正确分到 5 个类里。模型在训练阶段见过的类别集合,和测试阶段的新类别集合必须完全不重叠,这一点非常关键,直接决定了方法和普通图像分类有本质区别。
普通分类假设测试类别在训练中出现过,模型只需要学习一个从图像到固定类别编号的映射;小样本则假设类别编号可以不断扩展,模型必须学会“认识新的东西”而不是“记住旧的东西”。所以小样本学习的本质可以归结为两件事:一是从少量数据中估计类别分布,二是利用训练阶段积累的先验知识完成快速归纳。为什么难?因为数据量太小,模型容易把支持集背下来;因为类别全新,直接套用旧分类器无效;因为视觉信息高维,少量样本根本不足以覆盖类内变化。一只狗可以有无数种姿态、角度和背景,给 1 张图让模型理解整个类别,本质上是在做极端条件下的分布推断。
1.2 图像任务带来的额外难度
图像和文本、表格数据在小样本场景下的难点很不一样。文本里一个词往往带着明确的语义,图像则是一个几千甚至几万维的像素矩阵。同样是一只鸟,不同姿态、光照、遮挡情况下的像素差异,可能比不同鸟之间的差异还要大。这意味着支持集中如果只给 1 张图,模型很难判断哪一部分是判别性特征,哪一部分只是拍摄条件造成的噪声。这也是小样本图像研究长期绕不开的一个核心矛盾:特征必须同时具备“类内不变性”和“类间区分性”,但少量样本不足以支撑模型学到这两点。
另一个问题是图像数据增强并不总是安全。旋转一张带文字的图,文字方向变了,语义也可能变了;翻转一张不对称的物体图,可能把“朝左”变成“朝右”;随机裁剪可能直接切掉主体,剩下背景。所以在图像小样本训练里,增强操作的选择要比普通分类谨慎得多。这也是为什么研究者宁愿把精力放在更好的预训练特征上,而不是盲目堆增强——图像里的“无损增强”其实很少,大多数增强都在改变像素分布,只是有些改变对语义的影响小,有些影响大。
1.3 三条主流技术路线对比
回到主流技术路线,业内大致分成三类。数据层面的方法想尽办法扩充样本,传统图像变换、插值合成、生成模型合成样本都属于这一类。模型层面的方法关注网络结构和嵌入空间,例如把图像映射到低维空间,让同类近、异类远,或者用自注意力结构建模支持集和查询集之间的关系。算法层面的方法直接改变训练方式,这是元学习的主战场,典型思路是把训练过程本身当作学习对象,网络学习的是“如何快速适应新类别”,而不仅仅是“如何分类当前数据”。
| 技术路线 | 典型思想 | 优点 | 缺点 |
|---|---|---|---|
| 数据层 | 扩充或合成样本 | 通用、易实现 | 合成样本可能失真,增强可能改变语义 |
| 模型层 | 设计更好的特征空间 | 收益稳定、可解释 | 对backbone质量依赖较高 |
| 算法层 | 元学习、任务级训练 | 面向新类别泛化 | 训练复杂、算力开销大 |
三者的选择没有绝对答案。我的经验是:数据层最便宜,先试;模型层收益最稳定,跟随主流;算法层最复杂,但在标准基准上不一定比简单基线高。很多论文看着强,是因为同时动了数据、模型、算法三块,工程上复现时很难拆分贡献到底来自哪里。
2. 方法演进:从手工度量到元学习再到预训练范式
2.1 第一代:基于距离度量的思路
最早一批方法把注意力集中在距离度量上。思路很直接:不再直接输出类别概率,而是让网络学习一个度量空间,然后用最近邻或者距离排序来决定查询样本属于哪一个新类别。具体实现里有几种代表性做法:双分支结构比较两张图是否来自同一类;每类支持样本特征取平均得到“原型”,查询样本归到最近的原型;也有方法把支持样本和查询样本的特征拼接起来,再用一个小网络输出关系分数。
这类方法到今天仍然是强基线。原因是它简单、稳定、不需要复杂的训练调度,而且特征空间本身的可解释性很强。但难点在于嵌入空间的质量。如果用普通分类目标训练特征提取网络,特征虽然能区分基类,却不一定能区分新类,因为新类在训练时从未出现过。所以后续研究者强调要在一个模拟测试结构的“任务”上训练,让模型在大量小任务中见过足够多的“类别切换”,这种思想直接影响了后面所有方法。
2.2 第二代:元学习带来的“会学习”范式
元学习的核心是“学会学习”。它不是学习一个固定的分类器,而是学习一个能够快速适应新任务的学习过程。大致有几条流派。基于参数初始化的思路,在大量模拟任务上学习一组好的起始参数,新任务到来时只需要几步梯度更新就能收敛,本质上是在学一个“容易被微调”的初始化;基于优化器学习的思路,学习一个梯度更新规则,替代人工设计的优化器;基于记忆的思路,把训练中看到的任务经验存到外部记忆中,测试时读取相关记忆来完成推断。
元学习的优势在于任务层面的泛化,它对“类别切换”这件事更加敏感。但实践中的观察是,元学习对数据分布非常敏感,当训练基类和测试新类分布一致时表现不错,一旦跨域,优势就逐步消失甚至变成劣势。另一个问题是元训练过程需要大量算力,基于初始化的方法如果用了较深的骨干网络,二阶梯度和计算图开销会明显增加。前几年这方向非常热闹,最近热度有所下降,不是没有道理。
2.3 第三代:大规模预训练与小样本适配
最近两三年,格局发生了明显变化。大规模图像预训练和图文对齐预训练把视觉表征的水平推高了一大截。受此影响,小样本研究的重心从“设计精巧算法”转向“如何利用已有强特征”。现在很多性能靠前的方法并不复杂:把图像输入预训练模型,得到高质量特征,然后只训练一个轻量分类头、一个低秩适配结构,或者直接在特征空间里做最近邻。研究现状里讨论最多的问题,已经从“怎么设计元学习算法”变成了“预训练特征在小样本场景下到底够不够用”。
这产生一个看似反直觉但很重要的结论:在小样本场景里,特征本身的质量比算法的复杂程度更关键。我踩过类似的坑——在一个内部项目里,我用复杂的元学习方案反复调参,折腾了两周,最后换成预训练特征加线性分类头,效果反而更好,代码量还少了一大截。所以如果你准备进入这个方向,先把当下的预训练基线做扎实,再谈改进,不要一上来就追求算法复杂度。
2.4 研究现状的核心关键词
如果给现在的图像小样本研究做关键词画像,排在前面的是跨域、测试时自适应、参数高效微调,以及小样本检测与分割。跨域小样本关注的是基类与新类分布不一致的情况,比如用自然图像做基类,在医学影像或卫星影像上测试。测试时自适应则利用查询集中的无标注样本,在测试阶段动态调整模型,这更像实际部署中的使用方式:支持集是用户提供的几张样例,查询集是现场不断捞回的图像。参数高效微调研究的是一个预训练模型如何用最少的额外参数去适应各种新类别。
另一个明显趋势是从分类任务走向更复杂的视觉任务。目标检测、实例分割、姿态估计都在引入小样本设定,因为很多实际场景真正缺标注的就是像素级任务。检测里一个小目标只有几十个像素,分割里一个类别只有几个掩膜样本,这些问题比分类更棘手,但离落地更近。
3. 评测体系与数据集:为什么论文里的分数很难直接迁移
3.1 N-way K-shot 到底怎么测
评测协议是研究现状里最容易被忽略但非常重要的一环,也是很多论文分数虚高的“操作空间”。一个标准的 N-way K-shot 评测,是从一个已经预留好的新类别集合中随机抽取 N 个类别,每类给 K 个标注样本构成支持集,再从这个 N 个类别中抽取一定数量的查询样本,计算查询样本的分类准确率。典型设置是 5-way 1-shot、5-way 5-shot。1-shot 时每类只有一张标注图,支持集总共只有 5 张图,任务难度极高。
评测结果通常要报告多轮随机抽样的平均准确率和 95% 置信区间。为什么强调多轮随机抽样?因为单次抽样的运气成分很大。同样是 5-way 5-shot,换一组支持集,准确率可能相差十几个点。如果一个实验只跑一个 episode 就下结论,基本等于靠抛硬币定方向。所以做对比实验时,至少要在不同的随机种子下重复多次,报告平均值和波动范围,才具备说服力。
3.2 数据划分与泄漏陷阱
规范的做法是把所有有标签数据按类别划分为三个互不相交的集合:基类集合用于训练,验证类集合用于调超参和选模型,新类集合用于最终测试。其中新类与基类、验证类在类别上不能有任何重叠。很多成绩“漂亮”但无法复现的实验,问题往往就出在泄漏上。
常见泄漏有三种。第一种是类别重叠,训练数据里混入了新类样本。第二种是数据增强泄漏,增强后的图像和原图同时出现在训练和测试里,导致测试特征异常相似。第三种是统计信息泄漏,用整个数据集计算归一化统计量,新类图像的统计信息被间接喂给了模型。还有一种更隐蔽的问题:虽然不更新参数,但研究者反复看新类的测试结果来调整超参数,这已经相当于用测试集来引导超参数选择。我在做小样本基线时,会把数据划分代码单独封装,让任何人、任何脚本都不能直接访问新类样本,从源头堵住泄漏。数据增强泄漏值得单独说。如果你在训练集上先做增强再划分,增强样本可能通过随机裁剪落在其他类别的范围内;如果你在 episode 内部动态增强支持集和查询集,查询集和支持集可能来自同一张原图的不同增强版本,特征高度相似,得分会虚高。测试时应该尽量不使用增强,至少不要使用训练时同一优先级的数据扩增。
3.3 从论文指标到实际场景的落差
读论文时看到 60% 甚至 70% 的 5-way 1-shot 准确率,有人会以为部署后也有类似表现。实际不是。基准评测的 episode 是类别均衡、背景干净、任务结构明确的;真实场景中类别数量、背景干扰、领域差异全部不可控。我在一个模拟项目里做过对比,5-way 5-shot 在公开基准上能接近 78%,但换到现场数据通常只有一半左右。这不是模型不行,而是评测条件变了。
所以解读论文指标时,一定要看清三件事。第一,它用的数据集和骨干网络是什么,换一个更强的骨干可能直接涨 10 个点。第二,它报告的准确率是在哪种 episode 设置下算的,训练和测试的 shot 不一致会让结果完全不可比。第三,它是否使用了额外的无标签数据或预训练数据,这决定了指标的天花板。这几个细节决定指标能不能迁移,也是我判断一篇论文是否值得复现的优先检查清单。
4. 从零搭建小样本图像分类实验:一份可复制的基线方案
4.1 实验目标与软硬件准备
如果要搭建一个可落地的基线,原型方法是最合适的选择。它的训练稳定、原理清晰、超参数不多,而且表现不会比很多复杂方法差。实验目标定为:在公开小规模图像分类数据集上,实现 5-way 1-shot 和 5-way 5-shot 的原型分类基线,跑通完整评测流程。
软硬件不用太夸张。小样本实验常用的公开图像数据集,规模也就是数万到数十万张图片,一张中等显存显卡足够。关键不是卡有多好,而是把类别划分和采样逻辑做对。骨干网络建议选择参数量较小的残差类网络,原因有三个:训练快、部署容易、作为基线足够可信。等验证了思路,再换成更大的预训练模型去刷点。不要一上来就用超大模型,否则后面很难判断性能提升到底来自算法还是来自参数量的增加。
4.2 核心代码结构:Episode 采样与原型计算
Episode 是理解小样本代码的第一个门槛。普通分类任务用 DataLoader 随机抽一批样本,小样本任务则是先随机选择 N 个类别,再在每类里抽取 K 个支持样本和 Q 个查询样本,组成一个“任务”。核心采样逻辑大致如下:
def sample_episode(label_to_indices, n_way, k_shot, q_query): # 从所有类别中随机抽 n_way 个类别,作为当前任务的新类 classes = random.sample(list(label_to_indices.keys()), n_way) support_x, support_y, query_x, query_y = [], [], [], [] for class_id in classes: # 从该类别中抽取 k_shot + q_query 个样本 indices = random.sample(label_to_indices[class_id], k_shot + q_query) support_x += indices[:k_shot] support_y += [class_id] * k_shot query_x += indices[k_shot:] query_y += [class_id] * q_query # 返回当前 episode 的支持集和查询集 return support_x, support_y, query_x, query_y这里有一个新手很容易犯的错误:支持集和查询集的标签必须重映射为 0 到 N-1,而不是保留原始类别编号。否则后面算原型时循环索引会乱,mask 也会错。原型计算和距离分类是核心步骤:
def compute_prototypes(support_features, support_labels, n_way): # 对每个类别,把支持样本的特征均值作为原型 prototypes = [] for c in range(n_way): cls_features = support_features[support_labels == c] prototypes.append(mean(cls_features, axis=0)) return stack_all(prototypes, axis=0) def classify_with_prototypes(query_features, prototypes): # 计算查询特征与每个原型的负欧氏距离作为 logits logits = -euclidean_distance(query_features, prototypes) return logits原型方法的核心假设是:每类样本存在一个具有代表性的中心点,少量支持样本特征的平均值足以逼近这个中心。损失函数通常用查询集上的交叉熵,梯度会同时更新特征提取网络,让嵌入空间更适应当前任务结构。简单说就是:先用支持集算原型,再用原型去预测查询集,最后拿预测结果和真实标签算 loss,反向传播更新网络。
4.3 训练与评测参数建议
参数上,一个比较稳的起点是训练 20000 个 episode,每 500 或 1000 个 episode 在验证类集合上评测一次。学习率不要贪大,常规优化器下从 1e-3 开始,如果 loss 震荡就降到 1e-4。现在很多做元学习的项目还会用更低的学习率,因为训练过程本身对超参数更敏感。
还有一个关键点在很多教科书里不会写:小批量 BatchNorm 在 episode 训练中很不稳定。支持集只有 5 个类别、每类 5 张图时,一个 episode 可能只有 25 张图,BN 的均值和方差估算噪声极大,训练和推理的行为会很不一样。解决办法是”:把关键卷积层换成组归一化,或者在测试阶段固定 BN 统计量。否则你会发现训练 loss 在降,但验证准确率忽高忽低,怎么看都像是随机噪声。
数据增强方面,随机裁剪、翻转可以用,但不要用那种彻底改变像素统计的强增强。小样本训练时模型本来的有效数据就少,强增强如果破坏了类别主体,等同于往训练里加噪声标签。我用过一个简单的判断标准:增强后的图像,人眼是否还能确定类别。如果人类都认不出来,模型更不可能从里面学到有效信号。
4.4 端到端训练流程与评测报告
完整训练流程可以按以下步骤走。第一步,按类别把数据集划分为基类、验证类、新类三个集合,确保类别不重叠。第二步,构建特征提取网络,可以直接加载预训练权重,但要注意预训练过程不能接触到新类样本。第三步,进入主循环:采样一个 episode,提取支持集和查询集特征,计算原型,用查询集算交叉熵 loss,反向传播更新参数。第四步,周期性在验证类集合上做同样的 episode 评测,记录表现最好的模型参数。第五步,训练结束后,在新类集合上重新采样大量 episode,计算平均准确率和 95% 置信区间。
评测时建议至少用 5 个不同的随机种子重复完整实验。小样本任务对随机性非常敏感,同一个模型换个 seed,结果可能差 3 到 5 个点。内部做对比实验时,我会把 seed 固定成确定性参数,保证算法差异不被随机波动淹没。报告结果时也尽量不要只报一个最好成绩,要报平均值和方差,否则在小样本这个方向里毫无意义。
5. 真实踩坑记录:不收敛、过拟合与跨域崩盘
5.1 训练 loss 降不下去的排查清单
小样本训练最让人血压升高的时刻,就是跑了半天 loss 纹丝不动。遇到这种情况,我的排查顺序非常固定。先检查标签重映射,确认支持集标签是 0 到 N-1,而不是原始的类别编号;再检查特征归一化,欧氏距离对特征尺度非常敏感,如果特征没有归一化,距离会被个别高维值主导,loss 很容易卡住。第三看学习率,学习率太高会导致 loss 震荡或者爆炸,太低则收敛极慢。
还有一个最容易忽视的问题:Backbone 里的 BatchNorm。在大小的 episode 上,BN 统计量本身就是错的,必须换成组归一化或者在训练时冻结 running stats。这种问题通常不会报错,只会表现为 loss 不降、验证分数乱跳,非常浪费调参时间。最后可以做一个简单的可视化:把采样到的图像和标签打印出来,人工看一眼当前 episode 的类别是否合理。有时候低级错误只要肉眼扫一眼日志就能发现,比看十行数字有效得多。
5.2 验证集分数高,新类测试崩了
这种情况很像普通机器学习里的过拟合,但在小样本里还有几个特殊原因。最典型的是域漂移:验证类和新类看起来都是图像,但内容差异大,模型在验证类上调出的超参数在新类上不适用。解决办法是尽量选择与新类分布接近的代理验证集,如果做不到,就要降低超参数搜索的激进程度。第二种常见原因是训练和测试的 episode 设置不一致,比如训练时用 5-way 15-shot,测试时却用 5-way 1-shot,模型没见过这么少的支持样本,自然很难适应。
第三种情况比较隐蔽:支持集和查询集在评测时来自同一批数据,特征高度相似,导致测试分虚高。真实场景里支持集可能是用户上传的几张照片,查询集是监控视角的抓拍,分布差异远大于基准评测。处理办法是尽量在评测中做跨批次、跨场景采样,哪怕只是按图像拍摄时间切分,也比完全随机划分可信得多。这也是我强调评测协议要贴近真实使用方式的原因。如果协议本身就不像实际场景,那么优化协议就是在自欺欺人。
5.3 跨域小样本的工程对策
跨域是我这几年见过最多的小样本“翻车现场”。模型在普通图像上预训练得再好,到了医学影像或卫星影像上都会出现明显的特征漂移,导致算法假设失效。如果任务里允许使用目标域的未标注数据,可以先用无监督方式估计统计量,做特征归一化;或者用聚类给无标注数据打伪标签,作为辅助支持集,这会在不增加标注成本的情况下扩大有效信息。如果不允许使用额外数据,就尽量少调整预训练参数,只训练一个线性分类头,这是最保守但通常最稳定的选择。
另一个反直觉的经验是:跨域小样本里,复杂算法不一定比简单算法好。复杂算法通常需要更多假设,比如任务之间分布一致、支持集能够代表类别分布等,而这些假设在跨域时更容易失效。简单算法反而因为假设少,展现出更强的鲁棒性。所以做跨域项目时,我一般会把线性分类和最近邻作为基准,先看简单方法能到什么程度,再决定是否值得增加复杂度。很多时候,找到一个好的预训练特征比在一个跨域问题上强行套用元学习带来的收益大得多。
5.4 常见问题速查表
| 现象 | 大概率原因 | 优先处理方式 |
|---|---|---|
| loss 一直不降 | 标签未重映射或特征未归一化 | 检查标签、加入特征归一化 |
| 验证高、新类测试低 | 域漂移或训练/测试 shot 不一致 | 更换代理验证集,统一 episode 设置 |
| 测试分数波动巨大 | 随机采样差异或 BN 统计量不稳定 | 多 seed 评测,换组归一化 |
| 增强后性能变差 | 强增强破坏了语义 | 降低增强强度,人工验证增强后类别 |
| 跨域迁移性能崩盘 | 特征分布漂移过大 | 冻结主干,用线性头或最近邻先测底线 |
6. 我个人的一些实操体会
做了几年小样本方向的实验,我的最大体会是:小样本学习是一个被高估算法复杂度、被低估评测难度和研究质量的方向。很多论文的领先来自更快的训练、更合理的正则化或更大的骨干网络,而不是算法本身。如果准备做实际部署,先跑通一个预训练特征加线性分类的强基线,再决定要不要上更复杂的方案,这能帮你少走很多弯路。还有一点,做这个方向一定要把随机 seed 和评测协议当成实验的一部分,而不只是最后顺手跑个测试。seed 不同、episode 采样方式不同,结论甚至可能反转。
最后再分享一个我最近在用的习惯:每次新拿到一个数据集,我都先做一次零样本基线——直接用预训练特征做最近邻,完全不做任何训练,看看准确率在什么水平。很多项目做完这个测试之后,立刻就能判断后续优化空间有多大。如果零样本基线已经有 60 分,那后面加一点适配、加一点训练,做到 75 分并不难;如果零样本基线不到 20 分,那真正的问题很可能不是算法,而是数据划分和任务定义本身。