1 原理
何恺明2015年于《Spatial Pyramid Pooling in Deep Convolutional Networks for Visual Recognition》中提出。SPP 在论文中的主要工作是解决 CNN 要求输入图片尺寸固定的问题。(其它解决方案有用 Conv 层替代 FC 层)
![](https://i-blog.csdnimg.cn/blog_migrate/aa873bfc434c7763c3453a3b68045957.png)
如上图所示,输入层 input image 大小任意,通过 conv5 的卷积得到任意大小的输出特征映射 feature maps,而后送入 SPP 层。图中蓝、绿、灰三色格子分别代表将 feature maps 分成 16、4 和 1份(不一定是等分)。设定输出通道数为 256,对分割后的每一份进行池化操作,则通过 SPP 层可将 feature maps 转化成 16 × 256 + 4 × 256 + 1 × 256 = 21 × 256 16 \times 256 + 4 \times 256 + 1 \times 256 = 21 \times 256 16×256+4×256+1×256=21×256 的矩阵,在送入 FC 层时扩展成一维矩阵( 1 × 10752 1 \times 10752 1×10752),因此该 FC 层的参数就可以设置为 10752,如此便无需要求输入图片尺寸大小固定。
2 计算
SPP 层输出运算。设核的高度为
K
h
K_h
Kh,高度方向的步长为
S
h
S_h
Sh,高度方向的填充数量为
P
h
P_h
Ph(需要乘以 2),则相关公式如下:
K
h
=
⌈
h
i
n
n
⌉
S
h
=
⌈
h
i
n
n
⌉
P
h
=
⌊
K
h
∗
n
−
h
i
n
+
1
2
⌋
h
n
e
w
=
2
∗
P
h
+
h
i
n
K_h = \lceil \frac{h_{in}}{n} \rceil \\ S_h = \lceil \frac{h_{in}}{n} \rceil \\ P_h = \lfloor \frac{K_h * n - h_{in} + 1}{2} \rfloor \\ h_{new} = 2*P_h + h_{in}
Kh=⌈nhin⌉Sh=⌈nhin⌉Ph=⌊2Kh∗n−hin+1⌋hnew=2∗Ph+hin
K
w
=
⌈
w
i
n
n
⌉
S
w
=
⌈
w
i
n
n
⌉
P
w
=
⌊
K
w
∗
n
−
w
i
n
+
1
2
⌋
w
n
e
w
=
2
∗
P
w
+
w
i
n
K_w = \lceil \frac{w_{in}}{n} \rceil \\ S_w = \lceil \frac{w_{in}}{n} \rceil \\ P_w = \lfloor \frac{K_w * n - w_{in} + 1}{2} \rfloor \\ w_{new} = 2*P_w + w_{in}
Kw=⌈nwin⌉Sw=⌈nwin⌉Pw=⌊2Kw∗n−win+1⌋wnew=2∗Pw+win
池化后矩阵大小计算公式为:
- 没有步长: ( h + 2 p − f + 1 ) ∗ ( w + 2 p − f + 1 ) (h+2p−f+1)∗(w+2p−f+1) (h+2p−f+1)∗(w+2p−f+1)
- 有步长:
⌊ h + 2 p − f s + 1 ⌋ ∗ ⌊ w + 2 p − f s + 1 ⌋ ⌊h+2p−fs+1⌋*⌊w+2p−fs+1⌋ ⌊h+2p−fs+1⌋∗⌊w+2p−fs+1⌋
对上述公式进行检验。设输入数据为 (10, 7, 11),池化数量为 (4, 4),Kernel 大小为 (2, 3),Stride 大小为(2, 3),则 Padding 为 (1, 1)。利用上述公式计算得到池化后的矩阵大小为:4 ∗ 4。
3 代码
使用 PyTorch 构建一个 SPP 层,代码如下:
#coding=utf-8
import math
import torch
import torch.nn.functional as F
# 构建SPP层(空间金字塔池化层)
class SPPLayer(torch.nn.Module):
def __init__(self, num_levels, pool_type='max_pool'):
super(SPPLayer, self).__init__()
self.num_levels = num_levels
self.pool_type = pool_type
def forward(self, x):
num, c, h, w = x.size() # num:样本数量 c:通道数 h:高 w:宽
for i in range(self.num_levels):
level = i+1
kernel_size = (math.ceil(h / level), math.ceil(w / level))
stride = (math.ceil(h / level), math.ceil(w / level))
pooling = (math.floor((kernel_size[0]*level-h+1)/2), math.floor((kernel_size[1]*level-w+1)/2))
# 选择池化方式
if self.pool_type == 'max_pool':
tensor = F.max_pool2d(x, kernel_size=kernel_size, stride=stride, padding=pooling).view(num, -1)
else:
tensor = F.avg_pool2d(x, kernel_size=kernel_size, stride=stride, padding=pooling).view(num, -1)
# 展开、拼接
if (i == 0):
x_flatten = tensor.view(num, -1)
else:
x_flatten = torch.cat((x_flatten, tensor.view(num, -1)), 1)
return x_flatten
【参考】