caffe源碼解析之Layer層(1)

前言

老實說,caffe中的layer層代碼比較多,各種抽象看起來比較繞。官方關於Layer的教程寫的很清楚,我根據這個文檔,簡單畫了個圖,再理解起來就方便了一些。

caffe_layer.png

layer.hpp

和layer相關的頭文件有:

common_layers.hpp
data_layers.hpp
layer.hpp
loss_layers.hpp
neuron_layers.hpp
vision_layers.hpp

其中``layer.hpp是抽象出來的基類,其他都是在其基礎上的繼承,也即剩下的五個頭文件和上圖中的五個部分。在layer.hpp`頭文件裏,包含了這幾個頭文件:

#include "caffe/blob.hpp"
#include "caffe/common.hpp"
#include "caffe/proto/caffe.pb.h"
#include "caffe/util/device_alternate.hpp"

device_alternate.hpp中,通過#ifdef CPU_ONLY定義了一些宏來取消GPU的調用:

#define STUB_GPU(classname)
#define STUB_GPU_FORWARD(classname, funcname)
#define STUB_GPU_BACKWARD(classname, funcname)

layer中有這三個主要參數:

LayerParameter layer_param_;      // 這個是protobuf文件中存儲的layer參數
vector<share_ptr<Blob<Dtype>>> blobs_;        // 這個存儲的是layer的參數,在程序中用的
vector<bool> param_propagate_down_;        // 這個bool表示是否計算各個blob參數的diff,即傳播誤差

Layer類的構建函數explicit Layer(const LayerParameter& param) : layer_param_(param)會嘗試從protobuf文件讀取參數。其三個主要接口:

virtual void SetUp(const vector<Blob<Dtype>*>& bottom, vector<Blob<Dtype>*>* top)
inline Dtype Forward(const vector<Blob<Dtype>*>& bottom, vector<Blob<Dtype>*>* top);
inline void Backward(const vector<Blob<Dtype>*>& top, const vector<bool>& propagate_down, const <Blob<Dtype>*>* bottom);

SetUp函數需要根據實際的參數設置進行實現,對各種類型的參數初始化;ForwardBackward對應前向計算和反向更新,輸入統一都是bottom,輸出爲top,其中Backward裏面有個propagate_down參數,用來表示該Layer是否反向傳播參數。

ForwardBackward的具體實現裏,會根據Caffe::mode()進行對應的操作,即使用cpu或者gpu進行計算,兩個都實現了對應的接口Forward_cpuForward_gpuBackward_cpuBackward_gpu,這些接口都是virtual,具體還是要根據layer的類型進行對應的計算(注意:有些layer並沒有GPU計算的實現,所以封裝時加入了CPU的計算作爲後備)。另外,還實現了ToProto的接口,將Layer的參數寫入到protocol buffer文件中。

data_layers.hpp

data_layers.hpp這個頭文件包含了這幾個頭文件:

#include "boost/scoped_ptr.hpp"
#include "hdf5.h"
#include "leveldb/db.h"
#include "lmdb.h"

#include "caffe/blob.hpp"
#include "caffe/common.hpp"
#include "caffe/filler.hpp"
#include "caffe/internal_thread.hpp"
#include "caffe/layer.hpp"
#include "caffe/proto/caffe.pb.h"

看到hdf5、leveldb、lmdb,確實是與具體數據相關了。data_layer作爲原始數據的輸入層,處於整個網絡的最底層,它可以從數據庫leveldb、lmdb中讀取數據,也可以直接從內存中讀取,還可以從hdf5,甚至是原始的圖像讀入數據。

關於這幾個數據庫,簡介如下:

LevelDB是Google公司搞的一個高性能的key/value存儲庫,調用簡單,數據是被Snappy壓縮,據說效率很多,可以減少磁盤I/O,具體例子可以看看維基百科

LMDB(Lightning Memory-Mapped Database),是個和levelDB類似的key/value存儲庫,但效果似乎更好些,其首頁上寫道“ultra-fast,ultra-compact”,這個有待進一步學習啊~~

HDF(Hierarchical Data Format)是一種爲存儲和處理大容量科學數據而設計的文件格式及相應的庫文件,當前最流行的版本是HDF5,其文件包含兩種基本數據對象:

  • 羣組(group):類似文件夾,可以包含多個數據集或下級羣組;
  • 數據集(dataset):數據內容,可以是多維數組,也可以是更復雜的數據類型。

以上內容來自維基百科,關於使用可以參考[HDF5 小試——高大上的多對象文件格式](HDF5 小試——高大上的多對象文件格式),後續會再詳細的研究下怎麼用。

caffe/filler.hpp的作用是在網絡初始化時,根據layer的定義進行初始參數的填充,下面的代碼很直觀,根據FillerParameter指定的類型進行對應的參數填充。

// A function to get a specific filler from the specification given in
// FillerParameter. Ideally this would be replaced by a factory pattern,
// but we will leave it this way for now.
template <typename Dtype>
Filler<Dtype>* GetFiller(const FillerParameter& param) {
  const std::string& type = param.type();
  if (type == "constant") {
    return new ConstantFiller<Dtype>(param);
  } else if (type == "gaussian") {
    return new GaussianFiller<Dtype>(param);
  } else if (type == "positive_unitball") {
    return new PositiveUnitballFiller<Dtype>(param);
  } else if (type == "uniform") {
    return new UniformFiller<Dtype>(param);
  } else if (type == "xavier") {
    return new XavierFiller<Dtype>(param);
  } else {
    CHECK(false) << "Unknown filler name: " << param.type();
  }
  return (Filler<Dtype>*)(NULL);
}

internal_thread.hpp裏面封裝了pthread函數,繼承的子類可以得到一個單獨的線程,主要作用是在計算當前的一批數據時,在後臺獲取新一批的數據。

關於data_layer,基本要注意的我都在圖片上標註了。

neuron_layers.hpp

輸入了data後,就要計算了,比如常見的sigmoidtanh等等,這些都計算操作被抽象成了neuron_layers.hpp裏面的類NeuronLayer,這個層只負責具體的計算,因此明確定義了輸入ExactNumBottomBlobs()ExactNumTopBlobs()都是常量1,即輸入一個blob,輸出一個blob。

common_layers.hpp

NeruonLayer僅僅負責簡單的一對一計算,而剩下的那些複雜的計算則通通放在了common_layers.hpp中。像ArgMaxLayerConcatLayerFlattenLayerSoftmaxLayerSplitLayerSliceLayer等各種對blob增減修改的操作。

loss_layers.hpp

前面的data layercommon layer都是中間計算層,雖然會涉及到反向傳播,但傳播的源頭來自於loss_layer,即網絡的最終端。這一層因爲要計算誤差,所以輸入都是2個blob,輸出1個blob。

vision_layers.hpp

vision_layer主要是圖像卷積的操作,像convolusion、pooling、LRN都在裏面,按官方文檔的說法,是可以輸出圖像的,這個要看具體實現代碼了。裏面有個im2col的實現,看caffe作者的解釋,主要是爲了加速卷積的,這個具體是怎麼實現的要好好研究下~~

後語

結合官方文檔,再加畫圖和看代碼,終於對整個layer層有了個基本認識:data負責輸入,vision負責卷積相關的計算,neuroncommon負責中間部分的數據計算,而loss是最後一部分,負責計算反向傳播的誤差。具體的實現都在src/caffe/layers裏面,慢慢再研究研究。

在這些抽象的基類頭文件裏,看起來挺累,好在各種搜索,也能學到一些技巧,如,巧用宏定義來簡寫C,C++代碼,使用模板方法,將有大量重複接口和參數的類抽象爲一個宏定義,達到簡化代碼的目的。


轉載自http://www.shwley.com/index.php/archives/68/

發佈了17 篇原創文章 · 獲贊 57 · 訪問量 7萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章