以前在簡書的文章,搬遷過來 ^-^
本文是做者原創,若有理解錯誤,懇請你們指出,如需引用,請註明出處。網絡
#Caffe FeatureMap數據流的創建 ##用語解釋框架
在講解FeatureMap的數據流以前,首先須要明確一下caffe的大致結構,caffe的總體邏輯結構分爲3層,分別是Net,Layer和Blob,分別的做用以下:函數
由上面的講解分層關係不難看出,FeatureMap在整個Caffe框架中,不屬於任何一個Layer,因此它被最頂層的Net層所持有。Net層就須要可以經過caffe的模型文件推倒出每一層所依賴的輸入,這樣才能構建出一個完整的數據鏈。在這種需求下Caffe引入了兩個定義:spa
因此Net在調用Layer以前就必定知道了Layer的所須要的輸入數據,也就是須要Net層所持有的Blob變量須要被那些層所引用。這些在模型文件中也有直觀的反應(爲了方便截圖,刪除了下圖proto中關於Convlution的參數配置):指針
上述的工做都在Net的Init(void Net::Init(const NetParameter& in_param)
)函數裏面進行了處理,主要實現的就是根據上圖左側的模型文件獲得須要創建的Layer的類型,並將各個Layer間的數據連接起來。函數中的關鍵參數以下:code
名稱 | 功能 |
---|---|
in_param | 存放由protobuf轉換出的模型文件 |
bottom_vecs_ | 存放每一層中的輸入數據類型爲:vector<vector<Blob*> > |
top_vecs_ | 存放每一層中的輸出數據類型爲:vector<vector<Blob*> > |
available_blobs | 存放每一層中的輸出數據類型爲:vector<vector<Blob*> > |
##常規的數據鏈創建流程是(單輸入單輸出的場景):orm
連接本層的bottom數據( int Net::AppendBottom(const NetParameter& param, const int layer_id, const int bottom_id, set<string>* available_blobs, map<string, int>* blob_name_to_idx)
),該函數會使用從當前layer持有的bottom信息中獲得對應bottom的層名,而後利用該名稱找到對應的blob,並加入到bottom_vecs_。cdn
連接本層的top數據(void Net::AppendTop(const NetParameter& param, const int layer_id,const int top_id, set<string>* available_blobs, map<string, int>* blob_name_to_idx)
),該操做就是將本層的輸出數據加入到top_vecs_中,並與 layer_id相關聯,這裏同時負責Blob對象的申請。 須要指出的是,新的Blob對象是在top中進行建立的,在Bottom中只是將上一層top的指針添加進來,同時在這個過程當中CAFFE還利用available_blobs進行了異常校驗,在每次新加入top的時候記錄對應的Blob名稱,在bottom中連接上一層top以後,在available_blobs中將對應的Blob名稱剔除。相關僞代碼以下:對象
for (int layer_id = 0; layer_id < param.layer_size(); ++layer_id) {
AppendBottom();
AppendTop();
}
複製代碼
##多輸入的數據鏈的創建: 細心的同窗應該已經發現,當數據爲多bottom輸入的時候,由於available_blobs的數據被上一次的連接過程刪掉,則再次連接相同bottom的時候,會出先異常告警,在這種狀況下咱們就要引入CAFFE的另一處理函數 void InsertSplits(const NetParameter& param, NetParameter* param_split)
,該函數的主要功能就是對 top輸出到多個 Layer的狀況進行分割。 整個函數分爲兩個部分:blog
遍歷整個網絡,記錄每個Layer的top的使用狀況,記錄結構放在 top_idx_to_bottom_count
中。
遍歷整個網絡,對 top_idx_to_bottom_count > 1
的狀況進行處理: a. 首先是對top被多個層使用的Layer進行分割,主要的作法是在該層的後面新建一個Layer ,這個新的Layer的會按照 top_idx_to_bottom_count
的個數和約定的分割名稱(SplitBlobName
)去新建top,添加層的代碼以下(此處只展現核心的建立過程,具體調用流程請自行跟蹤):
//該函數執行新層的添加
void ConfigureSplitLayer(const string& layer_name, const string& blob_name,
const int blob_idx, const int split_count, const float loss_weight,
LayerParameter* split_layer_param) {
split_layer_param->Clear();
split_layer_param->add_bottom(blob_name);
split_layer_param->set_name(SplitLayerName(layer_name, blob_name, blob_idx));
split_layer_param->set_type("Split");
for (int k = 0; k < split_count; ++k) {//split_count就是該top被引用的個數
//添加了分割後的top
//命名由SplitBlobName生成
split_layer_param->add_top(
SplitBlobName(layer_name, blob_name, blob_idx, k));
if (loss_weight) {
if (k == 0) {
split_layer_param->add_loss_weight(loss_weight);
} else {
split_layer_param->add_loss_weight(0);
}
}
}
}
複製代碼
b. 以後,是對使用同一個top的後續層的bottom的blob進行更名,使用與上一步相同的命名規則進行更名。
下面以SqueezeNet1.1爲例,展現了添加新的分割層的實例:
![Upload new_split_layer.jpg failed. Please try again.]
經過這樣一個分割的轉化,達到了對多輸入數據流的創建。
##遺留問題 上面講的是在初始化階段對FeatureMap數據的連接關係的創建,可是對於weights的填充和初始圖片的輸入並無進行分析。