medium上的这篇文章A guide to receptive field arithmetic for Convolutional Neural Networks是目前关于感受野计算最详细的文章,其翻译详见文章卷积神经网络中的感受野计算(译)和关于卷积神经网络(CNN)中的感受野,你知道多少?
感受野(receive field)是指当前feature map中的一个原子点P与输入层中多少个原子个数相关的问题。
在卷积神经网络中,感受野的定义是 卷积神经网络每一层输出的特征图(feature map)上的像素点在原始图像上映射的区域大小。
感受野:在卷积神经网络CNN中,决定某一层输出结果中一个元素所对应的输入层的区域大小,被称作感受野receptive field。
上述三个定义来自不同的文章,可见关于感受野没有明确的定义,整体理解是feature map上像素在输入层图像上覆盖区域的大小。cs231n上关于感受野的描述如下。
Suppose that you stack three 3x3 CONV layers on top of each other (with non-linearities in between, of course). In this arrangement,
each neuron on the first CONV layer has a 3x3 view of the input volume. A neuron on the second CONV layer has a 3x3 view of the first
CONV layer, and hence by extension a 5x5 view of the input volume. Similarly, a neuron on the third CONV layer has a 3x3 view of the
2nd CONV layer, and hence a 7x7 view of the input volume.
关于感受野的计算公式可见该文章,其中第一层感受野的大小为卷积核的大小;感受野的大小与卷积或池化时的填充大小无关。
式子中,与分别表示网络第k层和第k-1层的感受野,表示卷积核的大小(假设长宽相等),表示第i层的步长。式中是从底层向顶层计算的(从第一层开始)。对于小括号中的式子,直观上理解是第k层比第k-1层多覆盖个像素,所以对输入层来说多覆盖的像素变成了乘积项,即k-1层网络在输入层上的步长与底层网络步长的关系是成倍数的增加。
开头提到的medium上的文章对网络输出层的属性给出了更详细的描述。
第一个式子表示输出特征图的大小;
第二个式子表示特征图上相邻像素映射到输入层时的像素间隔,大小为当前层至首层的步长的乘积;
第三个式子表示感受野的计算;
第四个公式表示特征图上第一个像素点(第一个输出特征)的感受野中心位置的计算。
文中还给出了如下例子。其中,Conv2上5*5的感受野表示当前卷积核在输入层上的覆盖范围。
上述两个公式都是从下向上(从第一层开始)计算感受野,文章则采用自上向下的方式计算。其计算公式可参考该文章。
(公式5)
式中与分别表示第k-1层和第k层的感受野大小,stride和Size为第k-1层的步长和卷积核大小。
对比两种方式(第三个公式和公式5)的计算公式:
- 第一层和最后一层的感受野为当前层的卷积核大小;
- 自下向上计算方式中,stride为之前所有步长的乘积。而公式5中stride是前一层(底层)的步长;
- 等式右边的感受野分别表示当前层前一层(底层)的感受野和当前层后一层(顶层)的感受野;
- 卷积核的大小均为当前层的卷积核。
- 公式5自第k层计算至第一层时的感受野等于第三个公式自第一层至第k层感受野的大小相等。
参考文章的计算代码,可以获得自上向下和自下向上两种方式的感受野计算过程。
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 = 227
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
def topToBottom(net, layernum, top_layer):
RF = 1
for layer in reversed(range(layernum, top_layer)): #当前层至底层
# print 'in func {}'.format(layer)
fsize, stride, pad = net[layer]
RF = ((RF -1)* stride) + fsize
return RF
def bottomToTop(net, layernum):
if layernum==1: #从1开始计数的
return net[0][0]
r_in=net[0][0]
j_in=net[0][1]
for layer in (range(1,layernum)):
fsize, stride, pad = net[layer]
j_out =j_in*stride
r_out = r_in + (fsize -1)*j_in
r_in=r_out
j_in=j_out
return r_out
if __name__ == '__main__':
print ("layer output sizes given image = %dx%d" % (imsize, imsize))
for net in net_struct.keys(): #自下向上(底层至当前层)公式3
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_uToD = inFromOut(net_struct[net]['net'], i+1)
rf_dToU = bottomToTop(net_struct[net]['net'], i+1)
print ("Layer Name = %s, Output size = %3d, Stride = % 3d, RF size: topToDown = %3d, downToTop = %3d" % (net_struct[net]['name'][i], p[0], p[1], rf_uToD, rf_dToU))
for net in net_struct.keys(): #自上向下(当前层至底层)公式5
print ('************net structrue name is %s**************'% net)
for top_layer in reversed(range(len(net_struct[net]['net']))):
for layernum in reversed(range(top_layer + 1)):
rf_uToD = topToBottom(net_struct[net]['net'], layernum, top_layer+1)
print ("layernum %3d, Layer Name = %s, RF size: topToDown = %3d" % (layernum, net_struct[net]['name'][layernum], rf_uToD))
参考文献: