前言
老實說,caffe中的layer層代碼比較多,各種抽象看起來比較繞。官方關於Layer的教程寫的很清楚,我根據這個文檔,簡單畫了個圖,再理解起來就方便了一些。
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
函數需要根據實際的參數設置進行實現,對各種類型的參數初始化;Forward
和Backward
對應前向計算和反向更新,輸入統一都是bottom
,輸出爲top
,其中Backward
裏面有個propagate_down
參數,用來表示該Layer是否反向傳播參數。
在Forward
和Backward
的具體實現裏,會根據Caffe::mode()
進行對應的操作,即使用cpu或者gpu進行計算,兩個都實現了對應的接口Forward_cpu
、Forward_gpu
和Backward_cpu
、Backward_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後,就要計算了,比如常見的sigmoid
、tanh
等等,這些都計算操作被抽象成了neuron_layers.hpp
裏面的類NeuronLayer
,這個層只負責具體的計算,因此明確定義了輸入ExactNumBottomBlobs()
和ExactNumTopBlobs()
都是常量1,即輸入一個blob,輸出一個blob。
common_layers.hpp
NeruonLayer
僅僅負責簡單的一對一計算,而剩下的那些複雜的計算則通通放在了common_layers.hpp
中。像ArgMaxLayer
、ConcatLayer
、FlattenLayer
、SoftmaxLayer
、SplitLayer
和SliceLayer
等各種對blob增減修改的操作。
loss_layers.hpp
前面的data layer
和common
layer
都是中間計算層,雖然會涉及到反向傳播,但傳播的源頭來自於loss_layer
,即網絡的最終端。這一層因爲要計算誤差,所以輸入都是2個blob,輸出1個blob。
vision_layers.hpp
vision_layer
主要是圖像卷積的操作,像convolusion、pooling、LRN都在裏面,按官方文檔的說法,是可以輸出圖像的,這個要看具體實現代碼了。裏面有個im2col
的實現,看caffe作者的解釋,主要是爲了加速卷積的,這個具體是怎麼實現的要好好研究下~~
後語
結合官方文檔,再加畫圖和看代碼,終於對整個layer
層有了個基本認識:data
負責輸入,vision
負責卷積相關的計算,neuron
和common
負責中間部分的數據計算,而loss
是最後一部分,負責計算反向傳播的誤差。具體的實現都在src/caffe/layers
裏面,慢慢再研究研究。
在這些抽象的基類頭文件裏,看起來挺累,好在各種搜索,也能學到一些技巧,如,巧用宏定義來簡寫C,C++代碼,使用模板方法,將有大量重複接口和參數的類抽象爲一個宏定義,達到簡化代碼的目的。
轉載自http://www.shwley.com/index.php/archives/68/