您的位置:首页 > 理论基础 > 计算机网络

【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的函数,

二、训练网络结构

Layerlayer TypeBottom BlobTop BlobBlob Shape
1minstDatadata&&label64 1 28 28 (50176) && 64 (64)
2conv1Convolutiondataconv164 20 24 24 (737280)
3pool1Poolingconv1pool164 20 12 12 (184320)
4conv2Convolutionpool1conv264 50 8 8 (204800)
5pool2Poolingconv2pool264 50 4 4 (51200)
6ip1InnerProductpool2ip164 500 (32000)
7relu1ReLUip1ip1(in-place)64 500 (32000)
8ip2InnerProductip1ip264 10 (640)
9lossSoftmaxWithLossip2&&labelloss(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
的基类
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_中

两个类间的关系如下图:

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: