【caffe源码研究】第四章:完整案例源码篇(2) :LeNet初始化训练网络
2017-01-06 11:18
387 查看
一、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); } ... }
说明:
Lenet5在caffe中共有9层,即param.layer_size()==9,以上代码每一次for循环创建一个网络层
每层网络是通过LayerRegistry::CreateLayer()创建的,类似与Solver的创建
14行Net::AppendBottom(),对于layer_id这层,从Net::blob_中取出blob放入该层对应的bottom_vecs_[layer_id]中
20行Net::AppendTop(),对于layer_id这层,创建blob(未包含数据)并放入Net::blob_中
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) |
网络结构如图所示:
三、 第一层: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的基类
BaseConvolutionLayer的
LayerSetUp及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_中
两个类间的关系如下图:
相关文章推荐
- 【caffe源码研究】第四章:完整案例源码篇(3) :LeNet初始化测试网络
- 【caffe源码研究】第四章:完整案例源码篇(1) :LeNetSolver初始化
- 【caffe源码研究】第四章:完整案例源码篇(4) :LeNet前向过程
- 【caffe源码研究】第四章:完整案例源码篇(5) :LeNet反向过程
- (Caffe,LeNet)初始化训练网络(三)
- 【caffe源码研究】第三章:源码篇(9) :DataLayer
- 【caffe源码研究】第三章:源码篇(1) :caffe整体架构
- 【caffe源码研究】第三章:源码篇(5) :Net
- Caffe框架源码剖析(2)—训练网络
- (Caffe,LeNet)网络训练流程(二)
- Caffe源码阅读(粗读)--网络训练
- 【caffe源码研究】第三章:源码篇(13) :损失层
- 【caffe源码研究】第三章:源码篇(7) :Layer种类
- (Caffe,LeNet)初始化测试网络(四)
- (Caffe,LeNet)网络训练流程(二)
- 【caffe源码研究】第三章:源码篇(12) :激活函数层
- 【caffe源码研究】第三章:源码篇(3) :工厂模式
- Ubuntu 14.04 64位机上用Caffe+MNIST训练Lenet网络操作步骤
- Caffe源码阅读(粗读)--网络初始化
- 【caffe源码研究】第三章:源码篇(4) :Solver