Net的网络层的构建(源码分析)

时间:2022-12-10 00:02:23

概述

网络层的构建是在Net<Dtype>::Init()函数中完成的,构建的流程图如下所示:

Net的网络层的构建(源码分析)

 

从图中可以看出网络层的构建分为三个主要部分:解析网络文件、开始建立网络层、网络层需要参与计算的位置。

解析网络文件

该部分主要有两个函数FilterNet()InsertSplits() 

1 void Net<Dtype>::Init(const NetParameter& in_param) {
2 CHECK(Caffe::root_solver() || root_net_)
3 << "root_net_ needs to be set for all non-root solvers";
4 // Set phase from the state.
5 phase_ = in_param.state().phase();
6 // Filter layers based on their include/exclude rules and
7 // the current NetState.
8 NetParameter filtered_param;
9 FilterNet(in_param, &filtered_param);

 FilterNet()的作用是模型参数文件(*.prototxt)中的不符合规则的层去掉。例如:在caffeexamples/mnist中的lenet网络中,如果只是用于网络的前向,则需要将包含train的数据层去掉。 

1 /*
2 *调用InsertSplits()函数,对于底层的一个输出blob对应多个上层的情况,
3 *则要在加入分裂层,形成新的网络。
4 *函数从filtered_param读入新网络到param
5 **/
6 InsertSplits(filtered_param, &param);

 InsertSplits()函数的作用是对于底层的一个输出blob对应多个上层的情况,则要在加入分裂层,形成新的网络。这么做的主要原因是多个层反传给该blob的梯度需要累加例如:LeNet网络中的数据层的top label blob对应两个输入层,分别是accuracy层和loss层,那么需要在数据层在插入一层。如下图:

Net的网络层的构建(源码分析)

 

建立网络层

 该部分重要的函数有CreateLayer()AppendBottom()AppendTop()SetUp() 

 1   ...............
2 //(很大的一个for循环)对每一层处理
3 for (int layer_id = 0; layer_id < param.layer_size(); ++layer_id) {//开始遍历所有层
4 ............
5 // Setup layer.
6 //param.layers(i)返回的是关于第当前层的参数:
7 const LayerParameter& layer_param = param.layer(layer_id);
8 if (share_from_root) {
9 ............
10 } else {
11 /*
12 *把当前层的参数转换为shared_ptr<Layer<Dtype>>,
13 *创建一个具体的层,并压入到layers_中
14 */
15 layers_.push_back(LayerRegistry<Dtype>::CreateLayer(layer_param));
16 }

 对于CreateLayer()函数,把解析的当前层调用CreatorRegistry类进行注册,从而获取到当前层。然后会调用AppendBottom()AppendTop()函数具体创建层结构。 

1 //下面开始产生当前层:分别处理bottom的blob和top的blob两个步骤
2 for (int bottom_id = 0; bottom_id < layer_param.bottom_size(); ++bottom_id) {
3 const int blob_id = AppendBottom(param, layer_id, bottom_id,
4 &available_blobs, &blob_name_to_idx);
5 need_backward |= blob_need_backward_[blob_id];
6 }

对于AppendBottom()函数,其作用是为该层创建bottom blob,由于网络是堆叠而成,即:当前层的输出 bottom是前一层的输出top blob,因此此函数并没没有真正的创建blob,只是在将前一层的指针压入到了bottom_vecs_中。

1 int num_top = layer_param.top_size();
2 for (int top_id = 0; top_id < num_top; ++top_id) {
3 AppendTop(param, layer_id, top_id, &available_blobs, &blob_name_to_idx);
4 ...............
5 }

 对于AppendBottom()函数,其作用是为该层创建top blob,该函数真正的new的一个blob的对象。并将top blob 的指针压入到top_vecs_中。经过这两个函数网络层创建出该层所有的输入、输出blob,接下来就是调用SetUp()函数,正式建立层结构,并为blob分配内存空间。

 1 //层已经连接完成,开始建立关系
2 if (share_from_root) {
3 // Set up size of top blobs using root_net_
4 const vector<Blob<Dtype>*>& base_top = root_net_->top_vecs_[layer_id];
5 const vector<Blob<Dtype>*>& this_top = this->top_vecs_[layer_id];
6 for (int top_id = 0; top_id < base_top.size(); ++top_id) {
7 this_top[top_id]->ReshapeLike(*base_top[top_id]);
8 }
9 } else {
10 layers_[layer_id]->SetUp(bottom_vecs_[layer_id], top_vecs_[layer_id]);
11 }
12
13 //SetUp()函数的具体内容
14 void SetUp(const vector<Blob<Dtype>*>& bottom, const vector<Blob<Dtype>*>& top) {
15 InitMutex();
16 CheckBlobCounts(bottom, top);
17 LayerSetUp(bottom, top);
18 Reshape(bottom, top);
19 SetLossWeights(top);
20 }

 对于SetUp()函数,包含了CheckBlobCounts()LayerSetUp()SetLossWeights()Reshape()等子函数,CheckBlobCounts()函数式读取Blob的数量,LayerSetUp()Reshape()是虚函数,会在相应的层中实现这两个函数,SetLossWeights(top)函数会把top(输出blob)loss weight进行初始化,loss weight是用来表不同Layer产生的loss的重要性,Layer名称中以Loss结尾表示这是一个会产生lossLayer,其他的Layer只是单纯的用于中间计算,同时每一层的loss值就是所有输出top blobloss值的和。到此当前层的结构建立完成。经过多次循环,就可以构建整个网络。

确定网络层需要计算的blob

该部分的作用是确定哪些层或哪些层的blob需要参与计算,比如前向时需要确定哪些层的blob需要计算loss,后向时确定哪些层的blob需要计算diff。一个layer是否需要backward computation,主要依据两个方面:

  (1)layertop blob 是否参与loss的计算;

  (2)layerbottom blob 是否需要backward computation,比如Data层一般就不需要backward computation

对于前向的过程,部分源码如下: 

 1     ..............
2 for (int param_id = 0; param_id < num_param_blobs; ++param_id) {
3 const ParamSpec* param_spec = (param_id < param_size) ?
4 &layer_param.param(param_id) : &default_param_spec;
5 const bool param_need_backward = param_spec->lr_mult() != 0;
6 need_backward |= param_need_backward;
7 layers_[layer_id]->set_param_propagate_down(param_id, param_need_backward);
8 }
9 for (int param_id = 0; param_id < num_param_blobs; ++param_id) {
10 ...........
11 AppendParam(param, layer_id, param_id);
12 }

 AppendParam()函数的作用是记录带有参数的层或者blob对于某些有参数的层,例如:卷基层、全连接层有weightbias。该函数主要是修改和参数有关的变量,实际的层参数的blob在上面提到的setup()函数中已经创建。对于后向的过程和前向类似,部分源码如下:

 1 if (param.force_backward()) {
2 for (int layer_id = 0; layer_id < layers_.size(); ++layer_id) {//迭代所有层
3 layer_need_backward_[layer_id] = true;//需要参与backward
4 for (int bottom_id = 0;
5 bottom_id < bottom_need_backward_[layer_id].size(); ++bottom_id) {//每一层下的需要计算diff的所有blob
6 bottom_need_backward_[layer_id][bottom_id] =
7 bottom_need_backward_[layer_id][bottom_id] ||
8 layers_[layer_id]->AllowForceBackward(bottom_id);
9 blob_need_backward_[bottom_id_vecs_[layer_id][bottom_id]] =
10 blob_need_backward_[bottom_id_vecs_[layer_id][bottom_id]] ||
11 bottom_need_backward_[layer_id][bottom_id];
12 }
13 for (int param_id = 0; param_id < layers_[layer_id]->blobs().size();
14 ++param_id) {//设置不需要计算参数的层
15 layers_[layer_id]->set_param_propagate_down(param_id, true);
16 }
17 }
18 }