1. 程式人生 > >關於CNN中感受野的理解和計算方法

關於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. 卷積神經網路物體檢測之感受野大小計算