Caffe 源码阅读笔记 [基本模块] 网络Net

概述

前面讲了Layer,Net就是把Layer串起来的神经网络。本篇主要讨论在神经网络中如何进行Layer之间的管理和计算

Net成员变量

网络相关变量

  string name_; // 网络名字
  Phase phase_; // TRAIN 或者 TEST
  const Net* const root_net_; // root_net存储共享的Layer以方便数据并行处理
  size_t memory_used_; // 计算这个网络的内存使用量
  bool debug_info_; // 是否输出调试信息

Layer相关变量

  vector<shared_ptr<Layer<Dtype> > > layers_; // Layer的集合
  vector<string> layer_names_; // Layer的名字集合
  map<string, int> layer_names_index_; // 通过Layer名字得到Layer的索引
  vector<bool> layer_need_backward_; // layer_need_backward_[i]=true表示第i个Layer需要反向传播

Blob相关变量

  // 存储整个Net中所有用到的Blob
  vector<shared_ptr<Blob<Dtype> > > blobs_; 
  vector<string> blob_names_; // 类似于layer_names_
  map<string, int> blob_names_index_; // 类似于layer_names_index_
  vector<bool> blob_need_backward_; // 类似于layer_need_backward_
  vector<Dtype> blob_loss_weights_; // 存储每个blob计算Loss函数时使用的loss权重
  // 存储每个Layer的bottom和top blob的指针,他们通过id_vecs_存储在blobs_中的索引
  vector<vector<Blob<Dtype>*> > bottom_vecs_,top_vecs_;
  vector<vector<int> > bottom_id_vecs_, top_id_vecs_;
  vector<vector<bool> > bottom_need_backward_;
  // 网络的输入和输出Blobs以及在blobs_中对应的索引
  vector<Blob<Dtype>*> net_input_blobs_, net_output_blobs_;
  vector<int> net_input_blob_indices_, net_output_blob_indices_;

训练参数相关变量

  vector<shared_ptr<Blob<Dtype> > > params_; // 存储整个Net中所有用到的参数Blob,可重复
  vector<Blob<Dtype>*> learnable_params_; // 可学习的参数集合
  // learnable_param_ids_元素个数等于params_元素个数
  // 当且仅当params_[i]是参数所有者的时候,learnable_params_[learnable_param_ids_[i]]等于params_[i],否则params_[i]仅仅共享了这个参数而已, learnable_params_[learnable_param_ids_[i]]等于这个参数的所有者。
  vector<int> learnable_param_ids_;
  vector<vector<int> > param_id_vecs_; //存储每个Layer里每个param在params_中的索引
  vector<int> param_owners_; // param_owners_[i]存储params_[i]的owner_id,如果它本身是owner,则param_owners_[i] = -1
  vector<string> param_display_names_; // 所有参数的名字集合
  vector<pair<int, int> > param_layer_indices_; // 存储所有的<layer_id, param_id> 对
  map<string, int> param_names_index_; //存储参数名字到参数所有者id的映射
    vector<float> params_lr_; // 第i个learnable_params_参数的学习率(learning rate)
  vector<bool> has_params_lr_; // 第i个参数是否有学习率
  vector<float> params_weight_decay_; // 第i个learnable_params_参数的权重递减率(weight decay)
  vector<bool> has_params_decay_; // 第i个参数是否有权重递减

Net成员函数

初始化函数Init

  1. FilterNet(in_param, &filtered_param); // 过滤Net中不使用的Layer
    1. 每个Layer都有两个NetStateRule类型的属性include和exclude,NetStateRule描述了在什么条件下这个Layer会被使用或者不被使用(基于Phase, min_level, max_level, 允许使用的stage集合和不允许使用的stage集合)。如果include和exclude都没有定义,则默认这个Layer允许使用。
    2. FilterNet会遍历Net参数里面的Layer,逐个判断每个Layer是否应该在这个Net里面使用,如果是,则把它的参数拷贝到filtered_param里。相当于把不使用的Layer过滤掉。
  2. InsertSplits(filtered_param, &param); // 给网络添加SplitLayer,如果一个Layer L的输出blob B同时被n个Layer作为输入blob,则会给它专门加一个SplitLayer,这个SplitLayer的输入是B,有n个输出blob,每个输出blob都等于B,但第i个输出blob的名字是B_L_i_split。同时因为我们新引进了一个SplitLayer,如果Layer i 以B作为输入,那么需要把它的输入从B改为B_L_i_split。
  3. 遍历param中的每一个LayerParameter构造Layer
    1. 如果root_net里面是共享这个Layer的, 从root_net里面获得Layer
      1. layers_.push_back(root_net_->layers_[layer_id]);
      2. layers_[layer_id]->SetShared(true)
    2. 否则建立一个新的layers_.push_back(LayerRegistry<Dtype>::CreateLayer(layer_param));
    3. layer_names_.push_back(layer_param.name())
    4. 调用AppendTop和AppendBottom把Top和Bottom的blobs加到Net中
    5. 如果Layer的类型是”Input”, 则更新net_input_blobs_和net_input_blob_indices_把每个top blob和它的idx加上
    6. 初始化Layer layers_[layer_id]->SetUp(bottom_vecs_[layer_id], top_vecs_[layer_id])
    7. 对每个top blob设置loss_weights值 blob_loss_weights_[top_id_vecs_[layer_id][top_id]] = layer->loss(top_id);
    8. memory_used_ += top_vecs_[layer_id][top_id]->count() 对每一层我们只累加top blobs的内存总量而不是top和bottom的内存总量,这是因为上一层的top blob就是下一层的bottom blob,我们不需要存相同数据两次。
    9. 对Layer的每个可训练的参数来说,如果param_spec->lr_mult() 不等于0,则我们需要设置Layer中对应的param_propagate_down_值为true,表示我们需要把误差梯度传反向传播下去。
    10. 调用AppendParam把每个Param blob加到Net中
    11. layer_need_backward_[layer_id] = need_backward 如果Layer中有需要反向传播的参数或者bottom blob,need_backward就设为true。
    12. blob_need_backward_[top_id_vecs_[layer_id][top_id]] = need_backward 把所有的top blob都设为need_backward。
  4. for (layer_id = layer.size() - 1; layer_id >=0; --layer_id) // 从后往前检查每层来查看哪些blob跟最后的loss函数有关,从而只对那些blob进行反向计算。同时检查哪些bottom blob不需要进行反向计算,可以因此跳过某些层的计算
    1. layer_contributes_loss = false
    2. layer_skip_propagate_down = true
    3. for (top_id = 0; top_id < top_vecs_[layer_id].size(); top_id++)
      1. 如果layer->loss(top_id) > 0 或者blobs_under_loss包含了这个blob,则layer_contributes_loss=true
      2. 如果blobs_skip_backp不包含这个blob,则layer_skip_propagate_down=false
    4. 如果layer_skip_propagate_down或者!layer_contributes_loss,则把所有的bottom blob和layer标记为不需要反向计算layer_need_backward[layer_id]=false; bottom_need_backward[layer_id][bottom_id]=false
    5. for (bottom_id = 0; bottom_id < bottom_vecs_[layer_id].size(); bottom_id++)
      1. 如果layer_contribute_loss,则把所有bottom blob加到blobs_under_loss里
      2. 如果!bottom_need_backward[layer_id][bottom_id],则把它加到blobs_skip_backp里
  5. 如果强制要求反向计算,那么就会把bottom_need_backward和blob_need_backward_, param_propagate_down_都设成true
  6. 剩下没处理的blob会被当成网络的output blob加到net_output_blobs_和net_output_blob_indices_ 变量里
  7. ShareWeigths, 把所有共享同一param blob的blobs都通过ShareData和ShareDiff函数共享同一块内存。

AppendTop, AppendBottom和AppendParam函数

void AppendTop(const NetParameter& param, const int layer_id,
               const int top_id, set<string>* available_blobs,
               map<string, int>* blob_name_to_idx) {
  // 获得当前blob的名字
  const string& blob_name = (layer_param->top_size() > top_id) ?
      layer_param->top(top_id) : "(automatic)";
  // 如果这层的bottom blob和当前blob是一样的话,直接把bottom blob加到top_vecs_和top_id_vecs_里面
  if (blob_name_to_idx && layer_param->bottom_size() > top_id &&
      blob_name == layer_param->bottom(top_id)) {
    top_vecs_[layer_id].push_back(blobs_[(*blob_name_to_idx)[blob_name]].get());
    top_id_vecs_[layer_id].push_back((*blob_name_to_idx)[blob_name]);
  } else {
    // 申请一个新的blob指针
    shared_ptr<Blob<Dtype> > blob_pointer(new Blob<Dtype>());
    const int blob_id = blobs_.size(); // 获得blob id
    // 相应更新各个blob相关的变量
    blobs_.push_back(blob_pointer); 
    blob_names_.push_back(blob_name);
    blob_need_backward_.push_back(false);
    if (blob_name_to_idx) { (*blob_name_to_idx)[blob_name] = blob_id; }
    top_id_vecs_[layer_id].push_back(blob_id);
    top_vecs_[layer_id].push_back(blob_pointer.get());
  }
  // 把所有top blob加进去,然后把所有bottom blob删掉,就能得到剩下的只作为网络输出的blob
  if (available_blobs) { available_blobs->insert(blob_name); }
}
int AppendBottom(const NetParameter& param, const int layer_id,
    const int bottom_id, set<string>* available_blobs,
    map<string, int>* blob_name_to_idx) {
  // 获得当前blob的名字和id。
  const string& blob_name = layer_param.bottom(bottom_id);
  const int blob_id = (*blob_name_to_idx)[blob_name];
  // 更新blob相关变量
  bottom_vecs_[layer_id].push_back(blobs_[blob_id].get());
  bottom_id_vecs_[layer_id].push_back(blob_id);
  available_blobs->erase(blob_name);
  // 通过blob_need_backward_和layer_param确定bottom_need_backward_的值
  bool need_backward = blob_need_backward_[blob_id];
  if (layer_param.propagate_down_size() > 0) {
    need_backward = layer_param.propagate_down(bottom_id);
  }
  bottom_need_backward_[layer_id].push_back(need_backward);
  return blob_id;
}
void AppendParam(const NetParameter& param, const int layer_id, const int param_id) {
  // 参数名字和id
  string param_name =
      (param_size > param_id) ? layer_param.param(param_id).name() : "";
  const int net_param_id = params_.size();
  // 更新param相关变量
  param_display_names_.push_back(param_name);
  params_.push_back(layers_[layer_id]->blobs()[param_id]);
  param_id_vecs_[layer_id].push_back(net_param_id);
  param_layer_indices_.push_back(make_pair(layer_id, param_id));
  if (!param_size || !param_name.size() || (param_name.size() &&
      param_names_index_.find(param_name) == param_names_index_.end())) {
    // 如果这个参数是新的参数, 也就是说这个参数是参数的所有者
    param_owners_.push_back(-1);
    if (param_name.size()) {
      param_names_index_[param_name] = net_param_id;
    }
    // 加入到可学习参数集合
    const int learnable_param_id = learnable_params_.size();
    learnable_params_.push_back(params_[net_param_id].get());
    learnable_param_ids_.push_back(learnable_param_id);
    // 更新lr/weight_decay相关变量
    has_params_lr_.push_back(param_spec->has_lr_mult());
    has_params_decay_.push_back(param_spec->has_decay_mult());
    params_lr_.push_back(param_spec->lr_mult());
    params_weight_decay_.push_back(param_spec->decay_mult());
  } else {
    // 当前参数是参数的共享者,owner_net_param_id是参数所有者的id
    const int owner_net_param_id = param_names_index_[param_name];
    // 记录它的owner_id
    param_owners_.push_back(owner_net_param_id);
    const int learnable_param_id = learnable_param_ids_[owner_net_param_id];
    learnable_param_ids_.push_back(learnable_param_id);
    // 更新lr/weight_decay相关变量,略
  }
}

Net前向函数

// 从Layer start前向传播到Layer end,loss的叠加是整个Net的loss
Dtype ForwardFromTo(int start, int end) {
  Dtype loss = 0;
  for (int i = start; i <= end; ++i) {
    Dtype layer_loss = layers_[i]->Forward(bottom_vecs_[i], top_vecs_[i]);
    loss += layer_loss;
    if (debug_info_) {
       // 打印Layer i的每个top blob, param blob数据的平均值
       ForwardDebugInfo(i); 
    }
  }
  return loss;
}
// 除了计算loss之外,返回output_blobs的值
const vector<Blob<Dtype>*>& Forward(Dtype* loss) {
  if (loss != NULL) {
    *loss = ForwardFromTo(0, layers_.size() - 1);
  } else {
    ForwardFromTo(0, layers_.size() - 1);
  }
  return net_output_blobs_;
}

Net反向函数

// 从Layer start反向传播刀Layer end
void Net<Dtype>::BackwardFromTo(int start, int end) {
  for (int i = start; i >= end; --i) {
    if (layer_need_backward_[i]) { // 只对需要反向传播的Layer处理
      layers_[i]->Backward(
          top_vecs_[i], bottom_need_backward_[i], bottom_vecs_[i]);
      if (debug_info_) { 
        // 打印Layer i的bottom blob和param blob的diff的平均值
        BackwardDebugInfo(i); 
      }
    }
  }
}
void Net<Dtype>::Backward() {
  BackwardFromTo(layers_.size() - 1, 0);
  if (debug_info_) {
    // 打印所有参数的数据和diff的L1, L2 norm 
    Dtype asum_data = 0, asum_diff = 0, sumsq_data = 0, sumsq_diff = 0;
    for (int i = 0; i < learnable_params_.size(); ++i) {
      asum_data += learnable_params_[i]->asum_data();
      asum_diff += learnable_params_[i]->asum_diff();
      sumsq_data += learnable_params_[i]->sumsq_data();
      sumsq_diff += learnable_params_[i]->sumsq_diff();
    }
    const Dtype l2norm_data = std::sqrt(sumsq_data);
    const Dtype l2norm_diff = std::sqrt(sumsq_diff);
    LOG(ERROR) << "    [Backward] All net params (data, diff): "
               << "L1 norm = (" << asum_data << ", " << asum_diff << "); "
               << "L2 norm = (" << l2norm_data << ", " << l2norm_diff << ")";
  }
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值