Spatial Pyramid Pooling
作者:互联网
1. 摘要
现有的深度卷积神经网络(CNNs)需要一个固定大小的(例如,224×224)的输入图像。这个要求是“人工的”,可能会降低对任意大小/尺度的图像或子图像的识别精度。在这项工作中,我们为网络配备了另一种池化策略,即“空间金字塔池化”,以消除上述需求。新的网络结构,称为SPP-net,可以生成一个固定长度的表示,而不管图像的大小/规模。金字塔池对对象变形也具有鲁棒性。有了这些优点,SPP-net一般应该改进所有基于cnn的图像分类方法。
2. 介绍
我们正在目睹我们的视觉社区的一个快速、革命性的变化,主要是由深度卷积神经网络(CNNs)[1]和大规模训练数据[2]的可用性造成的。基于深度网络的方法最近在图像分类,目标检测,上大大改进了许多其他识别任务[,甚至非识别任务。然而,在cnn的训练和测试中存在一个技术问题:普遍的cnn需要一个固定的输入图像大小(例如,224×224),这限制了高宽比和输入图像的比例。当应用于任意大小的图像时,当前的方法大多将输入图像的固定大小,或通过裁剪,或通过扭曲,如图1(上)所示。但裁剪的区域可能不包含整个对象,而扭曲的内容可能导致不必要的几何失真。
由于内容丢失或失真,可能会影响识别精度。此外,一个预定义的当物体的尺度变化时,可能不合适。固定输入大小忽略了涉及规模的问题。
为什么CNN需要一个固定的输入大小呢
CNN主要由两部分组成:卷积层和后面的全连接层。卷积层以滑动窗口的方式运行,输出特征映射表示激活的空间排列(图2)。事实上,卷积层并不需要固定的图像大小,并且可以生成任何大小的特征映射。另一方面,完全连接的图层需要根据它们的定义有固定的大小/长度的输入。因此,固定大小的约束只来自于全连接的层,它们存在于网络的更深层次的阶段。
SPPNet
作者针对这个问题,提出了在CNN的最后一个卷积层之后,加入一个SPP层,也就是空间金字塔池化,对之前卷积得到的特征进行”整合”(aggregation),然后得到一个固定长度的特征向量,再传到全连层去.如下图.
SPP是词袋模型(Bag-of-Words)的扩展.为什么说是扩展?词袋模型没有特征的空间信息(就像它只能统计一个句子中每个单词的词频,而不能记录词的位置信息一样).在深层CNN里加入SPP会有3个优势: 1) 相比之前的滑动窗池化(sliding window pooling),SPP可以对不同维度输入得到固定长度输出. 2) SPP使用了多维的spatial bins(我的理解就是多个不同大小的窗),而滑动窗池化只用了一个窗. 3) 因为输入图片尺度可以是任意的,SPP就提取出了不同尺度的特征.作者说这3点可以提高深度网络的识别准确率。
SPP结构图:
输入不同尺寸的图片,经过卷积层会输出的不同大小的feature map.把最以后一次卷积后的池化层去掉,换成一个SPP去做最大池化操作(max pooling).如果最后一次卷积得到了k个feature map,也就是有k个filter,SPP有M个bin(M个不同维度的pyramid),那经过SPP得到的是一个kM维的向量.我的理解是,比如上图中第一个feature map有16个bin,一共有256个feature map,每一个经过16个bin的max pooling得到16个数,那256个feature map就是16x256的向量了,第二个产生4x256维向量,SPP的bin大小可以选择多个,所以经过SPP还能产生4x256,1x256维的向量。
实际上,SPP也就是多个池化层的组合,再将每个池化层的输出拼接起来,池化层不像卷积层,没有什么参数需要学习;所以需要确定的就只有池化层的kernel size和strid。
取整符号:
- ⌊⌋:向下取整符号 \(\lfloor 59/60\rfloor\)=0,有时也用 floor() 表示
- ⌈⌉:向上取整符号 \(\lceil 59/60\rceil\)=1, 有时也用ceil() 表示
池化后矩阵大小计算公式:
- 没有步长(Stride):\((h+2p−f+1)∗(w+2p−f+1)(h+2p−f+1)∗(w+2p−f+1)\)
- 有步长(Stride):\(\lfloor \frac{h+2p−f}{s}+1\rfloor*\lfloor \frac{w+2p−f}{s}+1\rfloor\)
完整公式:
\[H_{out}=\lfloor \frac{H_{in}+2\times padding[0]-dilation[0]\times (kernel_size[0]-1)-1}{stride[0]}+1\rfloor \\ W_{out}=\lfloor \frac{W_{in}+2\times padding[1]-dilation[1]\times (kernel_size[1]-1)-1}{stride[1]}+1\rfloor \]
SPP所用公式:
假设
- 输入数据大小是\((c,h_{in},w_{in})\),分别表示通道数,高度,宽度
- 池化数量:\((n,n)\)
那么则有
- 核(Kernel)大小: \(⌈\frac{h_{in}}{n},\frac{w_{in}}{n}⌉=ceil(\frac{h_{in}}{n},\frac{w_{in}}{n})\)
- 步长(Stride)大小: \(⌊\frac{h_{in}}{n},\frac{w_{in}}{n}⌋=floor(\frac{h_{in}}{n},\frac{w_{in}}{n})\)
我们可以验证一下,假设输入数据大小是(10,7,11), 池化数量(2,2):
那么核大小为(4,6), 步长大小为(3,5), 得到池化后的矩阵大小的确是2∗2。
Warning:
这里还有一个问题,例如输入数据大小分别为\((1,1,7,7),(1,1,8,8)\)的时候,池化数量为\((3,3)\),根据上述公式我们计算出的核大小都是\((3,3)\),步长都是\((2,2)\)。
发现在feature map为8的上面会多出一个,我们直接舍去就好。
import torch
import torch.nn as nn
# 仅定义一个 3x3 的池化层窗口
m = nn.MaxPool2d(kernel_size=(3, 3),stride=2)
input1 = torch.randn(1, 1, 7, 7)
input2=torch.randn(1,1,8,8) # 再增加一维
input2[:,:,:-1,:-1]=input1
out1=m(input1)
out2=m(input2)
input1:
tensor([[[[-1.1362, 0.1328, 1.4978, -0.0211, -1.3889, -0.0423, -1.0154],
[-0.1284, -0.3292, -0.0764, -1.4095, 0.3154, 0.6622, -0.4282],
[-1.2616, 2.4910, -0.8188, -1.3509, 1.3286, -0.5559, 0.2009],
[-0.2745, 0.4059, 1.1654, -0.7211, 0.7951, -0.6104, -0.0327],
[-0.4799, 0.4474, -0.8476, 1.5097, 0.8168, -0.0067, -0.0267],
[-0.0743, -0.3155, -0.2110, 2.1554, -1.4772, 0.0029, 1.0181],
[ 0.2785, -0.6448, 1.8553, 1.3306, 1.3171, 0.6611, -0.7505]]]])
input2:
tensor([[[[-1.1362, 0.1328, 1.4978, -0.0211, -1.3889, -0.0423, -1.0154,
-1.3181],
[-0.1284, -0.3292, -0.0764, -1.4095, 0.3154, 0.6622, -0.4282,
0.5563],
[-1.2616, 2.4910, -0.8188, -1.3509, 1.3286, -0.5559, 0.2009,
-0.2980],
[-0.2745, 0.4059, 1.1654, -0.7211, 0.7951, -0.6104, -0.0327,
-1.4227],
[-0.4799, 0.4474, -0.8476, 1.5097, 0.8168, -0.0067, -0.0267,
-0.2595],
[-0.0743, -0.3155, -0.2110, 2.1554, -1.4772, 0.0029, 1.0181,
0.3205],
[ 0.2785, -0.6448, 1.8553, 1.3306, 1.3171, 0.6611, -0.7505,
-0.3688],
[ 1.1252, -0.5459, -0.4047, 0.9076, 0.5693, -0.8110, 0.6319,
1.4798]]]])
out1.shape:
torch.Size([1, 1, 3, 3])
out2.shape:
torch.Size([1, 1, 3, 3])
out1:
tensor([[[[2.4910, 1.4978, 1.3286],
[2.4910, 1.5097, 1.3286],
[1.8553, 2.1554, 1.3171]]]])
out2:
tensor([[[[2.4910, 1.4978, 1.3286],
[2.4910, 1.5097, 1.3286],
[1.8553, 2.1554, 1.3171]]]])
当然还可以通过公式修改,更改padding,这样就不会出现多一个的情况:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YYTX896v-1644117964667)(C:\Users\Liujiawang\AppData\Roaming\Typora\typora-user-images\image-20220206112200671.png)]
具体参考:
3. 代码
#coding=utf-8
import math
import torch
import torch.nn.functional as F
# 构建SPP层(空间金字塔池化层)
class SPPLayer(torch.nn.Module):
def __init__(self, levels, pool_type='max_pool'):
super(SPPLayer, self).__init__()
self.levels = levels # [[3,3],[4,4],[5,5]]
self.pool_type = pool_type
def forward(self, x):
num, c, h, w = x.size() # num:样本数量 c:通道数 h:高 w:宽
for i in range(len(self.levels)):
level = self.levels[i]
kernel_size = (math.ceil(h / level[0]), math.ceil(w / level[1]))
stride = (math.floor(h / level), math.floor(w / level))
# 选择池化方式
if self.pool_type == 'max_pool':
tensor = F.max_pool2d(x, kernel_size=kernel_size, stride=stride).view(num, -1)
else:
tensor = F.avg_pool2d(x, kernel_size=kernel_size, stride=stride).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
Reference:
[1]Spatial Pyramid Pooling in Deep Convolutional Networks for Visual Recognition
标签:Pyramid,frac,SPP,torch,Pooling,Spatial,大小,卷积,size 来源: https://www.cnblogs.com/a-runner/p/15865423.html