news 2026/10/23 3:14:48

小样本学习实战:从原理到代码,解决图像分类数据稀缺难题

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
小样本学习实战:从原理到代码,解决图像分类数据稀缺难题

开头我从一个实际场景说起。我去年接了一个某工业质检项目,客户给的需求是识别产线上几种新产品的外观缺陷,但每个缺陷类型只有几十张图,多的也就一百来张。一开始想直接拿预训练的ResNet微调,发现效果不理想,几个缺陷类别之间本身就长得像,数据一卷进去就过拟合。后来换成小样本学习(Few-shot Learning)的思路重新设计,才把效果拉到能上线用的水平。

从那以后我就摸清了一个事实:图像分类里的小样本学习并不是要造一个能“凭空学会一切”的模型,而是解决一个更现实的问题——在每类只有一张、五张或十几张标注图的情况下,怎么还能把分类器训练出来,并且保证泛化能力。这套东西适合谁?适合所有“业务数据少、标注成本高、新类别不断出现”的场景,比如工业缺陷检测、医学影像的罕见病筛查、农作物病虫害识别,甚至手机相册里自动给陌生物品打标签。下面我会把原理和思路拆开讲,再给一套可以直接抄走的复现流程和经验。

1. 小样本学习到底在解决什么问题

1.1 常规图像分类为什么会失效

要理解小样本学习,先要搞清楚常规分类方法在数据少的时候到底哪里不行。

常规分类走的是“大批量数据 + 全连接分类头 + 交叉熵”的老路。模型先通过卷积网络提取特征,然后把特征送到一个全连接层,输出每个类别的得分。全连接层的参数规模往往是(特征维度 × 类别数),当类别少、但每类样本也少的时候,这个分类头很容易把训练集背下来,而不是真正学到类别的共性。

举个例子:你的特征是512维,共5个类别,那分类头就是512×5的矩阵。如果每个类别只有5张图,总共25个训练样本,一个参数量超过2500的分类头被25条样本约束,过拟合几乎是必然的。更麻烦的是,测试时如果来了第6个新类别,分类头就得重新训练,因为输出维度变了。

小样本学习的做法不同:它不直接学“某个类别是什么”,而是学“怎样去比较两个样本像不像”。这种思路的典型框架是“支持集”(Support Set)和“查询集”(Query Set)。支持集里每类给少量标注图,查询集里是需要预测的样本。模型要做的是在支持集的约束下,给查询样本分配一个类别标签。

1.2 N-way K-shot 这个术语到底怎么读

凡是接触小样本学习的人,一定会碰到5-way 1-shot、5-way 5-shot这种说法。这里的 way 表示一次任务中有几个类别,shot 表示每个类别提供几张支持图。

这么说可能还不够直观,我拿考试类比:way 就是这次考试一共考几门课,shot 就是每门课考试前给你几道样题。5-way 1-shot就是一共5门课,每门课只给1道样题,让你去答剩余题目。5-way 5-shot就是每门课给5道样题,显然难度低不少。

在代码实现里,这个设置直接决定张量的形状。假设我们做5-way 1-shot,batch size 里每一轮 episode 由两部分构成:

  • 支持集:5×1 = 5张图,给5个类别各1张
  • 查询集:每个类别再抽比如15张图,一共5×15 = 75张图用来计算损失和更新模型

所以一次 episode 的总数据量是80张图。小样本学习之所以用 episode 这种采样方式训练,是因为它模拟了测试时的真实场景:每次只给少数标注,要求快速适应。这个逻辑在后面讲训练流程时还会反复出现。

1.3 真实场景里哪些问题适合用它

不是所有数据少的项目都适合硬套小样本学习。我总结下来,下面三类场景收益最大。

第一类是工业瑕疵检测。产品种类多、换产频繁,有些新品类刚上线时良品数据充足,但瑕疵数据可能只有几十张。用传统方法每个类别单独训练一个模型,人工成本和维护成本都扛不住。小样本学习可以做一个“通用比较器”:新品类来的时候,每类摆几张样例图,就能跑了。

第二类是医疗影像里的极端类别。正常的体检数据很多,但某种罕见病变可能是几年才攒下来几十例。这类场景里,不同医院的数据还不能随便汇聚,导致单个机构的数据更少。小样本学习配合自监督预训练,是当前比较可行的思路。

第三类是图像检索与自动归档。比如相册里突然出现猫、狗、滑板、无人机,类别清单本来就不是预先固定的,传统分类头的方案完全没法实时扩展。用度量学习做小样本分类,新增类别只需要嵌入新的支持图,不需要重新训练模型。

不适合的场景也有:类别数量本来就不多、每类样本又非常充足的场景,强行用小样本学习反而会浪费已有数据,直接微调预训练模型效果往往更好。这一点后面的“方法选型”部分我会再说原因。

2. 主流方法的原理和选型逻辑

2.1 度量学习:原型网络和匹配网络是怎么工作的

小样本学习最主流的方法之一是度量学习,核心理念是“学习一个特征空间,让同类的样本靠近,异类样本远离”。在这个空间里,分类变成了简单的最近邻问题。

先看原型网络(Prototypical Network)。它的做法很直接:把每个类别的支持集特征取平均,得到一个“原型向量”(prototype),然后计算查询样本特征与各个原型之间的距离,距离哪个原型最近,就分到哪一类。你可以在特征维度上做欧氏距离,也可以做余弦相似度。

伪代码逻辑如下:

# 特征提取:支持集和查询集都过同一个 backbone support_feat = backbone(support_images) # 形状 [n_way * k_shot, feature_dim] query_feat = backbone(query_images) # 形状 [n_query, feature_dim] # 算原型:每个类别内部取平均 support_feat = support_feat.view(n_way, k_shot, -1) prototypes = support_feat.mean(dim=1) # 形状 [n_way, feature_dim] # 算距离并转成 logits(距离越近分数越高) distances = torch.cdist(query_feat, prototypes) # [n_query, n_way] logits = -distances

我觉得原型网络最妙的一点是它的概率解释:在欧氏距离下,用高斯分布建模每个类别的样本分布,取均值就是最大似然估计的原型。这意味着即使只给你一张支持图,模型也至少有一个可比较的类别中心,不至于像传统分类头那样完全不知道输出什么。

匹配网络(Matching Network)的思路类似,但它更进一步,不再把支持集压缩成一个均值向量,而是让查询样本去对支持集中每个样本做带权求和,权重就是相似度。这样做的好处是在支持样本数少、类内差异又大的场景下,不至于被一个均值向量带偏。代价是计算量更大,每个查询样本都要跟所有支持样本比较。

关系网络(Relation Network)又是另一个变体,它不预设用欧氏距离还是余弦距离,而是把“怎么比较”也交给网络去学。具体做法是把查询特征和候选类别特征拼接起来,喂进一个小型神经网络,输出一个关系分数。这种设计在特征空间比较复杂的任务上更灵活,但训练难度也更大,我在实际项目里用的不多,这里提一下,方便你做对比时的备选项。

2.2 优化方法:MAML 想解决什么问题

另一大流派是优化方法,代表是模型无关元学习(MAML)。它想解决的是“模型如何快速适应新任务”的问题:在训练阶段,故意构造很多小样本任务,让模型的初始参数经过少量梯度更新后就能在新任务上表现好;最终学到的不是某个具体分类器,而是一个好的初始参数。

我理解 MAML 的关键在于双层优化。内层循环是在当前任务的支持集上算损失,更新模型得到临时参数;外层循环是在临时参数的基础上,用查询集的损失去更新最原始的模型参数,目标是让“内层更新一次或几次后损失很小”。简化代码如下:

for task in tasks: # 内层更新:用支持集算一次损失,更新临时参数 temp_params = model.params - lr_inner * grad(loss_support(model.params)) # 外层更新:用查询集算损失,梯度反传到原始参数 loss_query = loss(model_with_params(temp_params, query)) optimizer.zero_grad() loss_query.backward() optimizer.step()

MAML 看起来很美,但实际用起来有两个麻烦。第一是二阶导数的计算开销很重,虽然可以用一阶近似来缓解,但实现难度和显存占用都比度量学习高;第二是它对 backbone 的选择比较敏感,我用 ResNet 之类的大网络时,内层几步更新很难真正改变高层特征,效果不一定比原型网络好。所以在多数工业落地场景里,我优先考虑度量学习,MAML 类方法更适合方法研究和任务分布特别复杂的场合。

这里放一张方法对比的速查表,方便你在选型时参照:

方法核心思想训练代价适用场景
匹配网络查询样本与支持集逐样本比较低支持集内类内差异大、样本较少的场景
原型网络求支持集均值作为类别原型,做距离比较低类内分布相对紧凑、快速落地的首选
关系网络用神经网络学相似度度量中特征语义复杂,手动距离函数不稳定的情况
MAML学习对梯度更新不敏感的初始参数高跨任务差异大、要求快速适应的研究场景

2.3 怎么根据你的数据选方法

我个人的经验是:先看类内差异,再看支持集样本数,最后看可用的训练资源。

如果你的支持集样本分布比较紧凑,比如工业零件在固定光源下的照片,类内差异小,原型网络就是最稳的起点。它简单、好调、不容易出幺蛾子。如果类别里面形态变化很大,比如同一个“瑕疵”有时是条纹、有时是颗粒,一个均值向量很难代表整个类,这时候匹配网络的逐样本比较往往更稳。

如果你有足够多的辅助任务数据做元训练,且新任务之间的分布差异很大,再考虑 MAML。大多数情况下一开始不用上它,因为它的坑比较多,内层学习率、外层学习率、更新步数都是敏感超参,调起来很费劲。

还要强调一个容易忽略的点:无论是哪种方法,backbone 的特征质量比方法本身更影响最终效果。同一个原型网络,用随机初始化的浅层网络可能只有40%的准确率,换上在大型数据集上预训练过的 ResNet,能直接拉到60%以上。所以后面实验部分,我会把“怎样准备 backbone 和特征”放在比较靠前的位置。

3. 从零复现一个图像小样本分类项目

3.1 数据集和评估协议怎么选

复现小样本学习通常有几个公开数据集可选,各有各的脾气。

miniImageNet 是使用最广泛的基准,100个类别,每类600张图,其中64类用于训练、16类用于验证、20类用于测试。它的图片都来自一个大规模图像数据集,类别杂,难度适中。tieredImageNet 是升级版,类别按高层语义划分到不同集合,更考验模型的泛化能力,但下载和预处理稍微麻烦一点。CUB(鸟类数据集)适合做细粒度识别,类别之间的差异小,对特征质量要求高。

如果你只是想快速验证流程,我建议先用 CUB 或者一个自建的小数据集合跑通,原因有两个:第一数据量小,迭代速度快;第二细粒度任务更贴近真实工业场景里的“相似缺陷分类”,参考价值更高。

评估协议上,“episode 式评估”是标准方式。测试时随机采样很多个5-way 1-shot或5-way 5-shot的任务,在每个任务上计算准确率,最后取平均。我一般采样600个episode,这样准确率的波动会比较小。千万不要只跑几十个episode就下结论,方差大到能骗死人。

3.2 Backbone 的选择和预处理细节

Backbone 这块,我推荐直接使用在 ImageNet 上预训练过的 ResNet-12 或 ResNet-18。ResNet-12 是很多小样本论文里的标配,但实际工程里 ResNet-18 更容易找到预训练权重,两者差距不大。如果有条件,用更大的 backbone 不一定更好,因为特征维度越高小样本下的距离计算越容易受到噪声干扰,我一般在特征维度64到512之间做实验,过高的维度反而需要额外加降维。

预处理也不能粗糙。小样本任务里每张图都很宝贵,训练时一定要做数据增强,我常用的组合是:随机裁剪、随机水平翻转、颜色抖动和归一化。其中颜色抖动对很多自然图像数据集帮助明显,但在工业固定光源场景里要慎用,否则可能引入不存在的色差变化。

另外要注意一个细节:支持集和查询集的图都需要做同样方式的预处理,而且模型在 forward 前要把所有图统一尺寸。我习惯把输入尺寸设成84×84或96×96,这比直接用224×224快很多,在效果上差异不大。

3.3 完整的训练流程和关键超参

下面给出一套我实际项目里验证过的训练流程,基于 PyTorch,以一个简单的原型网络为例。

先定义采样逻辑。每个episode随机选类别和样本:

def sample_episode(dataset, n_way=5, k_shot=1, n_query=15): classes = random.sample(dataset.classes, n_way) support_x, support_y = [], [] query_x, query_y = [], [] for label, cls in enumerate(classes): samples = random.sample(dataset.class_to_samples[cls], k_shot + n_query) support_x.extend(samples[:k_shot]) support_y.extend([label] * k_shot) query_x.extend(samples[k_shot:]) query_y.extend([label] * n_query) return support_x, support_y, query_x, query_y

接着是训练循环。我这里用--epochs表示外层循环次数,每个 epoch 内跑若干个 episode:

model = ProtoNet(backbone) optimizer = torch.optim.Adam(model.parameters(), lr=1e-3) for epoch in range(epochs): total_loss = 0.0 for _ in range(episodes_per_epoch): support_x, support_y, query_x, query_y = sample_episode(train_data) # 把所有图转成 tensor 并过 backbone logits = model(support_x, query_x, n_way, k_shot) loss = F.cross_entropy(logits, torch.tensor(query_y)) optimizer.zero_grad() loss.backward() optimizer.step() total_loss += loss.item()

训练完保存模型参数,评估阶段的关键是让模型处于eval()模式,并且关闭梯度计算:

model.eval() with torch.no_grad(): acc_list = [] for _ in range(600): support_x, support_y, query_x, query_y = sample_episode(test_data) logits = model(support_x, query_x, n_way, k_shot) preds = logits.argmax(dim=1) acc = (preds == torch.tensor(query_y)).float().mean().item() acc_list.append(acc) print("5-way 1-shot acc:", np.mean(acc_list), "+-", np.std(acc_list))

超参方面,我习惯从下面这组开始试:

  • 优化器:Adam,初始学习率1e-3,每20个epoch衰减为原来的0.5
  • 每个epoch的episode数:200到400,太少容易欠拟合
  • n_query:15,太少会让损失方差偏大
  • 特征维度:128到256

我在这套设置下,用 ResNet-18 在 CUB 数据集上能得到约65%的5-way 1-shot准确率和约80%的5-way 5-shot准确率。在 miniImageNet 上会低一些,一般5-way 1-shot在60%上下,这属于正常情况,说明模型真的在“学习比较”,而不是靠记忆。

3.4 训练策略上容易忽略的点

训练时有个细节我踩过坑:不要把所有类别均匀采样后就开训,而是尽可能在episode里模拟测试时的采样难度。如果你的测试任务是5-way 5-shot,训练时也用5-way 5-shot;如果测试是 1-shot,训练时也要用 1-shot,最多做一点shot数上的轻微扰动。道理很简单,模型会把“支持集样本数量”当成隐式的先验,训练和测试差距太大时会明显掉点。

还有一个点是监督信号的粒度。早期训练时损失降得快,后面就可能卡在一个平台上。这时可以加大episode数量,或者稍微调低学习率,不要马上换方法。原型网络收敛相对平稳,多数情况下不是模型有问题,而是训练时长不够。

另外,如果显存比较紧张,可以把一个episode里的查询集拆成两个小的子batch分别计算,但要注意梯度要累加后再更新。有些人图省事直接减小n_query,这样损失的评价不够稳定,测试效果会有波动。

4. 实验结果怎么看,怎么调

4.1 分清“训练损失低”和“测试准确率高”

训练损失和测试准确率是两回事,尤其在小样本场景下,中间隔着“支持集能否代表类别分布”的问题。

我在实际项目里见过好几次这样的情况:训练损失已经降到0.01以下,看着是收敛了,但测试准确率只有50%上下,跟瞎猜差不多。这说明模型记住了训练时支持集的模式,却没有学到可迁移的比较规则。这时候不应该继续加训练轮数,而是去检查数据增强、查看特征可视化、或者降低backbone的特征维度。

反过来也有训练损失不算太低,但测试准确率不错的情况,原因是episode里的查询集给了一定的正则作用。所以如果你只盯着训练损失来判断早停,很容易误判。

4.2 画出特征分布,直接看类间距离

这一步看起来简单,但特别有用。我每次实验都会把支持集特征和查询集特征投影到二维空间,用散点图看分布。如果同一类特征聚成一团,不同类之间有清晰的间隔,说明模型学好了;如果特征乱糟糟的,或者所有类别都挤在一起,问题很可能出在backbone或训练配置上。

投影工具我一般用 t-SNE 或 UMAP,直接把 backbone 输出那一层拿到,不要用最后的分类层。画图时把支持集样本用大号圆点标出来,查询集用同色的小号圆点,这样能一眼看出原型点的位置是否合理。

4.3 常见问题速查表

下面是我整理的一张速查表,覆盖了我遇到的大部分问题:

现象可能原因解决方向
训练损失不降学习率太高或太低修改初始学习率,或换用Adam的默认参数观察
训练损失降、测试准确率低过拟合,模型记死支持集加强数据增强,降低特征维度,增大episode数量
不同类别的测试结果差距过大某个类别支持图太少或有误标注检查支持集图片质量,尝试增加shot数
支持集内部的类别得分都差不多特征空间没学好更换backbone预训练权重,或调整度量方式
在miniImageNet上效果尚可,但在自建数据上崩塌领域分布差异大加入领域内无标注数据做预训练,或做领域自适应
5-shot比1-shot提升不明显n_query太小,损失方差大增大n_query到30,或减少每个类别的查询数量差异

4.4 排查训练不收敛的实用顺序

训练崩了不要慌,按下面的顺序排查往往能找到根因。

第一,先跑一个极小的配置,比如2-way 1-shot,每个episode只拿6张图。如果这个都学不动,说明代码问题大于数据问题。第二,检查backbone的梯度是否在正常范围内,在第一个episode后打印一下梯度的均值和方差,如果梯度爆炸了,检查输入是否有归一化。第三,把学习率降到1e-4重新跑,看训练损失是否下降。第四,去掉所有数据增强,确认模型在无增强条件下能记住训练集,这能排除增强过强导致的信息丢失。最后再逐渐加回增强,找到合适的强度。

5. 最后再分享两个实操心得

项目做完后我经常复盘,有两个心得值得单独讲。

第一个心得是关于“支持集的维护”。小样本学习上线后,不是把模型训完就结束了。新类别出现时,你需要往支持集里补充图片,所以支持集的图片质量直接决定线上效果。我曾见过有人直接把产线拍下的模糊图塞进支持集,导致那个类别测试准确率掉了一截。一定要维护一套人工审核过的支持图库,每张图都要清晰、有代表性。

第二个心得是关于“先小后大”的实验习惯。新任务上来,先用最小的模型和最小的图像尺寸把流程跑通,再去逐个放大。这个习惯帮我避开了很多“一上来就几百兆模型,训练三天后才发现数据预处理有bug”的尴尬情况。

小样本学习不是什么银弹,它解决的是有限标注下的比较判断问题。理解了这一点,你在实际项目里就能知道什么时候该用原型网络、什么时候该上更强的元学习策略,以及怎样设计支持集才真正有效。希望这些经验能帮你少走一段我走过的弯路。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/23 3:14:47

AI产品落地实战:技术-场景适配与产品规划避坑指南

简介:这份PDF是《产品经理的AI实战》一书的读书笔记,面向AI产品经理、技术转产品人员及关注AI商业化的从业者,帮助读者快速理解人工智能产品从技术到商业落地的完整逻辑。笔记将原书三部分13章、216页内容精炼为28页,涵盖技术商业…

作者头像 李华
网站建设 2026/10/23 3:14:44

点开头目录全解析:从.git到.npm,哪些能删哪些不能?

很多开发者估计都有过这样的瞬间:刚拿到一台新电脑,或者打开项目目录准备排查磁盘空间,结果发现里面密密麻麻地躺着十几个点开头的文件夹——.git、.next、.cache、.npm、.vscode,还有一堆叫不上名字的.xxx。第一反应通常是“这是…

作者头像 李华
网站建设 2026/10/23 3:14:28

CO5300驱动IC数据手册深度解读:QSPI与MIPI DSI双接口及TE撕裂效应

简介:这份CO5300 AMOLED数据手册面向智能手表等嵌入式显示方案的硬件工程师与驱动开发者,用于解决OLED驱动IC选型、电路设计与底层驱动开发中的规格确认问题。资源包为1个PDF文件,大小约12.62MB,即Chipone官方发布的CO5300 OLED D…

作者头像 李华
网站建设 2026/10/23 3:14:11

wbengine.exe文件丢失报错?安全修复思路与实战排查指南

相信不少Windows用户都碰到过这样的弹窗:打开某个软件时,系统突然提示“找不到wbengine.exe”或者“wbengine.exe文件丢失”,应用直接退出,甚至开机的时候都会冒出一个报错框。大多数人第一反应是去搜索引擎找“wbengine.exe免费下…

作者头像 李华
网站建设 2026/10/23 3:14:03

H100规格书怎么读:FP8算力、NVLink互联与选型避坑指南

简介:NVIDIA H100 Tensor Core GPU官方数据手册(Datasheet),面向数据中心架构师、AI平台工程师、HPC研究人员以及GPU选型决策者,帮助读者全面掌握Hopper架构在AI训练、大模型推理与高性能计算等场景下的技术能力与关键…

作者头像 李华
网站建设 2026/10/23 3:12:47

从景品到手办收藏:零之使魔露易丝巫女服的验收与工程化管理指南

在二次元手办这个圈子里,“景品”两个字长期带着一点偏见。很多人第一次接触周边时,会下意识觉得:景品嘛,便宜、简单、随机,抽到什么全看运气,和“高端手办”完全是两个物种。但如果你认真观察近两年的日系…

作者头像 李华