Net的网络层的构建(源码分析)
概述
网络层的构建是在Net<Dtype>::Init()函数中完成的,构建的流程图如下所示:
从图中可以看出网络层的构建分为三个主要部分:解析网络文件、开始建立网络层、网络层需要参与计算的位置。
解析网络文件
该部分主要有两个函数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)中的不符合规则的层去掉。例如:在caffe的examples/mnist中的lenet网络中,如果只是用于网络的前向,则需要将包含train的数据层去掉。
1 /* 2 *调用InsertSplits()函数,对于底层的一个输出blob对应多个上层的情况, 3 *则要在加入分裂层,形成新的网络。 4 *函数从filtered_param读入新网络到param 5 **/ 6 InsertSplits(filtered_param, ¶m);
InsertSplits()函数的作用是对于底层的一个输出blob对应多个上层的情况,则要在加入分裂层,形成新的网络。这么做的主要原因是多个层反传给该blob的梯度需要累加。例如:LeNet网络中的数据层的top label blob对应两个输入层,分别是accuracy层和loss层,那么需要在数据层在插入一层。如下图:
建立网络层
该部分重要的函数有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结尾表示这是一个会产生loss的Layer,其他的Layer只是单纯的用于中间计算,同时每一层的loss值就是所有输出top blob的loss值的和。到此当前层的结构建立完成。经过多次循环,就可以构建整个网络。
确定网络层需要计算的blob
该部分的作用是确定哪些层或哪些层的blob需要参与计算,比如前向时需要确定哪些层的blob需要计算loss,后向时确定哪些层的blob需要计算diff。一个layer是否需要backward computation,主要依据两个方面:
(1)该layer的top blob 是否参与loss的计算;
(2)该layer的bottom 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,对于某些有参数的层,例如:卷基层、全连接层有weight和bias。该函数主要是修改和参数有关的变量,实际的层参数的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 }