小喵的博客网址是,小喵认为这么些主意和DeepID2并不争论

小喵的唠叨话:在写完上三回的博客之后,已经仙逝了2个月的年月,小喵在此期间,做了大气的尝试工作,最后在选拔的DeepID2的方法之后,取得了很不错的结果。这一次呢,紧要讲述一个相比较新的舆论中的方法,L-Softmax,据说单model在LFW上能达到98.71%的等错误率。更关键的是,小喵认为这些点子和DeepID2并不争辩,如果两者可以填补,或许单model达到99%+将不是可望。

小喵的唠叨话:在写完上几遍的博客之后,已经过去了2个月的岁月,小喵在此期间,做了汪洋的试验工作,最后在应用的DeepID2的情势之后,取得了很科学的结果。本次呢,紧要描述一个相比新的杂谈中的方法,L-Softmax,据说单model在LFW上能达成98.71%的等错误率。更关键的是,小喵认为那些艺术和DeepID2并不顶牛,假诺双方可以填补,或许单model达到99%+将不是希望。

小喵的唠叨话:前一篇博客,大家做完了L-Softmax的备选工作。而这一章,我们起初举行前馈的钻探。

 

 

 

双重推销一下~

再一次推销一下~

小喵博客:
http://miaoerduo.com

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

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

博客原文:  http://www.miaoerduo.com/deep-learning/基于caffe的large-margin-softmax-loss的实现(中).html

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

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

 

 

 

四、前馈

还记得上一篇博客,小喵给出的六个公式吗?不记得也没涉及。

这一次,我们要一点一点的通过代码来贯彻这一个公式。小喵首如若GPU上落实内外馈的代码,因为这一个层只是用来磨炼,GPU速度相应会快一点。

大家率先要拓展一般的FC层的前馈,因为LM_FC的前馈只是修改了相似的FC中的若干个值,而大部分的值都是素来不改动过的。

 1 const Dtype* bottom_data = bottom[0]->gpu_data();
 2 const Dtype* label_data = bottom[1]->gpu_data();
 3 Dtype* top_data = top[0]->mutable_gpu_data();
 4 const Dtype* weight = this->blobs_[0]->gpu_data();
 5 // 普通fc层的计算
 6 if (M_ == 1) {
 7   caffe_gpu_gemv<Dtype>(CblasNoTrans, N_, K_, (Dtype)1.,
 8                        weight, bottom_data, (Dtype)0., top_data);
 9 } else {
10   caffe_gpu_gemm<Dtype>(CblasNoTrans,
11                         transpose_ ? CblasNoTrans : CblasTrans,
12                         M_, N_, K_, (Dtype)1.,
13                         bottom_data, weight, (Dtype)0., top_data);
14 }

这样就总括完了一个常常的FC的前馈。

今后是局部具体的落实。

和上一篇博客一样,小喵对读者做了之类的假诺:

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

1,$\cos(\theta_j)=\frac{W_j^Tx_i}{\|W_j\|\|x_i\|}$

这是要求出label为$j$的weight的权值和feature之间的余弦值。公式大家在高中应该就学过了。那样需要出三有些:$W_j^Tx_i$,$\|W_j\|$和$\|x_i\|$。这里$i$表示feature的序号,因为一个mini
batch中有好多张图纸。$j$表示正确的label值。

$W_j^Tx_i$的测算相当简单,因为FC层的前馈统计出来的就是这些值。因而大家可以直接从FC的前馈结果中一贯复制对应地点的结果。

$\|W_j\|$和$\|x_i\|$是相比较简单的模值的计量,使用caffe_cpu_dot很容易就足以求得(为啥不行使caffe_gpu_dot呢?因为小喵在行使caffe_gpu_dot的时候,caffe会报一个奇怪的荒唐,不知道是不是因为GPU的显存无法自由走访的)。

说到底的余弦值带入到地点的架子,就一下子搞定~

这里运用了多少个变量:

M_: batch size

N_: class num

K_: feature length

 1 // w * x
 2 // 直接从前馈的结果中复制
 3 Dtype *wx_data = this->wx_.mutable_gpu_data();
 4 copy_label_score<Dtype><<<CAFFE_GET_BLOCKS(M_), CAFFE_CUDA_NUM_THREADS>>>(M_, N_, label_data, top_data, wx_data);
 5 
 6 // w * w
 7 Dtype *abs_w_data = this->abs_w_.mutable_cpu_data();
 8 for (int m = 0; m < M_; ++ m) {
 9   abs_w_data[m] = caffe_cpu_dot<Dtype>(
10     K_,
11     this->blobs_[0]->cpu_data() + static_cast<int>(label_cpu_data[m]) * K_,
12     this->blobs_[0]->cpu_data() + static_cast<int>(label_cpu_data[m]) * K_
13     );
14 }
15 
16 // x * x
17 Dtype *abs_x_data = this->abs_x_.mutable_cpu_data();
18 for (int m = 0; m < M_; ++ m) {
19   abs_x_data[m] = caffe_cpu_dot<Dtype>(
20     K_, 
21     bottom[0]->cpu_data() + m * K_,
22     bottom[0]->cpu_data() + m * K_
23     );
24 }
25 // abs_w, abs_x
26 caffe_gpu_powx<Dtype>(M_, this->abs_w_.mutable_gpu_data(), 0.5, this->abs_w_.mutable_gpu_data());
27 caffe_gpu_powx<Dtype>(M_, this->abs_x_.mutable_gpu_data(), 0.5, this->abs_x_.mutable_gpu_data());
28 
29 // cos_t = wx / (|x| * |w|)
30 Dtype *cos_t_data = this->cos_t_.mutable_gpu_data();
31 caffe_gpu_div<Dtype>(M_, wx_data, this->abs_x_.gpu_data(), cos_t_data);
32 caffe_gpu_div<Dtype>(M_, cos_t_data, this->abs_w_.gpu_data(), cos_t_data);

其中copy_label_score是大家团结编辑的用来复制结果的核函数(怎么着编写Cuda程序就是另一门课程了):

1 template <typename Dtype>
2 __global__ void copy_label_score(const int M, const int N, const Dtype *label_data, const Dtype *top_data, Dtype *wx_data) {
3   CUDA_KERNEL_LOOP(index, M) {
4     wx_data[index] = top_data[index * N + static_cast<int>(label_data[index])];
5   }
6 }

深信机智如您的喵粉,看到这几行代码,一定能够轻松理解。

此处,小喵想多介绍一点东西。

我们领会Caffe里面的数据都是经过Blob结构来存储的,比如这里的bottom_data,其实就是一个blob,默认形状是(n,
c, h, w),n表示的就是batch
size,c是channel数,h,w分贝表示高和宽。而且blob中的内存的储存顺序,也和一般的C语言中的数组一样。因而我们这边总结feature的模的时候,是一向每K_个数值统计一回点乘。

同理,weight是储存在this->blobs[0]中的,那么weight的形态又是怎么着样子的吧?这里丰盛碰巧的是,尽管我们在prototxt中设置的transpose为false的话,weight的形制是N_*K_,也就是说,大家可以将weight看成一个矩阵,它的每一行都与feature直接点乘,得到输出,也就是说weight的每一行都是我们需要总结模值的$W_j$,所以我们总结weight的模的时候,用的盘算办法和计量feature模时很相像。大家这里强制安装transpose为false,因为如此总计会相比较简单。倘诺您设成了true,那就亟须协调写个求模的函数了。

  1. 刺探Deep Learning的基本知识。
  2. 周到阅读过L-Softmax的杂文,了然其中的数学推理。
  3. 使用Caffe作为锻练框架。
  4. 固然不满足上述3条,也能坚定不移不懈的求学。
  1. 刺探Deep Learning的基本知识。
  2. 细心阅读过L-Softmax的小说,了解其中的数学推理。
  3. 使用Caffe作为磨练框架。
  4. 不怕不满足上述3条,也能锲而不舍的上学。

2,$\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)$

我们在(1)中求出了$\cos(\theta)$,对于给定的margin,只需要代入公式就足以求出$\cos(m\theta)$的值了。

 1 template <typename Dtype>
 2 __global__ void cal_cos_mt(const int count, const unsigned int margin, const int *C_M_N, const Dtype *cos_t_data, Dtype *cos_mt_data) {
 3   CUDA_KERNEL_LOOP(index, count) {
 4     Dtype cos_t = cos_t_data[index];
 5     Dtype sin_t_2 = 1 - cos_t * cos_t;
 6     Dtype cos_mt = 0.;
 7     int flag = -1;
 8     for (int n = 0; n <= (margin / 2); ++ n) {
 9       flag *= -1;
10       cos_mt += flag * C_M_N[2 * n] * powf(cos_t, (margin - 2 * n)) * powf(sin_t_2, n);
11     }
12     cos_mt_data[index] = cos_mt;
13   }
14 }

下面是用来总结$\cos(m\theta)$的cuda函数,调用也万分的粗略:

1 // cos(mt)
2 cal_cos_mt<Dtype><<<CAFFE_GET_BLOCKS(M_), CAFFE_CUDA_NUM_THREADS>>>(
3   M_, this->margin, this->C_M_N_.gpu_data(), this->cos_t_.mutable_gpu_data(), this->cos_mt_->mutable_gpu_data());

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

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

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

严俊上来说,我们需要求的并不是以此姿势,而是:

\[f_{y_i}=\frac{\lambda\|W_{y_i}\|\|x_i\|\cos(\theta_{y_i})+\|W_{y_i}\|\|x_i\|\varphi(\theta_{y_i})}{1+\lambda}\]

\[\varphi(\theta)=(-1)^k\cos(m\theta)-2k,
\theta\in[\frac{k\pi}{m}, \frac{(k+1)\pi}{m}]\]

可以看看,当$\lambda$为0的时候,这两个姿态就退化成后边的一个姿势了。

k的求法分外简易,只需要将$\cos(\theta)$与各类区间举行相比就可以收获。

 1 // k
 2 int *k_cpu_data = this->k_.mutable_cpu_data();
 3 const Dtype *cos_t_cpu_data = this->cos_t_.cpu_data();
 4 for (int m = 0; m < M_; ++ m) {
 5   for (int _k = 0; _k < this->cos_theta_bound_.count(); ++ _k) {
 6     if (this->cos_theta_bound_.cpu_data()[_k] < cos_t_cpu_data[m]) {
 7       k_cpu_data[m] = _k - 1;
 8       break;
 9     }
10   }
11 }

末尾一步就是总结出真正的前馈值了!依照公式容易编写程序:

 1 template <typename Dtype>
 2 __global__ void LMForward(
 3   const int M, const int N, const float lambda,
 4   const Dtype *label_data, const Dtype *cos_mt_data, const int *k_data,
 5   const Dtype *abs_w_data, const Dtype *abs_x_data, Dtype *top_data) {
 6 
 7   CUDA_KERNEL_LOOP(index, M) {
 8     Dtype cos_mt = cos_mt_data[index];
 9     int k = k_data[index];
10     int label = static_cast<int>(label_data[index]);
11     Dtype abs_w = abs_w_data[index];
12     Dtype abs_x = abs_x_data[index];
13     top_data[N * index + label] =  (lambda * top_data[N * index + label] + abs_w * abs_x * ( powf(-1, k) * cos_mt - 2 * k )) / (1 + lambda);
14   }
15 }

调用也异常简约:

1 // y
2 LMForward<Dtype><<<CAFFE_GET_BLOCKS(M_), CAFFE_CUDA_NUM_THREADS>>>(
3   M_, N_, this->lambda,
4   label_data, this->cos_mt_->gpu_data(), this->k_.gpu_data(),
5   this->abs_w_.gpu_data(), this->abs_x_.gpu_data(), top[0]->mutable_gpu_data());

终极附上,完整的前馈代码(省略头文件和caffe的名字空间):

  1 template <typename Dtype>
  2 __global__ void copy_label_score(const int M, const int N, const Dtype *label_data, const Dtype *top_data, Dtype *wx_data) {
  3   CUDA_KERNEL_LOOP(index, M) {
  4     wx_data[index] = top_data[index * N + static_cast<int>(label_data[index])];
  5   }
  6 }
  7 
  8 template <typename Dtype>
  9 __global__ void cal_cos_mt(const int count, const unsigned int margin, const int *C_M_N, const Dtype *cos_t_data, Dtype *cos_mt_data) {
 10   CUDA_KERNEL_LOOP(index, count) {
 11     Dtype cos_t = cos_t_data[index];
 12     Dtype sin_t_2 = 1 - cos_t * cos_t;
 13     Dtype cos_mt = 0.;
 14     int flag = -1;
 15     for (int n = 0; n <= (margin / 2); ++ n) {
 16       flag *= -1;
 17       cos_mt += flag * C_M_N[2 * n] * powf(cos_t, (margin - 2 * n)) * powf(sin_t_2, n);
 18     }
 19     cos_mt_data[index] = cos_mt;
 20   }
 21 }
 22 
 23 template <typename Dtype>
 24 __global__ void LMForward(
 25   const int M, const int N, const float lambda,
 26   const Dtype *label_data, const Dtype *cos_mt_data, const int *k_data,
 27   const Dtype *abs_w_data, const Dtype *abs_x_data, Dtype *top_data) {
 28 
 29   CUDA_KERNEL_LOOP(index, M) {
 30     Dtype cos_mt = cos_mt_data[index];
 31     int k = k_data[index];
 32     int label = static_cast<int>(label_data[index]);
 33     Dtype abs_w = abs_w_data[index];
 34     Dtype abs_x = abs_x_data[index];
 35     top_data[N * index + label] =  (lambda * top_data[N * index + label] + abs_w * abs_x * ( powf(-1, k) * cos_mt - 2 * k )) / (1 + lambda);
 36   }
 37 }
 38 
 39 template <typename Dtype>
 40 void LargeMarginInnerProductLayer<Dtype>::Forward_gpu(const vector<Blob<Dtype>*>& bottom,
 41     const vector<Blob<Dtype>*>& top) {
 42   const Dtype* bottom_data = bottom[0]->gpu_data();
 43   const Dtype* label_data = bottom[1]->gpu_data();
 44   Dtype* top_data = top[0]->mutable_gpu_data();
 45   const Dtype* weight = this->blobs_[0]->gpu_data();
 46 
 47   // 普通fc层的计算
 48   if (M_ == 1) {
 49     caffe_gpu_gemv<Dtype>(CblasNoTrans, N_, K_, (Dtype)1.,
 50                          weight, bottom_data, (Dtype)0., top_data);
 51   } else {
 52     caffe_gpu_gemm<Dtype>(CblasNoTrans,
 53                           transpose_ ? CblasNoTrans : CblasTrans,
 54                           M_, N_, K_, (Dtype)1.,
 55                           bottom_data, weight, (Dtype)0., top_data);
 56   }
 57 
 58   const Dtype* label_cpu_data = bottom[1]->cpu_data();
 59 
 60   // w * x
 61   // 直接从前馈的结果中复制
 62   Dtype *wx_data = this->wx_.mutable_gpu_data();
 63   copy_label_score<Dtype><<<CAFFE_GET_BLOCKS(M_), CAFFE_CUDA_NUM_THREADS>>>(M_, N_, label_data, top_data, wx_data);
 64 
 65   // w * w
 66   Dtype *abs_w_data = this->abs_w_.mutable_cpu_data();
 67   for (int m = 0; m < M_; ++ m) {
 68     abs_w_data[m] = caffe_cpu_dot<Dtype>(
 69       K_,
 70       this->blobs_[0]->cpu_data() + static_cast<int>(label_cpu_data[m]) * K_,
 71       this->blobs_[0]->cpu_data() + static_cast<int>(label_cpu_data[m]) * K_
 72       );
 73   }
 74   
 75   // x * x
 76   Dtype *abs_x_data = this->abs_x_.mutable_cpu_data();
 77   for (int m = 0; m < M_; ++ m) {
 78     abs_x_data[m] = caffe_cpu_dot<Dtype>(
 79       K_, 
 80       bottom[0]->cpu_data() + m * K_,
 81       bottom[0]->cpu_data() + m * K_
 82       );
 83   }
 84 
 85   // abs_w, abs_x
 86   caffe_gpu_powx<Dtype>(M_, this->abs_w_.mutable_gpu_data(), 0.5, this->abs_w_.mutable_gpu_data());
 87   caffe_gpu_powx<Dtype>(M_, this->abs_x_.mutable_gpu_data(), 0.5, this->abs_x_.mutable_gpu_data());
 88 
 89   // cos_t = wx / (|x| * |w|)
 90   Dtype *cos_t_data = this->cos_t_.mutable_gpu_data();
 91   caffe_gpu_div<Dtype>(M_, wx_data, this->abs_x_.gpu_data(), cos_t_data);
 92   caffe_gpu_div<Dtype>(M_, cos_t_data, this->abs_w_.gpu_data(), cos_t_data);
 93 
 94   // cos(mt)
 95   cal_cos_mt<Dtype><<<CAFFE_GET_BLOCKS(M_), CAFFE_CUDA_NUM_THREADS>>>(
 96     M_, this->margin, 
 97     this->C_M_N_.gpu_data(), 
 98     this->cos_t_.gpu_data(),
 99     this->cos_mt_.mutable_gpu_data()
100     );
101 
102   // k
103   int *k_cpu_data = this->k_.mutable_cpu_data();
104   const Dtype *cos_t_cpu_data = this->cos_t_.cpu_data();
105   for (int m = 0; m < M_; ++ m) {
106     for (int _k = 0; _k < this->cos_theta_bound_.count(); ++ _k) {
107       if (this->cos_theta_bound_.cpu_data()[_k] < cos_t_cpu_data[m]) {
108         k_cpu_data[m] = _k - 1;
109         break;
110       }
111     }
112   }
113 
114   // y
115   LMForward<Dtype><<<CAFFE_GET_BLOCKS(M_), CAFFE_CUDA_NUM_THREADS>>>(
116     M_, N_, this->lambda,
117     label_data, this->cos_mt_.gpu_data(), this->k_.gpu_data(),
118     this->abs_w_.gpu_data(), this->abs_x_.gpu_data(), top[0]->mutable_gpu_data());
119 }

 

那么,那样关于large margin softmax
loss的前馈大家就自在的兑现了。下一篇,我们要讲最复杂的后馈的贯彻了。

 

只要你认为本文对你有匡助,那请小喵喝杯茶啊O(∩_∩)O 再度感叹$\LaTeX$ 大法好。

图片 1

转载请表明出处~

 

Google一下,第一条应该就是舆论的地点,鉴于我们时刻少于,小喵把原文地址也贴出来了,但不保险短期有效。http://jmlr.org/proceedings/papers/v48/liud16.pdf
这里大家也将全体系列分几部分来讲。

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的最伊始可以看出那般一行注释:
图片 2

证实下一个可行的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 }

 

一、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的最起头可以看来这般一行注释:
图片 3

证实下一个实惠的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(\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)$的取值决定的。

后馈比前馈要复杂一些,然则使用的变量也是千篇一律的。
由此我们可以编写自己的头文件了。

 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。

二,运筹帷幄之成员变量

我们恰好在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)$的取值决定的。

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

 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$。

图片 4

 

转载请表明出处~

三、内存和常量的起头化

这一部分,重要给大家的相继成员变量分配内存,同时给多少个常量举行起初化。这里也是照着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$。

图片 5

 

转载请注解出处~

相关文章