Caffe源碼-InsertSplits()函數

InsertSplits()函數

在Net初始化的過程當中,存在一個特殊的修改網絡結構的操做,那就是當某層的輸出blob對應多個其餘層的輸入blob時,會在輸出blob所在層的後面插入一個新的Split類型的層。大體方式以下圖所示,左側爲原始網絡的結構,右側爲修改以後的網絡結構。我的理解這樣作的目的應該是爲了在梯度反傳時,方便多個分支的梯度可以累加到同一個blob上。左側圖,分別計算出layer1和layer2的blob0的梯度後,在計算layer0的blob0的梯度時,Net類中須要額外增長一些操做來將各個分支的梯度累加起來。而右側圖,則是將梯度累加操做當作一個Layer來實現,看起來更合理些。網絡

graph BT A[name: layer0<br>top: blob0]-->B[name: layer1<br>bottom: blob0<br>top: ...] A-->C[name: layer2<br>bottom: blob0<br>top: ...] U[name: layer0<br>top: blob0]-->V[name: blob0_layer0_0_split<br>type: Split<br>bottom: blob0<br>top: blob0_layer0_0_split_0<br>top: blob0_layer0_0_split_1] V-->W[name: layer1<br>bottom: blob0_layer0_0_split_0<br>top: ...] V-->X[name: layer2<br>bottom: blob0_layer0_0_split_1<br>top: ...]

insert_splits.cpp源碼

//根據網絡參數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();
}

小結

  1. 該部分代碼重點是理解InsertSplits()函數初始定義的幾個map類型的變量的含義

參考

https://blog.csdn.net/limengjuhanxin/article/details/87939996函數

Caffe的源碼筆者是第一次閱讀,一邊閱讀一邊記錄,對代碼的理解和分析可能會存在錯誤或遺漏,但願各位讀者批評指正,謝謝支持!.net

相關文章
相關標籤/搜索