【caffe解读】caffe从数学公式到代码实现3-shape相关类

本文首发于微信公众号《与有三学AI》

[caffe解读] caffe从数学公式到代码实现3-shape相关类​

接着上一篇说,本篇开始读layers下面的一些与blob shape有关的layer,比如flatten_layer.cpp等,具体包括的在下面;

flatten_layer.cpp

slice_layer.cpp

split_layer.cpp

tile_layer.cpp

concat_layer.cpp

reduction_layer.cpp

eltwise_layer.cpp

crop_layer.cpp

pooling_layer.cpp

scale_layer.cpp

conv与deconv虽然也与shape有关,但是由于比较复杂,我们以后专门留一篇来说。下面这些层,如果你没有仔细读过源码,那么建议你来读一读,因为有很多并没有想象中那么简单

1 flatten_layer.cpp

Flatten layer的作用是把一个维度为n * c * h * w的输入转化为一个维度为 n* (c*h*w)的向量输出,虽然在我们看来不一样,但是在blob看来,输入和输出的数据存储是没有差异的,只是记录的shape信息不同。所以forward和backward只是数据拷贝

template

void FlattenLayer::Forward_cpu(const vector& bottom,

const vector& top) { top[0]->ShareData(*bottom[0]);

}

template

void FlattenLayer::Backward_cpu(const vector& top,

const vector& propagate_down, const vector*>& bottom) {

bottom[0]->ShareDiff(*top[0]);

}

2 slice_layer.cpp

Slice layer 的作用是将bottom按照需要分解成多个tops,它的定义如下:

message SliceParameter {

// By default, SliceLayer

concatenates blobs along the "channels" axis (1).

optional int32 axis = 3 [default = 1];

repeated uint32 slice_point = 2;

// DEPRECATED: alias for "axis" -- does not support negative indexing.

optional uint32 slice_dim = 1 [default = 1];

}

默认axis是1,也就是blob的第1个维度,即channel通道,这也是我经常使用的,一般用于有多种label时分离label。

前向反向时小心计算好offset就行,有兴趣可以去看。

3 split_layer.cpp

它的作用是将输入复制多份。

Forward: 在前向的时候,top[i]=bottom[0],直接赋值。

template

void SplitLayer::Forward_cpu(const vector*>& bottom,

const vector*>& top) {

for (int i = 0; i < top.size(); ++i) {

  top[i]->ShareData(*bottom[0]);

}

}

Backward: 在反向的时候,需要将所有top的diff叠加起来。

template

void SplitLayer::Backward_cpu(const vector*>& top,

const vector& propagate_down, const vector*>& bottom) {

if (!propagate_down[0]) { return; }

if (top.size() == 1) { caffe_copy(count_, top[0]->cpu_diff(), bottom[0]->mutable_cpu_diff());

return;

}

caffe_add(count_, top[0]->cpu_diff(), top[1]->cpu_diff(),

bottom[0]->mutable_cpu_diff());

// Add remaining top blob diffs.

for (int i = 2; i < top.size(); ++i) {

const Dtype* top_diff = top[i]->cpu_diff();

Dtype* bottom_diff = bottom[0]->mutable_cpu_diff();

caffe_axpy(count_, Dtype(1.), top_diff, bottom_diff);

}

}

4 tile_layer.cpp

数学定义:

将数据按照某个维度扩大n倍,看下面forward源码,将bottom_data的前inner_dim_个数据复制了tiles份,反向时将对应diff累加回去即可。

Forward,backward与split layer很像。

void TileLayer::Forward_cpu(

  const vector*>& bottom, const vector*>& top) {

const Dtype* bottom_data = bottom[0]->cpu_data();

Dtype* top_data = top[0]->mutable_cpu_data();

for (int i = 0; i < outer_dim_; ++i) {

  for (int t = 0; t < tiles_; ++t) {

    caffe_copy(inner_dim_, bottom_data, top_data);

    top_data += inner_dim_;

  }

  bottom_data += inner_dim_;

}

}

5 concat_layer.cpp

与slice_layer是反向操作,将多个bottom blob合并成一个top_data,forward,backward计算好index就行。

6 reduction_layer.cpp

顾名思义,这是一个降维的层。

数学定义:

message ReductionParameter {

enum ReductionOp {

SUM = 1;

ASUM = 2;

SUMSQ = 3;

MEAN = 4;

}

optional ReductionOp operation = 1 [default = SUM]; // reduction operation The first axis to reduce to a scalar -- may be negative to index from the end (e.g., -1 for the last axis). (Currently, only reduction along ALL "tail" axes is supported;

reduction of axis M through N, where N < num_axes - 1, is unsupported.)

Suppose we have an n-axis bottom Blob with shape:

(d0, d1, d2, ..., d(m-1), dm, d(m+1), ..., d(n-1)).

If axis == m, the output Blob will have shape  (d0, d1, d2, ..., d(m-1)),

and the ReductionOp operation is performed (d0 * d1 * d2 * ... * d(m-1))

times, each including (dm * d(m+1) * ... * d(n-1)) individual data.

If axis == 0 (the default), the output Blob always has the empty shape

(count 1), performing reduction across the entire input often useful for creating new loss functions.

optional int32 axis = 2 [default = 0];

optional float coeff = 3 [default = 1.0]; // coefficient for output

}

从上面可以看出,reduct有4类操作,sum,mean,asum,sumsq,分别是求和,求绝对值和,求平方和与平均。它会从axis这个维度开始去降维,比如当axis=0,就是从0开始将所有blob降维,最终会得到一个标量数,常用于loss。

在reshape函数中可以看到,

axis_ = bottom[0]->CanonicalAxisIndex(this->layer_param_.reduction_param().axis());

vector top_shape(bottom[0]->shape().begin(), bottom[0]->shape().begin() + axis_);

top[0]->Reshape(top_shape);

num_ = bottom[0]->count(0, axis_);

dim_ = bottom[0]->count(axis_);

CHECK_EQ(num_, top[0]->count());

通过reduction_param().axis())设置维度之后,top[0]的元素数目就是num_ =bottom[0]->count(0, axis_);我们假设输入blob是10*3*224*224,如果axis=0,那么top[0]=10*1*1*1;如果axis=1,那么top[0]=10*3*1*1,以此类推。

Forward和Backward对应这4个操作去看代码即可,只要知道反向的时候,top的每一个元素的梯度会反传给bottom的多个元素。

7 eltwise_layer.cpp

eltwise是一个有多个bottom输入,一个top输出的layer,对逐个的元素进行操作,所bottom[i]和top[j]的大小都是相等的。Eltwise参数有相乘PROB,相加SUM,求MAX。对于SUM操作,该层定义了 coeff 参数,用于调整权重。 对于PROB操作,设定了stable_prod_grad #[default = true ] 来选择是否渐进较慢的梯度计算方法,forward过程不需要说太多,而对于backward,有必要说一下。下面举prob操作的例子;


我们看相应函数,这只是内循环,实际上还有外循环。

case EltwiseParameter_EltwiseOp_PROD:

if (stable_prod_grad_) { bool initialized = false;

for (int j = 0; j < bottom.size(); ++j) {

if (i == j) { continue; }

if (!initialized) {

caffe_copy(count, bottom[j]->cpu_data(), bottom_diff);

initialized = true;

} else {

caffe_mul(count, bottom[j]->cpu_data(), bottom_diff,

bottom_diff);

}

}

} else {

caffe_div(count, top_data, bottom_data, bottom_diff);

}

caffe_mul(count, bottom_diff, top_diff, bottom_diff);

当stable_prod_grad = false时,直接对应了上面的式 top_data/bottom_data*bottom_diff,但是如果stable_prod_grad = true,差异在哪呢?反正我是没看出啥区别,只是为true时没有利用已经计算号的结果,计算更慢了。

8 crop_layer.cpp

crop layer改变blob的第2,3个维度,而不是改变前两个维度,也没有复杂的数学操作,所以只需要记录下offset即可,感兴趣还是去看源码。

9 pooling_layer.cpp

pooling layer想必大家都很熟悉了,caffe官方的有MAX,MEAN两种,还保留了一种random的没有实现。 Max和Mean的区别会在什么地方呢?主要就是max会存在一个mask,因为它要记录对top有贡献的那个元素,在梯度反传的时候,也只会反传到1个元素,而mean则会反传到r*r个元素,r就是滤波的半径

其他的倒是没有需要特别注意的地方,主要就是bottom到top到index到计算,细节处小心即可。

10 bnll_layer.cpp

数学定义:

就这么多。

11 scale_layer.cpp

scale这个layer绝对比你想象中复杂多。我们通常以为是这样就完了

其中a是一个标量,x是一个矢量,在caffe中就是blob,但是实际上a也可以是blob,它可以有如下尺寸,见scale参数的定义:

message ScaleParameter {

// The first axis of bottom[0] (the first input Blob) along which to apply, bottom[1] (the second input Blob). May be negative to index from the end (e.g., -1 for the last

axis).

// For example, if bottom[0] is 4D with shape 100x3x40x60, the output top[0] will have the same shape, and bottom[1] may have any of the following shapes (for the given value of axis):

(axis == 0 == -4) 100; 100x3; 100x3x40; 100x3x40x60

(axis == 1 == -3) 3; 3x40; 3x40x60

(axis == 2 == -2) 40; 40x60

(axis == 3 == -1) 60

// Furthermore, bottom[1] may have the empty shape (regardless of the value of "axis") a scalar multiplier.

optional int32 axis = 1 [default = 1];

// (num_axes is ignored unless just one bottom is given and the scale is a learned parameter of the layer. Otherwise, num_axes is determined by the number of axes by the second bottom.)

// The number of axes of the input (bottom[0]) covered by the scale

// parameter, or -1 to cover all axes of bottom[0] starting from `axis`.

// Set num_axes := 0, to

multiply with a zero-axis Blob: a scalar.

optional int32 num_axes = 2 [default = 1];

// (filler is ignored unless just one bottom is given and the scale is

// a learned parameter of the layer.)

// The initialization for the learned scale parameter.

// Default is the unit (1) initialization, resulting in the ScaleLayer

// initially performing the identity operation.

optional FillerParameter filler = 3;

// Whether to also learn a bias (equivalent to a ScaleLayer+BiasLayer, but

// may be more efficient). Initialized with bias_filler (defaults to 0).

optional bool bias_term = 4 [default = false];

optional FillerParameter bias_filler = 5;

}

从上面我们可以知道这些信息;

(1) scale_layer是输入输出可以都是1个,但是,输入可以是两个,也就是bottom[1]是scale,当没有bottom[1]时,就是通过一个标量参数来实现scale。

(2) scale可以有多种尺寸。从1维到4维。

上面举了例子,当输入x是100x3x40x60,scale blob可以是100; 100x3; 100x3x40; 100x3x40x60这几种尺寸,所以在forward,backward的时候,需要对上尺寸。

这一节看起来比较乱,就当读书笔记吧,只是有很多细节,真的需要自己去抠才知道坑在哪。

更多请移步

1,我的gitchat达人课

龙鹏的达人课

2,AI技术公众号,《与有三学AI》

[caffe解读] caffe从数学公式到代码实现4-认识caffe自带的7大loss​

3,以及摄影号,《有三工作室》

冯小刚说,“他懂我”

你可能感兴趣的:(【caffe解读】caffe从数学公式到代码实现3-shape相关类)