Caffe Scale层解析

前段时间做了caffe的batchnormalization层的解析,由于整体的BN层实现在Caffe是分段实现的,因此今天抽时间总结下Scale层次,也会后续两个层做合并做下铺垫。

基本公式梳理

Scale层主要完成 \(top = alpha*bottom+ beta\)的过程,则层中主要有两个参数\(alpha\)与\(beta\),

求导会比较简单。

\[\frac{\partial y}{\partial x} = alpha ;\quad \frac{\partial y}{\partial alpha} = x;\quad \frac{\partial y}{\partial beta} = 1

\]

需要注意的是\(alpha\)与\(beta\)均为向量,针对输入的\(channels\)进行的处理,因此不能简单的认定为一个\(float\)的实数。

具体实现

该部分将结合源码实现解析\(scale\)层:

在Caffe proto中ScaleParameter中对Scale有如下几个参数:

axis [default = 1] ; 默认的处理维度

num_axes [default = 1] ; //在BN中可以忽略,主要决定第二个bottom

FillerParameter filler ; //两个FillerParameter即决定初始alpha和beta的填充方式。

//决定是否学习bias,如果不学习,则可以简化为alpha*x = y

optional bool bias_term = 4 [default = false];

FillerParameter bias_filler;

基本成员变量

// caffe的scale层实现+beta调用了bias层。。。。。。。。。。

shared_ptr> bias_layer_; /

vector*>bias_bottom_vec_;

vector bias_propagate_down_;

int bias_param_id_;

Blob sum_multiplier_;

Blobsum_result_;

Blob temp_;

int axis_;

int outer_dim_,inner_dim_,scale_dim_;

基本成员变量主要包含了Bias层的参数以及Scale层完成对应通道的标注工作。

基本成员函数

主要包含了LayerSetup,Reshape ,Forward和Backward ,内部调用的时候bias_term为true的时候会调用biasLayer的相关函数.

LayerSetup,层次的建立

template

void ScaleLayer::LayerSetUp(const vector*>& bottom,

const vector*>& top) {

const ScaleParameter param = this->layer_param_->scale_param();

if (bottom.size() == 1 && this->blobs_.size() > 0) {

//区分测试与训练,测试时 blobs-已经有值

}

else if(bottom.size() == 1){

// 考虑BN的scale 不需要考虑axes

axis_ = bottom[0]->CanonicalAxisIndex(param.axis());// 1 通道

const int num_axes = param.num_axes(); // 1

this->blobs_.resize(1);// alpha;

//这么大一串,实际就是blobs_[0].reset(new Blob(vector(C)));

const vector::const_iterator& shape_start =

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

const vector::const_iterator& shape_end =

(num_axes == -1) ? bottom[0]->shape().end() : (shape_start + num_axes);

vectorscale_shape(shape_start, shape_end);

this->blobs_[0].reset(new Blob(scale_shape));

FillerParameter filler_param(param.filler());

if (!param.has_filler()) { //没写明填充模式

filler_param.set_type("constant");

filler_param.set_value(1);

}

shared_ptr>filler(GetFiller(filler_param));

filler->Fill(this->blobs_[0].get());

}

// 处理需不需要bias

if (param.bias_term()) {

LayerParameter layer_param(this->layer_param_);

layer_param.set_type("Bias");

BiasParameter* bias_param = layer_param_.mutable_bias_param();

bias_param->set_axis(param.aixs());

if (bottom.size() > 1) {

bias_param->set_num_axes(bottom[1]->num_axes());

}

else{

bias_param->set_num_axes(param.num_axes());//bn层走下面

}

bias_param->mutable_filler()->CopyFrom(param.bias_filler());

bias_layer_ = LayerRegistry::CreateLayer(layer_param);

bias_bottom_vec_.resize(1);

bias_bottom_vec_[0] = bottom[0];

bias_layer_->Setup(bias_bottom_vec_,top);

bias_param_id = this->blobs_.size(); //1 alpha 此处增加个beta

this->blobs_.resize(bias_param_id_+1); // 2

this->blobs_[bias_param_id] = bias_layer_->blobs()[0];

bias_propagate_down_.resize(1,false);

}

this->param_propagate_down_.resize(this->blobs_.size(),true);

}

Scale层的一部分在完整BN中是不需要考虑的,完整BN中bottomSize为1,num_axes默认为1,blobs_[0]为长度为C的向量,bias需要调用caffe的bias层,所以会看着比较麻烦。

Reshape 调整输入输出与中间变量

Reshape层完成许多中间变量的size初始化

//Reshape操作

template

void ScaleLayer::Reshape(const vector*>& bottom,

const vector*>& top) {

const ScaleParameter param = this->layer_param_->scale_param();

Blob* scale = (bottom.size() > 1)?bottom[1]:this->blobs_[0].get();

axis_=(scale->num_axes()==0)?0:botom[0]->CanonicalAxisIndex(param.axis());

//这里做了下比较 bottom的NCHW axis_ = 1 则 C == C

CHECK_EQ(bottom[0]->shape(axis_) = scale->shape(0));

outer_dim_ = bottom[0]->count(0,axis_);// n

scale_dim = scale->count(); //c

inner_dim_ = bottom[0]->count(axis+1);// hw

if (bottom[0] == top[0]) {

// Layer得top和bottom同名 in-place computation

const bool scale_param = (bottom.size() == 1); //true

if (!scale_param || (scale_param && this->param_propagate_down_[0]) {

// 后面一个条件成立,需要backward

//防止修改top时,bottom改变,做临时,因为求导要用到原始的bottom-data

temp_.ReshapeLike(*bottom[0]);

}

}

else{

top[0]->ReshapeLike(*bottom[0]);//

}

//类似于bn的num-by—tran 保存中间的NC结果 NC*1*1*1

sum_result_.Reshape(vector(1,outer_dim_*scale_dim_));

const int sum_mult_size = std::max(outer_dim_,inner_dim_);

// 为什么不类似于BN做两个temp vector呢

sum_multiplier_.Reshape(vector(1,sum_mult_size));

if (sum_multiplier_.cpu_data()[sum_mult_size-1] != Dtype(1)) {

caffe_set(sum_mult_size,Dtype(1),sum_multiplier_.mutable_cpu_data());

}

if (bias_layer_) {

bias_bottom_vec_[0] = top[0];

bias_layer_->Reshape(bias_bottom_vec_,top);

}

}

Reshape操作同BN的基本相似,只不过此处只是新建了两个中间变量,sum_multiplier_和sum_result_.

Forward 前向计算

前向计算,在BN中国紧跟着BN的归一化输出,完成乘以alpha与+bias的操作,由于alpha与bias均为C的向量,因此需要先进行广播。

template

void ScaleLayer::Forward_cpu(

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

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

if (bottom[0] == top[0]) {

// 先进行一次临时拷贝复制

caffe_copy(bottom[0]->count(),bottom_data,temp_.mutable_cpu_data());

}

const Dtype* scale_data = (bottom.size() > 1)?bottom[1]:

this->blios_[0]->cpu_data();

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

// 这里的遍历实际上和广播的类似,一种是每次操作inner_dim个元素,一种是讲alpha

// 广播到整个feature_map,然后再调用一次cpu_scale

for (size_t n = 0; n < outer_dim_; n++) { // n

for (size_t d = 0; d < scale_dim_; d++) { //c

const Dtype factory = scale_data[d];// 取某一个通道的值

caffe_cpu_scale(inner_dim,factory,bottom_data,top_data);

top_data += inner_dim_;

bottom_data += inner_dim;

}

}

if (bias_layer_) {

bias_layer_->Forward(bias_bottom_vec_,top);

}

}

Backward 反向计算

主要求解三个梯度,对alpha 、beta和输入的bottom(此处的temp)

template

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

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

if (bias_layer_ && // 默认false

this->param_propagate_down_[this->param_propagate_down_.size()-1]) {

bias_layer_->Backward(top,bias_propagate_down_,bias_bottom_vec_);

}

const scale_param = (bottom.size() == 1);

Blob* scale = scale_param? this->blobs_[0].get(),bottom[1];

if ((!scale_param && propagate_down[1])|| //bottomsize大于1的时候判断

(scale_param&&this->param_propagate_down_[0])) {// 1个输入是判断alpha

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

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

const in_place = (bottom[0] == top[0]);

// 需要做备份 如果输入输出同名,需要注意用原来临时的temp

const Dtype* bottom_data =in_place?temp_.cpu_data():bottom[0]->cpu_data();

// BN中输入是NCHW,而alpha和beta仅仅针对C

const bool is_eltwise = (bottom[0]->count() == scale->count());//不相等的

Dtype* product= is_eltwise?scale_.mutable_cpu_diff():

(in_place?temp_.mutable_cpu_data():bottom[0]->mutable_cpu_diff());

caffe_mul(top[0]->count(),top_diff,bottom_data,product);

if (!is_eltwise) { // blobs_与输入对不上

Dtype* sum_result_ = NULL;

if (inner_dim_ == 1) {

//H*W == 1;

sum_result_ = product;

}

else if(sum_result_.count() == 1){ // 1*1*1*1

const Dtype* sum_mult_ = sum_multiplier_.cpu_data();

Dtype* scale_diff = scale->mutable_cpu_diff();

if (scale_param) { //true

Dtype result = caffe_cpu_dot(inner_dim,product,sum_mult);

*scale_diff += result; //H*W的相乘

}

else{

*scale_diff = caffe_cpu_dot(inner_dim_,product,sum_mult);

}

}

else{

const Dtype* sum_mult = sum_multiplier_.mutable_cpu_data();

sum_result = (outer_dim_ == 1)? // nc如果n==1就直接幅值C

scale_.mutable_cpu_diff():sum_result_.mutable_cpu_data();

//NC HW * HW*1 = NC*1 HW全1

caffe_cpu_gemv(CblasNoTrans,sum_result.count(),inner_dim,

Dtype(1),product,sum_mult,Dtype(0),Dtype(0),sum_result);

}

if (out_dim_ != 1) {

const Dtype* sum_mult = sum_multiplier_.cpu_data();

Dtype* scale_diff = scale->mutable_cpu_diff();

if (scale_dim_ ==1) {

if (scale_param) { // C==1直接计算 NC*NC

Dtype result = caffe_cpu_dot(outer_dim_,sum_mult_,sum_result);

*scale_diff += result;

}

else{

*scale_diff = caffe_cpu_dot(outer_dim_,sum_mult_,sum_result);

}

}

else{ //如果C != 1 需要gemv,(num * channels)^t * 1 *num*1

caffe_cpu_gemv(CblasTrans,outer_dim_,scale_dim,

Dtype(1),sum_result,sum_mult,Dtype(scale_param),scale_diff);

}

}

}

}

if (propagate_down[0]) { //x求导

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

const Dtype* scale_data = scale->cpu_data();

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

for (size_t n = 0; n < outer_dim_; n++) {

for (size_t d = 0; d < inner_dim_; d++) {

const Dtype factory = scale_data[d];

caffe_cpu_scale(inner_dim_,factory,top_diff,bottom_diff);

bottom_diff += inner_dim_;

top_diff += inner_dim_;

}

}

}

}

Caffe中的Scale层由于不仅仅作为BN的后续层,因此看着会比较绕,实际去上去掉很多if else 后会清晰很多

本文作者: 张峰

本文链接:http://www.enjoyai.site/2017/11/09

版权声明:本博客所有文章,均采用CC BY-NC-SA 3.0 许可协议。转载请注明出处!

Caffe_Scale层解析的更多相关文章

slice层解析

如果说之前的Concat是将多个bottom合并成一个top的话,那么这篇博客的slice层则完全相反,是把一个bottom分解成多个top,这带来了一个问题,为什么要这么做呢?为什么要把一个低层的切 ...

json两层解析

public class Demo { public static void main(String[] args) { try { // 创建连接 服务器的连接地址 URL url = new UR ...

ASP.NET SignalR2持久连接层解析

越是到年底越是感觉浑身无力,看着啥也不想动,只期盼着年终奖的到来以此来给自己打一针强心剂.估摸着大多数人都跟我一样犯着这样浑身无力的病,感觉今年算是没挣到啥钱,但是话也不能这么说,搞得好像去年挣到钱了 ...

Euclideanloss_layer层解析

这里说一下euclidean_loss_layer.cpp关于该欧式loss层的解析,代码如下: #include #include "caffe/layers ...

Spring的Service层与Dao层解析

本文转载于网络,觉得写得很透彻. dao完成连接数据库修改删除添加等的实现细节,例如sql语句是怎么写的,怎么把对象放入数据库的.service层是面向功能的,一个个功能模块比如说银行登记并完成一次存 ...

Mybatis框架基础支持层——解析器模块(2)

解析器模块,核心类XPathParser /** * 封装了用于xml解析的类XPath.Document和EntityResolver */ public class XPathParser { / ...

Eltwise层解析

Concat层虽然利用到了上下文的语义信息,但仅仅是将其拼接起来,之所以能起到效果,在于它在不增加算法复杂度的情形下增加了channel数目.那有没有直接关联上下文的语义信息呢?答案是Eltwise层 ...

Concat层解析

Concat层的作用就是将两个及以上的特征图按照在channel或num维度上进行拼接,并没有eltwise层的运算操作,举个例子,如果说是在channel维度上进行拼接conv_9和deconv_9 ...

TCP协议详解7层和4层解析(美团,阿里) 尤其是三次握手,四次挥手 具体发送的报文和状态都要掌握

如果想了解HTTP的协议结构,原理,post,get的区别(阿里面试题目),请参考:HTTP协议 结构,get post 区别(阿里面试) 这里有个大白话的解说,可以参考:TCP/IP协议三次握手和四 ...

随机推荐

CentOS 7添加开机启动服务/脚本

一.添加开机自启服务 在CentOS 7中添加开机自启服务非常方便,只需要两条命令(以 jenkins 为例):systemctl enable jenkins.service #设置jenkins服 ...

EL表达式中,param和requestScope的区别

在看param和requestScope之前,不妨先了解下在java下request的情况: 1. request对象通常用来接收客户端提交到服务端的数据,如:在servlet或者action中可以用 ...

mongoDB authentication

转自:http://blog.csdn.net/allen_jinjie/article/details/9235073 1. 最开始的时候,我们启动mongodb,但是不包含--auth参数: E: ...

ELO kernels 记录

these kernel for discuss how to handle outliers in target values. 一:Ashish Gupta: 在16年6月到18年8月,激活卡的人 ...

提高生产力:SpringMVC中,使用扩展数据类型TypedMap接收Web请求参数

在Web项目中,如果前端MVC框架使用的是SpringMVC,可以使用Map接收前端请求参数,比bean要方便很多. 尤其是SpringMVC和Mybatis一起用的时候,用Map大大减少了需要的be ...

uni-app 之验证码

手机APP---验证码 最近公司在开发手机APP,app避不可免的就是登录了,emmmm 登录验证码那必须的是有的,我们公司发给我们的图片是酱紫的~~ 这个要求大家应该都能看懂,做这个手机号啊,验证码 ...

【Manthan, Codefest 18 (rated, Div. 1 + Div. 2) C】Equalize

[链接] 我是链接,点我呀:) [题意] 在这里输入题意 [题解] Swap操作显然只能对(i-1,i)执行才有用. 不然直接将i翻转以及j翻转 显然比直接交换更优. 那么现在我们就相当于有两种操作. ...

HDU1850 Being a Good Boy in Spring Festival

/* HDU1850 Being a Good Boy in Spring Festival http://acm.hdu.edu.cn/showproblem.php?pid=1850 博弈论 尼姆 ...

ie固定table单元格宽度

< ...

跳出$.each()循环

return false:将停止循环 ,跳出eachreturn true:跳至下一个循环(就像在普通的循环中使用'continue').