目标
複現 DeepID 用 Caffe 實作人臉識别時,網絡的訓練的架構往往是這樣的:
就是說 Image List 中的資料是按對整理好的,類内 (intra class) 類間 (inter class) 資料交替排列。這樣就可以直接利用 ImageDataLayer 獲得一個個均勻的 Batch。現在隻要對 Loss Layer 簡單做一下修改,網絡已經可以正常訓練了,相當簡單。
但簡單的代價也相當明顯:
- 資料類内與類間的組合已經固定,想用新的組合訓練,就得重新整理一個 ImageList。
- 左圖中類内資料與類間資料交替排列的方式不利于 LossLayer 計算的優化。
新的架構
是以如果能把資料整理這一步內建到 DataLayer 中,将會得到更大程度的簡化。而且可以額外附加一些功能,比如随機抽取類内類間資料;類内資料抽取差別較大的組合,類間資料則抽取差別較小的組合,如果計算速度允許的話。
于是新的訓練架構應該是下面這樣的,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 除外)。
TripletDataLayer 的實作
為 Caffe 添加新資料層的步驟 ( 以 TripletDataLayer 為例 ) 如下:
- 在 src/caffe/proto/caffe.proto 中定義相關的參數
- 在 src/caffe/layers/ 目錄下添加 triplet_data_layer.cpp
- 在 include/caffe/layers/ 目錄下添加 triplet_data_layer.hpp
- 在 src/caffe/test 目錄下添加 test_triplet_data_layer.cpp (可選)
第 2, 3 步就是實作 TripletDataLayer 類,最為重要。然後把需要定制的參數添加到 caffe.proto 檔案中。準确地完成這兩步的話,添加 DataLayer 層的工作就算完成了。如果不能确定,最好在 src/caffe/test 目錄下寫一個測試用例。
分析 ImageDataLayer
我的實作原則是 對 Caffe 源碼做最少的修改。縱觀現有的 DataLayer 最佳參考的是 ImageDataLayer 。理所當然地應該分析一下它的實作,下面是我畫的一個非常粗糙的 ImageDataLayer 類圖:
可以看到,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();
待續。。。