目标检测从入门到精通—SPP-Net详细解析(三)
SPP-Net网络结构分析
Author:Mr. Sun
Date:2019.03.18
Loacation: DaLian university of technology
论文名称:《Spatial Pyramid Pooling in Deep Convolutional Networks for Visual Recognition》
摘要:
我们之前学习了基于深度学习进行目标检测的R-CNN算法,它虽然是一个开创性的理论,但是本身存在很多缺点,是有很多可以改进的地方的。本篇研究的Paper是何恺明大神在2014年发表的。这篇Paper最大的创新点在于提出了空间金字塔池化(Spatial Pyramid Pooling),简称SPP-Net。这个算法(algorithm)比R-CNN算法的速度快了很多倍(20-104倍)。我们知道在现有的CNN中,对于结构已经确定的网络,需要输入一张固定大小的图片,比如224*224、32*32、96*96等。这样对于我们希望检测各种大小的图片的时候,需要经过裁剪(Crop),或者缩放等一系列操作,这样往往会降低识别检测的精度,于是paper提出了“空间金字塔池化”方法,这个算法的牛逼之处,在于使得我们构建的网络,可以输入任意大小的图片,不需要经过裁剪缩放等操作,只要你喜欢,任意大小的图片都可以。不仅如此,这个算法用了以后,精度也会有所提高,总之一句话:不得不学的一篇论文。
1、为什么提出SPP-Net?
R-CNN的候选框是通过Selective search方法得到的,一张图片大概有2000个Region Proposals,然后通过crop/warp进行处理(归一化),将每个Region Proposal送入CNN中进行卷积特征的提取,这样导致:
(1)训练时间非常慢,因为一张图片产生2000个Region Proposals,都会送入CNN中进行训练;
(2)识别准确率很低,因为产生的Region Proposals都会通过crop/warp操作,resize到同一大小送入CNN中进行训练,这样会造成图片信息的缺失或者变形失真,会降低图片识别的正确率。
如何解决上述两个存在的问题呢?
(a)共享卷积计算:将一张完整图片整体送入CNN中进行特征提取,然后将一张图片的多个Region Proposals映射到最后的特征层上,形成每个Region Proposal的Feature Maps,进而加速特征的提取;
(b)设计可以处理任意尺度的图像的结构:不同尺寸的Feature Map,映射称同样大小的特征。这个结构就是空间金字塔池化。
在传统的CNN网络和物体检测相关的文章中,比如R-CNN,他们都要求输入固定大小的图片,这些图片或者经过裁切(Crop)或者经过变形缩放(Warp),都在一定程度上导致图片信息的丢失和变形,限制了识别精确度。两种方式如下图1所示。
图1:图像尺寸处理方式
究竟是什么原因导致卷积网络中必须要固定输入图片的尺寸呢?
(a)首先我们来看卷积操作:卷积操作对图片输入的大小会有要求吗?比如一个5*5的卷积核,我输入的图片是30*30的大小,可以得到(26,26)大小的图片(这里并没有进行padding,且stride=1),并不会影响卷积操作。我输入600*500,它还是照样可以进行卷积,也就是卷积对图片输入大小没有要求,只要你喜欢,任意大小的图片进入,都可以进行卷积。
(b)再来看看池化操作:池化对图片大小会有要求吗?比如我池化大小为(2,2)我输入一张30*40的,那么经过池化后可以得到15*20的图片。输入一张53*22大小的图片,经过池化后,我可以得到26*11大小的图片。因此池化这一步也没对图片大小有要求。只要你喜欢,输入任意大小的图片,都可以进行池化。
(c)最后看全连接层:既然池化和卷积都对输入图片大小没有要求,那么就只有全连接层对图片结果又要求了。因为全连接层我们的连接劝值矩阵的大小W,经过训练后,就是固定的大小了,比如我们从卷积到全连层,输入和输出的大小,分别是50、30个神经元,那么我们的权值矩阵(50,30)大小的矩阵了。因此空间金字塔池化,要解决的就是从卷积层到全连接层之间的一个过度。
总之,卷积层的参数和输入图像的尺寸无关,它仅仅是一个卷积核在图像上滑动,不管输入图像是多少都没关系,只是对不同大小的图片卷积出不同大小的特征图,池化层对输入图像的尺寸也没有任何限制,只是获得不同的特征图而已,但是全连接层的参数就和输入图像大小有关,因为它要把输入的所有像素点连接起来,需要指定输入层神经元个数和输出层神经元个数,所以需要规定输入的feature的大小。
因此,对于图片限制其长度的根源是全连接层。以下图为例说明:
作为全连接层,如果输入的x维数不等,那么参数w肯定也会不同,因此,全连接层是必须是固定的输入,输出个数的。
2、SPP-Net的网络结构
SPP-Net在最后一个卷积层后,接入了金字塔池化层,使用这种方式,可以让网络输入任意的图片,而且还会生成固定大小的输出。
3、什么是空间金字塔池化(Spatial Pyramid Pooling )?
在讲解什么是空间金字塔池化之前,我们先从空间金字塔特征说起(这边先不考虑池化)。如下图4中,当我们输入一张图片的时候,我们利用不同大小的刻度,对一张图片进行了划分。图4中,利用了三种不同大小的刻度,对一张输入的图片进行了划分,最后总共可以得到16+4+1=21个块,我们即将从这21个块中,每个块提取出一个特征,这样刚好就是我们要提取的21维特征向量
图4:空间金字塔的结构
空间金字塔最大池化(SPP-Max)的过程,其实就是从这21个图片块中,分别计算每个块的最大值,从而得到一个输出特征向量(跟上一层卷积核深度一样可能是256)。最后把一张任意大小的图片转换成了一个固定大小的21维特征(当然你可以设计其它维数的输出,增加金字塔的层数,或者改变划分网格的大小)。上面的三种不同刻度的划分,每一种刻度我们称之为金字塔的一层,使用多个不同刻度的层,可以提高我们所提取特征的鲁棒性。每一个图片块大小我们称之为:Sliding Windows Size了。如果你希望,金字塔的某一层输出n*n个特征,那么你就要用Windows Size大小为:(w/n,h/n)进行池化了。具体的SPP过程如下图5所示:
图5:SPP的处理过程
空间金字塔池化的意义是什么呢?
当网络输入的是一张任意大小的图片,这个时候我们可以一直进行卷积、池化,直到网络的倒数几层的时候,也就是我们即将与全连接层连接的时候,就要使用金字塔池化,使得任意大小的特征图都能够转换成固定大小的特征向量,这就是空间金字塔池化的意义(多尺度特征提取出固定大小的特征向量)。
空间金字塔池化的的公式是什么呢?
根据空间金字塔池化的意义可以知道,无论最后一个卷积层得到的feature maps的大小是怎样的,我们都将其转化为了(4*4+2*2+1*1)*256的全连接层,不过需要注意的是,可以转化的条件是虽然这些feature maps的大小不同,但是feature maps的channels相同(这里为256),那么如何将不同大小的feature maps进行spp 呢?论文中给出的空间金字塔的三个Sliding Window Size 和 strides如下图6所示:
图6:论文中空间金字塔的三个池化窗口的尺寸
接下来我们来具体分析一下在空间金字塔中不同尺度的池化窗口和步长的计算方法:
假设输入的大小为a*a*c(最后一个卷积核的特征),然后,将这 c 个 a * a 的Feature Maps分别分成了[1*1,2*2,4*4]大小的块(这里的分,指的是移动窗口池化),期望的输出为1*1*c,2*2*c,4*4*c,然后将reshape成(1*1+2*2+4*4)*c的二维数组,具体这儿是采用Max pooling操作来实现的,不过pool层的Sliding Window size和stride是不同的,具体有如下公式: 输出为[n,n],输入为[a,a],假设取上整运算为"++()",取下整运算为"--()",那么pool_size=++(n/a),stride=--(n/a),这样我们就将其转化为了n*n*c的矩阵,例如(13*13,10*10)都要转化为(4*4),那么采用[p_s=4,,s=3],[p_s=3,s=2]的池化操作后便可以得到。(这里有一个问题哈,就是如果(7*7)也要得到4*4的话,计算得到的size=2,stride=1,利用公式算出来得到的池化为(6*6)与预期的4*4不符,这里暂时还有问题,不清楚具体原因是什么)。
我在这里有一个很大的疑问?
在做目标检测的时候,SPP-Net是将Selective Search算法提取的2000个Region Proposal映射到要输出的最后一个卷积层,大概是要除以所有步长的乘积,如果Region Proposal很小,映射之后的特征更小(比如3*3),这样的话我们该如何应用(4*4)的金字塔池化呢?到底作者是怎么处理这种情况的呢?现在还是不能很好的理解这一点。
空间金字塔池化的代码分析:
代码返回的是SPP Layer之后要输出的神经元个数,代码中bins=[1,2,3],经过处理之后就可以得到对应的(1*1+2*2+3*3)*256=14*256=3584个神经元,即无论前面的Feature Map是多大的,经过SPP Layer处理之后得到固定大小的神经元,然后就可以和全连接层进行矩阵运算了!
1 import tensorflow as tf 2 import math 3 4 class SPPLayer(): 5 def __init__(self,bins,feature_map_size): 6 self.strides = [] 7 self.filters = [] 8 # print(type(feature_map_size)) 9 self.a = float(feature_map_size) 10 self.bins = bins 11 self.n = len(bins) 12 13 def spatial_pyramid_pooling(self,data): 14 self.input = data 15 self.batch_size = self.input.get_shape().as_list()[0] 16 for i in range(self.n): 17 x = int(math.floor(self.a/float(self.bins[i]))) 18 self.strides.append(x) 19 x = int (math.ceil(self.a/float(self.bins[i]))) 20 self.filters.append(x) 21 22 self.pooled_out = [] 23 for i in range(self.n): 24 self.pooled_out.append(tf.nn.max_pool(self.input, 25 ksize=[1, self.filters[i], self.filters[i], 1], 26 strides=[1, self.strides[i], self.strides[i], 1], 27 padding='VALID')) 28 29 for i in range(self.n): 30 self.pooled_out[i] = tf.reshape(self.pooled_out[i], [self.batch_size, -1]) 31 32 self.output = tf.concat(1, [self.pooled_out[0], self.pooled_out[1], self.pooled_out[2]]) 33 34 return self.output
4、SPP-Net网络的训练方式(单尺度和多尺度)
Paper中将网络的训练分为两种:一种是Single-size,一种是Multi-size。
先讲解single-size的训练过程:
从理论上讲,SPP-Net支持直接以多尺度的原始图片作为输入后直接反向传播(Backpropagation)。实际上(Actually),Caffe等实现中,为了计算的方便,GPU,CUDA等比较适合固定尺寸的输入,所以训练的时候输入的图片是固定了尺度了的。以224*224的输入为例:在Conv5之后的特征图为:13x13*256(a*a*c);金字塔层bins: n*n;将pooling层作为Sliding window pooling。Windows_size=[a/n] 向上取整 , Stride_size=[a/n]向下取整。得到的特征是n*n个特征向量(向量的长度是256)。对于pool 3*3: Windows_size=5 的计算公式是:[13/3]向上取整=5 ,Stride_size= 4的计算公式是:[13/3]向下取整。如果输入改成180x180,这时候Conv5出来的Reponse map为10x10,类似的方法,能够得到新的pooling参数(滑动池化窗口的大小和步长)。
对于Multi-size training训练:
使用两个尺度进行训练:224*224 和180*180,训练的时候,224x224的图片通过Crop得到,180x180的图片通过缩放224x224的图片得到。之后,迭代训练,即用224的图片训练一个epoch,之后180的图片训练一个epoch,交替地进行。
两种尺度下,在SSP后,输出的特征维度都是(9+4+1)x256,参数是共享的,之后接全连接层即可。作者在论文中说,这样训练的好处是一样的收敛速度,模型的测试精度却提高了。