在过去的几年里,卷积神经网络成为最常用的深度学习概念之一。它们被用于各种行业的物体检测、姿势估计和图像分类。例如,在医疗保健领域,它们被大量用于放射学,以检测乳房X光照片和 X光图像 中的疾病。
这些架构的一个概念,在文献中经常被忽略,那就是接受场的概念。从本质上讲,所有国家的架构都使用这个概念,围绕它建立自己的想法。这就是为什么在这篇文章中,我们要探讨什么是感受野,它们是如何定义的,以及我们如何计算它们的大小。
为了理解本文的概念,我们假设你已经熟悉了卷积运算、池化运算和其他基本的卷积神经网络原理。如果你需要学习更多关于这些概念的知识,请务必查看我们的 用Python进行机器学习的终极指南.
1.什么是卷积神经网络的感受野?
作为一个简单的提醒,卷积神经网络使用内核(或过滤器)来检测图像中的特征。这是通过在图像上使用卷积运算来完成的。每个内核都 "看着 "输入图像的某个部分,进行乘法运算,然后按定义的像素数(跨度)移动。在输入空间中,一个特定的CNN的内核所看的这个区域被称为 "感知场"。

卷积过程
感受野是由中心和大小定义的。感受野的中心是非常重要的,因为它决定了像素的重要性。如果像素位于更靠近中心的位置,那么它在该特定计算中的重要性就更高。这意味着CNN的特征更注重于接收区域的中心像素。
卷积神经网络在此基础上更新其内核偏向,这就是为什么感受野是一个如此重要的概念。由于现代CNN是深层的,即堆叠多个卷积层,每层的感受野是不同的。如果该层在架构中比较深,它的感受野就会比较大,因为它的输入空间是来自前几层的特征图,即已经降采样的输入图像。
让我们观察一下下面的例子。一个输入图像是5×5,我们使用两个卷积层。两层都使用3×3核,2×2跨度和1×1填充。

第一层创建一个3×3的特征图。它的感受野也有同样的大小。第二层对该特征图的输出进行卷积,创建2×2的特征图。好吧,这很酷,但第二层的感受野是什么?记住,感受野是输入图像的空间,而不是特征图的空间。这就是它。

第二层的感受野
像这样观察它也很有趣。

第一层和第二层的感受野(左)以及第一层和第二层的感受野中心(右)。 来源
在右边的图像中,我们可以观察到感受野的中心。注意每层之后的步幅是如何累积的,以及第一层的中心比第二层的步幅更接近对方。
2.CNN中的感受野的数学特性
现在,我们对感受野是什么以及结构的深度如何改变它有了一些直观的认识。让我们再深入一点,了解其背后的数学原理。说到这个计算,第一步是得到每一层的输出特征图的大小。这是由公式计算出来的。

其中ni 是第i层的输出特征的数量,ni-1是第i 层的输入特征的数量*,p* 是第i层的padding大小*,* k是第i 层的kernel大小,s 是第i 层的stride。一般来说,跳跃表示累积的步幅。我们可以通过乘以我们正在调查的层之前的所有层的步幅来得到它。我们可以使用这个公式。

其中ji-1是前一层的跳跃。最后,利用以前的数值,我们可以用这个公式计算出感受野的大小。

用于计算某一层的感受野的上述公式的更一般形式看起来是这样的。

正如我们所提到的,感受野是由中心和大小决定的。因此,这里是计算给定层的感受野中心的公式。

此刻,你可能会想知道输入图像的数值是多少,因为我们没有前一层的数据。对于输入图像,我们使用这些值:
- n = 图像大小
- r = 1
- j = 1
- 开始 = 0.5
让我们把这些公式应用到我们之前的例子中。第0层是输入图像,其尺寸为5×5,这意味着n0为5。 默认情况下,r0、 j0和start0分别为1、1和0.5。当我们将前面的函数应用于第二层时,我们得到的值是n1=2,r1=7,j2=4,start1=0.5。

请注意,在这些例子中,为了简化,我们假设CNN架构是对称的,而且输入图像是方形的。
3.用Python实现
好吧,让我们在Python中实现这些计算。假设我们想用一个字典来描述CNN架构。例如,AlexNet看起来会是这样的:
alex_net = {
'conv1': [11, 4, 0],
'pool1': [3, 2, 0],
'conv2': [5, 1, 2],
'pool2': [3, 2, 0],
'conv3': [3, 1, 1],
'conv4': [3, 1, 1],
'conv5': [3, 1, 1],
'pool5': [3, 2, 0],
'fc6-conv': [6, 1, 0],
'fc7-conv': [1, 1, 0]
}
Key是层的名称,value是分别由内核大小、stride和padding组成的数组。这意味着层conv2有 5×5的内核,1×1的步长和2×2的填充。接下来,我们像这样实现ReceptiveFieldCalculator类。
import math;
class ReceptiveFieldCalculator():
def calculate(self, architecture, input_image_size):
input_layer = ('input_layer', input_image_size, 1, 1, 0.5)
self._print_layer_info(input_layer)
for key in architecture:
current_layer = self._calculate_layer_info(architecture[key], input_layer, key)
self._print_layer_info(current_layer)
input_layer = current_layer
def _print_layer_info(self, layer):
print(f'------')
print(f'{layer[0]}: n = {layer[1]}; r = {layer[2]}; j = {layer[3]}; start = {layer[4]}')
print(f'------')
def _calculate_layer_info(self, current_layer, input_layer, layer_name):
n_in = input_layer[1]
j_in = input_layer[2]
r_in = input_layer[3]
start_in = input_layer[4]
k = current_layer[0]
s = current_layer[1]
p = current_layer[2]
n_out = math.floor((n_in - k + 2*p)/s) + 1
padding = (n_out-1)*s - n_in + k
p_right = math.ceil(padding/2)
p_left = math.floor(padding/2)
j_out = j_in * s
r_out = r_in + (k - 1)*j_in
start_out = start_in + ((k-1)/2 - p_left)*j_in
return layer_name, n_out, j_out, r_out, start_out
这个类有三个方法:
- calculate- 一个公共方法,用于协调计算和打印输出特征图的大小、跳跃、接受图像的大小和中心。
- _print_layer_info- 用于打印信息的辅助方法
- _calculate_layer_info- 用于计算的辅助方法。
3.1 如何使用它?
下面是我们如何使用它。
calculator = ReceptiveFieldCalculator()
calculator.calculate(alex_net, 227)
------
input_layer: n = 227; r = 1; j = 1; start = 0.5
------
------
conv1: n = 55; r = 4; j = 11; start = 5.5
------
------
pool1: n = 27; r = 8; j = 19; start = 9.5
------
------
conv2: n = 27; r = 8; j = 51; start = 9.5
------
------
pool2: n = 13; r = 16; j = 67; start = 17.5
------
------
conv3: n = 13; r = 16; j = 99; start = 17.5
------
------
conv4: n = 13; r = 16; j = 131; start = 17.5
------
------
conv5: n = 13; r = 16; j = 163; start = 17.5
------
------
pool5: n = 6; r = 32; j = 195; start = 33.5
------
------
fc6-conv: n = 1; r = 32; j = 355; start = 113.5
------
------
fc7-conv: n = 1; r = 32; j = 355; start = 113.5
------
结论
在这篇文章中,我们探索了有趣的、有时被忽视的感受野的概念。我们有机会了解它背后的直觉和数学,并使用Python实现了一个版本的接受场计算器。
谢谢您的阅读!