卷积神经网络与之前所讨论的常规神经网络十分相似,它们都由可以对权重和偏置进行学习的神经元构成。每个神经元接收一些输入,然后执行点积操作,再紧接一个任意的非线性函数。整个网络仍然表示为一个可微分的评估函数,从其一端输入原始图像像素,另一端输出类别的概率。其最后一层(全连接层)同样有损失函数,并且我们学习常规神经网络的方法和技巧在这里仍然奏效。
那么卷积神经网络有什么不同之处?首先卷积网络很明确地假设所有输入都为图像,基于此假设,我们在结构中添加了一些特有的性质。这些特有的性质使得前向函数的实现更加高效,并且极大的减少了网络中参数的数量。
回顾:正如我们所熟知的,常规神经网络接收一个输入(一维向量),并通过一系列的隐藏层对其进行转换。每个隐藏层由一组神经元构成,其中每个神经元都与前一层的全部神经元相连接,并且同一层的各个神经元都是独立工作的,它们的连接并不共享。位于最后一层的全连接层称为输出层,在分类模型中,它将会得出类别的概率。
常规的神经网络并不能很好的应用于完整的图像:在CIFAR-10(一个开源在线的图像数据集)中,图像的尺寸仅为32×32×3(宽度32,高度32,3个通道),因此一个常规神经网络的第一层隐藏层中的单个全连接的神经元将会有32×32×3=3072个权重(全连接网络中,输入向量有多少维,每个神经元就需要有多少个权重,这样才能做点积)。这个数量看起来还好,但是显然这种全连接的结构并不能扩展到更大尺寸的图像。例如:一个200×200×3的大尺寸图像,将会使神经元有200×200×3=120000个权重。并且我们几乎肯定会有许多这样的神经元,因此参数的数量将会急剧增加。显然,这种全连接结构十分浪费,并且大量的参数将会很快导致过拟合。
三维神经元:卷积神经网络充分利用了其输入是由图像组成的这一事实,并以更加合理的方式对结构进行了约束。特别是,与常规网络不同,卷积网络各层的神经元具有三个维度,即宽度、高度、深度。例如:CIFAR-10中作为输入的图像是输入特征体,并且该特征体的尺寸为32×32×3(分别为宽度,高度,深度)。正如我们随后将看到的,每一层的神经元仅与前一层的局部相连,而不是采用全连接的方式。此外,CIFAR-10的最终输出层的尺寸为1×1×10,因为在卷积网络结构的最后我们将会把完整的图像缩减为关于类别概率的一维向量,且该一维向量沿着深度维度排列。如下图所示:
左图:常规三层神经网络。右图:由三维神经元构成的卷积网络(宽度,高度,深度)。卷积网络的每一层将三维输入特征体换为经过神经元激活的三维输出特征体。在这个图例中,红色的输入层代表图像,因此它的宽度和高度就是图像的宽度和高度,而它的深度为3(红,绿,蓝三个通道)。
正如上文所描述的,一个简单的卷积网络由一系列的层所构成,并且卷积网络的每一层通过一个可微函数将一个特征体转换成另一个特征体。我们主要用三类层来构造卷积网络:卷积层、池化层以及全连接层。我们将通过堆叠这些层来形成一个完整的卷积网络结构。
示例:接下来我们将讨论更多细节问题,一个用于对CIFAT-10进行分类的简单的卷积网络的结构是(输入层-卷积层-ReLu函数层-池化层-全连接层),详细来说:
通过这种方式,卷积网络将原始图像逐层地从原始像素值转换为最终的类别概率。注意,有些层包含一些参数,而有些层则不包含参数。尤其是卷积层和全连接层,其不仅有激活函数,也有参数(神经元的权重和偏置),二者共同对输入特征体执行变换操作。另一方面,ReLu层和池化层将执行一个具有固定功能的函数。卷积层和全连接层中的参数将使用梯度下降进行训练,以使卷积网络计算的类别概率与训练集中每个图像的标签一致。
综上所述:
现在,让我们来对每一层,及其超参数的细节以及其连接方式进行具体的描述。
卷积层是一个卷积神经网络的核心组成部分,它负责完成大部分计算繁重的工作。
概述以及直观认识:我们先来讨论卷积层是如何进行计算的,并且是从更加直观的角度来进行讨论,而不是将其类比为脑神经。首先,卷积层的参数由一组可以进行学习的滤波器组成。每一个滤波器都是一个小的二维面区域(沿宽度和高度),但却延伸到输入特征体的全部深度。例如:卷积网络第一层上的一个典型滤波器的尺寸可能为5×5×3(即,5个像素的宽度和高度,而3是由于图像的深度为3)。在正向传播期间,我们沿着输入特征体的宽度和高度方向滑动(更确切地说是卷积)每个滤波器,并计算滤波器的端口与滤波器处于每个位置上的输入的点积。当我们将滤波器沿着输入特征体的高度和宽度滑过之后,将会得到一个二维的激活映射图,该图给出了滤波器在滑动过的每个位置上得到的结果。直观地说,网络将让滤波器进行学习,使得当他们看到某种类型的视觉特征时就会激活,例如第一层上某种方向的条棱或某种颜色的斑点,或者最终在网络的更高层上形成的整个蜂窝或轮状图案。
接下来,我们将在每个卷积层上使用一整组的滤波器(即12个滤波器),其中每一个滤波器都将产生一个二维的激活映射图。我们将这些激活映射图沿着深度堆叠起来,得到一个输出特征体。
从脑神经角度理解:如果您喜欢把它与脑神经进行类比的话,那么三维的输出特征体的每个值也可以解释为神经元的输出值,该神经元只关注于输入的一小部分区域,并且与左右相邻的神经元共享参数(因为这些神经元的输出值都是来自同一个滤波器的结果)。接下来我们将讨论这些神经元的连接细节,它们在空间中的排列方式,以及它们的参数共享方案。
局部连接:当处理例如图像这种高维输入时,正如我们上面看到的,将所有神经元进行全连接是不现实的。因此,我们将每个神经元只与输入特征体的部分相连接。这种连接在二维面(即宽度和高度)上的范围是一个超参数,称之为神经元的接收域(相当于是过滤器的大小),而沿着深度方向的连接范围却总是和输入特征体的深度相等。必须再次强调我们在处理二维面(宽度和高度)和深度上的不对称性。在二维面上是局部连接(沿着宽度和高度的),而沿着输入特征体的整个深度方向进行的却是全连接。
左图:浅红色所表示的是输入特征体(即32×32×3),而深红色表示第一层卷积层上的神经元所连接的局部特征体的示例。卷积层上的每个神经元只与输入特征体的局部相连接,但是在深度上却是全连接的。注意,这里沿着深度方向有多个神经元(在本例中是5个),它们都注视着输入特征体的同一区域(参见后文提到的深度列)。右图:仍然计算神
经元的权重和输入的点积,再紧接一个非线性函数,只是它们的连接现在被限制在局部区域内。
空间排列:我们已经阐明了卷积层中的每个神经元与输入特征体之间的连接,但是我们还没有讨论神经元的个数以及它们如何进行排列的。三个超参数控制着输出特征体的尺寸:分别是深度、步长以及零填充,接下来我们将对其进行讨论:
我们可以根据输入特征体的尺寸 (W) ,卷积层神经元的接收域的尺寸 (F) ,它们所应用的步长 (S) ,以及边界上使用的零填充的尺寸 (P) ,来计算输出特征体的尺寸。您可以自己证明一下,计算有多少个神经元合适的公式为 (W−F+2P)/S+1 。例如有一个7×7的输入,步长为1的3×3的滤波器,没有零填充即 P=0 ,于是我们会得到5×5的输出。如果是2步长我们将会得到3×3的输出。让我们再来看一个图形化的例子:
如图所示:在这个例子中,只有一个空间维度(X轴),神经元的接收域F=3,输入大小为W=5,而零填充P=1。左边:神经元以1的步长滑过输入,给出的输出尺寸为(5 - 3 + 2)/ 1 + 1 = 5。右边:神经元的步长为S=2,给出的输出尺寸为(5 - 3 + 2)/ 2 + 1 = 3。而本例中的神经元的权重为1,0,-1(最右边的三个绿色方格所示),而偏置为0。显然,所有的黄色的神经元之间共享这些权重参数(参见下面的参数共享)。
零填充的使用:在上面的例子中,左图中的输入尺寸是5,输出尺寸也是5。这是因为我们的接收域是3并且我们使用的零填充的尺寸为1。如果没有使用零填充,那么输出特征体的尺寸将是3。通常,在步长S = 1时,将零填充设置为P = (F - 1)/2 可以确保输入特征体与输出特征体具有相同大小的二维面(宽度和高度)。以这种方式使用零填充是非常常见的,我们将在讨论更多卷积网络的结构时对其充分原因进行探讨。
步长的限制:需要再次注意,决定神经元空间排列方式的超参数是相互约束的。例如,当输入尺寸 W=10 ,不使用零填充即 P=0 ,滤波器的大小为 F=3 ,那么步长就不可能为2,因为 (W−F+2P)/S+1=(10−3+0)/2+1=4.5 ,即结果为非整数,这显然不行。因此,这样设置超参数是无效的,神经网络库可能会抛出一个异常,或是对其余部分进行零填充,或是对输入进行剪裁,等等。正如我们将在神经网络结构部分看到的,适当调整神经网络的尺寸以使所有维度都得到合理的解决可能是一件非常头疼的事情,因此使用零填充和某些设计准则将显著缓解这种压力。
真实世界的例子: Krizhevsky等人赢得2012年ImageNet挑战赛的架构能够接受大小为227×227×3的图片输入。在第一层卷积层,使用的神经元的接收域尺寸为 F=11 ,步长 S=4 ,零填充 P=0 。因为 (227−11)/4+1=55 ,并且由于卷积层深度 K=96 ,因此卷积层的输出大小为55×55×96。55×55×96个神经元中的每一个都与输入特征体中大小为11×11×3的区域连接。此外,每一个深度列(纤维条)上的神经元都与同一个11×11×3的区域相连接,但每个神经元必然都有着不同的权重。
参数共享:参数共享方案在卷积层中用于控制参数的数量。我们使用上述真实世界的例子,在第一层卷积层中共有55×55×96=290400个神经元,并且每一个神经元都有11×11×3=363个权重和一个偏置。将这些加在一起,那么仅在卷积网络的第一层上就有290400×364=105705600个参数。显然,这个数量非常大。事实证明,我们可以通过一个合理的假设来大大减少参数的数量:即一个特征值如果在一些位置上( x,y )可用于计算,那么在另一个位置上( x2,y2 )也可用于计算。换句话说,将一个2维面切片表示为深度切片(例如:尺寸为55×55×96的特征体具有96个深度切片,每个深度切片尺寸为55×55)。我们将限制每个深度切片中的神经元具有相同的权重和偏置。有了这种参数共享方案,我们例子中的第一层卷积层中将只有96组不同的权重(每一组对应一个深度切片),总共有96×11×11×3=34848个权重,或者说是34944个参数(加上96个偏置)。或者说,每个深度切片中的55×55个神经元现在将使用相同的参数。在实际的反向传播过程中,特征体中的每个神经元都会计算其权重的梯度,但是该梯度将叠加在每个深度切片上,并且仅更新每个切片的一组权重。
此图为Krizhebsky等人学习到的示例滤波器,此处显示的96个滤波器中每个滤波器的尺寸为11×11×3,并且每个滤波器由一个深度切片中的55×55个神经元共享。注意,参数共享假设是相对比较合理的:即如果在图片中的某个位置检测到水平条棱很重要,那么由于图片的平移不变性,它也将在图片的其他位置起到重要作用,这是很直观的。因此不需要重新进行学习来检测卷积层输出特征体中每个55×55不同位置上的水平条棱。
请注意,有时参数共享假设可能没有意义。当输入到卷积网络中的图像具有特定的中心结构时,尤其如此,例如,我们期望在图像的一侧学习到完全不同的特征而不是另一侧。一个实际的例子是,当输入的是在图像中居中的人脸图像时,你可能(而且应该)希望在不同的空间位置学习到眼部或头发的特定特征。在这种情况下,通常会放宽参数共享方案,而只将该层称之为局部连接层。
Numpy实际举例:为了使以上的讨论更加具体,我们将用具体例子的代码来阐明上述思想。假设输入特征体是一个numpy arrayX
,于是:
(x,y)
位置上的深度列(或者说是纤维条)为X[x,y,:]
。d
位置上的激活映射图为X[:,:,d]
。假设输入特征体X
的尺寸为X.shape:(11,11,4)
。此外,假设我们不使用零填充(即 P=0 ),滤波器尺寸为 F=5 ,并且步长为 S=2 。因此输出特征体的尺寸将会是 (11−5)/2+1=4 ,即给定一个宽度和高度都为4的特征体。输出特征体中的激活映射图(称之为V
)如下所示(在该例中只对一部分元素进行计算)。
V[0,0,0] = np.sum(X[:5,:5,:] * W0) + b0
V[1,0,0] = np.sum(X[2:7,:5,:] * W0) + b0
V[2,0,0] = np.sum(X[4:9,:5,:] * W0) + b0
V[3,0,0] = np.sum(X[6:11,:5,:] * W0) + b0
您应该还记得在numpy中*
操作表示array之间的每个元素相乘。同时向量W0
是神经元的权重向量,b0
是神经元的偏置。这里假设W0
的尺寸为W0.shape:(5,5,4)
,因为滤波器的尺寸为5×5,输入特征体的深度为4。注意,在每个点上,我们都是像之前普通神经网络中那样进行点积,此外,正如我们看到的,我们使用相同的权重和偏置(由于参数共享),并且沿着宽度维度以2为单位递增(即步长)。要构建输出特征体中的第二个激活映射图,我们可以:
V[0,0,1] = np.sum(X[:5,:5,:] * W1) + b1
V[1,0,1] = np.sum(X[2:7,:5,:] * W1) + b1
V[2,0,1] = np.sum(X[4:9,:5,:] * W1) + b1
V[3,0,1] = np.sum(X[6:11,:5,:] * W1) + b1
V[0,1,1] = np.sum(X[:5,2:7,:] * W1) + b1
(沿y滑动位置的例子)V[2,3,1] = np.sum(X[4:9,6:11,:] * W1) + b1
(既沿x又沿y滑动的例子)正如我们看到的,因为我们正在计算第二个激活映射图,故我们索引到V
的第二层深度,并且我们使用了一组不同的参数W1
。在上述例子中,我们为了简洁起见,只计算输出特征体 arrayV
的一部分。此外,这些激活映射图中的每个元素通常都会执行一个诸如ReLu函数之类的激活函数,但是这里并没有提及。
总结:对卷积层进行一下总结。
需要四个超参数
产生一个尺寸为 W2×H2×D2 的特征体,并且
通过参数共享,它将为每个滤波器引入 F⋅F⋅D1 个权重,总共有 (F⋅F⋅D1)⋅K 个权重和 K 个偏置。
一种比较常见的超参数的设置为 F=3,S=1,P=1 。
矩阵乘法的实现:注意,卷积运算本质上是将滤波器和输入特征体的局部区域进行点积。而卷积层的常见实现模式是利用这一事实并将卷积层的正向传播制定为如下的一个大矩阵乘法:
X_col
,其中每列都是一个被拉伸的接收域,因此总共有55×55=3025列。W_row
。np.dot(W_row,X_col)
,它算出每个滤波器和其在每个位置上的接收域内的输入执行点积的结果值,在我们的例子中,该操作的输出矩阵大小为96×3025。这种方法的缺点是浪费内存,因为输入特征体中的一些值在X_col
中被复制了多次。然而,其好处是有很多非常有效的矩阵乘法的实现方式可以利用。此外,我们可以重复使用和im2col相同的思想来执行池化操作,我们将在下面的内容中来讨论该操作。
反向传播:卷积操作(对于数据和权重)的反向传播也是卷积(但是,却是具有空间反转的滤波器)。这很容易用一个玩具例子在1维情况下推导出来(在这里先不进行扩展)。
1×1卷积:一些论文开始会使用1×1卷积作为网络的研究工作。有些人一开始看到1×1卷积会感到有些困惑,尤其是当他们具有信号处理研究背景时。通常情况下,信号是2维的,所以1×1卷积是没有意义的(它只是逐点缩放)。然而,在卷积网络中情况并非如此,因为我们一定要记住,我们是在3维特征体上进行操作,并且滤波器始终是贯穿输入特征体的整个深度。例如,如果输入特征体是32×32×3,于是执行1×1卷积将会高效地执行3维点积(因为输入激活量的深度是3)
扩张卷积:一项最近的研究进展打算向卷积层中再引入一个超参数(参见Fisher Yu 和 Vladlen Koltun的论文),称之为扩张度。到目前为止,我们只讨论了连续的卷积滤波器。然而,我们也可以设置一个在每个单元格之间都有空格的滤波器,称之为扩张。举例来说,假设在1维空间上一个大小为3的滤波器W
对输入X
进行计算可得:w[0]*x[0] + w[1]*x[1] + w[2]*x[2]
,即扩张度为0。若扩张度为1,我们将会计算:w[0]*x[0] + w[1]*x[2] + w[2]*x[4]
。换句话说,滤波器的应用具有大小为1的间隔。在某些设置中,将其与0扩张滤波器结合使用,将会非常有用。因为它允许你使用更少的图层迅速地将输入的空间信息进行整合。例如:如果您将两个3x3 的卷积层叠在一起,那么第二层上的神经元将对输入特征体有着的5x5的接收域(我们可以说第二层上神经元的有效接收域是5×5)。 如果我们使用扩张卷积,那么这个有效的接收域会增长得更快。
在卷积网络结构中,在连续的卷积层之间周期性的插入池化层是很常见的。其功能是逐步减少表示的空间尺寸,以减少网络中的参数数量以及降低计算量,且因此也可以控制过拟合。池化层在输入特征体的每个深度切片上独立运行,并使用MAX操作调整其二维面的尺寸。最常见的池化层的形式具有大小为2×2的滤波器,在输入特征体的每个深度切片上分别沿着宽度和高度执行以2为步长的降采样操作,以丢弃75%的特征值。
在这种情况下,每个MAX操作将会从4个数值中(在某个深度切片的一个小的2×2的区域)选择最大的一个数值。而深度方向仍保持不变,更一般地来说,池化层:
需要两个超参数:
产生一个尺寸为 W2×H2×D2 的特征体,且:
由于它对输入执行一个固定的函数计算,因此不包含参数。
值得注意的是,在实际中,只建立两种常见形式的最大池化层:具有 F=3,S=2 的池化层(也叫做交叉池化),以及更常见的 F=2,S=2 。而以更大的接收域进行池化则太具有破坏性。
常见池化:除了最大池化之外,池化单元还可以执行其他功能,例如平均池化甚至是L2-正则池化。过去人们经常使用平均池化,但最近,其地位逐渐被已经在实践中有着更良好表现的最大池化所替代。
池化层在输入特征体的每个深度切片上独立地执行二维面上的降采样操作。左图:在该例中,尺寸为224×224×64的输入特征体由尺寸为2×2的滤波器以2为步长池化为一个尺寸为112×112×64的的输出特征体。注意到,特征体的深度保持不变。右图:最常见的降采样操作是取最大值,即最大池化,图中所示的是以2为步长进行的最大池化。也就是说每个最大值都是从四个数值中选择的(小的2×2的区域)。
反向传播:回顾一下反向传播章节, max(x,y) 操作的反向传播非常简单,因为只需要找到前向传播中输入的最大值的梯度。因此,在池化层的前向传播期间,通常对其特征体的最大值的索引进行跟踪(通常称之为转换器),以使反向传播期间能更加高效地寻找到其梯度。
避免池化:许多人并不喜欢这种池化操作,并认为我们可以完全摆脱掉它。例如:为了达到简单性的目的,人们建议所有卷积网络都放弃池化层转而采用仅由重复的卷积层组成的结构。为了减少表征的大小,他们建议偶尔在卷积层中使用更大的步长。在训练良好的生成模型中,丢弃池化层也是很重要的,例如变分自动编码器(VAEs)或生成对抗网络(GAN)。 未来的网络结构很可能很少有或者几乎没有池化层。
人们已经提出了许多类型的归一化层来用于卷积网络结构,有些时候意图采取在生物脑中观察到的抑制方案。然而,这些层都已经失宠,因为实际上即使它们对卷积网络结构有着一些贡献的话,其贡献也已经被证明是极其有限的。对于各种类型的归一化,请参阅Alex Krizhevsky的cuda-convnet库API中的讨论。
正如在常规神经网络中看到的,全连接层中的神经元与前一层的全部特征值完全连接。因此其输出特征可以通过矩阵乘法和偏置来进行计算。
值得注意的是,全连接层和卷积层之间的唯一区别在于,卷积层中的神经元仅与输入中的局部区域相连,并且卷积层中的许多神经元共享参数。然而,全连接层和卷积层的神经元仍然都进行点积运算,所以它们的函数形式是相同的。因此,事实证明可以在全连接层和卷积层之间进行转换:
全连接层->卷积层:在这两种转换中,将全连接层转换为卷积层的能力在实践中非常有用。想象一下,采用尺寸为224×224×3的图像作为输入的卷积网络结构,使用一系列的卷积层和池化层来将图像压缩为尺寸为7×7×512的特征体(稍后我们会看到,在AlexNet结构中,这是通过使用5个池化层对输入的二维面执行 F=2 的降采样操作,使得最终的二维面尺寸为224/2/2/2/2/2=7)。紧接着,AlexNet使用两个有4096个神经元的全连接层,并且最终使用有1000个神经元的全连接层作为最后一层,用于计算类别的概率。如上所述,我们可以将这三个全连接层每个都转换为卷积层:
实际上,每一个转换都涉及将全连接层中的权重矩阵 W 重塑为卷积层的滤波器。事实证明,在一次前向传播中,这种转换使我们即使面对更大的图像,也能在其许多区域位置上非常有效地“滑动”原始卷积网络
例如,如果尺寸为224×224的图像输出得到7×7×512的特征体,即二维面上缩小了32倍。那么由于384/32=12,故一个尺寸为384×384的图像,将通过转换后的网络结构得到一个尺寸为12×12×512的特征体。接下来,我们刚才从三个全连接层转换而来的三个卷积层将会输出最终特征体,其尺寸为6×6×1000,因为(12-7)/1+1=6。注意,我们现在得到的是关于该尺寸为384×384的图像的整个6×6的概率矩阵。而不是1×1×1000的一维向量。
在一次前向传播中,转换后的卷积网络自然比在全部的36个位置上迭代原始卷积网络更加高效,其原因就是因为这36个计算共享计算资源。这种技巧在实际中也被用于提升模型的性能表现。例如,人们通常改变一张图像的尺寸,使其更大,然后使用转换后的卷积网络在其二维面的多个位置上计算类别概率,然后再取类别概率的平均值。
我们已经了解到,卷积网络通常仅由三种类型的层所构成:卷积层,池化层(通常假设是最大池化层,除非另有说明)以及全连接层。同时,我们将ReLu激活函数明确地表示为一个层,其功能是对每个元素执行一次非线性操作。
卷积网络最常见的形式是叠加一些卷积-ReLu层,随后是池化层,然后重复此模式,直到图像被整合到一个较小的尺寸。在某些情况下,常常使用全连接层进行过渡。最后的全连接层将会得到输出,例如类别的概率。换句话说,常见的卷积网络结构如下:
INPUT -> [[CONV -> RELU]*N -> POOL?]*M -> [FC -> RELU]*K -> FC
其中*
表示重复,POOL?
表示可选的池化层。此外,N>=0
(且通常N<=3
),M>=0
,K>=0
(且通常K<3
)。例如,这里有一些常见的卷积网络结构,它们都遵循这样的模式:
INPUT -> FC
,即执行一个线性分类器,其中N=M=K=0
。INPUT -> CONV -> RELU -> FC
INPUT -> [CONV -> RELU -> POOL]*2 -> FC -> RELU -> FC
这里我们可以看到,在每两个池化层之间都有一个卷积层。INPUT -> [CONV -> RELU -> CONV -> RELU -> POOL]*3 -> [FC -> RELU]*2 -> FC
这里我们看到,每个池化层之前都堆叠了两个卷积层。这对于较大且较深的网络结构来说通常是一个比较好的思路,因为在执行具有破坏性的池化操作之前,多个堆叠的卷积层可以发掘输入特征体中更复杂的特征。几个小的卷积滤波器堆叠在一起比一个大的卷积滤波器要好:假如你将三个3×3的卷积层堆叠在一起(当然其中还包括层与层之间的非线性激活函数)。在这种排列方式下,第一层卷积层的每个神经元对输入特征体有着3×3的接收域,而第二层卷积层的每个神经元对第一层卷积层也有着3×3的接收域,因此相当于对输入特征体有着5×5的接收域。同理,第三层卷积层的每个神经元对第二层卷积层也有着3×3的接收域,故相当于其对输入特征体有着7×7的接收域。假设我们不使用这三个接收域为3x3卷积层,而是只使用一个接收域为7×7的卷积层,那么其神经元同样将对输入特征体有着7×7的接收域,但是却有如下几个缺点:首先,三个包含非线性激活函数的卷积层堆叠在一起,比单一卷积层的结构更能提取出深层的更好的特征。其次,如果我们假设所有的特征值都有 C 个通道,那么一个接收域为7×7的卷积层将包含 C×(7×7×C)=49C2 个参数,然而三个接收域为3×3的卷积层将只包含 3×(C×(3×3×C))=27C2 个参数。直观地说,将具有小接收域的卷积层堆叠,与用一个具有较大接收域的卷积层相比而言,更能够强有力地表达出输入特征体中的特征,并且使用的参数也更少。唯一不足的是,在进行反向传播时,中间的卷积层可能会导致占用更多的内存。
最新进展:应当指出的是,逐层排列各个层的传统范例已经受到了挑战,挑战来自Google的Inception 架构,以及微软亚洲研究院当前的(最先进的)Residual 网络。这两者具有更复杂且不同的连接结构(详细信息请参阅案例研究部分)。
实践中使用ImageNet上最好用的模式:您可能在考虑该使用哪种架构时感到有些力不从心,不过值得庆幸的是,在90%或更多的应用程序中,您不必担心这些问题。我喜欢将这一点总结为“不要逞英雄”:与其自己费劲心思搞一套架构,不如看看目前在ImageNet上都有哪些比较好的架构,下载一个预训练模型并在你自己的数据集上对其进行微调。所以,你几乎不需要从头开始训练或是设计一个卷积网络。
到目前为止,我们还并没有提及卷积网络中每一层中常用的超参数。那么接下来,就让我们先对确定结构尺寸的通用规则进行阐述,然后再对其需要注意的地方进行探讨。
输入层应该能被2整除多次,其常用数字包括32(例如CIFAR-10),64,96(例如STL-10)或224(例如,常见的 ImageNet 卷积网络),384和512。
卷积层应该使用小尺寸的滤波器(例如3×3或者最多是5×5),使用步长 S=1 ,还有最为关键的一点就是使用零填充对输入特征体进行填充,以使卷积层的输出保持和输入特征体相等的二维面大小。亦即,当 F=3 时,使用零填充 F=1 ,则会保留输入特征体最初的尺寸。而当 F=3 时,则 P=2 。显而易见,对于任意一个 F ,若 P=(F−1)/2 ,则输出特征体将会保留输入特征体的尺寸。如果您不得不使用更大尺寸的滤波器(例如7×7左右),这种做法在第一层卷积层上将会十分常见。
池化层负责在输入的二维面上进行降采样,其最常见的设置是采用具有2×2接收域(即 F=2 )的最大池化,并且步长为2(即 S=2 )。值得注意的是,这会丢弃输入特征体中75%的特征值(由于降采样在宽度和高度上均降低2倍)。另一种不算是特别常见的设置是,使用3×3的接收域,步长为2。对于最大池化来说,接收域的尺寸大于3是极其罕见的,因为进行池化操作会造成很大的损耗和破坏,若接收域尺寸太大,将会降低网络的性能表现。
摆脱尺寸困扰:上面介绍的方案都十分令人满意,所有的卷积层都保留了输入的尺寸,池化层负责执行特征体二维面上的降采样操作。然而,如果步长大于1,或者不进行零填充时,我们就必须非常谨慎地对通过整个卷积网络结构的输入特征体进行跟踪,以确保所有的步长和滤波器能够“正常工作”,并且我们要尽可能地使卷积网络具有优美而对称的结构布局。
为什么在卷积层中使用1步长?在实践中,步长越小,其效果越好。此外,如之前所述,步长为1可以让二维面上的降采样全部由池化层负责,而卷积层只负责对输入特征体的深度进行变换。
为什么使用零填充?如之前所述,除了保证特征体通过卷积层之后二维面尺寸不变的好处之外,这样做实际上也起到了提升性能的作用。如果卷积层没有对输入进行零填充且只执行有效的卷积,那么在每个卷积之后特征体的尺寸都会减少一小部分,即图片边缘上的信息很快将会被“冲掉”。
基于内存限制所做出的妥协:在某些情况下(尤其是早期的卷积网络架构),使用上面介绍的规则可以快速建立内存容量。 例如,三个3x3 的卷积层,其中每个卷积层有64个滤波器,零填充为 P=1 ,用其对大小为224x224x3的图像进行过卷积将会创建三个大小为224x224x64的特征体。这相当于总共大约1000万个特征值,或72MB内存。 由于GPU经常受到内存瓶颈的限制,因此我们可能需要对此做出妥协。实际上,人们更喜欢在网络的第一个卷积层上做出妥协。例如,一个折中的方案可以是使用一个滤波器尺寸为7×7,步长为2的卷积层作为第一层。又例如,一个Alex网络使用尺寸为11×11的滤波器且以4作为步长。
在卷积网络领域有若干个有名有姓的架构,其中最常见的是:
VGGNet的细节:我们将VGGNet作为例子分解开来,对其进行详细的研究。整个VGG网络由卷积层和池化层组成,其中卷积层执行3×3,步长为1,零填充为1的卷积操作,池化层执行2×2,步长为2的最大池化操作(没有零填充)。我们可以写出过程中每一步特征体尺寸的变化,从而对特征体的尺寸和权重的数量进行跟踪:
INPUT: [224x224x3] memory: 224*224*3=150K weights: 0
CONV3-64: [224x224x64] memory: 224*224*64=3.2M weights: (3*3*3)*64 = 1,728
CONV3-64: [224x224x64] memory: 224*224*64=3.2M weights: (3*3*64)*64 = 36,864
POOL2: [112x112x64] memory: 112*112*64=800K weights: 0
CONV3-128: [112x112x128] memory: 112*112*128=1.6M weights: (3*3*64)*128 = 73,728
CONV3-128: [112x112x128] memory: 112*112*128=1.6M weights: (3*3*128)*128 = 147,456
POOL2: [56x56x128] memory: 56*56*128=400K weights: 0
CONV3-256: [56x56x256] memory: 56*56*256=800K weights: (3*3*128)*256 = 294,912
CONV3-256: [56x56x256] memory: 56*56*256=800K weights: (3*3*256)*256 = 589,824
CONV3-256: [56x56x256] memory: 56*56*256=800K weights: (3*3*256)*256 = 589,824
POOL2: [28x28x256] memory: 28*28*256=200K weights: 0
CONV3-512: [28x28x512] memory: 28*28*512=400K weights: (3*3*256)*512 = 1,179,648
CONV3-512: [28x28x512] memory: 28*28*512=400K weights: (3*3*512)*512 = 2,359,296
CONV3-512: [28x28x512] memory: 28*28*512=400K weights: (3*3*512)*512 = 2,359,296
POOL2: [14x14x512] memory: 14*14*512=100K weights: 0
CONV3-512: [14x14x512] memory: 14*14*512=100K weights: (3*3*512)*512 = 2,359,296
CONV3-512: [14x14x512] memory: 14*14*512=100K weights: (3*3*512)*512 = 2,359,296
CONV3-512: [14x14x512] memory: 14*14*512=100K weights: (3*3*512)*512 = 2,359,296
POOL2: [7x7x512] memory: 7*7*512=25K weights: 0
FC: [1x1x4096] memory: 4096 weights: 7*7*512*4096 = 102,760,448
FC: [1x1x4096] memory: 4096 weights: 4096*4096 = 16,777,216
FC: [1x1x1000] memory: 1000 weights: 4096*1000 = 4,096,000
TOTAL memory: 24M * 4 bytes ~= 93MB / image (only forward! ~*2 for bwd)
TOTAL params: 138M parameters
注意,与常见的卷积网络一样,大部分的内存(以及计算时间)都被前面的卷积层所消耗,且绝大多数参数都位于后面的全连接层上。在本例中,第一个全连接层包含的权重数量为100M,而权重的总量也不过是140M。
构建卷积网络结构时需要考虑的最大瓶颈就是内存瓶颈。现今的许多GPU的内存都为3/4/6GB,而最好的GPU也只有12GB的内存。一般有三种内存占用的来源:
一旦对于所有这些数值的数量有了一个粗略估计(包含特征值,梯度和各种杂项),其数量就应转化为以GB为计量单位。把这个值乘以4,得到原始的字节数(因为每个浮点数占用4个字节,如果是双精度浮点数那就是占用8个字节),然后多次除以1024分别得到以KB,MB,最后是GB为计量单位的内存占用量。如果你的网络构造得不够好,一个常用的方法是降低批尺寸(batch size),因为绝大多数的内存都是被特征值消耗掉的。
和实践相关的拓展资源: