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格式

剩下的函数都是返回成员变量的一些函数了,比方说返回名字等等。

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