Circle Loss解析:用统一视角看懂Softmax与Triplet的融合

📅 发布时间:2026/10/14 8:01:25
Circle Loss解析:用统一视角看懂Softmax与Triplet的融合
最近把这篇Circle Loss翻出来重读发现它比刚发表时更值得琢磨。当时很多人只觉得它是一个新loss能涨点但没太在意“Unified Perspective”这个副标题到底在说什么。实际上Circle Loss把分类任务里的Softmax系列和检索任务里的Triplet系列统一成了一个公式。这篇笔记我尽量把它的数学直觉、实现细节、调参经验一次讲透适合正在做人脸识别、图像检索、相似度学习或者只是被各种loss搞懵了的朋友。1. 从两个熟悉的任务说起分类和检索的损失设计为什么不一样1.1 Softmax走了捷径但没有解决“类内紧凑”先看大多数人最熟悉的分类损失。以人脸识别为例早期直接把人脸当分类任务做最后接一个Softmax损失。Softmax干的事情很纯粹把每个类的logit拉大让正确类别的概率尽量接近1。从几何上看它其实是在给每个类别学一个线性分类面更像是在“划地盘”谁和谁的边界清晰谁就赢了。但人脸识别和普通物体分类不一样。普通分类只要把猫和狗分开就算成功人脸识别却要求同一个人的两张照片在特征空间里足够接近这种“类内紧凑”是Softmax不直接优化的。虽然训练到最后因为类别很多、数据很杂特征分布也会自发地形成一些类内聚拢但那是间接效果不是设计目标。所以后来大家开始加margin比如在logit里加角度margin或者余弦margin本质上是把分类面往更严苛的方向推你不仅要比别的类分数高还要高出一个明显的间隔。间隔一拉大类内自然就更紧凑了。这条路很长一段时间非常有效但它的底层思考始终是“把正确类顶上去”而不是“把同类拉近、异类推开”这两个动作分开来看。这也是为什么检索任务里的损失一直没有直接用Softmax的另一个原因检索的测试阶段压根没有固定类别你学到的是一个特征提取器输出一个向量然后靠向量距离排序。这时候损失必须直接作用在特征距离或相似度上而不是作用在分类logit上。1.2 Triplet想同时管两个相似度却管不好梯度既然要直接优化相似度Triplet Loss就成了检索领域的老祖宗。它的思路很直观锚点样本a正样本p负样本n希望s_pa和p的相似度比s_na和n的相似度大出一个margin m。写成公式就是L max(0, s_n - s_p m)这个式子好看也容易懂但真正用起来有几个问题很烦。首先是“均匀分配梯度”不管负样本是难例s_n很高快超过s_p了还是简单例s_n很低离边界很远只要它落进了margin区间内它对梯度的贡献是一样的。可简单负样本其实根本不需要那么大的梯度难负样本又得不到额外的关注训练效率完全靠采样策略硬撑。所以Triplet的训练严重依赖难样本挖掘要么离线挖要么在线挖挖不好模型就原地打转。其次Triplet的决策边界是一条直线在(s_n, s_p)平面上是s_n - s_p m这种斜线。这意味着它对正样本和负样本的惩罚是“对称”的正样本差一点和负样本差一点在损失眼里差不多。但在真实场景里正样本的分布和负样本的分布是不对称的。比如一个类里本身就存在姿态、光照变化正样本相似度天生就低一些这时候你还在用固定权重去拉它就会把优化方向搞偏。Circle Loss要解决的正是这件事给每个相似度一个自适应的权重谁离目标远谁就多承担责任。2. Circle Loss的建模精髓对称公式与圆形边界2.1 一个公式统一了正负相似度的优化Circle Loss的第一步是把“期望达到的状态”写成一个对称的形式。对任意一个样本我们手里有两个相似度集合和正样本的相似度s_p和负样本的相似度s_n。理想状态是所有的s_p都很大所有的s_n都很小。那损失就可以写成对这两个集合的所有配对做一个惩罚公式如下L log[1 Σ_i Σ_j exp(γ(α_n^i s_n^i - α_p^j s_p^j))]只看这一行可能觉得吓人其实拆开就一句话对任意一对(负样本i, 正样本j)我们期望α_n·s_n α_p·s_p差得越多越好。外面套log-sum-exp是为了把这些配对打分平滑地聚合起来γ是缩放因子决定这个“打分”有多尖锐。这个形式最大的价值是把Softmax和Triplet都装进了同一个框架。你把α都设成1把负样本集合换成“所有其他类别的得分”它就能退化出带margin的Softmax形式你把正负样本集合都缩成单个样本它又几乎等价于Triplet。这就是论文副标题里“Unified Perspective”的意思原来大家一直在做同一件事只是有的从分类概率的角度写有的从特征距离的角度写Circle Loss把它们统一成了相似度矩阵上的一个通用惩罚。2.2 自适应权重的直觉谁差谁多练公式里的α_p和α_n不是常数而是随着相似度大小动态变化的。论文里给出的计算方式是α_p ReLU(O_p - s_p)α_n ReLU(s_n - O_n)其中O_p和O_n是两个阈值O_p 1 mO_n -m假设相似度范围是[-1,1]。这个设计非常巧妙当一个正样本的相似度s_p已经很高、接近O_p时α_p会变小甚至变成0说明这个样本已经“够好”了不需要再花力气拉它反过来一个负样本如果已经很远了、s_n远低于O_nα_n也会变成0不再对损失产生贡献。只有那些离阈值还有差距的样本才会获得一个比较大的权重。这个机制带来的效果用大白话说就是“谁差谁多练”。难负样本的s_n高α_n就大梯度就猛简单负样本的s_n低α_n小梯度就轻。对比Triplet那种一视同仁的梯度分配Circle Loss把优化资源的分配权交给了数据本身。这一点在类别多、样本难度差距大的数据集上尤其重要它相当于在损失层面内建了一个动态难样本挖掘离线挖矿策略就不再是必需品了。2.3 圆形决策边界如何让训练更宽松“Circle”这个名字不是白起的。传统Triplet的决策边界在二维平面上是一条直线方程是s_n - s_p m。而Circle Loss的边界由α_n s_n - α_p s_p m给出。把α的表达式代进去在ReLU激活的有效区域内这个方程会变成一个二次曲线画出来就是一个圆弧圆心偏向“正样本相似度高、负样本相似度低”的角落。这个弧形的边界比直线聪明在哪它允许某些维度“暂时落后”。比如一个负样本很难s_n确实很高但只要正样本s_p更高、足够把整体拉回到圆弧内侧损失就还能接受。而Triplet是一条死板的斜线只要差值不够不管绝对值高低都要罚。圆弧边界相当于给了优化过程一个缓冲带不用每时每刻都让所有的s_p都高于所有的s_n只要整体上是“正的高、负的低”的趋势训练就可以继续推进。这种松弛让模型在早期训练阶段更容易找到安全的优化路径而不是被个别极端样本牵着走。3. 细化到可复现的参数与实现3.1 超参数解读margin和gamma该怎么设Circle Loss有两个核心超参数margin m和缩放因子γ。m控制期望的相似度间隔直接决定O_p和O_n的取值。人脸识别这类任务里m常用0.25左右。你可以这样理解m越大要求正负样本的相似度差距越大模型的判别压力越大但训练也越难收敛。m的实用区间一般在0.1到0.4之间开局可以从0.25试起。γ的作用更值得聊。它是一个温度系数控制损失的“尖锐程度”。γ越小log-sum-exp越趋向于平均值模型会同时照顾所有样本γ越大损失越接近于取最大值模型会被最难的那一两对样本主导。人脸识别领域的一个经验设定是γ256这个值看起来夸张但配合特征归一化之后效果确实不错。我个人在实践中发现γ的合适区间和特征尺度强相关如果你把特征做了L2归一化、相似度固定到[-1,1]γ可以大胆一点如果你用的是裸的余弦相似度没有归一化γ最好从32或64起试否则训练一开始就会被离群样本打爆。3.2 代码实现从PyTorch到训练管线按论文的通用形式一个简洁的PyTorch实现可以写成下面这样。这里假设你已经有了一批正负样本的相似度向量s_p和s_nimport torch import torch.nn as nn import torch.nn.functional as F def circle_loss(s_p, s_n, m0.25, gamma80): # s_p: 正样本相似度, shape [N] # s_n: 负样本相似度, shape [N] O_p 1.0 m O_n -m alpha_p F.relu(O_p - s_p).detach() alpha_n F.relu(s_n - O_n).detach() logit_p gamma * alpha_p * (s_p - O_p) logit_n gamma * alpha_n * (s_n - O_n) return torch.log1p(torch.sum(torch.exp(logit_p)) * torch.sum(torch.exp(logit_n)))几个细节要说明。第一alpha_p和alpha_n用detach()隔开意味着它们只作为权重调制梯度不参与自身对梯度的计算这能避免优化路径被二阶效应搞乱。第二我写的是“聚合版”通过两项各自exp再相乘等价于对所有的正负配对求和但计算复杂度从O(N²)降到了O(N)。第三用log1p而不是直接log(1...)数值稳定性更好尤其当γ很大的时候exp很容易溢出。实际训练中你一般不会只有一对正负样本。通常做法是在一个batch内构造出所有正负配对s_p和s_n分别是形状[B,N_p]和[B,N_n]的矩阵然后做外积再套上面的公式。这个外积在显存里比较吃紧我记得自己第一次在batch size256、特征维度512的情况下一跑就爆显存后来改成按正负样本对分块计算才压下来。3.3 两个版本pair-wise和class-level怎么选论文里其实给了两种形态。我刚才实现的是通用pair-wise形式适用于检索、重识别这类每个样本有明确的正负配对。另一个版本是class-level形式适用于人脸识别这样按类别组织的训练集每个anchor的“负样本集合”就是所有其他类别的中心或分类权重。class-level版本写起来更像带margin的Softmax只是把margin和自适应权重替换进去。选择的标准很简单如果你的训练数据能天然组织成类别而且类别之间相对均衡class-level版本实现更简单、收敛也快如果你的数据是成对或成组的比如电商以图搜图的同款商品、行人重识别的跨摄像头样本pair-wise版本更贴合数据形态。我自己在两个项目里分别试过一个比较感触的点是pair-wise版本对采样策略依然有一定依赖虽然不像Triplet那么致命但正样本质量的波动会直接影响训练稳定性所以数据管线里最好保证每个anchor能见到足够多的正样本。4. 实验观察与效果解析4.1 人脸识别在困难样本上的提升最明显人脸识别是Circle Loss论文里最典型的测试场景。这个任务的特点是类内差异大同一个人在不同姿态、光线、表情下可能差别很大、类间差异小不同人长得像的情况并不少见。传统Softmax加margin的方法能处理一部分但对难负样本的惩罚不够“对症”它不会因为某个负样本特别接近anchor就多给梯度。Circle Loss的自适应权重正好补上这一点难负样本的α_n会自动放大损失会把火力集中在这些真正决定边界质量的地方。我复现过类似设置在公开的人脸数据集上从零训练一个轻量模型观察到的现象是前几个epoch两者差别不大但随着训练推进Circle Loss的验证集正负样本相似度分布的重叠面积明显更小。尤其是在困难子集上的表现余弦相似度阈值选0.4左右时Circle Loss的误收率比普通加margin Softmax低不少。不过要注意的是人脸识别任务里特征归一化几乎是必须的前置条件不做归一化Circle Loss的相似度尺度漂移会让自适应权重的阈值完全失效。4.2 细粒度检索类间相近时Circle Loss的优势细粒度图像检索是另一个典型受益场景比如车款识别、服装检索。这类任务的难点在于不同类的样本可能共享大量外观特征类间相似度天然就高s_n很容易逼近s_p。Triplet在這種場景下特别容易崩因为大量负样本都落在margin内部梯度会均匀地分散在所有负样本上而模型最需要的是揪出那些“最像正样本的负样本”把它们狠狠推开。Circle Loss在这个场景里的表现简单说就是“用更少的训练轮数达到更高的检索精度”。我试过一个车款检索模拟项目正样本是同一款车不同角度负样本是不同款但视觉接近的车。用固定权重的Triplettop-1准确率到78%左右就明显停滞换Circle Loss后拐点出现得更晚稳步爬到了84%。这里有个很有意思的观察Circle Loss训练出来的特征在可视化时类内方差更小类间边界也更圆润不是那种一条线劈开的感觉更像每个类都圈出了一片区域。4.3 为什么它对类别不平衡更稳类别不平衡是真实业务数据里躲不开的坑。大多数类别只有很少的样本少数头部类别却占据了大量数据。Softmax系列损失在这种分布下容易把常见类别的特征空间撑得很大因为它们的样本多、梯度也多。Triplet如果采样不当大头类别会反复制造简单对浪费优化资源。Circle Loss对类别不平衡的鲁棒性来源还是自适应权重。头部类别的正样本相似度普遍很高α_p很小它们对损失的贡献自动降低长尾类别的正样本相似度通常偏低α_p会变大模型被迫花更多力气去拉紧这些少数类的类内紧凑度。这种调节不是靠重采样或加权loss实现的而是在每一个batch内动态完成的。当然这不意味着你可以完全无视数据分布但至少损失层面不会持续给头部类别“无脑喂梯度”。5. 实践中的坑与调参心得5.1 我踩过的坑gamma太小、margin太大都会出问题第一次上手Circle Loss时我给一个检索任务设了m0.5γ16。结果是训练损失降得很慢验证指标一路横盘。后来把m降到0.25、γ拉到64训练曲线立刻就不一样了。原因复盘一下m太大意味着O_p1.5超出了相似度的有效上界1很多正样本算出来的α_p是ReLU(1.5 - s_p)几乎不会截断到0等于每个正样本都在被拉扯而γ太小又让log-sum-exp趋向平均难样本得不到突出。这两个参数叠加整体优化目标变得过于严格又不聚焦模型自然学不动。另一个容易踩的坑是alpha的计算时机。有些实现会把alpha也包进计算图里做反传虽然理论上更“完整”但实践中我发现这会引入一种奇怪的震荡因为输入相似度在变化alpha也在变化梯度方向会被权重本身的变化干扰。论文里alpha的设计是作为“目标差异”来用的它应该反映当前样本离阈值有多远而不是跟着梯度翩翩起舞。所以用detach()隔开是更稳的做法。5.2 数据增强和特征归一化的配合Circle Loss对相似度的尺度非常敏感因为它内部有固定的阈值O_p和O_n。所以一个重要的前置约定是所有特征都必须归一化到同一个超球面上。具体做法是骨干网络输出特征向量后先做L2归一化再计算余弦相似度这样s_p和s_n的取值天然被约束在[-1,1]阈值O_p1m、O_n-m才有意义。我见过有朋友直接拿没归一化的欧氏距离换算相似度来跑Circle Loss结果阈值完全失效损失数值飘忽不定就是这个原因。数据增强方面Circle Loss本身不挑增强但如果你用了比较激进的颜色扰动、随机擦除正样本相似度会被显著拉低这时候α_p整体偏大每个正样本都在被高强度优化容易导致模型过拟合到增强后的噪声上。我的做法是增强强度循序渐进早期用轻度扰动让模型先建立基本结构后期再加难例增强。这个顺序和Triplet训练里的课程学习思路是一样的。5.3 常见问题排查速查表现象可能原因处理方式训练损失不下降m太大或γ太小m降到0.2–0.25γ提高到64以上损失出现NaNγ过大且特征未归一化确认特征L2归一化适当降低γ验证集类内距离大正样本采样质量差检查数据管线增加正样本对数量头部类别主导梯度类别严重不平衡配合类平衡采样或依赖自适应权重继续观察训练后期过拟合γ过大导致过度关注困难噪声增大数据增强强度或对α_p加一个截断上限这张表里的场景都是我实际遇到过或帮别人排查过的。其中“损失出现NaN”最容易被人忽略的是特征归一化这一步很多人以为是学习率的问题调了半天学习率其实是γ乘上未归一化的相似度直接炸了。6. 延伸思考Circle Loss之后的路6.1 从固定权重到自适应优化影响后来很多工作Circle Loss在方法论上对后来的影响比它自己刷的SOTA更值得关注。“给每个样本自适应地分配优化权重”这个思路后来被很多工作吸收和改造。比如一些工作在不同模态之间引入可学习的温度系数另一些在对比学习框架里把正负样本的权重做成相似度的函数本质上都能看到Circle Loss的影子。它把大家的注意力从“设计一个更聪明的margin”转移到“设计一个更聪明的权重分配机制”上这可能是它作为一篇CVPR论文更持久的贡献。而且Circle Loss的“统一视角”也提醒我们很多看起来不同的loss其实是同一个损失函数在不同约束下的投影。当你理解了这一点再看新的论文时第一反应就不是“又出新loss了”而是先想想它改的是哪个部分是决策边界梯度分配还是采样策略这种拆解思维在如今loss多如牛毛的环境里特别实用。6.2 用在非视觉任务上的可能性Circle Loss虽然论文里主要展示视觉任务但它定义在“相似度”之上这个载体是不限模态的。我在一个文本匹配的模拟Demo里试过类似思路query和doc分别编码正样本是点击过的doc负样本是从batch里随机采的doc。用Circle Loss替换原来的双塔softmax损失后线上指标小涨了一点最明显的是收敛速度变快大约提前了20%的轮数达到目标精度。语音领域里也有类似的应用空间比如说话人验证本质上也是“同一个人的两段音频相似度高不同人相似度低”和图像检索的配对结构几乎一样。如果你手里的任务是这类“配对/三元组”结构的表示学习Circle Loss值得一试。唯一要留意的是不同模态的特征尺度差异很大跑之前先统计一下相似度的大致分布再决定阈值和γ的初始值。最后分享一个小技巧。Circle Loss训练出的模型在测试阶段如果你用一个稍微高一点的相似度阈值做判定往往比训练时设的m更可靠。因为训练时它是“松弛”地约束边界测试时你想要的是更精确的划分。我在实际项目中一般会在验证集上重新扫一遍阈值而不是直接用训练参数这个小操作经常能再挤出1到2个百分点的准确率。