1. 程式人生 > 實用技巧 >Caffe 原始碼閱讀筆記之 Layer 類

Caffe 原始碼閱讀筆記之 Layer 類

Caffe 原始碼閱讀筆記之 Layer 類

Caffe 的基礎抽象類 Layer

設計模式

Layer 類主要採用了模板的設計模式,基類裡實現了一些基本功能的操作流程,子類裡實現了具體的操作。每一個子類都通過工廠方法去建立,子類的具體方法通過多型的方式呼叫得到。當需要實現一個新的模型層時,只需要關心模型層的初始化和前後向計算即可,然不需要管一些瑣碎的事情(基類已經有基礎的功能)。

舉一個例子
我們當前有一個基類指標Layer* layer;

在程式執行之前,計算機並不知道這個指標究竟要指向何種派生類。是卷積層?Pooling 層?ReLU 層?
一個最簡單的做法:

1
2
3
4
if(type==CONV) {....}
else if(type==POOLING) {....}
else if(type==RELU} {.....}
else {ERROR}

工廠模式借鑑了工廠管理產品的經驗,將各種型別存在資料庫中,需要時,拿出來看看。這種模式相當得靈活,當然,在 Caffe 中作用不是很大,僅僅是為了花式好看。要實現這個模式,你只需要一個關聯容器(C++/JAVA),字典容器(Python)。 將 string 與建立指標繫結即可。

C/C++中有函式指標的說法,如:

1
typedef boost::shared_ptr< Layer<Dtype> > (*NEW_FUNC)(const LayerParameter& );

經過typdef之後,NEW_FUNC就可以指向函式:

1
2
3
4
5
boost::shared_ptr< Layer<Dtype> > xxx(const LayerParameter& x);
NEW_FUNC yyy=boost::shared_ptr< Layer<Dtype> > xxx(const LayerParameter& x);

yyy(); //相當於xxx()
xxx();

需要訪問工廠時,我們只需要訪問這個代替工廠管理資料庫的容器,而不是幼稚地使用if(…..)

模板類 Layer 包含的欄位和方法

Layer 類及其派生

Layer 的基本操作

Layer 的建立–LayerRegistry

檔案位置在[caffe_home]/include/caffe/layer_factory.hpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include "caffe/layer_factory.hpp"

...

template <typename Dtype>
class LayerRegistry {
public:
typedef shared_ptr<Layer<Dtype> > (*Creator)(const LayerParameter&);
typedef std::map<string, Creator> CreatorRegistry;

static CreatorRegistry& Registry() {
static CreatorRegistry* g_registry_ = new CreatorRegistry();
return *g_registry_;
}

private:
// Layer registry should never be instantiated - everything is done with its
// static variables.
LayerRegistry() {}

如果想根據一個名字建立一個 Layer,可以呼叫下面的程式碼完成:

1
2
3
4
5
6
7
// Get a layer using a LayerParameter.
static shared_ptr<Layer<Dtype> > CreateLayer(const LayerParameter& param) {

const string& type = param.type();
CreatorRegistry& registry = Registry();
return registry[type](param);
}

1
2
3
4
5
6
7
8
9
template <typename Dtype>
class LayerRegisterer {
public:
LayerRegisterer(const string& type,
shared_ptr<Layer<Dtype> > (*creator)(const LayerParameter&)) {
// LOG(INFO) << "Registering layer type: " << type;
LayerRegistry<Dtype>::AddCreator(type, creator);
}
};

構造引數的靈活性。這個類的定義儲存在 [caffe_home]/src/caffe/proto/caffe.proto 中,它將在編譯中由 protobuf 自動生成。 由於不同的模型層相差較大,需要的引數也千差萬別,因此這個檔案記錄了一個非常複雜的 LayerParameter 結構。由於 Protobuf 提供了一系列的基礎功能–類的構造、成員變數設定、序列化等功能,這部分的操作同樣保持了足夠的靈活性,同時不需要完成太多的程式碼編寫。

1
2
3
class LayerParameter : public ::google::protobuf::Message{
...
}

Layer 的初始化

1
2
3
4
5
6
7
void SetUp(const vector<Blob<Dtype>*>& bottom,
const vector<Blob<Dtype>*>& top) {
CheckBlobCounts(bottom, top);
LayerSetUp(bottom, top);
Reshape(bottom, top);
SetLossWeights(top);
}

LayerSetUpReshape在 Layer 類中都被定義為抽象方法,也就是說,它們的具體內容要靠子類實現
LayerSetUp對模型層進行初始化。
Reshape根據 bottom (也就是層的輸入資料)的維度,確定 top(也就是層的輸出資料)的維度。

SetLossWeights在 Layer 中有具體實現,它的作用是幫助模型為訓練過程中的前向後向計算做準備工作。

Layer 的前向計算

常用層

包括:softmax_loss層,Inner Product層,accuracy層,reshape層和dropout層及其它們的引數配置。

新增自定義的 layer 步驟

https://github.com/BVLC/caffe/issues/684

  1. 屬於哪個型別的 layer,就開啟哪個hpp檔案,這裡就開啟vision_layers.hpp,然後自己新增該 layer 的定義,或者直接複製 Convolution_Layer 的相關程式碼來修改類名和建構函式名都改為 Aaa_Layer,如果不用 GPU,將*_gpu的宣告都去掉。
  2. 實現自己的layer,編寫Aaa_Layer.cpp,加入到src/caffe/layers,主要實現 Setup、Forward_cpu、Backward_cpu。
  3. 如果需要 GPU 實現,那麼在 Aaa_Layer.cu 中實現 Forward_gpu 和 Backward_gpu。
  4. 修改src/caffe/proto/caffe.proto,好到 LayerType,新增 Aaa,並更新ID,如果 Layer 有引數,新增 AaaParameter 類。
  5. src/caffe/layer_factory.cpp中新增響應程式碼。
  6. src/caffe/gtest中寫一個test_Aaa_layer.cpp,對所寫的layer前傳和反傳進行測試,測試還包括速度。 用include/caffe/test/test_gradient_check_util.hpp來檢查前向後向傳播是否正確。

一個例子(triplet loss 層的實現)

  1. caffe.proto 中增加 triplet loss layer 的定義
  2. 在./include/caffe/loss_layers.hpp中增加triplet loss layer的類的
    宣告(TripletLossLayer 類公有繼承於 LossLayer 類,定義了一些變數,用來在前傳中儲存中間計算結果,以便在反傳的時候避免重複計算)
  3. 在./src/caffe/layers/目錄下新建triplet_loss_layer.cpp,實現類
    • LayerSetUp:主要是做一些CHECK工作,然後根據bottom和top對類中的資料成員初始化。
    • Forward_cpu:前傳,計算loss。
    • Backward_cpu:反傳,計算梯度。
  4. 在./src/caffe/layers/目錄下新建triplet_loss_layer.cu,實現GPU下的前傳和反傳
  5. 在./src/caffe/test/目錄下增加test_triplet_loss_layer.cpp
  6. 編譯測試
    • 重新 make all 如果出錯,檢查程式碼語法錯誤。
    • make test。
    • make runtest 如果成功,全是綠色的OK 否則會給出紅色提示,就得看看是不是實現邏輯上出錯了。

參考資料

  1. C++ 單件模式:Singleton 學習筆記
  2. C++ 物件工廠模式:ObjectFactory 學習筆記
  3. 用 C++ 實現簡單工廠模式
  4. Caffe 原始碼閱讀 Layer 載入機制
  5. caffe原始碼:工廠模式
  6. Layer in Caffe
  7. http://www.runoob.com/design-pattern/factory-pattern.html
  8. caffe程式碼閱讀4:LayerRegistry的介紹與實現