[Caffe]: 關於concat layer


http://caffe.berkeleyvision.org/tutorial/layers/concat.html

http://blog.csdn.net/cham_3/article/details/58586263

今天,我們看一下caffe的拼接層,即將兩個或多個layer進行拼接。 
首先,看一下caffe官方文檔。 
concat


同其他layer一樣,分為setup、reshape、Forward_cpu、Backward_cpu。

//concat_layer 用來實現兩個或者多個blob的鏈接,即多輸入一輸出 //支持在num 維度上的鏈接(concat_dim = 0 : (n1+n2+...+nk)∗c∗h∗w ) //和channel維度上的鏈接(concat_dim = 1 : n∗(c1+c2+...+ck)∗h∗w)。 //axis ,dim :0 為 num 維度鏈接,1 為 channel 維度鏈接 //這里需要給出axis或concat_dim template <typename Dtype> void ConcatLayer<Dtype>::LayerSetUp(const vector<Blob<Dtype>*>& bottom, const vector<Blob<Dtype>*>& top) { const ConcatParameter& concat_param = this->layer_param_.concat_param(); CHECK(!(concat_param.has_axis() && concat_param.has_concat_dim())) << "Either axis or concat_dim should be specified; not both."; } template <typename Dtype> void ConcatLayer<Dtype>::Reshape(const vector<Blob<Dtype>*>& bottom, const vector<Blob<Dtype>*>& top) { //獲取axis,確定拼接哪一維度 const int num_axes = bottom[0]->num_axes(); const ConcatParameter& concat_param = this->layer_param_.concat_param(); //以下都在獲取、判斷axis的維度 if (concat_param.has_concat_dim()) { concat_axis_ = static_cast<int>(concat_param.concat_dim()); // Don't allow negative indexing for concat_dim, a uint32 -- almost // certainly unintended. CHECK_GE(concat_axis_, 0) << "casting concat_dim from uint32 to int32 " << "produced negative result; concat_dim must satisfy " << "0 <= concat_dim < " << kMaxBlobAxes; CHECK_LT(concat_axis_, num_axes) << "concat_dim out of range."; } else { concat_axis_ = bottom[0]->CanonicalAxisIndex(concat_param.axis()); } // Initialize with the first blob. //這里有一點需要解釋,可以看到,bottom類型為 vector<Blob<Dtype>*>,這里只需要使用bottom[0] //給shape賦值就好,其實botom本身就是一個Blob的vector //比如:我要將兩個layer拼接,那么久有bottom[0]以及bottom[1] vector<int> top_shape = bottom[0]->shape(); //concat_axis_ = 0 : num_concats_=num;concat_axis_ = 1 : num_concats_=num x channel; num_concats_ = bottom[0]->count(0, concat_axis_); //concat_axis_ = 0 : concat_input_size_=channel x height x width; //concat_axis_ = 1 : concat_input_size_=height x width; concat_input_size_ = bottom[0]->count(concat_axis_ + 1); int bottom_count_sum = bottom[0]->count(); //檢測num_axes拼接的層是否相同,num_axes為維度信息 for (int i = 1; i < bottom.size(); ++i) { CHECK_EQ(num_axes, bottom[i]->num_axes()) << "All inputs must have the same #axes."; for (int j = 0; j < num_axes; ++j) { if (j == concat_axis_) { continue; } CHECK_EQ(top_shape[j], bottom[i]->shape(j)) << "All inputs must have the same shape, except at concat_axis."; } bottom_count_sum += bottom[i]->count(); top_shape[concat_axis_] += bottom[i]->shape(concat_axis_); } top[0]->Reshape(top_shape); CHECK_EQ(bottom_count_sum, top[0]->count()); }

 

1、這里有一點需要解釋,可以看到,bottom類型為 vector blob,這里只需要使用bottom[0]給shape賦值就好,其實bottom本身就是一個Blob的vector。 
2、CHECK_**,這里給小白們解釋一下,就是判斷是否相等、小於、大於 
這里寫圖片描述 
3、 count,這看到有好多的count函數,這些函數在blob層實現,解釋如下:

inline int count() const { return count_; } /** * @brief Compute the volume of a slice; i.e., the product of dimensions * among a range of axes. * * @param start_axis The first axis to include in the slice. * * @param end_axis The first axis to exclude from the slice. */ inline int count(int start_axis, int end_axis) const { CHECK_LE(start_axis, end_axis); CHECK_GE(start_axis, 0); CHECK_GE(end_axis, 0); CHECK_LE(start_axis, num_axes()); CHECK_LE(end_axis, num_axes()); int count = 1; for (int i = start_axis; i < end_axis; ++i) { count *= shape(i); } return count; } /** * @brief Compute the volume of a slice spanning from a particular first * axis to the final axis. * * @param start_axis The first axis to include in the slice. */ inline int count(int start_axis) const { return count(start_axis, num_axes()); }

前向傳播就是layer的拼接

template <typename Dtype> void ConcatLayer<Dtype>::Forward_cpu(const vector<Blob<Dtype>*>& bottom, const vector<Blob<Dtype>*>& top) { Dtype* top_data = top[0]->mutable_cpu_data(); int offset_concat_axis = 0; const int top_concat_axis = top[0]->shape(concat_axis_); //遍歷所有輸入bottom for (int i = 0; i < bottom.size(); ++i) { const Dtype* bottom_data = bottom[i]->cpu_data(); const int bottom_concat_axis = bottom[i]->shape(concat_axis_); //把 各個bottom data 拷貝到輸出 top data 的對應位置 for (int n = 0; n < num_concats_; ++n) { //case 0:num x channel x h x w;case 1: channel x h x w //case 0:bottom_data + n x num x channel x h x w ; //case 1:bottom_data + n x channel x h x w ; caffe_copy(bottom_concat_axis * concat_input_size_, bottom_data + n * bottom_concat_axis * concat_input_size_, top_data + (n * top_concat_axis + offset_concat_axis) * concat_input_size_); } offset_concat_axis += bottom_concat_axis; } }

反向傳播,就是layer層之間diff和data的傳播

//反向傳播就是對每一個bottom的 diff 做和 data 相同的鏈接 template <typename Dtype> void ConcatLayer<Dtype>::Backward_cpu(const vector<Blob<Dtype>*>& top, const vector<bool>& propagate_down, const vector<Blob<Dtype>*>& bottom) { const Dtype* top_diff = top[0]->cpu_diff(); int offset_concat_axis = 0; const int top_concat_axis = top[0]->shape(concat_axis_); for (int i = 0; i < bottom.size(); ++i) { if (!propagate_down[i]) { continue; } Dtype* bottom_diff = bottom[i]->mutable_cpu_diff(); const int bottom_concat_axis = bottom[i]->shape(concat_axis_); for (int n = 0; n < num_concats_; ++n) { caffe_copy(bottom_concat_axis * concat_input_size_, top_diff + (n * top_concat_axis + offset_concat_axis) * concat_input_size_, bottom_diff + n * bottom_concat_axis * concat_input_size_); } offset_concat_axis += bottom_concat_axis; } }

Concat layer

在Deep Neural Network中,最主要的兩種提高模型性能的優化方向就是使模型wider or deeper。 
在使模型變寬時,常需要把多個分支合並起來作為后續層的輸入。它就是今天要介紹的concat layer。

按照慣例,我們先來看下concat layer的參數。

message ConcatParameter {
  // The axis along which to concatenate -- may be negative to index from the // end (e.g., -1 for the last axis). Other axes must have the // same dimension for all the bottom blobs. // By default, ConcatLayer concatenates blobs along the "channels" axis (1). optional int32 axis = 2 [default = 1]; //caffe中,blobs一般表示成NxCxHxW. 也就是說,axis默認在channel維度來進行concat. // DEPRECATED: alias for "axis" -- does not support negative indexing. 已棄用,axis的別名,不支持負數索引 optional uint32 concat_dim = 1 [default = 1]; }

 

concat作為鏈接多個輸入的工具層,其參數很少,只有一個指定是根據N維度還是根據C維度來進行鏈接的參數。 該層要求至少有兩個輸入,即bottom的size >= 2,如下所示: 

x1:=N×C×H×Wx2:=N×C×H×Wxk:=N×C×H×Woutput:=kN×C×H×Woroutput:=N×kC×H×W


至此,我們大致了解了concat層怎么用呢。接下來,我們介紹介紹它的實現。

 

向前傳播時,實現比較簡單。

template <typename Dtype> void ConcatLayer<Dtype>::Forward_cpu(const vector<Blob<Dtype>*>& bottom, const vector<Blob<Dtype>*>& top) { if (bottom.size() == 1) { return; } \\如果只有一個輸入,不執行操作 Dtype* top_data = top[0]->mutable_cpu_data(); int offset_concat_axis = 0; const int top_concat_axis = top[0]->shape(concat_axis_); for (int i = 0; i < bottom.size(); ++i) { const Dtype* bottom_data = bottom[i]->cpu_data(); \\第i個輸入的讀指針 const int bottom_concat_axis = bottom[i]->shape(concat_axis_); for (int n = 0; n < num_concats_; ++n) { caffe_copy(bottom_concat_axis * concat_input_size_, bottom_data + n * bottom_concat_axis * concat_input_size_, top_data + (n * top_concat_axis + offset_concat_axis) * concat_input_size_); \\把所有輸入根據指定的axis連接起來 } offset_concat_axis += bottom_concat_axis; } }

單看主要函數顯然有些不清不楚,接下來我們看看layersetup和reshape就能明白它具體是怎么做的了。

template <typename Dtype> void ConcatLayer<Dtype>::LayerSetUp(const vector<Blob<Dtype>*>& bottom, const vector<Blob<Dtype>*>& top) { const ConcatParameter& concat_param = this->layer_param_.concat_param(); \\獲取concat參數,即axis或者concat_dim,不能同時指定。 CHECK(!(concat_param.has_axis() && concat_param.has_concat_dim())) << "Either axis or concat_dim should be specified; not both."; } template <typename Dtype> void ConcatLayer<Dtype>::Reshape(const vector<Blob<Dtype>*>& bottom, const vector<Blob<Dtype>*>& top) { const int num_axes = bottom[0]->num_axes(); \\獲取輸入維度數 const ConcatParameter& concat_param = this->layer_param_.concat_param(); if (concat_param.has_concat_dim()) { \\如果指定concat_dim,判斷是否非負 concat_axis_ = static_cast<int>(concat_param.concat_dim()); // Don't allow negative indexing for concat_dim, a uint32 -- almost // certainly unintended. CHECK_GE(concat_axis_, 0) << "casting concat_dim from uint32 to int32 " << "produced negative result; concat_dim must satisfy " << "0 <= concat_dim < " << kMaxBlobAxes; CHECK_LT(concat_axis_, num_axes) << "concat_dim out of range."; \\concat_dim不能超過輸入的維度數 } else { concat_axis_ = bottom[0]->CanonicalAxisIndex(concat_param.axis()); \\指定了axis,轉換成非負索引得到concat_axis } // Initialize with the first blob. vector<int> top_shape = bottom[0]->shape(); \\初始化輸出,shape與輸入一致 num_concats_ = bottom[0]->count(0, concat_axis_); \\需要concat的個數, concat_input_size_ = bottom[0]->count(concat_axis_ + 1); \\每個concat的數據量大小 int bottom_count_sum = bottom[0]->count(); \\輸入總的特征值個數,初始時只有第一個輸入的個數 for (int i = 1; i < bottom.size(); ++i) { \\ CHECK_EQ(num_axes, bottom[i]->num_axes()) \\判斷每個輸入維度是否一致 << "All inputs must have the same #axes."; for (int j = 0; j < num_axes; ++j) { \\除了進行concat的那個維度外,其他維度的大小是否保持一致 if (j == concat_axis_) { continue; } CHECK_EQ(top_shape[j], bottom[i]->shape(j)) << "All inputs must have the same shape, except at concat_axis."; } bottom_count_sum += bottom[i]->count(); \\累加第i個輸入的個數 top_shape[concat_axis_] += bottom[i]->shape(concat_axis_); \\累加輸出的指定axis的值 } top[0]->Reshape(top_shape); \\reshape輸出blob CHECK_EQ(bottom_count_sum, top[0]->count()); \\檢查bottom_count_sum和top_count的數據量是否一致 if (bottom.size() == 1) { top[0]->ShareData(*bottom[0]); \\只有一個輸入,直接復制成輸出 top[0]->ShareDiff(*bottom[0]); \\梯度shape也和輸入一致 } }

 

源碼解析這里基本上就明白concat層的原理了,最后我們來看下它的后向傳播。其原理十分簡單,把輸出求得的梯度直接復制給對應的輸入即可。

template <typename Dtype> void ConcatLayer<Dtype>::Backward_cpu(const vector<Blob<Dtype>*>& top, const vector<bool>& propagate_down, const vector<Blob<Dtype>*>& bottom) { if (bottom.size() == 1) { return; } const Dtype* top_diff = top[0]->cpu_diff(); int offset_concat_axis = 0; const int top_concat_axis = top[0]->shape(concat_axis_); for (int i = 0; i < bottom.size(); ++i) { const int bottom_concat_axis = bottom[i]->shape(concat_axis_); \\從輸出的梯度直接復制到對應的輸入 if (propagate_down[i]) { Dtype* bottom_diff = bottom[i]->mutable_cpu_diff(); for (int n = 0; n < num_concats_; ++n) { caffe_copy(bottom_concat_axis * concat_input_size_, top_diff + (n * top_concat_axis + offset_concat_axis) * concat_input_size_, bottom_diff + n * bottom_concat_axis * concat_input_size_); } } offset_concat_axis += bottom_concat_axis; } }

對與不熟悉blob類的成員函數可以參考這里


免責聲明!

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



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