基於Caffe的DeepID2實現(中)


  小喵的嘮叨話:我們在上一篇博客里面,介紹了Caffe的Data層的編寫。有了Data層,下一步則是如何去使用生成好的訓練數據。也就是這一篇的內容。

 

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

博客原文:http://www.miaoerduo.com/deep-learning/基於caffe的deepid2實現(中).html

二、精髓,DeepID2 Loss層

DeepID2這篇論文關於verification signal的部分,給出了一個用於監督verification的loss。

verification_loss

其中,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 contrastive_loss where l2. 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。

三、小問題,大智慧之Normalization Layer

這個歸一化的層用於將輸入的feature map進行歸一化。Caffe官網並沒有提供相關的層,因此我們必須自己實現(或者從網上找),這里我們還是選擇自己來實現,順便學習一下Caffe加層的技巧。

Normalization層的前饋非常的簡單,輸入為一個向量x,輸出為歸一化之后的向量:

至於后饋,需要求導,計算稍微有點復雜,小喵在推導4遍之后才給出如下表達式:

其中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只要在新增參數的情況下才需要修改。我們的這個Normalization層並沒有用到新的參數,因此並不需要修改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~~

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

 

 

轉載請注明出處~


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM