最近在整理少样本学习few-shot learning方向的文献时我重新精读了《Prototypical Networks for Few-shot Learning》这篇论文。它发表于2017年底属于度量学习路线里绕不开的基石工作。整篇论文的思想非常朴素用每一类支持样本在嵌入空间中的均值作为“原型”prototype分类时看查询样本离哪个原型最近。但这个朴素思路背后有一套完整的概率解释和度量学习推导效果也压过了当时主流的匹配网络。这篇博文就是我的完整阅读笔记。我会先把论文要解决的问题讲清楚再拆解核心思想和公式推导然后给出可直接参考的训练流程与伪代码最后分享我复现时踩过的坑和几个实用建议。适合三类人看正在入门少样本学习的同学、准备复现基线模型做对比实验的研究者以及想理解“为什么用均值当类别代表也有效”的读者。1. 这篇论文要解决什么问题1.1 少样本学习的核心矛盾传统图像分类假设每个类别有大量标注样本模型可以把每个类别的分布统计得很充分。但少样本学习面对的是完全相反的场景每个类别只有一两张图甚至连完整的类别统计都做不出来。更棘手的是测试阶段出现的类别在训练阶段往往从未出现过。模型在训练时必须学到的不再是“这张图对应哪个具体类别”而是一种迁移能力遇到一个新类别只要有少数几个样本就能快速建立判断依据。这种“任务级的学习”和传统分类里的“样本级学习”完全是两回事。你可以把少样本学习理解成让模型练出一种“临场反应”平时在大量相似任务上反复演练考试时只给每个新类别一两道例题要求马上举一反三。显然这种能力没法靠背答案获得只能靠学会某种通用的比较规则。1.2 一条“学习距离”的技术路线当时解决少样本问题大致有几条路线。一类做法是元学习式的参数初始化先跨任务训练一个对初始化参数敏感的模型到新任务上只需要少量梯度更新就能收敛。另一类做法是本文关注的度量学习学一个嵌入函数把原始样本映射到某个度量空间让同一类的样本天然聚集在一起、不同类的样本互相远离。分类阶段不做复杂的自适应只在这个空间里做简单的距离判断。在度量学习内部也有分歧。匹配网络的做法是对每个查询样本与所有支持样本逐一做注意力交互查询样本的嵌入还会受到整个支持集合影响而这篇论文选择了更“省事”的路线——用每一类的均值当代表把所有支持信息压缩成N个原型向量查询样本只需要和这N个原型比距离。这个选择在后续实验中证明不只是省事还更稳。我读这篇论文时最大的感受是它把“度量学习解决少样本问题”这件事讲得非常完整。从问题定义、方法设计、理论解释到实验验证每一步都扣得很紧。这也是为什么五年之后再回头看它仍然是这个方向绕不开的参照系。2. 核心思想用“原型”代表每一类2.1 原型网络的定义给定一个嵌入函数 f_φ类别 k 的原型定义为其支持集中所有样本嵌入的均值c_k (1 / |S_k|) · Σ_{x_i ∈ S_k} f_φ(x_i)对一个查询样本 x模型计算它到每个原型的距离再通过softmax得到属于每个类别的概率p_φ(y k | x) exp(-d(f_φ(x), c_k)) / Σ_j exp(-d(f_φ(x), c_j))这里的 d 默认取平方欧氏距离。距离越近概率越大。整套结构本质上是一个“最近类均值分类器”Nearest Class Mean的神经网络版本传统NCM用原始特征做均值分类原型网络把“用均值分类”这件事作为训练目标端到端地学习产生这个空间的嵌入网络。换句话说网络真正学的东西不是“哪个类别长什么样”而是“什么样的特征空间能让均值分类器好使”。这是一个微妙的视角转换也是整个方法的核心竞争力所在。2.2 为什么均值可以作为原型把“均值当代表”的做法看似简单但论文给出了两个层面的支撑。第一层是概率视角。假设在嵌入空间中每个类别的样本围绕各自的原型呈高斯分布且不同类别拥有相同的协方差结构那么给定一个类的若干样本最大似然估计出的分布中心正好是这些样本的均值。在这种假设下用欧氏距离做最近均值分类等价于各类等协方差的高斯判别分析。也就是说这其实是一个有明确生成模型解释的分类规则不是随手拍脑袋选的。第二层是Bregman散度视角。对一类特定的距离度量正则Bregman散度簇均值恰好是使该类所有样本到该点之间散度总和最小的点。这保证了“均值质心”在信息几何意义下的最优性。反过来看这也说明嵌入空间学得是否“成簇”非常重要——均值这个统计量本身不会凭空产生结构它只是忠实地反映潜在分布的聚集程度。2.3 和匹配网络的区别在哪里读这篇论文时对照匹配网络一起看会清楚很多。匹配网络对每个查询样本都要计算它与所有支持样本的注意力权重并且查询样本的表示还会由序列结构结合支持集上下文来生成。这样做理论上很灵活但有两个问题计算复杂、参数多支持集只有一两张样本时注意力权重的估计很不稳定反而容易过拟合到单个样本上。原型网络的取舍是把“支持集如何影响查询样本”这一步删掉。支持集只被压缩成N个均值向量查询样本与支持样本之间没有逐对的交互。论文的核心观点是少样本场景下一个强但简单的归纳偏置比一个灵活但容易过拟合的机制更重要。实验也验证了这个观点——原型网络在当时的少样本基准上取得了明显更好的结果。提示理解“归纳偏置”这个词可以把它理解成“模型天生的倾向”。匹配网络天生倾向每个样本都单独起作用原型网络天生倾向类别是一个整体簇。少样本时显然“整体”更扛得住噪声。3. 关键公式与理论推导3.1 从距离到概率的完整展开把平方欧氏距离展开d(f_φ(x), c_k) ||z - c_k||² z·z - 2·z·c_k c_k·c_k其中 z f_φ(x)。在给定查询样本 x 后z·z 对每个类别来说都是同一个常数所以决定哪个类别概率最大的只有两项2·z·c_k - ||c_k||²。这两项关于 z 是线性的等价于一个线性分类器权重 w_k 2c_k偏置 b_k -||c_k||²。这个展开非常重要。它说明原型分类器在嵌入空间里其实是若干线性分类面的组合但这些线性分类器的参数不是自由学习的而是从少量样本的均值中“生成”的。自由参数变少正是少样本场景下能扛住过拟合的原因。这也是为什么经常有人把原型网络理解成“绑定了参数的线性分类器”。为了方便后文讨论先把论文中反复用到的符号统一列出来符号含义f_φ带参数φ的嵌入网络c_k第k类的原型向量d平方欧氏距离p_φ(ykx)N-way / K-shot任务包含N个类别每类K个支持样本3.2 平方欧氏距离背后的高斯假设如果每个类的嵌入特征满足 z | yk ~ N(c_k, σ²I)那么查询样本属于类别 k 的对数后验概率正比于 -||z - c_k||² / (2σ²)加上常数项。在这个前提下最近均值分类就是最优的MAP分类。这个推导解释了论文为什么选择欧氏距离而不是其他度量它对应的刚好是“每个类围绕原型呈同性高斯分布”的生成模型。如果你换成别的距离表面上只是改了一个函数实际上等于悄悄改掉了你对特征分布的假设效果往往不会更好。这在复现时感受特别明显同样的数据、同样的网络换了距离函数精度能差好几个点。3.3 为什么不能用余弦相似度当时很多注意力机制喜欢用余弦相似度但论文实验和后续复现都发现在少样本分类中它普遍不如平方欧氏距离。原因有两层。第一层是信息量问题。余弦相似度只比较向量的方向完全忽略模长。而模长实打实地携带信息它反映了样本到原点的距离在高斯假设里直接与置信度相关。丢掉模长等于丢掉了一部分可用于分类的信号。第二层是理论适配问题。Bregman散度框架对距离函数是有要求的而余弦相似度或者说归一化后的内积不属于正则Bregman散度“均值是最优质心”这个命题对它不成立。你依然可以强行用余弦做原型网络但你会失去论文里推导出来的那些保证实验上也很难占便宜。4. 训练流程与实现细节4.1 Episode训练框架原型网络的训练不是传统的“一个大batch塞很多类别”而是构造大量“模拟少样本测试”的任务每个任务在论文里叫一个episode。一次episode的构成方式是从训练类别中随机抽取N个类别每个类别随机抽取K个样本作为支持集再从这N个类里各抽取Q个样本作为查询集。模型用支持集计算原型对查询集计算交叉熵损失更新嵌入网络。N-way一次性区分多少个类别K-shot每个类别给几个支持样本Q-query每个类别抽几个查询样本参与损失计算训练时的N和K最好与测试设置一致。比如测试是5-way 5-shot训练也用5-way 5-shot比较稳妥。不一致会造成训练和测试的任务分布偏移直观表现就是训练loss正常但测试精度上不去。如果你希望模型对多种设置都鲁棒也可以在训练时随机混合不同的N和K但要保证测试用的那组设置出现在训练分布里。4.2 核心伪代码与编程实现整个网络比想象中简单。以下给出核心逻辑的伪代码网络结构抽象成 embed 函数# 伪代码原型网络一个episode的前向与损失计算 def train_step(support_x, support_y, query_x, query_y, N, K, Q): # support_x: [N*K, C, H, W] # query_x: [N*Q, C, H, W] z_support embed(support_x) # [N*K, D] z_query embed(query_x) # [N*Q, D] # 按类别分组求原型即每个类支持样本嵌入的均值 z_support z_support.reshape(N, K, D) prototypes z_support.mean(dim1) # [N, D] # 计算查询样本与所有原型的平方欧氏距离 diff z_query.unsqueeze(1) - prototypes.unsqueeze(0) # [N*Q, N, D] dist (diff ** 2).sum(dim2) # [N*Q, N] # 距离取负作为logits对每个查询样本做softmax logits -dist loss cross_entropy(logits, query_y) loss.backward() optimizer.step()关键点有两个。第一嵌入函数必须对所有样本完全共享参数不能因为支持集和查询集而使用不同的网络分支否则学出来的空间会不一致。第二reshape的维度顺序要对建议先用形状[N*K, D]压平再分组不要用切片循环不然又慢又容易下标错位。这种向量化的写法既快又不容易出边界错误。4.3 容易踩到的超参数细节嵌入输出维度是第一个要注意的参数。太小原型的表达力不够不同类容易挤在一起太大高维空间里距离度量趋于“扁平”欧氏距离的区分度反而下降。我在常见的小型图像基准上试下来64维是一个比较稳的默认值很多复现项目也沿用这个数。优化器方面直接用带自适应学习率的优化器自适应矩估计这一类通常就能跑起来学习率初始值在1e-3量级配合学习率衰减。另外训练episode的数量要足够多少样本任务本身信息密度低需要大量任务迭代才能让嵌入网络收敛出一个稳定的空间。一个常见误区是看到图像数量少就把epoch设得很小结果网络还没学会“如何做少样本分类”。还要留意验证集的使用方式。验证集同样要按“少样本任务”来评估而不是把验证集所有类别混在一起算传统分类精度。两者的含义完全不同前者衡量的是模型在少样本场景下的泛化能力后者只反映常规分类质量。很多刚开始复现的人在这里被误导看到传统分类精度很高就以为模型很强实际上少样本指标可能一塌糊涂。5. 实验结果与核心结论5.1 少样本分类上的表现论文在手写字符数据集和通用的自然图像少样本基准上做了评测主要对比匹配网络等当时的方法。在5-way 5-shot的设置下原型网络在两个基准上都占据明显优势5-way 1-shot的提升则更夸张。手写字符这类结构清晰、类内差异小的任务上效果一度接近人类水平。训练速度也是这篇论文的一个隐藏优势。由于没有逐对的注意力计算一个episode的前向只需要“嵌入均值距离”计算量比匹配网络小一个量级。我当时用普通显卡复现几分钟就能在小型基准上跑到可用的验证精度调试起来很舒服。5.2 延伸场景零样本学习论文还做了零样本学习的实验。零样本意味着测试类别没有一张图像样本只有类别层面的语义描述比如属性向量或者文本空间里的词向量。原型网络在这个场景下几乎没有改动把类的原型直接定义为该类语义向量的映射结果其中映射函数把语义向量搬到嵌入空间里查询图像照样被嵌入到同一个空间再和这些语义原型比距离。实验表明同一套框架在两类任务上都成立这从侧面说明“原型”的概念很通用不限定于图像样本本身。这个扩展也给了我一个启发原型的本质是“类别在某个空间中的代表点”至于这个点来自均值、来自语义向量还是来自其他先验都是可以替换的选项。论文把这种灵活性留在了框架里而不是把方法焊死在“图像均值”这一个实现上。5.3 与当时方法的横向对比我把论文中的对比思路整理成了一张表方便看清差异方法查询样本是否与支持集交互对每类的建模方式典型特点传统最近邻是与每个支持样本比无独立类别模型简单但受单样本噪声影响大匹配网络是逐对注意力支持样本加权灵活、参数多、易过拟合原型网络否只与原型比类均值原型强归纳偏置、计算快、稳定这个表格也解释了一个现象少样本分类的进展很多时候不是模型越复杂越好而是如何在“有限的样本”里找到最稳定的表示。原型思想的本质是把多个支持样本合并成一个更鲁棒的代表点再用它消除单样本噪声。6. 复现时常见的坑与排查技巧6.1 原型到底对谁求平均新手最容易犯的错误是把查询集样本也混进原型的计算里。支持集和查询集虽然在同一个episode里但原型只能由支持集样本求平均得到。查询集一旦参与计算均值就构成标签泄漏测试时根本没有这个条件。写代码时务必保证原型计算只发生在支持分支上。另一个隐含的坑是数据增强。如果你对同一张原图做了多个增强版本千万别把它们一部分放进支持集、另一部分放进查询集。这会让查询样本和原型之间的“距离”被人为缩短测试指标虚高真实场景里没有这种作弊条件。我见过有人因为这个问题在基准上虚高了好几个点排查了半天才发现是增强副本串了阵营。6.2 损失不下降时的排查顺序我复现时遇到过训练loss一直不降的情况排查顺序基本固定第一步检查logits的符号softmax里是 -d 不是 d符号反了会导致模型反向优化第二步看嵌入输出是否过小或过大如果网络初始化的数值范围不合适距离可能全挤在很小的区间里softmax输出接近均匀分布梯度很小第三步检查优化器学习率少样本任务迭代次数多学习率过大会在后期震荡过小则收敛极慢。如果loss能降但测试精度上不去优先怀疑任务分布不匹配训练N/K和测试N/K不一致或者训练类别数量太少导致嵌入空间泛化不足。把常见现象整理成一张速查表复现时对照着查会快很多现象可能原因处理方式训练loss不降logits符号错误、数值范围异常、学习率不当先查符号再看数值范围最后调学习率测试精度虚高数据增强导致信息泄漏保证同一原图的增强副本不分属支持/查询两侧测试精度上不去训练N/K与测试不一致统一训练和测试的任务设置1-shot明显崩坏嵌入质量不够原型等于单样本增加数据增强、适当降低嵌入维度6.3 1-shot是最考验嵌入质量的设置1-shot时原型就是支持集的唯一样本本身没有“平均”来平滑噪声。此时整个模型的成败完全压在嵌入函数上如果单张图被嵌入到某个不合适的位置错误会被直接放大。这也是1-shot精度远低于5-shot的原因。想在1-shot下提升稳定性有两个实操技巧。一个是在训练时加入数据增强让模型见过更多同类变体学到更不变性的表示另一个是把嵌入输出维度适当调低避免高维空间下距离区分度被稀释。我这里说的“适当调低”是相对而言的具体数值还是要以验证集为准多试几组。6.4 从论文到工程落地的距离如果只是复现原型网络非常友好。但真要把它用到自己的业务数据上有几个问题需要额外处理。第一业务里的类别数往往不是固定5个原型的数量会随任务变化建议在框架里把“类别数”作为动态参数。第二业务数据往往类别不平衡均值会偏向大类别这时候可以考虑加权的原型计算权重根据支持集中各类样本数量动态设定。第三原型网络假设类别在嵌入空间中是各向同性的如果你的数据明显不是这样可以尝试学习每个类别独立的协方差矩阵但参数会变多需要权衡复杂度与收益。7. 我的阅读体会与后续扩展7.1 这篇论文的局限在哪里精读之后论文的局限也值得记下来。最根本的一个假设是各类样本在嵌入空间中服从相同形式的高斯分布。这个假设在类内差异很大的真实场景里不成立均值原型容易被离群点带偏。此外模型完全抛弃了支持集内部的结构信息——比如样本之间的相关性、类别之间的先验关系这些信息在极端少样本时其实很宝贵。另外原型网络是诱导式的对给定的查询样本独立预测没有利用整个查询集合的联合分布。后来不少工作证明在测试时引入直推式机制、利用无标签查询数据的统计信息还能再涨一截精度。这也说明一个框架的“简单”背后往往藏着一串可以继续优化的缝隙。7.2 从这篇论文延伸出去的工作读这篇论文时建议顺手看几个衍生方向。分布传播类的方法不再把原型当固定点而是让原型在图上迭代更新贝叶斯视角的工作给原型加上了不确定性估计样本少的时候对原型的置信度也低分类时会更加保守。还有一类工作把原型思想和自监督预训练结合先用大规模无标签数据学表示再在少量标签上构造原型效果比从零训练好不少。这些方向都共享同一个底层逻辑在样本不足时靠“类别可以用一个紧凑表示来概括”这个假设来撑住模型。原型网络之所以影响深远正是因为把这一条逻辑做得足够干净、足够通用。7.3 我个人复现后的几点感受最后聊点实在的。这篇论文我完整复现过代码量比想象中小得多核心部分也就几十行。但越是这样越要重视数据划分和episode构造的正确性因为bug不在网络结构里而在数据的流动方式里。我踩过最大的坑就是数据增强导致的信息泄漏一度让测试精度虚高了好几个点查了很久才发现是同一个原图的多个增强副本被分到了支持集和查询集两侧。另外原型网络虽然简单却是一个非常优秀的“基线”。后来我做少样本方向的新实验第一件事都是先把原型网络跑通、把它的精度作为对比基准再谈新方法比它好在哪里。读论文如果只停留在看公式和结论很多隐含的细节是体会不到的建议你把代码亲手写一遍再把那些超参数逐个调一调收获会比单纯读十遍还大。希望这篇笔记也能帮你在读论文和复现时少走一些弯路。