首页 > 编程语言 >人脸识别ArcFace 算法原理与实现

人脸识别ArcFace 算法原理与实现

时间:2024-09-06 18:25:09浏览次数:6  
标签:人脸识别 self 算法 cosine planes device ArcFace out

在深度学习用于人脸识别方面,为了提高识别的准确率,研究者提出了ArcFace 技术。ArcFace 通过在 Softmax 损失函数上添加一种角度余弦距离的 margin 来提高人脸识别的准确率,ArcFace 始终优于 SOTA,且容易实现,计算开销可忽略不计。

论文:ArcFace: Additive Angular Margin Loss for Deep Face Recognition,地址:https://arxiv.org/pdf/1801.07698

如上图所示,相较于其他的人脸识别算法,在整个网络过程中,ArcFace有一个线性角度间距贯穿全过程。

ArcFace主要分两部分:

一、提取特征与对特征处理-主干

特征提取:通过一个深度卷积神经网络ResNet提取人脸图像的特征向量。

特征归一化:对提取到的特征向量进行 L2 归一化,将其转换为单位向量,使特征向量更加稳定。

二、头部

引入角度余弦(cosine)相似性作为度量标准,以增加样本之间的区分性。具体来说,为每个类别增加一个 learnable 的权重(称为 margin),将输入特征与各个类别的权重向量做余弦相似性计算,并确保同类别特征之间的相似度尽可能大,不同类别间的相似度尽可能小。

深度卷积神经网络(DCNN) 特征和最后一个 全连接层(FC) 权重之间的点积/内积 等于 特征和权重归一化之后的余弦距离。先利用 反余弦 (arc-cosine) 函数来计算当前特征与目标权重之间的角度。然后,把一个加性角度边距 (additive angular margin) 加到目标角度,然后通过余弦 (cosine) 函数再次获得目标logit。接着,通过固定的特征范数重缩放所有logit,且后续的步骤与Softmax Loss 中的步骤完全相同。

ArcFace算法通过上面两步骤后,进行分类器训练:在 Softmax 损失函数的基础上,引入角度余弦(cosine)相似性作为度量标准,以增加样本之间的区分性。

在上面已为每个类别增加一个 learnable 的权重(称为 margin),这里将输入特征与各个类别的权重向量做余弦相似性计算,并确保同类别特征之间的相似度尽可能大,不同类别间的相似度尽可能小。提出基于角度和余弦间隔的加性角度边距损失 (Additive Angular Margin Loss, ArcFace),cos( θ + m ) ,(θ为当前特征与目标权重之间的夹角),对归一化后的权重和特征在角度空间内进行优化以最大化决策边界,其几何含义更加直观,大量实验表明识别效果也更好。

ArcFace算法代码实现:

一、主干

用深度卷积神经网络ResNet_50提取人脸图像特征,ResNet_50代码如下:

class Bottleneck(Module):

    expansion = 4

    def __init__(self, inplanes, planes, stride = 1, downsample = None):

        super(Bottleneck, self).__init__()

        self.conv1 = conv1x1(inplanes, planes)

        self.bn1 = BatchNorm2d(planes)

        self.conv2 = conv3x3(planes, planes, stride)

        self.bn2 = BatchNorm2d(planes)

        self.conv3 = conv1x1(planes, planes * self.expansion)

        self.bn3 = BatchNorm2d(planes * self.expansion)

        self.relu = ReLU(inplace = True)

        self.downsample = downsample

        self.stride = stride

    def forward(self, x):

        identity = x

        out = self.conv1(x)

        out = self.bn1(out)

        out = self.relu(out)

        out = self.conv2(out)

        out = self.bn2(out)

        out = self.relu(out)

        out = self.conv3(out)

        out = self.bn3(out)

        if self.downsample is not None:

            identity = self.downsample(x)

        out += identity

for m in self.modules():

                if isinstance(m, Bottleneck):

                    nn.init.constant_(m.bn3.weight, 0)

                elif isinstance(m, BasicBlock):

                    nn.init.constant_(m.bn2.weight, 0)

    def _make_layer(self, block, planes, blocks, stride = 1):

        downsample = None

        if stride != 1 or self.inplanes != planes * block.expansion:

            downsample = Sequential(

                conv1x1(self.inplanes, planes * block.expansion, stride),

                BatchNorm2d(planes * block.expansion),

            )

        layers = []

        layers.append(block(self.inplanes, planes, stride, downsample))

        self.inplanes = planes * block.expansion

        for _ in range(1, blocks):

            layers.append(block(self.inplanes, planes))

        return Sequential(*layers)

    def forward(self, x):

        x = self.conv1(x)

        x = self.bn1(x)

        x = self.relu(x)

        x = self.maxpool(x)

        x = self.layer1(x)

        x = self.layer2(x)

        x = self.layer3(x)

        x = self.layer4(x)

        x = self.bn_o1(x)

        x = self.dropout(x)

        x = x.view(x.size(0), -1)

        x = self.fc(x)

        x = self.bn_o2(x)

        return x

def ResNet_50(input_size, **kwargs):

    """Constructs a ResNet-50 model.

"""

    model = ResNet(input_size, Bottleneck, [3, 4, 6, 3], **kwargs)

return model

二、头部

这里主要实现ArcFace算法的核心部分,代码如下:

class ArcFace(nn.Module):

    r"""Implement of ArcFace (https://arxiv.org/pdf/1801.07698v1.pdf):

        Args:

            in_features: size of each input sample

            out_features: size of each output sample

            device_id: the ID of GPU where the model will be trained by model parallel.

                       if device_id=None, it will be trained on CPU without model parallel.

            s: norm of input feature

            m: margin

            cos(theta+m)

        """

    def __init__(self, in_features, out_features, device_id, s = 64.0, m = 0.50, easy_margin = False):

        super(ArcFace, self).__init__()

        self.in_features = in_features

        self.out_features = out_features

        self.device_id = device_id

        self.s = s

        self.m = m

        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  # coso-sin(pi-m)*m

def forward(self, input, label):

        # --------------------------- cos(theta) & phi(theta) ---------------------------

        if self.device_id == None:

            cosine = 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])

            cosine = 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])

                cosine = torch.cat((cosine, F.linear(F.normalize(temp_x), F.normalize(weight)).cuda(self.device_id[0])), dim=1)

        sine = torch.sqrt(1.0 - torch.pow(cosine, 2))

        phi = cosine * self.cos_m - sine * self.sin_m  # cos��m+o)

        if self.easy_margin:

            phi = torch.where(cosine > 0, phi, cosine)

        else:

            phi = torch.where(cosine > self.th, phi, cosine - self.mm)  # coso-m*sim(m)

        # --------------------------- convert label to one-hot ---------------------------

        one_hot = torch.zeros(cosine.size())

        if self.device_id != None:

            one_hot = one_hot.cuda(self.device_id[0])

        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

        return output

在训练程中,先训练主干网络模型,再用获得的特征作为参数训练头部模型,两个模型的文件独立保存,所在在模型初始化、推理时候,先是先后分别用不同的模型文件加载主干与头部模型,然后进行训练或推理。

训练程中的损失用一种称为Focal(焦点)的损失函数,代码如下:

class FocalLoss(nn.Module):

    def __init__(self, gamma = 2, eps = 1e-7):

        super(FocalLoss, self).__init__()

        self.gamma = gamma

        self.eps = eps

        self.ce = nn.CrossEntropyLoss()

    def forward(self, input, target):

        logp = self.ce(input, target)

        p = torch.exp(-logp)

        loss = (1 - p) ** self.gamma * logp

        return loss.mean()

Focal Loss 通过引入一个可调参数gamma,使得模型在训练过程中更加关注难以分类的样本,从而在类别不平衡的情况下提高模型的性能。这个损失函数在目标检测和分类任务中特别有效,因为它能够平衡不同类别样本的贡献。

ArcFaceArcFace有时候可能需要保存抽取的人脸特征向量,在上面头部执行完成,要以得到ArcFace处理过的特征向量,保存在向量数据库中即可。

论文作者提到,在现实中,要获取大规模的标注人脸训练数据集,可能需要花费大量的人力与时间,成本很昂贵。可以从网络上模型有噪声的数据,通过在ArcFace 中引入子类来放松类内约束,迫使所有样本向对应的正中心靠近。我们为每个类设计K副中心,训练样本只需要靠近K正子中心中的任何一个,而不是

只有一个正中心。如果训练人脸是一个有噪声的样本,它就不属于相应的正类。自动隔离这些不属于相应正类的数据,直接用于清理训练数据,得到大量干净的训练数据集,值得关注。

标签:人脸识别,self,算法,cosine,planes,device,ArcFace,out
From: https://blog.csdn.net/heyiqiunet/article/details/141965729

相关文章

  • 掌握检索技术:构建高效知识检索系统的架构与算法6
    在检索专业知识层需要涵盖更高级的检索技术,包括工程架构和算法策略。一、工程架构工程架构在构建检索系统中决定了系统的可扩展性、高可用性和性能。比如需要考虑的基本点:分布式架构:水平扩展:采用分布式架构,将检索任务分布到多个节点上,实现水平扩展。这可以通过将索引数据......
  • 文心一言 VS 讯飞星火 VS chatgpt (341)-- 算法导论23.1 10题
    十、给定图GGG和GGG的一棵最小生成树......
  • 文心一言 VS 讯飞星火 VS chatgpt (341)-- 算法导论23.1 10题
    十、给定图和的一棵最小生成树,假设减小了中一条边的权重。证明:仍然是的一棵最小生成树。更形式化地,设为的一棵最小生成树,的边权重由权重函数给出。选择一条边和一个正数,并定义下述的权重函数:证明:仍然是的一棵最小生成树,这里的边权重由函数给出。如果要写代码,请用go语言。文心一言:首......
  • Leetcode算法挑战:详解如何实现交替合并字符串的解题思路
    Leetcode算法挑战中的“交替合并字符串”问题,要求我们将两个字符串以交替的方式合并,终形成一个新的字符串。乍一看,这道题目似乎不复杂,但要写出高效且简洁的解法,还需要一定的思路和技巧。一、问题描述题目要求给定两个字符串word1和word2,将它们按照索引依次交替合并。如果某个......
  • 搜索算法之二分搜索详细解读(附带Java代码解读)
    1.基本概念二分搜索(BinarySearch)是一种高效的查找算法,用于在一个已排序的数组中查找特定元素。它通过逐步将搜索范围减少一半来实现搜索,从而比线性搜索更快。由于它利用了数组的有序性,能够在对数时间内完成搜索操作。2.工作原理二分搜索的基本思想是:初始化:设置两个指针......
  • 算法练习小技巧之有序集合--套路详细解析带例题(leetcode)
    前言:    本文详细讲解Python中的有序集合SortedList和C++中的有序集合multiset的用法,配合leetcode的例题来展示实际的用处。(本人水平不够,还无法讲解有序集合的实现方法,只会用)    觉得有帮助或者写的不错可以点个赞,后面也有几道我找出来的题目可以用这个方......
  • 快速掌握AI算法基础:对于AI行业的“共同语言”入门指南
    对于非相关专业的AI产品或者想要转型AI产品的同学,算法知识晦涩难懂,如何用很短的时间快速入门,让你在AI领域更加游刃有余。 一、机器学习、深度学习、强化学习的定义1、机器学习(MachineLearning,ML)机器学习是人工智能(AI)的一个分支领域,旨在通过计算机系统的学习和自动化推......
  • 图解最常用的 10 个机器学习算法
    可在文章最后获取文章提到的!在机器学习领域,有种说法叫做“世上没有免费的午餐”,简而言之,它是指没有任何一种算法能在每个问题上都能有最好的效果,这个理论在监督学习方面体现得尤为重要。举个例子来说,你不能说神经网络永远比决策树好,反之亦然。模型运行被许多因素左右,例如数据......
  • 代码随想录算法训练营第十天| 232.用栈实现队列 、 225. 用队列实现栈 、20. 有效的括
    学习文章链接:代码随想录文章目录一、232.用栈实现队列二、225.用队列实现栈三、20.有效的括号四、1047.删除字符串中的所有相邻重复项一、232.用栈实现队列题目链接:232.用栈实现队列栈的操作:stack<int>s;s.empty();//如果栈为空则返回true,......
  • 一文看懂数据结构7种查询算法
    常见的七种查找算法:​数据结构是数据存储的方式,算法是数据计算的方式。所以在开发中,算法和数据结构息息相关。今天的讲义中会涉及部分数据结构的专业名词,如果各位铁粉有疑惑,可以先看一下哥们后面录制的数据结构,再回头看算法。1.基本查找​也叫做顺序查找​说明:顺序......