首页 > 程序开发 > 软件开发 > 其他 >

卷积神经网络(CNNs / ConvNets) 二

2017-02-27

接着《卷积神经网络(CNNs ConvNets) 一》,我们继续对原网站进行翻译。我们现在开始讨论每一个层,以及它们的超参数和连接关系的细节。

接着《卷积神经网络(CNNs / ConvNets) 一》,我们继续对原网站进行翻译。我们现在开始讨论每一个层,以及它们的超参数和连接关系的细节。

2.1 卷积层Convolutional Layer

卷积层是卷积网络的核心部分,承担了大部分计算繁重的工作。

不涉及到大脑/神经元的简介。让我们首先讨论在没有大脑/神经元类比的情况下,CONV层计算了什么。CONV层的参数包括了一组可学习的滤波器。每一个滤波器在空间上(沿着宽度和高度)很小,但是深度与输入量的深度相同。例如,在CNN第一层上的一个典型的滤波器,可能拥有5*5*3的尺寸(5像素的宽度和高度,3是因为图片深度为3,即有3个颜色通道)。在正向通过期间,我们在输入量的宽和高上滑动(更准确的说,卷积)每个滤波器,并计算在任何位置处滤波器和输入之间的点积。当我们在输入量的宽和高上滑动滤波器的饿时候,我们将得到一个二维激活图,给出该滤波器在每个空间位置上的响应。直观的,网络将会学习滤波器,使它们能够在看到某些类型的视觉特征时激活,例如第一层某个方向上的边缘或某种颜色的斑点,或者最终在网络较高层上的整个蜂窝状或轮状的图案。现在,我们将在每一个CONV层上有一组滤波器(例如12个),并且它们每一个都可以产生一个独立的二维激活图。我们将把这些激活图沿着深度方向堆叠起来,然后产生输出量。

大脑/神经元视图。如果你是一个大脑/神经元类比的粉丝,3D输出量的每一个条目也可以被解释为一个神经元的输出,它只查看输入的一小块区域,并且与空间左侧和右侧所有的神经元共享参数(因为这些数字都是由应用相同的滤波器得到的)。我们现在讨论神经元连接的细节,他们在空间上的安排,以及他们参数共享的方案。

局部连接。当处理高维的输入,例如图片时,我们可以看到将神经元连接到前一个量中所有的神经元是不太可行的。所以我们会将神经元只连接到输入量的一个局部区域。这种连接的空间范围是一个被称为神经元接受场(receptive field)的超参数(等价的这也是滤波器尺寸)。连接沿着深度方向上的范围总是与输入量的深度相等。需要再次强调的是我们在对待空间维度(宽度和高度)和深度维度时的不对称性:连接在空间中是局部的(在宽度和高度方向上),但在深度方向上总是和输入量相等。

例子1:举例来说,假设输入量的尺寸是[32*32*3],(例如一个RGB的CIFAR-10图片)。如果接受场(或者说滤波器尺寸)是5*5,那么卷积层中的每一个神经元都将拥有对应于输入量中[5*5*3]区域的权重,也就是总共5*5*3=75权重(同时+1偏差参数)。注意连接在深度方向的范围必须是3,因为这是输入量的深度。

例子2:假设一个输入量的尺寸是[16*16*20]。然后使用一个示例的接受场,尺寸为3*3,卷积层中每一个神经元现在就拥有了与输入量之间总共3*3*20=180个连接。再次注意,连接在空间上是局部的,但在深度上是完整的的。

depthcol

neuron_model

左图:红色的是一个示例输入量(例如,一个32*32*3的CIFAR-10图片),右侧的是在卷积层中神经元的一个示例量。卷积层中每一个神经元都只连接到输入量的空间上的局部区域,但连接到完整的深度(例如,所有的颜色通道)。注意,在深度方向上有许多个神经元(在本例子中是5个),所有的都查看输入量中的同一区域——可以参考接下来关于深度列的讨论。

右图:神经网络一章中的神经元仍然没有改变:它们仍然通过非线性计算它们的权重和输入之间的点积,但是它们的连接被限制在空间的局部上。

空间安排。我们已经解释了在卷积层中每一个神经元与输入量之间的连接,但是我们仍然没有讨论在输出量中究竟有多少个神经元,以及他们是如何被安排的。三个超参数控制了输出量的尺寸:深度(depth),步幅(stride)和零填充(zero-padding)。我们接下来将对这些参数进行讨论:

首先,输出量的深度(depth)是一个超参数:它表征了我们想要使用的滤波器的个数,每一个都进行了学习以用来寻找输入中一些不同的东西。例如,如果第一个卷积层的输入是原始图像,那么在深度维度上不同的神经元可能会在多个方向边缘或颜色斑点存在的时候被激活。我们将参考一组神经元作为深度列(有的人也称之为纤维(fibre)),这一组神经元都查看输入的同一区域。

其次,我们必须明确我们滑动滤波器的步幅(stride)。当步幅是1的时候,我们一次移动滤波器一个像素。当步幅是2的时候(或者在少数情况下是3或者更多,尽管这在实际操作中很少见),我们移动滤波器的时候一次跳两个像素。这将产生在空间上更小的输出量。

可以看到,有时候将输入量的边界填充为零是很方便的。零填充(zero-padding)的尺寸也是一个超参数。零填充的好处是它允许我们来控制输出量的空间尺寸(更一般的来说,我们很快就可以看到,我们将会利用它来精确抱球输入量的空间尺寸,以使得输入和输出的宽度和高度是相同的)。

我们可以将输出量的尺寸视作输入量的尺寸(W),卷积层神经元的接受场的尺寸(F),应用的步幅(S),和在边界上用零来填充的数目(P)的函数来进行计算。你可以自己证明用来计算多少神经元“适合”的公式,(W-F+2P)/S+1。例如一个7*7的输入和一个3*3的滤波器,使用的步幅为1,零填充为0,我们将的到5*5的输出。使用的步幅为2,我们将得到3*3的输出。让我们再看一个更图形化的示例:

空间安排的示意图。在这个例子中只有一个空间维度(x轴),一个神经元的接受场的尺寸F=3,输入尺寸W=5,零填充P=1。左图:步幅S=1的情况,得到的输出尺寸是(5-3+2)/1+1=5。右图:步幅S=2的情况,得到的输出尺寸是(5-3+2)/2+1=3。注意步幅S=3的情况我们不会使用,因为它无法整齐地通过目标量。如果从公式上分析,(5-3+2)=4是无法为3整除的。

在本例中神经元的权重是[1,0,-1](最右边所展示的),并且他们的偏差是零。这些权重对所有的黄色神经元是共享的(参考下面共享的参数)。

零填充的使用。在上面的例子中的左边,注意到输入的维度是5并且输出的维度是相同的:也是5。这样的原因是我们的接收场是3同时我们使用了1的零填充。如果我们没有使用零填充,那么输出量在空间尺寸上将只有3,因为那就是“适合”通过原始输出的神经元数量。通常来说,当步幅S=1时,将零填充设置为P=(F-1)/2,保证了输入量和输出量在空间上尺寸是相同的。这样使用零填充是十分常见的,我们将会在讲到CNN结构的时候讨论完整的原因。

步幅的约束。再次注意空间安排的超参数是相互约束的。例如,当输入的尺寸W=10,没有零填充P=0,并且滤波器尺寸F=3,那么步幅S=2是不可能的,因为(W-F+2P)/S+1=(10-3+0)/2+1=4.5,并不是一个整数,说明神经元不能“适合”整齐并且对称地通过输入。因此,这种超参数的设置是无效的,并且一个CNN库可以通过找出一个例外或零填充来让它适合,或者剪裁输入来让它适合,或者别的什么方法。正如我们在CNN的结构一节中看到的,适当地调整CNN的尺寸以使得所有的维度“工作”是真正让人头痛的事,使用零填充和一些设计指南可以显著地减轻困难。

真实的例子。Krizhevsky et al.结构赢得了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]的区域。另外,在每个深度列中所有的96个神经元都连接到了输入量中同样的区域,但是当然使用了不同的权重。有趣的是,当你阅读实际的论文时,它宣称输入的图片是224*224的,这显然是错的因为(224-11)/4+1并不是一个整数。这在CNN的历史上困惑了很多人,并且很少有人知道究竟发生了什么。我个人的猜测是Alex使用了3像素的零填充,但是他没有在论文中提到这一点。

参数共享。在卷积层中人们使用参数共享方案来控制参数的数量。使用上面真实的例子,我们可以看到在第一个卷积层中有55*55*96=290,400个神经元,并且每个都有11*11*3=363权重和1偏差。综合起来,仅在第一个卷积层中就累计有290400*364=105,705,600个参数。显然这个数字太大了。

结果是我们可以通过一个理性的假设,非常戏剧性地缩减参数的数量:如果一个功能对于计算一些空间位置(x,y)有效,那么它也应该对计算一个不同的空间位置(x2,y2)有效。换句话说,将一个深度上的二维切片作为深度切片(例如,一个尺寸为[55*55*96]的量有96个深度切片,每一个的尺寸都是[55*55]),我们将会约束每个深度切片中的神经元使用相同的权重和偏差。使用这样的参数共享方案,在我们示例中的第一个卷积层将之后96组不同的权重(一个深度切片对应一个),总共96*11*11*3=34,848个权重,或者34,944个参数(+96偏差)。或者说,在每个深度切片中全部55*55个神经元现在将使用相同的参数。在实践中,在反向传播期间,量中的每一个神经元将计算其权重的梯度,但是这些梯度将会在每个深度切片上叠加,并每个切片仅仅更新一组权重。

注意如果在一个深度切片中所有的神经元都使用相同的权重向量,那么卷积层在每个深度切片上的前向通过会被计算为一个神经元的权重和输入量的卷积。这就是为什么我们通常都会将一组权重作为一个滤波器(或者核)来看待,是因为它们和输入卷积。

由Krizhevsky et al.学习的示例滤波器。这里展示的96个滤波器的尺寸都是[11*11*3],并且每一个都被同一个深度切片上的55*55个神经元所共享。注意参数共享的假设是有一定道理的:如果检测水平边缘在图像中的某个位置是重要的,那么因为图像的平移不变结构,直觉上讲某些其他位置也应该适用。因此没有必要在55*55个不同的位置都重复学习如何检测水平边缘。

注意有时候参数共享假设可能不适用。特别是当CNN的输入图像有一些特别的中心结构时,我们可以想见,在一侧应该学习的功能会与另一侧完全不同。一个实际的例子就是当输入是人脸的时候,人脸总是处于图像的中心。你可能会发现不同的眼睛和发色的特征在不同的空间位置应该被学习。在这种情况下通常我们会放松参数共享的方案,转而简单地称呼该层为局部连接层(Locally-Connected Layer)。

Numpy例子。为了使得上面的讨论更加具体,让我们解释相同的原理,但是用代码的方式并且使用一个特定的例子。假设输入量是一个numpy数组 X。那么:

在位置(x, y)上的一个深度列(或一个纤维)将会是激活(activations) X[x, y, :]。

在深度d上的一个深度切片,或者等效的一个激活图将会是激活(activations) X[:, :, d]。

卷积层示例。假设输入量 X 有形状 X.shape:(11, 11, 4)。再进一步假设我们不使用零填充,并且滤波器尺寸为5,步幅为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中,上面提到的操作 * 表示数组之间的元素乘法。同时这里的权重向量 W0 被假设为 W0.shape:(5,5,4),因为滤波器尺寸是5并且输入量深度是4。而对于一些更一般的情况,我们可以表示如下:

V[3,0,1] = np.sum(X[6:11, :5, :] * W1) + b1

V[0,1,1] = np.sum(X[:5, 2:7, :] * W1) + b1

V[2,3,1] = np.sum(X[4:9, 6:11, :] * W1) + b1

可以看到,这里我们再计算第二层激活图,同时也使用了一组新的权重和偏差。在上面的例子中,我们为了简洁去掉了卷积层为了填充输出数组 V 的剩余部分而进行的一些运算。同时,回想起来,这些激活图常常通过激活函数(例如ReLU)在元素方面跟随,但是这里没有展示出来。

总结。卷积层有以下几个要点:

接收一个尺寸为W1*H1*D1的量

需要滤波器数目K,滤波器空间范围F,步幅S,零填充数目P四个参数

产生一个尺寸为W2*H2*D2的量,其中W2=(W1-F+2P)/S+1,H2=(H1-F+2P)/S+1,D2=K

通过参数共享,每个滤波器会有F*F*D1个权重,那么总共就是(F*F*D1)*K个权重和K个偏差

在输出量中,第d个深度切片是由第d个滤波器和输入量进行卷积得到,使用的步幅为S,而偏移量是第d个偏差。

一个通常的参数设置是F=3,S=1,P=1。然而也存在一些设置这些参数的惯例和规则,详见下面的CNN结构一节。

卷积演示。下面是一个运转的卷积层的演示。因为3D量很难展示,所有的量(蓝色的是输入量,红色的是权重,绿色的是输出量)都用每个深度切片堆叠起来展示。因为原网站使用的是动态画面,这里没有办法展示,点击这里到原网站浏览。

使用矩阵乘法的实现。注意卷积操作基本上表现为滤波器和输入的局部区域之间进行点积。一个常用的卷积层的实现方式就利用了这个事实的优点,将前向通过卷积层看做一个大的矩阵乘法来进行运算:

输入图像中的局部区域在一个被称为im2col的操作中被展开进入列中。例如,如果输入时[227*227*3]并且它将会被11*11*3的滤波器以4为步幅进行卷积。那么我们将使用输入中[11*11*3]的像素块,并将每一个块展开进入尺寸为11*11*3=363的列向量。在输入中以4位步幅迭代进行这个过程,那么沿着宽度和高度方向总共有(227-11)/4+1=55个位置,从而得到一个尺寸为[363*3025]的输出矩阵 X_col,其中每一列都是完整的接收场,而总共有55*55个这样的列。注意因为接收场是相互重叠的,所以在多个不同的列中输入量中的数字可能是重复的。

CONV层的权重也以相似的方式展开到行中。例如,如果总共有96个尺寸为[11*11*3]的滤波器,那么会得到一个尺寸为[96*363]的矩阵 W_row。

这样一来卷积的结果就等效于一个巨大的矩阵乘法 np.dot(W_row, X_col),该乘法计算了每一个滤波器和每一个位置的接收场之间的点积。在我们的例子中,这个操作的输出尺寸会是[96*3025]。

最终的结果必须被变形为它需要的输出形状[55*55*96]。

这个方法有一个缺点,就是它需要使用大量的内存资源,因为输入中的一些值在 X_col 中被重复计算了多次。然而这样的好处就是矩阵乘法的有很多十分高效的实现(例如,人们通常使用的BLASAPI)。此外,同样的im2col原理也会在池化操作中再次被使用,我们接下来将会继续讨论。

反向传播(Backpropagation)。对于卷积操作(包括数据和权重),反向通过也是一个卷积(但是使用空间翻转的滤波器)。这在一维的简单例子中是十分容易推导的。

1*1卷积。有一些论文使用1*1卷积进行讨论,最先使用的是Network in Network。一些人起初看到1*1卷积的时候十分困惑,尤其是当他们拥有信号处理背景的时候。通常来说信号是二维的,所以1*1卷积是没有意义的(因为这只是一个点)。然而在CNN中并不是如此,因为要知道,我们处理的是一个三维量,所以滤波器总是拥有和输入量相同的深度。

扩张卷积(Dilated convolutions)。一个最新的进展(参考paper by Fisher Yu and Vladlen Koltun)引入了卷积层的一个新的超参数扩张(dilation)。目前为止我们仅仅讨论了连续的CONV滤波器。然而在每个单元之间留有空隙的滤波器也是可行的,我们称之为扩张(dilation)。举例来说,在一维上一个尺寸为3的滤波器 w 将会与输入 x 进行运算:w[0]*x[0] + w[1]*x[1] + w[2]*x[2]。这样的dilation是0。如果dilation是1那么滤波器将会计算w[0]*x[0] + w[1]*x[2] + w[2]*x[4]。换句话说在应用之间会有1的间隙。这在某些设置与0扩张滤波器结合使用是非常有用的,因为它允许你通过更少的层更加积极地合并空间信息。举例来说,如果你堆叠两个3*3 CONV层在顶层,那么你可以说第二层的神经元是输入的一个5*5的补丁的函数(我们会说这些神经元的高效接收场是5*5)。如果我们使用扩张卷积,那么这个高效接收场将会更快地成长。

接下来的《卷积神经网络(CNNs / ConvNets) 三》将继续对原网站进行翻译。

相关文章
最新文章
热点推荐