在Net初始化的過程當中,存在一個特殊的修改網絡結構的操做,那就是當某層的輸出blob對應多個其餘層的輸入blob時,會在輸出blob所在層的後面插入一個新的Split類型的層。大體方式以下圖所示,左側爲原始網絡的結構,右側爲修改以後的網絡結構。我的理解這樣作的目的應該是爲了在梯度反傳時,方便多個分支的梯度可以累加到同一個blob上。左側圖,分別計算出layer1和layer2的blob0的梯度後,在計算layer0的blob0的梯度時,Net類中須要額外增長一些操做來將各個分支的梯度累加起來。而右側圖,則是將梯度累加操做當作一個Layer來實現,看起來更合理些。網絡
//根據網絡參數param建立新的網絡參數param_split. param_split主要是將param中一些被屢次使用的blob //後面增長一層,將blob分解成多個不一樣名稱的分支,用於後續的輸入 void InsertSplits(const NetParameter& param, NetParameter* param_split) { // Initialize by copying from the input NetParameter. param_split->CopyFrom(param); //拷貝網絡參數 param_split->clear_layer(); //一樣先清空全部layer參數 //<輸出blob的名稱, <第m層網絡, 第n個輸出blob>>,存放當前已記錄的全部輸出blob的名稱最近一次出現的位置 map<string, pair<int, int> > blob_name_to_last_top_idx; //<<第i層網絡, 第j個輸入blob>, <第m層網絡, 第n個輸出blob>> //指示輸入blob數據的來源 map<pair<int, int>, pair<int, int> > bottom_idx_to_source_top_idx; map<pair<int, int>, int> top_idx_to_bottom_count; //<第m層網絡, 第n個輸出blob>,表示該blob被用做輸入blob的次數 map<pair<int, int>, float> top_idx_to_loss_weight; //<第m層網絡, 第n個輸出blob>,該輸出blob對應的loss weight map<pair<int, int>, int> top_idx_to_bottom_split_idx; //<<第m層網絡, 第n個輸出blob>, 輸出blob第k次用做輸入> map<int, string> layer_idx_to_layer_name; //<第i層網絡, 第i層網絡的名稱> for (int i = 0; i < param.layer_size(); ++i) { const LayerParameter& layer_param = param.layer(i); //net中第i層的layer參數 layer_idx_to_layer_name[i] = layer_param.name(); //保存其名稱 for (int j = 0; j < layer_param.bottom_size(); ++j) { //該層的全部輸入blob const string& blob_name = layer_param.bottom(j); //第i層layer的第j個輸入blob的名稱 if (blob_name_to_last_top_idx.find(blob_name) == blob_name_to_last_top_idx.end()) { //輸入blob不在blob_name_to_last_top_idx中,說明與之同名的輸出blob也不在其中,未能在當前的記錄中找到輸出blob的網絡位置, //那個該輸入blob的數據的來源未知,返回錯誤 LOG(FATAL) << "Unknown bottom blob '" << blob_name << "' (layer '" << layer_param.name() << "', bottom index " << j << ")"; } const pair<int, int>& bottom_idx = make_pair(i, j); //第i層的第j個輸入blob const pair<int, int>& top_idx = blob_name_to_last_top_idx[blob_name]; //找到該blob在網絡中用做輸出的最近一次出現的位置 bottom_idx_to_source_top_idx[bottom_idx] = top_idx; //用於輸出的最近的位置,即爲該輸入blob數據的來源,保存 ++top_idx_to_bottom_count[top_idx]; //對應的輸出blob的被使用計數器加一 } for (int j = 0; j < layer_param.top_size(); ++j) { //該層的全部輸出blob const string& blob_name = layer_param.top(j); //第i層的第j個輸出blob的名稱 //輸出blob的名稱重複出現時只會記錄最後一次出現的位置 blob_name_to_last_top_idx[blob_name] = make_pair(i, j); //關聯輸出blob的名稱與位置 } // A use of a top blob as a loss should be handled similarly to the use of // a top blob as a bottom blob to another layer. const int last_loss = std::min(layer_param.loss_weight_size(), layer_param.top_size()); //取較小的 for (int j = 0; j < last_loss; ++j) { const string& blob_name = layer_param.top(j); //第i層的第j個輸出blob的名稱 const pair<int, int>& top_idx = blob_name_to_last_top_idx[blob_name]; //輸出blob的位置 top_idx_to_loss_weight[top_idx] = layer_param.loss_weight(j); //保存輸出blob對應的權重 if (top_idx_to_loss_weight[top_idx]) { //loss權重不爲0,說明loss有效,也將這種類型的輸出blob當作某層的輸入,計數加一 ++top_idx_to_bottom_count[top_idx]; } } } for (int i = 0; i < param.layer_size(); ++i) { //便利全部layer LayerParameter* layer_param = param_split->add_layer(); //在param_split中添加新的層,返回其指針 layer_param->CopyFrom(param.layer(i)); //將當前層的參數拷貝到param_split的新增的層中 //先處理layer 的輸入數據,若是輸入數據對應的來源輸出blob存在被屢次使用的狀況,則會修改輸入blob的名稱 //如下注釋假設第m層的第n個輸出來源於第i層的第j個輸入 // Replace any shared bottom blobs with split layer outputs. for (int j = 0; j < layer_param->bottom_size(); ++j) { //該層的輸入blob,第j個 const pair<int, int>& top_idx = bottom_idx_to_source_top_idx[make_pair(i, j)]; //輸入blob的來源的位置,第m層的第n個輸出 const int split_count = top_idx_to_bottom_count[top_idx]; //第m層的第n個輸出blob被用做輸入blob的次數 if (split_count > 1) { //次數大於1,被屢次使用 const string& layer_name = layer_idx_to_layer_name[top_idx.first]; //第m層layer的名稱 const string& blob_name = layer_param->bottom(j); //第i層的第j個輸入blob的名稱,一樣也是第m層的第n個輸出blob的名稱 //將param_split的新增的層的第j個輸出blob的名稱修改成: blob_name + layer_name + n + 拆分索引 layer_param->set_bottom(j, SplitBlobName(layer_name, blob_name, top_idx.second, top_idx_to_bottom_split_idx[top_idx]++)); //第k次用做輸入,用後加一,保證後續再用於輸入時建立的名稱不一樣 } } //處理layer的輸出數據,若是輸出數據存在屢次使用的狀況,則會在該層後面添加一個新的層.新的層的輸入對應該層的輸出,新層的輸出blob的個數 //對應該層輸出blob被使用的次數,新層的輸出blob的名稱對應上面的layer_param->set_bottom()中SplitBlobName()獲得的名稱,新層類型爲"Split" // Create split layer for any top blobs used by other layer as bottom blobs more than once. for (int j = 0; j < layer_param->top_size(); ++j) { //該層的第j個輸出blob const pair<int, int>& top_idx = make_pair(i, j); const int split_count = top_idx_to_bottom_count[top_idx]; //找到第i層的第j個輸出blob的被使用次數 if (split_count > 1) { const string& layer_name = layer_idx_to_layer_name[i]; //第i層layer的名稱 const string& blob_name = layer_param->top(j); //第j個輸出blob的名稱 LayerParameter* split_layer_param = param_split->add_layer(); //在param_split中增長一個新的層 const float loss_weight = top_idx_to_loss_weight[top_idx]; //第i層的第j個輸出blob對飲的權重 ConfigureSplitLayer(layer_name, blob_name, j, split_count, loss_weight, split_layer_param); //屢次使用時,在該輸出blob後面添加一個新的layer if (loss_weight) { layer_param->clear_loss_weight(); //權重轉移到新增的層中,param_split的當前層的權重置爲0 top_idx_to_bottom_split_idx[top_idx]++; //loss layer中,將當前層的輸出blob當作是某層的輸入,則當前層的輸出blob的計數加一 } } } } } //設置param_split中新增層的參數split_layer_param,新層的輸出blob的個數爲split_count 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(); //先清空全部layer參數 split_layer_param->add_bottom(blob_name); //添加一個輸入blob,名稱爲blob_name split_layer_param->set_name(SplitLayerName(layer_name, blob_name, blob_idx)); //生成一個layer的名稱,設置到新層中 split_layer_param->set_type("Split"); //設置新層的類型爲"Split" for (int k = 0; k < split_count; ++k) { //添加一個新的輸出blob,規則與InsertSplits()中的layer_param->set_bottom()中的一致 split_layer_param->add_top(SplitBlobName(layer_name, blob_name, blob_idx, k)); if (loss_weight) { //權重不爲0,只設置第一條分支的權重,其他分支的權重置爲0. if (k == 0) { //(防止每條分支都計算權重,分割後的網絡與原網絡計算結果不一致) split_layer_param->add_loss_weight(loss_weight); } else { split_layer_param->add_loss_weight(0); } } } } string SplitLayerName(const string& layer_name, const string& blob_name, const int blob_idx) { //生成新的layer的名稱: 輸入blob的名稱 + blob所在layer的名稱 + blob的位置 ostringstream split_layer_name; split_layer_name << blob_name << "_" << layer_name << "_" << blob_idx << "_split"; return split_layer_name.str(); } string SplitBlobName(const string& layer_name, const string& blob_name, const int blob_idx, const int split_idx) { //生成新的blob名稱: 原blob名稱 + layer名稱 + blob索引 + 拆分索引 ostringstream split_blob_name; split_blob_name << blob_name << "_" << layer_name << "_" << blob_idx << "_split_" << split_idx; return split_blob_name.str(); }
https://blog.csdn.net/limengjuhanxin/article/details/87939996函數
Caffe的源碼筆者是第一次閱讀,一邊閱讀一邊記錄,對代碼的理解和分析可能會存在錯誤或遺漏,但願各位讀者批評指正,謝謝支持!.net