功能性模块:(4)感受野计算:疑惑中。。。
一、模块介绍
使用深度学习的小伙伴,相信对感受野这个概念肯定是不会陌生的,当时使用比较大尺寸的卷积核还是使用比较小的卷积核还引发了一系列的讨论,一层网络的感受野还是非常容易计算的,但是整个网络结构的感受野要怎么计算呢?
其实对于本文中涉及到计算感受野的方式LZ也是比较有疑惑的,如果小伙伴有自己的理解,欢迎给LZ留言,一起探讨一下!
LZ主要参考的文章是这一篇A guide to receptive eld arithmetic for Convolutional Neural Networks,所以图片基本上也来自这篇文章。
这个LZ自己需要储备下,所以就按照文章的内容讲的稍微详细些吧。
这是文中说的两种显示感受野的方式,在这两个例子中,假设原始打输入图像是5x5,卷积核的尺寸为3x3,padding的尺寸为1x1,stride打大小为2x2,这样我们进行一次卷积操作,就可以得到3x3的特征图,也就是下面示意图中中间层(用绿色进行表示),再重复一次卷积操作,就可以得到2x2的特征图,在图中用橙色进行表示。我们可以从下图直观的看出绿色feature map上每个像素的感受野对应原图上3x3的区域,橙色feature map对应到原始原图上7x7的区域。
此时需要暂停1s,小伙伴们有没有什么疑惑?
没错,就是橙色feature map上每个像素上面的感受野都已经超过了输入的原图大小,那样我们计算感受野的方式是否合理呢?现在才只有两层,如果输入224x224,计算出感受野为7x7好像也是可以接受的。
对应已知输入大小,卷积核的尺寸,padding的尺寸,stride的大小,我们就可以按照下方的公式得到对应输出特征图的大小
感受野计算公式:
- n n n:特征图的大小,这里其实默认都是正方形的
- s s s:stride的尺寸
- p p p:padding的尺寸
- k k k:卷积核的大小
- r r r:感受野的尺寸,当前输入特征图的感受野,如果是输入图像,这里应该就是1
- j j j:相邻两个特征图的距离,这是什么意思呢?就是在feature map上相邻两个像素,对应原图输入是相隔几个像素的值
- s t a r t start start:start坐标相当于输入图像的坐标系,如果是输入图像,那么应该是(0.5,0.5)
二、代码实现
# [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
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]))
def ComputeReceptiveField(imsize, convnet, layer_names):
# 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------")
layerInfos = []
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("------------------------")
if __name__ == '__main__':
# toy experiments
imsize = 5
convnet = [[3, 2, 1], [3, 2, 1]]
layer_names = ['conv1', 'conv2']
print("len of convnet: ", len(convnet))
print("len of layer_names: ", len(layer_names))
ComputeReceptiveField(imsize, convnet, layer_names)
输出是这样的:
其中这个计算也只能是粗略的计算对应的感受野,如果加上pad操作的话,那么原图会扩充的很大,这个感受野计算的意义也就没那么大了,并且原始图像的像素对多次卷积后的特征图中的像素贡献是不一样的,那么具体贡献衰减到什么程度,就可以不进行计算呢?这个确实还需要考虑!
参考
- A guide to receptive eld arithmetic for Convolutional Neural Networks