Caffe源码解析7:Pooling_Layer

转载 2017年01月03日 19:36:44

转载自:http://home.cnblogs.com/louyihang-loves-baiyan/

Pooling 层一般在网络中是跟在Conv卷积层之后,做采样操作,其实是为了进一步缩小feature map,同时也能增大神经元的视野。在Caffe中,pooling层属于vision_layer的一部分,其相关的定义也在pooling_layer.hpp的头文件中,pooling_layer是Layer的一个子类(见class PoolingLayer : public Layer<Dtype>)。Pooling层的相关操作比较少,在Caffe的自带模式下只有Max poolingAverage poooling两种。

下图是一个LeNet的网络结构图,全连接之前主要有2个卷基层,2个池化层,其中sub_sampling layer就是pooling的操作。pooling的范围是给定的一个region。
686170-20160223202453005-2039693809

PoolingLayer

caffe中Pooling的操作相对比较少,结构也简单,查看pooling_layer.cpp,首先看它的Forward_cpu函数,在forward的时候根据相应的Pooling_method选择相应的pooling方法

forward_cpu
void PoolingLayer<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();
  const int top_count = top[0]->count();
  //将mask信息输出到top[1],如果top大于1
  const bool use_top_mask = top.size() > 1;
  int* mask = NULL;  // suppress warnings about uninitalized variables
  Dtype* top_mask = NULL;
  switch (this->layer_param_.pooling_param().pool()) {
  case PoolingParameter_PoolMethod_MAX://这里的case主要是实现max pooling的方法
    // Initialize
    if (use_top_mask) {
      top_mask = top[1]->mutable_cpu_data();
      caffe_set(top_count, Dtype(-1), top_mask);
    } else {
      mask = max_idx_.mutable_cpu_data();
      caffe_set(top_count, -1, mask);
    }
    caffe_set(top_count, Dtype(-FLT_MAX), top_data);
    // The main loop
    for (int n = 0; n < bottom[0]->num(); ++n) {
      for (int c = 0; c < channels_; ++c) {
        for (int ph = 0; ph < pooled_height_; ++ph) {
          for (int pw = 0; pw < pooled_width_; ++pw) {
            int hstart = ph * stride_h_ - pad_h_;//这里的hstart,wstart,hend,wend指的是pooling窗口在特征图中的坐标,对应左上右下即x1 y1 x2 y2
            int wstart = pw * stride_w_ - pad_w_;
            int hend = min(hstart + kernel_h_, height_);
            int wend = min(wstart + kernel_w_, width_);
            hstart = max(hstart, 0);
            wstart = max(wstart, 0);
            const int pool_index = ph * pooled_width_ + pw;
            for (int h = hstart; h < hend; ++h) {
              for (int w = wstart; w < wend; ++w) {
                const int index = h * width_ + w;//记录index偏差
                if (bottom_data[index] > top_data[pool_index]) {//不停迭代
                  top_data[pool_index] = bottom_data[index];
                  if (use_top_mask) {
                    top_mask[pool_index] = static_cast<Dtype>(index);//记录当前最大值的的坐标索引
                  } else {
                    mask[pool_index] = index;
                  }
                }
              }
            }
          }
        }
        // 计算偏移量,进入下一张图的index起始地址
        bottom_data += bottom[0]->offset(0, 1);
        top_data += top[0]->offset(0, 1);
        if (use_top_mask) {
          top_mask += top[0]->offset(0, 1);
        } else {
          mask += top[0]->offset(0, 1);
        }
      }
    }
    break;
 case PoolingParameter_PoolMethod_AVE://average_pooling
    for (int i = 0; i < top_count; ++i) {
      top_data[i] = 0;
    }
    // The main loop
    for (int n = 0; n < bottom[0]->num(); ++n) {//同样是主循环
      for (int c = 0; c < channels_; ++c) {
        for (int ph = 0; ph < pooled_height_; ++ph) {
          for (int pw = 0; pw < pooled_width_; ++pw) {
            int hstart = ph * stride_h_ - pad_h_;
            int wstart = pw * stride_w_ - pad_w_;
            int hend = min(hstart + kernel_h_, height_ + pad_h_);
            int wend = min(wstart + kernel_w_, width_ + pad_w_);
            int pool_size = (hend - hstart) * (wend - wstart);
            hstart = max(hstart, 0);
            wstart = max(wstart, 0);
            hend = min(hend, height_);
            wend = min(wend, width_);
            for (int h = hstart; h < hend; ++h) {
              for (int w = wstart; w < wend; ++w) {
                top_data[ph * pooled_width_ + pw] +=
                    bottom_data[h * width_ + w];
              }
            }
            top_data[ph * pooled_width_ + pw] /= pool_size;//获得相应的平均值
          }
        }
        // compute offset同理计算下一个图的起始地址
        bottom_data += bottom[0]->offset(0, 1);
        top_data += top[0]->offset(0, 1);
      }
    }
    break;
  case PoolingParameter_PoolMethod_STOCHASTIC:
    NOT_IMPLEMENTED;
    break;
  default:
    LOG(FATAL) << "Unknown pooling method.";
  }
backward_cpu

对于误差的反向传导
对于pooling层的误差传导,根据下式
\[\delta^l_j=upsample(\delta^{l+1}_{j})\cdot h(a^l_j)' \]
这里的Upsample具体可以根据相应的pooling方法来进行上采样,upsample的基本思想也是将误差进行的平摊到各个采样的对应点上。在这里pooling因为是线性的所以h这一项其实是可以省略的。
具体的计算推导过程请结合http://www.cnblogs.com/tornadomeet/p/3468450.html有详细的推导过程,结合代码中主循环中的最里项会更清晰的明白

template <typename Dtype>
void PoolingLayer<Dtype>::Backward_cpu(const vector<Blob<Dtype>*>& top,
      const vector<bool>& propagate_down, const vector<Blob<Dtype>*>& bottom) {
  if (!propagate_down[0]) {
    return;
  }
  const Dtype* top_diff = top[0]->cpu_diff();//首先获得上层top_blob的diff
  Dtype* bottom_diff = bottom[0]->mutable_cpu_diff();
  caffe_set(bottom[0]->count(), Dtype(0), bottom_diff);
  // We'll output the mask to top[1] if it's of size >1.
  const bool use_top_mask = top.size() > 1;
  const int* mask = NULL;  // suppress warnings about uninitialized variables
  const Dtype* top_mask = NULL;
  switch (this->layer_param_.pooling_param().pool()) {
  case PoolingParameter_PoolMethod_MAX:
    // The main loop
    if (use_top_mask) {
      top_mask = top[1]->cpu_data();
    } else {
      mask = max_idx_.cpu_data();
    }
    for (int n = 0; n < top[0]->num(); ++n) {
      for (int c = 0; c < channels_; ++c) {
        for (int ph = 0; ph < pooled_height_; ++ph) {
          for (int pw = 0; pw < pooled_width_; ++pw) {
            const int index = ph * pooled_width_ + pw;
            const int bottom_index =
                use_top_mask ? top_mask[index] : mask[index];//根据max pooling记录的mask位置,进行误差反转
            bottom_diff[bottom_index] += top_diff[index];
          }
        }
        bottom_diff += bottom[0]->offset(0, 1);
        top_diff += top[0]->offset(0, 1);
        if (use_top_mask) {
          top_mask += top[0]->offset(0, 1);
        } else {
          mask += top[0]->offset(0, 1);
        }
      }
    }
    break;
  case PoolingParameter_PoolMethod_AVE:
    // The main loop
    for (int n = 0; n < top[0]->num(); ++n) {
      for (int c = 0; c < channels_; ++c) {
        for (int ph = 0; ph < pooled_height_; ++ph) {
          for (int pw = 0; pw < pooled_width_; ++pw) {
            int hstart = ph * stride_h_ - pad_h_;
            int wstart = pw * stride_w_ - pad_w_;
            int hend = min(hstart + kernel_h_, height_ + pad_h_);
            int wend = min(wstart + kernel_w_, width_ + pad_w_);
            int pool_size = (hend - hstart) * (wend - wstart);
            hstart = max(hstart, 0);
            wstart = max(wstart, 0);
            hend = min(hend, height_);
            wend = min(wend, width_);
            for (int h = hstart; h < hend; ++h) {
              for (int w = wstart; w < wend; ++w) {
                bottom_diff[h * width_ + w] +=
                  top_diff[ph * pooled_width_ + pw] / pool_size;//mean_pooling中,bottom的误差值按pooling窗口中的大小计算,从上一层进行填充后,再除窗口大小
              }
            }
          }
        }
        // offset
        bottom_diff += bottom[0]->offset(0, 1);
        top_diff += top[0]->offset(0, 1);
      }
    }
    break;
  case PoolingParameter_PoolMethod_STOCHASTIC:
    NOT_IMPLEMENTED;
    break;
  default:
    LOG(FATAL) << "Unknown pooling method.";
  }
}

Deep Learning学习 之 CNN代码解析(MATLAB)

MATLAB实现CNN一般会用到deepLearnToolbox-master。但是根据Git上面的说明,现在已经停止更新了,而且有很多功能也不太能够支持,具体的请大家自习看一看Git中的README...
  • u013007900
  • u013007900
  • 2016年05月16日 21:00
  • 36346

Caffe源码(六): pooling_layer 分析

目录目录 简单介绍 主要函数 LayerSetUp 函数 Reshape 函数 Forward_cpu 函数 Backward_cpu 函数简单介绍pooling_layer 是Layer 的子类,实...
  • seven_first
  • seven_first
  • 2015年08月16日 14:52
  • 4604

Caffe源码解析(一) —— caffe.proto

caffe.proto
  • Quincuntial
  • Quincuntial
  • 2017年07月11日 18:05
  • 856

Caffe源码解析

作者:薛云峰(https://github.com/HolidayXue),主要从事视频图像算法的研究,就职于浙江捷尚视觉科技股份有限公司担任深度学习算法研究员。 本文来源微信公众号:深度学习大讲堂...
  • shitsnail
  • shitsnail
  • 2017年01月06日 17:22
  • 712

如何解读Caffe源码

如何解读Caffe源码导读Caffe是现在非常流行的深度学习库,能够提供高效的深度学习训练。该库是用C++编写,能够使用CUDA调用GPU进行加速。但是caffe内置的工具不一定能够满足用户的所有需求...
  • thesby
  • thesby
  • 2016年03月13日 21:42
  • 3618

CAFFE源码学习笔记之池化层pooling_layer

一、前言 池化层的输入来自上一个卷积层的输出,主要作用是提供了平移不变性,并且减少了参数的数量,防止过拟合现象的发生。比如在最大池化中,选择区域内最大的值为采样点,这样在发生平移的时候,采样点不变。...
  • sinat_22336563
  • sinat_22336563
  • 2017年04月10日 21:15
  • 1030

CAFFE源码学习笔记之一

单纯的将自己的笔记上的内容一点点搬运过来。 在复习卷积神经网络的同时还能学习一下系统级c++程序的规范和技巧。 ××××××××××××××××× 一、前言 本系列就是要把caffe这样一个系...
  • sinat_22336563
  • sinat_22336563
  • 2017年03月30日 11:18
  • 327

caffe源码解析

Caffe源码(十一):io.cpp 分析 目录目录 简单介绍 主要函数ReadProtoFromTextFile 函数 WriteProtoToTextFile 函数 ReadProtoF...
  • YLH9604
  • YLH9604
  • 2017年07月07日 20:31
  • 200

caffe源码分析(1)——protobuf

caffe源码分析(1)——protobuf
  • woyaopojie1990
  • woyaopojie1990
  • 2015年04月29日 11:44
  • 944

caffe源码深入学习1:caffe.cpp解析

距离笔者接触深度学习已经将近半年了,在这段时间中,笔者最先接触的是lenet网络,然后就学习了2015-2016年非常火爆的fast-rcnn与faster-rcnn,到最近自己利用深度学习搞事情,笔...
  • jiongnima
  • jiongnima
  • 2017年02月13日 20:20
  • 3135
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:Caffe源码解析7:Pooling_Layer
举报原因:
原因补充:

(最多只允许输入30个字)