caffe中的bn层和scale层

Normalization是数据标准化(归一化,规范化),Batch 可以理解为批量,加起来就是批量标准化。
先说Batch是怎么确定的。在CNN中,Batch就是训练网络所设定的图片数量batch_size。

Normalization过程,引用论文中的解释:
这里写图片描述
输入:输入数据x1..xm(这些数据是准备进入激活函数的数据)
计算过程中可以看到,
1.求数据均值;
2.求数据方差;
3.数据进行标准化(个人认为称作正态化也可以)
4.训练参数γ,β
5.输出y通过γ与β的线性变换得到新的值
在正向传播的时候,通过可学习的γ与β参数求出新的分布值

在反向传播的时候,通过链式求导方式,求出γ与β以及相关权值
这里写图片描述

公式看的是不是有点绕,来看一个简单的回答,其实就做了两件事  ,但是为啥caffe里的bn要和scale一起使用呢?

1) 输入归一化 x_norm = (x-u)/std, 其中u和std是个累计计算的均值和方差。

2)y=alpha×x_norm + beta,对归一化后的x进行比例缩放和位移。其中alpha

和beta是通过迭代学习的。那么caffe中的bn层其实只做了第一件事。scale层做了第二件事。

这样你也就理解了scale层里为什么要设置bias_term=True,这个偏置就对应深度2)件事里的beta。

再看caffe代码

layer {
    bottom: "conv1"
    top: "conv1"
    name: "bn_conv1"
    type: "BatchNorm"
    batch_norm_param {
        use_global_stats: false
    }
}
/*  参数解答use_global_stats:如果为真,则使用保存的均值和方差,否则采用滑动平均计算新的均值和方差。该参数缺省的时候,如果是测试阶段则等价为真,如果是训练阶段则等价为假。

       moving_average_fraction  :滑动平均的衰减系数,默认为0.999

       eps:分母附加值,防止除以方差时出现除0操作,默认为1e-5(不同框架采用的默认值不一样),

      caffe自带的BatchNorm层,与scale和在一起完成batchnorm,batchnorm层该层也是有参数的*/

layer {
    bottom: "conv1"
    top: "conv1"
    name: "scale_conv1"
    type: "Scale"
    scale_param {
        bias_term: true//是否使用偏置
    }
}

二、bn_layer.cpp源码分析

batch_norm参数:

message BatchNormParameter {
  // 当为真,使用保存的均值和方差,否则使用滑动平均计算新的方差和均值
  optional bool use_global_stats = 1;
  //滑动平均的系数
  optional float moving_average_fraction = 2 [default = .999];
  // 平滑,防止除以0
  optional float eps = 3 [default = 1e-5];
}

 

卷积层这样具有权值共享的层,Wx+b的均值和方差是对整张map求得的,在batch_size * channel * height * width这么大的一层中,对总共batch_size*height*width个像素点统计得到一个均值和一个标准差,共得到channel组参数。

template <typename Dtype>
void BatchNormLayer<Dtype>::LayerSetUp(const vector<Blob<Dtype>*>& bottom,
      const vector<Blob<Dtype>*>& top) {
  BatchNormParameter param = this->layer_param_.batch_norm_param();//batch_norm参数
  moving_average_fraction_ = param.moving_average_fraction();
  use_global_stats_ = this->phase_ == TEST;//训练时均值和方差是基于每个batch进行计算的,而测试的时候均值和方差是对整个数据集而言的。
  if (param.has_use_global_stats())
    use_global_stats_ = param.use_global_stats();
  if (bottom[0]->num_axes() == 1)//一维的时候,通道数为1
    channels_ = 1;
  else
    channels_ = bottom[0]->shape(1);//否则等于输入的通道数
  eps_ = param.eps();//滑动平均系数
  if (this->blobs_.size() > 0) {//存储学习参数
    LOG(INFO) << "Skipping parameter initialization";
  } else {
    this->blobs_.resize(3);//存储学习参数
    vector<int> sz;
    sz.push_back(channels_);
    this->blobs_[0].reset(new Blob<Dtype>(sz));//均值滑动和,元素个数为channels_的数组
    this->blobs_[1].reset(new Blob<Dtype>(sz));//方差滑动和,元素个数为channels_的数组
    sz[0] = 1;
    this->blobs_[2].reset(new Blob<Dtype>(sz));//滑动系数和,元素个数为1的数组
    for (int i = 0; i < 3; ++i) {
      caffe_set(this->blobs_[i]->count(), Dtype(0),
                this->blobs_[i]->mutable_cpu_data());//初始化学习参数,初始化为0.
    }
  }
  // Mask statistics from optimization by setting local learning rates
  // for mean, variance, and the bias correction to zero.
  for (int i = 0; i < this->blobs_.size(); ++i) {
    if (this->layer_param_.param_size() == i) {
      ParamSpec* fixed_param_spec = this->layer_param_.add_param();
      fixed_param_spec->set_lr_mult(0.f);
    } else {
      CHECK_EQ(this->layer_param_.param(i).lr_mult(), 0.f)
          << "Cannot configure batch normalization statistics as layer "
          << "parameters.";
    }
  }
}

2、reshape

template <typename Dtype>
void BatchNormLayer<Dtype>::Reshape(const vector<Blob<Dtype>*>& bottom,
      const vector<Blob<Dtype>*>& top) {
  if (bottom[0]->num_axes() >= 1)//输入维度大于1,检查channels
    CHECK_EQ(bottom[0]->shape(1), channels_);
  top[0]->ReshapeLike(*bottom[0]);//复制输入的形状给输出

  vector<int> sz;
  sz.push_back(channels_);
  mean_.Reshape(sz);
  variance_.Reshape(sz);//shape[0] = channels_
  temp_.ReshapeLike(*bottom[0]);//n*c*h*w
  x_norm_.ReshapeLike(*bottom[0]);//x的归一化缓存
  sz[0] = bottom[0]->shape(0);//sz = {batch_size} 
  batch_sum_multiplier_.Reshape(sz);//shape[0] = batch_size,元素个数为n的数组,在计算mean_时,将所要图像的相应的通道值相加。  
  int spatial_dim = bottom[0]->count()/(channels_*bottom[0]->shape(0));//h*w
  if (spatial_sum_multiplier_.num_axes() == 0 ||
      spatial_sum_multiplier_.shape(0) != spatial_dim) {
    sz[0] = spatial_dim;//sz[0] =h*w
    spatial_sum_multiplier_.Reshape(sz);//{h*w}
    Dtype* multiplier_data = spatial_sum_multiplier_.mutable_cpu_data();//在计算mean_时通过乘的方式将一副图像的值相加,结果是一个数值

    caffe_set(spatial_sum_multiplier_.count(), Dtype(1), multiplier_data);
  }

  int numbychans = channels_*bottom[0]->shape(0);//channels_*batch_size
  if (num_by_chans_.num_axes() == 0 ||
      num_by_chans_.shape(0) != numbychans) {
    sz[0] = numbychans;
    num_by_chans_.Reshape(sz);//元素个数为c*n
    caffe_set(batch_sum_multiplier_.count(), Dtype(1),
        batch_sum_multiplier_.mutable_cpu_data());
  }
}

3、前向计算

这里写图片描述
训练的过程并不是一次前向计算就结束,而是从总样本中抽取mini-batch个样本,进行多次前向计算,这样的话需要考虑每次计算得到的mean和 variance,caffe这里的算法并不是简简单单的将每次计算的mean和variance累加,而是把前一次计算的mean和variance的 影响减小(乘以一个小于1的变量),再加上本次计算的结果。

所以,均值和方差采用的是滑动平均的更新方式

前一批次的均值:St−1


当前批次的均值:Yt
St=(1−β)Yt+β⋅St−1

 

设滑动系数 moving_average_fraction 为 λ

对于滑动系数有:snew=λsold+1

 

 this->blobs_[2]->mutable_cpu_data()[0] *= moving_average_fraction_;
    this->blobs_[2]->mutable_cpu_data()[0] += 1;

 

 

caffe_cpu_axpby(mean_.count(), Dtype(1), mean_.cpu_data(),
        moving_average_fraction_, this->blobs_[0]->mutable_cpu_data());
  •  

对于方差有:σnew=λσold+mσif(m>1),m=(m−1)/m

 

 caffe_cpu_axpby(variance_.count(), bias_correction_factor,
        variance_.cpu_data(), moving_average_fraction_,
        this->blobs_[1]->mutable_cpu_data());

通过源码可以看出,CAFFE没有加最后的两个参数。

template <typename Dtype>
void BatchNormLayer<Dtype>::Forward_cpu(const vector<Blob<Dtype>*>& bottom,
    const vector<Blob<Dtype>*>& top) {
  const Dtype* bottom_data = bottom[0]->cpu_data();//输入数据
  Dtype* top_data = top[0]->mutable_cpu_data();//输出数据
  int num = bottom[0]->shape(0);//n
  int spatial_dim = bottom[0]->count()/(bottom[0]->shape(0)*channels_);//spatial_dim = h*w

  if (bottom[0] != top[0]) {
    caffe_copy(bottom[0]->count(), bottom_data, top_data);//将输入数据复制到输出上
  }

  if (use_global_stats_) {
    // 直接用保存的方差和均值、滑动平均系数
    const Dtype scale_factor = this->blobs_[2]->cpu_data()[0] == 0 ?
        0 : 1 / this->blobs_[2]->cpu_data()[0];
    caffe_cpu_scale(variance_.count(), scale_factor,
        this->blobs_[0]->cpu_data(), mean_.mutable_cpu_data());
    caffe_cpu_scale(variance_.count(), scale_factor,
        this->blobs_[1]->cpu_data(), variance_.mutable_cpu_data());
  } else {
    // compute mean
    //bottom_data是(n*c)*(h*w)的矩阵,spatial_sun_multiplier_是元素个数为1*1*h*w的向量,那么num_by_chans_就是n*c*1*1的向量。
    //num_by_chans_=(1/N*H*W)*bottom_data*spatial_sum_multiplier_.cpu_data()

    caffe_cpu_gemv<Dtype>(CblasNoTrans, channels_ * num, spatial_dim,
        1. / (num * spatial_dim), bottom_data,
        spatial_sum_multiplier_.cpu_data(), 0.,
        num_by_chans_.mutable_cpu_data());

     //num_by_chans_转换为n行c列的矩阵,batch_sum_multiplier_是元素个数为n*1*1*1的向量,
     //mean_其元素个数为c。
     //mean_=num_by_chans_*batch_sum_multiplier_
    //计算得到对应channels的平均值,这也解释了为什么之前要除以1./(num*spatial_dim)
    //而不是仅除以1./spatial_dim,这样减少了计算量
    caffe_cpu_gemv<Dtype>(CblasTrans, num, channels_, 1.,
        num_by_chans_.cpu_data(), batch_sum_multiplier_.cpu_data(), 0.,
        mean_.mutable_cpu_data());// mean_=bottom_data*(1/N*H*W),按通道计算其均值
  }

  // 减去均值x-u
  //top_data = top_data-num_by_chans_*spatial_sum_multiplier_
  caffe_cpu_gemm<Dtype>(CblasNoTrans, CblasNoTrans, num, channels_, 1, 1,
      batch_sum_multiplier_.cpu_data(), mean_.cpu_data(), 0.,
      num_by_chans_.mutable_cpu_data());
  caffe_cpu_gemm<Dtype>(CblasNoTrans, CblasNoTrans, channels_ * num,
      spatial_dim, 1, -1, num_by_chans_.cpu_data(),
      spatial_sum_multiplier_.cpu_data(), 1., top_data);

  if (!use_global_stats_) {
    // 计算方差:var(X) = E((X-EX)^2)
    caffe_powx(top[0]->count(), top_data, Dtype(2),
        temp_.mutable_cpu_data());  // (X-EX)^2
    caffe_cpu_gemv<Dtype>(CblasNoTrans, channels_ * num, spatial_dim,
        1. / (num * spatial_dim), temp_.cpu_data(),
        spatial_sum_multiplier_.cpu_data(), 0.,
        num_by_chans_.mutable_cpu_data());
    caffe_cpu_gemv<Dtype>(CblasTrans, num, channels_, 1.,
        num_by_chans_.cpu_data(), batch_sum_multiplier_.cpu_data(), 0.,
        variance_.mutable_cpu_data());  // E((X_EX)^2)

    // 计算滑动平均,blob_[0] = mean_ + moving_average_fraction_* blob_[0];
    this->blobs_[2]->mutable_cpu_data()[0] *= moving_average_fraction_;
    this->blobs_[2]->mutable_cpu_data()[0] += 1;

    caffe_cpu_axpby(mean_.count(), Dtype(1), mean_.cpu_data(),
        moving_average_fraction_, this->blobs_[0]->mutable_cpu_data());
    int m = bottom[0]->count()/channels_;//n*h*w
    Dtype bias_correction_factor = m > 1 ? Dtype(m)/(m-1) : 1;//算整个数据集的方差
    //blob_[1] = bias_correction_factor * variance_ + moving_average_fraction_ * blob_[1] 
    caffe_cpu_axpby(variance_.count(), bias_correction_factor,
        variance_.cpu_data(), moving_average_fraction_,
        this->blobs_[1]->mutable_cpu_data());
  }

  // 将方差的每个值平滑一下
  caffe_add_scalar(variance_.count(), eps_, variance_.mutable_cpu_data());
  //开平方
  caffe_powx(variance_.count(), variance_.cpu_data(), Dtype(0.5),
             variance_.mutable_cpu_data());

  // 将channels_个值的方差variance_矩阵扩展到num_*channels_*height*width  
  caffe_cpu_gemm<Dtype>(CblasNoTrans, CblasNoTrans, num, channels_, 1, 1,
      batch_sum_multiplier_.cpu_data(), variance_.cpu_data(), 0.,
      num_by_chans_.mutable_cpu_data());
  caffe_cpu_gemm<Dtype>(CblasNoTrans, CblasNoTrans, channels_ * num,
      spatial_dim, 1, 1., num_by_chans_.cpu_data(),
      spatial_sum_multiplier_.cpu_data(), 0., temp_.mutable_cpu_data());
  caffe_div(temp_.count(), top_data, temp_.cpu_data(), top_data);
  // TODO(cdoersch): The caching is only needed because later in-place layers
  //                 might clobber the data.  Can we skip this if they won't?
  caffe_copy(x_norm_.count(), top_data,
      x_norm_.mutable_cpu_data());
}

 

参考文献:

caffe常用层的参数设置说明

BN(Batch Normalization) 原理

CAFFE源码学习笔记之batch_norm_layer

评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值