技术控

    今日:0| 主题:63445
收藏本版 (1)
最新软件应用技术尽在掌握

[其他] 基于Caffe的Large Margin Softmax Loss的实现(上)

[复制链接]
与我无关统统 发表于 2016-10-1 18:26:05
439 6
小喵的唠叨话:在写完上一次的博客之后,已经过去了2个月的时间,小喵在此期间,做了大量的实验工作,最终在使用的DeepID2的方法之后,取得了很不错的结果。这次呢,主要讲述一个比较新的论文中的方法,L-Softmax,据说单model在LFW上能达到98.71%的等错误率。更重要的是,小喵觉得这个方法和DeepID2并不冲突,如果二者可以互补,或许单model达到99%+将不是梦想。
  和上一篇博客一样,小喵对读者做了如下的假定:
  
       
  • 了解Deep Learning的基本知识。   
  • 仔细阅读过L-Softmax的论文,了解其中的数学推导。   
  • 使用Caffe作为训练框架。   
  • 即使不满足上述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的语法写的,简单的修改只要照着其他的参数来改写就好。
  首先定义我们的这个层的参数。
  1. message LargeMarginInnerProductParameter {
  2.   optionaluint32num_output = 1; // The number of outputs for the layer
  3.   optionalbool bias_term = 2 [default = true]; // whether to have bias terms
  4.   optionalFillerParameterweight_filler = 3; // The filler for the weight
  5.   optionalFillerParameterbias_filler = 4; // The filler for the bias
  6.   // The first axis to be lumped into a single inner product computation;
  7.   // all preceding axes are retained in the output.
  8.   // May be negative to index from the end (e.g., -1 for the last axis).
  9.   optionalint32axis = 5 [default = 1];
  10.   // Specify whether to transpose the weight matrix or not.
  11.   // If transpose == true, any operations will be performed on the transpose
  12.   // of the weight matrix. The weight matrix itself is not going to be transposed
  13.   // but rather the transfer flag of operations will be toggled accordingly.
  14.   optionalbool transpose = 6 [default = false];
  15.   optionaluint32margin = 7 [default = 1];
  16.   optionalfloat lambda = 8 [default = 0];
  17. }
复制代码
参数的定义和InnerProductParameter非常相似,只是多了两个参数margin和lambda。
  之后在LayerParameter添加一个可选参数(照着InnerProductParameter写就好)。
  1. optionalLargeMarginInnerProductParameterlarge_margin_inner_product_param = 147;
复制代码
这时,喵粉可能很在意这个147是怎么回事。其实呢,在protobuf中,每个结构中的变量都需要一个id,只要保证不重复即可。我们在LayerParameter的最开始可以看到这么一行注释:
   

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

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

  说明下一个有效的id是147。这里我们新加的参数就果断占用了这个id。修改之后,建议把注释改一下(不要人为的挖坑):
  LayerParameter next available layer-specific ID: 148 (last added: large_margin_inner_product_param)
  避免之后再新加层的时候出问题。
  工作完毕,我们就可以在train_val.prototxt中用这种方式使用这个新层了(具体的使用,后面再说):
  1. layer {
  2.   name: "fc2"
  3.   type: "LargeMarginInnerProduct"
  4.   bottom: "fc1"
  5.   bottom: "label"
  6.   top: "fc2"
  7.   param {
  8.     lr_mult: 1
  9.     decay_mult: 1
  10.   }
  11.   param {
  12.     lr_mult: 0
  13.     decay_mult: 0
  14.   }
  15.   large_margin_inner_product_param {
  16.     num_output: 10000
  17.     margin: 2
  18.     lambda: 0
  19.     weight_filler {
  20.       type: "xavier"
  21.     }   
  22.   }
  23. }
复制代码
二,运筹帷幄之成员变量

  我们刚刚在caffe.proto中,添加了新参数的定义。而事实上,我们还没有这个层的具体实现。这部分,主要介绍我们需要的临时变量。
  首先,我们要理清整个计算的流程。
  先看前馈。
  第一步,需要求出W和x的夹角的余弦值:
      

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

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

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

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

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

  第三步,计算前馈:
      

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

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

  k是根据的取值决定的。
  后馈比前馈要复杂一些,不过使用的变量也是一样的。
  因此我们可以编写自己的头文件了。
  1. #ifndef CAFFE_LARGE_MARGIN_INNER_PRODUCT_LAYER_HPP_
  2. #define CAFFE_LARGE_MARGIN_INNER_PRODUCT_LAYER_HPP_
  3. #include <vector>
  4. #include "caffe/blob.hpp"
  5. #include "caffe/layer.hpp"
  6. #include "caffe/proto/caffe.pb.h"
  7. namespace caffe {
  8. template <typenameDtype>
  9. class LargeMarginInnerProductLayer : public Layer<Dtype> {
  10. public:
  11.   explicitLargeMarginInnerProductLayer(const LayerParameter& param)
  12.       : Layer<Dtype>(param) {}
  13.   virtualvoid LayerSetUp(const vector<Blob<Dtype>*>& bottom,
  14.       const vector<Blob<Dtype>*>& top);
  15.   virtualvoid Reshape(const vector<Blob<Dtype>*>& bottom,
  16.       const vector<Blob<Dtype>*>& top);
  17.   virtualinlineconst char* type() const { return "LargeMarginInnerProduct"; }
  18.   // edited by miao
  19.   // LM_FC层有两个bottom
  20.   virtualinlineint ExactNumBottomBlobs() const { return 2; }
  21.   // end edited
  22.   virtualinlineint ExactNumTopBlobs() const { return 1; }
  23. protected:
  24.   virtualvoid Forward_cpu(const vector<Blob<Dtype>*>& bottom,
  25.       const vector<Blob<Dtype>*>& top);
  26.   virtualvoid Forward_gpu(const vector<Blob<Dtype>*>& bottom,
  27.       const vector<Blob<Dtype>*>& top);
  28.   virtualvoid Backward_cpu(const vector<Blob<Dtype>*>& top,
  29.       const vector<bool>& propagate_down, const vector<Blob<Dtype>*>& bottom);
  30.   virtualvoid Backward_gpu(const vector<Blob<Dtype>*>& top,
  31.       const vector<bool>& propagate_down, const vector<Blob<Dtype>*>& bottom);
  32.   int M_;
  33.   int K_;
  34.   int N_;
  35.   bool bias_term_;
  36.   Blob<Dtype> bias_multiplier_;
  37.   bool transpose_;  ///< if true, assume transposed weights
  38.   // added by miao
  39.   // 一些常数
  40.   Blob<Dtype> cos_theta_bound_;  // 区间边界的cos值
  41.   Blob<int> k_;                  // 当前角度theta所在的区间的位置
  42.   Blob<int> C_M_N_;              // 组合数
  43.   unsigned int margin;            // margin
  44.   float lambda;                  // lambda
  45.   Blob<Dtype> wx_;                // wjT * xi
  46.   Blob<Dtype> abs_w_;            // ||wj||
  47.   Blob<Dtype> abs_x_;            // ||xi||
  48.   Blob<Dtype> cos_t_;            // cos(theta)
  49.   Blob<Dtype> cos_mt_;            // cos(margin * theta)
  50.   Blob<Dtype> dydw_;              // 输出对w的导数
  51.   Blob<Dtype> dydx_;              // 输出对x的导数
  52.   // end added
  53. };
  54. }  // namespace caffe
  55. #endif  // CAFFE_LARGE_MARGIN_INNER_PRODUCT_LAYER_HPP_
复制代码
这里主要是复制了inner_product_layer.hpp,然后做了一点修改。具体是增加了几个成员变量,同时改了ExactNumBottomBlobs的返回值,因为我们的这个层磁带bottom需要两个,前一层的feature和样本的label。
  三、内存和常量的初始化

  这部分,主要给我们的各个成员变量分配内存,同时给几个常量进行初始化。这里也是照着inner_product_layer.cpp来写的,在setup的时候,增加了一些用于初始化的代码,并删除了forward_cpu和backwark_cpu的具体实现。
  修改之后的代码如下:
  1. #include <vector>
  2. #include <cmath>
  3. #include "caffe/filler.hpp"
  4. #include "caffe/layers/large_margin_inner_product_layer.hpp"
  5. #include "caffe/util/math_functions.hpp"
  6. #define PI 3.14159265
  7. namespace caffe {
  8. int factorial(int n) {
  9.   if (0 == n) return 1;
  10.   int f = 1;
  11.   while (n) {
  12.     f *= n;
  13.     -- n;
  14.   }
  15.   return f;
  16. }
  17. template <typename Dtype>
  18. void LargeMarginInnerProductLayer<Dtype>::LayerSetUp(const vector<Blob<Dtype>*>& bottom,
  19.       const vector<Blob<Dtype>*>& top) {
  20.   const int axis = bottom[0]->CanonicalAxisIndex(
  21.       this->layer_param_.large_margin_inner_product_param().axis());
  22.   // added by miao
  23.   std::vector<int> wx_shape(1);
  24.   wx_shape[0] = bottom[0]->shape(0);
  25.   this->wx_.Reshape(wx_shape);
  26.   this->abs_w_.Reshape(wx_shape);
  27.   this->abs_x_.Reshape(wx_shape);
  28.   this->k_.Reshape(wx_shape);
  29.   this->cos_t_.Reshape(wx_shape);
  30.   this->cos_mt_.Reshape(wx_shape);
  31.   std::vector<int> cos_theta_bound_shape(1);
  32.   this->margin = static_cast<unsigned int>(this->layer_param_.large_margin_inner_product_param().margin());
  33.   cos_theta_bound_shape[0] = this->margin + 1;
  34.   this->cos_theta_bound_.Reshape(cos_theta_bound_shape);
  35.   for (int k = 0; k <= this->margin; ++ k) {
  36.     this->cos_theta_bound_.mutable_cpu_data()[k] = std::cos(PI * k / this->margin);
  37.   }
  38.   this->C_M_N_.Reshape(cos_theta_bound_shape);
  39.   for (int n = 0; n <= this->margin; ++ n) {
  40.     this->C_M_N_.mutable_cpu_data()[n] = factorial(this->margin) / factorial(this->margin - n) / factorial(n);
  41.   }
  42.   // d size
  43.   std::vector<int> d_shape(2);
  44.   d_shape[0] = bottom[0]->shape(0);
  45.   d_shape[1] = bottom[0]->count(axis);
  46.   this->dydw_.Reshape(d_shape);
  47.   this->dydx_.Reshape(d_shape);
  48.   this->lambda = this->layer_param_.large_margin_inner_product_param().lambda();
  49.   // end added
  50.   transpose_ = false; // 坚决不转置!
  51.   const int num_output = this->layer_param_.large_margin_inner_product_param().num_output();
  52.   bias_term_ = this->layer_param_.large_marin_inner_product_param().bias_term();
  53.   N_ = num_output;
  54.   
  55.   // Dimensions starting from "axis" are "flattened" into a single
  56.   // length K_ vector. For example, if bottom[0]'s shape is (N, C, H, W),
  57.   // and axis == 1, N inner products with dimension CHW are performed.
  58.   K_ = bottom[0]->count(axis);
  59.   // Check if we need to set up the weights
  60.   if (this->blobs_.size() > 0) {
  61.     LOG(INFO) << "Skipping parameter initialization";
  62.   } else {
  63.     if (bias_term_) {
  64.       this->blobs_.resize(2);
  65.     } else {
  66.       this->blobs_.resize(1);
  67.     }
  68.     // Initialize the weights
  69.     vector<int> weight_shape(2);
  70.     if (transpose_) {
  71.       weight_shape[0] = K_;
  72.       weight_shape[1] = N_;
  73.     } else {
  74.       weight_shape[0] = N_;
  75.       weight_shape[1] = K_;
  76.     }
  77.     this->blobs_[0].reset(new Blob<Dtype>(weight_shape));
  78.     // fill the weights
  79.     shared_ptr<Filler<Dtype> > weight_filler(GetFiller<Dtype>(
  80.         this->layer_param_.large_margin_inner_product_param().weight_filler()));
  81.     weight_filler->Fill(this->blobs_[0].get());
  82.     // If necessary, intiialize and fill the bias term
  83.     if (bias_term_) {
  84.       vector<int> bias_shape(1, N_);
  85.       this->blobs_[1].reset(new Blob<Dtype>(bias_shape));
  86.       shared_ptr<Filler<Dtype> > bias_filler(GetFiller<Dtype>(
  87.           this->layer_param_.inner_product_param().bias_filler()));
  88.       bias_filler->Fill(this->blobs_[1].get());
  89.     }  
  90.   }  // parameter initialization
  91.   this->param_propagate_down_.resize(this->blobs_.size(), true);
  92. }
  93. template <typename Dtype>
  94. void LargeMarginInnerProductLayer<Dtype>::Reshape(const vector<Blob<Dtype>*>& bottom,
  95.       const vector<Blob<Dtype>*>& top) {
  96.   // Figure out the dimensions
  97.   const int axis = bottom[0]->CanonicalAxisIndex(
  98.       this->layer_param_.large_margin_inner_product_param().axis());
  99.   const int new_K = bottom[0]->count(axis);
  100.   CHECK_EQ(K_, new_K)
  101.       << "Input size incompatible with large margin inner product parameters.";
  102.   // The first "axis" dimensions are independent inner products; the total
  103.   // number of these is M_, the product over these dimensions.
  104.   M_ = bottom[0]->count(0, axis);
  105.   // The top shape will be the bottom shape with the flattened axes dropped,
  106.   // and replaced by a single axis with dimension num_output (N_).
  107.   vector<int> top_shape = bottom[0]->shape();
  108.   top_shape.resize(axis + 1);
  109.   top_shape[axis] = N_;
  110.   top[0]->Reshape(top_shape);
  111. }
  112. template <typename Dtype>
  113. void LargeMarginInnerProductLayer<Dtype>::Forward_cpu(const vector<Blob<Dtype>*>& bottom,
  114.     const vector<Blob<Dtype>*>& top) {
  115.   // not implement
  116. }
  117. template <typename Dtype>
  118. void LargeMarginInnerProductLayer<Dtype>::Backward_cpu(const vector<Blob<Dtype>*>& top,
  119.     const vector<bool>& propagate_down,
  120.     const vector<Blob<Dtype>*>& bottom) {
  121.   // not implement
  122. }
  123. #ifdef CPU_ONLY
  124. STUB_GPU(LargeMarginInnerProductLayer);
  125. #endif
  126. INSTANTIATE_CLASS(LargeMarginInnerProductLayer);
  127. REGISTER_LAYER_CLASS(LargeMarginInnerProduct);
  128. }  // namespace caffe
复制代码
至此,large_margin_inner_product_layer的准备工作就做完了。下一篇博客,我们来详细的讨论前馈的具体实现。
  如果您觉得本文对您有帮助,那请小喵喝杯茶吧~~O(∩_∩)O~~ 小喵为了写公式,还专门学习了。
   

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

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

  转载请注明出处~
qa402 发表于 2016-10-2 01:37:34
楼主的头像是本人吗?
回复 支持 反对

使用道具 举报

中国电器门户 发表于 2016-10-2 17:48:15
那些路人甲乙丙丁在年生散场的剧场里将五彩纷呈和苍白无力潇洒的演绎。
回复 支持 反对

使用道具 举报

董怡壤 发表于 2016-10-4 04:24:01
锄禾日当午,发帖真辛苦。谁知坛中餐,帖帖皆辛苦!
回复 支持 反对

使用道具 举报

qcwvw 发表于 2016-10-5 01:43:15
以后要跟与我无关统统好好学习学习!
回复 支持 反对

使用道具 举报

delihuaf 发表于 2016-11-6 09:23:38
朋友妻不可欺睡睡觉还可以
回复 支持 反对

使用道具 举报

kshskb005 发表于 2016-11-13 14:22:41
好厉害啊!楼主怎么做到的
回复 支持 反对

使用道具 举报

我要投稿

推荐阅读


回页顶回复上一篇下一篇回列表
手机版/c.CoLaBug.com ( 粤ICP备05003221号 | 粤公网安备 44010402000842号 )

© 2001-2017 Comsenz Inc.

返回顶部 返回列表