博客原来的书文,小喵博客

小喵的唠叨话:此次的博客,真心累伤了小喵的心。但思虑到文化须要加固和享受,小喵决定此番把结余的剧情都写完。

  小喵的唠叨话:大家在上一篇博客里面,介绍了Caffe的Data层的编写。有了Data层,下一步则是哪些去接纳生成好的教练多少。也正是这一篇的内容。

小喵的唠叨话:前一篇博客,大家做完了L-Softmax的预备专门的学问。而这一章,我们初始展开前馈的切磋。

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

 

 

博客原作: http://www.miaoerduo.com/deep-learning/基于caffe的deepid2实现(下).html ‎

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

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

四、数据的横盘,简单的细分

前方的Data层用于生成成对的输入数据,诺玛lization层,用于将feature归一化,那么之后是还是不是就足以使用ContrastiveLoss层实行操练了吗?

且慢,还差一步。

ContrastiveLoss层须求有3个bottom:feature1、feature2以及代表对位的feature是不是为同一个identity的label。

我们未来获得的feature却是全数的都在一同,data层直接拿走的label也和这里须要的label不一致。由此必供给对数据开始展览一回重新整建。

叁个轻巧易行的条条框框正是服从奇偶,将feature划分成两有个别。那样得到的两有些正好正是一律地方为一对。对于label的整理,也足以用类似的格局。小喵这里只对feature举办重新整建,而label的拍卖则是透过改ContrastiveLoss层来完结。

feature的股价整理本质上就是三个切开的操作,这里命名字为id2_slice_layer,完毕形式正是依据奇偶把bottom的数量复制到top。后馈的时候,也正是将两片段的feature的diff都直接复制到对应地方的bottom_diff中,具体贯彻如下:

 1 // created by miao
 2 #ifndef CAFFE_ID2_SLICE_LAYER_HPP_
 3 #define CAFFE_ID2_SLICE_LAYER_HPP_
 4 
 5 #include <vector>
 6 
 7 #include "caffe/blob.hpp"
 8 #include "caffe/layer.hpp"
 9 #include "caffe/proto/caffe.pb.h"
10 
11 namespace caffe {
12 
13 /**
14  * @brief Takes a Blob and slices it along either the num or channel dimension,
15  *        outputting multiple sliced Blob results.
16  *
17  * TODO(dox): thorough documentation for Forward, Backward, and proto params.
18  */
19 template <typename Dtype>
20 class Id2SliceLayer : public Layer<Dtype> {
21  public:
22   explicit Id2SliceLayer(const LayerParameter& param)
23       : Layer<Dtype>(param) {}
24   virtual void LayerSetUp(const vector<Blob<Dtype>*>& bottom,
25       const vector<Blob<Dtype>*>& top);
26   virtual void Reshape(const vector<Blob<Dtype>*>& bottom,
27       const vector<Blob<Dtype>*>& top);
28 
29   virtual inline const char* type() const { return "Id2Slice"; }
30   virtual inline int ExactNumBottomBlobs() const { return 1; }
31   virtual inline int MinTopBlobs() const { return 1; }
32 
33  protected:
34   virtual void Forward_cpu(const vector<Blob<Dtype>*>& bottom,
35       const vector<Blob<Dtype>*>& top);
36   virtual void Forward_gpu(const vector<Blob<Dtype>*>& bottom,
37       const vector<Blob<Dtype>*>& top);
38   virtual void Backward_cpu(const vector<Blob<Dtype>*>& top,
39       const vector<bool>& propagate_down, const vector<Blob<Dtype>*>& bottom);
40   virtual void Backward_gpu(const vector<Blob<Dtype>*>& top,
41       const vector<bool>& propagate_down, const vector<Blob<Dtype>*>& bottom);
42 };
43 
44 }  // namespace caffe
45 
46 #endif  // CAFFE_ID2_SLICE_LAYER_HPP_

头文件,巨简单。。。

Cpp的代码,也特别轻松,要注意id2_slice层的top有八个,每种的形象都以bottom的八分之四。

 1 // created by miao
 2 #include <algorithm>
 3 #include <vector>
 4 
 5 #include "caffe/layers/id2_slice_layer.hpp"
 6 #include "caffe/util/math_functions.hpp"
 7 
 8 namespace caffe {
 9 
10 template <typename Dtype>
11 void Id2SliceLayer<Dtype>::LayerSetUp(const vector<Blob<Dtype>*>& bottom,
12       const vector<Blob<Dtype>*>& top) {
13 }
14 
15 template <typename Dtype>
16 void Id2SliceLayer<Dtype>::Reshape(const vector<Blob<Dtype>*>& bottom,
17       const vector<Blob<Dtype>*>& top) {
18     vector<int> top_shape = bottom[0]->shape();
19     top_shape[0] /= 2;
20     top[0]->Reshape(top_shape);
21     top[1]->Reshape(top_shape);
22 }
23 
24 template <typename Dtype>
25 void Id2SliceLayer<Dtype>::Forward_cpu(const vector<Blob<Dtype>*>& bottom,
26       const vector<Blob<Dtype>*>& top) {
27     const int feature_size = bottom[0]->count(1);
28     for (int n = 0; n < bottom[0]->num(); ++ n) {
29         caffe_copy(
30                 feature_size, 
31                 bottom[0]->cpu_data() + n * feature_size, 
32                 top[n & 1]->mutable_cpu_data() + (n / 2) * feature_size
33                 );
34     }
35 }
36 
37 template <typename Dtype>
38 void Id2SliceLayer<Dtype>::Backward_cpu(const vector<Blob<Dtype>*>& top,
39       const vector<bool>& propagate_down, const vector<Blob<Dtype>*>& bottom) {
40     const int feature_size = bottom[0]->count(1);
41     for (int n = 0; n < bottom[0]->num(); ++ n) {
42         caffe_copy(
43                 feature_size,
44                 top[n & 1]->cpu_diff() + (n / 2) * feature_size,
45                 bottom[0]->mutable_cpu_diff() + n * feature_size
46                 );
47     }
48 }
49 
50 #ifdef CPU_ONLY
51 STUB_GPU(Id2SliceLayer);
52 #endif
53 
54 INSTANTIATE_CLASS(Id2SliceLayer);
55 REGISTER_LAYER_CLASS(Id2Slice);
56 
57 }  // namespace caffe

GPU上的达成,为了轻巧起见,也是一向调用了CPU的前馈函数。

 1 // created by miao
 2 #include <vector>
 3 
 4 #include "caffe/layers/id2_slice_layer.hpp"
 5 #include "caffe/util/math_functions.hpp"
 6 
 7 namespace caffe {
 8 template <typename Dtype>
 9 void Id2SliceLayer<Dtype>::Forward_gpu(const vector<Blob<Dtype>*>& bottom,
10       const vector<Blob<Dtype>*>& top) {
11     this->Forward_cpu(bottom, top);
12 }
13 
14 template <typename Dtype>
15 void Id2SliceLayer<Dtype>::Backward_gpu(const vector<Blob<Dtype>*>& top,
16       const vector<bool>& propagate_down, const vector<Blob<Dtype>*>& bottom) {
17     this->Backward_cpu(top, propagate_down, bottom);
18 }
19 
20 INSTANTIATE_LAYER_GPU_FUNCS(Id2SliceLayer);
21 
22 }  // namespace caffe

如此就实现了feature的整治。由于未有应用新的参数,由此也不必要修改caffe.proto。

亲能够效仿这一个艺术对label来做类似的操作。鉴于小喵比较懒。。。这里就只是简单的改ContrastiveLoss层的代码了。

首先步,在ContrastiveLossLayer中新扩大贰个用于记录feature
pair是还是不是是同二个identity的分子变量,替代原本的第二个bottom的机能。这样只要求在前馈的时候提前算好,就能够代替从前的第八个bottom来利用,而不要求再修改别的地点的代码。

为了大家使用的有益,小喵直接把修改之后的头文件粘贴出来(删掉注释)。新增添的行,用“added
by miao”那么些注释注脚出来。头文件只加了一行。

 1 #ifndef CAFFE_CONTRASTIVE_LOSS_LAYER_HPP_
 2 #define CAFFE_CONTRASTIVE_LOSS_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 #include "caffe/layers/loss_layer.hpp"
11 
12 namespace caffe {
13 template <typename Dtype>
14 class ContrastiveLossLayer : public LossLayer<Dtype> {
15  public:
16   explicit ContrastiveLossLayer(const LayerParameter& param)
17       : LossLayer<Dtype>(param), diff_() {}
18   virtual void LayerSetUp(const vector<Blob<Dtype>*>& bottom,
19       const vector<Blob<Dtype>*>& top);
20 
21   virtual inline int ExactNumBottomBlobs() const { return 3; }
22   virtual inline const char* type() const { return "ContrastiveLoss"; }
23   virtual inline bool AllowForceBackward(const int bottom_index) const {
24     return bottom_index != 2;
25   }
26  protected:
27   /// @copydoc ContrastiveLossLayer
28   virtual void Forward_cpu(const vector<Blob<Dtype>*>& bottom,
29       const vector<Blob<Dtype>*>& top);
30   virtual void Forward_gpu(const vector<Blob<Dtype>*>& bottom,
31       const vector<Blob<Dtype>*>& top);
32   virtual void Backward_cpu(const vector<Blob<Dtype>*>& top,
33       const vector<bool>& propagate_down, const vector<Blob<Dtype>*>& bottom);
34   virtual void Backward_gpu(const vector<Blob<Dtype>*>& top,
35       const vector<bool>& propagate_down, const vector<Blob<Dtype>*>& bottom);
36 
37   Blob<Dtype> diff_;  // cached for backward pass
38   Blob<Dtype> dist_sq_;  // cached for backward pass
39   Blob<Dtype> diff_sq_;  // tmp storage for gpu forward pass
40   Blob<Dtype> summer_vec_;  // tmp storage for gpu forward pass
41   Blob<Dtype> is_same_; // added by miao
42 };
43 }  // namespace caffe
44 
45 #endif  // CAFFE_CONTRASTIVE_LOSS_LAYER_HPP_

源文件的退换也要命轻巧,这里只贴出来Cuda的一对。源文件,修改了与原本的bottom3辅车相依的地点。

  1 #include <algorithm>
  2 #include <vector>
  3 #include <iostream>
  4 #include "caffe/layers/contrastive_loss_layer.hpp"
  5 #include "caffe/util/math_functions.hpp"
  6 
  7 namespace caffe {
  8 
  9 template <typename Dtype>
 10 void ContrastiveLossLayer<Dtype>::Forward_gpu(
 11     const vector<Blob<Dtype>*>& bottom, const vector<Blob<Dtype>*>& top) {
 12   const int count = bottom[0]->count();
 13   caffe_gpu_sub(
 14       count,
 15       bottom[0]->gpu_data(),  // a
 16       bottom[1]->gpu_data(),  // b
 17       diff_.mutable_gpu_data());  // a_i-b_i
 18   caffe_gpu_powx(
 19       count,
 20       diff_.mutable_gpu_data(),  // a_i-b_i
 21       Dtype(2),
 22       diff_sq_.mutable_gpu_data());  // (a_i-b_i)^2
 23   caffe_gpu_gemv(
 24       CblasNoTrans,
 25       bottom[0]->num(),
 26       bottom[0]->channels(),
 27       Dtype(1.0),
 28       diff_sq_.gpu_data(),  // (a_i-b_i)^2
 29       summer_vec_.gpu_data(),
 30       Dtype(0.0),
 31       dist_sq_.mutable_gpu_data());  // \Sum (a_i-b_i)^2
 32   Dtype margin = this->layer_param_.contrastive_loss_param().margin();
 33   bool legacy_version =
 34       this->layer_param_.contrastive_loss_param().legacy_version();
 35   Dtype loss(0.0);
 36   for (int i = 0; i < bottom[0]->num(); ++i) {
 37     // added by miao
 38     is_same_.mutable_cpu_data()[i] = (bottom[2]->cpu_data()[2 * i] == bottom[2]->cpu_data()[2 * i + 1])? 1:0;
 39     if (is_same_.cpu_data()[i] == 1) {  // similar pairs
 40       loss += dist_sq_.cpu_data()[i];
 41     } else {  // dissimilar pairs
 42       if (legacy_version) {
 43         loss += std::max(margin - dist_sq_.cpu_data()[i], Dtype(0.0));
 44       } else {
 45         Dtype dist = std::max(margin - sqrt(dist_sq_.cpu_data()[i]),
 46                               Dtype(0.0));
 47         loss += dist*dist;
 48       }
 49     }
 50   }
 51   loss = loss / static_cast<Dtype>(bottom[0]->num()) / Dtype(2);
 52   top[0]->mutable_cpu_data()[0] = loss;
 53 }
 54 
 55 template <typename Dtype>
 56 __global__ void CLLBackward(const int count, const int channels,
 57     const Dtype margin, const bool legacy_version, const Dtype alpha,
 58     const Dtype* y, const Dtype* diff, const Dtype* dist_sq,
 59     Dtype *bottom_diff) {
 60   CUDA_KERNEL_LOOP(i, count) {
 61     int n = i / channels;  // the num index, to access y and dist_sq
 62     if (static_cast<int>(y[n])) {  // similar pairs
 63       bottom_diff[i] = alpha * diff[i];
 64     } else {  // dissimilar pairs
 65       Dtype mdist(0.0);
 66       Dtype beta(0.0);
 67       if (legacy_version) {
 68         mdist = (margin - dist_sq[n]);
 69         beta = -alpha;
 70       } else {
 71         Dtype dist = sqrt(dist_sq[n]);
 72         mdist = (margin - dist);
 73         beta = -alpha * mdist / (dist + Dtype(1e-4)) * diff[i];
 74       }
 75       if (mdist > 0.0) {
 76         bottom_diff[i] = beta;
 77       } else {
 78         bottom_diff[i] = 0;
 79       }
 80     }
 81   }
 82 }
 83 
 84 template <typename Dtype>
 85 void ContrastiveLossLayer<Dtype>::Backward_gpu(const vector<Blob<Dtype>*>& top,
 86     const vector<bool>& propagate_down, const vector<Blob<Dtype>*>& bottom) {
 87   for (int i = 0; i < 2; ++i) {
 88     if (propagate_down[i]) {
 89       const int count = bottom[0]->count();
 90       const int channels = bottom[0]->channels();
 91       Dtype margin = this->layer_param_.contrastive_loss_param().margin();
 92       const bool legacy_version =
 93           this->layer_param_.contrastive_loss_param().legacy_version();
 94       const Dtype sign = (i == 0) ? 1 : -1;
 95       const Dtype alpha = sign * top[0]->cpu_diff()[0] /
 96           static_cast<Dtype>(bottom[0]->num());
 97       // NOLINT_NEXT_LINE(whitespace/operators)
 98       CLLBackward<Dtype><<<CAFFE_GET_BLOCKS(count), CAFFE_CUDA_NUM_THREADS>>>(
 99           count, channels, margin, legacy_version, alpha,
100           is_same_.gpu_data(),  // pair similarity 0 or 1  added by miao
101           diff_.gpu_data(),  // the cached eltwise difference between a and b
102           dist_sq_.gpu_data(),  // the cached square distance between a and b
103           bottom[i]->mutable_gpu_diff());
104       CUDA_POST_KERNEL_CHECK;
105     }
106   }
107 }
108 
109 INSTANTIATE_LAYER_GPU_FUNCS(ContrastiveLossLayer);
110 
111 }  // namespace caffe

内需专注的时候,前馈和后馈都亟待做一些代码上的修改,虽说十三分的简要,但也要小心。

 

于今,基于Caffe的DeepID2的退换总体达成。

 

假使你以为本文对您有救助,那请小喵喝杯茶啊O(∩_∩)O

图片 1

 

转发请证明出处~

博客原来的小说:http://www.miaoerduo.com/deep-learning/基于caffe的deepid2实现(中).html

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

二、精髓,DeepID2 Loss层

DeepID2那篇随想关于verification
signal的片段,给出了二个用于监督verification的loss。

图片 2

其中,fi和fj是归一化之后的性子。

当fi和fj属于同一个identity的时候,也正是yij=1时,loss是双方的L2距离,约束使得特征越发周围。

当fi和fj不属于同二个identity的时候,即yij=-1,那时的loss表示什么吧?参数m又代表什么?

 

m在那边是margin的情趣,是三个能够自行安装的参数,表示希望的不等identity的feature之间的距离。当多少个feature的超越margin时,表达网络已经得以很好的界别那八个特征,由此这是loss为0,当feature间的相距小于margin时,loss则为(m-|fi

fj|)^2,表示还亟需八个特点能够更加好的不同。由此这么些loss函数相比较好的反应了我们的急需,也便是DeepID2的算法观念。

本条Loss层达成起来就如并不劳动,前馈相当大约。至于后馈,求导也特别轻松。不过Caffe加入新层,须要在caffe.proto文件中,做一些修改,那也是最苦恼小喵的地点。

而是有个好消息正是:Caffe官方网站扩张了ContrastiveLossLayer那些层!

官方网站的文书陈说如下:

Computes the contrastive loss
图片 3 where
图片 4.
This can be used to train siamese networks.

和大家的需如果一样的。因而大家不需求团结完毕那一个层。

喜大普奔之余,小喵也特意看了Caffe的文书档案,以及这里涉及了siamese
network,发掘这一个互连网利用ContrastiveLossLayer的法子相比较新鲜,Caffe项目中的examples中有例子,感兴趣能够看看。

ContrastiveLossLayer的输入,也正是bottom有三部分,feature1、feature2、label,feature1和feature2是分别对应的两组feature,而label则表示该对feature是不是是属于同一个identity,是的话,则为1,不是则为0。何况该层还提供三个参数margin,也正是舆论的公式里面包车型地铁m。

末尾的下结论正是,尽管我们无需协调写Loss层,可是照旧必须扩张一些卓殊的层。

最首要有2个,用于将特色归一化的NormalizationLayer以及用于将feature层转变到ContrastiveLossLayer的输入的层,无妨命名字为ID2SliceLayer。

四、前馈

还记得上一篇博客,小喵给出的多个公式吗?不记得也没提到。

此番,大家要一点一点的经过代码来促成那一个公式。小喵首就算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的前馈。

事后是有个别现实的落到实处。

三、小标题,大智慧之Normalization Layer

其一归一化的层用于将输入的feature
map进行归一化。Caffe官方网站并从未提供有关的层,因而大家亟须团结完毕(大概从网络找),这里大家依旧选取自身来完结,顺便学习一下Caffe加层的本事。

Normalization层的前馈非常的轻松,输入为叁个向量x,输出为归一化之后的向量:

图片 5

有关后馈,需须求导,总计稍微有一点复杂,小喵在演绎4遍之后才给出如下表明式:

图片 6

在那之中x为输入的特征向量,为列向量。这里是将全体feature map看做四个列向量。

略知一二了前馈后馈的持筹握算准则,那么很轻巧编写本身的层了,这里小喵建议我们找个Caffe已经有了的开始和结果相近的层,照着改写。比方那些Normalization层,没有别的层的参数,所以照着ReLU类似的层就很好编写。

而后就祭出我们的code:

 1 // create by miao
 2 // 主要实现了feature的归一化
 3 #ifndef CAFFE_NORMALIZATION_LAYER_HPP_
 4 #define CAFFE_NORMALIZATION_LAYER_HPP_
 5 
 6 #include <vector>
 7 
 8 #include "caffe/blob.hpp"
 9 #include "caffe/layer.hpp"
10 #include "caffe/proto/caffe.pb.h"
11 
12 #include "caffe/layers/neuron_layer.hpp"
13 
14 namespace caffe {
15 
16 template <typename Dtype>
17 class NormalizationLayer : public NeuronLayer<Dtype> {
18  public:
19   explicit NormalizationLayer(const LayerParameter& param)
20       : NeuronLayer<Dtype>(param) {}
21   virtual void LayerSetUp(const vector<Blob<Dtype>*>& bottom,
22       const vector<Blob<Dtype>*>& top);
23   virtual inline const char* type() const { return "Normalization"; }
24   virtual inline int ExactNumBottomBlobs() const { return 1; }
25   virtual inline int ExactNumTopBlobs() const { return 1; }
26   
27  protected:
28   virtual void Forward_cpu(const vector<Blob<Dtype>*>& bottom,
29       const vector<Blob<Dtype>*>& top);
30   virtual void Forward_gpu(const vector<Blob<Dtype>*>& bottom,
31       const vector<Blob<Dtype>*>& top);
32   virtual void Backward_cpu(const vector<Blob<Dtype>*>& top,
33       const vector<bool>& propagate_down, const vector<Blob<Dtype>*>& bottom);
34   virtual void Backward_gpu(const vector<Blob<Dtype>*>& top,
35       const vector<bool>& propagate_down, const vector<Blob<Dtype>*>& bottom);
36   Blob<Dtype> norm_val_; // 记录每个feature的模
37 };
38 
39 }  // namespace caffe
40 
41 #endif  // CAFFE_NORMALIZATION_LAYER_HPP_

本条层的头文件非常的简要,和ReLU的仅部分分化正是类的名字不一致等,并且多了个成员变量norm_val_,用来记录种种feature的模值。

 1 // create by miao
 2 #include <vector>
 3 #include <cmath>
 4 #include "caffe/layers/normalization_layer.hpp"
 5 #include "caffe/util/math_functions.hpp"
 6 
 7 namespace caffe {
 8 
 9 template <typename Dtype>
10 void NormalizationLayer<Dtype>::LayerSetUp(const vector<Blob<Dtype>*>& bottom,
11         const vector<Blob<Dtype>*>& top) {
12     NeuronLayer<Dtype>::LayerSetUp(bottom, top);
13     CHECK_NE(top[0], bottom[0]) << this->type() << " Layer does not "
14         "allow in-place computation.";
15     norm_val_.Reshape(bottom[0]->shape(0), 1, 1, 1); // 申请norm的内存
16 }
17 
18 
19 template <typename Dtype> 
20 void NormalizationLayer<Dtype>::Forward_cpu(
21     const vector<Blob<Dtype>*>& bottom, const vector<Blob<Dtype>*>& top) {
22 
23     Dtype *norm_val_cpu_data = norm_val_.mutable_cpu_data();
24     for (int n = 0; n < bottom[0]->shape(0); ++ n) {
25         // 计算每个c * h * w的区域的模
26         norm_val_cpu_data[n] = std::sqrt(static_cast<float>(
27                     caffe_cpu_dot<Dtype>(
28                         bottom[0]->count(1), 
29                         bottom[0]->cpu_data() + bottom[0]->offset(n), 
30                         bottom[0]->cpu_data() + bottom[0]->offset(n)
31                         )
32                     ));
33         // 将每个bottom归一化,输出到top
34         caffe_cpu_scale<Dtype>(
35                 top[0]->count(1), 
36                 1. / norm_val_cpu_data[n], 
37                 bottom[0]->cpu_data() + bottom[0]->offset(n), 
38                 top[0]->mutable_cpu_data() + top[0]->offset(n)
39                 );
40     }
41 }
42 
43 template <typename Dtype>
44 void NormalizationLayer<Dtype>::Backward_cpu(
45     const vector<Blob<Dtype>*>& top, 
46     const vector<bool>& propagate_down,
47     const vector<Blob<Dtype>*>& bottom) {
48     
49     const Dtype *norm_val_cpu_data = norm_val_.cpu_data();
50     const Dtype *top_diff = top[0]->cpu_diff();
51     Dtype *bottom_diff = bottom[0]->mutable_cpu_diff();
52     const Dtype *bottom_data = bottom[0]->cpu_data();
53 
54     caffe_copy(top[0]->count(), top_diff, bottom_diff);
55     
56     for (int n = 0; n < top[0]->shape(0); ++ n) {
57         Dtype a = - 1./(norm_val_cpu_data[n] * norm_val_cpu_data[n] * norm_val_cpu_data[n]) * caffe_cpu_dot<Dtype>(
58                 top[0]->count(1),
59                 top_diff + top[0]->offset(n),
60                 bottom_data + bottom[0]->offset(n)
61                 );
62         Dtype b = 1. / norm_val_cpu_data[n];
63         caffe_cpu_axpby<Dtype>(
64                 top[0]->count(1),
65                 a,
66                 bottom_data + bottom[0]->offset(n),
67                 b,
68                 bottom_diff + top[0]->offset(n)
69                 );
70     }
71 }
72 #ifdef CPU_ONLY
73 STUB_GPU(NormalizationLayer);
74 #endif
75 
76 INSTANTIATE_CLASS(NormalizationLayer);
77 REGISTER_LAYER_CLASS(Normalization);
78 
79 } // namespace caffe

 最终正是GPU部分的代码,假如不在乎质量的话,直接在CUDA的内外馈里面调用CPU版的上下馈就行。当然假使驾驭CUDA的话,完全能够写一份GPU版的代码。小喵这里就偷懒了一晃。。。

 1 // create by miao
 2 #include <vector>
 3 #include <cmath>
 4 #include "caffe/layers/normalization_layer.hpp"
 5 #include "caffe/util/math_functions.hpp"
 6 
 7 namespace caffe {
 8 
 9 template <typename Dtype> 
10 void NormalizationLayer<Dtype>::Forward_gpu(
11     const vector<Blob<Dtype>*>& bottom, const vector<Blob<Dtype>*>& top) {
12     this->Forward_cpu(bottom, top);
13 }   
14 
15 template <typename Dtype>
16 void NormalizationLayer<Dtype>::Backward_gpu(
17     const vector<Blob<Dtype>*>& top, 
18     const vector<bool>& propagate_down,
19     const vector<Blob<Dtype>*>& bottom) {
20     this->Backward_cpu(top, propagate_down, bottom);
21 }
22 INSTANTIATE_LAYER_GPU_FUNCS(NormalizationLayer);
23 } // namespace caffe

 

如此那般,大家就写完了Normalization层的享有代码。

对于比较老版本的Caffe,还索要修改/caffe_root/src/caffe/caffe.proto文件。而新版的Caffe只要在疯长参数的意况下才供给修改。大家的这些诺玛lization层并不曾行使新的参数,因而并不须要修改caffe.proto文件。

有关新版的Caffe为啥这么智能,原因实在就在这两行代码:

INSTANTIATE_CLASS(NormalizationLayer);
REGISTER_LAYER_CLASS(Normalization);

宏INSTANTIATE_CLASS在/caffe_root/include/caffe/common.hpp中定义。

宏REGISTER_LAYER_CLASS在/caffe_root/include/caffe/layer_factory.hpp中定义。

感兴趣能够自行查阅。

 

例如你认为本文对您有赞助,那请小喵喝杯茶啊O(∩_∩)O

图片 7

 

 

转发请评释出处~

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$的一个钱打二17个结非常轻松,因为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的模的时候,用的一个钱打二17个结办法和计量feature模时很相像。大家这里强制安装transpose为false,因为如此总计会相比简单。要是您设成了true,那就亟须团结写个求模的函数了。

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());

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$ 大法好。

图片 8

转发请评释出处~

 

相关文章