caffe源碼閱讀《四》net

caffe的net是一個網絡架構,串聯起所有的blob,支撐着整個神經網絡前向和反向傳播的一個結構。他也是分爲hpp和cpp文件的。
首先看註釋

/**
 * @brief Connects Layer%s together into a directed acyclic graph (DAG)
 *        specified by a NetParameter.
 *
 * TODO(dox): more thorough description.
 */
template <typename Dtype>
class Net

把各個層連接在一起,形成一個有向無環圖。
接着直接看代碼

Net 構造函數

  explicit Net(const NetParameter& param);
  explicit Net(const string& param_file, Phase phase,
      const int level = 0, const vector<string>* stages = NULL);

首先是構造函數,他又兩個,第一個傳入的是 NetParameter的對象,第二個是通過文件讀取的,傳入的文件路徑和一個 phase, phase是指的我這個網絡的狀態是 train 還是 test的,然後還有 levelstages.
看看它的具體實現

template <typename Dtype>
Net<Dtype>::Net(const string& param_file, Phase phase,
    const int level, const vector<string>* stages) {
  NetParameter param;
  ReadNetParamsFromTextFileOrDie(param_file, &param);
  // Set phase, stages and level
  param.mutable_state()->set_phase(phase);
  if (stages != NULL) {
    for (int i = 0; i < stages->size(); i++) {
      param.mutable_state()->add_stage((*stages)[i]);
    }
  }
  param.mutable_state()->set_level(level);
  Init(param);
}

由於我們參數是從文件讀取的,所以這個

NetParameter param;

是我們自己定義的。
然後是執行了

ReadNetParamsFromTextFileOrDie(param_file, &param);

這個函數傳入了兩個參數,一個是這個文件的地址,一個是 param這個變量,用來接收文件中讀取的配置參數。這個函數是位於 src/caffe/util/upgrade_proto.cpp這個目錄下的,它的大概功能就是從這個文件裏面讀取參數到 param這個變量裏去。這個 param的格式是在 src/caffe/proto/caffe.proto裏面定義的,我們可以去看一看。

message NetParameter {
  optional string name = 1; // consider giving the network a name
  // DEPRECATED. See InputParameter. The input blobs to the network.
  repeated string input = 3;
  // DEPRECATED. See InputParameter. The shape of the input blobs.
  repeated BlobShape input_shape = 8;

  // 4D input dimensions -- deprecated.  Use "input_shape" instead.
  // If specified, for each input blob there should be four
  // values specifying the num, channels, height and width of the input blob.
  // Thus, there should be a total of (4 * #input) numbers.
  repeated int32 input_dim = 4;

  // Whether the network will force every layer to carry out backward operation.
  // If set False, then whether to carry out backward is determined
  // automatically according to the net structure and learning rates.
  optional bool force_backward = 5 [default = false];
  // The current "state" of the network, including the phase, level, and stage.
  // Some layers may be included/excluded depending on this state and the states
  // specified in the layers' include and exclude fields.
  optional NetState state = 6;

  // Print debugging information about results while running Net::Forward,
  // Net::Backward, and Net::Update.
  optional bool debug_info = 7 [default = false];

  // The layers that make up the net.  Each of their configurations, including
  // connectivity and behavior, is specified as a LayerParameter.
  repeated LayerParameter layer = 100;  // ID 100 so layers are printed last.

  // DEPRECATED: use 'layer' instead.
  repeated V1LayerParameter layers = 2;
}

這裏面還是定義了很多東西的,並且都有非常詳細的註釋,比方說網絡的名字,形狀,是否打印等等。
緊接着是設置狀態,就是下面這一步。

param.mutable_state()->set_phase(phase);

然後如果設置了 stage的話,就把每一層的 stage加載進來。
然後 level也是同理

param.mutable_state()->set_level(level);

最後初始化一下 param就完了

Init(param);

然後我們再看直接傳入這個 NetParameter的構造函數

template <typename Dtype>
Net<Dtype>::Net(const NetParameter& param) {
  Init(param);
}

他就是直接調用了 Init這個函數。
然後我們再看一下 Init這個函數,它也是在這個 Net類裏面。

Init 函數

  /// @brief Initialize a network with a NetParameter.
  void Init(const NetParameter& param);

這個函數實際上就是做一個初始化的功能。

  // Set phase from the state.
  phase_ = in_param.state().phase();

比方這一句就是把從參數裏讀取的 phase賦值給自己的私有變量 phase_.

  // Filter layers based on their include/exclude rules and
  // the current NetState.
  NetParameter filtered_param;
  FilterNet(in_param, &filtered_param);
  LOG_IF(INFO, Caffe::root_solver())
      << "Initializing net from parameters: " << std::endl
      << filtered_param.DebugString();

然後定義了一個 NetParameter的變量,去調用了一個 FilterNet函數, 這個FilterNet函數是幹嘛的呢?
它是根據 state初始化所有層的一個函數。這個後面緊跟着就會講解。

  // Create a copy of filtered_param with splits added where necessary.
  NetParameter param;
  InsertSplits(filtered_param, &param);

然後跟着的是 InsertSplits這個函數,這個函數是幹嘛的呢?
因爲有的層可能會分裂,下面會有兩條路可以走,比方說有兩個top,那麼這個時候就需要分割出來了。

  // For each layer, set up its input and output
  bottom_vecs_.resize(param.layer_size());
  top_vecs_.resize(param.layer_size());
  bottom_id_vecs_.resize(param.layer_size());
  param_id_vecs_.resize(param.layer_size());
  top_id_vecs_.resize(param.layer_size());
  bottom_need_backward_.resize(param.layer_size());

接下來的這個是根據layer的大小給這些vector分配空間

    if (!param.layer(layer_id).has_phase()) {
      param.mutable_layer(layer_id)->set_phase(phase_);
    }

給每個layer分配 phase,決定它是 train還是 test

    layers_.push_back(LayerRegistry<Dtype>::CreateLayer(layer_param));
    layer_names_.push_back(layer_param.name());
    LOG_IF(INFO, Caffe::root_solver())
        << "Creating Layer " << layer_param.name();
    bool need_backward = false;

    // Figure out this layer's input and output
    for (int bottom_id = 0; bottom_id < layer_param.bottom_size();
         ++bottom_id) {
      const int blob_id = AppendBottom(param, layer_id, bottom_id,
                                       &available_blobs, &blob_name_to_idx);
      // If a blob needs backward, this layer should provide it.
      need_backward |= blob_need_backward_[blob_id];
    }

然後開始把每個layer鏈接起來,因爲它是自 topbottom鏈接的,所以這裏是 AppendBottom,他的 bottom添加了對應的blob,也就是相當於一個鏈表一樣的結構,每個 bottom添加了後面一個 bottom的id。

    int num_top = layer_param.top_size();
    for (int top_id = 0; top_id < num_top; ++top_id) {
      AppendTop(param, layer_id, top_id, &available_blobs, &blob_name_to_idx);
      // Collect Input layer tops as Net inputs.
      if (layer_param.type() == "Input") {
        const int blob_id = blobs_.size() - 1;
        net_input_blob_indices_.push_back(blob_id);
        net_input_blobs_.push_back(blobs_[blob_id].get());
      }
    }

然後是把 top對應層的id添加進去,也就是相當於一個雙向的鏈表一樣的。

    Layer<Dtype>* layer = layers_[layer_id].get();
    if (layer->AutoTopBlobs()) {
      const int needed_num_top =
          std::max(layer->MinTopBlobs(), layer->ExactNumTopBlobs());
      for (; num_top < needed_num_top; ++num_top) {
        // Add "anonymous" top blobs -- do not modify available_blobs or
        // blob_name_to_idx as we don't want these blobs to be usable as input
        // to other layers.
        AppendTop(param, layer_id, num_top, NULL, NULL);
      }
    }

如果你這個層定義了自動top的話,那麼這裏就會把自動top添加進去。
這個自動top blob就是之前我們在layer看到的這麼一個接口。至於每個層具體怎麼實現的其實是在不同層有不同的實現方法的。

    // After this layer is connected, set it up.
    layers_[layer_id]->SetUp(bottom_vecs_[layer_id], top_vecs_[layer_id]);

全部都添加好了以後就開始走這個層的 setup函數,這個 setup我們之前在 layer裏面講到過,就是那4步,就是。

  void SetUp(const vector<Blob<Dtype>*>& bottom,
      const vector<Blob<Dtype>*>& top) {
    CheckBlobCounts(bottom, top);
    LayerSetUp(bottom, top);
    Reshape(bottom, top);
    SetLossWeights(top);
  }
  • 檢查blob的count
  • 走layer的setup
  • 然後reshape它
  • 最後設置loss權重
    for (int top_id = 0; top_id < top_vecs_[layer_id].size(); ++top_id) {
      if (blob_loss_weights_.size() <= top_id_vecs_[layer_id][top_id]) {
        blob_loss_weights_.resize(top_id_vecs_[layer_id][top_id] + 1, Dtype(0));
      }
      blob_loss_weights_[top_id_vecs_[layer_id][top_id]] = layer->loss(top_id);
      LOG_IF(INFO, Caffe::root_solver())
          << "Top shape: " << top_vecs_[layer_id][top_id]->shape_string();
      if (layer->loss(top_id)) {
        LOG_IF(INFO, Caffe::root_solver())
            << "    with loss weight " << layer->loss(top_id);
      }
      memory_used_ += top_vecs_[layer_id][top_id]->count();
    }

然後resize一下loss權重的大小,最後還統計了一下會用到多少內存,然後打印出來。

    for (int param_id = 0; param_id < num_param_blobs; ++param_id) {
      const ParamSpec* param_spec = (param_id < param_size) ?
          &layer_param.param(param_id) : &default_param_spec;
      const bool param_need_backward = param_spec->lr_mult() != 0;
      need_backward |= param_need_backward;
      layers_[layer_id]->set_param_propagate_down(param_id,
                                                  param_need_backward);
    }

接着設置了每一層的 propagate_down

    for (int param_id = 0; param_id < num_param_blobs; ++param_id) {
      AppendParam(param, layer_id, param_id);
    }

再把每一層的參數添加進去。

    // Finally, set the backward flag
    layer_need_backward_.push_back(need_backward);
    if (need_backward) {
      for (int top_id = 0; top_id < top_id_vecs_[layer_id].size(); ++top_id) {
        blob_need_backward_[top_id_vecs_[layer_id][top_id]] = true;
      }
    }

最後設置一下反向傳播的狀態,如果參數裏指定了這一層有反向傳播那麼這裏就會設置成 true
這樣我們這個對於layer初始化的過程就完成了

  // Go through the net backwards to determine which blobs contribute to the
  // loss.  We can skip backward computation for blobs that don't contribute
  // to the loss.
  // Also checks if all bottom blobs don't need backward computation (possible
  // because the skip_propagate_down param) and so we can skip backward
  // computation for the entire layer

然後他說通過網絡反向傳播去決定哪一個blob貢獻了loss,就可以跳過這些不需要貢獻loss的反傳計算。
然後看看是不是所有的blob都不需要反傳,如果是的話那麼我們就可以直接跳過 propagate_down這一步了

    bool layer_contributes_loss = false;
    bool layer_skip_propagate_down = true;

首先默認他沒有貢獻loss的。

      if (layers_[layer_id]->loss(top_id) ||
          (blobs_under_loss.find(blob_name) != blobs_under_loss.end())) {
        layer_contributes_loss = true;
      }

如果發現它貢獻了loss就把這個 layer_contributes_loss設置成 true

      if (blobs_skip_backp.find(blob_name) == blobs_skip_backp.end()) {
        layer_skip_propagate_down = false;
      }
      if (layer_contributes_loss && !layer_skip_propagate_down)
        break;

如果一直沒有做貢獻的話那麼我們就可以跳過 propagate_down這一步了。

  // Handle force_backward if needed.
  if (param.force_backward()) {
    for (int layer_id = 0; layer_id < layers_.size(); ++layer_id) {
      layer_need_backward_[layer_id] = true;
      for (int bottom_id = 0;
           bottom_id < bottom_need_backward_[layer_id].size(); ++bottom_id) {
        bottom_need_backward_[layer_id][bottom_id] =
            bottom_need_backward_[layer_id][bottom_id] ||
            layers_[layer_id]->AllowForceBackward(bottom_id);
        blob_need_backward_[bottom_id_vecs_[layer_id][bottom_id]] =
            blob_need_backward_[bottom_id_vecs_[layer_id][bottom_id]] ||
            bottom_need_backward_[layer_id][bottom_id];
      }
      for (int param_id = 0; param_id < layers_[layer_id]->blobs().size();
           ++param_id) {
        layers_[layer_id]->set_param_propagate_down(param_id, true);
      }
    }
  }

然後這裏處理了一下強制反傳,就是設置一下哪些blob需要強制反傳,哪些不需要。

  // In the end, all remaining blobs are considered output blobs.
  for (set<string>::iterator it = available_blobs.begin();
      it != available_blobs.end(); ++it) {
    LOG_IF(INFO, Caffe::root_solver())
        << "This network produces output " << *it;
    net_output_blobs_.push_back(blobs_[blob_name_to_idx[*it]].get());
    net_output_blob_indices_.push_back(blob_name_to_idx[*it]);
  }

然後又弄了一個做 net_output_blobs_的vector,就相當於把這個網絡串聯起來,類似於一個單鏈表結構。

  ShareWeights();
  debug_info_ = param.debug_info();
  LOG_IF(INFO, Caffe::root_solver()) << "Network initialization done.";

接着從這個參數裏面抄權值,然後設置一下debug的狀態。
最後這個 Init函數就結束了。
這樣我們構造函數和 Init函數就結束了。

FilterNet 函數

  // Helpers for Init.
  /**
   * @brief Remove layers that the user specified should be excluded given the current
   *        phase, level, and stage.
   */
  static void FilterNet(const NetParameter& param,
      NetParameter* param_filtered);

這個函數是根據 state初始化所有層的一個函數。state包含了 phase, level stage

  NetState net_state(param.state());
  param_filtered->CopyFrom(param);

這裏拷貝了參數到 param_filtered這個變量裏面

param_filtered->clear_layer();

然後調用 clear_layer清除所有層,因爲它是剛剛開始初始化時候調用的,就需要把內存裏的一些垃圾信息刪掉。

for (int i = 0; i < param.layer_size(); ++i)

這裏循環遍歷所有層,然後
加載每一層的參數和名字

    const LayerParameter& layer_param = param.layer(i);
    const string& layer_name = layer_param.name();

檢查每一層的 StateMeetsRule

    bool layer_included = (layer_param.include_size() == 0);
    for (int j = 0; layer_included && j < layer_param.exclude_size(); ++j) {
      if (StateMeetsRule(net_state, layer_param.exclude(j), layer_name)) {
        layer_included = false;
      }
    }

這個StateMeetsRule屬於級聯網絡才用得到的開關,我們先跳過這一步。
然後開始copy layer

    if (layer_included) {
      param_filtered->add_layer()->CopyFrom(layer_param);
    }

之前不是把layer都clear了嗎,現在就把從param裏讀的參數添加給layer。

Forward 函數

  /**
   * @brief Run Forward and return the result.
   *
   */
  const vector<Blob<Dtype>*>& Forward(Dtype* loss = NULL);

這個是 NetForward函數,不是 LayerForward函數,NetForward函數是指揮每一個 Layer往哪個地方去傳。

template <typename Dtype>
const vector<Blob<Dtype>*>& Net<Dtype>::Forward(Dtype* loss) {
  if (loss != NULL) {
    *loss = ForwardFromTo(0, layers_.size() - 1);
  } else {
    ForwardFromTo(0, layers_.size() - 1);
  }
  return net_output_blobs_;
}

這裏判斷了一下需不需要 loss,如果不需要 loss
直接走這個 ForwardFromTo這個函數

ForwardFromTo 函數

我們可以進來看一下

for (int c = 0; c < before_forward_.size(); ++c) {
      before_forward_[c]->run(i);
    }
    Dtype layer_loss = layers

首先讓 before_forward_的這些步驟先走,因爲他們的名字就是在前傳之前的步驟,他們走完之後就開始走 layer的步驟

    Dtype layer_loss = layers_[i]->Forward(bottom_vecs_[i], top_vecs_[i]);
    loss += layer_loss;

然後 layer挨個走完 Forward之後,有個 loss把每一步額的 loss加起來。
這個走完之後就是 after_forward

    for (int c = 0; c < after_forward_.size(); ++c) {
      after_forward_[c]->run(i);
    }

也是顧名思義,所以他們在 forward之後也走了一遍。
最後把總的 loss返回就可以了。

return loss;

因爲這個是net的 Forward函數,他就是一個大的框架,起的是串聯的作用,所以比較簡單。

ForwardPrefilled 函數

他的註釋上寫了,使用 Forward代替

  /// @brief DEPRECATED; use Forward() instead.
  const vector<Blob<Dtype>*>& ForwardPrefilled(Dtype* loss = NULL) {
    LOG_EVERY_N(WARNING, 1000) << "DEPRECATED: ForwardPrefilled() "
        << "will be removed in a future version. Use Forward().";
    return Forward(loss);
  }

而且他的實現也就是直接調用的 Forward函數,所以這個函數實際上是一個比較老的函數,他只是留下了一個接口而已,以後都不會再用到了。

ForwardFromTo 函數

  /**
   * The From and To variants of Forward and Backward operate on the
   * (topological) ordering by which the net is specified. For general DAG
   * networks, note that (1) computing from one layer to another might entail
   * extra computation on unrelated branches, and (2) computation starting in
   * the middle may be incorrect if all of the layers of a fan-in are not
   * included.
   */
  Dtype ForwardFromTo(int start, int end);

這個函數註釋上就寫的很明白了,就是指定從某一層到某一層的一個函數,與之類似的還有 ForwardFrom ForwardTo。都只是參數不同而已。

Forward 函數

這裏是另外的一個 Forward函數

  /// @brief DEPRECATED; set input blobs then use Forward() instead.
  const vector<Blob<Dtype>*>& Forward(const vector<Blob<Dtype>* > & bottom,
      Dtype* loss = NULL);

可以看到,他把 loss放在了參數裏面,這個也是一個比較老的實現方法了,現在也都是用之前哪個 Forward函數來實現了。

template <typename Dtype>
const vector<Blob<Dtype>*>& Net<Dtype>::Forward(
    const vector<Blob<Dtype>*> & bottom, Dtype* loss) {
  LOG_EVERY_N(WARNING, 1000) << "DEPRECATED: Forward(bottom, loss) "
      << "will be removed in a future version. Use Forward(loss).";
  // Copy bottom to net bottoms
  for (int i = 0; i < bottom.size(); ++i) {
    net_input_blobs_[i]->CopyFrom(*bottom[i]);
  }
  return Forward(loss);
}

以前是會把 bottom都傳進來的,而現在 bottom是在初始化的時候就加載了,所以不用傳了。但是保留了原來的接口,所以主頁裏還是先複製一下它傳進來的 bottom,然後再 Forward

ClearParamDiffs 函數

  /**
   * @brief Zeroes out the diffs of all net parameters.
   *        Should be run before Backward.
   */
  void ClearParamDiffs();

他說了,這裏是把所有的參數都清成0,這個必須得在反傳之前運行。

Backward 函數

反傳函數其實和前傳是一樣的,因爲這個是net的反傳,它只是一個架構的作用

BackwardFromTo(layers_.size() - 1, 0);

可以看到,它第一步就是調用這個 BackwardFromTo函數,實際上和前傳是一樣的。

  if (debug_info_) {
    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 << ")";
  }
}

然後下面這些都是需要debug的一些參數,比方說各個data,各個diff,還有第二範式等等。

BackwardFromTo 函數

template <typename Dtype>
void Net<Dtype>::BackwardFromTo(int start, int end) {
  CHECK_GE(end, 0);
  CHECK_LT(start, layers_.size());
  for (int i = start; i >= end; --i) {
    for (int c = 0; c < before_backward_.size(); ++c) {
      before_backward_[c]->run(i);
    }
    if (layer_need_backward_[i]) {
      layers_[i]->Backward(
          top_vecs_[i], bottom_need_backward_[i], bottom_vecs_[i]);
      if (debug_info_) { BackwardDebugInfo(i); }
    }
    for (int c = 0; c < after_backward_.size(); ++c) {
      after_backward_[c]->run(i);
    }
  }
}

這個 BackwardFromTo函數和前傳是一樣的,設置一個起點,設置一個終點,然後運行 before_backward_,完了運行 Backward,最後運行 after_backward_

Reshape 函數

  /**
   * @brief Reshape all layers from bottom to top.
   *
   * This is useful to propagate changes to layer sizes without running
   * a forward pass, e.g. to compute output feature size.
   */
  void Reshape();

這裏是net的Reshape,不是blob的Reshape。這裏說Reshape會改變所有的layer的 top bottom的shape,也就是說你你前傳的時候如果就沒有改變這個layer的大小的話就會調用這個函數。

template <typename Dtype>
void Net<Dtype>::Reshape() {
  for (int i = 0; i < layers_.size(); ++i) {
    layers_[i]->Reshape(bottom_vecs_[i], top_vecs_[i]);
  }
}

可以看到,它的實現非常簡單,就是遍歷了所有layer,每個layer都Reshape了一下。
因爲layer的Reshape是調整了一下top的形狀,然後給他分配緩存空間。

ForwardBackward 函數

這個函數就是 ForwardBackward結合起來

  Dtype ForwardBackward() {
    Dtype loss;
    Forward(&loss);
    Backward();
    return loss;
  }

最後他也會返回一個前傳得到的loss,這一步是實現好了的,可以方便的在訓練時候去調它。

Update 函數

  /// @brief Updates the network weights based on the diff values computed.
  void Update();

這個 Updata函數是根據計算的diff來更新網絡的權重

template <typename Dtype>
void Net<Dtype>::Update() {
  for (int i = 0; i < learnable_params_.size(); ++i) {
    learnable_params_[i]->Update();
  }
}

它調用的是 learnable_params_Update,而這個learnable_params_UpdateBlob裏的函數,他就是計算權重的一個函數。

ShareWeights 函數

  /**
   * @brief Shares weight data of owner blobs with shared blobs.
   *
   * Note: this is called by Net::Init, and thus should normally not be
   * called manually.
   */
  void ShareWeights();

這個函數是從參數裏面讀取那些權重,這個函數只在 Net::Init裏調一次,所以不應該手動去調用它。

ShareTrainedLayersWith 函數

  /**
   * @brief For an already initialized net, implicitly copies (i.e., using no
   *        additional memory) the pre-trained layers from another Net.
   */
  void ShareTrainedLayersWith(const Net* other);

它的註釋是說從已經初始化了的網絡,隱式的去拷貝,不佔用內存,也就內存映射的意思,其實就是加載一個pretrain的模型。

Layer<Dtype>* source_layer = other->layers()[i].get();

可以看到,它的指針指向了傳進來的那個layer,這個意思就是內存映射的意思。

copy 函數

// For an already initialized net, CopyTrainedLayersFrom() copies the already
  // trained layers from another net parameter instance.
  /**
   * @brief For an already initialized net, copies the pre-trained layers from
   *        another Net.
   */
  void CopyTrainedLayersFrom(const NetParameter& param);
  void CopyTrainedLayersFrom(const string& trained_filename);
  void CopyTrainedLayersFromBinaryProto(const string& trained_filename);
  void CopyTrainedLayersFromHDF5(const string& trained_filename);

這些都是拷貝的函數,就是從文件裏面拷貝一個網絡到對象裏面。

toProto 函數

  /// @brief Writes the net to a proto.
  void ToProto(NetParameter* param, bool write_diff = false) const;
  /// @brief Writes the net to an HDF5 file.
  void ToHDF5(const string& filename, bool write_diff = false) const;

這些是把網絡儲存到文件中的兩個函數,一個是proto一個是HDF5格式

剩下的函數都是返回成員變量的一些函數了,比方說返回名字等等。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章