CS231n 卷积神经网络: 架构, 卷积/池化层(下)
卷积神经网络: 架构, 卷积/池化层(下)
2.CNNs中的各种层(接上文)
2.2 池化层
在CNNs架构中经常在连续的CONV层之间插入池化层,它的功能是逐渐降低空间大小来减少网络中的参数数量和计算量,并且也可以控制过拟合。池化层单独的在每个深度上进行操作,用MAX操作减小每个深度切片的空间大小。最常见的池化层用大小为2x2的滤波器以2为步长对每一个深度切片进行下采样,丢弃75%的输入数据。需要注意的是空间大小减小但深度不会改变。
概要 概括一下池化层的概念:
- 输入块大小W_1 \times H_1 \times D_1
- 需要4个超参数:
- 滤波器大小F
- 步长S
- 输出块大小为W_2 \times H_2 \times D_2
- W_2 = (W_1 - F)/S + 1
- H_2 = (H_1 - F)/S + 1
- D_2 = D_1
- 没有引入参数因为它执行固定操作。
- 注意池化层一般不进行0填充。
值得注意的是最大池化层在实践中只有两种常用的变种:一种是F=3,S=2的重叠池化层(overlapping pooling),还有一种更常见的是F=2,S=2的池化层。池化用更大的接受域对数据破坏性太大。
常规池化 除了最大池化,池化层也可以进行其他形式的处理,比如平均池化,甚至L2范数池化。平均池化以前经常用,不过最近不流行了,因为最大池化操作在实践中有更好的表现。
image上图表示了大小为2x2,步长为2的滤波器在一个深度切片上的池化操作。
image上图表示了通过池化操作后,数据的空间大小减小而深度不变。
反向传播 回忆一下BP章节中对max(x,y)的操作是只需要传导输入时最大值的所在位置的梯度值。所以只需要在池化的正向传递时记录最大值的索引(有时我们叫它switches)就能够在BP时高效的计算梯度。
去掉池化 有很多人不喜欢池化操作而且觉得没有它也行。比如Striving for Simplicity: The All Convolutional Net提议抛弃池化层而改用只有CONV的架构。他们建议在CONV层中用更大的步长来缩小数据的大小。摒弃池化层被发现对训练好的生成模型来说很重要,比如变分自动编码器(VAEs)或者生成对抗网络(GANs)。未来的CNNs架构或许很少有甚至没有池化层。
2.3 归一化层
许多种类的归一化层被提出并使用在CNNs构架中,为了实现生物大脑中观察到的抑制状态。然而它们在实践中的贡献非常小,相关论述请看cuda-convnet library API。
2.4 全连接层
FC层中的神经元与上一层中的所有神经元相连,计算方式和普通神经网络的相关部分一样。
2.5 FC层转化为CONV层
值得注意的是FC层和CONV层的唯一区别就是CONV层的神经元共享参数并且只连接输入的一个局部区域。然而两种层仍然都是计算点积以至于函数形式是一样的。所以,结果是FC层和CONV层可以相互转换:
- 对于任何一个CONV层都有一个对应的FC层实现同样的前向函数。它的权值矩阵是一个大多都是0的大矩阵,其中只有特定的区域有值(因为局部连接),还有很多个区域的权值相同(因为参数共享)。
- 相反的任何一个FC层都能转化为CONV层。例如一个K=4096的FC层面对一个7x7x512的输入块可以等价表示为一个F=7,P=0,S=1,K=4096的CONV层。换句话说,我们设置滤波器大小和输入块大小一样,那么输出块将会是1x1x4096。
FC->CONV 转换 对于这两种转换,FC层向CONV层的转换在实践中特别有用。想一下一个输入为大小为224x224x3的图片的CNNs,然后用一系列的CONV层和POOL层来将图片缩小为大小为7x7x512的输出块(后面将会看到的AlexNet架构,每次下采样输入缩减一半数据,224/2/2/2/2/2= 7)。然后AlexNet用两个大小为4096的FC层,最后用一个FC层用1000个神经元计算类型得分。我们可以把这三个FC层分别用上述方法转换为CONV层:
- 将第一个面对[7x7x512]输入块的FC层换为滤波器为F=7的CONV层并得到输出块[1x1x4096]。
- 将第二个FC层换为滤波器为F=1的CONV层,得到输出块[1x1x4096]。
- 将最后的FC层换为滤波器为F=1的CONV层,得到输出块[1x1x1000]。
每一个这种转换在实践中的操作是将FC层中的权值矩阵W变形成CONV层中的多个滤波器。这种转换的结果是可以让我们在一次前向传播中得到一张更大图片的多个空间位置的得分。
例如,如果224x224的图片产生的输出为[7x7x512],那么处理384x384的图片就会产生大小为[12x12x512]的输出(384/32=12)。接下经过来我们刚才从FC层变换而来的CONV层会最终产生大小为[6x6x1000]的输出,因为(12-7)/1+1=6。注意不同于224x224图片得到的为一组标量类型得分,384x384图片得到的是一组大小为6x6的类型得分。
用原始的CNNs(FC没有转换为CONV)以32为步长对384x384图片中的多个224x224区域进行评价的结果和用转换后的CNNs进行评价的结果一致。(这个地方原文比较难懂,其实可以看作是用224滤波器卷积384图片,输出大小为(384-224)/32 + 1 = 6,所以384图片的单个类型得分大小为6x6)
自然的,转换后的CNNs前向传播一次比原始CNNs在36个位置传播一次要高效,因为36次计算是共享参数的。这种技巧经常在实践用于提高性能,例如我们经常放大一张图片(其中很多数据是冗余的),然后用一个转换后的CNNs去评价在多个空间位置上的得分然后取平均值。
最后,当步长小于32像素时如果我们想要高效的处理图片该做什么?我们可以用多次前向传播解决这个问题。比如我们想要用16像素的步长可以这样做:将两次由转换后CNNs前向传播的输出合并,前一次使用原始图片,第二次使用在宽和高上都平移了16个像素的图片。
- 一个IPython Notebook的例子Net Surgery展示了这种变换的代码(用Caffe)。
3.CNNs架构
我们已经看到CNNs一般由三种类型的层构成:CONV,POOL(不特殊申明一般指最大池化)和FC。我们会显式的将RELU激活作为一层,它对所有元素进行非线性操作。在这一部分我们讨论如何堆叠这些层来构建整个CNNs。
3.1 层排列
最常见的CNNs形式是堆叠一些CONV-RELU层,在它们后面添加POOL层,然后不断重复这种模式直到图片被从空间上合并到一个小的尺度。在这之后,经常需要接上一些全连接层,最后一个全连接层保存像类型得分之类的输出。最常见的CNNs架构遵从下面的模式:
INPUT -> [[CONV -> RELU]*N -> POOL?]*M -> [FC -> RELU]*K -> FC
其中的*
代表重复,?
代表可选项。一般情况下:
N >= 0 && N <=3, M >= 0, K >= 0 && K < 3
下面是一些遵循上述模式的基本CNNs架构:
-
INPUT -> FC
,线性分类器。 -
INPUT -> [FC -> RELU]*K -> FC
,常规的K隐层神经网络。 INPUT -> CONV -> RELU - FC
INPUT -> [CONV -> RELU -> POOL]*2 -> FC -> RELU -> FC
INPUT -> [[CONV -> RELU]*2 -> POOL]*3 -> [FC -> RELU]*2 -> FC
使用一堆小滤波器的CONV层而不是一个大滤波器的CONV层。
假设你堆叠三个3x3的CONV层(当前每层之间有非线性单元),在这种排列下第一层CONV的每个神经元对输入有3x3的视野。第二层CONV的神经元对第一层CONV有3x3的视野,因此对输入有5x5的视野。类似的,一个第三层CONV的神经元对第二层CONV有3x3的视野,对输入有7x7的视野。
假设不用三个3x3的CONV层,而直接使用一个单独的7x7的CONV层,这样会有很多缺点。首先,这些神经元对输入做线性操作,而三个CONV层含有非线性单元操作使得它们更有表现力。第二,如果我们假设有C个通道,那么7x7的CONV层有C \times (7 \times 7 \times C) = 49 C^2个参数,而三个3x3的CONV层只有3 \times (C \times (3 \times 3 \times C) = 27 C^2个参数。这种方式也有一个缺点,在实践中需要更多的显存在存储BP时中间CONV层的结果。
非线性的层排列 应该注意的是传统的线性排列层的模式已经被挑战了,在Google的Inception构架中和最近的(最先进的)残差网络(Residual Network)中,都采用了与线性排列不同的且更复杂的层连接结构。
在实践用什么架构在ImageNet中效果最好 如果你已经对思考架构选择感到疲劳了,可能会对下面的结论感到高兴,其实90%甚至更多的应用不需要考虑这些问题。对这个问题的总结为“不要逞英雄”:当你在对问题采用什么架构摇摆不定时,应该看看当前ImageNet上表现最好的架构,下载预训练模型然后在你的数据上进行微调。你应该基本没有必要去从头开始训练CNNs或者从头设计CNNs。我也是从Deep Learning school了解这一点的。
3.2 层定制
到目前为止我们没有提及CNNs每类层中常用的超参数。我们首先将会论述一些定制层的常用规则,随后是一些关于规则的讨论:
- 输入层的图片数据应该是能够被2整除的。常见的大小包括32(CIFAR-10),64,96(STL-10),224(ImageNet),384,512。
- 卷积层应该使用小的滤波器(3x3或者最多5x5),步长S=1,重要的是设置0填充使输出和输入在空间上大小一致(P=(F-1)/2)。如果你非要用大滤波器,那么常用的只有在最开始的CONV层中用于查看图片。
- 池化层负责下采样输入的空间维度数据。最常见的最大池化设置是F=2的接受域和S=2的步长,它每次丢弃输入中75%的数据(宽和高都减少1/2)。另一种稍微少见一些的设置是F=3的接受域和S=2的步长。大于3的接受域会带来太多的数据损失并过于激进,一般表现很差。
减少定制层的麻烦。 上述方案是令人愉快的,因为CONV层保持了输入的空间大小,POOL层单独的负责在空间上下采样数据。如果我们在CONV层中用大于1的步长而且不用0填充,那就要特别小心CNNs中每层的输出,保证所有的步长和滤波器能够协同工作。
CONV为什么使用为1的步长? 小的步长在实践中效果更好,而且已经提到过步长为1可以使所有空间上下采样的工作都交给POOL层,CONV层只变换改变输入块的深度。
为什么使用0填充? 除了之前提到的可以在CONV层处理后保持空间大小之外,其实还可以提高性能。如果CONV层不进行0填充而只做有效卷积,那么在CONV后数据会减小,数据边界处的信息会很快被“洗掉”。
对显存限制的妥协。 在某种情况下,使用上述经验使显存消耗增长非常快。例如用64个3x3滤波器且0填充为1的CONV层处理224x224x3的图片,应该生成3D输出块大小为[224x224x64]。这大概有1000万个参数和300万个输出元素,换句话说要消耗大概99MB的显存。因为通常GPU的瓶颈来自显存,所以有必要进行妥协。在实践中,人们一般只在CNNs的第一个CONV层进行妥协,例如ZFNet中的7x7滤波器和步长2,AlexNet中的11x11滤波器和步长4。
3.3 案例学习
下面这些是在CNNs领域中著名的架构:
- LeNet 第一个CNNs的成功应用,由Yann LeCun在1990年开发。LeNet广为人知是它能识别邮编和数字等信息。
- AlexNet AlexNet是使CNNs在计算机视觉领域火起来的第一个成果,由Alex Krizhevsky,Ilya Sutskever和Geoff Hinton共同开发。AlexNet参加了2012年的ImageNet ILSVRC challenge并以巨大的优势夺冠(top5error为16%,第二名为26%)。这个网络拥有比LeNet小很多的架构,但是层次更深更大且堆叠多个CONV(这之前的CNNs在一个CONV层后就马上连接一个POOL层)。
- ZFNet ILSVRC 2013的冠军是Matthew Zeiler和Rob Fergus开发的CNNs,一般被称为ZFNet。这个网络对AlexNet进行了改进,调整了架构的超参数,加大了中间的CONV层的大小并减小第一层的步长和滤波器大小。
- GooLeNet ILSVRC 2014的冠军是来自Google的Szegedy团队。这个网络主要的贡献是开发了Inception模块使网络中的参数大幅减少(4M对比AlexNet中的60M)。另外用平均池化代替了CNNs顶部的FC层,进一步消灭了大量似乎不重要的参数。这里有一些GooLeNet的后续版本,最新的是Inception-v4。
- VGGNet ILSVRC 2014的亚军是Karen Simonyan和Andrew Zisserman开发的VGGNet。这个网络的主要贡献是展示了网络深度是获得好的性能的关键因素。它含有16个CONV/FC层,并从头到尾使用3x3的CONV和2x2的POOL,这个预训练模型已经集成到了Caffe中。VGGNet的缺点是用更大的开销去使用了更多的显存和参数(140M),其中大多数参数都在第一个FC层中(后来发现这些FC层都可以在不降低性能的情况下移除,极大的减少了参数)。
- ResNet 残差网络(Residual Network)由ILSVRC 2015的冠军何恺明团队开发。这个网络的特点是一些特别的跨层连接和对batch normalization的大量使用。这个架构也在网络的最后去掉了FC层。你也可以参考何恺明的演示(视频和PPT)和一些用Torch做的近期实验。ResNet是目前为止(2016)最先进的CNNs并且是实践CNNs的默认选择。最后,也可以从Kaiming He et al. Identity Mappings in Deep Residual Networks得到对这个架构的最新研究(2016年3月)。
VGGNet的细节 让我们分析VGGNet的更多细节作为案例教学。整个VGGNet由大小3x3、步长为1、0填充为1的CONV层和大小2x2、步长为2的POOL层组成。我们可以把每一步的数据大小写下来,并追踪数据大小和参数总数:
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
在CNNs常见的是大多数的显存开销(计算时间也一样)是使用在开始的几个CONV层中,大多数的参数都在最后几个FC层中。在上面的例子中,第一个FC层包含1亿个权值,整个网络权值大概总共1.4亿个。
3.4 计算开销的注意事项
当构建CNNs架构时最大的瓶颈是显存,很多现代GPU有3/4/6GB的显存限制,最好的GPU大概有12GB显存。有三个主要的显存开销需要跟踪:
- 来自中间块大小:CNNs中每个层激活数据的原始大小和它们的梯度大小(大小和数据一样)。通常大多数激活数据都在CNNs开始的几层中(比如第一个CONV层)。这些数据需要被保存因为它们在BP时要用到,但是一种在测试中的聪明实现能够大体上减少这个开销,方法是只存储当前的激活数据然后丢弃前面层中的激活数据。
- 来自参数数量:网络中参数的数量,它们在BP中的梯度。如果使用动量,Adagrad或RMSProp等方法更新参数通常还需要一个分步缓冲。所以存储参数的显存开销通常需要至少乘以3。
- 每一个CNNs的实现都不得不维护一些杂项的开销,比如分批的图片,也可能包括他们的增量版本等。
一旦你已经粗略的估计了显存开销(激活数据,参数,梯度和杂项),应该将开销大小转化为GB。转化方法是取得这个数值后将它乘以4获得开销的原始字节数量(因为浮点数占4字节,或者使用双精度乘以8),然后除以1024多次获得开销的KB数,MB数和GB数。如果你的网络开销超过硬件限制,一个常用的方法是减小batch的大小,因为大多数显存开销消耗在激活数据上。
4.额外的资源
关于CNNs实现的额外资源:
- Soumith benchmarks for CONV performance。
- ConvNetJS CIFAR-10 demo让你玩转CNNs并且实时在浏览器中显示结果。
- Caffe,一个流行的CNNs库。
- State of the art ResNets in Torch7。