神經網絡感受野(receptive field)推到分析與計算(總結)

經典目標檢測和最新目標跟蹤都用到了RPN(region proposal network),錨框(anchor)是RPN的基礎,感受野(receptive field, RF)是anchor的基礎。

在卷積神經網絡中,感受野的定義是 卷積神經網絡每一層輸出的特徵圖(feature map)上的像素點在原始圖像上映射的區域大小。

例如1:原始圖像爲 5 x 5 ,卷積核(Kernel Size)爲 3 x 3 ,padding 爲 1 ,stride爲 2 ,依照此卷積規則,連續做兩次卷積。熟悉卷積過程的朋友都知道第一次卷積結果是 3 x 3 大小的feature map,第二次卷積結果是 2 x 2 大小的feature map。整個過程如圖所示:

 

 

如圖所示,第一層卷積結束後,感受野是3*3。在第二層卷積結束了,感受野是7*7。

例2:

 

圖中是個微型CNN,來自Inception-v3論文,原圖是爲了說明一個conv5x5可以用兩個conv3x3代替,從下到上稱爲第1, 2, 3層:

  1. 第2層左下角的值,是第1層左下紅框中3x3區域的值經過卷積,也就是乘加運算計算出來的,即第2層左下角位置的感受野是第1層左下紅框區域

  2. 第3層唯一值,是第2層所有3x3區域卷積得到的,即第3層唯一位置的感受野是第2層所有3x3區域

  3. 第3層唯一值,是第1層所有5x5區域經過兩層卷積得到的,即第3層唯一位置的感受野是第1層所有5x5區域

任意兩個層之間都有位置—感受野對應關係,但我們更常用的是feature map層到輸入圖像的感受野,如目標檢測中我們需要知道feature map層每個位置的特徵向量對應輸入圖像哪個區域,以便我們在這個區域中設置anchor,檢測該區域內的目標。

感受野有什麼用呢?

  • 一般task要求感受野越大越好,如圖像分類中最後卷積層的感受野要大於輸入圖像,網絡深度越深感受野越大性能越好

  • 密集預測task要求輸出像素的感受野足夠的大,確保做出決策時沒有忽略重要信息,一般也是越深越好

  • 目標檢測task中設置anchor要嚴格對應感受野,anchor太大或偏離感受野都會嚴重影響檢測性能

感受野的計算

首先介紹一種從後向前計算方法,極其簡單適合人腦計算,看看網絡結構就知道感受野了,之後介紹一種通用的從前往後計算方法,比較規律適合電腦計算,簡單編程就可以計算出感受野大小和位置。

感受野是一個矩形區域,如果卷積核全都長寬相等,則對應感受野就是正方形區域。輸出feature map中每個位置都對應輸入圖像一個感受野區域,所有位置的感受野在輸入圖像上以固定步進的方式平鋪。

我們要計算感受野的大小r(長或寬)和不同區域之間的步進S,從前往後的方法以感受野中心(x,y)的方式確定位置,從後往前的方法以等效padding P的方式確定位置。CNN的不同卷積層,用k表示卷積核大小,s表示步進(s1表示步進是1,s2表示步進是2),下標表示層數。

從後往前的計算方式的出發點是:一個conv5x5的感受野等於堆疊兩個conv3x3,反之兩個堆疊的conv3x3感受野等於一個conv5x5,推廣之,一個多層卷積構成的FCN感受野等於一個conv rxr,即一個卷積核很大的單層卷積,其kernelsize=r,padding=P,stride=S。

(如果我們將一個Deep ConvNet從GAP處分成兩部分,看成是FCN (全卷積網絡)+MLP (多層感知機),從感受野角度看FCN等價於一個單層卷積提取特徵,之後特徵經MLP得到預測結果,這個單層卷積也就比Sobel複雜一點,這個MLP可能還沒SVM高端,CNN是不是就沒那麼神祕了~)

以下是一些顯(bu)而(hui)易(zheng)見(ming)的結論:

  • 初始feature map層的感受野是1

  • 每經過一個conv k x k s1的卷積層,感受野 r = r + (k - 1),例常用k=3感受野 r = r + 2, k=5感受野r = r + 4

  • 每經過一個conv k x k s2的卷積層或max/avg pooling層,感受野 r = (r x 2) + (k -2),例常用卷積核k=3, s=2,感受野 r = r x 2 + 1,卷積核k=7, s=2, 感受野r = r x 2 + 5

  • 每經過一個maxpool 2 x 2 s2的max/avg pooling下采樣層,感受野 r = r x 2

  • 特殊情況,經過conv 1 x 1 s1不會改變感受野,經過FC層和GAP層,感受野就是整個輸入圖像

  • 經過多分枝的路徑,按照感受野最大支路計算,shotcut也一樣所以不會改變感受野

  • ReLU, BN,dropout等元素級操作不會影響感受野

  • 全局步進等於經過所有層的步進累乘,

  • 經過的所有層所加padding都可以等效加在輸入圖像,等效值P,直接用卷積的輸入輸出公式   反推出P即可

這種計算方法有多簡單呢?我們來計算目標檢測中最常用的兩個backbone的感受野。最初版本SSD和Faster R-CNN的backbone都是VGG-16,結構特點卷積層都是conv 3x3 s1,下采樣層都是maxpool 2 x 2 s2。先來計算SSD中第一個feature map輸出層的感受野,結構是conv4-3 backbone + conv3x3 classifier (爲了寫起來簡單省掉了左邊括號):

r = 1 +2 +2+2+2 )x2 +2+2+2 )x2 +2+2 )x2 +2+2 = 108

S = 2x2x2 = 8

P = floor(r/2 - 0.5) = 53

以上結果表示感受野的分佈方式是:在paddding=53(上下左右都加) 的輸入224x224圖像上,大小爲108x108的正方形感受野區域以stride=8平鋪。

再來計算Faster R-CNN中conv5-3+RPN的感受野,RPN的結構是一個conv3x3+兩個並列conv1x1:

r = 1 +2 +2+2+2 )x2 +2+2+2 )x2 +2+2+2 )x2 +2+2 )x2 +2+2 = 228

S = 2x2x2x2 = 16

P =floor(r/2 - 0.5) = 113

分佈方式爲在paddding=113的輸入224x224圖像上,大小爲228x228的正方形感受野區域以stride=16平鋪。

接下來是Faster R-CNN+++和R-FCN等採用的重要backbone的ResNet,常見ResNet-50和ResNet-101,結構特點是block由conv1x1+conv3x3+conv1x1構成,下采樣block中conv3x3 s2影響感受野。先計算ResNet-50在conv4-6 + RPN的感受野 (爲了寫起來簡單堆疊卷積層合併在一起):

r = 1 +2 +2x5 )x2+1 +2x3 )x2+1 +2x3 )x2+1 )x2+5 = 299

S = 2x2x2x2 = 16

P = floor(r/2 - 0.5) = 149

P不是整數,表示conv7x7 s2卷積有多餘部分。分佈方式爲在paddding=149的輸入224x224圖像上,大小爲299x299的正方形感受野區域以stride=16平鋪。

ResNet-101在conv4-23 + RPN的感受野:

r = 1 +2 +2x22 )x2+1 +2x3 )x2+1 +2x3 )x2+1 )x2+5 = 843

S = 2x2x2x2 = 16

P = floor(r/2 - 0.5) = 421

分佈方式爲在paddding=421的輸入224x224圖像上,大小爲843x843的正方形感受野區域以stride=16平鋪。

以上結果都可以反推驗證,並且與後一種方法結果一致。從以上計算可以發現一些的結論:

  • 步進1的卷積層線性增加感受野,深度網絡可以通過堆疊多層卷積增加感受野

  • 步進2的下采樣層乘性增加感受野,但受限於輸入分辨率不能隨意增加

  • 步進1的卷積層加在網絡後面位置,會比加在前面位置增加更多感受野,如stage4加捲積層比stage3的感受野增加更多

  • 深度CNN的感受野往往是大於輸入分辨率的,如上面ResNet-101的843比輸入分辨率大3.7倍

  • 深度CNN爲保持分辨率每個conv都要加padding,所以等效到輸入圖像的padding非常大

 


前面的方法是我自用的沒有出處,但後面要介紹的方法是通用的,來自一篇著名博客(https://medium.com/mlreview/a-guide-to-receptive-field-arithmetic-for-convolutional-neural-networks-e0f514068807)有求解代碼,再次強調兩種方法的結果是完全一致的。

文中給出通用的計算公式,也是逐層計算,不同點在於這裏是從前往後計算,核心四個公式:

上式中n是feature map的大小,p是padding,k是kernel size,j是jump(前面的S),r是感受野大小,start是第一個特徵向量(左上角位置)對應感受野的中心座標位置。搬運並翻譯:

  • 公式一是通用計算卷積層輸入輸出特徵圖大小的標準公式

  • 公式二計算輸出特徵圖的jump,等於輸入特徵圖的jump乘當前卷積層的步進s

  • 公式三計算感受野大小,等於輸入感受野加當前層的卷積影響因子(k - 1) * jin,注意這裏與當前層的步進s沒有關係

  • 公式四計算輸出特徵圖左上角位置第一個特徵向量,對應輸入圖像感受野的中心位置,注意這裏與padding有關係

從以上公式可以看出:start起始值爲0.5,經過k=3, p=1時不變,經過k=5, p=2時不變。

計算例子:

計算出r, j和start之後,所有位置感受野的大小都是r,其他位置的感受野中心是start按照j滑窗得到。這種方法比較規律,推薦編程實現。

有效感受野

NIPS 2016論文Understanding the Effective Receptive Field in Deep Convolutional Neural Networks提出了有效感受野(Effective Receptive Field, ERF)理論,論文發現並不是感受野內所有像素對輸出向量的貢獻相同,在很多情況下感受野區域內像素的影響分佈是高斯,有效感受野僅佔理論感受野的一部分,且高斯分佈從中心到邊緣快速衰減,下圖第二個是訓練後CNN的典型有效感受野。

 

這點其實也很好理解,繼續回到最初那個微型CNN,我們來分析第1層,下圖標出了conv3x3 s1卷積操作對每個輸入值的使用次數,用藍色數字表示,很明顯越靠近感受野中心的值被使用次數越多,靠近邊緣的值使用次數越少。5x5輸入是特殊情況剛好符合高斯分佈,3x3輸入時所有值的使用次數都是1,大於5x5輸入時大部分位於中心區域的值使用次數都是9,邊緣衰減到1。每個卷積層都有這種規律,經過多層堆疊,總體感受野就會呈現高斯分佈。

ECCV2016的SSD論文指出更好的anchar的設置應該對齊感受野.

References:

  1. https://zhuanlan.zhihu.com/p/44106492

     2. https://medium.com/mlreview/a-guide-to-receptive-field-arithmetic-for-convolutional-neural-networks-e0f514068807     

Attach:

 
 
# [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))

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章