cnn中感受野的计算

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/hzhj2007/article/details/79870936

    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)的计算公式:

  1.  第一层和最后一层的感受野为当前层的卷积核大小;
  2. 自下向上计算方式中,stride为之前所有步长的乘积。而公式5中stride是前一层(底层)的步长;
  3. 等式右边的感受野分别表示当前层前一层(底层)的感受野和当前层后一层(顶层)的感受野;
  4. 卷积核的大小均为当前层的卷积核。
  5. 公式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))  
对alexnet的计算结果如下。


参考文献:

  1. http://shawnleezx.github.io/blog/2017/02/11/calculating-receptive-field-of-cnn/
  2. https://blog.csdn.net/kuaitoukid/article/details/46829355
  3. https://blog.csdn.net/gzq0723/article/details/53138430

猜你喜欢

转载自blog.csdn.net/hzhj2007/article/details/79870936