SPP-Net(Spatial Pyramid Pooling Network)论文笔记

川长思鸟来 2022-05-13 14:58 332阅读 0赞

1. 论文思想

SPP-Net的提出首先是为了解决传统CNN网络对于输入图像尺寸具有严格的大小限制,其原因也就是最后的全连接层需要的输入尺寸是固定的。那么对于一副比较大的图像就需要进行剪裁了,如下图所示:
在这里插入图片描述
但是这样会造成数据失真以及数据的不完整。那么,要使CNN网络可以接受任意图像尺寸的输入,那就需要将提供给全连接层的参数量固定下来,这就需要SPP-Net了,也就是上图中下面的结构。其是在最后一个卷积的基础上增加一个SPP层,使得给卷积的数据固定,从而使得网络可以输入任意尺寸的图像。
对于分类问题,在Pascal VOC 2007和Caltech101数据集上,SPP-net使用单一的全图像表示,无需微调,就能实现较好的分类结果;对于目标检测问题上,使用SPP-net,网络只从整个图像计算特征映射一次,然后在任意区域(子图像)中汇集特征,生成固定长度的表示,以训练检测器,避免了重复计算卷积的特征,在Pascal VOC 2007取得了与RCNN一致甚至更好的检测结果,并且速度快于RCNN。
SPP-Net具有的特点
(1)不管输入的尺寸如何,网络输出的大小总是固定的
(2)在多个空间尺度上使用单个滑动窗,多尺度是为了提升性能
(3)由于输入尺度的灵活性,SPP可以在可变尺度下提取特征

2. SPP Layer

CNN网络中卷积层是对输入数据的尺寸不做要求的,但是后面接的全连接层就有要求了,论文里面借鉴了BOW模型的思想,目的就是让特征表达的数目或者维度固定下来。
在这篇论文里面是采用空间金字塔的思想,限定输出的数据,其网络结构如下:

在这里插入图片描述
下面是一个典型3层结构的SP-Net结构:
在这里插入图片描述
在上图中网络包含了 3 ∗ 3 , 2 ∗ 2 , 1 ∗ 1 3*3, 2*2, 1*1 3∗3,2∗2,1∗1的bins(用 n ∗ n n*n n∗n表示),而需要计算的互动窗口 s i z e X sizeX sizeX与 s t r i d e stride stride论文中是通过向上取整和向下取整实现的,对于输入的feature map大小为 a ∗ a a*a a∗a,则前面的两个参数可以通过如下计算得到:
s i z e X = a / n [ 向 上 取 整 ] sizeX = a/n[向上取整] sizeX=a/n[向上取整]
s i z e X = a / n [ 向 下 取 整 ] sizeX = a/n[向下取整] sizeX=a/n[向下取整]

3. Caffe中的实现

3.1 Caffe中的运用

首先来看下在proto文件中SPP的定义:

  1. #caffe.proto中的关于SPP层参数的定义
  2. message SPPParameter {
  3. enum PoolMethod {
  4. MAX = 0;
  5. AVE = 1;
  6. STOCHASTIC = 2;
  7. }
  8. optional uint32 pyramid_height = 1;
  9. optional PoolMethod pool = 2 [default = MAX]; // The pooling method
  10. enum Engine {
  11. DEFAULT = 0;
  12. CAFFE = 1;
  13. CUDNN = 2;
  14. }
  15. optional Engine engine = 6 [default = DEFAULT];
  16. }

接下来是在Caffe的prototxt中该如何定义:

  1. # SPP网络定义范例
  2. #之前的Pooling层
  3. #layer {
  4. # name: "pool5"
  5. # type: "Pooling"
  6. # bottom: "conv5"
  7. # top: "pool5"
  8. # pooling_param {
  9. # pool: MAX
  10. # kernel_size: 3
  11. # stride: 2
  12. # }
  13. #}
  14. layer {
  15. name: "spatial_pyramid_pooling"
  16. type: "SPP"
  17. bottom: "conv5"
  18. top: "pool5"
  19. spp_param {
  20. pool: MAX
  21. pyramid_height: 2 # SPP的level的数量
  22. }
  23. }

3.2 SPP-Net在Caffe中的实现

首先还是来看看SPP-Net在Caffe中的整体结构是什么样子的

  1. for (int i = 0; i < pyramid_height_; i++) {
  2. // pooling layer input holders setup 整个pooling结构的输入输出数据存放位置
  3. pooling_bottom_vecs_.push_back(new vector<Blob<Dtype>*>);
  4. pooling_bottom_vecs_[i]->push_back(split_top_vec_[i]);
  5. // pooling layer output holders setup
  6. pooling_outputs_.push_back(new Blob<Dtype>());
  7. pooling_top_vecs_.push_back(new vector<Blob<Dtype>*>);
  8. pooling_top_vecs_[i]->push_back(pooling_outputs_[i]);
  9. // pooling layer setup
  10. LayerParameter pooling_param = GetPoolingParam( //按照层获得Pooling的参数
  11. i, bottom_h_, bottom_w_, spp_param);
  12. //将得到的Pooling参数生成Pooling layer 再关联网络的输入与输出
  13. pooling_layers_.push_back(shared_ptr<PoolingLayer<Dtype> > (
  14. new PoolingLayer<Dtype>(pooling_param)));
  15. pooling_layers_[i]->SetUp(*pooling_bottom_vecs_[i], *pooling_top_vecs_[i]);
  16. // flatten layer output holders setup flatten 数据准备与参数设置,参数输入为Pooling之后的数据
  17. flatten_outputs_.push_back(new Blob<Dtype>());
  18. flatten_top_vecs_.push_back(new vector<Blob<Dtype>*>);
  19. flatten_top_vecs_[i]->push_back(flatten_outputs_[i]);
  20. // flatten layer setup
  21. LayerParameter flatten_param;
  22. flatten_layers_.push_back(new FlattenLayer<Dtype>(flatten_param));
  23. flatten_layers_[i]->SetUp(*pooling_top_vecs_[i], *flatten_top_vecs_[i]);
  24. // concat layer input holders setup 把flatten之后的数据concat起来
  25. concat_bottom_vec_.push_back(flatten_outputs_[i]);
  26. }
  27. // concat layer setup 构建concat层
  28. LayerParameter concat_param;
  29. concat_layer_.reset(new ConcatLayer<Dtype>(concat_param));
  30. concat_layer_->SetUp(concat_bottom_vec_, top);

从上面的代码中可以看到,SPP-Net在Caffe中的实现是通过Split_layer、Pooling_layer、Concat_layer实现的,其中会根据当前的pyramid level来设置当前Pooling的参数,那么它里面的参数计算是这样计算完成的。计算过程在GetPoolingParam函数中实现的。

  1. //根据pyramid level确定Pooling的参数
  2. template <typename Dtype>
  3. LayerParameter SPPLayer<Dtype>::GetPoolingParam(const int pyramid_level,
  4. const int bottom_h, const int bottom_w, const SPPParameter spp_param) {
  5. LayerParameter pooling_param;
  6. int num_bins = pow(2, pyramid_level); //按照2的幂设置Pooling的参数
  7. // find padding and kernel size so that the pooling is
  8. // performed across the entire image
  9. int kernel_h = ceil(bottom_h / static_cast<double>(num_bins));
  10. // remainder_h is the min number of pixels that need to be padded before
  11. // entire image height is pooled over with the chosen kernel dimension
  12. int remainder_h = kernel_h * num_bins - bottom_h;
  13. // pooling layer pads (2 * pad_h) pixels on the top and bottom of the
  14. // image.
  15. int pad_h = (remainder_h + 1) / 2;
  16. // similar logic for width
  17. int kernel_w = ceil(bottom_w / static_cast<double>(num_bins));
  18. int remainder_w = kernel_w * num_bins - bottom_w;
  19. int pad_w = (remainder_w + 1) / 2;
  20. pooling_param.mutable_pooling_param()->set_pad_h(pad_h);
  21. pooling_param.mutable_pooling_param()->set_pad_w(pad_w);
  22. pooling_param.mutable_pooling_param()->set_kernel_h(kernel_h);
  23. pooling_param.mutable_pooling_param()->set_kernel_w(kernel_w);
  24. pooling_param.mutable_pooling_param()->set_stride_h(kernel_h);
  25. pooling_param.mutable_pooling_param()->set_stride_w(kernel_w);
  26. switch (spp_param.pool()) {
  27. case SPPParameter_PoolMethod_MAX:
  28. pooling_param.mutable_pooling_param()->set_pool(
  29. PoolingParameter_PoolMethod_MAX);
  30. break;
  31. case SPPParameter_PoolMethod_AVE:
  32. pooling_param.mutable_pooling_param()->set_pool(
  33. PoolingParameter_PoolMethod_AVE);
  34. break;
  35. case SPPParameter_PoolMethod_STOCHASTIC:
  36. pooling_param.mutable_pooling_param()->set_pool(
  37. PoolingParameter_PoolMethod_STOCHASTIC);
  38. break;
  39. default:
  40. LOG(FATAL) << "Unknown pooling method.";
  41. }
  42. return pooling_param;
  43. }

可以从开始就看到,Pooling参数是按照2的幂次为基础进行计算的。对于SPP-Net的前向传播与反向传播可以参考对应类型层的正反传,其内部也是调用这些层的前向与后向函数实现的。

发表评论

表情:
评论列表 (有 0 条评论,332人围观)

还没有评论,来说两句吧...

相关阅读