人脸识别 损失函数 centerloss arcfaceloss pytorch实现

人脸识别介绍

MTCNN 实现人脸检测,回答了“是不是人脸”和“人脸在哪”的问题,接下来人脸识别要解决的就是“人脸是谁”。
人脸识别是目标识别中的一种,本质上也是分类问题,只不过是同类(人脸)中的细分,因为人脸之间相似度很大,这对损失函数的分类能力提出了更高的要求。

损失函数发展

下面介绍分类损失函数的主要类型和发展历程,及部分pytorch代码。
效果图来自 MNIST 数据集,将网络模型倒数第二层输出通道数设为2,将二维分类特征可视化即可。

Softmax loss

在这里插入图片描述

经典的分类损失 Softmax loss,将正确类别的预测概率最大化。但这种方式只考虑了能否正确分类,没有考虑类间距离。

人脸识别 损失函数 centerloss arcfaceloss pytorch实现_第1张图片

Center loss

在这里插入图片描述
在这里插入图片描述
Center loss 在 Softmax loss 基础上增加了 L C L_C LC项,给每个类都设置一个中心 c y i c_{yi} cyi,让该类尽量向中心靠拢,在保证分类的同时,最小化类内距离。

需要注意的是:

  1. Center loss 本身没有分类功能,需要配合 Softmax loss,不能单独使用。
  2. 中心 c y i c_{yi} cyi初始化是随机值,之后随着学习到的特征进行实时更新。
  3. 计算每一类的中心损失时,需要除以该类样本数计算均值,防止因样本失衡导致的不同类别梯度更新不同步。
  4. 参数 λ \lambda λ控制中心损失优化力度, λ \lambda λ越大区分度越高,但在人脸识别中,经验值一般取0.003即可。

人脸识别 损失函数 centerloss arcfaceloss pytorch实现_第2张图片
Center loss 在人脸识别上的效果还是不错的,但还有许多不足

  1. 类内距优化效果还不理想。
    类内距还是较大,当类别较多时,无法清晰区分特征。
  2. 类别多时,对硬件要求较高。
    每个类别需要维护一个中心点,当类别很多时计算量大。
  3. L2范数的离群点难以优化。
    因为中心损失计算的是每一类损失的均值,离群点导致loss较大,难以下降,同理在loss下降过程中,离群点的优化力度不够,相对仍然离中心很远。
  4. 只适用于同类样本间差异较小的数据。
    将同类样本向一个中心点优化的前提是,这一类样本间相似度较大,中心点可以代表这一类样本的特征,如果差异很大,就相当于有很多离群点,自然难以优化。可以抽象理解为:一个人的一堆人脸取均值大概还能看出是人脸,而各种类别的狗取均值就完全认不出是什么了。
def center_loss(feature, label, lambdas):
    """
    计算中心损失
    :param feature: 网络输出特征 (N, 2)
    :param label: 分类标签 如 tensor([0, 2, 1, 0, 1])
    :param lambdas: 参数 控制中心损失大小 即类内间距
    :return:
    """
    device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
    # 随机生成一组中心点参数 如 (C, 2)
    center = nn.Parameter(torch.randn(int(max(label).item() + 1), feature.shape[1]), requires_grad=True).to(device)
    # 根据标签索引 生成与feature相对应的中心点 如 (N, 2)
    center_exp = center.index_select(0, label.long())
    # 统计标签中各分类的个数 如 tensor([3, 2]) 代表类别0的样本有3个 类别1的样本有2个
    count = torch.histc(label, bins=int(max(label).item() + 1), min=0, max=int(max(label).item()))
    # 根据标签索引 生成与feature相对应的各类样本数
    count_exp = count.index_select(dim=0, index=label.long())
    
    loss = lambdas / 2 * torch.mean(torch.div(torch.sum(torch.pow(feature - center_exp, 2), dim=1), count_exp))

    return loss

网络模型倒数第二层输出二维特征,计算 CenterLoss,加上最后一层10分类 Softmax loss 作为总loss。
我的训练效果:
人脸识别 损失函数 centerloss arcfaceloss pytorch实现_第3张图片

Triplet loss


三元组损失函数,三元组由Anchor, Negative, Positive组成。如上图,Anchor 和 Positive 是同类样本,经过学习尽量靠近(减小同类距离),Anchor 和 Negative 尽量远离(增大不同类间距离)。


其中第一项为类内距 ,第二项为类间距,超参数 α \alpha α 表示两种距离的目标差距。

Triplet Loss 在 Center loss 之前是人脸识别的常用损失函数,缺点是在样本数增多的时候,样本对的组合数量会指数级激增,非常耗时,因此诞生了下列几种新的损失算法。

L-softmax loss

首先调整 Softmax Loss,将卷积运算转化为向量积:

在这里插入图片描述
忽略偏置b后损失函数变为:


在这里插入图片描述
其中 c o s θ cosθ cosθ 改成了 c o s ( m θ ) cos(mθ) cos(mθ) m > 1 m>1 m>1

以二分类举栗,优化目标发生了如下变化,相当于增加决策余量 margin,加大了学习难度,m越大难度越大。


在这里插入图片描述
由下图可以看到,无论 ||W1||||W2|| 的关系如何,L-softmax loss 都可以压缩类内距增大类间距。
人脸识别 损失函数 centerloss arcfaceloss pytorch实现_第4张图片

SphereFace(A-Softmax loss)

A-Softmax loss 在 L-softmax loss 基础上将权重归一化: ∣ ∣ W ∣ ∣ = 1 ||W|| = 1 W=1,导致特征上的点映射到单位超球面上,这样模型的预测仅取决于 W W W X X X 之间的角度,只需要保证 c o s ( θ 1 ) < c o s ( m θ 2 ) cos(\theta_1) < cos(m\theta_2) cos(θ1)<cos(mθ2),不用再考虑 ||W1||||W2|| 的大小关系。

在这里插入图片描述

CosFace(AM-Softmax loss)

人脸识别是根据两个特征向量之间的余弦相似度计算的。这表明,特征向量 x x x 的范数是对评分没有贡献,于是,令 ∣ ∣ W ∣ ∣ = 1 ||W|| = 1 W=1 ∣ ∣ x ∣ ∣ = s \displaystyle ||x||=s x=s s s s只是作为一个缩放系数,自由给定,经验值取30。

在这里插入图片描述
式子中减去余弦边缘项 m m m,作用和 A-Softmax loss 中一样, c o s cos cos函数在 ( 0 , π ) (0, \pi) (0,π)区间内递减,因此 c o s ( θ ) cos(\theta) cos(θ)增大角度 θ \theta θ,相当于减小余弦值。

ArcFace loss


A-Softmax loss 和 AM-Softmax loss 最大不同在于一个是角度距离,一个是余弦距离。
优化角度距离比优化余弦距离对分类的影响更直接,因此 ArcFace loss 还是选择增大角度 θ \theta θ,但不是通过乘 m m m而是加 m m m,避免了倍角计算在反向传播时的求导麻烦。
需要注意:上述几种对 Softmax loss 的改造,相当于在分子分母上同时减小一个值,破坏了 Softmax 总和为1的特性。

人脸识别 损失函数 centerloss arcfaceloss pytorch实现_第5张图片

class ArcNet(nn.Module):
    def __init__(self, feature_dim=2, cls_dim=10):
        """
        生成参数W (2, 10) 与最后一层权重shape相同
        :param feature_dim: 倒数第二层特征维度
        :param cls_dim: 最后一层分类置信度维度
        """
        super(ArcNet, self).__init__()
        self.W = nn.Parameter(torch.randn(feature_dim, cls_dim), requires_grad=True)

    def forward(self, feature, m=1, s=30):
        """
        计算Arc-Softmax
        :param feature: 倒数第二层 特征层输出
        :param m: 增大角度
        :param s: 缩放比例
        :return: (N, 10) 分类数
        """
        # 特征x (N, 2) 和参数w (2, 10) 在特征维度上 标准化
        x = F.normalize(feature, dim=1)
        w = F.normalize(self.W, dim=0)
        # 向量x与w的余弦相似度
        cosa = torch.matmul(x, w) / (torch.sqrt(torch.sum(torch.pow(x, 2))) * torch.sqrt(torch.sum(torch.pow(w, 2))))
        # 反余弦 算出角度θ
        a = torch.acos(cosa)
        
        arcsoftmax = torch.exp(s * torch.cos(a + m)) / (torch.sum(torch.exp(s * cosa), dim=1, keepdim=True)
            - torch.exp(s * cosa) + torch.exp(s * torch.cos(a + m)))

        return arcsoftmax

ArcNet 作为网络模型的最后一层参与训练, W W W是训练参数。

人脸识别流程

利用 MTCNN + Arcface loss 实现人脸识别的具体流程:

  1. 训练特征提取器
    a. 创建特征提取网络(如ResNet)
    b. 准备训练数据集(开源数据集如:VGG-Face 2、MS-Celeb-1M等等)
    c. 设计损失函数(Arcface loss)
    d. 训练网络使其获得特征提取能力
  2. 创建人脸特征库
    a. 通过 MTCNN 获得人脸框
    b. 将人脸框传入特征提取器提取人脸特征
    c. 将每个人脸特征和对应标签存入人脸特征库
  3. 检测目标人脸
    a. 通过 MTCNN 获得当前画面所有人脸框
    b. 将每个人脸框传入特征提取器提取人脸特征
    c. 将每个人脸特征和人脸特征库里的特征一一对比(余弦相似度)
    d. 如果对比差异小于阈值,则认为是同一个人脸

你可能感兴趣的:(AI,深度学习,pytorch,人工智能)