天天看點

為 Caffe 添加新的 DataLayer目标TripletDataLayer 的實作

目标

複現 DeepID 用 Caffe 實作人臉識别時,網絡的訓練的架構往往是這樣的:

為 Caffe 添加新的 DataLayer目标TripletDataLayer 的實作

就是說 Image List 中的資料是按對整理好的,類内 (intra class) 類間 (inter class) 資料交替排列。這樣就可以直接利用 ImageDataLayer 獲得一個個均勻的 Batch。現在隻要對 Loss Layer 簡單做一下修改,網絡已經可以正常訓練了,相當簡單。

但簡單的代價也相當明顯:

  • 資料類内與類間的組合已經固定,想用新的組合訓練,就得重新整理一個 ImageList。
  • 左圖中類内資料與類間資料交替排列的方式不利于 LossLayer 計算的優化。

新的架構

是以如果能把資料整理這一步內建到 DataLayer 中,将會得到更大程度的簡化。而且可以額外附加一些功能,比如随機抽取類内類間資料;類内資料抽取差別較大的組合,類間資料則抽取差別較小的組合,如果計算速度允許的話。

于是新的訓練架構應該是下面這樣的,DataLayer 的名字都取好了,叫 TripletDataLayer!

為 Caffe 添加新的 DataLayer目标TripletDataLayer 的實作

TripletDataLayer 會在每次疊代過程中抽取一個 Batch 的資料,Batch 中的資料等分為 Idenities A, Idenities P, Idenities N 三個部分, 分别代表 FaceNet 1 中提出的 Anchor, Positive, Negative。Anchor 與 Positive 是類内組合,Anchor 與 Negative 是類間組合,後面都用這種方式表示。可以看到由于 Batch 内的資料是有序的,對應的 Loss Layer 就不需要 label 資訊了(SoftMax Loss 除外)。

為 Caffe 添加新的 DataLayer目标TripletDataLayer 的實作

TripletDataLayer 的實作

為 Caffe 添加新資料層的步驟 ( 以 TripletDataLayer 為例 ) 如下:

  1. 在 src/caffe/proto/caffe.proto 中定義相關的參數
  2. 在 src/caffe/layers/ 目錄下添加 triplet_data_layer.cpp
  3. 在 include/caffe/layers/ 目錄下添加 triplet_data_layer.hpp
  4. 在 src/caffe/test 目錄下添加 test_triplet_data_layer.cpp (可選)

第 2, 3 步就是實作 TripletDataLayer 類,最為重要。然後把需要定制的參數添加到 caffe.proto 檔案中。準确地完成這兩步的話,添加 DataLayer 層的工作就算完成了。如果不能确定,最好在 src/caffe/test 目錄下寫一個測試用例。

分析 ImageDataLayer

我的實作原則是 對 Caffe 源碼做最少的修改。縱觀現有的 DataLayer 最佳參考的是 ImageDataLayer 。理所當然地應該分析一下它的實作,下面是我畫的一個非常粗糙的 ImageDataLayer 類圖:

為 Caffe 添加新的 DataLayer目标TripletDataLayer 的實作

可以看到,ImageDataLayer 與普通的 Layer 不同,它不需要實作 .cu 檔案。因為 GPU 的操作已經在高層的類中實作了,同樣資料預取操作 (經典的生産者消費者問題,每次預取 3 個 Batch) 也已在高層類中實作。是以我們隻要重寫 load_batch() 函數即可。

在 caffe.proto 中定義外部參數

為參數定義一個 message 結構

在 ImageDataParameter 的基礎上修改。雖然結構名的選擇是自由的,但最好按 Caffe 的風格來寫,即結構名與類名對應。有關 protobuf 的文法請自行 Google。

message TripletDataParameter 
{
  // 圖檔清單的路徑,可以是檔案名或檔案路徑
  required string source = ;

  // 是否根據圖檔的特征篩選資料
  optional bool use_feature =  [default = false];

  // 圖檔特征檔案的擴充名,特征檔案與對應圖檔位于同一目錄下
  optional string feature_extension =   [default = ".feat"];

  // 如果每個人的圖檔數量不等時,讓每個 Batch 的資料分布與訓練集相同
  optional bool batch_follow_distribution =  [default = true];

  // 存放圖檔的目錄(所有圖檔都存放在同一目錄時使用)
  optional string root_folder =  [default = ""];

  // Batch size,Layer 初始化時把它改為 3 的倍數
  optional uint32 batch_size =  [default = ];

  // 啟動時随機跳過幾個資料,與 ImageDataLayer 略有不同
  optional uint32 rand_skip =  [default = ];

  // 下面的參數的意義與 ImageDataLayer 完全相同
  optional bool shuffle =  [default = false];
  optional uint32 new_height =  [default = ];
  optional uint32 new_width =  [default = ];
  optional bool is_color =  [default = true];
  optional float scale =  [default = ];
  optional string mean_file = ;
  optional uint32 crop_size =  [default = ];
  optional bool mirror =  [default = false];
}
           

在 LayerParameter 中添加參數的定義

同樣,為了統一性參數名照樣與類名相關。應注意辨別号不能與前面的字段重複。

message LayerParameter {
  ...
  optional TripletDataParameter triplet_data_param = ;
}
           

然後在 TripletDataLayer 類中就可以讀取這些參數了 ( caffe.pb.cc 與 caffe.pb.h 會在 Caffe 編譯過程中自動編譯出來 )。比如我們想讀取配置檔案中的 batch_size 參數,可以這樣做:

int batch_size_ = this->layer_param_.triplet_data_param().batch_size();
           

待續。。。