关于CNN中感受野的理解和计算方法

1.感受野的理解

CNN中的感受野是CNN中的一个很重要的概念,关于其解释网上有许多版本,如

  • The receptive field is defined as the region in the input space that a particular CNN’s feature is looking at (i.e. be affected by).
  • 在卷积神经网络中,感受野的定义是 卷积神经网络每一层输出的特征图(feature map)上的像素点在原始图像上映射的区域大小。
  • 在机器视觉领域的深度神经网络中有一个概念叫做感受野,用来表示网络内部的不同位置的神经元对原图像的感受范围的大小。

大意都是指CNN中处在某一层的神经元(或者说特征图上的某一点)在输入图像中所对应的感受区域大小,这一概念其实正是从生物学上的感受野类比过来的。

2. 感受野的计算方法

在网上主要找到了两种计算感受野的方法,一种是自顶向下的计算方式,另一种是自底向上的计算方式,前者理解起来比较快,方便手算;后者理解起来可能要花点时间但比较系统且可以顺次计算出各层的感受野,方便编程实现。

2.1 自顶向下的计算方式

在这里插入图片描述
以卷积核尺寸k=5,步长s=2为例,计算示例图如下:
在这里插入图片描述
上式表示的是相邻输入输出层感受区域对应关系,由于感受野是相对于输入图像的,因此要利用上式进行多次迭代计算才能求出最终的感受野大小。如下图经过两次卷积后,右图中的一个像素点对应中图的区域是3*3,而中图的3*3又对应左图的7*7感受区域,所以右图一个像素点的感受野是7*7。
在这里插入图片描述
当上式中m为输出特征图的尺寸 w o u t w_{out} 时,其对应的输入感受野n还可以表示成 n = w i n + w p a d n=w_{in}+w_{pad} ,这样就有 w i n + w p a d = k + s ( w o u t 1 ) w_{in}+w_{pad}=k+s(w_{out}-1) ,简单整理一下就可以得到输出特征图尺寸的计算公式:
w o u t = w i n + w p a d k s 1 w_{out} = \frac{w_{in}+w_{pad}-k}{s}-1
以上图片和公式来源:CS231n

2.2 自底向上的计算方式

计算方法如下图所示:
这里写图片描述
图中已经解释的比较清楚,第一个式子就是常用的根据输入尺寸计算卷积输出特征图尺寸的公式,相信熟悉CNN的童鞋一定不陌生;第三个式子可以这样理解,对于 r i n r_{in} 所在层第一个k*k卷积区域(其对应的感受野也即下一层的一个神经元对应的感受野 r o u t r_{out} ),卷积区域的第一个位置对应的感受野就是 r i n r_{in} ,剩下的(横向或纵向)k-1个位置中每个位置产生的感受野增量就是上一层的特征间隔 j i n j_{in} ;最后一个式子则是在第三个式子的基础上把padding的影响考虑进来了,根据 s t a r t o u t = r o u t 2 p j i n 2 start_{out}=\frac{r_{out}-2*p*j_{in}}{2} (不难从下图中得出),再将第三个式子带入即可得到 s t a r t o u t = r i n / 2 + ( k 1 2 p ) j i n start_{out}=r_{in}/2+(\frac{k-1}{2}-p)*j_{in} ,对于Layer0而言, s t a r t i n = r i n / 2 start_{in}=r_{in}/2 ,即第四个式子。

另外,由上面的第二个和第三个式子可以推出关于感受野计算的一个简化公式:
式中 l k l_k 表示第k层对应的感受野尺寸,注意 f k 1 f_{k}-1 乘以的因子是前k-1层的步长累乘的结果(当前层的步长不参与当前层感受野的计算)

3.代码实现

自底向上的感受野计算方式的python3代码如下

# coding:utf-8
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']}}

def calc_receptive_field(network, input_size):
    """Calculate receptive field according to name of network and input_size.
    Assume input image is square"""
    if network is None or input_size is None:
        return None
    assert network in net_struct.keys(), "unknown net"
    layer_sizes, feat_gaps, RF_sizes = [], [], []
    net_list = net_struct[network]['net']
    name_list = net_struct[network]['name']
    assert len(net_list) == len(name_list), "unmatched between length of layers and number of names"
    n_in = input_size
    j_in = 1
    RF_in = 1
    for i in range(len(net_list)):
        kernel, stride, pad = net_list[i]
        n_out = (n_in + 2*pad - kernel)//stride + 1
        j_out = stride * j_in
        RF_out = RF_in + (kernel-1)*j_in
        layer_sizes.append(n_out)
        feat_gaps.append(j_in)
        n_in, j_in, RF_in = n_out, j_out, RF_out
        print("Layer Name:{}, Output size:{}, Stride:{}, RF size:{}".format(name_list[i], n_out, j_out, RF_out))
    result = zip(name_list, layer_sizes, feat_gaps, RF_sizes)
    return result

if __name__ == "__main__":
    input_size = 224
    print("input size: 224*224")
    for network in net_struct.keys():
        print("-----------------network name: %s--------------" % network)
        calc_receptive_field(network, input_size)

程序输出结果:
这里写图片描述

参考资料:

  1. 你知道如何计算CNN感受野吗?这里有一份详细指南
  2. 卷积神经网络物体检测之感受野大小计算

猜你喜欢

转载自blog.csdn.net/Blateyang/article/details/82683653