CNN中感受野的计算

感受野(receptive field)是怎样一个东西呢,从CNN可视化的角度来讲,就是输出featuremap某个节点的响应对应的输入图像的区域就是感受野。

在机器视觉领域的深度神经网络中有一个概念叫做感受野,用来表示网络内部的不同位置的神经元对原图像的感受范围的大小。神经元之所以无法对原始图像的所有信息进行感知,是因为在这些网络结构中普遍使用卷积层和pooling层,在层与层之间均为局部相连(通过sliding filter)。神经元感受野的值越大表示其能接触到的原始图像范围就越大,也意味着他可能蕴含更为全局、语义层次更高的特征;而值越小则表示其所包含的特征越趋向于局部和细节。因此感受野的值可以大致用来判断每一层的抽象层次。

比如我们第一层是一个3*3的卷积核,那么我们经过这个卷积核得到的featuremap中的每个节点都源自这个3*3的卷积核与原图像中3*3的区域做卷积,那么我们就称这个featuremap的节点感受野大小为3*3

如果再经过pooling层,假定卷积层的stride是1,pooling层大小2*2,stride是2,那么pooling层节点的感受野就是4*4

有几点需要注意的是,padding并不影响感受野,stride只影响下一层featuremap的感受野,size影响的是该层的感受野。

至于如何计算感受野,我的建议是top to down的方式。下面我拿一个例子来算算


pool3的一个输出对应pool3的输入大小为2*2

依次类推,对应conv4的输入为4*4,因为2*2的每个角加一个3*3的卷积核,就成了4*4,当然这是在stride=1的情况下才成立的,但是一般都是stride=1,不然也不合理

对应conv3的输入为6*6

对应pool2的输入为12*12

对应conv2的输入为14*14

对应pool1的输入为28*28

对应conv1的输入为30*30

所以pool3的感受野大小就是30*30

卷积神经网络物体检测之感受野大小计算

学习RCNN系列论文时, 出现了感受野(receptive field)的名词, 感受野的尺寸大小是如何计算的,在网上没有搜到特别详细的介绍, 为了加深印象,记录下自己对这一感念的理解,希望对理解基于CNN的物体检测过程有所帮助。

1 感受野的概念

  在卷积神经网络中,感受野的定义是 卷积神经网络每一层输出的特征图(feature map)上的像素点在原始图像上映射的区域大小。



  RCNN论文中有一段描述,Alexnet网络pool5输出的特征图上的像素在输入图像上有很大的感受野(have very large receptive fields (195 × 195 pixels))和步长(strides (32×32 pixels) ), 这两个变量的数值是如何得出的呢?

2 感受野大小的计算

 感受野计算时有下面的几个情况需要说明:

  (1)第一层卷积层的输出特征图像素的感受野的大小等于滤波器的大小

  (2)深层卷积层的感受野大小和它之前所有层的滤波器大小和步长有关系

  (3)计算感受野大小时,忽略了图像边缘的影响,即不考虑padding的大小,关于这个疑惑大家可以阅读一下参考文章2的解答进行理解

这里的每一个卷积层还有一个strides的概念,这个strides是之前所有层stride的乘积。  

  即strides(i) = stride(1) * stride(2) * ...* stride(i-1) 

  关于感受野大小的计算采用top to down的方式, 即先计算最深层在前一层上的感受野,然后逐渐传递到第一层,使用的公式可以表示如下:   

       RF = 1 #待计算的feature map上的感受野大小
  for layer in (top layer To down layer):
    RF = ((RF -1)* stride) + fsize

stride 表示卷积的步长; fsize表示卷积层滤波器的大小  

用python实现了计算Alexnet  zf-5和VGG16网络每层输出feature map的感受野大小,实现代码
receptiveField.py

#!/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)

执行后的结果如下:


最后顺便提一下,特征图的尺寸大小计算方式:

Feature Map的尺寸=(input_size + 2 * padding_size − ksize)/stride+1

感受野的一种前向计算方式是比较简洁的,一般常用这种计算方式。

某一层的strides等于该层与之前所有层的连乘积。

某一层感受野的计算,某一层的感受野指的是这一层的输出feature map上的一个点来自于原图的范围。某一层的感受野等于(前一层的感受野+(当前层kernel_size-1)*前一层的strides)。

即如下公式所示 


r_n=r_{n-1}+(k_n-1)\prod_{i=1}^{n-1}s_i

感受野的例子

(1)两层3*3的卷积核卷积操作之后的感受野是5*5,其中卷积核(filter)的步长(stride)为1、padding为0,如图2所示:


(2)三层3*3卷积核操作之后的感受野是7*7,其中卷积核的步长为1,padding为0,如图3所示:


从这两个图我有一些思考,想法不知道对不对。现在网络模型的优化改进,其中的一个趋势就是用小卷积核来代替大卷核。从感受野的角度来看,两个3*3的卷积核串联相当于一个5*5卷积核的感受野,三个3*3的卷积核串联相当于一个7*7的卷积核感受野。所以用小卷积核来代替大卷积核想要实现同样的语义抽象等级,网络层数需要更深。大卷积核从极限的角度计算量会比小卷积核计算量大。同时,多个小卷积核串联相比于单个大卷积核来说非线性程度往往更高。

 上面是常规计算,下面是扩展部分


本文翻译自A guide to receptive field arithmetic for Convolutional Neural Networks(可能需要翻墙才能访问),方便自己学习和参考。若有侵权,还请告知。

感受野(receptive field)可能是卷积神经网络(Convolutional Neural Network,CNNs)中最重要的概念之一,值得我们关注和学习。当前流行的物体识别方法的架构大都围绕感受野的设计。但是,当前并没有关于CNN感受野计算和可视化的完整指南。本教程拟填补空白,介绍CNN中特征图的可视化方法,从而揭示感受野的原理以及任意CNN架构中感受野的计算。我们还提供了代码实现证明计算的正确性,这样大家可以从感受野的计算开始研究CNN,从而更加深刻的理解CNN的架构。

本文假设读者已经熟悉CNN的思想,特别是卷积(convolutional)和池化(pooling)操作,当然你可以参考[1603.07285] A guide to convolution arithmetic for deep learning,回顾CNN的相关知识。如果你对CNNs已经有所了解,相信不超过半个小时就可以完成本文的阅读。实际上,本文受上述论文的启发,文中也采用了相似的表示符号。

The fixed-sized CNN feature map visualization

图1 CNN特征图可视化的两种方式。

如图1所示,我们采用卷积核C的核大小(kernel size)k=3*3,填充大小(padding size)p=1*1,步长(stride)s=2*2。(图中上面一行)对5*5的输入特征图进行卷积生成3*3的绿色特征图。(图中下面一行)对上面绿色的特征图采用相同的卷积操作生成2*2的橙色特征图。(图中左边一列)按列可视化CNN特征图,如果只看特征图,我们无法得知特征的位置(即感受野的中心位置)和区域大小(即感受野的大小),而且无法深入了解CNN中的感受野信息。(图中右边一列)CNN特征图的大小固定,其特征位置即感受野的中心位置。

感受野表示输入空间中一个特定CNN特征的范围区域(The receptive field is defined as the region in the input space that a particular CNN’s feature is looking at)。一个特征的感受野可以采用区域的中心位置和特征大小进行描述。图1展示了一些感受野的例子,采用核大小(kernel size)k=3*3,填充大小(padding size)p=1*1,步长(stride)s=2*2的卷积核C对5*5大小的输入图进行卷积操作,将输出3*3大小的特征图(绿色图)。对3*3大小的特征图进行相同的卷积操作,将输出2*2的特征图(橙色)。输出特征图在每个维度上的大小可以采用下面的公式进行计算([1603.07285] A guide to convolution arithmetic for deep learning):

为了简单,本文假设CNN的架构是对称的,而且输入图像长宽比为1,因此所有维度上的变量值都相同。若CNN架构或者输入图像不是对称的,你也可以分别计算每个维度上的特征图大小。如图1所示,左边一列展示了一种CNN特征图的常见可视化方式。这种可视化方式能够获取特征图的个数,但无法计算特征的位置(感受野的中心位置)和区域大小(感受野尺寸)。图1右边一列展示了一种固定大小的CNN特征图可视化方式,通过保持所有特征图大小和输入图大小相同来解决上述问题,接下来每个特征位于其感受野的中心。由于特征图中所有特征的感受野尺寸相同,我们就可以非常方便画出特征对应的包围盒(bounding box)来表示感受野的大小。因为特征图大小和输入图像相同,所以我们无需将包围盒映射到输入层。


图2 另外一种固定大小的CNN特征图表示。采用相同的卷积核C对7*7大小的输入图进行卷积操作,这里在特征中心周围画出了感受野的包围盒。为了表达更清楚,这里忽略了周围的填充像素。固定尺寸的CNN特征图可以采用3D(左图)或2D(右图)进行表示。

图2展示了另外一个例子,采用相同的卷积核C对7*7大小的输入图进行卷积操作。这里给出了3D(左图)和2D(右图)表示下的固定尺寸CNN特征图。注意:图2中感受野尺寸逐渐扩大,第二个特征层的中心特征感受野很快就会覆盖整个输入图。这一点对于CNN设计架构的性能提升非常重要。

感受野的计算(Receptive Field Arithmetic)

除了每个维度上特征图的个数,还需要计算每一层的感受野大小,因此我们需要了解每一层的额外信息,包括:当前感受野的尺寸r,相邻特征之间的距离(或者jump)j,左上角(起始)特征的中心坐标start,其中特征的中心坐标定义为其感受野的中心坐标(如上述固定大小CNN特征图所述)。假设卷积核大小k,填充大小p,步长大小s,则其输出层的相关属性计算如下:

  • 公式一基于输入特征个数和卷积相关属性计算输出特征的个数
  • 公式二计算输出特征图的jump,等于输入图的jump与输入特征个数(执行卷积操作时jump的个数,stride的大小)的乘积
  • 公式三计算输出特征图的receptive field size,等于k个输入特征覆盖区域(k-1)*j_{in}加上边界上输入特征的感受野覆盖的附加区域r_{in}
  • 公式四计算第一个输出特征的感受野的中心位置,等于第一个输入特征的中心位置,加上第一个输入特征位置到第一个卷积核中心位置的距离(k-1)/2*j_{in},再减去填充区域大小p*j_{in}。注意:这里都需要乘上输入特征图的jump,从而获取实际距离或间隔。
  • 图3 对图1中的例子执行感受野计算。第一行给出一些符号和等式;第二行和最后一行说明给定输入层信息下输出层感受野的计算过程。

    CNN的第一层是输入层,n = image sizer = 1j = 1start = 0.5。图3采用的坐标系中输入层的第一个特征中心位置在0.5。递归执行上述四个公式,就可以计算CNN中所有特征图中的感受野信息。图3给出这些公式计算的样例。

    这里给出一个python小程序,用于计算给定CNN架构下所有层的感受野信息。程序允许输入任何特征图的名称和图中特征的索引号,输出相关感受野的尺寸和位置。图4给出AlexNet下的例子。


  • 图4 AlexNet下感受野计算样例

  • # [filter size, stride, padding]
    #Assume the two dimensions are the same
    #Each kernel requires the following parameters:
    # - k_i: kernel size
    # - s_i: stride
    # - p_i: padding (if padding is uneven, right padding will higher than left padding; "SAME" option in tensorflow)
    # 
    #Each layer i requires the following parameters to be fully represented: 
    # - n_i: number of feature (data layer has n_1 = imagesize )
    # - j_i: distance (projected to image pixel distance) between center of two adjacent features
    # - r_i: receptive field of a feature in layer i
    # - start_i: position of the first feature's receptive field in layer i (idx start from 0, negative means the center fall into padding)
    
    import math
    convnet =   [[11,4,0],[3,2,0],[5,1,2],[3,2,0],[3,1,1],[3,1,1],[3,1,1],[3,2,0],[6,1,0], [1, 1, 0]]
    layer_names = ['conv1','pool1','conv2','pool2','conv3','conv4','conv5','pool5','fc6-conv', 'fc7-conv']
    imsize = 227
    
    def outFromIn(conv, layerIn):
      n_in = layerIn[0]
      j_in = layerIn[1]
      r_in = layerIn[2]
      start_in = layerIn[3]
      k = conv[0]
      s = conv[1]
      p = conv[2]
      
      n_out = math.floor((n_in - k + 2*p)/s) + 1
      actualP = (n_out-1)*s - n_in + k 
      pR = math.ceil(actualP/2)
      pL = math.floor(actualP/2)
      
      j_out = j_in * s
      r_out = r_in + (k - 1)*j_in
      start_out = start_in + ((k-1)/2 - pL)*j_in
      return n_out, j_out, r_out, start_out
      
    def printLayer(layer, layer_name):
      print(layer_name + ":")
      print("\t n features: %s \n \t jump: %s \n \t receptive size: %s \t start: %s " % (layer[0], layer[1], layer[2], layer[3]))
     
    layerInfos = []
    if __name__ == '__main__':
    #first layer is the data layer (image) with n_0 = image size; j_0 = 1; r_0 = 1; and start_0 = 0.5
      print ("-------Net summary------")
      currentLayer = [imsize, 1, 1, 0.5]
      printLayer(currentLayer, "input image")
      for i in range(len(convnet)):
        currentLayer = outFromIn(convnet[i], currentLayer)
        layerInfos.append(currentLayer)
        printLayer(currentLayer, layer_names[i])
      print ("------------------------")
      layer_name = raw_input ("Layer name where the feature in: ")
      layer_idx = layer_names.index(layer_name)
      idx_x = int(raw_input ("index of the feature in x dimension (from 0)"))
      idx_y = int(raw_input ("index of the feature in y dimension (from 0)"))
      
      n = layerInfos[layer_idx][0]
      j = layerInfos[layer_idx][1]
      r = layerInfos[layer_idx][2]
      start = layerInfos[layer_idx][3]
      assert(idx_x < n)
      assert(idx_y < n)
      
      print ("receptive field: (%s, %s)" % (r, r))
      print ("center: (%s, %s)" % (start+idx_x*j, start+idx_y*j))






   RCNN论文中有一段描述,Alexnet网络pool5输出的特征图上的像素在输入图像上有很大的感受野(have very large receptive fields (195 × 195 pixels)) 和步长(strides (32×32 pixels) ), 这两个变量的数值是如何得出的呢?
  • 13
    点赞
  • 56
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值