CS231n 卷积神经网络: 架构, 卷积/池化层(上)
卷积神经网络: 架构, 卷积/池化层(上)
卷积神经网络(CNNs/ConvNets)
卷积神经网络与普通的神经网络非常类似:它们都是由神经元组成并拥有可以训练的权值和偏差。整个网络仍然表示一个可导的评分函数:从原始的图片像素开始到各类型的得分结束。在最后的输出层它们也都有损失函数(比如SVM或Softmax),并且普通神经网络上的技巧同样能在CNNs中应用。
那么什么改变了呢?CNNs假设输入都是图片,这样允许我们在架构上能对特定的属性编码,这些变化能够实现和大量减少网络中的参数使前向函数更有效率。
1.架构概览
回忆之前课程所讲的常规神经网络,网络接收一个向量输入,然后通过多个隐层对其进行变换。每个隐层由一组神经元组成,其中的每个神经元都与前一层的所有神经元进行全连接,它们的功能都是独立的且不共享任何连接。最后一个被全连接的层叫做输出层,在分类应用中它表现为各类型得分。
常规的神经网络不能适应完全的图片数据。在CIFAR-10中,图片的大小只有32x32x3(32像素宽,32像素高,3个颜色通道),所以常规神经网络中在首个隐层中的一个全连接神经元应该有32323=3072个权值。这个数量看上去可以管理,但是很明显全连接结构不能适应更大的图片。例如一个更大分辨率的图片(200x200x3)会使神经元拥有2002003=120000个权值。进一步说,我们显然需要一些这样的神经元,所以参数数量会快速的上升。很明显这种情况下全连接是没用的,而且巨量的参数会快速导致过拟合。
三维神经元排列。CNNs改变了输入是由图片组成的事实并使用了一种更明智的架构。CNNs层中的神经元按3个维度排列而成:宽度,高度,深度。(注意这里的深度不是网络层数)。我们很快会看到在一个CNNs层中的神经元只连接上一层的一小块区域而不是全连接。进一步说,分类CIFAR-10的输出层的纬度为1x1x10,因为在CNNs架构的最后我们将完全的图片缩减成了一个各类型得分的向量。
image普通的三层神经网络。
image一个CNNs将它的神经元排列成三维结构,每一层将3D数据块转换为3D输出块。在这幅图中,宽和高是图片的分辨率而深度是图片的颜色通道数(红绿蓝)
一个CNNs由多个层组成,每个层拥有一个简单的API:将输入3D块用可导的函数转换为输出3D块,函数中可能有参数也可能没有。
2.CNNs中的各种层
如上锁描述,一个简单的CNNs是由一连串层组成,每个层通过一个可导函数将一个块转换为另一个块。我们用三个主要类型的层来构建CNNs:卷积层(Convolutional Layer),池化层(Pooling Layer)和 全连接层(Fully-Connected Layer)。我们将会堆叠这些层来形成一个完整的CNNs架构。
样例架构概览。我们将会在下面涉及更多细节,但是一个简单的分类CIFAR-10的CNNs应该有这种架构[输入层-卷积层-RELU层-池化层-全连接层],具体如下:
- 输入层(INPUT)[32x32x3]将会持有原始的图片像素(32像素宽,32像素高,3个颜色通道RGB)
- 卷积层(CONV)将会计算输入中的局部区域的输出,每一个层计算它的权值和它连接的局部区域的点积。结果可能是一个大小为[32x32x12]的块,如果我们选择用12个滤波器。
- ReLU层(RELU)将应用激活函数处理输入,比如max(0,x),输出大小不变为[32x32x12]。
- 池化层(POOL)将对空间维度(宽和高)做下采样操作,结果大小可能是[16x16x12]。
- 全连接层(FC)将会计算出各类型得分,结果的块大小为[1x1x10]。像它的名字一样,这一层的所有神经元都会和上一层的所有神经元相连。
在这种方式下,CNNs通过一层又一层操作将原始图片像素值转换为最终的类型得分。注意一些层是有参数的而另一些是没有参数的。具体来说,卷积层和全连接层的变换操作不仅是激活输入块,还包括其它参数(神经元的权重和偏差),而ReLU层和池化层只进行固定的操作。卷积层和全连接层中的参数通过梯度下降进行训练使得CNNs算出的类型得分和训练集中图片的真实类型标签一致。
概括:
- 一个CNNs架构的最简单的例子是将图像块通过一连串层转换为输出块(比如类型得分向量)
- 其中有一些不同类型的层(例如CONV/FC/RELU/POOL是最常见的,大写表示层)
- 每一层接受一个3D块输入然后通过一个可导函数转换为一个3D块输出
- 每一层有或没有参数(例如CONV/FC有,RELU/POOL没有)
- 每一层有或没有附加的超参数(例如CONV/FC/POOL有,RELU没有)
这是一个CNNs架构的例子,最左边的初始块存储原始图片像素,最右边的输出块存储类型得分,它只显示了得分最高的5种类型。接下来我们将描述各个独立的层和各层中的细节。
2.1 卷积层(CONV)
卷积层是CNNs的核心构建模块,它承担了绝大多数计算任务。
不涉及大脑分析的简介 让我们首先不从分析大脑神经元的角度来讨论CONV,它的参数由一组可学习的滤波器组成。每一个滤波器在空间维度上很小(比输入块的空间维度小),但是延伸到输入块的全部深度。例如一个作用于第一层的大小为5x5x3的滤波器(5像素宽,5像素高,3个颜色通道)。在向前的方向上,我们用每个滤波器在输入块的空间维度上滑动(从左到右,从上到下)并计算滤波器和当前位置的点积。直观的,网络将会学习这些滤波器,它们会在“看到”(做点积)某些图片上的特征时激活,这些特征开始可能是一些局部特征,最后将会是一些整体特征。每一个滤波器会生成一个2维的激活图,我们在深度维度上堆叠这些激活图形成输出块。
从大脑的角度分析 如果你是一个大脑神经元分析的发烧友,3D输出块的每一个点可以解释为一个只看输入中一块小区域的神经元的输出,而空间上所有的神经元都共享参数。(因为这些结果都来自于一个相同的滤波器)我们现在讨论神经元连接,它们空间上的排列和它们共享参数的细节。
局部连接 当处理类似图片的高维度输入时,全连接是不可行的。取而代之的是只连接输入块的一个局部区域。这个连接的空间大小是一个叫做接受域(receptive field)的超参数(和滤波器大小相同),而连接的深度总是和输入块的深度相同。
image上图中,红色为一个大小为32x32x3的CIFAR-10图片的输入块,蓝色部分为一个卷积层的样例块。卷积层的每一个神经元只连接输入块空间上(宽和高)的一个局部区域,但是连接整个深度。换句话说,每个神经元就是一个滤波器和输入块上某一局部区域的点积结果。
image普通神经网络中的神经元结构没变:依旧是计算权值和输入的乘积,然后跟随一个非线性单元,只是它们的链接被约束在一个局部区域。
空间排列 我们已经解释过CONV层中神经元和输入块之间的链接,但是还没有讨论输出块中有多少个神经元以及它们如何排列。三个超参数控制输出块的大小:深度(depth),步长(stride)和0填充(zero-padding)。
- 首先,输出块的深度是一个超参数,它取决于我们想要用多少个滤波器,每一个滤波器学习去看输入中的不同东西。
- 其次,我们必须设定步长用于滑动滤波器。步长为多少每次就滑动多少个像素,所以步长越大输出块越小。
- 我们马上会看到,有时候为了方便我们需要在输入块周围填充0值,这个0填充的数量是一个超参数。0填充的好处是让我们能够控制输出块的大小。(最常见的是使输出块和输入块有相同的宽和高)
我们可以用一个函数计算输出块的空间大小,假设输入块大小为(W),CONV层接受域大小为(F),步长为(S),在边界上0填充的数量为(P),公式为(W - F + 2P)/S + 1。
image图中是一个空间维度(宽)上的排列,一个神经元的接受域F=3,输入块宽度为W=5,0填充数量为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],偏差为[0,0,0],它们的参数是共享的。
0填充的使用 在步长S=1时设置0填充P = (F - 1)/2可以使输出块和输入块在空间上大小一致。
步长的限制 设置步长一定要使(W - F + 2P)/S能够整除。
共享参数 CONV的输出块在一个深度中共享权值(滤波器),权值大小就是接受域大小,输出块的深度就是滤波器的数目。
image上图中为96个大小为[11x11x3]的滤波器,输出块同一深度上的所有神经元共享它们中的一个。
注意有时候参数共享的假设可能没有意义。一个实际的例子是在人脸识别中,人脸在图片中央,而眼部特征、鼻子特征等都在不同的区域。这种情况下每个滤波器没有必要在整个输入块上滑动,此时CONV层变为局部连接层(Locally-Connected Layer)。
Numpy例子 假设输入块X
大小为X.shape:(11,11,4)
,0填充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
W0
为第一个滤波器的权值,大小为W0.shape: (5,5,4)
,第二个滤波器的部分结果为:
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的第三维深度就有多大。
概要 概括一下CONV层的概念:
- 输入块大小W_1 \times H_1 \times D_1
- 需要4个超参数:
- 滤波器数量K
- 滤波器大小F
- 步长S
- 0填充数量P
- 输出块大小为W_2 \times H_2 \times D_2
- W_2 = (W_1 - F + 2P)/S + 1
- H_2 = (H_1 - F + 2P)/S + 1
- D_2 = K
- 每个滤波器有F \cdot F \cdot D_1个权值,CONV层总共有(F \cdot F \cdot D_1) \cdot K个权值和K个偏差。
- 输出块在第d层深度上的切片(大小为W_2 \times H_2)是第d个滤波器以S的步长和偏差d在输入块上做卷积的结果。
一般的超参数设置为F=3,S=1,P=1。
image上图是一个用两个滤波器做卷积的例子。
用矩阵乘法实现 注意卷积操作本质上是将滤波器和输入的局部区域做点积,通常的实现方式如下:
- 将输入的局部区域拉伸成一列,我们称为im2col操作。例如假设输入大小为[227x227x3]且将被大小为[11x11x3]的滤波器以步长4进行卷积。我们将输入中的每个将要做点积的[11x11x3]块拉伸为一个11113=363的列,迭代这一操作得到长宽都为(227-11)/4+1=55的输出块
X_row
,大小为[3025x363]。注意每个大小为363的列中的数据有重叠。 - 将每个大小为[11x11x3]的滤波器也拉伸成一列,假如有96个滤波器,那么得到输出
W_col
大小为[363x96]。 - 然后将
X_row
和W_col
做矩阵乘法np.dot(X_row, W_col)
,得到输出大小为[3025x96]。 - 最后将输出变形为[55x55x96]。
这种方法有个缺点是会使用很多内存,因为X_row
中会有很多重复元素。尽管如此,它带来的好处是矩阵乘法有很多十分高效的实现(比如 BLAS API)。 而且im2col的这种思想在接下来的池化层也会用到。
反向传播 卷积操作的反向传播也是一个卷积(但是滤波器在空间上翻转了),这个结论可以简单的从1维的情况推导出(这里不展开)。
1x1卷积 1x1卷积的概念来自于网络中的网络,在CNNs中因为滤波器会延伸到输入块的整个深度,所以1x1卷积是有意义的。
扩张卷积 最近的一项关于扩张卷积的研究在CONV层引入了一个超参数扩张数(dilation)。如下图所示扩张卷积就是在滤波器的数据点之间插入空白,这样能够提高滤波器的感受野。(和池化作用类似还不丢失信息)
image