17

基于Caffe的Large Margin Softmax Loss的实现(上)

小喵的唠叨话:在写完上一次的博客之后,已经过去了2个月的时间,小喵在此期间,做了大量的实验工作,最终在使用的DeepID2的方法之后,取得了很不错的结果。这次呢,主要讲述一个比较新的论文中的方法,L-Softmax,据说单model在LFW上能达到98.71%的等错误率。更重要的是,小喵觉得这个方法和DeepID2并不冲突,如果二者可以互补,或许单model达到99%+将不是梦想。

和上一篇博客一样,小喵对读者做了如下的假定:

  1. 了解Deep Learning的基本知识。
  2. 仔细阅读过L-Softmax的论文,了解其中的数学推导。
  3. 使用Caffe作为训练框架。
  4. 即使不满足上述3条,也能持之以恒的学习。

L-Softmax的论文:Large-Margin Softmax Loss for Convolutional Neutral Networks

Google一下,第一条应该就是论文的地址,鉴于大家时间有限,小喵把原文地址也贴出来了,但不保证长期有效。http://jmlr.org/proceedings/papers/v48/liud16.pdf

这里我们也将整个系列分几部分来讲。

一、margin与lambda

margin和lambda这两个参数是我们这篇博客的重点。也是整篇论文的重点。对于分类的任务,每个样本都会有N的输出的分数(N的类别),如果在训练中,人为的使正确类别的得分变小,也就是说加大了区分正确类别的难度,那么网络就会学习出更有区分能力的特征,并且加大类间的距离。作者选用的加大难度的方式就是改变最后一个FC层中的weight和特征之间的角度值,角度增大的倍数就是margin,从而使特定类别的得分变小。而第二个参数lambda是为了避免网络不收敛而设定的,我们之后会讲到。

为了实现这个效果,我们需要设计一个新的层,large_margin_inner_product_layer。这个层和一般的inner_product_layer很相似,但是多了特定类别削弱的功能。

考虑到这个层是有参数的,我们需要在caffe.proto(caffe_home/src/caffe/proto/caffe.proto)中做一些修改。这里的定义是按照protobuf的语法写的,简单的修改只要照着其他的参数来改写就好。

首先定义我们的这个层的参数。

参数的定义和InnerProductParameter非常相似,只是多了两个参数margin和lambda。

之后在LayerParameter添加一个可选参数(照着InnerProductParameter写就好)。

这时,喵粉可能很在意这个147是怎么回事。其实呢,在protobuf中,每个结构中的变量都需要一个id,只要保证不重复即可。我们在LayerParameter的最开始可以看到这么一行注释:

next-availabel-layer-id

说明下一个有效的id是147。这里我们新加的参数就果断占用了这个id。修改之后,建议把注释改一下(不要人为的挖坑):

LayerParameter next available layer-specific ID: 148 (last added: large_margin_inner_product_param)

避免之后再新加层的时候出问题。

工作完毕,我们就可以在train_val.prototxt中用这种方式使用这个新层了(具体的使用,后面再说):

二,运筹帷幄之成员变量

我们刚刚在caffe.proto中,添加了新参数的定义。而事实上,我们还没有这个层的具体实现。这部分,主要介绍我们需要的临时变量。

首先,我们要理清整个计算的流程。

先看前馈。

第一步,需要求出W和x的夹角的余弦值:

    \[\cos(\theta_j)=\frac{W_j^Tx_i}{\|W_j\|\|x_i\|}\]

第二步,计算m倍角度的余弦值:

    \[\cos(m\theta_i)=\sum_n(-1)^n{C_m^{2n}\cos^{m-2n}(\theta_i)\cdot(1-\cos(\theta_i)^2)^n}, (2n\leq m)\]

第三步,计算前馈:

    \[f_{y_{i}}=(-1)^k\cdot\|W_{y_{i}}\|\|x_{i}\|\cos(m\theta_i)-2k\cdot\|W_{y_i}\|\|x_i\|\]

k是根据\cos(\theta)的取值决定的。

后馈比前馈要复杂一些,不过使用的变量也是一样的。

因此我们可以编写自己的头文件了。

这里主要是复制了inner_product_layer.hpp,然后做了一点修改。具体是增加了几个成员变量,同时改了ExactNumBottomBlobs的返回值,因为我们的这个层磁带bottom需要两个,前一层的feature和样本的label。

三、内存和常量的初始化

这部分,主要给我们的各个成员变量分配内存,同时给几个常量进行初始化。这里也是照着inner_product_layer.cpp来写的,在setup的时候,增加了一些用于初始化的代码,并删除了forward_cpu和backwark_cpu的具体实现。

修改之后的代码如下:

至此,large_margin_inner_product_layer的准备工作就做完了。下一篇博客,我们来详细的讨论前馈的具体实现。

 

如果您觉得本文对您有帮助,那请小喵喝杯茶吧~~O(∩_∩)O~~ 小喵为了写公式,还专门学习了\LaTeX

%e6%89%93%e8%b5%8f

 

转载请注明出处~

miao

miao

17 Comments

  1. 期待博主快点放出实现 :mrgreen: ,有个问题根据公式11推导k的值对应到公式6中,结果是不对的,但是作者给出的回复是公式11是没有问题的。不知道博主按照论文实现后能否正常训练?

    • 公式11没问题吧,和6的意思一样。我后馈已经计算出了偏导,在margin为2的时候,验证和作者公式12、13一致。目前还差对w_diff和x_diff做修正的部分。

      • 分析m=2的时候,根据公式11,当k=0是 cos(theta) <=cos(pi/2); 说明theta>=pi/2;回到公式6,k=0时, 0<=theta<=pi/2,这是不是矛盾的?

    • 文中确实出现了一些typo,我已经更改了一些并放在了arxiv以及wyliu.com中了,谢谢。

    • 我实现了后馈,但是并不能很好的训练。问作者,他说还有一些技巧,所以在等他的代码。

      • 我也试了下 ,自己做梯度检查应该是没有问题的…然后训练的时候loss有时候会突然变得很大…不知道是不是边界情况梯度NAN了…Backward我没有用文章里那种方式,我是用反三角函数去求导的…不知道数值上会不会有些问题

  2. 谢谢博主的分享,不知道博主能否邮件发送反向传播部分的代码给我呢,最近也希望能跑下这个方法,谢谢博主了!

  3. 按照论文的方法来实现反向传播,但是发现训练得到的loss值一直都是87,应该是梯度溢出了…估计需要写个测试代码来测试反向传播部分是否写对了

  4. 谢谢博主,虽然自己简单复现了论文效果,但效率不够好。最后还是拿到了作者的代码在研究

发表评论

电子邮件地址不会被公开。