A-Softmax的总结及与L-Softmax的对比——SphereFace

目录

1. A-Softmax的推导
2. A-Softmax Loss的性质
3. A-Softmax的几何意义
4. 源码解读
A-Softmax的效果
与L-Softmax的区别

A-Softmax的总结及与L-Softmax的对比——SphereFace

【引言】SphereFace在MegaFace数据集上识别率在2017年排名第一,用的A-Softmax Loss有着清晰的几何定义,能在比较小的数据集上达到不错的效果。这个是他们总结成果的论文:SphereFace: Deep Hypersphere Embedding for Face Recognition。我对论文做一个小的总结。

1. A-Softmax的推导

回顾一下二分类下的Softmax后验概率,即:

p1=exp(WT1x+b1)exp(WT1x+b1)+exp(WT2x+b2)p2=exp(WT2x+b2)exp(WT1x+b1)+exp(WT2x+b2)(1.1) (1.1)p1=exp⁡(W1Tx+b1)exp⁡(W1Tx+b1)+exp⁡(W2Tx+b2)p2=exp⁡(W2Tx+b2)exp⁡(W1Tx+b1)+exp⁡(W2Tx+b2)

显然决策的分界在当 p1=p2 p1=p2时,所以决策界面是 (W1W2)x+b1b2=0 (W1−W2)x+b1−b2=0。我们可以将 WTix+bi WiTx+bi写成 WTixcos(θi)+bi ‖WiT‖⋅‖x‖cos⁡(θi)+bi,其中 θi θi Wi Wi x x的夹角,如对 Wi Wi归一化且设偏置 bi bi为零( Wi=1 ‖Wi‖=1 bi=0 bi=0),那么当 p1=p2 p1=p2时,我们有 cos(θ1)cos(θ2)=0 cos⁡(θ1)−cos⁡(θ2)=0。从这里可以看到,如里一个输入的数据特征 xi xi属于 yi yi类,那么 θyi θyi应该比其它所有类的角度都要小,也就是说在向量空间中 Wyi Wyi要更靠近 xi xi
我们用的是Softmax Loss,对于输入 xi xi,Softmax Loss  Li Li定义以下:

Li=log(exp(WTyixi+byi)jexp(WTjxi+bj))=log(exp(WTyixicos(θyi,i)+byi)jexp(WTjxicos(θj,i)+bj))(1.2) (1.2)Li=−log⁡(exp⁡(WyiTxi+byi)∑jexp⁡(WjTxi+bj))=−log⁡(exp⁡(‖WyiT‖·‖xi‖cos⁡(θyi,i)+byi)∑jexp⁡(‖WjT‖·‖xi‖cos⁡(θj,i)+bj))

(1.2) (1.2)中的 j[1,K] j∈[1,K],其中 K K类别的总数。上面我们限制了一些条件: Wi=1 ‖Wi‖=1 bi=0 bi=0,由这些条件,可以得到修正的损失函数(也就是论文中所以说的modified softmax loss):
Lmodified=1Nilog(exp(xicos(θyi,i))jexp(xicos(θj,i)))(1.3) (1.3)Lmodified=1N∑i−log⁡(exp⁡(‖xi‖cos⁡(θyi,i))∑jexp⁡(‖xi‖cos⁡(θj,i)))

在二分类问题中,当 cos(θ1)>cos(θ2) cos⁡(θ1)>cos⁡(θ2)时,可以确定属于类别1,但分类1与分类2的决策面是同一分,说明分类1与分类2之间的间隔(margin)相当小,直观上的感觉就是分类不明显。如果要让分类1与分类2有一个明显的间隔,可以做两个决策面,对于类别1的决策平面为: cos(mθ1)=cos(θ2) cos⁡(mθ1)=cos⁡(θ2),对于类别2的策平面为: cos(θ1)=cos(mθ2) cos⁡(θ1)=cos⁡(mθ2),其中 m2,mN m≥2,m∈N m m是整数的目的是为了方便计算,因为可以利用倍角公式, m2 m≥2说明与该分类的最大夹角要比其它类的小小夹角还要小 m m倍。如果 m=1 m=1,那么类别1与类别2的决策平面是同一个平面,如果 m2 m≥2v,那么类别1与类别2的有两个决策平面,相隔多大将会在性质中说明。从上述的说明与 Lmodified Lmodified可以直接得到A-Softmax Loss:

Lang=1Nilog(exp(xicos(mθyi,i))exp(xicos(mθyi,i))+jyiexp(xicos(θj,i)))(1.4) (1.4)Lang=1N∑i−log⁡(exp⁡(‖xi‖cos⁡(mθyi,i))exp⁡(‖xi‖cos⁡(mθyi,i))+∑j≠yiexp⁡(‖xi‖cos⁡(θj,i)))

其中 θyi,i[0,πm] θyi,i∈[0,πm],因为 θyi,i θyi,i在这个范围之外可可能会使得 mθyi,i>θj,i,jyi mθyi,i>θj,i,j≠yi(这样就不属于分类 yi yi了),但 cos(mθ1)>cos(θ2) cos⁡(mθ1)>cos⁡(θ2)仍可能成立,而我们Loss方程用的还是 cos(θ) cos⁡(θ)。为了避免这个问题,可以重新设计一个函数来替代 cos(mθyi,i) cos⁡(mθyi,i),定义 ψ(θyi,i)=(1)kcos(mθyi,i)2k ψ(θyi,i)=(−1)kcos⁡(mθyi,i)−2k,其中 θyi,i[kπm,(k+1)πm] θyi,i∈[kπm,(k+1)πm] k[1,k] 且k∈[1,k]。这个函数的定义可以使得 ψ ψ θyi,i θyi,i单调递减,如果 mθyi,i>θj,i,jyi mθyi,i>θj,i,j≠yi, 那么必有 ψ(θyi,i)<cos(θj,i) ψ(θyi,i)<cos⁡(θj,i),反而亦然,这样可以避免上述的问题,所以有:

Lang=1Nilog(exp(xiψ(θyi,i))exp(xiψ(θyi,i))+jyiexp(xicos(θj,i)))(1.5) (1.5)Lang=1N∑i−log⁡(exp⁡(‖xi‖ψ(θyi,i))exp⁡(‖xi‖ψ(θyi,i))+∑j≠yiexp⁡(‖xi‖cos⁡(θj,i)))

对于以上三种二分类问题的Loss(多分类是差不多的情况)的决策面,可以总结如下表:

Loss FuntionSoftmax LossModified Softmax LossA-Softmax LossDecision Boundary(W1W2)x+b1b2=0x(cosθ1cosθ2)=0Class1:x(cosmθ1cosθ2)=0Class2:x(cosθ1cosmθ2)=0 Loss FuntionDecision BoundarySoftmax Loss(W1−W2)x+b1−b2=0Modified Softmax Loss‖x‖(cos⁡θ1−cos⁡θ2)=0A-Softmax LossClass1:‖x‖(cos⁡mθ1−cos⁡θ2)=0Class2:‖x‖(cos⁡θ1−cos⁡mθ2)=0

论文中还给出了这三种不同Loss的几何意义,可以看到的是普通的softmax(Euclidean Margin Loss)是在欧氏空间中分开的,它映射到欧氏空间中是不同的区域的空间,决策面是一个在欧氏空间中的平面,可以分隔不同的类别。Modified Softmax Loss与A-Softmax Loss的不同之处在于两个不同类的决策平面是同一个,不像A-Softmax Loss,有两个分隔的决策平面且决策平面分隔的大小还是与 m m的大小成正相关,如下图所示。

Figure3

2. A-Softmax Loss的性质

性质1:A-Softmax Loss定义了一个大角度间隔的学习方法, m m越大这个间隔的角度也就越大,相应区域流形的大小就越小,这就导致了训练的任务也越困难。
这个性质是相当容易理解的,如图1所示:这个间隔的角度为 (m1)θ1 (m−1)θ1,所以 m m越大,则间隔的角度就越小;同时 mθ1<π mθ1<π,当所以 m m越大,则相应的区域流形 θ1 θ1就越小。

Figure4

图1:性质1的示意图

定义1: mmin mmin被定义为当 m>mmin m>mmin时有类内间的最大角度特征距离小于类间的最小角度特征距离。
性质2:在二分类问题中: mmin>2+3 mmin>2+3,有多分类问题中: mmin3 mmin≥3
证明:1.对于二分类问题,设 W1 W1 W2 W2分别是类别1与类别2的权重, W1 W1 W2 W2之间的夹角是 θ12 θ12,输入的特征为 x x,那么权重与输入特征之间的夹角就决定了输入的特征属于那个类别,不失一般性地可以认为输入的特征性于类别1,则有 mθ1<θ2 mθ1<θ2。当 x x θ12 θ12之间时,如图2所示,可以由 mθ1=θ2 mθ1=θ2求出这时 θ1 θ1的最大值为 θinmax1=θ12m+1 θmax1in=θ12m+1

图2: x x θ12 θ12之间时的示意图

x x θ12 θ12之外时,第一种情况是当 θ12m1mπ θ12≤m−1mπ,如图3所示,可以由 mθ1=θ2 mθ1=θ2求出这时 θ1 θ1的最大值为 θoutmax1=θ12m1 θmax1out=θ12m−1,还有一种情况就是当 θ1 θ1 θ2 θ2不是同一侧时, θ12<m1mπ θ12<m−1mπ,如图4所示,可以得到: θoutmax1=2πθ12m+1 θmax1out=2π−θ12m+1

图3: x x θ12 θ12之外时的示意图

图4: x x θ12 θ12之外时的示意图

无论是上述中的第一种情况还是第二种情况,类间的最小角度特征距离如图5所示情况中的 θinter θinter,所以有: θinter=(m1)θ1=m1m+1θ12 θinter=(m−1)θ1=m−1m+1θ12

图5:最小的类间距离示意图

以上的分析可以总结为以下方程:

θ12m1+θ12m+1m1m+1θ12,θ12m1mπ2πθ12m1+θ12m+1m1m+1θ12,θ12>m1mπ(2.1) (2.1)θ12m−1+θ12m+1≤m−1m+1θ12,θ12≤m−1mπ2π−θ12m−1+θ12m+1≤m−1m+1θ12,θ12>m−1mπ

解上述不等式可以行到 mmin2+3 mmin≥2+3
2.对于 K K类( K3 K≥3)问题,设 θi+1i θii+1是权重 Wi Wi Wi+1 Wi+1的夹角,显然最好的情况是 Wi Wi是均匀分布的,所以有 θi+1i=2πK θii+1=2πK。对于类内的最大距离与类间的小距离有以下方程:
θi+1im+1+ θii1m+1<min{(m1)θi+1im+1,(m1)θii1m+1}(2.2) (2.2)θii+1m+1+ θi−1im+1<min{(m−1)θii+1m+1,(m−1)θi−1im+1}

可以解得 mmin3 mmin≥3。综合上面对 mmin mmin的讨论,论文中取了 m=4 m=4

3. A-Softmax的几何意义

个人认为A-Softmax是基于一个假设:不同的类位于一个单位超球表面的不同区域。从上面也可以知道它的几何意义是权重所代表的在单位超球表面的点,在训练的过程中,同一类的输入映射到表面上会慢慢地向中心点(这里的中心点大部分时候和权重的意义相当)聚集,而到不同类的权重(或者中心点)慢慢地分散开来。 m m的大小是控制同一类点聚集的程度,从而控制了不同类之间的距离。从图6可以看到,不同的 m m对映射分布的影响(作者画的图真好看,也不知道作者是怎么画出来的)。

图6:不同的 m m对映射分布的影响

4. 源码解读

作者用Caffe实现了A-Softmax,可以参考这个wy1iu/SphereFace,来解读其中的一些细节。在实际的编程中,不需要直接实现式 (1.4) (1.4)中的 Lang Lang,可以在SoftmaxOut层前面加一层 MarginInnerProduct MarginInnerProduct,这个文件sphereface_model.prototxt的最后如下面引用所示,可以看到作者是加多了一层。具体的C++代码在margin_inner_product_layer.cpp

############### A-Softmax Loss ##############
layer {
  name: "fc6"
  type: "MarginInnerProduct"
  bottom: "fc5"
  bottom: "label"
  top: "fc6"
  top: "lambda"
  param {
    lr_mult: 1
    decay_mult: 1
  }
  margin_inner_product_param {
    num_output: 10572
    type: QUADRUPLE
    weight_filler {
      type: "xavier"
    }
    base: 1000
    gamma: 0.12
    power: 1
    lambda_min: 5
    iteration: 0
  }
}
layer {
  name: "softmax_loss"
  type: "SoftmaxWithLoss"
  bottom: "fc6"
  bottom: "label"
  top: "softmax_loss"
}

了解这个实现的思路后,关键看前向和后向传播,现在大部分的深度学习框架都支持自动求导了(如tensorflow,mxnet的gluon),但我还是建议大家写后向传播,因为自动求导会消耗显存或者内存(看运行的设备)而且肯定不如自己写的效率高。在Forword的过程中,有如下细节:

cosθi,jcos2θcos3θcos4θ=xiWjxiWjxiWnormjxi=2cos2θ1=4cos2θ3cosθ=8cos4θ8cos2θ1(4.1) (4.1)cos⁡θi,j=xi→⋅Wj→‖xi→‖⋅‖Wj→‖xi→⋅Wnormj→‖xi→‖cos⁡2θ=2cos2⁡θ−1cos⁡3θ=4cos2⁡θ−3cos⁡θcos⁡4θ=8cos4⁡θ−8cos2⁡θ−1

Mi,j={xicosθi,j=xiWnormj,xiψ(θi,j),if jyi if j=yi (4.2) (4.2)Mi,j={‖xi→‖cos⁡θi,j=xi→⋅Wnormj→,if j≠yi ‖xi→‖ψ(θi,j),if j=yi 

M M是输出,代码中的 sign_3_=(1)k,sign_4_=2k sign_3_=(−1)k,sign_4_=−2k,Caffe的代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
template  < typename  Dtype>
void  MarginInnerProductLayer<Dtype>::Forward_cpu( const  vector<Blob<Dtype>*>& bottom, const  vector<Blob<Dtype>*>& top)
{
   iter_ += (Dtype)1.;
   Dtype base_ = this ->layer_param_.margin_inner_product_param().base();
   Dtype gamma_ = this ->layer_param_.margin_inner_product_param().gamma();
   Dtype power_ = this ->layer_param_.margin_inner_product_param().power();
   Dtype lambda_min_ = this ->layer_param_.margin_inner_product_param().lambda_min();
   lambda_ = base_ * pow (((Dtype)1. + gamma_ * iter_), -power_);
   lambda_ = std::max(lambda_, lambda_min_);
   top[1]->mutable_cpu_data()[0] = lambda_;
   
   /************************* normalize weight *************************/
   Dtype* norm_weight = this ->blobs_[0]->mutable_cpu_data();
   Dtype temp_norm = (Dtype)0.;
   for  ( int  i = 0; i < N_; i++) {
     temp_norm = caffe_cpu_dot(K_, norm_weight + i * K_, norm_weight + i * K_);
     temp_norm = (Dtype)1./ sqrt (temp_norm);
     caffe_scal(K_, temp_norm, norm_weight + i * K_);
   }
 
   /************************* common variables *************************/
   // x_norm_ = |x|
   const  Dtype* bottom_data = bottom[0]->cpu_data();
   const  Dtype* weight = this ->blobs_[0]->cpu_data();
   Dtype* mutable_x_norm_data = x_norm_.mutable_cpu_data();
   for  ( int  i = 0; i < M_; i++) {
     mutable_x_norm_data[i] = sqrt (caffe_cpu_dot(K_, bottom_data + i * K_, bottom_data + i * K_));
   }
   Dtype* mutable_cos_theta_data = cos_theta_.mutable_cpu_data();
   caffe_cpu_gemm<Dtype>(CblasNoTrans, CblasTrans, M_, N_, K_, (Dtype)1.,
       bottom_data, weight, (Dtype)0., mutable_cos_theta_data);
   for  ( int  i = 0; i < M_; i++) {
     caffe_scal(N_, (Dtype)1./mutable_x_norm_data[i], mutable_cos_theta_data + i * N_);
   }
   // sign_0 = sign(cos_theta)
   caffe_cpu_sign(M_ * N_, cos_theta_.cpu_data(), sign_0_.mutable_cpu_data());
 
   /************************* optional variables *************************/
   switch  (type_) {
   case  MarginInnerProductParameter_MarginType_SINGLE:
     break ;
   case  MarginInnerProductParameter_MarginType_DOUBLE:
     // cos_theta_quadratic
     caffe_powx(M_ * N_, cos_theta_.cpu_data(), (Dtype)2., cos_theta_quadratic_.mutable_cpu_data());
     break ;
   case  MarginInnerProductParameter_MarginType_TRIPLE:
     // cos_theta_quadratic && cos_theta_cubic
     caffe_powx(M_ * N_, cos_theta_.cpu_data(), (Dtype)2., cos_theta_quadratic_.mutable_cpu_data());
     caffe_powx(M_ * N_, cos_theta_.cpu_data(), (Dtype)3., cos_theta_cubic_.mutable_cpu_data());
     // sign_1 = sign(abs(cos_theta) - 0.5)
     caffe_abs(M_ * N_, cos_theta_.cpu_data(), sign_1_.mutable_cpu_data());
     caffe_add_scalar(M_ * N_, -(Dtype)0.5, sign_1_.mutable_cpu_data());
     caffe_cpu_sign(M_ * N_, sign_1_.cpu_data(), sign_1_.mutable_cpu_data());
     // sign_2 = sign_0 * (1 + sign_1) - 2
     caffe_copy(M_ * N_, sign_1_.cpu_data(), sign_2_.mutable_cpu_data());
     caffe_add_scalar(M_ * N_, (Dtype)1., sign_2_.mutable_cpu_data());
     caffe_mul(M_ * N_, sign_0_.cpu_data(), sign_2_.cpu_data(), sign_2_.mutable_cpu_data());
     caffe_add_scalar(M_ * N_, - (Dtype)2., sign_2_.mutable_cpu_data());
     break ;
   case  MarginInnerProductParameter_MarginType_QUADRUPLE:
     // cos_theta_quadratic && cos_theta_cubic && cos_theta_quartic
     caffe_powx(M_ * N_, cos_theta_.cpu_data(), (Dtype)2., cos_theta_quadratic_.mutable_cpu_data());
     caffe_powx(M_ * N_, cos_theta_.cpu_data(), (Dtype)3., cos_theta_cubic_.mutable_cpu_data());
     caffe_powx(M_ * N_, cos_theta_.cpu_data(), (Dtype)4., cos_theta_quartic_.mutable_cpu_data());
     // sign_3 = sign_0 * sign(2 * cos_theta_quadratic_ - 1)
     caffe_copy(M_ * N_, cos_theta_quadratic_.cpu_data(), sign_3_.mutable_cpu_data());
     caffe_scal(M_ * N_, (Dtype)2., sign_3_.mutable_cpu_data());
     caffe_add_scalar(M_ * N_, (Dtype)-1., sign_3_.mutable_cpu_data());
     caffe_cpu_sign(M_ * N_, sign_3_.cpu_data(), sign_3_.mutable_cpu_data());
     caffe_mul(M_ * N_, sign_0_.cpu_data(), sign_3_.cpu_data(), sign_3_.mutable_cpu_data());
     // sign_4 = 2 * sign_0 + sign_3 - 3
     caffe_copy(M_ * N_, sign_0_.cpu_data(), sign_4_.mutable_cpu_data());
     caffe_scal(M_ * N_, (Dtype)2., sign_4_.mutable_cpu_data());
     caffe_add(M_ * N_, sign_4_.cpu_data(), sign_3_.cpu_data(), sign_4_.mutable_cpu_data());
     caffe_add_scalar(M_ * N_, - (Dtype)3., sign_4_.mutable_cpu_data());
     break ;
   default :
     LOG(FATAL) << "Unknown margin type." ;
   }

对于后面传播,求推比较麻烦,而且在作者的源码中训练用了不少的trick,并不能通过梯度测试,我写出推导过程,方便大家在看代码的时候可以知道作用用了哪些trick,作者对这些trick的解释是有助于模型的稳定收敛,并没有给出原理上的解释。

yij yi≠j时,有(注意作者源码中对 W W求导有明显的两个错误,一个是作者只对 Wnorm Wnorm求导,对不是对 W W,二个是没有考虑到 yij yi≠j的情况):

Mi,jxi,kMi,jWk,j=(xiWnormj)xi,k=Wnormk,j=(xiWj/Wj)Wk,j=1Wj(xiWj)Wk,j+(xiWj)(1/Wj)Wk,j=xi,kWjWnormk,jcosθi,jxiWj(4.3) (4.3)∂Mi,j∂xi,k=∂(xi→⋅Wnormj→)∂xi,k=Wnormk,j∂Mi,j∂Wk,j=∂(xi→⋅Wj→/‖Wj→‖)∂Wk,j=1‖Wj→‖∂(xi→⋅Wj→)∂Wk,j+(xi→⋅Wj→)∂(1/‖Wj→‖)∂Wk,j=xi,k‖Wj→‖−Wnormk,jcos⁡θi,j‖xi→‖‖Wj→‖

在这里我仅于 m=4 m=4为例子,当 yi=j,m=4 yi=j,m=4,有:

ifM1,i,jMi,jMi,jxi,kMi,jWk,j=xicos(θi,j)=xiψ(θi,j)=(1)k[8xi3M41,i,j8xi1M21,i,j+xi]2kxi=((1)k(24xi4M41,i,j+8xi2M21,i,j+1)2k)x⃗ xi,k+(1)k(32xi3M31,i,j16xi1M1,i,j)M1,i,jxi,k=((1)k(24cos4θi,j+8cos2θi,j+1)2k)xi,k+(1)k(32cos3θi,j16cosθi,j)Wk,j=(1)k(32cos3θi,j16cosθi,j)(xi,kWjWnormk,jcosθi,jxiWj)(4.4) (4.4)ifM1,i,j=‖xi→‖cos⁡(θi,j)Mi,j=‖xi→‖ψ(θi,j)=(−1)k[8‖xi→‖−3M1,i,j4−8‖xi→‖−1M1,i,j2+‖xi→‖]−2k‖xi→‖∂Mi,j∂xi,k=((−1)k(−24‖xi→‖−4M1,i,j4+8‖xi→‖−2M1,i,j2+1)−2k)∂‖x→‖∂xi,k+(−1)k(32‖xi→‖−3M1,i,j3−16‖xi→‖−1M1,i,j)∂M1,i,j∂xi,k=((−1)k(−24cos4⁡θi,j+8cos2⁡θi,j+1)−2k)xi,k+(−1)k(32cos3⁡θi,j−16cos⁡θi,j)Wk,j∂Mi,j∂Wk,j=(−1)k(32cos3⁡θi,j−16cos⁡θi,j)(xi,k‖Wj→‖−Wnormk,jcos⁡θi,j‖xi→‖‖Wj→‖)

要注意的是上述的 i,j,k i,j,k分别第i个样本、第j个输出特征和第k个输入特征。上面的仅是推导偏导数的过程,并没有涉及到梯度残差的反向传播,如果上层传过来的梯度残差为 Δ Δ,本层的向下层传播的残差为 δ δ(一个样本中的一个特征要对所有的输出累加),权重的更新值为 ζ ζ(一个权重要对所有的样本量累加),则可以得到:

δi,k=jMi,jxi,kΔi,jζk,j=iMi,jWk,jΔi,j(4.5) (4.5)δi,k=∑j∂Mi,j∂xi,kΔi,jζk,j=∑i∂Mi,j∂Wk,jΔi,j

Caffe代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
template  < typename  Dtype>
void  MarginInnerProductLayer<Dtype>::Backward_cpu( const  vector<Blob<Dtype>*>& top,
     const  vector< bool >& propagate_down,
     const  vector<Blob<Dtype>*>& bottom) {
 
   const  Dtype* top_diff = top[0]->cpu_diff();
   const  Dtype* bottom_data = bottom[0]->cpu_data();
   const  Dtype* label = bottom[1]->cpu_data();
   const  Dtype* weight = this ->blobs_[0]->cpu_data();
  
   // Gradient with respect to weight
   if  ( this ->param_propagate_down_[0]) {
     caffe_cpu_gemm<Dtype>(CblasTrans, CblasNoTrans, N_, K_, M_, (Dtype)1.,
         top_diff, bottom_data, (Dtype)1., this ->blobs_[0]->mutable_cpu_diff());
   }
   
   // Gradient with respect to bottom data
   if  (propagate_down[0]) {
     Dtype* bottom_diff = bottom[0]->mutable_cpu_diff();
     const  Dtype* x_norm_data = x_norm_.cpu_data();
     caffe_set(M_ * K_, Dtype(0), bottom_diff);
     switch  (type_) {
     case  MarginInnerProductParameter_MarginType_SINGLE: {
       caffe_cpu_gemm<Dtype>(CblasNoTrans, CblasNoTrans, M_, K_, N_, (Dtype)1.,
         top_diff, this ->blobs_[0]->cpu_data(), (Dtype)0.,
         bottom[0]->mutable_cpu_diff());
       break ;
     }
     case  MarginInnerProductParameter_MarginType_DOUBLE: {
       const  Dtype* sign_0_data = sign_0_.cpu_data();
       const  Dtype* cos_theta_data = cos_theta_.cpu_data();
       const  Dtype* cos_theta_quadratic_data = cos_theta_quadratic_.cpu_data();
       for  ( int  i = 0; i < M_; i++) {
         const  int  label_value = static_cast < int >(label[i]);
         for  ( int  j = 0; j < N_; j++) {
           if  (label_value != j) {
             // 1 / (1 + lambda) * w
             caffe_cpu_axpby(K_, (Dtype)1. / ((Dtype)1. + lambda_) * top_diff[i * N_ + j],
                             weight + j * K_, (Dtype)1., bottom_diff + i * K_);
           } else  {
             // 4 * sign_0 * cos_theta * w
             Dtype coeff_w = (Dtype)4. * sign_0_data[i * N_ + j] * cos_theta_data[i * N_ + j];
             // 1 / (-|x|) * (2 * sign_0 * cos_theta_quadratic + 1) * x
             Dtype coeff_x = (Dtype)1. / (-x_norm_data[i]) * ((Dtype)2. *
                             sign_0_data[i * N_ + j] * cos_theta_quadratic_data[i * N_ + j] + (Dtype)1.);
             Dtype coeff_norm = sqrt (coeff_w * coeff_w + coeff_x * coeff_x);
             coeff_w = coeff_w / coeff_norm;
             coeff_x = coeff_x / coeff_norm;
             caffe_cpu_axpby(K_, (Dtype)1. / ((Dtype)1. + lambda_) * top_diff[i * N_ + j] * coeff_w,
                             weight + j * K_, (Dtype)1., bottom_diff + i * K_);
             caffe_cpu_axpby(K_, (Dtype)1. / ((Dtype)1. + lambda_) * top_diff[i * N_ + j] * coeff_x,
                             bottom_data + i * K_, (Dtype)1., bottom_diff + i * K_);
           }
         }
       }
       // + lambda/(1 + lambda) * w
       caffe_cpu_gemm<Dtype>(CblasNoTrans, CblasNoTrans, M_, K_, N_, lambda_/((Dtype)1. + lambda_),
         top_diff, this ->blobs_[0]->cpu_data(), (Dtype)1.,
         bottom[0]->mutable_cpu_diff());
       break ;
     }
     case  MarginInnerProductParameter_MarginType_TRIPLE: {
       const  Dtype* sign_1_data = sign_1_.cpu_data();
       const  Dtype* sign_2_data = sign_2_.cpu_data();
       const  Dtype* cos_theta_quadratic_data = cos_theta_quadratic_.cpu_data();
       const  Dtype* cos_theta_cubic_data = cos_theta_cubic_.cpu_data();
       for  ( int  i = 0; i < M_; i++) {
         const  int  label_value = static_cast < int >(label[i]);
         for  ( int  j = 0; j < N_; j++) {
           if  (label_value != j) {
             caffe_cpu_axpby(K_, (Dtype)1. / ((Dtype)1. + lambda_) * top_diff[i * N_ + j],
                             weight + j * K_, (Dtype)1., bottom_diff + i * K_);
           } else  {
             // sign_1 * (12 * cos_theta_quadratic - 3) * w
             Dtype coeff_w = sign_1_data[i * N_ + j] * ((Dtype)12. *
                             cos_theta_quadratic_data[i * N_ + j] - (Dtype)3.);
             // 1 / (-|x|) * (8 * sign_1 * cos_theta_cubic - sign_2) * x
             Dtype coeff_x = (Dtype)1. / (-x_norm_data[i]) * ((Dtype)8. * sign_1_data[i * N_ + j] *
                               cos_theta_cubic_data[i * N_ + j] - sign_2_data[i * N_ +j]);
             Dtype coeff_norm = sqrt (coeff_w * coeff_w + coeff_x * coeff_x);
             coeff_w = coeff_w / coeff_norm;
             coeff_x = coeff_x / coeff_norm;
             caffe_cpu_axpby(K_, (Dtype)1. / ((Dtype)1. + lambda_) * top_diff[i * N_ + j] * coeff_w,
                             weight + j * K_, (Dtype)1., bottom_diff + i * K_);
             caffe_cpu_axpby(K_, (Dtype)1. / ((Dtype)1. + lambda_) * top_diff[i * N_ + j] * coeff_x,
                             bottom_data + i * K_, (Dtype)1., bottom_diff + i * K_);
           }
         }
       }
       // + lambda/(1 + lambda) * w
       caffe_cpu_gemm<Dtype>(CblasNoTrans, CblasNoTrans, M_, K_, N_, lambda_/((Dtype)1. + lambda_),
         top_diff, this ->blobs_[0]->cpu_data(), (Dtype)1.,
         bottom[0]->mutable_cpu_diff());
       break ;
     }
     case  MarginInnerProductParameter_MarginType_QUADRUPLE: {
       const  Dtype* sign_3_data = sign_3_.cpu_data();
       const  Dtype* sign_4_data = sign_4_.cpu_data();
       const  Dtype* cos_theta_data = cos_theta_.cpu_data();
       const  Dtype* cos_theta_quadratic_data = cos_theta_quadratic_.cpu_data();
       const  Dtype* cos_theta_cubic_data = cos_theta_cubic_.cpu_data();
       const  Dtype* cos_theta_quartic_data = cos_theta_quartic_.cpu_data();
       for  ( int  i = 0; i < M_; i++) {
         const  int  label_value = static_cast < int >(label[i]);
         for  ( int  j = 0; j < N_; j++) {
           if  (label_value != j) {
             caffe_cpu_axpby(K_, (Dtype)1. / ((Dtype)1. + lambda_) * top_diff[i * N_ + j],
                             weight + j * K_, (Dtype)1., bottom_diff + i * K_);
           } else  {
             // 1 / (1 + lambda) * sign_3 * (32 * cos_theta_cubic - 16 * cos_theta) * w
             Dtype coeff_w = sign_3_data[i * N_ + j] * ((Dtype)32. * cos_theta_cubic_data[i * N_ + j] -
                                 (Dtype)16. * cos_theta_data[i * N_ + j]);
             // 1 / (-|x|) * (sign_3 * (24 * cos_theta_quartic - 8 * cos_theta_quadratic - 1) +
             //                        sign_4) * x
             Dtype coeff_x = (Dtype)1. / (-x_norm_data[i]) * (sign_3_data[i * N_ + j] *
                             ((Dtype)24. * cos_theta_quartic_data[i * N_ + j] -
                             (Dtype)8. * cos_theta_quadratic_data[i * N_ + j] - (Dtype)1.) -
                              sign_4_data[i * N_ + j]);
             Dtype coeff_norm = sqrt (coeff_w * coeff_w + coeff_x * coeff_x);
             coeff_w = coeff_w / coeff_norm;
             coeff_x = coeff_x / coeff_norm;
             caffe_cpu_axpby(K_, (Dtype)1. / ((Dtype)1. + lambda_) * top_diff[i * N_ + j] * coeff_w,
                             weight + j * K_, (Dtype)1., bottom_diff + i * K_);
             caffe_cpu_axpby(K_, (Dtype)1. / ((Dtype)1. + lambda_) * top_diff[i * N_ + j] * coeff_x,
                             bottom_data + i * K_, (Dtype)1., bottom_diff + i * K_);
           }
         }
       }
       // + lambda/(1 + lambda) * w
       caffe_cpu_gemm<Dtype>(CblasNoTrans, CblasNoTrans, M_, K_, N_, lambda_/((Dtype)1. + lambda_),
         top_diff, this ->blobs_[0]->cpu_data(), (Dtype)1.,
         bottom[0]->mutable_cpu_diff());
       break ;
     }
     default : {
       LOG(FATAL) << "Unknown margin type." ;
     }
     }
   }
}

A-Softmax的效果

在训练模型(training)用的是A-Softmax函数,但在判别分类结果(vilidation)用的是余弦相似原理,如下图7所示:

图7

所用的模型如图8所示:

图8

效果如下所示(详细的对比,请看原文):

图9

A-Softmax在较小的数据集合上有着良好的效果且理论具有不错的可解释性,它的缺点也明显就是计算量相对比较大,也许这就是作者在论文中没有测试大数据集的原因。

与L-Softmax的区别

A-Softmax与L-Softmax的最大区别在于A-Softmax的权重归一化了,而L-Softmax则没的。A-Softmax权重的归一化导致特征上的点映射到单位超球面上,而L-Softmax则不没有这个限制,这个特性使得两者在几何的解释上是不一样的。如图10所示,如果在训练时两个类别的特征输入在同一个区域时,如下图10所示。A-Softmax只能从角度上分度这两个类别,也就是说它仅从方向上区分类,分类的结果如图11所示;而L-Softmax,不仅可以从角度上区别两个类,还能从权重的模(长度)上区别这两个类,分类的结果如图12所示。在数据集合大小固定的条件下,L-Softmax能有两个方法分类,训练可能没有使得它在角度与长度方向都分离,导致它的精确可能不如A-Softmax。

图10:类别1与类别2映射到特征空间发生了区域的重叠

图11:A-Softmax分类可能的结果

图12:L-Softmax分类可能的结果

【防止爬虫转载而导致的格式问题——链接】:http://www.cnblogs.com/heguanyou/p/7503025.html

猜你喜欢

转载自blog.csdn.net/lien0906/article/details/79240985