【轉】卷積神經網絡中的感受野


原文鏈接:https://www.zhihu.com/collection/172241377

感受野(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))

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM