Caffe 原始碼閱讀筆記之 Layer 類
Caffe 原始碼閱讀筆記之 Layer 類
Caffe 的基礎抽象類 Layer
設計模式
Layer 類主要採用了模板的設計模式,基類裡實現了一些基本功能的操作流程,子類裡實現了具體的操作。每一個子類都通過工廠方法去建立,子類的具體方法通過多型的方式呼叫得到。當需要實現一個新的模型層時,只需要關心模型層的初始化和前後向計算即可,然不需要管一些瑣碎的事情(基類已經有基礎的功能)。
舉一個例子
我們當前有一個基類指標Layer* layer
;
在程式執行之前,計算機並不知道這個指標究竟要指向何種派生類。是卷積層?Pooling 層?ReLU 層?
一個最簡單的做法:
1
|
if(type==CONV)
|
工廠模式借鑑了工廠管理產品的經驗,將各種型別存在資料庫中,需要時,拿出來看看。這種模式相當得靈活,當然,在 Caffe 中作用不是很大,僅僅是為了花式好看。要實現這個模式,你只需要一個關聯容器(C++/JAVA),字典容器(Python)。 將 string 與建立指標繫結即可。
C/C++中有函式指標的說法,如:
1
|
typedef boost::shared_ptr< Layer<Dtype> > (*NEW_FUNC)(const LayerParameter& );
|
經過typdef之後,NEW_FUNC就可以指向函式:
1
|
boost::shared_ptr< Layer<Dtype> > xxx(const LayerParameter& x);
|
需要訪問工廠時,我們只需要訪問這個代替工廠管理資料庫的容器,而不是幼稚地使用if(…..)
Layer 的基本操作
Layer 的建立–LayerRegistry
檔案位置在[caffe_home]/include/caffe/layer_factory.hpp
1
|
|
如果想根據一個名字建立一個 Layer,可以呼叫下面的程式碼完成:
1
|
// Get a layer using a LayerParameter.
|
1
|
template <typename Dtype>
|
構造引數的靈活性。這個類的定義儲存在 [caffe_home]/src/caffe/proto/caffe.proto 中,它將在編譯中由 protobuf 自動生成。 由於不同的模型層相差較大,需要的引數也千差萬別,因此這個檔案記錄了一個非常複雜的 LayerParameter 結構。由於 Protobuf 提供了一系列的基礎功能–類的構造、成員變數設定、序列化等功能,這部分的操作同樣保持了足夠的靈活性,同時不需要完成太多的程式碼編寫。
1
|
class LayerParameter : public ::google::protobuf::Message{
|
Layer 的初始化
1
|
void SetUp(const vector<Blob<Dtype>*>& bottom,
|
LayerSetUp
和Reshape
在 Layer 類中都被定義為抽象方法,也就是說,它們的具體內容要靠子類實現LayerSetUp
對模型層進行初始化。Reshape
根據 bottom (也就是層的輸入資料)的維度,確定 top(也就是層的輸出資料)的維度。
SetLossWeights
在 Layer 中有具體實現,它的作用是幫助模型為訓練過程中的前向後向計算做準備工作。
Layer 的前向計算
常用層
包括:softmax_loss層,Inner Product層,accuracy層,reshape層和dropout層及其它們的引數配置。
新增自定義的 layer 步驟
https://github.com/BVLC/caffe/issues/684
- 屬於哪個型別的 layer,就開啟哪個hpp檔案,這裡就開啟
vision_layers.hpp
,然後自己新增該 layer 的定義,或者直接複製 Convolution_Layer 的相關程式碼來修改類名和建構函式名都改為 Aaa_Layer,如果不用 GPU,將*_gpu
的宣告都去掉。 - 實現自己的layer,編寫Aaa_Layer.cpp,加入到
src/caffe/layers
,主要實現 Setup、Forward_cpu、Backward_cpu。 - 如果需要 GPU 實現,那麼在 Aaa_Layer.cu 中實現 Forward_gpu 和 Backward_gpu。
- 修改
src/caffe/proto/caffe.proto
,好到 LayerType,新增 Aaa,並更新ID,如果 Layer 有引數,新增 AaaParameter 類。 - 在
src/caffe/layer_factory.cpp
中新增響應程式碼。 - 在
src/caffe/gtest中寫一個test_Aaa_layer.cpp
,對所寫的layer前傳和反傳進行測試,測試還包括速度。 用include/caffe/test/test_gradient_check_util.hpp
來檢查前向後向傳播是否正確。
一個例子(triplet loss 層的實現)
- caffe.proto 中增加 triplet loss layer 的定義
- 在./include/caffe/loss_layers.hpp中增加triplet loss layer的類的
宣告(TripletLossLayer 類公有繼承於 LossLayer 類,定義了一些變數,用來在前傳中儲存中間計算結果,以便在反傳的時候避免重複計算) - 在./src/caffe/layers/目錄下新建triplet_loss_layer.cpp,實現類
- LayerSetUp:主要是做一些CHECK工作,然後根據bottom和top對類中的資料成員初始化。
- Forward_cpu:前傳,計算loss。
- Backward_cpu:反傳,計算梯度。
- 在./src/caffe/layers/目錄下新建triplet_loss_layer.cu,實現GPU下的前傳和反傳
- 在./src/caffe/test/目錄下增加test_triplet_loss_layer.cpp
- 編譯測試
- 重新 make all 如果出錯,檢查程式碼語法錯誤。
- make test。
- make runtest 如果成功,全是綠色的OK 否則會給出紅色提示,就得看看是不是實現邏輯上出錯了。
參考資料
- C++ 單件模式:Singleton 學習筆記
- C++ 物件工廠模式:ObjectFactory 學習筆記
- 用 C++ 實現簡單工廠模式
- Caffe 原始碼閱讀 Layer 載入機制
- caffe原始碼:工廠模式
- Layer in Caffe
- http://www.runoob.com/design-pattern/factory-pattern.html
- caffe程式碼閱讀4:LayerRegistry的介紹與實現