1 动机
常见的用于识别、分类的cnn都要求输入的图像size是固定的,如224*224。因为CNN由卷积层和全连接层两部分组成。卷积层就是一个大小固定的滑动窗在图像上滑动计算窗口覆盖区域的特征,这其实对图像的size是没有要求的,无论输入的size是什么,都可以根据滑动步长和窗口大小生成对应size的输出/feature map。
全连接层需要固定长度的输入,如图,绿色的那一层是前面黄色那一层拉升之后的结果,从绿色到红色那一层,本质上就是在进行矩阵与向量的乘法,这个参数矩阵的型是需要确定好的,是不可以随着图像大小的变化而变化的,因此,限制在这里。
也许有人会说,那我在输入图像之前,把图像裁剪或者缩放到网络要求的大小不就行了吗?没错,在SPP提出之前,人们也确实是这样做的。
当给定的任意size的图像时,只能在输入网络之前做crop或warp。Crop后可能不包括完整的物体,warp会带来几何失真。这些内容的损失、图像的失真会给识别精度带来损失。除此之外,一个预定义好的图像大小,当图像大小变化、目标尺度变化时,就不再适用了,固定输入图像的大小这一限制忽视了尺度问题。
基于上述限制,论文提出了Spatial Pyramid Pooling层,来移除网络固定size的限制。
2 SPP-Layer
SPP的思想源于BoW的思想,只是SPP包含了一些空间信息。
卷积层产生的feature map的size是可变的,而全连接层或者分类器的输入却要求是固定长度的向量。这种固定长度的向量可由BoW方法生成。
如上图所示。这些spatial bins 的 size 与图像的 size 成比例关系(1/4, 1/2, 1),因此 ,无论图像的size如何,bins 的数量(4*4 + 2*2 + 1*1)都是固定的。这与先前深度网络中的滑动窗池化相反,先前的是窗口大小固定,而滑动窗的数量取决于输入图像的 size。现在用SPP是,窗口size与图像size的比例关系是确定的,当输入图像的size变化时,滑动窗size会随着输入图像size的变化成比例的变化,即池化窗的大小是不固定的,但因为比例确定,所以输出的size(bins的数量)是固定的。
为了使网络可以输入任意size的图像,我们将最后一个卷积层后的池化层换成SPP Layer, 这样就能输出一个固定长度的向量,再作为后面全连接层或分类器的输入。
更普遍一点,假设最后一个卷积层的输出大小是 a ∗ a ∗ k a*a*k a∗a∗k,其中 a ∗ a a*a a∗a为空间维大小, k k k为深度为大小。总共做 l l l-level pyramid pooling,每个level中pooling的bins数量分别是 s 1 ∗ s 1 s_1*s_1 s1∗s1, s 2 ∗ s 2 s_2*s_2 s2∗s2,……, s l ∗ s l s_l*s_l sl∗sl(比如上图中就是3级金字塔,bins数量分别是4*4 、2*2、1*1,concat之后是长为256*21的vector)。
3 training
理论上,SPP-net可用标准的后向传播进行训练。但实际上,GPU实现时更倾向于使用固定size的输入,使用固定size的输入会更好。为了利用GPU的实现优势,同时保留SPP特性,训练方法如下:
Single-size training:正如之前的工作,首先考虑网络的输入是裁剪为固定大小的情况,裁剪是为了数据扩增。对于给定size的图像,我们可预先计算出用于SPP的bin的sizes。比如conv5输出的feature map是a*a(13*13)。当一个n*n个bins的金字塔级别,用滑动窗池化实现这一级的池化,其中窗的大小 w i n = c e i l ( a / n ) win=ceil(a/n) win=ceil(a/n),滑动步长 s t r = f l o o r ( a / n ) str=floor(a/n) str=floor(a/n)。对于一个l级的金字塔,我们实现 l l l个这样的池化层,接下来的全连接层将这 l l l个输出进行连接。
Multi-size training:要定位训练过程中图像尺寸的变化问题,我们考虑一个预定义的size集合。比如考虑两种size,180*180, 224*224。这次不将224的crop成180,而是resize成180大小的。因此,两种尺度上的差别只有分辨率,而在内容/布局上无差别。对于接受180*180输入的网络,我们实现另一个固定size(180)的网络,此时conv5的feature map a*a=10*10,win和str的计算方法和之前一样,实现每一级的池化。该180-网络的SPP层输出与224-网络的SPP层输出长度是一样的。因此180-网络与224-网络在每一层有完全相同的参数。换句话说,训练期间,通过两个共享参数的固定size的网络,我们实现了输入size变化的SPP-net。
为了降低网络之间切换的开销,我们在一个网络中训练完整的一轮,然后在保持所有参数不变的情况下,切换成另一个网络用于下一轮数据的训练。这个过程可以迭代。实验发现,这种多size训练的收敛率与上述单size训练的收敛速度;相似。
Multi-size训练的主要目的是在借力现有良好优化的定长实现下,模拟size可变的输入。除了上述的两种size实现,还测试过s*s的输入size,其中s是随机在[180,224]之间均匀采样的。
值得注意的是,上述单/多size的解决方案只是对于training阶段而言的。对于testing阶段,只将将任意size的图像输入到SPP-net即可。
4 实现细节
在理解SPP-layer的实现的时候,也查阅了一些网上的博客,其中http://www.mamicode.com/info-detail-2222982.html 这篇文章指出,恺明大神的论文里计算
w
i
n
win
win和
s
t
r
i
d
e
stride
stride的公式有疏漏,并做了一些修正。阅读并自己计算后,发现的确如文章中所述。于是我想找找看源码是如何实现的,以及看看是不是也有人遇到了相同的问题。在github上有人问出了类似的问题,
https://github.com/tensorflow/tensorflow/issues/6011 里面有一个回答,解决了这个问题,所做的修正比前一个链接中给出的修正还要简洁:window size 的计算方法保持不变,仍为
w
i
n
=
c
e
i
l
(
a
/
n
)
win=ceil(a/n)
win=ceil(a/n),stride的计算修正为
s
t
r
=
f
l
o
o
r
(
a
/
n
)
+
1
str=floor(a/n) + 1
str=floor(a/n)+1,且padding=‘SAME’.
反思:之前自己计算的时候,一直使用的是学习吴恩达大大深度神经网络系列课程时那个深入脑海的公式 f l o o r ( n − f + 2 p s ) + 1 floor(\frac{n-f+2p}{s})+1 floor(sn−f+2p)+1,导致在计算划分的时候,觉得没有填充就把公式想象成了 f l o o r ( n − f s ) + 1 floor(\frac{n-f}{s})+1 floor(sn−f)+1,直接把 p p p那一部分去掉了,总是得不出想要的结果。下面再将padding的实现重温一下:
1. padding=‘VALID’,无需0填充
n
o
u
t
=
c
e
i
l
(
n
i
n
−
f
+
1
s
)
=
f
l
o
o
r
(
n
i
n
−
f
s
)
+
1
n_{out}=ceil(\frac{n_{in}-f+1}{s})=floor(\frac{n_{in}-f}{s})+1
nout=ceil(snin−f+1)=floor(snin−f)+1
conv2d的VALID方式不会在原有输入的基础上添加新的像素(假定我们的输入是图片数据,因为只有图片才有像素),输出矩阵的大小直接按照公式计算即可
2. padding=‘SAME’,需要0填充
n
o
u
t
=
c
e
i
l
(
n
i
n
s
)
n_{out}=ceil(\frac{n_{in}}{s})
nout=ceil(snin)
需要填充的像素数为
p
a
d
_
n
e
e
d
e
d
=
(
n
o
u
t
−
1
)
∗
s
+
f
−
n
i
n
pad\_needed=(n_{out}-1)*s+f-n_{in}
pad_needed=(nout−1)∗s+f−nin
恰好与
n
o
u
t
=
c
e
i
l
(
n
i
n
−
f
+
2
p
+
1
s
)
=
f
l
o
o
r
(
n
i
n
−
f
+
2
p
s
)
+
1
n_{out}=ceil(\frac{n_{in}-f+2p+1}{s})=floor(\frac{n_{in}-f+2p}{s})+1
nout=ceil(snin−f+2p+1)=floor(snin−f+2p)+1 所表达的吻合
p
a
d
_
n
e
e
d
e
d
=
n
i
n
′
−
n
i
n
=
(
n
o
u
t
−
1
)
∗
s
+
f
−
n
i
n
pad\_needed=n^{'}_{in}-n_{in}=(n_{out}-1)*s+f-n_{in}
pad_needed=nin′−nin=(nout−1)∗s+f−nin
tensorflow官方文档。