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

栏目: IT技术 · 发布时间: 6年前

加入极市 专业CV交流群,与 6000+来自腾讯,华为,百度,北大,清华,中科院 等名企名校视觉开发者互动交流!更有机会与 李开复老师 等大牛群内互动!

同时提供每月大咖直播分享、真实项目需求对接、干货资讯汇总,行业技术交流。关注  极市平台  公众号  , 回复  加群, 立刻申请入群~

来源: 知乎

作者:Uno Whoiam

链接: https://zhuanlan.zhihu.com/p/60747096

本文 已由作者授权转载,未经允许,不得二次转载。

一般来说,人脸识别分三步走:

  1. 找人脸: 图片中找出含人脸的区域框出来

  2. 对齐人脸: 将人脸的眼镜鼻子嘴巴等标出来,以此作为依据对齐人脸

  3. 识别: 将对齐的人脸进行识别,判定这张脸究竟是谁

本篇要介绍的损失函数,用于第三步骤,聚焦于更准确地识别这张脸究竟属于谁,本质上属于一个分类问题。

一言以蔽之ArcFace、SphereFace、CosFace三个损失函数相对于前辈们而 言,改进的一个核心思路就是:

只有平常(train)更刻苦的训练,才有可能在比赛中(test)中得到更好的结果。 

它们都对卷积神经网络提出了更高的目标,在训练阶段更为艰难,也因此让其成为了一个更好的分类器。

一、从前辈说起

先谈谈他们的前辈:  

维基百科介绍:

Softmax函数,或称归一化指数函数[1],是逻辑函数的一种推广。 它能将一个含任意实数的K维向量      “压缩”到另一个K维实向量     中,使得每一个元素的范围都在    

之间,并且所有元素的和为1。 该函数的形式通常按下面的式子给出:

 

简单来说 softmax 将一组向量进行压缩,使得到的向量各元素之和为 1,而压缩后的值便可以作为置信率,所以常用于分类问题。 另外,在实际运算的时候,为了避免上溢和下溢,在将向量丢进softmax之前往往先对每个元素减去其中最大值,即:

想了解更多,可以参考: 忆臻: softmax函数计算时候为什么要减去一个最大值?https://zhuanlan.zhihu.com/p/29376573

再谈谈一个容易搞混的东西:  

上面我们丢入一个长度为     的     向量,得到     ,而softmax loss呢,则是:

其中     是一个长度为     的one-hot向量,即     ,只有ground truth对应的     。 所以也可以简写为:

到这里我们不妨在看看交叉熵     :

其中     是真实分布,在分类任务中,     实际上等价于上面的     。 而     则是预测分布,在分类任务中     实际上等价于上面的     。 这样一来进行化简就得到:

我咋觉得这么眼熟呢...

所以,我们可以得到:

参考链接: https://blog.csdn.net/u014380165/article/details/77284921

二、SphereFace

论文地址: https://arxiv.org/pdf/1704.08063.pdf

要想增强     的分类能力,其实就是要在分布上做到两点:

  1. 让同类之间距离更近

  2. 让不同类之间距离更远

不妨继续看看   :

其中     代表两个向量     之间的夹角,如果对     归一化,将偏置     置为0,即     ,则有:

下标     表示     。

对于     我们乘上一个大于等于1的整数     :

这样不仅放大了类之间的距离,也因放大了同类     与     之间的间隔而使类内更聚拢。

不过上述公式仍有问题: 原来的     ,如今     超出了向量之间的夹角函数     定义域范围     咋办?

那就变个函数呗,把n个cos怼起来变成一个递减的连续的函数:

这样一来:

如此我们就得到了SphereFace的损失函数  

原论文则是:

其中     表示第    个样本,     表示第     个样本的     标签,    表示第    和样本     之间的夹角。

论文中的可视化图片:

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

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

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

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

pytorch代码实现:

# SphereFace
class SphereProduct(nn.Module):
    r"""Implement of large margin cosine distance: :
    Args:
        in_features: size of each input sample
        out_features: size of each output sample
        m: margin
        cos(m*theta)
    """

    def __init__(self, in_features, out_features, m=4):
        super(SphereProduct, self).__init__()
        self.in_features = in_features
        self.out_features = out_features
        self.m = m
        self.base = 1000.0
        self.gamma = 0.12
        self.power = 1
        self.LambdaMin = 5.0
        self.iter = 0
        self.weight = Parameter(torch.FloatTensor(out_features, in_features))
        nn.init.xavier_uniform(self.weight)

        # duplication formula
        # 将x\in[-1,1]范围的重复index次映射到y\[-1,1]上
        self.mlambda = [
            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
        ]
        """
        执行以下代码直观了解mlambda
        import matplotlib.pyplot as  plt
        mlambda = [
            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
        ]
        x = [0.01 * i for i in range(-100, 101)]
        print(x)
        for f in mlambda:
            plt.plot(x,[f(i) for i in x])
            plt.show()
        """

    def forward(self, input, label):
        # lambda = max(lambda_min,base*(1+gamma*iteration)^(-power))
        self.iter += 1
        self.lamb = max(self.LambdaMin, self.base * (1 + self.gamma * self.iter) ** (-1 * self.power))

        # --------------------------- cos(theta) & phi(theta) ---------------------------
        cos_theta = F.linear(F.normalize(input), F.normalize(self.weight))
        cos_theta = cos_theta.clamp(-1, 1)
        cos_m_theta = self.mlambda[self.m](cos_theta)
        theta = cos_theta.data.acos()
        k = (self.m * theta / 3.14159265).floor()
        phi_theta = ((-1.0) ** k) * cos_m_theta - 2 * k
        NormOfFeature = torch.norm(input, 2, 1)

        # --------------------------- convert label to one-hot ---------------------------
        one_hot = torch.zeros(cos_theta.size())
        one_hot = one_hot.cuda() if cos_theta.is_cuda else one_hot
        one_hot.scatter_(1, label.view(-1, 1), 1)

        # --------------------------- Calculate output ---------------------------
        output = (one_hot * (phi_theta - cos_theta) / (1 + self.lamb)) + cos_theta
        output *= NormOfFeature.view(-1, 1)

        return output

    def __repr__(self):
        return self.__class__.__name__ + '(' \
               + 'in_features=' + str(self.in_features) \
               + ', out_features=' + str(self.out_features) \
               + ', m=' + str(self.m) + ')'

三、CosFace

论文地址:https://arxiv.org/pdf/1801.09414.pdf

和SphereFace类似,CosFace也是从 的余弦表达形式入手,令 。与此同时,作者发现  对于分类并没有啥帮助,所以干脆将其固定 ,所以有:

应该代表归一化的    

接下来与上文     类似的是也引入了常数    ,不同的是这里的     是加上去的:

以上我们就得到了CosFace中提出的Large Margin Cosine Loss

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

# CosFace
class AddMarginProduct(nn.Module):
    r"""Implement of large margin cosine distance: :
    Args:
        in_features: size of each input sample
        out_features: size of each output sample
        s: norm of input feature
        m: margin
        cos(theta) - m
    """

    def __init__(self, in_features, out_features, s=30.0, m=0.40):
        super(AddMarginProduct, self).__init__()
        self.in_features = in_features
        self.out_features = out_features
        self.s = s
        self.m = m
        self.weight = Parameter(torch.FloatTensor(out_features, in_features))
        nn.init.xavier_uniform_(self.weight)

    def forward(self, input, label):
        # --------------------------- cos(theta) & phi(theta) ---------------------------
        cosine = F.linear(F.normalize(input), F.normalize(self.weight))
        phi = cosine - self.m
        # --------------------------- convert label to one-hot ---------------------------
        one_hot = torch.zeros(cosine.size(), device='cuda')
        # one_hot = one_hot.cuda() if cosine.is_cuda else one_hot
        one_hot.scatter_(1, label.view(-1, 1).long(), 1)
        # -------------torch.where(out_i = {x_i if condition_i else y_i) -------------
        output = (one_hot * phi) + ((1.0 - one_hot) * cosine)
        # you can use torch.where if your torch.__version__ is 0.4
        output *= self.s
        # print(output)

        return output

    def __repr__(self):
        return self.__class__.__name__ + '(' \
               + 'in_features=' + str(self.in_features) \
               + ', out_features=' + str(self.out_features) \
               + ', s=' + str(self.s) \
               + ', m=' + str(self.m) + ')'

四、ArcFace

论文地址:https://arxiv.org/pdf/1801.07698.pdf

话不多说,直接上公式:

可以看到和CosFace非常类似,只是将     作为角度加上去了,这样就强行拉大了同类之间的角度,使得神经网络更努力地将同类收得更紧。

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

伪代码实现步骤:

  1. 对   进行归一化

  2. 对   进行归一化

  3. 计算   得到预测向量 

  4. 从   中挑出与ground truth对应的值

  5. 计算其反余弦得到角度

  6. 角度加上m

  7. 得到挑出从   中挑出与ground truth对应的值所在位置的独热码

  8. 将   通过独热码放回原来的位置

  9. 对所有值乘上固定值 

代码实现:

# ArcFace
class ArcMarginProduct(nn.Module):
    r"""Implement of large margin arc distance: :
        Args:
            in_features: size of each input sample
            out_features: size of each output sample
            s: norm of input feature
            m: margin
            cos(theta + m)
        """

    def __init__(self, in_features, out_features, s=30.0, m=0.50, easy_margin=False):
        super(ArcMarginProduct, self).__init__()
        self.in_features = in_features
        self.out_features = out_features
        self.s = s
        self.m = m
        # Parameter 的用途:
        # 将一个不可训练的类型Tensor转换成可以训练的类型parameter
        # 并将这个parameter绑定到这个module里面
        # net.parameter()中就有这个绑定的parameter,所以在参数优化的时候可以进行优化的
        # https://www.jianshu.com/p/d8b77cc02410
        # 初始化权重
        self.weight = Parameter(torch.FloatTensor(out_features, in_features))
        nn.init.xavier_uniform_(self.weight)

        self.easy_margin = easy_margin
        self.cos_m = math.cos(m)
        self.sin_m = math.sin(m)
        self.th = math.cos(math.pi - m)
        self.mm = math.sin(math.pi - m) * m

    def forward(self, input, label):
        # --------------------------- cos(theta) & phi(theta) ---------------------------
        # torch.nn.functional.linear(input, weight, bias=None)
        # y=x*W^T+b
        cosine = F.linear(F.normalize(input), F.normalize(self.weight))
        sine = torch.sqrt(1.0 - torch.pow(cosine, 2))
        # cos(a+b)=cos(a)*cos(b)-size(a)*sin(b)
        phi = cosine * self.cos_m - sine * self.sin_m
        if self.easy_margin:
            # torch.where(condition, x, y) → Tensor
            # condition (ByteTensor) – When True (nonzero), yield x, otherwise yield y
            # x (Tensor) – values selected at indices where condition is True
            # y (Tensor) – values selected at indices where condition is False
            # return:
            # A tensor of shape equal to the broadcasted shape of condition, x, y
            # cosine>0 means two class is similar, thus use the phi which make it
            phi = torch.where(cosine > 0, phi, cosine)
        else:
            phi = torch.where(cosine > self.th, phi, cosine - self.mm)
        # --------------------------- convert label to one-hot ---------------------------
        # one_hot = torch.zeros(cosine.size(), requires_grad=True, device='cuda')
        # 将cos(\theta + m)更新到tensor相应的位置中
        one_hot = torch.zeros(cosine.size(), device='cuda')
        # scatter_(dim, index, src)
        one_hot.scatter_(1, label.view(-1, 1).long(), 1)
        # -------------torch.where(out_i = {x_i if condition_i else y_i) -------------
        output = (one_hot * phi) + ((1.0 - one_hot) * cosine)
        # you can use torch.where if your torch.__version__ is 0.4
        output *= self.s
        # print(output)

        return output

到此ArcFace、SphereFace、CosFace的损失函数就介绍完啦~

参考链接:https://blog.csdn.net/fuwenyan/article/details/79657738

——  END ——

*延伸阅读

PS:新年假期,极市将为大家分享计算机视觉顶会 ICCV 2019 大会现场报告系列视频,欢迎前往B站【极市平台】观看,春节也学习,极市不断更,快来打卡点赞吧~

https://www.bilibili.com/video/av83518299

点击  阅读原文 ,可跳转浏览本文内所有网址链接

CV细分方向交流群

添加极市小助手微信 (ID : cv-mart) ,备注: 研究方向-姓名-学校/公司-城市 (如:目标检测-小极-北大-深圳),即可申请加入 目标检测、目标跟踪、人脸、工业检测、医学影像、三维&SLAM、图像分割等极市技术交流群 (已经添加小助手的好友直接私信) ,更有每月 大咖直播分享、真实项目需求对接、干货资讯汇总,行业技术交流 一起来让思想之光照的更远吧~

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

△长按添加极市小助手

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

△长按关注极市平台

觉得有用麻烦给个在看啦~    人脸识别损失函数简介与Pytorch实现:ArcFace、SphereFace、CosFace


以上所述就是小编给大家介绍的《人脸识别损失函数简介与Pytorch实现:ArcFace、SphereFace、CosFace》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

The Elements of Statistical Learning

The Elements of Statistical Learning

Trevor Hastie、Robert Tibshirani、Jerome Friedman / Springer / 2009-10-1 / GBP 62.99

During the past decade there has been an explosion in computation and information technology. With it have come vast amounts of data in a variety of fields such as medicine, biology, finance, and mark......一起来看看 《The Elements of Statistical Learning》 这本书的介绍吧!

随机密码生成器
随机密码生成器

多种字符组合密码

RGB CMYK 转换工具
RGB CMYK 转换工具

RGB CMYK 互转工具

HEX HSV 转换工具
HEX HSV 转换工具

HEX HSV 互换工具