传统人脸识别训练里,softmax 交叉熵是最常用的分类目标,但它并没有显式告诉模型“同一类的人脸特征要尽量靠近”。只要样本被正确分到对应类别,softmax 就不再额外压缩类内距离,这导致学到的特征常常带着明显的身份无关信息,比如光照、姿态、表情带来的扰动。SphereFace 解决这个问题的方法很直接:把分类权重归一化,去掉偏置,让输出只由特征和类中心之间的夹角决定,再对正确类引入乘性角度间隔,从而在训练阶段就强制模型学习角度上更紧凑的类内分布。

从 Softmax 到角度间隔:SphereFace 的核心改造
普通 softmax 的输出可以写成 p_i = exp(W_i^T x + b_i) / Σ_j exp(W_j^T x + b_j),其中 x 是输入特征,W_i 是第 i 类的权重向量,b_i 是偏置。这样的形式同时依赖内积和偏置,特征模长越大,logits 越大,模型可以通过增大特征模长来降低损失,而不是真正让同类样本夹角变小。SphereFace 先把偏置置零,再对权重做 L2 归一化,使 ||W_i|| = 1。此时 W_i^T x = ||x|| cos(θ_i),θ_i 是 x 与第 i 类权重向量之间的夹角。分类概率就只由 ||x|| 和夹角 θ_i 决定,决策边界也变成了只比较 cos(θ_1) 和 cos(θ_2) 的大小。
这个中间形态通常叫 modified softmax。它把特征空间从欧氏距离主导拉回到角度空间,但并没有给类内分布增加约束。SphereFace 进一步提出 A-Softmax,在正确类对应的角度上乘一个整数 m,用 cos(mθ_y) 替代原来的 cos(θ_y),损失函数变成:
L = -1/N Σ log( exp(||x_i|| cos(mθ_{y_i})) / ( exp(||x_i|| cos(mθ_{y_i})) + Σ_{j≠y_i} exp(||x_i|| cos(θ_j)) ) )
这样一来,模型不能只让正确类的 cos(θ_y) 略大于其他类的 cos(θ_j),而是必须把正确类的夹角缩小到原来的 1/m 量级,才能在经过 m 倍放大后仍然保持余弦值优势。这个乘性间隔直接作用在角度上,比单纯增大特征模长要严格得多。
决策边界如何随角度间隔改变
以二分类为例,modified softmax 的分类边界是 θ_1 = θ_2。也就是说,样本与两个类中心夹角相同时正好落在边界上。A-Softmax 对于类别 1 的样本要求 cos(mθ_1) > cos(θ_2),在 θ 位于 [0, π] 范围且余弦单调递减的前提下,等价于 mθ_1 < θ_2,即 θ_1 < θ_2 / m。对于类别 2 的样本则要求 θ_2 < θ_1 / m。于是边界从一条 θ_1 = θ_2 的直线,分裂成两条更靠近各自类别中心的方向,这个区域就是角度间隔。
例如 m = 2 时,属于类别 1 的样本与类别 2 中心的夹角至少要比它与类别 1 中心夹角的 2 倍还大。模型如果想把损失压到很低,就必须把同类样本压缩到非常小的角度范围内。m 越大,类内角度约束越强,但训练也越困难,因为 cos(mθ) 在 θ 超过 π/m 后会失去单调性。
这是 SphereFace 最主要的实现难点。原始论文使用了一个分段单调函数 ψ(θ) = (-1)^k cos(mθ) - 2k,其中 θ 落在 [kπ/m, (k+1)π/m] 区间,来保证函数在整个角度范围内单调递减。这个设计虽然严谨,但实现复杂,训练初期容易震荡。后续 CosFace 和 ArcFace 改用加性间隔,比如 cos(θ + m) 或 cosθ - m,避免了乘性间隔的非单调问题,同时保留了角度判别的核心思想。
A-Softmax 的简化实现与训练细节
实际复现时,很多开源实现会在训练后期直接替换正确类的余弦值,而不是严格实现分段单调函数。下面给出一个简化的 PyTorch 版本,只用于理解角度间隔的注入方式。
import torch
import torch.nn as nn
import torch.nn.functional as F
class SphereFaceLoss(nn.Module):
def __init__(self, in_features, n_classes, m=4, s=64.0):
super(SphereFaceLoss, self).__init__()
self.m = m
self.s = s
self.weight = nn.Parameter(torch.FloatTensor(n_classes, in_features))
nn.init.xavier_uniform_(self.weight)
self.ce = nn.CrossEntropyLoss()
def forward(self, input, label):
# 权重逐行归一化,每个类中心变成单位向量
w = F.normalize(self.weight, dim=1)
# 输入特征保留模长,用于提供缩放尺度
cos_theta = F.linear(input, w)
theta = torch.acos(torch.clamp(cos_theta, -1.0 + 1e-7, 1.0 - 1e-7))
one_hot = torch.zeros_like(cos_theta)
one_hot.scatter_(1, label.view(-1, 1).long(), 1.0)
# 只对真实类别使用乘性角度间隔
cos_m_theta = torch.cos(self.m * theta)
output = cos_theta * (1 - one_hot) + cos_m_theta * one_hot
output = output * self.s
loss = self.ce(output, label)
return loss
这段代码先把权重归一化,然后计算输入特征与每个类中心之间的余弦值,再对真实类别对应的余弦值做 m 倍角度变换。注意代码里没有处理 cos(mθ) 的非单调区域,因此它只适合小规模实验或损失理解,不能直接用于生产级训练。真正复现 SphereFace 时,一般会在前几个 epoch 使用普通 softmax 或 modified softmax 进行预热,之后逐步增大 m,让模型稳定过渡到角度间隔约束。
训练数据方面,SphereFace 通常配合较大的 batch size 和 SGD 优化器,特征维度常设为 512。归一化权重后,logits 的尺度由特征模长决定,但不同样本的模长差异会带来损失波动,所以很多实现会额外乘一个固定尺度 s,例如 64,来稳定梯度。推理阶段则完全丢弃分类头,只使用主干网络输出的嵌入特征,再对特征做 L2 归一化后计算余弦相似度,损失函数的 margin 只在训练时生效。
SphereFace 的效果与局限
SphereFace 在 LFW、YTF 以及 MegaFace 等基准上取得了当时很有竞争力的结果,尤其是 MegaFace 这种大规模干扰集场景,说明角度间隔能有效降低不同身份之间的误匹配。它的价值更多在于证明了“在角度空间加间隔”是一条可行的路线。后来的 CosFace、ArcFace 基本沿用了这个思路,只是把乘性 margin 改成加性 margin,从而简化实现并提升训练稳定性。
不过 SphereFace 本身存在几个问题。首先是 cos(mθ) 的非单调性,让优化过程对学习率和初始化非常敏感;其次是参数 m 需要手动调节,过大会导致收敛困难,过小则判别力不足。再者,SphereFace 没有对输入特征做归一化,特征模长仍然会参与损失计算,这与后续一些方法强制 ||x|| = 1 的做法不同。理解这些局限,有助于在实际项目里选择更稳定的角度间隔损失,而不是盲目照搬原始实现。
如果读者想在自有人脸数据集上验证角度间隔思路,建议从 modified softmax 开始训练一个基线,再尝试 ArcFace 或 CosFace。SphereFace 更适合作为理解角度间隔起源的切入点。它的核心贡献在于把分类任务重新定义为一个角度度量问题,这一视角在目前的人脸识别、说话人识别甚至细粒度图像识别中仍然广泛沿用。
SphereFace角度间隔Softmax人脸识别修改时间:2026-09-19 06:34:03