circle loss代码实现_一种写法统一人脸Loss:Pytorch实现及Mnist可视化

↑ 点击 蓝字  关注极市平台 8d5f76b2af3dc3d3d8c195e0ff79655b.gif 作者丨赵赫 Mccree 来源丨 Smarter 编辑丨极市平台

极市导读

 

本文主要总结了softmax、Modified Softmax、NormFace、SphereFace、InsightFace、Center Loss六种loss的Pytorch实现以及Mnist的可视化实验,让大家能够借助代码更深刻的理解Loss的设计以及直观的比较各种Loss的有效性。 >>加入极市CV技术交流群,走在计算机视觉的最前沿

写在前面

这篇文章的重点不在于讲解FR的各种Loss,因为知乎上已经有很多,搜一下就好,本文主要提供了各种Loss的Pytorch实现以及Mnist的可视化实验,一方面让大家借助代码更深刻地理解Loss的设计,另一方面直观的比较各种Loss的有效性,是否涨点并不是我关注的重点,因为这些Loss的设计理念之一就是增大收敛难度,所以在Mnist这样的简单任务上训练同样的epoch,先进的Loss并不一定能带来点数的提升,但从视觉效果可以明显的看出特征的分离程度,而且从另一方面来说,分类正确不代表一定能能在用欧式/余弦距离做1:1验证的时候也正确。 本文主要仿照CenterLoss文中的实验结构,使用了一个相对复杂一些的LeNet升级版网络,把输入图片Embedding成2维特征向量以便于可视化。 对了,代码里用到了TensorBoardX来可视化,当然如果你没装,可以注释掉相关代码,我也写了本地保存图片,虽然很不喜欢TensorFlow,但TensorBoard还是真香,比Visdom强太多了。 早就想写这篇文章了,趁着五一假期终于... 具体代码在Github: github.com/MccreeZhao/F  有兴趣的话点个Star呀~虽然刚起步还没什么东西 文章里只展示loss写法

Softmax

公式推导

a7ab135b-8832-eb11-8da9-e4434bdf6706.svg

Pytorch代码实现

class Linear(nn.Module):def __init__(self):super(Linear, self).__init__()self.weight = nn.Parameter(torch.Tensor(2, 10))  # (input,output)
nn.init.xavier_uniform_(self.weight)def forward(self, x, label):
out = x.mm(self.weight)
loss = F.cross_entropy(out, label)return out, loss
emmm...现实生活中根本没人会这么写好吧!明明就有现成的Linear层啊喂! 写成这样只是为了方便统一框架...

可视化

00eb52f6d0162f0cbf3a29d1f0e28e5f.png
这一张图是二维化的特征,注意观察不同两类任意点之间的余弦距离和欧氏距离。
1288aa8d6cbebff2b871a90cb060f829.png
这张图是将特征归一化的结果,能更好的反映余弦距离,竖线是该类在最后一个FC层的权重,等同于类别中心(这一点对于理解loss的发展还是挺关键的)。 后面的图片也都是这种形式,大家可以比较着来看。

Modified Softmax

公式推导

b3ab135b-8832-eb11-8da9-e4434bdf6706.svg 去除了权重的模长和偏置对loss的影响,将特征映射到了超球面,同时避免了样本量差异带来的预测倾向性(样本量大可能导致权重模长偏大)。

Pytorch代码实现

class Modified(nn.Module):def __init__(self):super(Modified, self).__init__()self.weight = nn.Parameter(torch.Tensor(2,10))#(input,output)
nn.init.xavier_uniform_(self.weight)self.weight.data.uniform_(-1,1).renorm_(2,1,1e-5).mul_(1e5)#因为renorm采用的是maxnorm,所以先缩小再放大以防止norm结果小于1def forward(self, x):
w=self.weight
ww=w.renorm(2,1,1e-5).mul(1e5)
out = x.mm(ww)return out

可视化

65f1b44e007bb36bb7be47f3b854703c.png
2ce995e1422c227474a0f673f17fdfc2.png
这里要提一句,如果大家留心的话可以发现,虽然modified loss并没有太好的聚拢效果,但确让类别中心准确地落在了feature的中心,这对于网络的性能是有很大好处的,但是具体原因我没想出来...希望能有大佬在评论区给解释一下...

NormFace

既然权重的模长有影响,Feature的模长必然也有影响,具体还是看文章,另外,质量差的图片feature模长往往较短,做normalize之后消除了这个影响,有利有弊,还没有达成一致观点,目前主流的Loss还是包括feature normalize的

公式推导

bdab135b-8832-eb11-8da9-e4434bdf6706.svg

可视化

3fe601921e98320bc4cc66f96f518d74.png
97bef412ee0f9412cc0a6fe8fbcd4dec.png
就是一个字:猛!感觉有了NormFace,后面的花式Loss都体现不出来效果了...

Pytorch代码实现

class NormFace(nn.Module):def __init__(self):super(NormFace, self).__init__()self.weight = nn.Parameter(torch.Tensor(2, 10))  # (input,output)
nn.init.xavier_uniform_(self.weight)self.weight.data.uniform_(-1, 1).renorm_(2, 1, 1e-5).mul_(1e5)self.s = 16# 因为renorm采用的是maxnorm,所以先缩小再放大以防止norm结果小于1def forward(self, x, label):
cosine = F.normalize(x).mm(F.normalize(self.weight, dim=0))
loss = F.cross_entropy(self.s * cosine, label)return cosine, loss

SphereFace:A-softmax

为了进一步约束特征向量之间的余弦距离,我们人为地增加收敛难度,给两个向量之间的夹角乘上一个因子:m

公式推导

c7ab135b-8832-eb11-8da9-e4434bdf6706.svg

Pytorch代码实现

class SphereFace(nn.Module):def __init__(self, m=4):super(SphereFace, self).__init__()self.weight = nn.Parameter(torch.Tensor(2, 10))  # (input,output)
nn.init.xavier_uniform_(self.weight)self.weight.data.renorm_(2, 1, 1e-5).mul_(1e5)self.m = mself.mlambda = [ # calculate cos(mx)lambda x: x ** 0,lambda x: x ** 1,lambda x: 2 * x ** 2 - 1,lambda x: 4 * x ** 3 - 3 * x,lambda x: 8 * x ** 4 - 8 * x ** 2 + 1,lambda x: 16 * x ** 5 - 20 * x ** 3 + 5 * x
]self.it = 0self.LambdaMin = 3self.LambdaMax = 30000.0self.gamma = 0def forward(self, input, label):# 注意,在原始的A-softmax中是不对x进行标准化的,# 标准化可以提升性能,也会增加收敛难度,A-softmax本来就很难收敛
cos_theta = F.normalize(input).mm(F.normalize(self.weight, dim=0))
cos_theta = cos_theta.clamp(-1, 1) # 防止出现异常# 以上计算出了传统意义上的cos_theta,但为了cos(m*theta)的单调递减,需要使用phi_theta
cos_m_theta = self.mlambda[self.m](cos_theta)# 计算theta,依据theta的区间把k的取值定下来
theta = cos_theta.data.acos()
k = (self.m * theta / 3.1415926).floor()
phi_theta = ((-1) ** k) * cos_m_theta - 2 * k
x_norm = input.pow(2).sum(1).pow(0.5) # 这个地方决定x带不带模长,不带就要乘s
x_cos_theta = cos_theta * x_norm.view(-1, 1)
x_phi_theta = phi_theta * x_norm.view(-1, 1)############ 以上计算target logit,下面构造loss,退火训练#####self.it += 1 # 用来调整lambda
target = label.view(-1, 1) # (B,1)
onehot = torch.zeros(target.shape[0], 10).cuda().scatter_(1, target, 1)
lamb = max(self.LambdaMin, self.LambdaMax / (1 + 0.2 * self.it))
output = x_cos_theta * 1.0 # 如果不乘可能会有数值错误?
output[onehot.byte()] -= x_cos_theta[onehot.byte()] * (1.0 + 0) / (1 + lamb)
output[onehot.byte()] += x_phi_theta[onehot.byte()] * (1.0 + 0) / (1 + lamb)# 到这一步可以等同于原来的Wx+b=y的输出了,# 到这里使用了Focal Loss,如果直接使用cross_Entropy的话似乎效果会减弱许多
log = F.log_softmax(output, 1)
log = log.gather(1, target)
log = log.view(-1)
pt = log.data.exp()
loss = -1 * (1 - pt) ** self.gamma * log
loss = loss.mean()# loss = F.cross_entropy(x_cos_theta,target.view(-1))#换成crossEntropy效果会差return output, loss

可视化

ecb2ec3e99febf13f232369a4e0efdb2.png
09f4d8dc4a0155e0092397dd0c911447.png

InsightFace(ArcSoftmax)

公式推导

d0ab135b-8832-eb11-8da9-e4434bdf6706.svg

Pytorch代码实现

class ArcMarginProduct(nn.Module):def __init__(self, s=32, m=0.5):super(ArcMarginProduct, self).__init__()self.in_feature = 2self.out_feature = 10self.s = sself.m = mself.weight = nn.Parameter(torch.Tensor(2, 10))  # (input,output)
nn.init.xavier_uniform_(self.weight)self.weight.data.renorm_(2, 1, 1e-5).mul_(1e5)self.cos_m = math.cos(m)self.sin_m = math.sin(m)# 为了保证cos(theta+m)在0-pi单调递减:self.th = math.cos(3.1415926 - m)self.mm = math.sin(3.1415926 - m) * mdef forward(self, x, label):
cosine = F.normalize(x).mm(F.normalize(self.weight, dim=0))
cosine = cosine.clamp(-1, 1) # 数值稳定
sine = torch.sqrt(torch.max(1.0 - torch.pow(cosine, 2), torch.ones(cosine.shape).cuda() * 1e-7)) # 数值稳定##print(self.sin_m)
phi = cosine * self.cos_m - sine * self.sin_m # 两角和公式# # 为了保证cos(theta+m)在0-pi单调递减:# phi = torch.where((cosine - self.th) > 0, phi, cosine - self.mm)#必要性未知#
one_hot = torch.zeros_like(cosine)
one_hot.scatter_(1, label.view(-1, 1), 1)
output = (one_hot * phi) + ((1.0 - one_hot) * cosine)
output = output * self.s
loss = F.cross_entropy(output, label)return output, loss

可视化

d3c9d84c36a698916c23b1a8c032a5e3.png
c8ffb26bfa931eb29db7523ee39ad5e3.png
ArcSoftmax需要更久的训练,这个收敛还不够充分...颜值堪忧,另外ArcSoftmax经常出现类别在特征空间分布不均匀的情况,这个也有点费解,难道在训FR模型的时候先用softmax然后慢慢加margin有奇效?SphereFace那种退火的训练方式效果好会不会和这个有关呢...

Center Loss

乱入一个欧式距离的细作

公式推导

dbab135b-8832-eb11-8da9-e4434bdf6706.svg 其中  dfab135b-8832-eb11-8da9-e4434bdf6706.svg  是每个类别对应的一个中心,在这里就是一个二维坐标啦。

Pytorch代码实现

class centerloss(nn.Module):def __init__(self):super(centerloss, self).__init__()self.center = nn.Parameter(10 * torch.randn(10, 2))self.lamda = 0.2self.weight = nn.Parameter(torch.Tensor(2, 10))  # (input,output)
nn.init.xavier_uniform_(self.weight)def forward(self, feature, label):
batch_size = label.size()[0]
nCenter = self.center.index_select(dim=0, index=label)
distance = feature.dist(nCenter)
centerloss = (1 / 2.0 / batch_size) * distance
out = feature.mm(self.weight)
ceLoss = F.cross_entropy(out, label)return out, ceLoss + self.lamda * centerloss
这里实现的是center的部分,还要跟原始的CEloss相加的,具体看github吧。

可视化

744bdb07e02df53dd679bfd6d183d207.png
4e31cb26017b5265b5dd230133a59d99.png
会不会配合weight norm效果更佳呢?以后再说吧...

总结

先写到这里,如果大家有兴趣可以去github点个star之类的...作为一个研一快结束的弱鸡刚刚学会使用github...也是没谁了... 参考文献: Wang M, Deng W. Deep face recognition: A survey[J]. arXiv preprint arXiv:1804.06655, 2018.

推荐阅读

  • 人脸识别损失函数简介与Pytorch实现:ArcFace、SphereFace、CosFace

  • CVPR 2020 Oral | 人脸识别Loss新突破: 旷视提出Circle Loss,革新深度特征学习范式

  • 港中文开源基于PyTorch的多任务人脸识别框架

9e275950510880201d05a4a9d67c4752.png

添加极市小助手微信(ID : cvmart2),备注:姓名-学校/公司-人脸-城市(如:小极-北大-人脸-深圳),即可申请加入极市人脸等技术交流群:每月大咖直播分享、真实项目需求对接、求职内推、算法竞赛、干货资讯汇总、与 10000+来自港科大、北大、清华、中科院、CMU、腾讯、百度等名校名企视觉开发者互动交流~ 770e318af0f6d5e8c888c4ecc4cd1c5c.png △长按添加极市小助手 7679b3416fcb2c284049ce98ea001ccf.png △长按关注极市平台,获取 最新CV干货 觉得有用麻烦给个在看啦~   7723cb32e993b019ea44063acd30fb07.gif
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值