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 << ")";
  }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章