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

本文介绍了SphereFace、CosFace和ArcFace三种基于softmax的深度学习模型,它们通过归一化权重和输入特征、引入惩罚margin,使类别间距离增大,类型内部紧凑,适用于人脸识别和紧凑型分类任务。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

ArcFace简化版代码镇楼

import torch
import torch.nn as nn
import math
import torch.nn.functional as F

# ArcFace
class ArcMarginProduct(nn.Module):
    def __init__(self, in_features, out_features, s=30.0, m=0.50):
        super(ArcMarginProduct, self).__init__()
        self.s = s
        self.m = m
        self.weight = nn.Parameter(torch.FloatTensor(out_features, in_features))
        nn.init.xavier_uniform_(self.weight)
 
        self.cos_m = math.cos(m)
        self.sin_m = math.sin(m)
 
    def forward(self, input, label):
        # input: (bs, in_features) label: (bs)
        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
        # 对phi的修正参考easy margin
        one_hot = torch.zeros(cosine.size())
        # scatter_(dim, index, src)
        one_hot.scatter_(1, label.view(-1, 1).long(), 1)
        # 注意arcface的公式是分子上的加了margin,所以也就是one_hot*phi
        # 另外还有一个问题:CE中用-one_hot * logsoftmax就够了,为啥这里还多了((1.0 - one_hot) * cosine)?
        # 答案是因为这个函数本质上是normalized->转角度->把input的logits调整成为output的logits
        # 所以对于one_hot是1的位置,就使用phi,对于one_hot是0的位置,还是使用cos,所以就出现了 phi = cosine * self.cos_m - sine * self.sin_m
        # 最终这个函数得到的logits还要再去直接过一遍CE,代码 https://github.com/ronghuaiyang/arcface-pytorch/blob/47ace80b128042cd8d2efd408f55c5a3e156b032/train.py#L59 中过CE是这么写的
        '''
            metric_fc就是这里的ArcMarginProduct
            output = metric_fc(feature, label)
            loss = criterion(output, label)
        '''
        output = (one_hot * phi) + ((1.0 - one_hot) * cosine)
        output *= self.s

        # output: (bs, out_features)
        return output

if __name__ == '__main__':
    in_features,out_features,bs = 3,20,10
    arc = ArcMarginProduct(in_features=in_features, out_features=out_features)
    input = torch.randn(bs, in_features)
    label = torch.arange(bs)
    res = arc(input, label)
    print(res.shape)

 TripletLoss简化版代码镇楼

目标:使 d ( a , p ) + m < d ( a , n )尽量成立,如果不成立的话,max(d ( a , p ) + m - d ( a , n ), 0)尽量小

import torch
import torch.nn as nn

class TripletLoss(nn.Module):
    def __init__(self, margin=1.0):
        super(TripletLoss, self).__init__()
        self.margin = margin

    def forward(self, anchor, positive, negative):
        # 计算 anchor 和 positive 之间的欧氏距离的平方
        positive_distance = (anchor - positive).pow(2).sum(1)
        
        # 计算 anchor 和 negative 之间的欧氏距离的平方
        negative_distance = (anchor - negative).pow(2).sum(1)
        
        # 计算损失
        losses = torch.relu(positive_distance - negative_distance + self.margin)
        
        # 返回平均损失
        return losses.mean()

# 创建一些示例数据
anchor = torch.randn(10, 128, requires_grad=True)
positive = torch.randn(10, 128, requires_grad=True)
negative = torch.randn(10, 128, requires_grad=True)

# 实例化 TripletLoss
triplet_loss = TripletLoss(margin=1.0)

# 计算损失
loss = triplet_loss(anchor, positive, negative)

# 打印损失
print("Triplet Loss:", loss.item())

# 反向传播
loss.backward()

直观总结

ArcFace中的Arc其实是弧度的意思,直观感觉如下图,Triplet之前找一个参照,ArcFace找n类参照,自然就到了CrossEntropy多分类里面的Softmax:

总结成一句话就是:在softmax基础上,对最后一层全连接的权重和输入特征进行归一化,重新放缩到半径为s的超平面,增加惩罚的margin训练,使得类型紧凑,类间变得远离

从Softmax说起

SphereFace(也叫A-Softmax),margin乘在θ前

# 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,margin加在θ后

# 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,给正例转角度加margin

代码实现:

import torch
import torch.nn as nn
import math
import torch.nn.functional as F

# ArcFace
class ArcMarginProduct(nn.Module):
    def __init__(self, in_features, out_features, s=30.0, m=0.50):
        super(ArcMarginProduct, self).__init__()
        self.s = s
        self.m = m
        self.weight = nn.Parameter(torch.FloatTensor(out_features, in_features))
        nn.init.xavier_uniform_(self.weight)
 
        self.cos_m = math.cos(m)
        self.sin_m = math.sin(m)
 
    def forward(self, input, label):
        # input: (bs, in_features) label: (bs)
        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
        # 对phi的修正参考easy margin
        one_hot = torch.zeros(cosine.size())
        # scatter_(dim, index, src)
        one_hot.scatter_(1, label.view(-1, 1).long(), 1)
        # 注意arcface的公式是分子上的加了margin,所以也就是one_hot*phi
        output = (one_hot * phi) + ((1.0 - one_hot) * cosine)
        output *= self.s

        # output: (bs, out_features)
        return output

if __name__ == '__main__':
    in_features,out_features,bs = 3,20,10
    arc = ArcMarginProduct(in_features=in_features, out_features=out_features)
    input = torch.randn(bs, in_features)
    label = torch.arange(bs)
    res = arc(input, label)
    print(res.shape)

easy_margin的解释
只对cosine > 0的项添加margin惩罚,虽然函数整体不再满足单调递减的性质,但是总体上绝大部分样本与w的夹角小于pi/2,所以影响不会太大。

hard_margin的解释

对代码中else部分的解释,整体满足单调递减的性质。

以上转载自: arcface代码中对easy_margin的理解_arcface margin-优快云博客

<think>好的,我现在要处理用户关于面部识别(FR)模型的使用教程和实现方式的查询。首先,我需要回顾用户提供的引用内容,看看哪些信息可以派上用场。用户提到了四个引用,其中引用[1]列举了常用的算法模型和损失函数,引用[2]提到了faceRecoModel的具体调用方法,引用[3]和[4]涉及面部检测和预处理步骤。 接下来,我应该组织回答的结构。用户需要教程或实现方式,所以步骤式的指南会比较合适。首先,模型选择是基础,根据引用[1],可以提到DeepFace、FaceNet、ArcFace等模型。然后,数据预处理部分,引用[3]和[4]提到了面部检测和对齐,需要详细说明,比如使用MTCNN或Dlib库进行检测,以及图片的要求,如面部需占据大部分,避免全身照。 接下来是模型构建训练。引用[2]给出了faceRecoModel的输入形状为(3,96,96),这里可能需要解释不同框架的输入格式差异,比如TensorFlow是通道在后,而PyTorch可能不同。然后,损失函数部分,引用[1]提到了几种损失函数,如ArcFaceCosFace,需要说明它们的作用,即增强类间差异,缩小类内差异。 应用阶段需要涵盖人脸验证和识别,可能需要结合特征提取和相似度计算。此外,优化和部署部分要考虑模型压缩和实时处理,但用户可能更关注基础实现,所以这部分可以简要提及。 还要注意用户的引用标识,每个步骤中引用的内容都要正确标注,比如数据预处理部分引用[3]和[4],模型构建引用[2],损失函数引用[1]。同时,确保所有数学公式和代码块符合格式要求,比如行内公式用$...$,独立公式用$$...$$,代码块用三个反引号。 可能用户的问题还包括具体如何实现面部对齐,或者如何选择损失函数,但当前问题主要是使用教程和实现方式,所以需要覆盖主要步骤,从数据准备到模型应用。此外,需要检查是否有遗漏的信息,比如引用[4]提到的图片要求,必须在预处理步骤中强调,避免全身照的影响。 最后,生成的相关问题需要围绕FR模型的扩展,比如如何优化性能,处理复杂背景,或损失函数的选择,这些都能帮助用户进一步探索。</think>### 面部识别(FR)模型使用教程实现方式 #### 1. **模型选择数据预处理** - **常用模型**:根据引用[1],主流FR模型包括$FaceNet$、$ArcFace$(InsightFace)、$SphereFace$等。以$FaceNet$为例,其核心思想是将人脸映射到高维特征空间,使同一人脸的欧氏距离尽可能小,不同人脸的欧氏距离尽可能大[^1]。 - **输入要求**: 引用[4]指出,输入图片需满足以下条件: - 面部占据图像主要区域(如头部特写),全身照或复杂背景会导致效果下降; - 分辨率建议为$96 \times 96$或更高,通道顺序需模型定义一致(如RGB或BGR)[^4]。 - **关键预处理步骤**: 1. **人脸检测对齐**:使用$MTCNN$或$dlib$库提取面部区域,并进行仿射变换对齐(如旋转、缩放),确保双眼水平对齐[^3]。 2. **归一化**:将像素值归一化到$[-1,1]$或$[0,1]$区间。 ```python # 示例:使用MTCNN检测并对齐人脸 from mtcnn import MTCNN import cv2 detector = MTCNN() image = cv2.imread("face.jpg") result = detector.detect_faces(image) x, y, width, height = result[0]['box'] aligned_face = image[y:y+height, x:x+width] ``` #### 2. **模型构建训练** - **框架选择**: 如引用[2]所示,可通过预定义模型结构(如`faceRecoModel`)加载权重,输入形状需匹配训练时的设定(例如`(3, 96, 96)`对应通道优先的输入格式)[^2]。 - **损失函数**: 引用[1]提到,$ArcFace$、$CosFace$等损失函数通过角度间隔优化特征空间分布,公式为: $$ \mathcal{L} = -\log \frac{e^{s(\cos(\theta + m))}}{e^{s(\cos(\theta + m))} + \sum_{j \neq y} e^{s\cos\theta_j}} $$ 其中$\theta$为特征类别权重的夹角,$m$为间隔参数。 - **训练流程**: 1. 加载预训练模型(如VGGFace或CASIA-WebFace预训练权重); 2. 冻结部分层进行微调; 3. 使用Triplet Loss或ArcFace Loss优化特征提取。 #### 3. **模型应用** - **人脸验证**: 计算两张人脸特征向量的余弦相似度: $$ \text{similarity} = \frac{\boldsymbol{f_1} \cdot \boldsymbol{f_2}}{\|\boldsymbol{f_1}\| \|\boldsymbol{f_2}\|} $$ 若相似度超过阈值(如0.7),则判定为同一人。 - **人脸识别**: 将提取的特征数据库中的特征进行最近邻搜索(如KNN或Faiss索引)。 #### 4. **优化部署** - **模型压缩**:使用量化(如TensorRT)或剪枝减少计算量; - **实时处理**:结合OpenCV或WebRTC实现视频流实时检测。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值