小喵的嘮叨話:在寫完上一次的博客之后,已經過去了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
和上一篇博客一樣,小喵對讀者做了如下的假定:
- 了解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 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的最開始可以看到這么一行注釋:
說明下一個有效的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。
三、內存和常量的初始化
這部分,主要給我們的各個成員變量分配內存,同時給幾個常量進行初始化。這里也是照着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$。
轉載請注明出處~