论文阅读笔记:AM-Softmax: Additive Margin Softmax for Face Verification

本文介绍AM-Softmax算法,一种改进的人脸特征提取方法,通过增加余弦距离的间隔提升人脸识别精度。相较于L-Softmax和A-Softmax,AM-Softmax简化计算并提高效率,在LFW和MegaFace数据集上表现优异。

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

论文阅读笔记:AM-Softmax: Additive Margin Softmax for Face Verification

Tags:Deep_Learning_基础论文


本文主要包含如下内容:

  论文地址
  代码地址
  参考博客

  本篇论文来自电子科技大学UESTC,论文参考NormFace、A-Softmax进行优化,提出了AM-Softmax。


主要思想


  L-Softmax, A-Softmax引入了角间距的概念,用于改进传统的softmax loss函数,使得人脸特征具有更大的类间距和更小的类内距。作者在这些方法的启发下,提出了一种更直观和更易解释的additive margin Softmax (AM-Softmax)。同时,本文强调和讨论了特征正则化的重要性。实验表明AM-Softmax在LFW和MegaFace得到了比之前方法更好的效果。
  


算法原理


  L-Softmax和A-Softmax均是引入了一个参数因子m 将权重W和f的cos距离变为cos(mθ),通过m 来调节特征间的距离。与前两者类似,AM-Softmax将cos(θ)的式子改写为:式子是一个单调递减的函数,且比L-Softmax/A-Softmax所用的 Ψ(θ)在形式和计算时更为简单。

Ψ(θ)=cos(θ)m Ψ ( θ ) = c o s ( θ ) − m

LAMS=1ni=1nloges(cosθyim)es(cosθyim)+cj=1,jyiescos(θj) L A M S = − 1 n ∑ i = 1 n l o g e s ⋅ ( c o s θ y i − m ) e s ( c o s θ y i − m ) + ∑ j = 1 , j ≠ y i c e s ⋅ c o s ( θ j )

  其中s是一个缩放因子,论文中固定为30。
  角度距离与余弦距离的关系:Asoftmax是用m乘以θ,而AMSoftmax是用cosθ减去m,这是两者的最大不同之处:一个是角度距离,一个是余弦距离。之所以选择cosθ-m而不是cos(θ-m),这是因为我们从网络中得到的是W和f的内积,如果要优化cos(θ-m)那么会涉及到arccos操作,计算量过大。
  归一化特征 feature normalization:高质量的图片提取出来的特征范数大,低质量的图片提取出来的特征范数小,在进行了feature normalizaiton后,这些质量较差的图片特征会产生更大的梯度,导致网络在训练过程中将更多的注意力集中在这些样本上。因此,对于数据集图片质量较差时,更适合采用feature normalization。
  


实验结果


  
  值得注意的是,在LFW集上,未采用feature normalization比采用了feature normalizaiton的结果更好,作者分析是由于LFW的数据质量较高。
  这里的了feature normalizaiton指的是将Scale层s的参数进行相应的更换,即将固定的s参数改变为对应的特征归一化尺度。即根据特征,缩放比例不一样了。


总结


  本文在特征和权值正则化的情况下,提出了一种 additive margin Softmax,更直观也更易解释,同时也取得了比A-Softmax更好的实验结果。


代码实现


  代码可以参考NormFace的相关代码, 比较类似。只是在上面进行想应该改进。
  这里在NormFace的基础上,提出了新的层LabelSpecificAdd,即AMSoftmax的核心,将cosθ减去m。

layer {
  name: "norm1"
  type: "Normalize"
  bottom: "fc5"
  top: "norm1"
}
layer {
  name: "fc6_l2"
  type: "InnerProduct"
  bottom: "norm1"
  top: "fc6"
  param {
    lr_mult: 1
  }
  inner_product_param{
    num_output: 10516
    normalize: true
    weight_filler {
      type: "xavier"
    }
    bias_term: false
  }
}
layer {
  name: "label_specific_margin"
  type: "LabelSpecificAdd"
  bottom: "fc6"
  bottom: "label"
  top: "fc6_margin"
  label_specific_add_param {
    bias: -0.35
  }
}
layer {
  name: "fc6_margin_scale"
  type: "Scale"
  bottom: "fc6_margin"
  top: "fc6_margin_scale"
  param {
    lr_mult: 0
    decay_mult: 0
  }
  scale_param {
    filler{
      type: "constant"
      value: 30
    }
  }
}
layer {
  name: "softmax_loss"
  type: "SoftmaxWithLoss"
  bottom: "fc6_margin_scale"
  bottom: "label"
  top: "softmax_loss"
  loss_weight: 1
}
layer {
  name: "Accuracy"
  type: "Accuracy"
  bottom: "fc6"
  bottom: "label"
  top: "accuracy"
  include { 
    phase: TEST
  }
}

label_specific_add_layer.hpp/label_specific_add_layer.cpp


  label_specific_add_layer.hpp/label_specific_add_layer.cpp(执行cosθ减去m操作)
  公式:

Ψ(θ)=cos(θ)m Ψ ( θ ) = c o s ( θ ) − m

#ifndef CAFFE_LABEL_SPECIFIC_ADD_LAYER_HPP_
#define CAFFE_LABEL_SPECIFIC_ADD_LAYER_HPP_

#include <vector>

#include "caffe/blob.hpp"
#include "caffe/layer.hpp"
#include "caffe/proto/caffe.pb.h"

#ifndef M_PI
#define M_PI 3.14159265358979323846
#endif

namespace caffe {

template <typename Dtype>
class LabelSpecificAddLayer : public Layer<Dtype> {
 public:
  explicit LabelSpecificAddLayer(const LayerParameter& param)
      : Layer<Dtype>(param) {}
  virtual void LayerSetUp(const vector<Blob<Dtype>*>& bottom,
                          const vector<Blob<Dtype>*>& top);
  virtual void Reshape(const vector<Blob<Dtype>*>& bottom,
                       const vector<Blob<Dtype>*>& top);

  virtual inline const char* type() const { return "LabelSpecificAdd"; }
  virtual inline int MinNumBottomBlobs() const { return 2; }

 protected:
  virtual void Forward_cpu(const vector<Blob<Dtype>*>& bottom,
      const vector<Blob<Dtype>*>& top);
  virtual void Forward_gpu(const vector<Blob<Dtype>*>& bottom,
      const vector<Blob<Dtype>*>& top);

  virtual void Backward_cpu(const vector<Blob<Dtype>*>& top,
      const vector<bool>& propagate_down, const vector<Blob<Dtype>*>& bottom);
  virtual void Backward_gpu(const vector<Blob<Dtype>*>& top,
      const vector<bool>& propagate_down, const vector<Blob<Dtype>*>& bottom);

  Dtype bias_;
  bool transform_test_;
  bool anneal_bias_;
  Dtype bias_base_;
  Dtype bias_gamma_;
  Dtype bias_power_;
  Dtype bias_min_;
  Dtype bias_max_;
  int iteration_;
};

}  // namespace caffe

#endif  // CAFFE_LABEL_SPECIFIC_ADD_LAYER_HPP_
#include <algorithm>
#include <vector>

#include "caffe/layers/label_specific_add_layer.hpp"

namespace caffe {

  template <typename Dtype>
  void LabelSpecificAddLayer<Dtype>::LayerSetUp(const vector<Blob<Dtype>*>& bottom,
                                                    const vector<Blob<Dtype>*>& top) {
    const LabelSpecificAddParameter& param = this->layer_param_.label_specific_add_param();
    bias_ = param.bias();
    transform_test_ = param.transform_test() & (this->phase_ == TRAIN);
    anneal_bias_ = param.has_bias_base();
    bias_base_ = param.bias_base();
    bias_gamma_ = param.bias_gamma();
    bias_power_ = param.bias_power();
    bias_min_ = param.bias_min();
    bias_max_ = param.bias_max();
    iteration_ = param.iteration();
  }

  template <typename Dtype>
  void LabelSpecificAddLayer<Dtype>::Reshape(const vector<Blob<Dtype>*>& bottom,
                                                    const vector<Blob<Dtype>*>& top) {
    if(top[0] != bottom[0]) top[0]->ReshapeLike(*bottom[0]);
    if (top.size() == 2) top[1]->Reshape({ 1 });
  }

template <typename Dtype>
void LabelSpecificAddLayer<Dtype>::Forward_cpu(const vector<Blob<Dtype>*>& bottom,
                                                  const vector<Blob<Dtype>*>& top) {
  const Dtype* bottom_data = bottom[0]->cpu_data();
  const Dtype* label_data = bottom[1]->cpu_data();
  Dtype* top_data = top[0]->mutable_cpu_data();

  int num = bottom[0]->num();   // 返回batch_size
  int count = bottom[0]->count();   //返回输入的维度
  int dim = count / num;    // 对应输出的类别数

  if (top[0] != bottom[0]) caffe_copy(count, bottom_data, top_data);

  if (!transform_test_ && this->phase_ == TEST) return;     // 如果测试,则不进行该操作(思路正确)

  if (anneal_bias_) {   // 计算偏差,这里可以模拟模拟退化(缓慢变化)
    bias_ = bias_base_ + pow(((Dtype)1. + bias_gamma_ * iteration_), bias_power_) - (Dtype)1.;
    bias_ = std::max(bias_, bias_min_);
    bias_ = std::min(bias_, bias_max_);
    iteration_++;
  }
  if (top.size() == 2) {
    top[1]->mutable_cpu_data()[0] = bias_;
  }     // 输出计算偏差结果

  for (int i = 0; i < num; ++i) {
    int gt = static_cast<int>(label_data[i]);
    if(top_data[i * dim + gt] > -bias_) top_data[i * dim + gt] += bias_;    // 对应标签位置加上bias
  }
}

template <typename Dtype>
void LabelSpecificAddLayer<Dtype>::Backward_cpu(const vector<Blob<Dtype>*>& top,
                                                   const vector<bool>& propagate_down,
                                                   const vector<Blob<Dtype>*>& bottom) {       // 反向传播就是本身,故复制本身即可
  if (top[0] != bottom[0] && propagate_down[0]) {
    const Dtype* top_diff = top[0]->cpu_diff();
    Dtype* bottom_diff = bottom[0]->mutable_cpu_diff();
    int count = bottom[0]->count();
    caffe_copy(count, top_diff, bottom_diff);
  }
}


#ifdef CPU_ONLY
STUB_GPU(LabelSpecificAddLayer);
#endif

INSTANTIATE_CLASS(LabelSpecificAddLayer);
REGISTER_LAYER_CLASS(LabelSpecificAdd);

}  // namespace caffe
### A-SoftmaxAM-Softmax 和 AAM-Softmax 的区别与应用场景 #### 背景介绍 在深度学习领域,尤其是人脸识别任务中,设计有效的损失函数对于提升模型性能至关重要。A-SoftmaxAM-Softmax 和 AAM-Softmax 都是在传统 SoftMax Loss 基础上改进而来的高级损失函数,旨在增强类间区分性和类内紧凑性。 --- #### 1. **A-Softmax** A-Softmax(Angular Softmax)的核心思想是通过对角度施加约束来增大类别之间的间隔[^2]。具体来说,它通过引入一个超参数 \( m \),强制要求预测的角度 \( \theta_{y_i} \) 至少比其他类别的最小角度大 \( m\pi/rad \)[^4]。这种机制使得特征分布在高维空间中的分布更加集中于特定区域,从而提高分类准确性。 其主要特点如下: - 使用球面约束使特征向量规范化到单位长度。 - 提升了类间的判别能力,但由于优化目标复杂化可能导致收敛速度变慢。 实现方式通常涉及对原始 logits 进行变换操作: ```python import torch import torch.nn as nn class ASoftmaxLoss(nn.Module): def __init__(self, num_classes, embedding_size, margin=4): super(ASoftmaxLoss, self).__init__() self.num_classes = num_classes self.embedding_size = embedding_size self.margin = margin def forward(self, x, labels): cos_theta = F.linear(F.normalize(x), F.normalize(self.weight)) phi_theta = (-1)**k * cos(m*theta) - 2*k one_hot = torch.zeros_like(cos_theta) one_hot.scatter_(1, labels.view(-1, 1).long(), 1) output = (one_hot * phi_theta) + ((1.0 - one_hot) * cos_theta) logit = output * s loss = F.cross_entropy(logit, labels) return loss ``` --- #### 2. **AM-Softmax** AM-SoftmaxAdditive Margin Softmax)进一步简化了 A-Softmax 中复杂的三角函数计算,转而采用一种线性的加法形式来增加边界[^3]。它的核心在于直接修改 logits 计算公式,在正样本方向减去一个固定值 \( m \),而在负样本方向保持不变。这样既保留了 A-Softmax 的优势又降低了训练难度。 关键特性包括: - 更容易实现且稳定; - 减少了因多次幂运算带来的数值不稳定风险。 以下是其实现代码片段: ```python class AMSoftmaxLoss(nn.Module): def __init__(self, num_classes, embedding_size, margin=0.35, scale=30): super(AMSoftmaxLoss, self).__init__() self.num_classes = num_classes self.scale = scale self.margin = margin self.weight = nn.Parameter(torch.Tensor(num_classes, embedding_size)) def forward(self, embeddings, labels): cosine = F.linear(F.normalize(embeddings), F.normalize(self.weight)) one_hot = torch.zeros_like(cosine) one_hot.scatter_(1, labels.view(-1, 1), 1) adjusted_cosine = self.scale * (cosine - one_hot * self.margin) loss = F.cross_entropy(adjusted_cosine, labels) return loss ``` --- #### 3. **AAM-Softmax** AAM-SoftmaxAdditive Angular Margin Softmax),也称为 ArcFace,是对 AM-Softmax 的又一次升级版本。不同于简单地在线性维度上加入偏移项,该方法提出了基于角度的加法规则——即给定真实标签对应的余弦相似度减少一定弧度作为惩罚因子。这种方法能够更好地捕捉数据内在几何结构并促进更优解的学习过程。 技术亮点总结为以下几点: - 明确考虑到了欧几里得距离之外的角度关系; - 可以有效缓解由于尺度缩放引起的信息丢失问题。 下面是 Python 实现示例: ```python class AAMSoftmaxLoss(nn.Module): def __init__(self, num_classes, embedding_size, margin=0.5, scale=64): super(AAMSoftmaxLoss, self).__init__() self.num_classes = num_classes self.scale = scale self.margin = margin self.weight = nn.Parameter(torch.FloatTensor(num_classes, embedding_size)) def forward(self, embeddings, labels): cosine = F.linear(F.normalize(embeddings), F.normalize(self.weight)) theta = torch.acos(torch.clamp(cosine, -1.0 + 1e-7, 1.0 - 1e-7)) target_logits = torch.cos(theta[range(len(labels)), labels] + self.margin) final_target_logits = torch.cos(target_logits) onehot = torch.zeros_like(cosine) onehot.scatter_(1, labels.view(-1, 1), 1) adjusted_cosine = cosine + onehot * (final_target_logits - cosine[range(len(labels)), labels].unsqueeze(1)) scaled_logit = self.scale * adjusted_cosine loss = F.cross_entropy(scaled_logit, labels) return loss ``` --- ### 总结对比表 | 特性/算法 | A-Softmax | AM-Softmax | AAM-Softmax | |------------------|------------------------------------|-----------------------------------|----------------------------------| | 边界类型 | 多次幂 | 加法 | 角度加法 | | 数学表达式 | \( (\cos(\theta))^m \) | \( \cos(\theta)-m \) | \( \cos(\theta+m) \) | | 收敛效率 | 较低 | 较高 | 较高 | | 类间分离程度 | 很好 | 好 | 极佳 | --- ### 应用场景建议 - 如果追求极致精度并且可以接受较长训练时间,则推荐选用 **AAM-Softmax** 或者 **A-Softmax**。 - 对于资源受限或者希望快速迭代实验的情况下,可以选择相对简单的 **AM-Softmax** 来获得不错的平衡表现。 ---
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值