您当前的位置:首页 > 计算机 > 编程开发 > 人工智能

【人脸识别】RVFace详解

时间:04-17来源:作者:点击数:

论文题目:《RVFace: Reliable Vector Guided Softmax Loss for Face Recognition》

建议先了解下这篇文章:MVFace

1.前言

随着深度卷积神经网络 (CNN) 的进步,人脸识别取得了重大进展,其中心任务是如何提高特征辨别力。 为此,已经提出了几种基于margin(例如,角度、加法和加法角度margin)的 softmax 损失函数来增加不同类别之间的特征边距。 然而,尽管取得了很大的成就,但它们主要存在四个问题:

  1. 它们基于清洁良好的训练集的假设,而没有考虑大多数人脸识别数据集中固有存在的噪声标签的后果;
  2. 他们忽略了信息(例如,semi-hard)特征挖掘对判别学习的重要性;
  3. 他们只从ground truth类的角度鼓励feature margin,没有意识到与其他非ground truth类的可区分性;
  4. 他们将不同类之间的feature margin设置为相同且固定,这可能不能很好地适应不同类之间数据不平衡的情况。

相较于MVFace的改进:RVFace强调了semi-hard样本的重要性,而降低对噪声和离群点的关注。也就是上面的第1点。

在这里插入图片描述

为了克服上述缺点,本文尝试设计一种新的损失函数。 如图 1 所示,所提出的方法 RVFace 主要包含两个关键部分:(1)利用余弦相似度分布作为线索来估计嘈杂的标签;(2)强调来自剩余可靠标签的semi-hard向量进行训练。 因此,我们将它们整合到一个公式中,该公式明确指示可靠的特征向量并自适应地强调信息量(即semi-hard)向量以指导判别特征学习。 综上所述,本文的主要贡献可以归纳如下:

  • 我们提出了一种名为 RVFace 的新方法,它动态估计噪声标签,并明确强调来自剩余可靠特征向量的semi-hard特征向量,以指导判别特征学习。因此,我们的新损失函数还吸收了来自其他非真实类别的可辨别性,以及不同类别的自适应margin。
  • 据我们所知,这是第一次尝试将基于特征的噪声标签检测、特征挖掘和特征margin技术的优点有效地继承到一个统一的损失函数中。此外,我们深入分析了我们的新损失函数与当前基于噪声、基于挖掘和基于边缘的技术之间的关系和差异。

2.相关知识

Original Softmax

在这里插入图片描述

Softmax in face recognition

在这里插入图片描述

Margin-based Softmax

在这里插入图片描述

Mining-based Softmax

在这里插入图片描述

Noise-based Softmax

使用噪声标签学习正成为一种越来越有效地训练深度 CNN 的技术。 它的想法是检测噪声标签,使 CNN 模型集中在标记良好的样本上,从而获得更具辨别力的特征。 最近提出了几种基于噪声的 softmax 损失 [22]、[32],它们可以总结如下:

在这里插入图片描述

其中 q(x) 是对样本 x 进行加权的重新加权函数。 具体来说,如果样本 x 被预测为有噪声的标签,它的重要性就会退化。 否则,它将被重新加权函数 q(x) 强调。 例如,Zhong 等人。 [22] 开发了一个二进制重新加权函数,Hu 等人。 [32] 提出了一种动态且复杂的重新加权函数来处理嘈杂的标签。 综上所述,我们在表 I 中列出了不同类型损失函数的一些典型方法。

在这里插入图片描述

3.RVFace

首先,让我们回顾一下 margin-based softmax losses 的公式,即等式(3),从中我们可以总结出:

  1. 它是基于清洗良好的训练集,这是不现实的,因为大多数人脸识别数据集天生就由嘈杂的标签组成。
  2. 它忽略了信息(例如,semi-hard)特征挖掘对判别学习的重要性。
  3. 它只利用了来自真实类 y 的可辨别性,即 f (m, θwy,x),而没有意识到来自其他非真实类 k 的潜在可辨性,其中 k̸= y, k ∈ { 1,2, … , K} \ {y} ;
  4. 它简单地使用相同且固定的边距m1、m2或m3来扩大不同类之间的特征边距。

3.1.Naive Noise-Margin Softmax Loss

为了解决基于margin的 softmax 损失的第一个缺点,人们可以求助于基于噪声的策略 [22],[32]。 基于margin的 softmax 损失是基于训练集被良好清理的假设。 因此,将基于噪声的策略结合到基于margin的 softmax 损失中可以在一定程度上减轻噪声标签。 直接整合它们的朴素动机可以表述为:

在这里插入图片描述

公式(6) 确实通过重新加权函数 q(x) 解决了噪声标签,但其改进在实践中并不令人满意。 这背后的原因可能是,重新加权函数 q(x) 很难准确预测,并且基于margin的 softmax 损失没有考虑判别训练的信息特征。

3.2.Naive Mining-Margin Softmax Loss

为了解决基于margin的 softmax 损失的第二个缺点,可以求助于困难样本挖掘策略 [34]、[35]。 基于挖掘的损失函数旨在关注在hard samples上训练,而margin-based loss functions是为了扩大不同类别之间的特征间隔。 因此,这两个分支是正交的,可以无缝地相互结合, 直接整合它们的朴素动机可以表述为:

在这里插入图片描述

公式方程 (7) 确实涉及指示函数 g(x) 的信息特征,但其改进在实践中有限。 这背后的原因可能是,对于 HM-Softmax [34],它明确指出了困难的例子,但它丢弃了简单的例子。 对于 Focal-Softmax [35],它使用所有示例并根据经验通过调制因子对它们进行重新加权,但是hard samples对于训练来说不清楚并且没有直观的解释。 对于人脸识别,目前典型的困难样本挖掘策略对于判别学习来说通常可以忽略不计。

3.3.Mis-classified Vector Guided Softmax Loss (MVFace)

直觉告诉我们,考虑分离良好的特征向量对判别学习影响不大。 这意味着错误分类的特征向量对于增强特征可辨别性更为重要。 具体来说,基于 margin-based softmax 损失函数,工作 [64] 定义了一个二元指标 Ik 来自适应地指示样本(特征)在当前阶段是否被特定分类器 wk(其中 k̸= y)错误分类:

在这里插入图片描述

由式(8)可以看出,如果一个样本(特征)被误分类,即f(m,θwy,x)−cos(θwk,x)≤0,则暂时强调。 因此,错误分类向量引导 Softmax (MV-Softmax) [64] 损失公式如下:

在这里插入图片描述

其中 h(t, θwk,x, Ik) ≥ 1 是重新加权函数,用于强调指示的错误分类向量。 具体定义如下:

在这里插入图片描述

其中 t ≥ 0 是预设的超参数。 不幸的是,MV-Softmax 损失的成功在很大程度上也取决于标注良好的训练数据集。

3.4.Reliable Vector Guided Softmax Loss(RVFace)

在这里插入图片描述

如上所述,MV-Softmax 损失的错误分类向量(例如,图 2 中的红色、橙色和蓝色点)可能带有噪声标签。 幸运的是,根据文献[11]、[32],通常表明余弦相似度较小的样本有较大概率是噪声标签。 这种现象主要是因为 CNN 可以快速记住简单/干净的样本,也可以记住困难/嘈杂的样本 [63]。 受此启发,我们开始开发可靠的向量引导 softmax 损失。 如图 2 所示,如果错误分类的向量 x4(蓝点)与它们的真实类别 w1 相距较远(即其余弦相似度较小),则很可能是噪声标签。 为此,我们可以采用此提示来检测噪声标签。 具体来说,我们定义检测函数 d(x) 来预测噪声标签,如下所示:

在这里插入图片描述

其中 τ 是确定噪声标签的阈值。 特别是,随着 CNN 迭代更新其参数,确定噪声标签的阈值 τ 应该是动态的。 如[11]所示,余弦相似度的分布是双峰分布。 因此,我们可以求助于 OTSU 算法 [65] 来确定阈值。 从定义方程式。 (11),我们可以看出,如果样本 x 的余弦相似度 cos(θwy,x) 小于阈值 τ ,则它被检测为噪声标签。 对于剩余的样本,可以进一步分为三类:easy vectors(即绿点,f(m, θwy,x) > cos(θwk,x)),semi-hard vectors(即红点,f (m, θwy,x) ≤ cos(θwk,x) ≤ cos(θwy,x)) 和模糊向量(即橙色点,cos(θwk,x) > cos(θwy,x))。 简单的向量对于判别学习来说可以忽略不计,而模糊的向量可能仍然是嘈杂的标签。 因此,我们强调semi-hard向量的重要性。 相应地,我们定义指标函数 Jk 如下:

在这里插入图片描述

因此,我们的 Reliable Vector guided Softmax loss (RVFace) 可以表示如下 L9 :=

在这里插入图片描述

显然,当训练集干净 d(x) = 1 且超参数 t = 0 时,设计的 RVFace Eq. (13) 变得与原始的基于margin的 softmax 损失等式相同。 (3).

3.5.Comparison to Existing Methods

  1. Comparison to Noise-based Softmax Losses:为了说明我们的 RVFace 相对于当前基于噪声的 softmax 损失的优势[22],[32],我们以图 2 为例。 假设 [32] 正确检测到噪声人脸 x4,其策略是在训练期间自适应地重新加权噪声人脸。嘈杂标签的不良影响在一定程度上仍然存在。 工作 [22] 使用二元指标来消除噪声标签的不良影响。然而,它使用最大概率来简单地检测噪声标签,这通常是不稳定的。 相比之下,我们的 RVFace可以根据余弦相似度的双峰分布检测噪声标签,并通过在训练过程中丢弃噪声标签来消除噪声标签的不良影响,从而有效解决基于边缘损失的第一个缺点
  2. Comparison to Mining-based Softmax Losses:我们进一步说明了我们的 RVFace 相对于传统的基于挖掘的损失函数(例如,HM-Softmax [34] 和 Focal-Softmax [35])的优势。 在图 2 中,假设我们有两个样本(特征)x1 和 x2,它们都来自类别 1,其中 x1 分类良好,而 x2 分类不良。 HMSoftmax 根据经验指示困难样本并丢弃简单样本 x1 以使用困难样本 x2 进行训练。 Focal-Softmax并没有明确指出hard samples,而是对所有样本重新加权,使得较硬的x2具有相对较大的损失值。 这两种策略都是直接从loss的角度出发,hard examples的选择没有语义指导。 我们的 RVFace Eq. (13) 是从不同的方式。首先,我们根据决策边界(即等式(12))在语义上指示hard sample(semi-hard vector)。 以往方法的困难程度被定义为特征(样本)和特征(样本)之间的全局关系。 而我们的硬度是特征和分类器之间的局部关系,这更符合判别特征学习。 然后,我们从概率的角度强调这些信息特征。 具体来说,由于交叉熵损失 −log§ 是单调递减函数,降低了概率 p(原因是 h(t, θwk,x, Jk) ≥ 1,见式(10)) 半硬向量 x2,将增加其对训练的重要性。 总之,我们已经很好地强调了信息特征对于判别特征学习的重要性
  3. **Comparison to Margin-based Softmax Losses:**类似地,在图 2 中,假设我们有来自类别 1 的样本 x2。最初的 softmax 损失旨在使 wT1 x2 > wT 2 x2 ⇐⇒ cos(θ1) > cos(θ2) 和 wT1 x2 > wT 3 x2 ⇐⇒ cos(θ1) > cos(θ3)。 为了使这些目标更加严格,margin-based的损失函数从ground-truth类(即 θ1)的角度引入了margin函数 f (m, θ1) = cos(m1θ1 + m3) − m2 , [8] [20] [28]:
    在这里插入图片描述
    其中 f (m, θ1) 对于不同的类具有相同且固定的边距,并且忽略了与其他非地面真值类(例如 w2/θ2 和 w3/θ3)的潜在可区分性。 为了解决这些问题,我们的 RVFace 试图进一步扩大从其他非地面实况类别的角度来看特征间隔。 具体来说,我们为semi-hard特征 x2 引入了一个新的边界函数 h∗(t, θ2):
    在这里插入图片描述
    其中 h∗(t, θ2) = log[h(t, θ2)ecos(θ2)] = (t + 1) cos(θ2) + t。 对于案例 θ3,因为 x2 被分类器 w3 很好地分类了,我们不需要给予任何额外的强制来进一步扩大它的margin。 此外,我们的 RVFace 还为不同的类别设置了自适应margin。 以 RV-AM-Softmax(即 f (m, θy) = cos(θy) − m)为例,对于 semi-hard 类 w2,margin 为 m + t cos(θ2) + t。 而对于分类良好的类 w3,边距为 m。 考虑到这些,我们的 RVFace 解决了基于边距的 softmax 损失的第三个和第四个缺点。

3.6.算法流程

在这里插入图片描述

4.实验

在这里插入图片描述

从左到右:来自具有不同噪声率的数据集 CASIA-WebFace-R-N1、CASIAWebFace-R-N2、CASIA-WebFace-R-N3 和 CASIA-WebFace-R-N4 的所有正对的余弦相似度分布, 分别。 从直方图中,我们可以看到嘈杂的数据集呈现双峰分布。 此外,左侧人脸的余弦相似度很可能是噪声标签。

在这里插入图片描述

来自数据集 CASIA-WebFace-R 的所有正对的余弦相似度分布。 从直方图分布,我们可以看出 CASIA-WebFace-R 可以被视为无噪声数据集。

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

从左到右:所有正对余弦相似度分布以及我们在 CASIA-WebFace-R-N4 上使用不同训练时期(1、10、20、30)的 RV-AM-Softmax 损失的噪声检测的相应精度和召回率。

在这里插入图片描述

从左到右:我们在不同训练时期的噪声检测策略的精确度和召回率。

在这里插入图片描述

在测试集 LFW、SLLFW、CALFW、CPLFW、AGEDB 和 CFP 上使用不同策略验证我们的 RVFACE(RV-ARC-SOFTMAX 和 RV-AM-SOFTMAX)损失函数的性能(%)。

在这里插入图片描述

我们的 RVFACE(RV-ARC-SOFTMAX 和 RV-AM-SOFTMAX)在不同超参数 t 下的验证性能(%)。 ‘-’ 表示该方法无法收敛。

在这里插入图片描述

在测试集 LFW、BLUFR(1E-4) 和 SLLFW 上使用不同架构验证我们的 RVFACE (RV-AM-SOFTMAX) 损失函数的性能 (%)。 训练集是 CASIA-WEBFACE-R-N4。

在这里插入图片描述

我们 RVFace 的收敛。 从曲线中,我们得出结论,我们的 RV-Arc-Softmax 和 RV-AM-Softmax 具有良好的收敛性。

在这里插入图片描述

不同损失函数在测试集 LFW、SLLFW、CALFW、CPLFW、AGEDB 和 CFP 上的验证性能(%)。

在这里插入图片描述

不同损失函数在测试集RFW上的验证性能(%)

在这里插入图片描述

从左到右:MegaFace Set 1 上具有 1M 干扰项的不同损失函数的 CMC 曲线和 ROC 曲线。

在这里插入图片描述

不同损失函数在 MEGAFACE CHALLENGE 上的表现(%)。

在这里插入图片描述

来自 vanilla CASIA-WebFace 数据集的所有正对的余弦相似度分布。 从直方图分布,我们可以看出 vanilla CASIA-WebFace 是一个带有噪声标签的数据集。

在这里插入图片描述

香草 CASIA-WebFace [44] 中估计噪声标签的示例。 我们随机选择几个用我们的方法 RVFace 预测带有噪声标签的人。 每个人的估计噪声标签用红色虚线框表示。

在这里插入图片描述

测试集 MEGAFACE 挑战中不同损失函数的性能 (%)。 训练集是 VANILLA CASIA-WEBFACE。

5.结论

本文针对人脸识别任务提出了一种简单但非常有效的损失函数,即 Reliable Vector(即 RVFace)引导的 softmax 损失。 具体来说,RVFace 显式估计噪声标签并自适应地强调来自剩余可靠特征向量的semi-hard特征向量以进行判别训练。 因此,它在语义上将基于特征的噪声标签检测、特征挖掘和特征余量的动机继承到一个统一的损失函数中。 因此,它表现出比基线 Softmax 损失、当前基于噪声、基于挖掘、基于边缘的损失、它们的原始融合和几种最先进的方法更高的性能。 对各种人脸识别基准的广泛实验已经验证了我们的新方法相对于最先进的替代方法的有效性。 请注意,我们方法的噪声标签检测和semi-hard向量挖掘是为基于 softmax 的损失而设计的。 它们不能直接用于度量学习损失,例如对比损失和三元组损失。

pytorch代码:

class RVArcFace(nn.Module):
    # Reliable Vector Guided Softmax Loss
    def __init__(self, in_features, out_features, device_id=None, s = 32.0, m = 0.35, t = 0.15, easy_margin = False, fp16 = False):
        super(RVArcFace, self).__init__()
        self.in_features = in_features
        self.out_features = out_features
        self.device_id = device_id

        self.s = s
        self.m = m
        self.t = t

        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
        self.fp16 = fp16

    def forward(self, input, label):
        # --------------------------- cos(theta) & phi(theta) ---------------------------
        if self.device_id == None:
            cos_theta = F.linear(F.normalize(input), F.normalize(self.weight))
            print('cos_theta_None:{}'.format(cos_theta.shape))
        else:
            x = input
            sub_weights = torch.chunk(self.weight, len(self.device_id), dim=0)
            temp_x = x.cuda(self.device_id[0]) 
            weight = sub_weights[0].cuda(self.device_id[0]) 
            cos_theta = F.linear(F.normalize(temp_x), F.normalize(weight))
            for i in range(1, len(self.device_id)):
                temp_x = x.cuda(self.device_id[i])
                weight = sub_weights[i].cuda(self.device_id[i])
                cos_theta = torch.cat((cos_theta, F.linear(F.normalize(temp_x), F.normalize(weight)).cuda(self.device_id[0])), dim=1)
        cos_theta = cos_theta.clamp(-1, 1)
        batch_size = label.size(0)
        gt = cos_theta[torch.arange(0, batch_size), label].view(-1, 1)
        sin_theta = torch.sqrt(1.0 - torch.pow(gt, 2))
        cos_theta_m = gt * self.cos_m - sin_theta * self.sin_m
        
        mask1 = cos_theta >= cos_theta_m
        mask2 = cos_theta <= gt
        mask = mask1 & mask2     #cos(ɵy+m) <= cosɵj <= cosɵy
        hard_vector = cos_theta[mask]
        cos_theta[mask] = (self.t + 1) * hard_vector + self.t 

        if self.fp16:
            cos_theta_m = cos_theta_m.half()
        if self.easy_margin:
            final_gt = torch.where(gt > 0.0, cos_theta_m, gt)
        else:
            final_gt = torch.where(gt > self.th, cos_theta_m, gt - self.mm)
        if self.device_id != None:
            cos_theta = cos_theta.cuda(self.device_id[0])
        cos_theta.scatter_(1, label.data.view(-1, 1), final_gt)
        cos_theta *= self.s

        return cos_theta
class RVCosFace(nn.Module):
    # Reliable Vector Guided Softmax Loss
    def __init__(self, in_features, out_features, device_id=None, s=32.0, m=0.35, t=0.15, easy_margin=False,
                 fp16=False):
        super(RVCosFace, self).__init__()
        self.in_features = in_features
        self.out_features = out_features
        self.device_id = device_id

        self.s = s
        self.m = m
        self.t = t

        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
        self.fp16 = fp16

    def forward(self, input, label):
        # --------------------------- cos(theta) & phi(theta) ---------------------------
        if self.device_id == None:
            cos_theta = F.linear(F.normalize(input), F.normalize(self.weight))
        else:
            x = input
            sub_weights = torch.chunk(self.weight, len(self.device_id), dim=0)
            temp_x = x.cuda(self.device_id[0])
            weight = sub_weights[0].cuda(self.device_id[0])
            cos_theta = F.linear(F.normalize(temp_x), F.normalize(weight))
            for i in range(1, len(self.device_id)):
                temp_x = x.cuda(self.device_id[i])
                weight = sub_weights[i].cuda(self.device_id[i])
                cos_theta = torch.cat(
                    (cos_theta, F.linear(F.normalize(temp_x), F.normalize(weight)).cuda(self.device_id[0])), dim=1)
        cos_theta = cos_theta.clamp(-1, 1)
        batch_size = label.size(0)
        gt = cos_theta[torch.arange(0, batch_size), label].view(-1, 1)

        # pos vectors
        cos_theta_m = gt - self.m
        # semi-hard vectors
        mask1 = cos_theta >= cos_theta_m
        mask2 = cos_theta <= gt
        mask = mask1 & mask2
        hard_vector = cos_theta[mask]
        cos_theta[mask] = (self.t + 1.0) * hard_vector + self.t

        if self.fp16:
            cos_theta_m = cos_theta_m.half()
        '''
        if self.easy_margin:
            final_gt = torch.where(gt > 0.0, cos_theta_m, gt)
        else:
            final_gt = torch.where(gt > self.th, cos_theta_m, gt - self.mm)
        '''
        final_gt = cos_theta_m
        if self.device_id != None:
            cos_theta = cos_theta.cuda(self.device_id[0])
        cos_theta.scatter_(1, label.data.view(-1, 1), final_gt)
        cos_theta *= self.s

        return cos_theta
方便获取更多学习、工作、生活信息请关注本站微信公众号城东书院 微信服务号城东书院 微信订阅号
推荐内容
相关内容
栏目更新
栏目热门