CS231n第四节:反向传播 传送门 本系列文章基于CS231n课程,记录自己的学习过程,所用视频资料为 2017年版CS231n,阅读材料为CS231n官网2022年春季课程相关材料 本文将介绍卷积神经网络,它和普通的神经网络十分相似:他们都由神经元组成,这些神经元都含有可学习的权重和偏置。每个神经元接受输入然后进行点积运算,并可选地进行非线性变换。整个卷积神经网络同样可以看成是一个可微分的评估函数,通过这个评估函数可以将原始图片像素转换成每个类别的得分。同样,卷积神经网络中还包含了一个损失函数(在最后一层),并且所有的优化方法和技巧都同样适用于卷积神经网络。 那么卷积神经网络和普通的神经网络有什么区别呢?卷积神经网络假设输入的数据都是图像,这就允许我们在神经网络的架构上加入一些特别的属性。这些特别的东西可以使得前向传播更高效地运行, 同时大幅减少网络中参数的个数。
1. 结构总览
常规神经网络:
对于普通的神经网络,首先收到输入数据,然后通过若干的隐藏层的转换得到输出。每个隐藏层是由一组神经元组成的,并且这些神经元与前一层进行全连接。在单层中的每个神经元都是完全独立的,不会与其他神经元共享任何连接。最后一个全连接层又称为输出层,在分类任务中,它代表了每个类别的得分。常规的神经网络不能很好地扩展到整个图像。在CIFAR-10数据集中,图片的大小只有32*32*3
,所以全连接的神经网络在第一个隐藏层中就需要 32*32*3=3072 个权重,这看起来还是可以接受的一个数据量,但是如果图片更大,常规的神经网络就不能很好地使用了。显然易见的是,全连接这样的形式带来参数量巨大的问题, 会导致性能的浪费和过拟合问题。
3维的神经元:
卷积神经网络充分利用输入是由图片组成的这一条件,用一种更为合理的方式构造网络结构。具体来说,不同于普通的神经网络,卷积神经网络每一层的神经元具有三个维度:长、宽、深度(这里的深度不是指网络的深度,而是神经元可表示的一个维度)。还是以CIFAR-10数据集为例,每张图片大小为 32*32*3
,也就意味着输入的体积为 32*32*3
,不同于常规神经网络将其展开成一个 3072 的向量,卷积神经网络中,保持输入图片的空间结构,以 32*32*3
的形式输入。并且在后面的介绍可以了解到,不同于常规神经网络的全连接,卷积神经网络中的神经元只会和前一层中的一个很小的区域进行连接。此外,最后的输出层是一个 1*1*10
的结构,也就是说在最后,我们会将整个图片缩减成一个表示类别得分的向量。
下图是常规神经网络和卷积神经网络处理的可视化流程:
卷积神经网络由层组成。每一层有一个很简单的API:将输入的3维数据通过一个可微函数(可能含有也可能不含有参数)转换成输出的3维数据。
2. 卷积神经网络的层
- 最简单的卷积神经网络的结构就是多个层的排列,用于将图片转换成输出值(分类问题中就是每个类别的得分)
- 有很多种不用的层,比如常见的卷积层、全连接层、激活层、池化层
- 每一层都接受一个3维的量然后通过一个可微函数转换成输出值
- 有些层有参数,有些没有,比如全连接层、卷积层有参数,而激活层和池化层没有
- 有些层有额外的超参数,而有些没有,比如全连接层、卷积层、池化层有,而激活层(RELU)没有
2.1 卷积层
卷积层是卷积神经网络中的核心组成,它使得网络的计算性能有了巨大的提升。
概述:
每个卷积层的参数是由一组可学习的过滤器(filter)组成的,每个过滤器在空间上是小的(长和宽小),但是在输入量的深度上进行了完整的延伸。比如说,在第一个卷积层中比较典型的过滤器大小为 5*5*3
,即在长宽方向上只占了5个像素,但是在深度上占据了输入的全部(输入为图像,所以深度表示通道,为3表示RGB3通道)。在前向传播的过程中,我们将过滤器在输入量上进行充分的滑动,并且计算过滤器和输入量上对应部分的点积。当我们完成过滤器在宽和高上的滑动后,我们得到一个关于输入量的2维激活映射,作为过滤器在输入量的空间上每个位置的处理结果。直觉上来说,神经网络会对过滤器进行学习,使得过滤器在第一层卷积层中看到某些视觉上的特征或者一些特定颜色的斑点时会发生激活,或者在最终的深层网络中能够对一些图案或者模板发生激活。每一个过滤器都会产生一个2维的激活映射,在某一层中可能有多个过滤器,将他们生成的激活映射堆叠起来组成深度上的维度,并作为输出传递到下一层中。
生物大脑视角:
每一个3维的输出也可以看成是大脑中神经元的输出,他们只会观察输入的某个很小的区域,并且与左边和右边的神经元共享参数(在卷积神经网络中,通过使用同一个过滤器在输入数据上移动来实现参数共享)。
局部连接:
当我们要处理例如图像这样的高维数据时,我们发现使用全连接的神经元是不切实际的,取而代之,我们让神经元只和输入的一个局部区域相连。这种在空间范围上的连通性是一个被称为神经元的感受野(receptive field)的超参数,它与过滤器的大小等价。在深度这一坐标轴上的连通性永远和输入的深度相同。需要特别重申的一点是,过滤器采用非对称的形式处理空间上的维度和深度上的维度:在空间上(宽和高)只与局部的2维空间进行连接,而在深度维度上贯穿输入数据的整个深度。比如说,输入的数据维度为 32*32*3
,如果感受野为 5*5
,那么在这一个卷积层上的神经元参数为5*5*3
,其第三个维度必须为3,因为输入的数据深度为3。
输出的空间尺度:
我们已经介绍了每个卷积层上的神经元如何连接,但是还没介绍每一层有多少个神经元,也没介绍他们是如何排列的。这里有三个超参数控制他们的排列,深度、步长、0填充,我们依次介绍:
- 深度表示的是该层输出的深度,这也是一个超参数,它在数值上等于该层过滤器的个数,每一个过滤器将会学习去识别输入数据中的一个特征。
- 我们还要确定步长,这指的是滑动过滤器时的步长,当步长为1时,我们每次将过滤器滑动一个像素,步长越大,那么输出的空间尺度就会越小。
- 0填充指的是在输入数据的空间尺度上向四周填充一圈0,这是一种控制输出数据空间尺度的方法。
我们可以使用公式 (W-F 2P)/S 1 来计算输出数据的长或高的大小。其中 W 表示输入数据的尺寸,F 表示感受野,即过滤器的大小,P 表示0填充的圈数, S 表示步长。需要说明的是,一般情况下输入数据和过滤器都是正方形,所以不区分长和高,如果不是正方形则需要分别计算。
权值共享:
权值共享用于控制卷积层中参数的数量,举例来说假设某个卷积层的输出为 55*55*96=290400 ,那么也就是说有2090400个神经元,每个神经元代表一个过滤器,这里的每个过滤器固定和输入数据的某个区域相连接。假设过滤器的大小为 11*11*3 = 363 ,再加上一个偏置项,那么也就是说这个卷积层的参数大小为290400 * 364 = 105,705,600,这是十分巨大的。但是我们稍加分析就可以将参数进行大幅减少,因为对于识别同一个特征的过滤器,他们是可以共享同一组参数的,因为某一特征可能会出现在图像的任何位置上,所以可以使用同一个过滤器去识别图像上的每一个区域是否出现该特征。这样由于输出为 55*55*96
,所以说明该层识别了96个特征,那么也就只需要 96*11*11*3 = 34848 个权重,再加上偏置项96,这比原来大幅减少。
如下图就是一些识别出来的特征示例:
需要注意的是,在某些情况下权值共享是不适用的。权值共享是基于某些特征具有全局分布的特性,采用权值共享后,无论特征出现在图像的哪个位置都可以找到它。但是某些特征不具有全局分布的特性,其位置是相对固定的,比如说人脸识别,一般人的面部都集中在图像的中央,你可能期望不同的特征,比如眼睛特征或者头发特征可能(也应该)会在图片的某个特定的位置被学习。直观点来说,假设某个分类器如果识别到眼睛、鼻子、耳朵、嘴巴这几个特征就会认为这是一张人脸。那么如果一张图片中五官是打乱的,比如嘴巴在额头上,眼睛在下巴,这显然不是一张人脸,而权值共享会认为这是一张人脸,所以这种情况下不能使用权值共享,而应该采用局部连接层 Locally-Connected Layer,即每个位置的每个特征对应一组权值。
以矩阵乘法的方式实现:
指的注意的是,卷积操作实际就是在过滤器和输入的一个局部区域内做点积,利用这一事实,可以采用矩阵乘法来实现卷积层的前向传播:
- 首先将每个输入图片的局部区域展平成一个向量,这称为
im2col
操作。比如,如果输入的图片大小为 227*227*3 ,然后使用一个大小为 11*11*3 的过滤器以步长为4进行卷积操作。那么,每次卷积操作都会将输入图片的一个大小为 11*11*3 的区域与过滤器进行运算,所以输入图片的每个局部区域展开后是一个列向量,大小为 11*11*3=363。根据公式计算,(227-11)/4 1=55,说明输出为空间大小为 55*55 的数据,而每个数据表示一次卷积运算的结果,所以一共有 55*55=3025 次卷积,也就是说有 3025 个输入图片上的局部区域被展平成列向量,当然,这些局部区域之间会存在很多的重复之处。我们将展平的列向量拼凑在一起组成一个 363*3025 的矩阵,命名为X_col
- 卷积层的权值同样要被展平成行向量,比如说,如果有96个大小为 11*11*3 的过滤器,那么最终会得到一个 96*363 的参数矩阵,称为
W_row
- 那么这一层卷积层的输出就是对这两个矩阵进行矩阵乘法了,具体来说就是
np.dot(W_row, X_col)
,在我们的例子中,相乘后的结果为一个 96*3025 的矩阵。 - 将矩阵相乘的结果重新调整变成 55*55*96 的形式,就是最终的输出了。
使用这一方法最大的缺点就是会占用大量的内存,但是其好处在于使用非常高效的矩阵乘法,可以提升计算的速度。
反向传播:卷积层的反向传播同样也是一个卷积操作。
1 * 1 的卷积核:
这首先在Network in Network这篇论文中使用,对于从信号处理背景而来的人来说,这一开始可能有点困惑。因为信号一般都是2维的,所以 1 * 1 的卷积操作似乎没有什么道理。不过卷积神经网络并不和这个情况相同,因为我们需要知道的是,这里的卷积操作是在3维上进行的,即使在空间上是 1* 1的,在深度这一维度上一定不为1。
空洞卷积:
在这篇论文中paper by Fisher Yu and Vladlen Koltun使用了空洞卷积,目前为止我们谈论的卷积都是连续的。不过我们也可以在过滤器的每个单元之间增加一些空隙,称之为空洞(dilation)。举例来说,在某一维度上,一个大小为 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像素的间隔。在某些条件下,这是一种很有用的设定,因为他可以更好地整合输入数据的空间信息。距离来说,当你堆叠了两个感受野为 3*3 的卷积层时,你会发现他们的感受野等同于一个 5*5 的卷积层。如果你使用空洞卷积,你就能更高效地提升感受野了。
2.2 池化层
周期性地在卷积层之间插入一个池化层在卷积神经网络的结构中是十分常见的。其作用是渐进地减小空间上的尺寸,以此来减小参数量以及计算量,因此,池化层也有控制过拟合问题的作用。池化层将 MAX 运行独立地作用在每一个输入的深度切片上,并且改变空间上的大小。最常见的池化形式为使用一个 2*2 的过滤器,以步长为2来进行下采样,这可以将宽度和高度同时减小一半,丢弃75%的内容。在这个例子中,每一个 MAX 操作都会对4个数字进行取max操作。深度上的尺寸保持不变。一般来说,常用的池化层有两个,第一种是上述 2*2 的池化层,还有一种是 3*3 大小的过滤器,以步长为2进行池化。更大的过滤器可能会导致过多的破坏,一般不会采用。如下图,就是池化操作的示例。
一般化的池化:
除了最大池化使用max函数,池化层的每个单元还可以使用及其他函数来进行,比如平均池化核L2正则池化。平均池化在历史中被使用过,不过最近被证明了其效果比max池化差。
反向传播:
在介绍反向传播那一篇文章中提到过,max操作对梯度的效果就是一个路由器,即将后方传来的梯度路由分配给值较大者。因此,在池化层的前向传播中,一般会记录最大值的下标,这样可以很高效地在反向传播时路由梯度。
放弃池化:
很多人不喜欢池化操作,认为我们可以放弃它,比如 Striving for Simplicity: The All Convolutional Net 中就舍弃了池化层,只使用了卷积层。但为了缩小尺寸,他们建议使用更大步长的卷积层。舍弃池化层也被发现对训练一个较好的通用模型是重要的,比如说自动编码器VAEs,生成对抗网络GANs。在未来的卷积神经网络体系机构中似乎可能很少再包含池化层了。
2.3 归一化层
目前以及提出了很多类型的归一化层用于卷积神经网络的结构中。不过,这些层已经不再受到青睐了,因为在实际应用中他们的帮助非常有限。
2.4 全连接层
全连接层中的神经元与前一层的激活项进行全连接,这和常规的神经网络一样,这些操作可以使用矩阵乘法来实现。
2.5 将全连接层转换成卷积层
全连接层和卷积层的区别在于,卷积层只和输入的一个局部区域相连,并且很多神经元是共享参数的。但是,无论是全连接层还是卷积层,实际都是进行点积运算,所以函数形式上是相似的。因此,可以在全连接层和卷积层之间相互转换:
- 对于任一卷积层,都有一个全连接层和它实现一样的前向传播函数。而这个全连接层的参数矩阵会非常地大,并且有很多位置为0(对应卷积层的局部连接特性),同时有很多参数的值相等(对应权值共享)。
- 同样的,任一全连接层可以转换成一个卷积层。比如,一个全连接层的大小为 4096,并且输入的维度为 7*7*512,那么它可以等价于一个卷积层,卷积层中每个过滤器的大小为 7*7*512 ,步长为 1 ,填充为 0 ,过滤器个数为 4096,那么输出就是 1*1*4096,在深度这一位维度上等于全连接层的输出。
这两种转换中,从全连接层向卷积层的转换在实际应用中是很有帮助的。假如有一个卷积神经网络接收 224*224*3 大小的图片作为输入,并且使用一系列的卷积层和池化层最终产生一个 7*7*512 的输出(实际上这就是AlexNet所进行的事情)。在AlexNet中,最后使用了两个 4096 大小的全连接层,并在最后一层用了一个 1000 的全连接层用于类别评分的输出。我们可以将这三个全连接层转换成卷积层:
- 使用一个过滤器大小为 7*7 的卷积层替换第一个全连接层,并使得输出为 1*1*4096
- 使用一个过滤器大小为 1*1 的卷积层替换第二个全连接层,并使得输出保持为 1*1*4096
- 使用一个过滤器大小为 1*1 的卷积层替换最后一个全连接层,并使得输出变为 1*1*1000
注意,深度的改变可以通过控制卷积层中过滤器的个数来实现。通过这一转变,我们可以使得原始的卷积神经网络(带全连接的)变成一个可以在更大的图像上灵活“滑动”的网络,这种没有全连接层的卷积神经网络又称为全卷积神经网络FCN。
原始的卷积神经网络受制于全连接层所以输入图像大小必须固定,因为全连接层的参数矩阵与前一层输出的大小相关,如果输入图像尺寸改变,那么前一层输出的尺寸也会变化,如果这是一个训练好的模型,那么参数矩阵大小就是固定无法改变的,这时候前向传播就会出错。而将全连接层替换成卷积层,就不会出现这种问题,因为卷积层的超参数与前一层无关。举例说明,如果输入的图像为 224*224 ,经过若干卷积层和池化层后变成 7*7*512 ,也就是说空间尺度上被压缩了32倍,那么此时如果输入图片大小变为 384*384 ,那么输出的特征图像同样也是压缩32倍,变成 12*12*512。这时候如果接着是全连接层,那么需要展平成向量,然后进行全连接,而由于特征图像大小发生了改变,原全连接层的参数矩阵不匹配了。而如果使用前面提到的三个卷积层(第一个卷积层过滤器大小为 7*7),那么能够继续前向传播,最终得到 6*6*1000 的输出,(12-7)/1 1 = 6),可以发现与原输出 1*1*1000 不同,这里的输出为 6*6*1000 的一个图像,并且输入和输出具有相对应的空间结构。在这种情况下,我们可以将FCN的输出看作是一张热度图,用热度来指示待检测的目标的位置和覆盖的区域。在目标所处的区域内显示较高的热度,而在背景区域显示较低的热度,这也可以看成是对图像上的每一个像素点都进行了分类,这个点是否位于待检测的目标上。
最后在某些场景下,全连接可以提升效率,对于传统的 CNN(即包含全连接层),一个确定的网络其输入图像的大小是固定的,比如224*224,那么对于更大的图像,一般的操作是裁剪出很多 224×224 的小图像分别送入 CNN 网络中,然后取平均,而使用卷积层替代后,就可以同时计算。
3. 卷积神经网络架构
我们已经介绍了卷积层、池化层、全连接层,这里我们将RELU操作也当做一个层,用于对元素进行非线性变换。这一节,我们将介绍如何将这些层堆叠起来形成一个卷积神经网络架构。
3.1 层的组合
最常见的卷积神经网络架构的形式是一些卷积-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 的卷积层后,通过计算可以发现,其等价于一个 7*7 的卷积层的感受野,但我们更倾向于堆叠3个小的卷积层,其原因主要有两点。首先,每个卷积层后都会进行一次非线性变换,所以使用多个卷积层使得表达能力会更好。其次,通过简单的计算可以发现,使用3个 3*3 的卷积层,虽然和使用1个 7*7 的卷积层感受野相同,但是参数量跟小。也就是说,使用小的卷积层在获得对输入数据更好的表达能力的同时还减少了参数量。在实际应用中,使用小卷积层的唯一的缺点在于可能需要更多的内存来存储中间的卷积层结果,以此来保证反向传播可以进行(如果需要的话)。
当前进展:
值得注意的是,目前出现了很多对传统结构的改变,比如Google的Inception结构和微软亚洲研究院的ResNet,这些结构都更加复杂并且使用了不同的连接结构。
3.2 层内超参数的设置
到目前为止,我们还遗漏了每层中超参数大小的设置,我们首先给出一般的规则,然后对其进行探讨:
输入层: 应该可以被2多次整除,即输入的图片大小最好是2的幂或能多次被2整除,比如常用的包括 32(CIFAR-10)、64、96(STL-10)、224(ImageNet)、384、512。
卷积层: 应该使用小的过滤器(比如 3*3 或者 5*5 ),使用步长 S = 1,并且最关键的是使用0填充来避免对输入图像空间维度的改变。也就是说,当过滤器大小 F = 3 时,填充 P = 1 ;F = 5,P = 2。对于一般的 F ,有公式 P = (F-1)/2,这样就不会改变输入的空间尺度。如果你必须使用大的过滤器,这一般出现在第一个卷积层中。
池化层:最常见的是最的池化,使用 2*2 的感受野,步长为 2 ,这样可以舍弃输入数据中的75%。另外一种稍微不常见一点的是使用 3*3 的感受野,步长为 2。一般很少在池化层中使用大于3的感受野,因为这样会造成过多的信息丢失和破坏,会导致模型有较差的表现。
尺度缩小问题: 前面介绍卷积层不改变输入的空间尺度,只在池化层中改变尺度,这是一种很理想的状态。如果在卷积层中使用大于1的步长,或者不使用填充,这样就会导致输入和输出的尺度不同,这时就需要认真计算每个卷积层的输入和输出大小,使得每层之间可以很好地连接起来。
为什么在卷积层中使用1步长? 在实际应用中,小的步长表现更好。此外,前面提到了,使用步长为1可以使得卷积层不改变输入在空间上的尺度,只改变深度,而只在池化层中改变空间尺度。
为什么使用填充? 除了前面提到的保持空间尺度不变外,使用填充实际上还能提升网络的表现。因为如果不使用填充,那么每次卷积后图像的空间尺度都会变小,那么每次图像边缘的信息都会被“洗掉”。
向内存妥协: 在某些情况下,网络在搭建时整体所需的内存会大幅增加。比如说,使用 3*3 的卷积层来处理 224*224*3 的图像,该层有 64 个过滤器,并且填充1像素,得到输出为 224*224*64,,这样就会产生了 72 MB左右的内存(每张图片,包括输出和梯度的存储)。由于GPU经常有内存瓶颈,所以进行妥协是很有必要的。在实践中,人们通常只在第一个卷积层中进行妥协。比如,在第一个卷积层中使用 7*7 的过滤器,步长为2(ZFnet),或者使用 11*11 的过滤器,步长为4(AlexNet)。
3.3 计算上的要点
在构建卷积神经网络时最大的瓶颈就是内存瓶颈,很多现代的GPU只有3/4/6GB的内存,最好的也就12GB(现在不止了),主要有3个使用内存的地方:
- 来自中间变量的开销:每一层中的输出结果以及梯度,为浮点数。这些中间结果需要被记录是因为需要进行反向传播,不过如果在测试的时候(只进行前向传播),可以节约这部分的开销,每次只需要记录下当前层的值,并且舍弃掉前面的数值。
- 来自参数的开销:各种网络的参数,比如反向传播时记录的梯度值,以及如果使用momentum, Adagrad 或者 RMSProp作为优化器时每一步产生的中间值。
- 其他开销:比如图片批度参数等各种繁杂琐碎的内存开销。
对这些进行计算得到以GB为单位的结果,其中每个单精度浮点数为4字节,双精度为8字节。如果发现自己的网络不能适应内存大小,一般通过减小批度来适应,因为大部分的内存开销都用于存储提取的特征。