一:回顾CNN
其中大家常见到的CNN运算原理应该就是下面这张图了
卷积本质就是kernel覆盖区域和kernel对应点乘,然后加和,最后每个单独维度的kernel的值加和,如果有bias再加上bias就得到这个filter的对应位置的feature map的值。这里揭开我之前的疑惑就是,同一个filter不同维度的filter'上的值是不同的,然后不同的filter也是不同值得。
二:感受野
感受野的定义:
也就是某个层上的某个feature map上的某个点,对应到原输入input图像上的区域的size,然后一个感受野可以用该感受野再原图的center location坐标以及size来表征。
根据:A guide to receptive field arithmetic for Convolutional Neural Networks
得到的几个CNN中的计算公式:
n-in n-out分别表示前一层输入特征图大小和后面输出的特征图大小,p,k,s表示padding kernel_size,stride
j-out,j-in表示的是jump,我理解的其实就是累乘的stride
r-in,r-out表示前一层的感受野大小和后一层感受野的大小
start-in,start-out表示前一层的第一个feature点的中心坐标,后一层的第一个feature点的中心坐标
前面两个公式比较好理解,第一个就是由于padding stride存在,当kernel在input上滑动卷积操作时,加上两边padding大小减去kernel大小然后除于stride得到输出的feature map尺寸。
第二个就是累乘的strde之积。
然后就是第三个公式:
直观上理解就是前面一层的感受野,此时由于当前层的感受野只限于前面的计算,后面一层的感受野则在此基础上加上由于padding和stride导致在两边增加的感受野(padding)出来。所以以上公式都是从前往后计算,即依次从浅层网络往后面深层网络计算的。
然后是第四个公式:
直观上理解就是由于padding和kernel的覆盖的总区域导致中心坐标发生偏移,最终结果就是最开始的center location加上kernel覆盖的区域的中心(除以2)减去由于边界padding增加的区域的中心,单边padding所以是只有一个p。所以就得到这个最终公式。下面是该博客上的一个例子推演:
其实如果是从后往前推算感受野可能会好理解,但是如果是网络设计的复杂或者层数很深就不好手算,故此可以借助以上公式方便计算,此外还有该博主给出的脚本用于计算感受野:
# [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 = input ("Layer name where the feature in: ")
layer_idx = layer_names.index(layer_name)
idx_x = int(input ("index of the feature in x dimension (from 0)"))
idx_y = int(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))
后来我又发现其实tf提供了这么一个工具用于计算感受野这些参数:
https://github.com/tensorflow/tensorflow/tree/master/tensorflow/contrib/receptive_field
这些参数虽说对算法模型设计帮助看起来无关紧要,但是其实是很重要的,这些就是CNN的本质,也是了解底层实现的原理。
最后如果是想从后往前计算感受野的大小,可以使用如下公式:
R(i) = (R(i+1) - 1)*stride + k_size,R(i),R(i+1),stride,k_size分别表示第i层感受野,第i+1层感受野,第i层的stride和第i层的kernel size