Caffe Scale層解析
前段時間做了caffe的batchnormalization層的解析,由於整體的BN層實現在Caffe是分段實現的,因此今天抽時間總結下Scale層次,也會后續兩個層做合並做下鋪墊。
基本公式梳理
Scale層主要完成 \(top = alpha*bottom+ beta\)的過程,則層中主要有兩個參數\(alpha\)與\(beta\),
求導會比較簡單。
需要注意的是\(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<Layer<Dtype>> bias_layer_; /
vector<Blob<Dtype>*>bias_bottom_vec_;
vector<bool> bias_propagate_down_;
int bias_param_id_;
Blob<Dtype> sum_multiplier_;
Blob<Dtype>sum_result_;
Blob<Dtype> temp_;
int axis_;
int outer_dim_,inner_dim_,scale_dim_;
基本成員變量主要包含了Bias層的參數以及Scale層完成對應通道的標注工作。
基本成員函數
主要包含了LayerSetup,Reshape ,Forward和Backward ,內部調用的時候bias_term為true的時候會調用biasLayer的相關函數.
LayerSetup,層次的建立
template <typename Dtype>
void ScaleLayer<Dtype>::LayerSetUp(const vector<Blob<Dtype>*>& bottom,
const vector<Blob<Dtype>*>& 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<Dtype>(vector<int>(C)));
const vector<int>::const_iterator& shape_start =
bottom[0]->shape().begin() + axis_;
const vector<int>::const_iterator& shape_end =
(num_axes == -1) ? bottom[0]->shape().end() : (shape_start + num_axes);
vector<int>scale_shape(shape_start, shape_end);
this->blobs_[0].reset(new Blob<Dtype>(scale_shape));
FillerParameter filler_param(param.filler());
if (!param.has_filler()) { //沒寫明填充模式
filler_param.set_type("constant");
filler_param.set_value(1);
}
shared_ptr<Filler<Dtype>>filler(GetFiller<Dtype>(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<Dtype>::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 <typename Dtype>
void ScaleLayer<Dtype>::Reshape(const vector<Blob<Dtype>*>& bottom,
const vector<Blob<Dtype>*>& top) {
const ScaleParameter param = this->layer_param_->scale_param();
Blob<Dtype>* 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<int>(1,outer_dim_*scale_dim_));
const int sum_mult_size = std::max(outer_dim_,inner_dim_);
// 為什么不類似於BN做兩個temp vector呢
sum_multiplier_.Reshape(vector<int>(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 <typename Dtype>
void ScaleLayer<Dtype>::Forward_cpu(
const vector<Blob<Dtype>*>& bottom, const vector<Blob<Dtype>*>& 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 <typename Dtype>
void ScaleLayer<Dtype>::Backward_cpu(const vector<Blob<Dtype>*>& top,
const vector<bool>& propagate_down, const vector<Blob<Dtype>*>& 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<Dtype>* 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<Dtype>(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<Dtype>(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 許可協議。轉載請注明出處!
