读完《RepMet: Representative-based metric learning for classification and few-shot object detection》这篇论文时,我的第一反应是:标题里每一个词都不陌生,但组合起来却是个不太一样的思路。当时我正被少样本目标检测折腾得头疼——新类别只有几张标注框,检测器的分类头基本就是摆设,微调就过拟合,不微调就完全没法用。RepMet给我的启发在于,它把“分类”这个动作从“学一个线性分类器”换成了“学一组分布代表点”,然后在嵌入空间里用类似距离度量的方式做决策。这篇笔记我会按照论文的主线拆开讲:它解决什么问题、核心的统计建模思路、训练和推理细节、怎么把度量学习集成进两阶段检测器,以及我在复现和迁移到其他任务时踩过的坑和观察到的局限。适合正在做少样本学习、目标检测小样本迁移,或者对度量学习感兴趣的读者参考。
1. 题面拆解:RepMet同时面对的两道题
1.1 少样本分类到底难在哪
先说说少样本分类(few-shot classification)的痛点。常规分类任务里,每个类别有上千张图,我们用一个全连接层把所有特征映射到类别得分上,这个全连接层的权重其实可以理解为每个类的模板向量。只要有足够数据,这套方案非常稳。但一旦每个类别只有5张、甚至1张图,问题就来了:全连接层的权重训练不出来,或者说训练出来的结果严重偏离真实类别分布。这不是调参能解决的,是结构性问题。
RepMet想做的事情很直接:能不能不要这个“需要大量数据才能训好的线性分类器”,而是换成一种参数更少、更有统计意义的分类方式——每个类用一个或多个“代表点”来描述,待测样本直接跟这些代表点比距离。这样新类别加入时,不需要重新训练分类头,只需要把新类的代表点估计出来,整个系统就能识别新类。
1.2 模板匹配与度量学习两条路线
少样本分类其实有两条常见路线。一条是模板匹配思路,最典型的如原型网络(Prototypical Networks):每个类取特征均值作为原型,距离度量用欧氏距离。这条路线简单、有效,但问题是对类内多模态(一个类里有多种外观)不够友好。比如“车”这个类,轿车、卡车、越野车在特征空间里可能分布在好几个簇,用一个均值点去代表,会把它们全部拉到一个位置,类内方差被严重低估。
另一条路线是度量学习思路,经典代表有孪生网络(Siamese Networks)、三元组损失(Triplet Loss)等。这类方法的目标是学一个嵌入空间,让同类靠近、异类远离。它们在已知类别上效果不错,但迁移到新类别时,判别式损失学到的嵌入未必有很好的泛化性——你只是教会了网络“尽量分开”,并没有给它一个明确的“类别长什么样”的模型。RepMet走的是第三条路线,本质上是把这两种思路结合了起来:既学习嵌入空间,又显式建立每个类的分布模型。
1.3 “代表点”代表谁:从单原型到多原型
RepMet中每个类别不是只用一个原型点,而是用一组“代表点”(representatives)。每一个代表点其实是一个高斯分布的均值,而所有类共享一个方差结构。一个类可以有K个代表点,对应K个子类。这样做的直观解释是:每个真实的物体类别内部都存在子结构,比如同一种鸟可能有不同姿态、不同背景下的外观差异,一个高斯均值建不出这种多峰分布,但K个均值就可以。
这带来的直接好处是:用少数样本估计K个均值,比估计一个完整的复杂分布要稳健得多。测试时,样本只需要找到离自己最近的子类代表点,属于哪个类就看这个最近代表点属于哪个类。这种设计的统计意义非常明确——每个类被建模为一个高斯混合模型(GMM),只是方差在所有类之间共享,以控制参数数量。
2. 嵌入空间里的高斯直觉:为什么用KL散度替代欧氏距离
2.1 从向量拉到分布:每个类是一团“高斯云”
理解RepMet的一个关键转折点是:它不再把图像特征看成高维空间里的一个点,而是把它看作某个高斯分布的一次采样。这个高斯分布的均值是类代表点,协方差由全局共享的方差矩阵决定。于是,判断“这个样本属于哪个类”变成了“这个样本更可能是从哪个高斯分布里采样出来的”。
这里的数学工具是KL散度。我们常说KL散度衡量两个分布的差异,而RepMet用的其实是负对数似然的形式:对于一个样本特征x,它属于第c类的第k个子类的负对数似然是:
[ -\log \mathcal{N}(x; \mu_{c,k}, \Sigma) = \frac{1}{2}(x - \mu_{c,k})^T \Sigma^{-1}(x - \mu_{c,k}) + \frac{1}{2}\log|\Sigma| + \text{const} ]
如果方差项是各向同性的(即 (\Sigma = \sigma^2 I)),这个式子就退化成欧氏距离的平方加一个常数。但RepMet保留了对角协方差矩阵,相当于对特征空间的每个维度做了单独的伸缩。这个伸缩很重要:嵌入空间的不同维度重要性往往不一样,有的维度区分性强、特征数值波动大,有的维度则比较稳定。用对角协方差去归一化,实际上是在做马氏距离(Mahalanobis distance),而不是普通欧氏距离。
2.2 子类代表点与共享方差的设计逻辑
为什么方差要共享?首先从参数数量角度说,如果一个类有自己的完整协方差矩阵,在只有几个支持样本的情况下,这个矩阵根本估计不出来,甚至可能奇异。共享方差相当于把所有类别的样本都拿来做方差估计,样本量大了很多,估计相对可靠。这在统计上类似于线性判别分析中假设各类协方差相同。
子类数K的选择则是另一个重要超参数。K过小,类内多模态描述不了;K过大,每个子类的估计又可能不稳定,毕竟每个子类分到的有效样本很少。论文实验中使用的是中等大小(比如每个类8个左右),但在迁移到新类别时,如果支持集样本数量不够,K可以适当下调,甚至退化成K=1的原型网络。可以说RepMet的框架把原型网络当作自己的一种特例。
2.3 欧氏距离、马氏距离与KL散度:一张表看懂差异
我在笔记里整理了一张对比表,帮助理解这三种距离度量之间的关系:
| 度量方式 | 数学形式 | 是否考虑特征方差 | 备注 |
|---|---|---|---|
| 欧氏距离 | (|x - \mu|_2) | 否 | 所有维度等权,实现最简单 |
| 马氏距离 | (\sqrt{(x - \mu)^T \Sigma^{-1}(x - \mu)}) | 是 | 需要估计协方差矩阵 |
| 高斯NLL/KL形式 | (\frac{1}{2}(x-\mu)^T\Sigma^{-1}(x-\mu) + \frac{1}{2}\log|\Sigma|) | 是 | 与马氏距离相差一个对数行列式项 |
注意最后的 (\log|\Sigma|) 项,在类别协方差相同时它是常数,不影响类别间的比较。但如果允许不同类别有不同的方差结构,这一项就会变成分类决策的一部分。RepMet选择共享方差,所以实际上分类决策等价于比较马氏距离。看到这里应该就明白了:KL散度不是一个“花哨的替代品”,它是把欧氏距离系统性地升级成考虑了分布形状的距离度量。
提示:如果从实现角度出发,你完全可以把RepMet的分类层理解为一个“带协方差归一化的最近类均值分类器”。一旦抓住这一点,后续的工程实现就不会跑偏。
3. 训练起来才知道的细节:EM迭代、负对数似然与决策规则
3.1 负对数似然损失是怎么化简到可代码实现的
从损失函数开始。给定一个训练样本 (x_i),网络输出其嵌入特征 (z_i),类别标签为 (y_i)。RepMet的损失函数是负对数似然:
[ L = -\log \left( \frac{\sum_k \exp(-NLL(x_i, \mu_{y_i,k}))}{\sum_{c,k} \exp(-NLL(x_i, \mu_{c,k}))} \right) ]
这里 (NLL(x, \mu_{c,k})) 就是前面提到的负对数似然距离。分子是样本到正确类别所有子类的距离做logsumexp,分母是到所有类别所有子类的距离做logsumexp。用代码表达就是:计算所有类所有子类的距离矩阵,取softmax,然后对正确类别那些项求和取负对数。这个形式看着复杂,实际写起来非常规整。
有个实现小细节值得说:由于距离表达式中包含方差倒数,建议在代码里直接存储方差的对数或者精度矩阵(协方差逆),避免数值不稳定。如果方差被优化得很小,精度矩阵会爆炸,距离也会爆炸。我在复现时一开始直接存方差,训到后期loss经常变成NaN,改成存方差的log后问题就消失了。
3.2 E步M步:硬分配与软分配的差别
论文中提到用EM风格(Expectation-Maximization)来交替优化网络和代表点。简单说,E步:固定当前网络和代表点,计算每个样本到所有子类的高斯后验概率,把样本软分配给各个子类;M步:根据软分配结果更新子类均值,同时更新网络参数。这里的软分配不是硬编码到某个子类,而是让样本以一定权重贡献给多个子类。
如果你做过K-Means,会更容易理解:K-Means是硬分配,每个样本只属于离它最近的簇;而EM的高斯混合是软分配,每个样本对每个簇都有一定的归属概率。RepMet在网络训练期间实际上是把E步和M步通过梯度下降联合起来做的,代表点就是一组可学习的参数。只不过它的初始化和更新策略带有EM的语义。实际训练时发现,交替训练(先固定代表点更新网络,再固定网络更新代表点)比同时更新的效果好一圈,损失曲线也更平稳。
3.3 推理阶段:argmin距离不是argmax相似度
在推理阶段,分类公式很简单:
[ y^* = \arg\min_c \left( \min_k NLL(x, \mu_{c,k}) \right) ]
也就是说,对每个类,先找到离样本最近的子类,再看这个距离在所有类里是不是最小。注意这里用的是argmin距离,而不是argmax相似度——虽然本质上等价,但我在写代码时一开始习惯性地用了相似度匹配,导致分类结果反了,后来才意识到应该直接比较负对数似然距离。这类小细节在论文里只是一句话,在实际工程里却非常容易踩坑。
提示:如果你要在一个新任务里重现这套逻辑,先把“距离值越小越好”这个决策方向写死在测试脚本里,再谈其他优化。方向反了,所有报告都不成立。
4. 从分类到检测:把度量学习塞进两阶段检测器的改造方案
4.1 为什么选Faster R-CNN作为载体
RepMet的检测部分选择了Faster R-CNN作为基础框架。原因不难理解:少样本目标检测首先得有区域提议能力,RPN(Region Proposal Network)正好负责这件事。它不关心目标属于哪个类,只负责找到“可能含有目标”的区域。这对新类别的泛化非常友好——一只没见过的鸟,它的边框和一只见过的狗在“是不是一个物体”这个层面没有本质区别。
如果在单阶段检测器(如YOLO)上做少样本检测,anchor分类本身就涉及到类别语义,新类很可能被抑制掉。而两阶段检测器的分类和定位解耦得比较干净,RPN只负责前景/背景,分类交给后面的ROI Head。RepMet的巧妙之处就在于:ROI Head的物体分类器不用标准的全连接层,而是用RepMet的度量学习模块替代。这样一来,新类别的分类不需要重新训练分类器,只需要估计新类的代表点。
4.2 RoI特征接入嵌入网络的“最后一公里”
具体改造过程是这样的:一个RoI(Region of Interest)经过ROIAlign得到固定尺寸的特征图,然后像图像分类那样经过嵌入网络映射到D维嵌入空间。这一步有个容易忽略的细节:嵌入网络的输入不是原图,而是ROI裁剪出来的区域。所以嵌入网络学到的是一个“区域特征到嵌入空间”的映射,而不是“整图到嵌入空间”的映射。
如果之前只做过图像分类的度量学习,迁移到检测时通常需要对嵌入网络做一定微调,因为ROI特征的空间分辨率和语义粒度跟整图特征不一样。RepMet论文里的做法是分阶段训练:先在base类上端到端训练整个检测器,包括RPN、回归分支和嵌入网络;然后再针对few-shot的新类别场景,做特定的微调或特征提取。
4.3 两阶段训练与迁移策略
整个训练流程可以拆成两大步。
第一步,用base类别(数据充足的大类集合)训练完整的Faster R-CNN + RepMet分类模块。这个阶段主要是让RPN学会提框、让嵌入网络学会产生可分的特征分布、让回归分支学会精准定位。训练完成后,嵌入空间是稳定的。
第二步,迁移到novel类别(每类只有少量标注样本)。RPN和回归分支基本冻结,新类代表点可以从支持集样本的特征中估计出来,或者继续微调嵌入网络。这里需要平衡:如果完全冻结嵌入网络,新类的特征可能跟base类的特征不在一个可匹配的分布中;如果微调太多,又容易过拟合到那几张新类样本上。论文中建议微调时要使用非常小的学习率,并且只训练少量迭代轮数。
考虑到实现复杂度,我把改造检测器的关键决策点整理成下面的清单:
- RPN部分:保持base类训练权重,只做少量前景/背景自适应;
- 分类Head:删除原有线性分类层,替换为均值/方差可学习的RepMet模块;
- 回归Head:保持原有结构,base类回归能力对新类大多可用;
- 嵌入网络:微调时设置小学习率,幅度控制在10%以内变化;
- 支持集处理:使用ROIAlign+嵌入网络提取框内特征,估计新类代表点。
5. 复现RepMet的避坑记录与技术瓶颈
5.1 方差只学一个标量还是对角矩阵:最容易崩的地方
我在自己的复现项目里,把RepMet用在了一个图像检索任务中,当时最头疼的就是方差的处理。如果方差是标量,相当于所有维度同等缩放,模拟的是“各向同性高斯”,这会丢失很多空间信息;如果方差是对角矩阵,参数数量是嵌入维度D,这是论文里的做法,效果好一些。但要注意的是,对角方差的初始值不能太小或太大,我试过初始化为0.1,结果模型前期loss非常大,训练很不稳定;设为1.0后,收敛快了不少。
另外要强调:方差的参数约束必须保持为正。很多深度学习框架中直接在参数上做softplus映射比加一个eps更稳。我一开始用clamp(min=1e-6),后期方差会被推到上界附近,效果变差。改成softplus后问题解决。
5.2 子类数K与嵌入维度D怎么调
子类数K是灵活性来源之一,也是过拟合来源。在base类训练时,K可以设置得稍微大一些,比如8或者16,因为数据充足,能撑起这么多子类。但在新类别只有5个样本时,K如果还是8,就会出现子类内样本数为0的情况——有些代表点完全没被分配到样本,等于噪声。遇到这种情况,可以动态减少K,或者在估计新类代表点时采用样本特征聚类的方法,根据聚类得到的簇数来设定K。
嵌入维度D同样是重要的设计变量。论文中实验使用几百维,比如256或512。D越高,能表示的特征越丰富,但需要的样本量也越大。少样本场景下,D过高反而会让距离计算变得不稳定。我的经验是:先固定D=256,用分类准确率做一个快速基线,然后再根据曲线微调。如果数据集比较小,也可以先试128维。
5.3 训练不稳定时的自查清单
复现过程中我总结了一张排查问题的清单,分享给大家:
- 损失NaN:先检查方差是否被推到接近0,再检查log和exp里是否有极端值;
- 分类准确率上不去:确认是硬分配还是软分配,软分配更容易收敛;
- 训练收敛但测试很差:八成是嵌入网络过拟合到base类,考虑在嵌入层加正则化;
- 新类性能骤降:先看RPN对novel类的召回率,如果框都没提出来,问题不在RepMet分类模块;
- 距离矩阵计算OOM:使用分块计算或者限制支持的类别数。
提示:RepMet最容易被误解的地方是“只要有个好距离度量就能解决少样本问题”。实际上,检测场景里RPN能不能提好新类物体的框,往往比分类距离度量更关键。这是我踩坑之后最深的体会。
6. 从RepMet往后看:度量学习范式的扩展与个人体会
6.1 对比后续方法,RepMet缺了什么
RepMet在2018年提出的那个时间点,确实解决了少样本检测中的不少痛点,尤其是对类别分布建模的统计严谨性。但如果放到后续几年的研究脉络里看,它的不足也比较明显。首先是方差共享的假设太强——真实数据中不同类别的特征波动范围差别很大,自然图像里的“鸟”和“椅子”的特征方差不可能一致。后续有一些工作通过引入类别相关的协方差预测来缓解这个问题,但参数又上去了。
其次是代表点的估计方式。RepMet用EM风格迭代,这在base类数据充足的问题上还好,但在极端少样本(比如1-shot)时,EM迭代容易陷入局部最优。后续的很多方法倾向于直接在支持集上用transformer结构或者图神经网络做上下文建模,不再显式建模概率分布,而是让网络自己从支持集里“读出”模式。这两种思路各有取舍,但RepMet提供的是最扎实的统计基座。
6.2 要不要在自己的任务里用RepMet
最后聊聊适用性的判断。我自己在一些实际项目中尝试过把RepMet改造到细粒度检索和工业缺陷分类上,总体感受是:当你的类别结构有明显的“多模态”特性(比如同一个产品类别有多个型号、多个拍摄角度),RepMet的K子类建模能明显优于单原型方法;当你的类别数量特别多且每个类样本极少(比如上千个类、每类3张图)时,为每个类估计多个代表点会比较吃力,单原型加一个强嵌入网络反而更稳。
如果你的数据集本身类别很干净、类内差异小,那完全没必要引入子类和协方差建模,直接原型网络足够。但如果类内形态复杂、且你愿意花时间调K和方差初始值,RepMet的思路依然有很强的实用价值。从我复现的经验看,它的工程难度中等,核心代码量不大,主要精力都花在数学公式到代码的转换和数值稳定性调试上。对于想做少样本学习的人来说,这篇论文依然是值得精读的存在。