Caffe源码导读(6):LRN层的实现

Caffe源码导读(6):LRN层的实现

LRN全称为Local Response Normalization,即局部响应归一化层,具体实现在CAFFE_ROOT/src/caffe/layers/lrn_layer.cpp和同一目录下lrn_layer.cu中。

 

该层需要参数有:

norm_region: 选择对相邻通道间归一化还是通道内空间区域归一化,默认为ACROSS_CHANNELS,即通道间归一化;

local_size:两种表示(1)通道间归一化时表示求和的通道数;(2)通道内归一化时表示求和区间的边长;默认值为5;

alpha:缩放因子(详细见后面),默认值为1;

beta:指数项(详细见后面), 默认值为5;

 

局部响应归一化层完成一种“临近抑制”操作,对局部输入区域进行归一化。

 

在通道间归一化模式中,局部区域范围在相邻通道间,但没有空间扩展(即尺寸为 local_size x 1 x 1);

在通道内归一化模式中,局部区域在空间上扩展,但只针对独立通道进行(即尺寸为 1 x local_size x local_size);

每个输入值都将除以

其中n为局部尺寸大小local_size, alpha和beta前面已经定义。

求和将在当前值处于中间位置的局部区域内进行(如果有必要则进行补零)。

 

下面我们看Caffe代码如何实现。打开CAFFE_ROOT/include/caffe/vision_layers.hpp,从第242行开始看起:

 

  1.   // Forward declare PoolingLayer and SplitLayer for use in LRNLayer.  
  2. template <typename Dtype> class PoolingLayer;  
  3. template <typename Dtype> class SplitLayer;  
  4.   
  5.   
  6. /** 
  7.  * @brief Normalize the input in a local region across or within feature maps. 
  8.  * 
  9.  * TODO(dox): thorough documentation for Forward, Backward, and proto params. 
  10.  */  
  11. template <typename Dtype>  
  12. class LRNLayer : public Layer<Dtype> {  
  13.  public:  
  14.   explicit LRNLayer(const LayerParameter& param)  
  15.       : Layer<Dtype>(param) {}  
  16.   virtual void LayerSetUp(const vector<Blob<Dtype>*>& bottom,  
  17.       vector<Blob<Dtype>*>* top);  
  18.   virtual void Reshape(const vector<Blob<Dtype>*>& bottom,  
  19.       vector<Blob<Dtype>*>* top);  
  20.   
  21.   
  22.   virtual inline LayerParameter_LayerType type() const {  
  23.     return LayerParameter_LayerType_LRN;  
  24.   }  
  25.   virtual inline int ExactNumBottomBlobs() const { return 1; }  
  26.   virtual inline int ExactNumTopBlobs() const { return 1; }  
  27.   
  28.   
  29.  protected:  
  30.   virtual void Forward_cpu(const vector<Blob<Dtype>*>& bottom,  
  31.       vector<Blob<Dtype>*>* top);  
  32.   virtual void Forward_gpu(const vector<Blob<Dtype>*>& bottom,  
  33.       vector<Blob<Dtype>*>* top);  
  34.   virtual void Backward_cpu(const vector<Blob<Dtype>*>& top,  
  35.       const vector<bool>& propagate_down, vector<Blob<Dtype>*>* bottom);  
  36.   virtual void Backward_gpu(const vector<Blob<Dtype>*>& top,  
  37.       const vector<bool>& propagate_down, vector<Blob<Dtype>*>* bottom);  
  38.   
  39.   virtual void CrossChannelForward_cpu(const vector<Blob<Dtype>*>& bottom,  
  40.       vector<Blob<Dtype>*>* top);  
  41.   virtual void CrossChannelForward_gpu(const vector<Blob<Dtype>*>& bottom,  
  42.       vector<Blob<Dtype>*>* top);  
  43.   virtual void WithinChannelForward(const vector<Blob<Dtype>*>& bottom,  
  44.       vector<Blob<Dtype>*>* top);  
  45.   virtual void CrossChannelBackward_cpu(const vector<Blob<Dtype>*>& top,  
  46.       const vector<bool>& propagate_down, vector<Blob<Dtype>*>* bottom);  
  47.   virtual void CrossChannelBackward_gpu(const vector<Blob<Dtype>*>& top,  
  48.       const vector<bool>& propagate_down, vector<Blob<Dtype>*>* bottom);  
  49.   virtual void WithinChannelBackward(const vector<Blob<Dtype>*>& top,  
  50.       const vector<bool>& propagate_down, vector<Blob<Dtype>*>* bottom);  
  51.   
  52.   int size_;  
  53.   int pre_pad_;  
  54.   Dtype alpha_;  
  55.   Dtype beta_;  
  56.   int num_;  
  57.   int channels_;  
  58.   int height_;  
  59.   int width_;  
  60.   
  61.   // Fields used for normalization ACROSS_CHANNELS  
  62.   // scale_ stores the intermediate summing results  
  63.   Blob<Dtype> scale_;  
  64.   
  65.   // Fields used for normalization WITHIN_CHANNEL  
  66.   shared_ptr<SplitLayer<Dtype> > split_layer_;  
  67.   vector<Blob<Dtype>*> split_top_vec_;  
  68.   shared_ptr<PowerLayer<Dtype> > square_layer_;  
  69.   Blob<Dtype> square_input_;  
  70.   Blob<Dtype> square_output_;  
  71.   vector<Blob<Dtype>*> square_bottom_vec_;  
  72.   vector<Blob<Dtype>*> square_top_vec_;  
  73.   shared_ptr<PoolingLayer<Dtype> > pool_layer_;  
  74.   Blob<Dtype> pool_output_;  
  75.   vector<Blob<Dtype>*> pool_top_vec_;  
  76.   shared_ptr<PowerLayer<Dtype> > power_layer_;  
  77.   Blob<Dtype> power_output_;  
  78.   vector<Blob<Dtype>*> power_top_vec_;  
  79.   shared_ptr<EltwiseLayer<Dtype> > product_layer_;  
  80.   Blob<Dtype> product_input_;  
  81.   vector<Blob<Dtype>*> product_bottom_vec_;  
  82. };  


内容较多,可能看一眼记不住所有的成员变量和函数,但记住一点,凡是Layer类型肯定都包含Forward()和Backward(),以及LayerSetUp()和Reshape(),这些在头文件中不必细看。关注的是以“_”结尾的成员变量,这些是和算法息息相关的。

 

很高兴看到了num_, height_, width_, channels_,这四个变量定义了该层输入图像的尺寸信息,是一个num_ x channels_ x height_ x width_的四维Blob矩阵(想不通?就当作视频流吧,前两维是宽高,第三维是颜色,第四维是时间)。

另外看到了alpha_, beta_, 这两个就是我们上面公式中的参数。

公式中的n(local_size)在类中用size_表示。

上面提到过需要补零,所以定义了pre_pad_变量。

在ACROSS_CHANNELS模式下,我们只需要用到scale_这个Blob矩阵,后面定义都可以忽略了~~好开森~~

 

读完了头文件中的声明,是不是觉得挺简单?我们接着看下实现细节,打开CAFFE_ROOT/src/caffe/layers/lrn_layer.cpp,从头看起,第一个实现函数为LayerSetUp(),代码如下:

 

  1. template <typename Dtype>  
  2. void LRNLayer<Dtype>::LayerSetUp(const vector<Blob<Dtype>*>& bottom,  
  3.       vector<Blob<Dtype>*>* top) {  
  4.   size_ = this->layer_param_.lrn_param().local_size();  
  5.   CHECK_EQ(size_ % 2, 1) << "LRN only supports odd values for local_size";  
  6.   pre_pad_ = (size_ - 1) / 2;  
  7.   alpha_ = this->layer_param_.lrn_param().alpha();  
  8.   beta_ = this->layer_param_.lrn_param().beta();  
  9.   if (this->layer_param_.lrn_param().norm_region() ==  
  10.       LRNParameter_NormRegion_WITHIN_CHANNEL) {  
  11.     // Set up split_layer_ to use inputs in the numerator and denominator.  
  12.     split_top_vec_.clear();  
  13.     split_top_vec_.push_back(&product_input_);  
  14.     split_top_vec_.push_back(&square_input_);  
  15.     LayerParameter split_param;  
  16.     split_layer_.reset(new SplitLayer<Dtype>(split_param));  
  17.     split_layer_->SetUp(bottom, &split_top_vec_);  
  18.     // Set up square_layer_ to square the inputs.  
  19.     square_bottom_vec_.clear();  
  20.     square_top_vec_.clear();  
  21.     square_bottom_vec_.push_back(&square_input_);  
  22.     square_top_vec_.push_back(&square_output_);  
  23.     LayerParameter square_param;  
  24.     square_param.mutable_power_param()->set_power(Dtype(2));  
  25.     square_layer_.reset(new PowerLayer<Dtype>(square_param));  
  26.     square_layer_->SetUp(square_bottom_vec_, &square_top_vec_);  
  27.     // Set up pool_layer_ to sum over square neighborhoods of the input.  
  28.     pool_top_vec_.clear();  
  29.     pool_top_vec_.push_back(&pool_output_);  
  30.     LayerParameter pool_param;  
  31.     pool_param.mutable_pooling_param()->set_pool(  
  32.         PoolingParameter_PoolMethod_AVE);  
  33.     pool_param.mutable_pooling_param()->set_pad(pre_pad_);  
  34.     pool_param.mutable_pooling_param()->set_kernel_size(size_);  
  35.     pool_layer_.reset(new PoolingLayer<Dtype>(pool_param));  
  36.     pool_layer_->SetUp(square_top_vec_, &pool_top_vec_);  
  37.     // Set up power_layer_ to compute (1 + alpha_/N^2 s)^-beta_, where s is  
  38.     // the sum of a squared neighborhood (the output of pool_layer_).  
  39.     power_top_vec_.clear();  
  40.     power_top_vec_.push_back(&power_output_);  
  41.     LayerParameter power_param;  
  42.     power_param.mutable_power_param()->set_power(-beta_);  
  43.     power_param.mutable_power_param()->set_scale(alpha_);  
  44.     power_param.mutable_power_param()->set_shift(Dtype(1));  
  45.     power_layer_.reset(new PowerLayer<Dtype>(power_param));  
  46.     power_layer_->SetUp(pool_top_vec_, &power_top_vec_);  
  47.     // Set up a product_layer_ to compute outputs by multiplying inputs by the  
  48.     // inverse demoninator computed by the power layer.  
  49.     product_bottom_vec_.clear();  
  50.     product_bottom_vec_.push_back(&product_input_);  
  51.     product_bottom_vec_.push_back(&power_output_);  
  52.     LayerParameter product_param;  
  53.     EltwiseParameter* eltwise_param = product_param.mutable_eltwise_param();  
  54.     eltwise_param->set_operation(EltwiseParameter_EltwiseOp_PROD);  
  55.     product_layer_.reset(new EltwiseLayer<Dtype>(product_param));  
  56.     product_layer_->SetUp(product_bottom_vec_, top);  
  57.   }  
  58. }  


这个函数实现了参数的初始化过程。首先从layer_param_对象中提取出size_的值,并检查是否为奇数,如果不是则报错;之后用size_计算pre_pad_的值,在前后各补一半0。接着alpha_和beta_也被初始化。如果是WITHIN_CHANNEL模式,那么还需要初始化一系列中间子层,这里我们不关心,因为我们用ACROSS_CHANNELS模式。这么简单,还是好开森~~

 

接下来看Reshape()函数的实现:

 

  1. template <typename Dtype>  
  2. void LRNLayer<Dtype>::Reshape(const vector<Blob<Dtype>*>& bottom,  
  3.       vector<Blob<Dtype>*>* top) {  
  4.   num_ = bottom[0]->num();  
  5.   channels_ = bottom[0]->channels();  
  6.   height_ = bottom[0]->height();  
  7.   width_ = bottom[0]->width();  
  8.   switch (this->layer_param_.lrn_param().norm_region()) {  
  9.   case LRNParameter_NormRegion_ACROSS_CHANNELS:  
  10.     (*top)[0]->Reshape(num_, channels_, height_, width_);  
  11.     scale_.Reshape(num_, channels_, height_, width_);  
  12.     break;  
  13.   case LRNParameter_NormRegion_WITHIN_CHANNEL:  
  14.     split_layer_->Reshape(bottom, &split_top_vec_);  
  15.     square_layer_->Reshape(square_bottom_vec_, &square_top_vec_);  
  16.     pool_layer_->Reshape(square_top_vec_, &pool_top_vec_);  
  17.     power_layer_->Reshape(pool_top_vec_, &power_top_vec_);  
  18.     product_layer_->Reshape(product_bottom_vec_, top);  
  19.     break;  
  20.   }  
  21. }  


首先根据bottom的尺寸初始化了num_, channels_, height_, width_这四个尺寸参数,之后根据归一化模式进行不同设置。在ACROSS_CHANNELS模式中,将top尺寸设置为和bottom一样大(num_, channels_, height_, width_),然后将scale_的尺寸也设置为一样大,这样我们在进行归一化时,只要逐点将scale_值乘以bottom值,就得到相应的top值。scale_值需要根据文章开头的计算公式得到,我们进一步考察怎么实现。

 

看下一个函数:

 

  1. template <typename Dtype>  
  2. void LRNLayer<Dtype>::Forward_cpu(const vector<Blob<Dtype>*>& bottom,  
  3.     vector<Blob<Dtype>*>* top) {  
  4.   switch (this->layer_param_.lrn_param().norm_region()) {  
  5.   case LRNParameter_NormRegion_ACROSS_CHANNELS:  
  6.     CrossChannelForward_cpu(bottom, top);  
  7.     break;  
  8.   case LRNParameter_NormRegion_WITHIN_CHANNEL:  
  9.     WithinChannelForward(bottom, top);  
  10.     break;  
  11.   default:  
  12.     LOG(FATAL) << "Unknown normalization region.";  
  13.   }  
  14. }  


很简单,根据归一化模式调用相应的Forward函数。我们这里看CrossChannelForward_cpu()这个函数,代码如下:

 

 

  1. template <typename Dtype>  
  2. void LRNLayer<Dtype>::CrossChannelForward_cpu(  
  3.     const vector<Blob<Dtype>*>& bottom, vector<Blob<Dtype>*>* top) {  
  4.   const Dtype* bottom_data = bottom[0]->cpu_data();  
  5.   Dtype* top_data = (*top)[0]->mutable_cpu_data();  
  6.   Dtype* scale_data = scale_.mutable_cpu_data();//用指针获取每个Blob对象的内存地址,便于后面操作  
  7.   // start with the constant value  
  8.   for (int i = 0; i < scale_.count(); ++i) {//初始化值为1.0  
  9.     scale_data[i] = 1.;  
  10.   }  
  11.   Blob<Dtype> padded_square(1, channels_ + size_ - 1, height_, width_);//补零后的Blob,第三维尺寸比bottom大了size_ - 1;  
  12.   Dtype* padded_square_data = padded_square.mutable_cpu_data();  
  13.   caffe_set(padded_square.count(), Dtype(0), padded_square_data);//先清零  
  14.   Dtype alpha_over_size = alpha_ / size_;//预先计算公式中的alpha/n  
  15.   // go through the images  
  16.   for (int n = 0; n < num_; ++n) {//bottom的第四维尺寸num_,需要分解为单个来做归一化  
  17.     // compute the padded square  
  18.     caffe_sqr(channels_ * height_ * width_,  
  19.         bottom_data + bottom[0]->offset(n),  
  20.         padded_square_data + padded_square.offset(0, pre_pad_));//计算bottom的平方,放入padded_square矩阵中,前pre_pad_个位置依旧0  
  21.     // Create the first channel scale  
  22.     for (int c = 0; c < size_; ++c) {//对n个通道平方求和并乘以预先算好的(alpha/n),累加至scale_中(实现计算 1 + sum_under_i(x_i^2))  
  23.       caffe_axpy<Dtype>(height_ * width_, alpha_over_size,  
  24.           padded_square_data + padded_square.offset(0, c),  
  25.           scale_data + scale_.offset(n, 0));  
  26.     }  
  27.     for (int c = 1; c < channels_; ++c) {//这里使用了类似FIFO的形式计算其余scale_参数,每次向后移动一个单位,加头去尾,避免重复计算求和  
  28.       // copy previous scale  
  29.       caffe_copy<Dtype>(height_ * width_,  
  30.           scale_data + scale_.offset(n, c - 1),  
  31.           scale_data + scale_.offset(n, c));  
  32.       // add head  
  33.       caffe_axpy<Dtype>(height_ * width_, alpha_over_size,  
  34.           padded_square_data + padded_square.offset(0, c + size_ - 1),  
  35.           scale_data + scale_.offset(n, c));  
  36.       // subtract tail  
  37.       caffe_axpy<Dtype>(height_ * width_, -alpha_over_size,  
  38.           padded_square_data + padded_square.offset(0, c - 1),  
  39.           scale_data + scale_.offset(n, c));  
  40.     }  
  41.   }  
  42.   
  43.   // In the end, compute output  
  44.   caffe_powx<Dtype>(scale_.count(), scale_data, -beta_, top_data);//计算求指数,由于将除法转换为乘法,故指数变负  
  45.   caffe_mul<Dtype>(scale_.count(), top_data, bottom_data, top_data);//bottom .* scale_ -> top  
  46. }  


可能你对caffe_axpy, caffe_sqr, caffe_powx, caffe_mul还不熟悉,其实都是很简单的数学计算,在CAFFE_ROOT/include/caffe/util/math_functions.hpp中有声明。

 

 

  1. template <typename Dtype>  
  2. void caffe_axpy(const int N, const Dtype alpha, const Dtype* X,  
  3.     Dtype* Y);  


实现如下操作:Y = alpha * X + Y;其中X, Y为N个元素的向量。

 

 

  1. template <typename Dtype>  
  2. void caffe_powx(const int n, const Dtype* a, const Dtype b, Dtype* y);  


实现如下操作:y = a^b, 其中a, y为n个元素的向量,b为标量。

 

其余请自己推导。

posted @ 2015-12-02 11:20  菜鸡一枚  阅读(814)  评论(0编辑  收藏  举报