回顾历史,SPPNet 标志着人们更加深入的使用 CNN 去做目标检测问题,这篇论文的作者们暗埋下的一条思绪让我深受启发,在此特地向他们致敬!

论文:Spatial Pyramid Pooling in Deep Convolutional Networks for Visual Recognition

核心思想

R-CNN 扔了一块敲门砖,从此后来者络绎不绝,SPPNet 的作者们点“砖”为“塔”,从此一起踏入了目标检测的炼丹事业。针对 R-CNN 提出了两点改进思想:

改进1:先卷积后取区域

R-CNN 提取特征的顺序是先生成区域、再通过卷积神经网络提取特征,虽然相比传统的滑窗策略减少了大量的运算,但是依旧有大量的算力冗余。

不同区域会有大量的重叠,对同一个区域的重复计算是不符合经济学原理的,于是何恺明等作者改变了一下顺序——先卷积,再特征图上取区域特征。这一操作虽然看似简单,但实际上凡而不凡。

改进2:不固定输入尺寸


Crop & Warp - SPP Net

一张 16:9 的图片 Resize1:1 的图片,就算是人眼看都会觉得很难受,更何况对卷积核。抱着让输入更加原汁原味,对卷积核更友好地想法,何恺明等人思索出了 SPPooling 层,解决了不定尺寸输入、定尺寸输出的问题。


Pool Layer - SPP Net

他们发现必须定尺寸输入的原因是后面的全连接层需要固定参数,那么只需要在 FC 层前加一个自定义的 Pooling 层,保证传到下一层的输入固定。这就是他们想出解决办法大致思路,但是他们并不知道自己实现的这个 SPPooling 对后面有多大的影响。

这个 SPPooling 层大致原理就是「拼凑七巧板」,我们需要的输出是一个固定的模样,输入是和输出不一样大小尺寸的数据,那么我们将输入通过 Pooling 将输入变成输出的模样、或者变成输出模样的一部分,最后达到输出固定尺寸数据的效果。

这一改进可谓是在 FCN 前的伟大创举,不固定输入尺寸保留原有特征,对卷积核更加友好,从而提升识别的效果。

流程步骤

仍然是四个步骤:

1、使用 EdgeBoxes 算法生成候选区域
2、使用 CNN 提取全图特征
3、将候选区域特征输入到 SVM 分类器,判别输入类别
4、以回归的方式精修候选框

注:作者在此选择了 EdgeBoxes 算法而不是 Selective Search 算法。

核心部分

SPPNet 和 R-CNN 的区别如我们再核心思想上讲的,我们在此只探讨「卷积后取特征」以及「SPPooling」,虽然生成候选区域的算法变成了 EdgeBoxes 但是改变不了他们提取的特征还是 Low Level 的特征。

先卷积,后取特征


Feature Map

其实从这里开始,就体现了作者们的功力,他们对 feature map 进行可视化处理,发现 feature map 里面有丰富的抽象后的信息,给后续的 R-CNN 大升级埋下了伏笔。

先卷积后取特征,最为麻烦的问题就是原始图和卷积处理后的图尺寸是不一样的,所以我们首先要找到对应关系,这部分作者们在 Appendix 中有详细的介绍。

假设原图某点的坐标是 $(x,y)$ ,特征图该点的坐标是 $(x’,y’)$ ,由于不同层处理后的特征图大小尺寸不一样,所以转换有一个中间参数 $S$ ,最后得到 $(x,y) = (Sx’,Sy’)$ 。

那么我们就可以得到如下坐标公式:
Left (top) boundary: $x’ = ⌊x/S⌋ + 1$
Right (bottom) boundary: $x’ = ⌈x/S⌉ − 1$

其中 $S$ 是卷积神经网络中所有的 strides 的乘积,包含了池化、卷积的 stride


Backbone

我们可以从上图中计算出对应的 $S$ :

Backbone Calculus S
ZF-5 $2*2*2*2$ 16
Overfeat-5/7 $2*3*2$ 12

SPPooling


Pool Layer - SPP Net

SPPNet 最核心的还是 SPPooling ,解决了可以输入任意尺寸图片的问题,而对「目标检测」而言,图像中物体的尺寸是一个很重要的特征。

SPPNet 实际上做了一个自适应的功能,利用 PoolingStidePadding 对输入数据进行任意尺寸的改变。一个 SPPooling 层可能会有多个 Pooling ,多个 Pooling 带来的好处就是 多尺度观察 ,这也是 SPPNet 准确度有提高的原因之一。

上面是论文给的图,下面给出 SPPooling 层的实现代码,帮助我们更好理解:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import math
def spatial_pyramid_pool(self,previous_conv, num_sample, previous_conv_size, out_pool_size):
'''
previous_conv: a tensor vector of previous convolution layer
num_sample: an int number of image in the batch
previous_conv_size: an int vector [height, width] of the matrix features size of previous convolution layer
out_pool_size: a int vector of expected output size of max pooling layer

returns: a tensor vector with shape [1 x n] is the concentration of multi-level pooling
'''
# print(previous_conv.size())
for i in range(len(out_pool_size)):
# print(previous_conv_size)
h_wid = int(math.ceil(previous_conv_size[0] / out_pool_size[i]))
w_wid = int(math.ceil(previous_conv_size[1] / out_pool_size[i]))
h_pad = (h_wid*out_pool_size[i] - previous_conv_size[0] + 1)/2
w_pad = (w_wid*out_pool_size[i] - previous_conv_size[1] + 1)/2
maxpool = nn.MaxPool2d((h_wid, w_wid), stride=(h_wid, w_wid), padding=(h_pad, w_pad))
x = maxpool(previous_conv)
if(i == 0):
spp = x.view(num_sample,-1)
# print("spp size:",spp.size())
else:
# print("size:",spp.size())
spp = torch.cat((spp,x.view(num_sample,-1)), 1)
return spp

代码来源:sppnet-pytorch - Github

在同年 9 月,Google 团队发布了第一版本的 Inception ,里面就借鉴了多尺度观察的思想,虽然一个是池化层一个是卷积核,但并不影响这个思想的伟大。

总结 Summary

SPPNet 改变了卷积的顺序,减少了冗余的计算量,大大提高了对图片处理的速度。也从这一思想可以看出作者们对卷积神经网络的理解十分深刻,特别是从这个时候开始就埋下了对 Feature Map 极致利用的主线。将金字塔池化 SPPooling 引入到卷积神经网络中,从此无需固定图像输入尺寸,保留更多的特征、对卷积核更加友好。

虽然其他的部分都是基于 R-CNN 的,但是他们这两大改进所体现出来的思想就足以让人注目眺望。

参考资料 Reference

  1. SPPNet “Spatial Pyramid Pooling in Deep Convolutional Networks for Visual Recognition”
  2. RCNN–对象检测的又一伟大跨越 2(包括SPPnet、Fast RCNN)
  3. SPP-Net论文详解