想直接看公式的可跳至第三节 3.公式修正python
首先须要知道为何会须要SPP。git
咱们都知道卷积神经网络(CNN)由卷积层和全链接层组成,其中卷积层对于输入数据的大小并无要求,惟一对数据大小有要求的则是第一个全链接层,所以基本上全部的CNN都要求输入数据固定大小,例如著名的VGG模型则要求输入数据大小是 (224*224) 。github
固定输入数据大小有两个问题:spring
1.不少场景所获得数据并非固定大小的,例如街景文字基本上其高宽比是不固定的,以下图示红色框出的文字。网络
2.可能你会说能够对图片进行切割,可是切割的话极可能会丢失到重要信息。框架
综上,SPP的提出就是为了解决CNN输入图像大小必须固定的问题,从而可使得输入图像高宽比和大小任意。ide
更加具体的原理可查阅原论文:Spatial Pyramid Pooling in Deep Convolutional Networks for Visual Recognition学习
上图是原文中给出的示意图,须要从下往上看:spa
那么将特征映射分红若干等分是作什么用的呢? 咱们看SPP的名字就是到了,是作池化操做,通常选择MAX Pooling,即对每一份进行最大池化。code
咱们看上图,经过SPP层,特征映射被转化成了16X256+4X256+1X256 = 21X256的矩阵,在送入全链接时能够扩展成一维矩阵,即1X10752,因此第一个全链接层的参数就能够设置成10752了,这样也就解决了输入数据大小任意的问题了。
注意上面划分红多少份是能够本身是状况设置的,例如咱们也能够设置成3X3等,但通常建议仍是按照论文中说的的进行划分。
理论应该理解了,那么如何实现呢?下面将介绍论文中给出的计算公式,可是在这以前先要介绍两种计算符号以及池化后矩阵大小的计算公式:
取整符号:
⌊⌋:向下取整符号 ⌊59/60⌋=0,有时也用 floor() 表示
⌈⌉:向上取整符号 ⌈59/60⌉=1, 有时也用ceil() 表示
池化后矩阵大小计算公式:
- 没有步长(Stride):\((h+2p-f+1)*(w+2p-f+1)\)
- 有步长(Stride):⌊\(\frac{h+2p-f}{s}\)+1⌋*⌊\(\frac{w+2p-f}{s}\)+1⌋
假设
那么则有
咱们能够验证一下,假设输入数据大小是\((10, 7, 11)\), 池化数量\((2, 2)\):
那么核大小为\((4,6)\), 步长大小为\((3,5)\), 获得池化后的矩阵大小的确是\(2*2\)。
是的,论文中给出的公式的确有些疏漏,咱们仍是以举例子的方式来讲明
假设输入数据大小和上面同样是\((10, 7, 11)\), 可是池化数量改成\((4,4)\):
此时核大小为\((2,3)\), 步长大小为\((1,2)\),获得池化后的矩阵大小的确是\(6*5\) ←[简单的计算矩阵大小的方法:(7=2+1*5, 11=3+2*4)],而不是\(4*4\)。
那么问题出在哪呢?
咱们忽略了padding的存在(我在原论文中没有看到关于padding的计算公式,若是有的话。。。那就是我看走眼了,麻烦提示我一下在哪一个位置写过,谢谢)。
仔细看前面的计算公式咱们很容易发现并无给出padding的公式,在通过N次使用SPP计算获得的结果与预期不同以及查找各类网上资料(尽管少得可怜)后,现将加入padding后的计算公式总结以下。
\(K_h = ⌈\frac{h_{in}}{n}⌉=ceil(\frac{h_{in}}{n})\)
\(S_h = ⌈\frac{h_{in}}{n}⌉=ceil(\frac{h_{in}}{n})\)
\(p_h = ⌊\frac{k_h*n-h_{in}+1}{2}⌋=floor(\frac{k_h*n-h_{in}+1}{2})\)
\(h_{new} = 2*p_h +h_{in}\)
\(K_w = ⌈\frac{w_{in}}{n}⌉=ceil(\frac{w_{in}}{n})\)
\(S_w = ⌈\frac{w_{in}}{n}⌉=ceil(\frac{w_{in}}{n})\)
\(p_w = ⌊\frac{k_w*n-w_{in}+1}{2}⌋=floor(\frac{k_w*n-w_{in}+1}{2})\)
\(w_{new} = 2*p_w +w_{in}\)
- \(k_h\): 表示核的高度
- \(S_h\): 表示高度方向的步长
- \(p_h\): 表示高度方向的填充数量,须要乘以2
注意核和步长的计算公式都使用的是ceil(),即向上取整,而padding使用的是floor(),即向下取整。
如今再来检验一下:
假设输入数据大小和上面同样是\((10, 7, 11)\), 池化数量为\((4,4)\):
Kernel大小为\((2,3)\),Stride大小为\((2,3)\),因此Padding为\((1,1)\)。
利用矩阵大小计算公式:⌊\(\frac{h+2p-f}{s}\)+1⌋*⌊\(\frac{w+2p-f}{s}\)+1⌋获得池化后的矩阵大小为:\(4*4\)。
这里我使用的是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
上述代码参考: sppnet-pytorch
为防止原做者将代码删除,我已经Fork了,也能够经过以下地址访问代码:
marsggbo/sppnet-pytorch