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


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

    再次推销一下~

    小喵的博客网址是: http://www.miaoerduo.com

    博客原文:  http://www.miaoerduo.com/deep-learning/基于caffe的large-ma…ftmax-loss的实现(上).html 

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

    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的语法写的,简单的修改只要照着其他的参数来改写就好。 首先定义我们的这个层的参数。

     1 message LargeMarginInnerProductParameter {
     2   optional uint32 num_output = 1; // The number of outputs for the layer
     3   optional bool bias_term = 2 [default = true]; // whether to have bias terms
     4   optional FillerParameter weight_filler = 3; // The filler for the weight
     5   optional FillerParameter bias_filler = 4; // The filler for the bias
     6 
     7   // The first axis to be lumped into a single inner product computation;
     8   // all preceding axes are retained in the output.
     9   // May be negative to index from the end (e.g., -1 for the last axis).
    10   optional int32 axis = 5 [default = 1];
    11   // Specify whether to transpose the weight matrix or not.
    12   // If transpose == true, any operations will be performed on the transpose
    13   // of the weight matrix. The weight matrix itself is not going to be transposed
    14   // but rather the transfer flag of operations will be toggled accordingly.
    15   optional bool transpose = 6 [default = false];
    16   optional uint32 margin = 7 [default = 1];
    17   optional float lambda = 8 [default = 0];
    18 }

    参数的定义和InnerProductParameter非常相似,只是多了两个参数margin和lambda。 之后在LayerParameter添加一个可选参数(照着InnerProductParameter写就好)。

    optional LargeMarginInnerProductParameter large_margin_inner_product_param = 147;

    这时,喵粉可能很在意这个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中用这种方式使用这个新层了(具体的使用,后面再说):

     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的夹角的余弦值:

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

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

    [cos(m heta_i)=sum_n(-1)^n{C_m^{2n}cos^{m-2n}( heta_i)cdot(1-cos( heta_i)^2)^n}, (2nleq m)]

    第三步,计算前馈:

    [f_{y_{i}}=(-1)^kcdot|W_{y_{i}}||x_{i}|cos(m heta_i)-2kcdot|W_{y_i}||x_i|]

    k是根据$cos( heta)$的取值决定的。

    后馈比前馈要复杂一些,不过使用的变量也是一样的。 因此我们可以编写自己的头文件了。

     1 #ifndef CAFFE_LARGE_MARGIN_INNER_PRODUCT_LAYER_HPP_
     2 #define CAFFE_LARGE_MARGIN_INNER_PRODUCT_LAYER_HPP_
     3 
     4 #include <vector>
     5 
     6 #include "caffe/blob.hpp"
     7 #include "caffe/layer.hpp"
     8 #include "caffe/proto/caffe.pb.h"
     9 
    10 namespace caffe {
    11 
    12 template <typename Dtype>
    13 class LargeMarginInnerProductLayer : public Layer<Dtype> {
    14  public:
    15   explicit LargeMarginInnerProductLayer(const LayerParameter& param)
    16       : Layer<Dtype>(param) {}
    17   virtual void LayerSetUp(const vector<Blob<Dtype>*>& bottom,
    18       const vector<Blob<Dtype>*>& top);
    19   virtual void Reshape(const vector<Blob<Dtype>*>& bottom,
    20       const vector<Blob<Dtype>*>& top);
    21 
    22   virtual inline const char* type() const { return "LargeMarginInnerProduct"; }
    23   // edited by miao
    24   // LM_FC层有两个bottom
    25   virtual inline int ExactNumBottomBlobs() const { return 2; }
    26   // end edited
    27   virtual inline int ExactNumTopBlobs() const { return 1; }
    28 
    29  protected:
    30   virtual void Forward_cpu(const vector<Blob<Dtype>*>& bottom,
    31       const vector<Blob<Dtype>*>& top);
    32   virtual void Forward_gpu(const vector<Blob<Dtype>*>& bottom,
    33       const vector<Blob<Dtype>*>& top);
    34   virtual void Backward_cpu(const vector<Blob<Dtype>*>& top,
    35       const vector<bool>& propagate_down, const vector<Blob<Dtype>*>& bottom);
    36   virtual void Backward_gpu(const vector<Blob<Dtype>*>& top,
    37       const vector<bool>& propagate_down, const vector<Blob<Dtype>*>& bottom);
    38 
    39   int M_;
    40   int K_;
    41   int N_;
    42   bool bias_term_;
    43   Blob<Dtype> bias_multiplier_;
    44   bool transpose_;  ///< if true, assume transposed weights
    45 
    46   // added by miao
    47 
    48   // 一些常数
    49   Blob<Dtype> cos_theta_bound_;   // 区间边界的cos值
    50   Blob<int> k_;                   // 当前角度theta所在的区间的位置
    51   Blob<int> C_M_N_;               // 组合数
    52   unsigned int margin;            // margin
    53   float lambda;                   // lambda
    54 
    55   Blob<Dtype> wx_;                // wjT * xi
    56   Blob<Dtype> abs_w_;             // ||wj|| 
    57   Blob<Dtype> abs_x_;             // ||xi||
    58   Blob<Dtype> cos_t_;             // cos(theta)
    59   Blob<Dtype> cos_mt_;            // cos(margin * theta)
    60 
    61   Blob<Dtype> dydw_;              // 输出对w的导数
    62   Blob<Dtype> dydx_;              // 输出对x的导数
    63   // end added
    64 };
    65 
    66 }  // namespace caffe
    67 
    68 #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 
      4 #include "caffe/filler.hpp"
      5 #include "caffe/layers/large_margin_inner_product_layer.hpp"
      6 #include "caffe/util/math_functions.hpp"
      7 
      8 #define PI 3.14159265
      9 
     10 namespace caffe {
     11 
     12 int factorial(int n) {
     13   if (0 == n) return 1;
     14   int f = 1;
     15   while (n) {
     16     f *= n;
     17     -- n;
     18   }
     19   return f;
     20 }
     21 
     22 template <typename Dtype>
     23 void LargeMarginInnerProductLayer<Dtype>::LayerSetUp(const vector<Blob<Dtype>*>& bottom,
     24       const vector<Blob<Dtype>*>& top) {
     25 
     26   const int axis = bottom[0]->CanonicalAxisIndex(
     27       this->layer_param_.large_margin_inner_product_param().axis());
     28   // added by miao
     29   std::vector<int> wx_shape(1);
     30   wx_shape[0] = bottom[0]->shape(0);
     31   this->wx_.Reshape(wx_shape);
     32   this->abs_w_.Reshape(wx_shape);
     33   this->abs_x_.Reshape(wx_shape);
     34   this->k_.Reshape(wx_shape);
     35   this->cos_t_.Reshape(wx_shape);
     36   this->cos_mt_.Reshape(wx_shape);
     37 
     38   std::vector<int> cos_theta_bound_shape(1);
     39   this->margin = static_cast<unsigned int>(this->layer_param_.large_margin_inner_product_param().margin());
     40   cos_theta_bound_shape[0] = this->margin + 1;
     41   this->cos_theta_bound_.Reshape(cos_theta_bound_shape);
     42   for (int k = 0; k <= this->margin; ++ k) {
     43     this->cos_theta_bound_.mutable_cpu_data()[k] = std::cos(PI * k / this->margin);
     44   }
     45   this->C_M_N_.Reshape(cos_theta_bound_shape);
     46   for (int n = 0; n <= this->margin; ++ n) {
     47     this->C_M_N_.mutable_cpu_data()[n] = factorial(this->margin) / factorial(this->margin - n) / factorial(n);
     48   }
     49 
     50   // d size
     51   std::vector<int> d_shape(2);
     52   d_shape[0] = bottom[0]->shape(0);
     53   d_shape[1] = bottom[0]->count(axis);
     54   this->dydw_.Reshape(d_shape);
     55   this->dydx_.Reshape(d_shape);
     56 
     57   this->lambda = this->layer_param_.large_margin_inner_product_param().lambda();
     58   // end added
     59 
     60   transpose_ = false; // 坚决不转置!
     61 
     62   const int num_output = this->layer_param_.large_margin_inner_product_param().num_output();
     63   bias_term_ = this->layer_param_.large_marin_inner_product_param().bias_term();
     64   N_ = num_output;
     65   
     66   // Dimensions starting from "axis" are "flattened" into a single
     67   // length K_ vector. For example, if bottom[0]'s shape is (N, C, H, W),
     68   // and axis == 1, N inner products with dimension CHW are performed.
     69   K_ = bottom[0]->count(axis);
     70   // Check if we need to set up the weights
     71   if (this->blobs_.size() > 0) {
     72     LOG(INFO) << "Skipping parameter initialization";
     73   } else {
     74     if (bias_term_) {
     75       this->blobs_.resize(2);
     76     } else {
     77       this->blobs_.resize(1);
     78     }
     79     // Initialize the weights
     80     vector<int> weight_shape(2);
     81     if (transpose_) {
     82       weight_shape[0] = K_;
     83       weight_shape[1] = N_;
     84     } else {
     85       weight_shape[0] = N_;
     86       weight_shape[1] = K_;
     87     }
     88     this->blobs_[0].reset(new Blob<Dtype>(weight_shape));
     89     // fill the weights
     90     shared_ptr<Filler<Dtype> > weight_filler(GetFiller<Dtype>(
     91         this->layer_param_.large_margin_inner_product_param().weight_filler()));
     92     weight_filler->Fill(this->blobs_[0].get());
     93     // If necessary, intiialize and fill the bias term
     94     if (bias_term_) {
     95       vector<int> bias_shape(1, N_);
     96       this->blobs_[1].reset(new Blob<Dtype>(bias_shape));
     97       shared_ptr<Filler<Dtype> > bias_filler(GetFiller<Dtype>(
     98           this->layer_param_.inner_product_param().bias_filler()));
     99       bias_filler->Fill(this->blobs_[1].get());
    100     }   
    101 
    102   }  // parameter initialization
    103   this->param_propagate_down_.resize(this->blobs_.size(), true);
    104 }
    105 
    106 template <typename Dtype>
    107 void LargeMarginInnerProductLayer<Dtype>::Reshape(const vector<Blob<Dtype>*>& bottom,
    108       const vector<Blob<Dtype>*>& top) {
    109   // Figure out the dimensions
    110   const int axis = bottom[0]->CanonicalAxisIndex(
    111       this->layer_param_.large_margin_inner_product_param().axis());
    112   const int new_K = bottom[0]->count(axis);
    113   CHECK_EQ(K_, new_K)
    114       << "Input size incompatible with large margin inner product parameters.";
    115   // The first "axis" dimensions are independent inner products; the total
    116   // number of these is M_, the product over these dimensions.
    117   M_ = bottom[0]->count(0, axis);
    118   // The top shape will be the bottom shape with the flattened axes dropped,
    119   // and replaced by a single axis with dimension num_output (N_).
    120   vector<int> top_shape = bottom[0]->shape();
    121   top_shape.resize(axis + 1);
    122   top_shape[axis] = N_;
    123   top[0]->Reshape(top_shape);
    124 }
    125 
    126 template <typename Dtype>
    127 void LargeMarginInnerProductLayer<Dtype>::Forward_cpu(const vector<Blob<Dtype>*>& bottom,
    128     const vector<Blob<Dtype>*>& top) {
    129   // not implement
    130 }
    131 
    132 template <typename Dtype>
    133 void LargeMarginInnerProductLayer<Dtype>::Backward_cpu(const vector<Blob<Dtype>*>& top,
    134     const vector<bool>& propagate_down,
    135     const vector<Blob<Dtype>*>& bottom) {
    136   // not implement
    137 }
    138 
    139 #ifdef CPU_ONLY
    140 STUB_GPU(LargeMarginInnerProductLayer);
    141 #endif
    142 
    143 INSTANTIATE_CLASS(LargeMarginInnerProductLayer);
    144 REGISTER_LAYER_CLASS(LargeMarginInnerProduct);
    145 
    146 }  // namespace caffe

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

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

    转载请注明出处~

  • 相关阅读:
    TextView autoLink不识别大写url问题
    用ImageLoader取拍摄的照片到imageView里面 图片的方向不对问题
    android使用qrcode_swetake.jar生成二维码
    jenkins + Git 搭建持续集成环境
    win10+jenkins+git+自动发布(搭建+构建)
    springMvc项目配置步骤
    linux系统下安装Jenkins
    解决java compiler level does not match the version of the installed java project facet
    Java通过FTP服务器上传下载文件的方法
    Nexus 安装(Linux 环境)
  • 原文地址:https://www.cnblogs.com/idiotgroup/p/5925974.html
Copyright © 2020-2023  润新知