• 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的源码笔者是第一次阅读,一边阅读一边记录,对代码的理解和分析可能会存在错误或遗漏,希望各位读者批评指正,谢谢支持!

  • 相关阅读:
    全排列和全组合实现
    (原)关于MEPG-2中的TS流数据格式学习
    linux的PAM认证和shadow文件中密码的加密方式
    vim 撤销 回退操作
    memcached解压报错gzip: stdin: not in gzip format tar: Child returned status 1 tar: Error is not recoverable: exiting now的解决方法
    Linux系统安全之pam后门安装使用详解
    漏洞预警:Linux内核9年高龄的“脏牛”0day漏洞
    linux软链接的创建、删除和更新
    关于“.bash_profile”和“.bashrc”区别的总结
    linux下批量杀死进程
  • 原文地址:https://www.cnblogs.com/Relu110/p/12008459.html
Copyright © 2020-2023  润新知