视觉识别:CS231n卷积神经网络

原译文地址请猛戳这里

目录:

    • 架构概述
    • 卷积网络层
      • 卷积层
      • 池化层
      • 归一化层
      • 全连接层
      • 将全连接层转换为卷积层
    • 卷积网络架构
      • 层模式
      • 层大小模式
      • 案例研究LeNet AlexNet ZFNet GoogLeNet VGGNet
      • 计算考虑
    • 其他参考


卷积神经网络(CNNs / ConvNets)

卷积神经网络与上一章中的普通神经网络非常相似:它们由具有可学习的weights 和 biases的神经元组成。 每个神经元接收一些输入,进行一个点积,并选择性的后边跟随一个非线性函数。 整个网络仍然表现出一个单一可微的分数函数:从一端的原始图像像素到另一端的分数。 而且它们在最后一个(全连接)层上仍然具有损失函数(例如SVM / Softmax),并且我们为学习常规神经网络而开发的所有提示/技巧仍然适用。

那么有什么改变? ConvNet架构明确地假设输入是图像,这允许我们将某些属性编译到架构中。 这些使得早前的函数更有效地执行,并大大减少了网络中的参数数量。

架构概述

回想一下:普通的神经网络。正如我们在前一章中看到的那样,神经网络接收一个输入(一个单独的向量),并通过一系列隐藏的层来转换它。每个隐藏层由一组神经元组成,其中每个神经元全连接到前一层的所有神经元,而单层神经元完全独立运行,不共享任何连接。最后一个全连接的层称为“输出层”,在分类设置中,它表示等级分数。

规则的神经网络不能很好地扩展到完整的图像。在CIFAR-10中,图像只有32×32×3(32宽,32高,3色通道),所以在一个规则的神经网络的第一个隐藏层中的一个全连接的神经元将具有32×32×3 = 3072的weights。这个数量似乎仍然可以控制,但显然这种全连接的结构并不能扩大到更大的图像。例如,比较可观的尺寸的图像,例如200x200x3,会导致200 * 200 * 3 = 120,000weights的神经元。而且,我们几乎肯定会想要有几个这样的神经元,所以参数会快速加起来!显然,这种全连接是浪费的,大量的参数很快就会导致过度拟合。

3D体积的神经元。卷积神经网络充分利用了输入由图像组成的事实,并以更合理的方式约束了架构。特别是,与常规的神经网络不同的是,卷积层具有三维排列的神经元:width, height, depth。(请注意这里的词depth是指激活体积的第三个维度,而不是整个神经网络的depth,可以指网络中的总层数。)例如,在CIFAR- 10中输入图像是激活的输入体积,并且体积具有32×32×3(分别为width, height, depth)的维度。正如我们将要看到的那样,一层中的神经元只能连接到它之前层的一个小区域,而不是以全连接的方式连接到所有的神经元。此外,CIFAR-10的最终输出层的维度为1x1x10,因为在ConvNet架构的最后,我们将把整个图像缩减为沿depth维度排列的单个等级分数向量。这是可视化:


视觉识别:CS231n卷积神经网络_第1张图片
左:常规的三层神经网络。 右:一个ConvNet在三个维度(width, height, depth)上排列它的神经元,如在一个图层中可视化的那样。 ConvNet的每一层都将3D输入的体积转换为神经元激活的3D输出体积。 在这个例子中,红色输入层保留图像,所以它的width 和 height将是图像的尺寸,并且depth将是3(红色,绿色,蓝色通道)。


ConvNet由层组成。 每层都有一个简单的API:它通过可能有或没有参数的可微函数将输入的3D体积转换为输出的3D体积。

卷积网络层

正如我们上面所描述的,一个简单的ConvNet是一系列的层,ConvNet的每一层通过一个可微函数将一个激活函数体积转换成另一个激活函数体积。 我们使用三种主要类型的层来构建ConvNet架构:卷积层池化层全连接层(正如在常规神经网络中看到的那样)。 我们将堆叠这些层来形成一个完整的ConvNet 架构

示例架构:概述。 我们将在下面介绍更多的细节,但是一个简单的CIFAR-10类别ConvNet可以有架构[INPUT - CONV - RELU - POOL - FC]。 更详细地说:

  • INPUT [32x32x3]将保留图像的原始像素值,在这种情况下,图像的width为32,height为32,并具有三个颜色通道R,G,B。
  • CONV层将计算连接到输入中的局部区域的神经元的输出,在输入体积中每个计算它们的weights与它们连接的小区域之间的点积。如果我们决定使用12个filters,这可能导致体积如[32x32x12]。
  • RELU层将应用元素激活函数,如 max0x m a x ( 0 , x ) 阈值为零。这会使体积的大小不变([32x32x12])。
  • POOL层将沿着空间维度(width, height)执行下采样操作,从而产生诸如[16x16x12]的体积。
  • FC(即全连接)层将计算等级分数,导致大小为[1×1×10]的体积,其中每10个数字对应于一个等级分数,例如10个类别的CIFAR-10当中。和普通的神经网络一样,顾名思义,这个层中的每个神经元都将被连接到之前体积中的所有神经元。

通过这种方式,ConvNets将原始图像从原始像素值逐层转换为最终的类别分数。请注意,某些图层包含参数,其他图层则不包含。特别是,CONV / FC层执行的转换不仅是输入体积激活的函数,而且也是参数(神经元的weights 和 biases)的函数。另一方面,RELU / POOL层将实现一个固定的函数。 CONV / FC图层中的参数将用梯度下降训练,以便ConvNet计算的类别分数与每个图像的训练集中的标签一致。

综上所述:

  • ConvNet架构是最简单的情况下,通过一系列的层将输入图像的体积转换为输出体积(例如保持类别分数)
  • 有几种不同的图层(例如CONV /FC / RELU / POOL是目前最流行的)
  • 每个图层都接受一个3D体积的输入,并通过可微分函数将其转换为3D体积输出。
  • 每一层可能有也可能没有参数(例如,CONV / FC 有,RELU / POOL没有)
  • 每一层可能有也可能没有额外的超参数(例如,CONV / FC / POOL 有,RELU没有)

视觉识别:CS231n卷积神经网络_第2张图片
ConvNet架构的一个激活样例。 初始体积存储原始图像像素(左),最后的体积存储类别分数(右)。 处理路径上的每个激活体积显示为一列。 由于很难将3D体积可视化,因此我们将每个体积的切片放在一行中。 最后一个图层体积保存每类的分数,但是在这里我们只显示排序的前5个分数,并打印每个分数的标签。 完整的web-based demo显示在我们网站的标题中。 这里显示的架构是一个微小的VGG Net,我们将在后面讨论。


我们现在描述各个层和超参数及其连接的细节。

卷积层

Conv层是卷积网络的核心组成部分,完成大部分计算繁重的工作。

Overview and intuition without brain stuff。让我们首先讨论CONV层没有 brain/神经元类比计算什么。 CONV层的参数由一组可学习的filters组成。每个filter在空间上都很小(沿着width和height),但是贯穿输入体积的整个depth。例如,ConvNet的第一层上的典型filter可能具有5×5×3的大小(即5个像素width和height,和3因为图像具有depth 3,色彩通道)。在正向传播过程中,我们沿着输入体积的width和height滑动(更精确地说,卷积)每个filter,并计算filter的条目和任意位置的输入之间的点积。当我们在输入体积的width和height上滑动filter时,我们将生成一个二维activation map,给出每个空间位置filter的响应。直观地说,网络将学习filters,当他们看到某种类型的视觉特征例如某个方向的边缘或第一层上的某种颜色的污点,或最终整个网络的更高层上的整个蜂窝或轮状图案。现在,我们将在每个CONV层中有一整套filter(例如12个filters),并且它们中的每一个将产生单独的二维activation map。我们将沿depth维度堆叠这些activation maps并产生输出体积。

The brain view。如果你是brain/神经元类比的粉丝,3D输出体积中的每个条目也可以被解释为神经元的输出,该输出只查看输入中的一个小区域,并与所有神经元共享参数,在空间上是正确的(因为这些数字全部来自应用相同的filter)。我们现在讨论神经元连接的细节,它们在空间中的排列以及它们的参数共享方案。

Local Connectivity。当处理像图像这样的高维度输入时,正如我们上面看到的那样,将神经元连接到前一个体积中的所有神经元是不切实际的。相反,我们将每个神经元连接到输入体积的局部区域。这种连接的空间范围是一个超参数,称为神经元的感受野(相当于filter的大小)。沿depth轴的连通程度总是等于输入体积的depth。再次强调我们在如何处理空间维度(width和height)和depth维度时的不一致:连接在空间上(沿着width和height)是局部的,但depth总是与输入体积的全部depth一致。

示例1.例如,假设输入体积大小为[32x32x3](例如,一张CIFAR-10的RGB 图像)。如果感受野(或filter大小)是5x5,那么Conv层中的每个神经元将具有到输入体积[5x5x3]区域的weights,总共5 * 5 * 3 = 75个weights(和+1bias参数)。请注意,沿depth轴的连接范围必须为3,因为这是输入体积的depth。

示例2.假设输入体积大小为[16x16x20]。然后,使用3x3的示例感受野大小,Conv层中的每个神经元现在将具有总共3 * 3 * 20 = 180个连接到输入体积。注意,连通性在空间上是局部的(例如3×3),但是depth(20)与输入depth是一致的。


视觉识别:CS231n卷积神经网络_第3张图片
左图:红色的示例输入体积(例如,一个32x32x3 CIFAR-10图像)以及第一卷积层中的示例体积的神经元。 卷积层中的每个神经元只在空间上连接到输入体积中的局部区域,而不是连接到全部的depth(即所有颜色通道)。 请注意,沿着depth有多个神经元(在这个例子中是5个),所有神经元都在输入中查看相同的区域 - 请参阅下面文本中depth列的讨论。 右图:神经网络章节中的神经元保持不变:它们仍然计算它们的weights与输入之间的点积,紧跟着非线性函数,但是它们的连通性现在被限制在局部空间上。


Spatial arrangement。我们已经解释了Conv层中每个神经元与输入体积的连通性,但是我们还没有讨论输出体积中有多少个神经元或者它们是如何排列的。三个超参数控制输出体积的大小:depth, stridezero-padding。我们接下来讨论这些:

  1. 首先,输出体积的depth是一个超参数:它对应于我们想要使用的filter的数量,每个filter学习在输入中寻找不同的东西。例如,如果第一卷积层将原始图像作为输入,则沿着depth维度的不同神经元可以在存在各种定向边缘或颜色污点的情况下激活。我们将参考一组神经元,这些神经元都将输入的相同区域看作depth column(有些人更喜欢术语“fibre”)。
  2. 其次,我们必须指明我们滑动filter的stride。当stride为1时,我们一次将filters移动一个像素。当stride是2(或者不常见的是3或者更多,虽然这在实践中是少见的),那么当我们滑动它们时,filters一次跳跃2个像素。这将在空间上产生较小的输出体积。
  3. 正如我们将很快看到的,有时将输入体积填充到边界周围将会很方便。这个zero-padding 的大小是一个超参数。零填充的好处在于,它允许我们控制输出体积的空间大小(最常见的是,我们很快就会看到,我们将使用它来精确地保留输入体积的空间大小,以便输入和输出width和height是一样的)。

我们可以使用将输入体积大小 W ( W ) ,Conv层神经元的感受野的大小 F ( F ) ,它们所应用的stride S ( S ) 以及在边界上使用零填充的数量 P ( P ) 看做的函数计算输出体积的空间大小。你可以说服自己, WF+2P/S+1 ( W − F + 2 P ) / S + 1 给出了计算多少神经元“拟合”的正确公式。例如,对于一个7x7输入和一个3x3filter,stride 1和pad 0,我们将得到一个5x5的输出。stride 2,我们会得到一个3x3输出。让我们再看一个图形化的例子:


视觉识别:CS231n卷积神经网络_第4张图片
空间排列说明。 在这个例子中,只有一个空间维度(x轴),一个感受野大小为F = 3的神经元,输入大小为W = 5,并且存在零填充P = 1。 :神经元在S = 1的stride跨越输入,输出的大小为(5 - 3 + 2)/ 1 + 1 = 5。:神经元使用S = 2的stride,给出尺寸(5 - 3 + 2)/ 2 + 1 = 3的输出。请注意,strideS = 3不能使用,因为它不能很好地穿过体积。 就等式而言,这可以确定,因为(5 - 3 + 2)= 4不能被3整除。
神经元weights在这个例子中为[1,0,-1](在右边显示),其bias为零。 这些weights在所有黄色神经元上共享(参见下面的参数共享)。


Use of zero-padding。在上面左边的例子中,请注意输入维度是5,输出维度是相等的:也是5。这样做是因为我们的感受野是3,我们使用零填充1.如果没有使用零填充,那么输出体积的空间维数只有3,因为那是在原始输入中会有多少神经元“拟合”。一般情况下,当stride为 S=1 S = 1 时,设置零填充为 P=F1/2 P = ( F − 1 ) / 2 ,确保输入体积和输出体积在空间上具有相同的大小。以这种方式使用零填充是非常常见的,我们将在讨论更多关于ConvNet架构时讨论充分的原因。

Constraints on strides。再次注意,空间排列超参数具有相互约束。例如,当输入大小为 W=10 W = 10 时,使用无零填充 P=0 P = 0 ,并且filter大小为 F=3 F = 3 ,则不可能使用stride S=2 S = 2 ,因为 WF+2P/S+1=103+0/2+1=4.5 ( W − F + 2 P ) / S + 1 = ( 10 − 3 + 0 ) / 2 + 1 = 4.5 ,即不是一个整数,表明神经元在整个输入上不拟合整齐对称。因此,超参数的这种设置被认为是无效的,ConvNet库可以抛出一个异常,或者将剩下的部分零填充以使其拟合,或者crop输入以使其拟合,或者其他。正如我们将在ConvNet架构部分中看到的那样,适当调整ConvNets的大小以使所有维度“解决”可能是一个真正令人头痛的问题,将会使零填充和一些设计准则的使用显著减轻。

Real-world example。在2012年,Krizhevsky et al等的架构接受了大小为[227x227x3]的图像赢得了ImageNet挑战。在第一个卷积层上,它使用感受野大小 F=11 F = 11 的神经元,stride S=4 S = 4 ,无零填充 P=0 P = 0 。由于(227-11)/ 4 + 1 = 55,并且由于Conv层具有 K=96 K = 96 的depth,所以Conv层输出体积尺寸为[55×55×96]。体积中的每个55 * 55 * 96神经元连接到输入体积大小为[11x11x3]的区域。此外,每个depth列中的所有96个神经元连接到输入的相同的[11x11x3]区域,但是当然具有不同的weights。除此之外,如果你读到实际的论文,它主张输入图像是224×224,这肯定是不正确的,因为(224 - 11)/ 4 + 1很明显不是一个整数。这让很多人在ConvNets的历史上感到困惑,对发生的事情知之甚少。我自己最好的猜测是,Alex使用了他在本文中没有提到的3个额外像素的零填充。

Parameter Sharing。卷积层中使用参数共享方案来控制参数的数量。使用上面的实际例子,我们看到第一个Conv层有55 * 55 * 96 = 290,400个神经元,每个神经元有11 * 11 * 3 = 363个weights和1个bias。总之,在ConvNet的第一层单独添加了290400 * 364 = 105,705,600个参数。显然,这个数字非常高。

事实证明,通过作出一个合理的假设,我们可以大大减少参数的数量:如果一个特征有助于计算某个空间位置(x,y),那么它应该也有助于计算另一个位置(x2 ,y2)。换句话说,将一个二维depth slice表示为一个depth slice(例如,大小为[55x55x96]的体积具有96个depth slice,每个切片的大小为[55x55]),我们将限制每个depth slice中的神经元使用相同的weights和bias。使用这个参数共享方案,我们例子中的第一个Conv层现在将只有96个唯一weights集(每个depth slice一个weights集),总共96 * 11 * 11 * 3 = 34,848个唯一weights或34,944个参数(+96个biases)。或者,每个depth slice中的所有55 * 55个神经元现在将使用相同的参数。在反向传播的实践中,体积中的每个神经元将计算其weights的梯度,但这些梯度将叠加在每个depth slice上,并且仅更新每个切片的一组weights。

请注意,如果单个depth slice中的所有神经元都使用相同的weight向量,则可以在每个depth slice中将CONV层的正向传播计算为神经元weights与输入体积的卷积(此后名称:卷积层)。这就是为什么通常将weights集称为filter(或kernel),是因为和输入做了卷积。


视觉识别:CS231n卷积神经网络_第5张图片
Krizhevsky等人学习的示例filters 这里显示的96个filters中的每一个都是大小为[11x11x3],并且每一个都是在一个depth slice中由55 * 55个神经元共享。注意,参数共享假设是比较合理的:如果检测水平边缘在图像中的某个位置是重要的,那么由于图像的平移不变结构,它应该在其他位置也直观地有用。 因此,不需要重新学习来检测Conv层输出体积中每个55 * 55个不同位置的水平边缘。


请注意,有时参数共享假设可能没有意义。当ConvNet的输入图像具有特定的居中结构时,尤其如此,例如,我们应该期望在图像的一个方面学习完全不同的特征。一个实际的例子是当输入是在图像中居中的面部。您可能会期望,不同的眼睛特定或头发特定的函数可以(而且应该)在不同的空间位置学习。在这种情况下,通常放宽参数共享方案,而不是简单地将该层称为局部连接层

Numpy examples.。为了使上面的讨论更具体,让我们用代码和具体的例子表现同样的观点。假设输入体积是一个numpy数组 X X .然后:

  • 位置xy ( x , y ) 处的depth列(或fibre)将是激活函数 X[xy] X [ x , y , : ]

    • 一个depth slice,或者等同于depth d d 处的activation map将是激活函数X[d] X [ : , : , d ]
    • Conv Layer Example。假设输入体积 X X 具有shapeX.shape11,11,4 X . s h a p e : ( 11 , 11 , 4 ) 。进一步假设我们使用无零填充 P=0 ( P = 0 ) ,filter大小为 F=5 F = 5 ,stride为 S=2 S = 2 。因此,输出体积将具有空间大小(11-5)/ 2 + 1 = 4,给出width和height为4的体积。输出体积中的activation map(称为 V V )将如下所示(在这个例子中只计算了其中的一些元素):

      • V[0,0,0]=np.sum(X[:5,:5,:]W0)+b0 V [ 0 , 0 , 0 ] = n p . s u m ( X [ : 5 , : 5 , : ] ∗ W 0 ) + b 0

      • V[1,0,0]=np.sum(X[2:7,:5,:]W0)+b0 V [ 1 , 0 , 0 ] = n p . s u m ( X [ 2 : 7 , : 5 , : ] ∗ W 0 ) + b 0
      • V[2,0,0]=np.sum(X[4:9,:5,:]W0)+b0 V [ 2 , 0 , 0 ] = n p . s u m ( X [ 4 : 9 , : 5 , : ] ∗ W 0 ) + b 0
      • V[3,0,0]=np.sum(X[6:11,:5,:]W0)+b0 V [ 3 , 0 , 0 ] = n p . s u m ( X [ 6 : 11 , : 5 , : ] ∗ W 0 ) + b 0
      • 请记住,在numpy中,上面的操作 表示数组之间的元素相乘。 还要注意,weights向量W0 W 0 是该神经元的weights向量,而 b0 b 0 是bias。 在这里,假设 W0 W 0 的shape是 W0.shape5,5,4 W 0. s h a p e : ( 5 , 5 , 4 ) ,因为filter大小是5,输入体积的depth是4.注意,在每个点上,我们正在计算普通神经网络中的点积。同样,我们看到,我们使用相同的weights和bias(由于参数共享),以及沿着width的规模以2(即stride)为单位递增。 要在输出体积中构建第二个activation map,我们将会:

        • V[0,0,1]=np.sum(X[:5,:5,:]W1)+b1 V [ 0 , 0 , 1 ] = n p . s u m ( X [ : 5 , : 5 , : ] ∗ W 1 ) + b 1
        • V[1,0,1]=np.sum(X[2:7,:5,:]W1)+b1 V [ 1 , 0 , 1 ] = n p . s u m ( X [ 2 : 7 , : 5 , : ] ∗ W 1 ) + b 1
        • V[2,0,1]=np.sum(X[4:9,:5,:]W1)+b1 V [ 2 , 0 , 1 ] = n p . s u m ( X [ 4 : 9 , : 5 , : ] ∗ W 1 ) + b 1
        • V[3,0,1]=np.sum(X[6:11,:5,:]W1)+b1 V [ 3 , 0 , 1 ] = n p . s u m ( X [ 6 : 11 , : 5 , : ] ∗ W 1 ) + b 1
        • V[0,1,1]=np.sum(X[:5,2:7,:]W1)+b1 V [ 0 , 1 , 1 ] = n p . s u m ( X [ : 5 , 2 : 7 , : ] ∗ W 1 ) + b 1 (沿着 y 方向)
        • V[2,3,1]=np.sum(X[4:9,6:11,:]W1)+b1 V [ 2 , 3 , 1 ] = n p . s u m ( X [ 4 : 9 , 6 : 11 , : ] ∗ W 1 ) + b 1 (或者两个方向同时)

        在这里我们看到我们正在索引到 V V 中的第二个depth维度(在索引1处),因为我们正在计算第二个activation map,并且现在使用了一组不同的参数W1 ( W 1 ) 。在上面的例子中,为了简洁起见,省略了Conv层将执行的一些其他操作来填充输出数组 V V 的其他部分。此外,回想一下,这些activation maps经常通过诸如ReLU的激活函数跟随元素级别的操作,但这里没有显示。

        概要。总而言之,Conv层:

        • 接受体积为W1×H1×D1 W 1 × H 1 × D 1

        • 需要四个超参数:
          • filter数量 K K
          • 空间范围F F
          • stride S S
          • 零填充量P P .
        • 生成一个尺寸为 W2×H2×D2 W 2 × H 2 × D 2 的体积,其中:
          • W2=W1F+2P/S+1 W 2 = ( W 1 − F + 2 P ) / S + 1
          • H2=H1F+2P/S+1 H 2 = ( H 1 − F + 2 P ) / S + 1 (也就是说width和height是通过对称来平均计算的)
          • D2=K D 2 = K
        • 参数共享,它引入了每个filter的 FFD1 F ⋅ F ⋅ D 1 weights,总共为 FFD1K ( F ⋅ F ⋅ D 1 ) ⋅ K weights和 K K biases。
        • 在输出体积中,第d d 个depth slice(大小为 W2×H2 W 2 × H 2 )是对输入体积上的第 d d 个filter进行有效卷积的结果,其中stride为S S ,然后位移第 d d 个bias。

        超参数的常见设置是F=3S=1P=1 F = 3 , S = 1 , P = 1 。然而,激发这些超参数有一些常见的惯例和经验法则。请参阅下面的ConvNet architectures部分。

        Convolution Demo。以下是一个CONV层的运行演示。由于三维体积难以可视化,所有的体积(输入体积(蓝色),weight体积(红色),输出体积(绿色))可视化,每个depth slice堆叠成行。输入体积为 W1=5 W 1 = 5 H1=5 H 1 = 5 D1=3 D 1 = 3 ,CONV层参数为 K=2 K = 2 F=3 F = 3 S=2 S = 2 P=1 P = 1 。也就是说,我们有两个尺寸为3×3的filter,并且它们的stride为2.因此,输出体积尺寸的空间尺寸为 53+2/2+1=3 ( 5 − 3 + 2 ) / 2 + 1 = 3 。对输入体积应用 P=1 P = 1 的填充,使输入体积的外边界为零。下面的可视化对输出激活(绿色)进行迭代,并显示每个元素是通过将高亮显示的输入(蓝色)与filters(红色)进行元素相乘,然后对其进行求和,然后通过bias来抵消结果。


        视觉识别:CS231n卷积神经网络_第6张图片

        动图查看地址


        Implementation as Matrix Multiplication。请注意,卷积操作本质上是在filter和输入的局部区域之间执行点积。 CONV层的一个常见的实现模式是利用这个事实的优势并且将卷积层的正向通过制定为如下的一个大矩阵乘法:

        1. 输入图像中的局部区域在通常称为im2col的操作中被张开成列。例如,如果输入是[227x227x3],并且要在stride4中与11x11x3filter卷积,则我们将在输入中采用[11x11x3]个像素blocks,并将每个blocks张开到大小为11 * 11 * 3 = 363的列向量。在stride4的输入中迭代该过程沿着width和height给出(227-11)/ 4 + 1 = 55个位置,导致尺寸为[363×3025]的im2col的输出矩阵X_col,其中每列是一个伸出的感受野,总共有55 * 55 = 3025个。请注意,由于感受野重叠,输入体积中的每个数字可以复制到多个不同的列中。
        2. CONV层的weights同样地伸展成行。例如,如果有大小为[11x11x3]的96个filter,则将给出大小为[96×363]的矩阵 Wrow W r o w
        3. 卷积的结果现在相当于执行一个大的矩阵乘法 np.dotWrowXcol n p . d o t ( W r o w , X c o l ) ,它评估每个filter与每个感受野位置之间的点积。在我们的例子中,这个操作的输出是[96 x 3025],在每个位置输出每个filter的点积。
        4. 最终的结果必须重新调整到适当的输出维度[55x55x96]。

        这种方法的缺点是会使用大量内存,因为输入体积中的某些值在 Xcol X c o l 中被复制多次。但是,好处是有很多非常有效的Matrix Multiplication实现,我们可以利用(例如,常用的BLAS API)。而且,我们可以重复使用相同的im2col想法来执行pooling操作,接下来我们将讨论这个操作。

        Backpropagation。卷积操作的反向传播(对于data和weights)也是卷积(但带有空间反转的filters)。这很容易用一个一维的例子(现在没有扩展)在一维情况下推导出来。

        1x1 convolution。另外,一些论文使用1x1 convolutions,首先由Network in Network进行研究。有些人起初看到1x1 convolutions时感到困惑,特别是当他们来自信号处理背景时。通常情况下,信号是二维的,所以1x1 convolutions是没有意义的(这只是逐点缩放)。然而,在ConvNets中,情况并非如此,因为我们必须记住,我们操作的是三维体积,并且filters总是延伸到输入体积的全部depth。例如,如果输入是[32x32x3],则执行1x1 convolutions将有效地执行三维点积(因为输入depth是3个通道)。

        Dilated convolutions。最近的一个开发(例如参见paper by Fisher Yu and Vladlen Koltun)是引入另一个称为dilation的超参数到CONV层。到目前为止,我们只讨论了连续的CONV filter。但是,有可能在每个单元之间都有空间的filter,称为dilation。作为一个例子,在一个维度上,一个大小为3的filter w 将在输入为 x 上计算: w[0]x[0]+w[1]x[1]+w[2]x[2] w [ 0 ] ∗ x [ 0 ] + w [ 1 ] ∗ x [ 1 ] + w [ 2 ] ∗ x [ 2 ] 。这是dilation等于0。对于dilation等于1,filter将计算 w[0]x[0]+w[1]x[2]+w[2]x[4] w [ 0 ] ∗ x [ 0 ] + w [ 1 ] ∗ x [ 2 ] + w [ 2 ] ∗ x [ 4 ] 。换句话说,应用程序之间有1的差距。在某些设置中,与0-dilated的filter一起使用会非常有用,因为它允许您以更少的图层更加有效地pooling空间信息。例如,如果您将两个3x3 CONV层堆叠在一起,则可以确信第二层上的神经元是输入的5x5patch的函数(我们可以说这些神经元的有效感受野是5×5)。如果我们使用扩张的卷积,那么这个有效的感受野会变得更快。

        池化层

        周期性地在ConvNet架构中连续的Conv层之间插入一个Pooling层很常见。其功能是逐步减小表现空间的大小,以减少网络中的参数和计算量,从而也控制过度拟合。Pooling层在输入的每个depth slice上独立运行,并使用MAX操作在空间上调整其大小。最常见的形式是一个大小为2x2的filter的pooling层,在输入的每个depth slice上沿着width和height两次施加stride为2的下采样,丢弃75%的激活函数。在这种情况下,每个MAX操作最多需要4个以上的数字(在某个depth slice中只有很少的2×2区域)。depth维度保持不变。更一般地说,pooling层:

        • 接受体积为 W1×H1×D1 W 1 × H 1 × D 1
        • 需要两个超参数:
          • 其空间范围 F F
          • strideS S
        • 产生大小为 W2×H2×D2 W 2 × H 2 × D 2 的体积其中:
          • W2=W1F/S+1 W 2 = ( W 1 − F ) / S + 1
          • H2=H1F/S+1 H 2 = ( H 1 − F ) / S + 1
          • D2=D1 D 2 = D 1
        • 由于它计算输入的固定函数,因此引入零参数
        • 请注意,对Pooling图层使用零填充并不常见

        值得注意的是,在实践中发现的最大Pooling层只有两个常见的变化:一个pooling 层 F=3S=2 F = 3 , S = 2 (也称overlapping pooling),而 F=2S=2 F = 2 , S = 2 更普遍。具有较大感受野的Pooling大小太具破坏性。

        General pooling。除了最大限度的pooling以外,pooling单位还可以执行其他函数,如average pooling甚至L2-norm pooling。Average pooling历史上经常使用,但最近,与最大的、在实践中效果更好的pooling操作相比已经失宠。


        视觉识别:CS231n卷积神经网络_第7张图片
        Pooling层在输入体积的每个depth slice中独立地在空间上下采样该体积。 左:在此示例中,大小为[224x224x64]的输入体积与大小为2的filter pooled,stride为2进入大小为[112x112x64]的输出体积。 请注意,体积的depth保留。 右:最常见的下采样操作是最大的,产生max pooling,这里显示的stride是2。也就是说,每个最大值是4个数字(2×2小平方)。


        Backpropagation。回顾反向传播章节,max(x,y)运算的反向传播有一个简单的解释,只将梯度按指定路线发送到正向传播中具有最高值的输入。因此,在Pooling层的正向传播期间,通常跟踪最大激活函数的索引(有时也称为switches),使得在反向传播期间梯度路由是有效的。

        Getting rid of pooling。很多人不喜欢这个pooling操作,认为我们可以不用它。例如, Striving for Simplicity: The All Convolutional Net建议丢弃pooling层,转而使用仅包含重复CONV层的架构。为了减小表现的大小,他们建议在CONV层稍微增加stride。抛弃Pooling层也被认为在训练良好的生成模型(例如变分自动编码器(VAEs)或生成对抗网络(GANs))中很重要。未来的架构似乎很可能只有很少或没有pooling层。

        归一化层

        许多类型的归一化层已经被提出了用于ConvNet架构,有时意图实施在生物大脑中观察到的抑制方案。 然而,这些层次已经失宠,因为在实践中,他们的贡献如果有的话已被证明是最小的。对于各种类型的规范化,请参阅Alex Krizhevsky在cuda-convnet library API中的论述。

        全连接层

        正如在常规神经网络中所看到的那样,全连接层中的神经元与前一层中的所有激活函数全连接。 因此可以用一个矩阵乘法和一个bias offset来计算它们的激活函数。 有关更多信息,请参阅笔记的“神经网络”部分。

        将全连接层转换为卷积层

        值得注意的是,FC和CONV层之间的唯一区别在于,CONV层中的神经元仅连接到输入中的局部区域,并且CONV体积中的许多神经元共享参数。然而,这两种层的神经元都要计算点积,所以它们的函数形式是相同的。因此,在FC和CONV层之间进行转换是合理的:

        • 对于任何CONV层,都有一个实现相同向前计算的函数的FC层。weights矩阵可能是一个大的,除了某些大部分blocks的weights相等(由于参数共享)的blocks(由于局部连通性),大部分矩阵为零的矩阵。
        • 相反,任何FC层都可以转换为CONV层。例如,考虑一些尺寸为 7×7×512 7 × 7 × 512 的输入体积为 K=4096 K = 4096 的FC层可以等效地表示为一个 F=7P=0S=1K=4096 F = 7 , P = 0 , S = 1 , K = 4096 的CONV层。换句话说,我们将filter大小设置为输入体积的大小,因此输出将简单地为 1×1×4096 1 × 1 × 4096 ,因为只有单个的depth列“拟合”输入体积,结果与初始FC层相同。

        FC->CONV conversion。在这两种转换中,将FC层转换为CONV层的能力在实践中特别有用。考虑一个采用224x224x3图像的ConvNet架构,然后使用一系列CONV层和POOL层将图像缩小为7x7x512的激活函数体积(在AlexNet架构中,我们稍后会看到,这是通过使用5个Pooling层,每个Pooling层在空间上对输入进行2倍下采样,使得最终的空间尺寸为224/2/2/2/2/2 = 7)。从那里,一个AlexNet使用两个大小为4096的FC层,最后的FC层使用1000个神经元计算类分数。我们可以将这三个FC层中的每一个转换为CONV层,如上所述:

        • 将第一个体积看起来是[7x7x512]的FC层替换为使用filter大小为 F=7 F = 7 的CONV层,从而得到输出体积[1x1x4096]。
        • 将第二个FC层替换为使用filter大小为 F=1 F = 1 的CONV层,从而输出体积[1x1x4096]。
        • 同样的,用 F=1 F = 1 的CONV层替换最后一个FC层,给出最终输出[1x1x1000]。

        这些转换中的每一个实际上可以涉及将每个FC层中的weights矩阵 W W 操作(例如reshaping)为CONV层filters。事实证明,这种转换使我们能够在一个较大的图像中通过空间定位,在一个单独的正向通道中,非常有效地“滑动”原始的ConvNet。
        例如,如果224x224图像的体积大小为[7x7x512],即减少32,那么通过转换的架构传送大小为384x384的图像会得到大小为[12x12x512]的等效体积,因为[384/32] = 12。接下来我们刚刚从FC层转换而来的其次的3个CONV层现在将给出最终的体积大小[6x6x1000],因为(12-7)/ 1 + 1 = 6。注意,不是一个单个向量类别分数大小为[1x1x1000],我们现在可以在通过384x384的图片获得全部6x6的类别分数。

        通过在32像素的stride,crops为224x224的,大小为384x384的图像上独立评估原始ConvNet(使用FC层),可以获得与一次传递转换的ConvNet相同的结果。

        实际上,传递转换的ConvNet一次比在所有这36个位置迭代原始的ConvNet效率高得多,因为这36个评估共享计算。这个诀窍在实践中经常被用来获得更好的表现,例如通常调整图像的大小以使其更大,使用转换后的ConvNet评估许多空间位置的类别分数,然后平均类别分数。
        最后,如果我们想要在图像上有效地应用原始的ConvNet,但是stride小于32像素呢?我们可以用多个向前的传递来实现这一点。例如,如果我们想要使用一个16像素的stride,我们可以通过将转换后的ConvNet传递两次而得到的体积结合来实现:首先在原始图像上,然后在图像上,但是图像在空间上被沿着width和height移动了16个像素。

        • 一个Net Surgery上的IPython Notebook显示了在实践中如何用代码(使用Caffe)执行转换。

        卷积网络架构

        我们已经看到,卷积网络通常仅由三种类型的层构成:CONV,POOL(我们假定为Max pool除非另有说明)和FC(简称全连接)。 我们还将明确地将RELU激活函数写成一个应用了非线性元素的层。 在本节中,我们讨论通常这些是如何堆叠在一起形成整个ConvNets的。

        层模式

        ConvNet架构最常见的形式是将几个CONV-RELU层叠加在一起,然后使用POOL层,并重复这种模式,直到图像被空间地合并为一个小尺寸。在某些情况下,转换到全连接的层是很常见的。最后的全连接层保留输出,例如类别分数。换句话说,最常见的ConvNet架构遵循以下模式:

        INPUT>[[CONV>RELU]N>POOL]M>[FC>RELU]K>FC I N P U T − > [ [ C O N V − > R E L U ] ∗ N − > P O O L ? ] ∗ M − > [ F C − > R E L U ] ∗ K − > F C

        表示重复,POOL P O O L ? 表示一个可选的Pooling层。此外, N>=0 N >= 0 (通常 N<=3 N <= 3 ), M>=0K>=0 M >= 0 , K >= 0 (通常 K<3 K < 3 )。例如,下面是一些常见的ConvNet架构,您可能会看到遵循以下模式:

        • INPUT>FC I N P U T − > F C ,实现一个线性分类器。这里 N=M=K=0 N = M = K = 0
        • INPUT>CONV>RELU>FC I N P U T − > C O N V − > R E L U − > F C
        • INPUT>[CONV>RELU>POOL]2>FC>RELU>FC I N P U T − > [ C O N V − > R E L U − > P O O L ] ∗ 2 − > F C − > R E L U − > F C
          这里我们看到每个POOL层之间都有一个CONV层。
        • INPUT>[CONV>RELU>CONV>RELU>POOL]3>[FC>RELU]2>FC I N P U T − > [ C O N V − > R E L U − > C O N V − > R E L U − > P O O L ] ∗ 3 − > [ F C − > R E L U ] ∗ 2 − > F C
          这里我们看到两个CONV层堆叠在每个POOL层之前。对于更大更深的网络来说,这通常是一个好方法,因为在有害的pooling操作之前,多个堆叠的CONV层可以开发更复杂的输入体积的特征。

        首选一个小的filter CONV堆叠到一个大的感受野CONV层上。假设你将三个3x3的CONV层层叠在一起(当然是非线性的)。在这种排列中,第一个CONV层上的每个神经元都具有输入体积3×3的视图。第二个CONV层上的神经元在第一个CONV层上具有3×3的视图,并且因此扩展了输入体积5×5的视图。类似地,第三个CONV层上的神经元在第二个CONV层上具有3×3的视图,因此具有输入体积的7×7视图。假设我们只想使用单个带有7x7感受野的CONV层,而不是这三个3x3的CONV层。这些神经元将具有输入体积的感受野尺寸,其在空间范围(7×7)中是相同的,但是具有几个缺点。首先,神经元将在输入之上计算线性函数,而三个CONV层堆包含使得它们的特征更具表现性的非线性。其次,如果我们假设所有的体积都有 C C 通道,那么可以看出,单个7×7 CONV层将包含C×7×7×C=49C2 C × ( 7 × 7 × C ) = 49 C 2 个参数,而三个3×3 CONV层将只包含 3×C×3×3×C=27C2 3 × C × ( 3 × 3 × C ) ) = 27 C 2 个参数。直观地说,使用小型filter来堆叠CONV层,而不是使用一个带有大型filter的CONV层,这使得我们可以使输入表现出更强大的功能以及更少的参数。作为一个实际的缺点,如果我们打算做反向传播,我们可能需要更多的内存来保留所有的中间CONV层结果。

        Recent departures。应该指出的是,在Google的Inception架构中,以及微软亚洲研究院(Microsoft Research Asia)时下的(最先进的)Residual Networks中,线性列表层的传统范例最近受到了挑战。这两个(请参阅案例研究部分的详细信息)特征更具复杂性和不同的连接结构。

        In practice: use whatever works best on ImageNet.如果您在思考架构决策时感到疲劳,你会因为知道了在90%以上的应用程序中不必担心这些问题而感到高兴。我喜欢把这一点总结为“don’t be a hero”:你应该看看ImageNet上现有的最好的架构,下载一个预训练的模型,然后对你的数据进行微调,而不是为了一个问题而动摇自己的架构。你从不需要从零开始训练ConvNet或者从头开始设计一个ConvNet。在Deep Learning school我也提到了这一点。

        层大小模式

        到目前为止,我们已经忽略了在ConvNet中每个层中使用的常见超参数的提及。我们将首先说明确定架构的常用经验规则,然后遵循规则并讨论注释:

        输入层(包含图像)应该可以被整除2次。常见的数字包括32(例如CIFAR-10),64,96(例如STL-10)或224(例如,常见的ImageNet ConvNets),384和512。

        卷积层应该使用小的filter(例如3x3或至多5x5),使用S = 1的stride,并且关键是用零填充输入体积,使得conv层不改变输入的空间维度。也就是说,当 F=3 F = 3 时,则使用 P=1 P = 1 将保留输入的原始大小。当 F=5 F = 5 时, P=2 P = 2 。对于一般的F,可以看出 P=F1/2 P = ( F − 1 ) / 2 保留了输入大小。如果您必须使用更大的filter尺寸(例如7x7左右),则通常在查看输入图像的第一个conv层上看到这一点。

        pooling层负责对输入的空间维度进行下采样。最常见的设置是使用具有2×2感受野(即 F=2 F = 2 )的max-pooling,并且stride为2(即 S=2 S = 2 )。请注意,这会丢弃输入体积中激活函数的75%(由于在width和height上的向下采样都是2)。另一个稍微不太常见的设置是使用3x3感受野,stride为2,但这样做有意义。对于max pooling的感受野尺寸大于3是非常罕见的,因为pooling过于lossy和aggressive。这通常会导致更糟糕的表现。

        Reducing sizing headaches。上面介绍的方案是令人满意的,因为所有的CONV层保持其输入的空间大小,而POOL层本身负责空间下的体积下采样。在另一种方案中,我们使用大于1的stride,或者不要在CONV层中使用零填充输入,我们必须非常仔细地跟踪整个CNN架构的输入体积,并确保所有strides和filters“work out“,并且ConvNet架构是精细的并且是对称的。

        为什么在CONV中使用stride为1呢?较小的stride在实践中效果更好。另外,如前所述,stride1允许我们将所有的空间下采样保留到POOL层,而CONV层仅在depth-wise转换输入体积。

        为什么使用填充?除了CONV之后保持空间大小恒定的上述益处之外,这样做实际上提高了性能。如果CONV层没有对输入进行零填充,而只执行有效的卷积,那么在每个CONV之后,体积的大小将会减少一小部分,并且边界处的信息将很快被“washed away”。

        Compromising based on memory constraints。在某些情况下(特别是在ConvNet架构的早期阶段),使用上面介绍的经验法则可以快速建立内存数量。例如,使用三个3x3 的CONV层(每个64个filters和填充为1)过滤一个224x224x3的图像将创建三个体积大小为[224x224x64]的激活函数。这相当于总计约1000万次激活,或72MB内存(每个图像,激活和梯度)。由于GPU经常受到内存的瓶颈,因此可能需要妥协。实际上,人们更喜欢在网络的第一个CONV层做出让步。事实上,人们更喜欢只在第一个CONV层作出让步。举个例子,一个让步可能是filter大小为7x7,stride为2(如ZF net所示)的第一个CONV层。又如,AlexNet使用11x11的filter大小和stride4。

        案例研究(LeNet / AlexNet / ZFNet / GoogLeNet / VGGNet)

        在卷积网络领域有几个有名字的架构。最常见的是:

        • LeNet。卷积网络的第一个成功应用是由Yann LeCun在20世纪90年代开发的。其中最著名的是用于读取zip编码,数字等的LeNet架构。
        • AlexNet。第一个在计算机视觉中推广卷积网络的工作是由Alex Krizhevsky,Ilya Sutskever和Geoff Hinton开发的AlexNet。 AlexNet于2012年提交给 ImageNet ILSVRC challenge,并且大大优于季军(与亚军错误率26%相比,排名前5的错误率为16%)。该网络与LeNet具有非常相似的架构,但是更深,更大,并且将卷积层叠加在彼此之上(以前通常只有一个总是被紧接着POOL层的CONV层)。
        • ZF Net。 ILSVRC 2013获胜者是来自Matthew Zeiler和Rob Fergus的卷积网络。它被称为ZFNet(Zeiler&Fergus Net的简称)。这是对AlexNet的改进,通过调整架构超参数,特别是通过扩大中间卷积层的大小,使第一层上的stride和filter尺寸更小。
        • GoogLeNet。 ILSVRC 2014获胜者是来自Szegedy et al.的一个卷积网络,来自Google。其主要贡献是开发了一个 Inception Module,大大减少了网络中的参数数量(与60M的AlexNet相比只有4M)。另外,本文在ConvNet的顶部使用Average Pooling而不是全连接Fully Connected层,从而消除了大量似乎并不重要的参数。 GoogLeNet还有几个后续版本,最新的是Inception-v4。
        • VGGNet。 ILSVRC 2014的亚军是来自Karen Simonyan和Andrew Zisserman的网络,后来被称为VGGNet。其主要贡献在于表明网络的depth是良好表现的关键组成部分。他们最终的最佳网络包含16个CONV / FC层,而且极具吸引力的是,突出非常均匀的架构,从头到尾只执行3x3卷积和2x2pooling。他们的pretrained model 可用于Caffe的即插即用。VGGNet的缺点是评估更为昂贵,使用更多的内存和参数(140M)。这些参数中的大部分都在第一个全连接的层中,因为发现这些FC层可以在没有性能降级的情况下被移除,从而大大减少了必要参数的数量。
        • ResNet。Residual Network由Kaiming He等开发,是ILSVRC 2015的获胜者。它具有特殊的skip connections和 batch normalization的大量使用。该架构在网络末端还缺少全连接层。读者还可以参考Kaiming的演示(video,slides),以及recent experiments 中重现这些网络的实验。 ResNets目前是迄今为止最先进的卷积神经网络模型,并且是在实践中使用ConvNets的默认选择(截至2016年5月10日)。特别是,还可以看到更多最新的进展,调整了Kaiming He et al. Identity Mappings in Deep Residual Networks 最初的架构(2016年3月发布)。

        VGGNet详细。让我们更详细地分解VGGNet作为案例研究。整个VGGNet由执行3x3卷积的CONV层和stride1和填充1组成,POOL层执行stride2(没有填充)的2×2的 max pooling。我们可以在处理的每个步骤中写出表现的大小,并跟踪表现大小和weights总数:
        视觉识别:CS231n卷积神经网络_第8张图片
        和卷积网络一样,请注意大部分内存(以及计算时间)都在早期的CONV层中使用,大部分参数都在最后一个FC层中。 在这种情况下,第一个FC层包含100M的weights,总共有140M。

        计算考虑

        构建ConvNet架构时要注意的最大瓶颈是内存。许多现代GPU具有3/4 / 6GB内存的限制,最好的GPU具有大约12GB的内存。有三个主要的内存来源要记录:

        • 从中间体积大小:这些是ConvNet每层激活的原始数量,以及它们的梯度(大小相同)。通常,大部分激活函数都在ConvNet的较早层(即第一个Conv层)上。这些都是反向传播所必需的所以他们都将保留,但是一个聪明的实现是只在测试时间运行一个ConvNet,原则上可以大大减少这种情况,只需将当前的激活函数存储在任何层,并丢弃之前在下层的激活函数。
        • 根据参数大小:这些是保存网络参数的数字,它们在反向传播期间的梯度,如果优化使用动量,Adagrad或RMSProp,通常也是一个步骤缓存。因此,单独存储参数向量的内存通常必须乘以至少3的因子。
        • 每个ConvNet实现都必须维护miscellaneous内存,比如图像数据 batches,可能还有扩展版本等等。

        一旦粗略估计了总值(激活函数,梯度和杂项),数字应该转换为GB单位。乘以4得到原始字节数(因为每个浮点数是4个字节,或者可能是双精度8个字节)取数值,然后再除以1024得到以KB, MB,最后是GB为单位的内存量。如果你的网络不适合,“使它适合”的一个常用的探索就是减少batch大小,因为大部分内存通常被激活函数所消耗。

        其他参考

        与执行有关的额外资源:

        • Soumith benchmarks for CONV performance
        • ConvNetJS CIFAR-10
          demo允许您使用ConvNet架构,并在浏览器中实时查看结果和计算结果。
        • Caffe,流行的ConvNet库之一。
        • State of the art ResNets in Torch7

你可能感兴趣的:(译文,计算机视觉,卷积神经网络,CNN,CS231n,李飞飞)