昨天鹅厂的电面,敲醒了我,面试后经过一天的失落,最终还是要站起来的,把论文里存在的问题点弄清楚。就从在SPPNet和fast RCNN都涉及到ROI到feature map的映射关系开始吧!
感受野(receptive field):卷积网络中某非输入层的feature map中的神经元(像素点)映射到输入feature map上的区域大小。
是否考虑padding,需要根据具体情况具体分析。首先要注意三个概念:
- 卷积层之间尺寸关系;
- 层间感受野尺寸关系;
- 层间坐标关系;
已知:inputFieldSize:输入层feature map尺寸;
KernelSize:核尺寸;
Padding:单边padding尺寸;
未知:输出层尺寸;
其中:
- 红色部分:卷积核扫过的像素数,除以stride后,就是对应输出层的像素数;
- 绿色部分 :输入层两边各加padding;
- 蓝色部分:由于第一步没有考虑最后一个卷积操作,最后的卷积操作对应输出层的最后一个像素
已知:otputFieldSize:输出层feature map尺寸;
KernelSize:核尺寸;
Padding:单边padding尺寸;
未知:输入层尺寸(出去padding后的尺寸);
- 红色部分:先不考虑最后一个像素,其他每个像素对应这输入层的长度。
- 绿色部分 :除去padding长度,才是输入层的实际长度。
- 蓝色部分:再加上最后一次卷积核的Size,即为输入层的Size。
根据感受野的定义,表示输出层单位像素点对应输入图像的区域,跟padding无关。只与kernel size和stride有关。
也就是说conv1的feature map的感受野=kernel size,即:上述公式退化成input field size = kernel size(由于padding=0,outputsize=1)
先计算从第i层到第i-1层计算感受野,然后再依次向前计算:
RF = 1 #待计算的feature map上的感受野大小
for layer in (top layer To down layer):
RF = ((RF -1)* stride) + fsize#此处padding区域不考虑,其他和上述公式相同,只是RF刚开始变成1
stride 表示卷积的步长; fsize表示卷积层滤波器的大小
如下图所示:
以下python代码计算Alexnet和VGG16的每层的feature map:
# -*- coding: UTF-8 –*-
import numpy as np
#!/usr/bin/env python
net_struct = {'alexnet': {'net':[[11,4,0],[3,2,0],[5,1,2],[3,2,0],[3,1,1],[3,1,1],[3,1,1],[3,2,0]],
'name':['conv1','pool1','conv2','pool2','conv3','conv4','conv5','pool5']},
'vgg16': {'net':[[3,1,1],[3,1,1],[2,2,0],[3,1,1],[3,1,1],[2,2,0],[3,1,1],[3,1,1],[3,1,1],
[2,2,0],[3,1,1],[3,1,1],[3,1,1],[2,2,0],[3,1,1],[3,1,1],[3,1,1],[2,2,0]],
'name':['conv1_1','conv1_2','pool1','conv2_1','conv2_2','pool2','conv3_1','conv3_2',
'conv3_3', 'pool3','conv4_1','conv4_2','conv4_3','pool4','conv5_1','conv5_2','conv5_3','pool5']},
'zf-5':{'net': [[7,2,3],[3,2,1],[5,2,2],[3,2,1],[3,1,1],[3,1,1],[3,1,1]],
'name': ['conv1','pool1','conv2','pool2','conv3','conv4','conv5']}}
imsize = 224
def outFromIn(isz, net, layernum):
totstride = 1
insize = isz
for layer in range(layernum):
fsize, stride, pad = net[layer]
outsize = (insize - fsize + 2*pad) / stride + 1
insize = outsize
totstride = totstride * stride
return outsize, totstride
def inFromOut(net, layernum):
RF = 1
for layer in reversed(range(layernum)):
fsize, stride, pad = net[layer]
RF = ((RF -1)* stride) + fsize
return RF
if __name__ == '__main__':
print "layer output sizes given image = %dx%d" % (imsize, imsize)
for net in net_struct.keys():
print '************net structrue name is %s**************'% net
for i in range(len(net_struct[net]['net'])):
p = outFromIn(imsize,net_struct[net]['net'], i+1)
rf = inFromOut(net_struct[net]['net'], i+1)
print "Layer Name = %s, Output size = %3d, Stride = % 3d, RF size = %3d" % (net_struct[net]['name'][i], p[0], p[1], rf)
由于每层的坐标的原点都是基于当前featuremap在padding前的左上角,如果padding后,图像左上角变化,导致坐标变化,因此,需要最后减去左上角的padding区域。
因此,相邻conv或pooling层的坐标映射关系:
上图所示:如果每层padding: ⌊F/2⌋ ⌊ F / 2 ⌋ ,则feature map的(0,0)点和上一层输入图像的(0,0)点相对应,且feature map的(x,y)点和上一层输入图像的(xS,yS)点相对应。因此,对上面的公式进行简化。
从公式分析: (ki−1)2−padding=(ki−1)2−⌊ki2⌋ ( k i − 1 ) 2 − p a d d i n g = ( k i − 1 ) 2 − ⌊ k i 2 ⌋
当 ki k i 为奇数时, (ki−1)2−⌊ki2⌋=0 ( k i − 1 ) 2 − ⌊ k i 2 ⌋ = 0
一般卷积核不为偶数,所以上式简化成: pi=si∗pi−1 p i = s i ∗ p i − 1
第i层相对于第1层的stride:strides(i) = stride(1) * stride(2) * …* stride(i-1) (只考虑conv/pooling)
第i层到第1层的坐标映射为: p1=∏i0si∗pi p 1 = ∏ 0 i s i ∗ p i
设:输入图像ROI左上角坐标 (x1,y1) ( x 1 , y 1 ) ,右下角坐标 (x2,y2) ( x 2 , y 2 ) (已知)
feature map的ROI左上角坐标 (x′1,y′1) ( x 1 ′ , y 1 ′ ) ,右下角坐标 (x′2,y′2) ( x 2 ′ , y 2 ′ ) (未知)
x′1=⌊x1S⌋+1 x 1 ′ = ⌊ x 1 S ⌋ + 1
y′1=⌊y1S⌋+1 y 1 ′ = ⌊ y 1 S ⌋ + 1
x′2=⌈x2S⌉−1 x 2 ′ = ⌈ x 2 S ⌉ − 1
y′2=⌈y2S⌉−1 y 2 ′ = ⌈ y 2 S ⌉ − 1
为什么加1或减1呢?
看看一下分析:
是否小数 | xS x S | ⌊xS⌋+1 ⌊ x S ⌋ + 1 | ⌈y2S⌉−1 ⌈ y 2 S ⌉ − 1 |
---|---|---|---|
有小数 | 1.2 | 1+1=2(多0.8) | 2-1=1(少0.2) |
无小数 | 2 | 2+1=3(多1) | 2-1=1(少1) |
- | - | 总比 xS x S 多0~1 | 总比 xS x S 少0~1 |
也就是说左上角坐标反映射到输入图像后,在真实ROI左上角的比较接近右下方,右下角坐标反映射到输入图像后,在真实ROI左上角的比较接近左上方,也就是防止反映射到输入图像的区域超过实际ROI区域。