【caffe源码研究】第四章:完整案例源码篇(2) :LeNet初始化训练网络

来源:互联网 发布:松竹梅清酒 知乎 编辑:程序博客网 时间:2024/04/30 05:07

一、Solver到Net

SGDSolver的构造函数中主要执行了其父类Solver的构造函数,接着执行Solver::Init()函数,在Init()中,有两个函数值得注意:InitTrainNet()InitTestNets()分别初始化训练网络和测试网络。

(1). InitTrainNet

首先,ReadNetParamsFromTextFileOrDie(param_.NET(), &net_param)param_.Net()(即examples/mnist/lenet_train_test.prototxt)中的信息读入net_param
其次,net_.reset(new Net<Dtype>(net_param))重新构建网络,调用Net的构造方法。
然后,在构造方法中执行Net::init(),开始正式创建网络。其主要代码如下:

 template <typename Dtype>    void Net<Dtype>::Init(const NetParameter& in_param) {    ...      for (int layer_id = 0; layer_id < param.layer_size(); ++layer_id) {        // Setup layer.        const LayerParameter& layer_param = param.layer(layer_id);        // 在这里创建网络层        layers_.push_back(LayerRegistry<Dtype>::CreateLayer(layer_param));        // 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];        }        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);        }     ...      // 在这里配置网络层      layers_[layer_id]->SetUp(bottom_vecs_[layer_id], top_vecs_[layer_id]);      ...     }    for (int param_id = 0; param_id < num_param_blobs; ++param_id) {      AppendParam(param, layer_id, param_id);    }    ...    }

说明:

  1. Lenet5在caffe中共有9层,即param.layer_size()==9,以上代码每一次for循环创建一个网络层
  2. 每层网络是通过LayerRegistry::CreateLayer()创建的,类似与Solver的创建
  3. 14行Net::AppendBottom(),对于layer_id这层,从Net::blob_中取出blob放入该层对应的bottom_vecs_[layer_id]中
  4. 20行Net::AppendTop(),对于layer_id这层,创建blob(未包含数据)并放入Net::blob_中
  5. AppendParam中把每层网络的训练参数与网络变量learnable_params_绑定,在lenet中,只有conv1,conv2,ip1,ip2四层有参数,每层分别有参数与偏置参数两项参数,因而learnable_params_的size为8.

(2). LayerRegistry::CreateLayer

工厂模式new出网络层对象

(3). Layer::SetUp

void SetUp(const vector<Blob<Dtype>*>& bottom,   const vector<Blob<Dtype>*>& top) { InitMutex(); CheckBlobCounts(bottom, top); //每层进行配置 LayerSetUp(bottom, top); //修改输出数据的维数(即top_blob的维数)等 //关注数据维数的应关注此函数 Reshape(bottom, top); //设置损失权重 SetLossWeights(top);}

其中,Reshape函数中通过compute_output_shape计算输出blob的函数,

二、训练网络结构

序 Layer layer Type Bottom Blob Top Blob Blob Shape 1 minst Data data&&label 64 1 28 28 (50176) && 64 (64) 2 conv1 Convolution data conv1 64 20 24 24 (737280) 3 pool1 Pooling conv1 pool1 64 20 12 12 (184320) 4 conv2 Convolution pool1 conv2 64 50 8 8 (204800) 5 pool2 Pooling conv2 pool2 64 50 4 4 (51200) 6 ip1 InnerProduct pool2 ip1 64 500 (32000) 7 relu1 ReLU ip1 ip1(in-place) 64 500 (32000) 8 ip2 InnerProduct ip1 ip2 64 10 (640) 9 loss SoftmaxWithLoss ip2&&label loss (1)

注:Top Blob Shape格式为:BatchSize,ChannelSize,Height,Width(Total Count)

网络结构如图所示:

这里写图片描述

三、 第一层:Data Layer

(1). protobuff定义

训练网络的第一层protobuff定义为:

layer {  name: "mnist"  type: "Data"  top: "data"  top: "label"  include {    phase: TRAIN  }  transform_param {    scale: 0.00390625  }  data_param {    source: "examples/mnist/mnist_train_lmdb"    batch_size: 64    backend: LMDB  }}

(2). 函数LayerRegistry::CreateLayer

第1节中代码第一次通过调用LayerRegistry::CreateLayer()创建了DataLayer类.
调用DataLayer()的构造函数,依次执行的顺序为其基类构造函数:Layer()、BaseDataLayer()、InternalThread()、BasePrefetchingDataLayer()、及DataLayer()。

其中,值得注意的是DataLayer(),在调用基类构造函数BasePrefetchingDataLayer()之后,对 DataReader reader_ 进行赋值,在该DataLayer对象中维护了一个DataReader对象reader_,其作用是添加读取数据任务至,一个专门读取数据库(examples/mnist/mnist_train_lmdb)的线程(若还不存在该线程,则创建该线程),此处一共取出了4*64个样本至BlockingQueue<Datum*> DataReader::QueuePair::full_

template <typename Dtype>DataLayer<Dtype>::DataLayer(const LayerParameter& param)  : BasePrefetchingDataLayer<Dtype>(param),    reader_(param) {}

(3). 函数Layer::SetUp

此处按程序执行顺序值得关注的有:
DataLayer::DataLayerSetUp中根据DataReader中介绍的读取的数据中取出一个样本推测blob的形状
BasePrefetchingDataLayer::LayerSetUp如下代码prefetch_[i].data_.mutable_cpu_data()用到了涉及到gpu、cpu间复制数据的问题.

 // Before starting the prefetch thread, we make cpu_data and gpu_data // calls so that the prefetch thread does not accidentally make simultaneous // cudaMalloc calls when the main thread is running. In some GPUs this // seems to cause failures if we do not so. for (int i = 0; i < PREFETCH_COUNT; ++i) {   prefetch_[i].data_.mutable_cpu_data();   if (this->output_labels_) {     prefetch_[i].label_.mutable_cpu_data();   } }

BasePrefetchingDataLayer类继承了InternalThread,BasePrefetchingDataLayer<Dtype>::LayerSetUp中通过调用StartInternalThread()开启了一个新线程,从而执行BasePrefetchingDataLayer::InternalThreadEntry

BasePrefetchingDataLayer::InternalThreadEntry关键代码如下,其中load_batch(batch)为,从BlockingQueue<Datum*> DataReader::QueuePair::full_(包含从数据库读出的数据)中读取一个batch_size的数据到BlockingQueue<Batch<Dtype>*> BasePrefetchingDataLayer::prefetch_full_中。由于该线程在prefetch_free_为空时将挂起等待(PREFETCH_COUNT=3),prefetch_full_中用完的Batch将放回prefetch_free_中。该线程何时停止?

    while (!must_stop()) {      Batch<Dtype>* batch = prefetch_free_.pop();      load_batch(batch);#ifndef CPU_ONLY      if (Caffe::mode() == Caffe::GPU) {        batch->data_.data().get()->async_gpu_push(stream);        CUDA_CHECK(cudaStreamSynchronize(stream));      }#endif      prefetch_full_.push(batch);    }

关于线程的总结:

  • 此外一共涉及到两个线程,分别为都是继承了InnerThread的BasePrefetchingDataLayer(DataLayer)类和DataReader中的Body类
  • Body为面向数据库的线程,不断从某个数据库中读出数据,存放至缓存为队列+ DataReader::QueuePair::BlockingQueue<Datum*>,一般保存4*64个单位数据,单位为Datum
  • BasePrefetchingDataLayer为面向网络的线程,从Body的缓存中不断读取数据。BasePrefetchingDataLayer的缓存为队列BlockingQueue<Batch*>,一般存放3个单位的数据,单位为Batch
static const int PREFETCH_COUNT = 3;Batch<Dtype> prefetch_[PREFETCH_COUNT];BlockingQueue<Batch<Dtype>*> prefetch_free_;BlockingQueue<Batch<Dtype>*> prefetch_full_;template <typename Dtype>BasePrefetchingDataLayer<Dtype>::BasePrefetchingDataLayer(    const LayerParameter& param)    : BaseDataLayer<Dtype>(param),      prefetch_free_(), prefetch_full_() {  for (int i = 0; i < PREFETCH_COUNT; ++i) {    prefetch_free_.push(&prefetch_[i]);  }}prefetch_full_与prefetch_free_中的元素由prefetch_提供

四、第二层:Convolution Layer

(1). protobuff定义

layer {  name: "conv1"  type: "Convolution"  bottom: "data"  top: "conv1"  param {    lr_mult: 1  }  param {    lr_mult: 2  }  convolution_param {    num_output: 20    kernel_size: 5    stride: 1    weight_filler {      type: "xavier"    }    bias_filler {      type: "constant"    }  }}

不像DataLayer 直接执行的是构造函数,此时执行的是GetConvolutuionLayer(),然后调用ConvolutionLayer().

(2). LayerSetUp

Layer::SetUp中,调用了ConvolutionLayer的基类BaseConvolutionLayerLayerSetUp及Reshape函数,该类的主要成员变量如下:

/** * @brief Abstract base class that factors out the BLAS code common to *        ConvolutionLayer and DeconvolutionLayer. */template <typename Dtype>class BaseConvolutionLayer : public Layer<Dtype> { public:  explicit BaseConvolutionLayer(const LayerParameter& param)      : Layer<Dtype>(param) {}  virtual void LayerSetUp(const vector<Blob<Dtype>*>& bottom,      const vector<Blob<Dtype>*>& top);  virtual void Reshape(const vector<Blob<Dtype>*>& bottom,      const vector<Blob<Dtype>*>& top); ...  /// @brief The spatial dimensions of a filter kernel.  Blob<int> kernel_shape_;  /// @brief The spatial dimensions of the stride.  Blob<int> stride_;  /// @brief The spatial dimensions of the padding.  Blob<int> pad_;  /// @brief The spatial dimensions of the dilation.  Blob<int> dilation_;  /// @brief The spatial dimensions of the convolution input.  Blob<int> conv_input_shape_;  /// @brief The spatial dimensions of the col_buffer.  vector<int> col_buffer_shape_;  /// @brief The spatial dimensions of the output.  vector<int> output_shape_;  const vector<int>* bottom_shape_;...};

说明:

  • LayerSetUp函数中,主要是初始化了kernel_shape_、stride_、pad_、dilation_以及初始化网络参数,并存放与Layer::blobs_中。
  • Reshape函数中,conv_input_shape_、bottom_shape_等

五、第三层:Pooling Layer

(2). protobuff定义

layer {  name: "pool1"  type: "Pooling"  bottom: "conv1"  top: "pool1"  pooling_param {    pool: MAX    kernel_size: 2    stride: 2  }}

(2). Layer::SetUp

通过调用虚函数LayerSetUp及Reshape对以下成员变量进行初始化

/** * @brief Pools the input image by taking the max, average, etc. within regions. * * TODO(dox): thorough documentation for Forward, Backward, and proto params. */template <typename Dtype>class PoolingLayer : public Layer<Dtype> { ....  int kernel_h_, kernel_w_;  int stride_h_, stride_w_;  int pad_h_, pad_w_;  int channels_;  int height_, width_;  int pooled_height_, pooled_width_;  bool global_pooling_;  Blob<Dtype> rand_idx_;  Blob<int> max_idx_;};

六、 第四层、第五层

基本同第二层、第三层

七、 第六层:InnerProduct Layer

(1). protobuff定义

layer {  name: "ip1"  type: "InnerProduct"  bottom: "pool2"  top: "ip1"  param {    lr_mult: 1  }  param {    lr_mult: 2  }  inner_product_param {    num_output: 500    weight_filler {      type: "xavier"    }    bias_filler {      type: "constant"    }  }}

(2). Layer::SetUp

/** * @brief Also known as a "fully-connected" layer, computes an inner product *        with a set of learned weights, and (optionally) adds biases. * * TODO(dox): thorough documentation for Forward, Backward, and proto params. */template <typename Dtype>class InnerProductLayer : public Layer<Dtype> { ...  int M_;  int K_;  int N_;  bool bias_term_;  Blob<Dtype> bias_multiplier_;};

说明:

  • N_为输出大小,即等于protobuff中定义的num_output
  • K_为输入大小,对于该层Bottom Blob形状为(N, C, H, W),N为batch_size,K_=C*H*W,M_=N。其中只有C、H、W跟内积相关

八、SoftmaxWithLoss Layer

(1). protobuff定义

layer {  name: "loss"  type: "SoftmaxWithLoss"  bottom: "ip2"  bottom: "label"  top: "loss"}

九、SoftmaxWithLoss Layer

(1). protobuff定义

layer {  name: "loss"  type: "SoftmaxWithLoss"  bottom: "ip2"  bottom: "label"  top: "loss"}

(2). Layer::SetUp

值得注意的是:

  • 类SoftmaxWithLossLayer包含类SoftmaxLayer的实例
    shared_ptr<Layer<Dtype> > softmax_layer_
  • softmax_layer_在LayerSetUp中赋值。
  • 此函数内调用Layer::SetLossWeights初始化了该层的Top Blob(loss)
  • 成员变量prob_作为Softmaxlayer的top blob
  • bottom blob[0]作为softmaxlayer的bottom blob
  • 所以经过softmaxlayer计算之后,得出64*10(每个样本的每个类别上的概率)存放在prob_中

两个类间的关系如下图:
这里写图片描述

0 0
原创粉丝点击