ConvNet

内容列表

  • 结构概述
  • 构建卷积神经网络的各种层
    • 卷积层
    • 池化(Pooling)层
    • 归一化层
    • 全连接层
    • 将全连接层转化成卷积层
  • 卷积神经网络的结构
    • 层的排列规律
    • 层的尺寸设置规律
    • 案例学习(LeNet/AlexNet/ZFNet/GoogleNet/VGGNet/ResNet)
    • 计算上的考量
  • 拓展阅读

Convolutional Neural Networks (CNNs / ConvNets)

卷积神经网络与常规的神经网络十分相似:

  • 都由神经元组成,神经元中有具有学习能力的权重和偏差
  • 每个神经元都得到一些输入数据,进行内积运算后再进行激活函数运算
  • 整个网络是一个可导的评分函数
  • 在最后一层(通常是全连接层),网络有一个损失函数(如SVM或Softmax) 变化:
  • CNN结构基于一个假设,即输入数据是图像,基于该假设,我们可以加入一些使前向传播函数更高效的特有属性

Architecture Overview

Recall: 常规神经网络

神经网络的输入是一个向量,然后在一系列的隐层中对它做变换。每个隐层都是由若干的神经元组成,每个神经元都与前一层中的所有神经元连接。但是在一个隐层中,神经元相互独立不进行任何连接。最后的全连接层被称为“输出层”,在分类问题中,它输出的值被看做是不同类别的评分值。

但是全连接的方式效率低下,且大量的参数也很快会导致网络过拟合。

神经元的三维排列

与常规神经网络不同,卷积神经网络的各层中的神经元是三维排列的:宽度、高度和深度(其中深度指激活数据体的第三个维度,而不是整个网络的深度,整个网络的深度指网络的层数)。层中的神经元将只与前一层的一小块区域连接,而不是采用全连接方式。在卷积神经网络的最后部分会把全尺寸的图像压缩为包含分类评分的一个向量,向量是在深度方向排列的。

左边是一个3层的神经网络。右边是一个卷积神经网络,图例中网络将它的神经元都排列成3个维度(宽、高和深度)。卷积神经网络的每一层都将3D的输入数据变化为神经元3D的激活数据并输出。

卷积神经网络是由层组成的。每一层都有一个简单的API:用一些含或者不含参数的可导的函数,将输入的3D数据变换为3D的输出数据。

Layers used to build ConvNets

卷积神经网络主要由三种类型的层构成:卷积层,池化(Pooling)层和全连接层。

网络结构例子:一个用于CIFAR-10图像数据分类的卷积神经网络的结构可以是[输入层-卷积层-ReLU层-汇聚层-全连接层]:

  • 输入$[32\times32\times3]$存有图像的原始像素值
  • 卷积层中,神经元与输入层中的一个局部区域连接,每个神经元都计算自己与输入层相连的小区域与自己权重的内积。卷积层会计算所有神经元的输出。如果使用12个滤波器(Kernel),得到的输出数据体的维度就是$[32\times32\times12]$
  • ReLU层会逐个元素地进行激活函数操作。该层对数据尺寸没有改变。
  • Pooling层在空间维度(宽度和高度)上进行降采样(downsampling)操作,数据尺寸变为$[16\times16\times12]$
  • 全连接层会计算分类评分,数据尺寸变为$[1\times1\times10]$。

卷积神经网络一层一层地将图像从原始像素值变换成最终的分类评分值。其中有的层含有参数,有的没有。

具体说来,卷积层和全连接层(Conv/FC)对输入执行变换操作的时候,不仅会用到激活函数,还会用到很多参数(神经元的突触权值和偏差)。而ReLU层和池化层则是进行一个固定不变的函数操作。卷积层和全连接层中的参数会随着梯度下降被训练,这样卷积神经网络计算出的分类评分就能和训练集中的每个图像的标签吻合了。

Convolutional Layer

概述和直观介绍:卷积层的参数是由一些可学习的滤波器集合构成的。每个滤波器在空间上(宽和高)都比较小,但是深度和输入数据一致。前向传播的时候,让每个滤波器都在输入数据的宽度和高度上滑动(卷积),然后计算整个滤波器和输入数据任一处的内积。当滤波器沿着输入数据的宽度和高度滑过后,会生成一个2维的激活图(activation map),激活图给出了在每个空间位置处滤波器的反应。

在每个卷积层上,我们会有一整个集合的滤波器(比如12个),每个都会生成一个不同的二维激活图。将这些激活映射在深度方向上层叠起来就生成了输出数据。

局部连接:让每个神经元只与输入数据的一个局部区域连接,该连接的空间大小叫做神经元的感受野(receptive field),其尺寸其实就是滤波器的空间尺寸。在深度方向上,这个连接的大小总是和输入量的深度相等。

空间排列:3个超参数控制着输出数据体的尺寸:深度(depth),步长(stride),零填充(zero-padding)。

  • depth:和使用的滤波器数量一致,而每个滤波器在输入数据中寻找一些不同的东西。我们将沿着深度方向排列、感受野相同的神经元集合成为深度列(depth column)或纤维(fibre)
  • stride:滑动滤波器时的移动步长
  • zero-padding:可以控制输出体的空间尺寸 输出数据体的空间尺寸可以通过输入数据体尺寸(W),卷积层中神经元的receptive field(F),步长(S)和零填充的数量(P)来计算(这里假设输入数组的高度和宽度相等): $$空间尺寸=(W-F+2P)/S+1$$

参数共享:在卷积层中使用参数共享来控制参数的数量。做一个合理的假设:如果某一特征在计算某个空间位置(x,y)时有用,则在计算另一个不同位置的时候也有用。换言之,就是讲深度维度上的一个单独的2维切片看做深度切片(depth slice),每个深度切片上的神经元都是用相同的权重和偏差。在反向传播时,要计算每个神经元对它的权重的梯度,但是需要把同一个深度切片上所有的神经元对权重的梯度累加,这样就得到了对共享权重的梯度。每个切片只更新一个权重集

注意

  • 如果在一个深度切片中的所有权重都使用同一个权重向量,那么卷积层的前向传播在每个深度切片中可以看做是在计算神经元权重和输入数据体的卷积,这也就是为什么叫做卷积层,而滤波器又可以叫做卷积核(kernel)。
  • 有时候参数共享假设可能没有意义,特别是当卷积神经网络的输入图像是一些明确的中心结构时。这时我们更期望在图片的不同位置学习到完全不同的特征。这时通常就放松参数共享的限制,将层称为局部连接层(Locally-Connected Layer)。

Numpy例子

假设输入数据体是numpy数组$X$,则

  • 一个位于$(x,y)$的深度列/纤维:$X[x,y,:]$
  • 在深度为d处的深度切片/激活图:$X[:,:,d]$

卷积层例子:假设输入数据体$X$的尺寸$X.shape:(11,11,4)$,不用零填充($P=0$),滤波器尺寸$F=5$,步长$S=2$。则输出数据体的空间尺寸是$(11-5)/2+1=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.shape:(5,5,4)$,因为滤波器尺寸为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[0,1,1] = np.sum(X[:5,2:7,:] * W1) + b1$ (在y方向上)
  • $V[2,3,1] = np.sum(X[4:9,6:11,:] * W1) + b1$ (或两个方向上同时)

在上面的例子中,为了简洁略去了卷积层对于输出数组V中其他部分的操作。

Convolutional layer summary

  • 输入数据体的尺寸$为W_1\times H_1\times D_1$
  • 4个超参数:
    • 滤波器的数量$K$
    • 滤波器的空间尺寸$F$
    • 步长$S$
    • 零填充数量$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$个权重,卷积层一共有$F\cdot F\cdot D_1\cdot K$个权重和$K$个偏置。
  • 在输出数据体中,第d个深度切片(空间尺寸是$W_2\times H_2$),用第$d$个滤波器和输入数据进行有效卷积运算的结果(使用步长$S$),最后在加上第$d$个偏差。

用矩阵乘法实现:卷积运算本质上就是在滤波器和输入数据的局部区域间做点积。将卷积层的前向传播变成一个巨大的矩阵乘法:

  • 输入图像的局部区域被$im2col$操作拉伸为列。比如,如果输入是$[227\times227\times3]$,要与尺寸为$11\times11\times3$的滤波器以步长为$4$进行卷积,就取输入中的$[11\times11\times3]$数据块,然后将其拉伸为长度为$11\times11\times3=363$的列向量。重复进行这一过程,因为步长为4,所以输出的宽高为$(227-11)/4+1=55$,所以得到im2col操作的输出矩阵$X_{col}$的尺寸是$[363\times3025]$,其中每列是拉伸的感受野,共有$55\times55=3,025$个。
  • 卷积层的权重也同样被拉伸成行。比如,如果有96个尺寸为$[11\times11\times3]$的滤波器,就生成一个矩阵$W_{row}$,尺寸为$[96\times363]$。
  • 卷积结果和$np.dot(W_{row},X_{col})$等价,在上面的例子中输出是$[96\times3025]$
  • 最后需要变成合理的输出$[55\times55\times\times96]$

反向传播:卷积操作的反向传播还是一个卷积(但是是和空间上翻转的滤波器)

$1\times1$卷积Network in Network

扩张卷积(Dilated convolutions)Multi-Scale Context Aggregation by Dilated Convolutions.让滤波器中元素之间有间隙也是可以的,这就叫做扩张。在某些设置中,扩张卷积与正常卷积结合起来非常有用,因为在很少的层数内更快地汇集输入图片的大尺度特征。

Pooling Layer

通常在连续的卷积层之间会周期性插入一个池化层,用来逐渐降低数据体的空间尺寸,从而减少网络中参数的数量并控制过拟合。池化层使用MAX操作,对输入数据体的每一个深度切片独立进行操作,改变它的空间尺寸。最常见的形式是池化层使用尺寸2x2的滤波器,以步长为2来对每个深度切片进行降采样,将其中75%的激活信息都丢掉。每个MAX操作是从4个数字中取最大值(也就是在深度切片中某个2x2的区域)。深度保持不变。池化层的一些公式:

  • 输入数据体尺寸$W_1\cdot H_1\cdot D_1$
  • 有两个超参数:
    • 空间大小$F$
    • 步长$S$
  • 输出数据体尺寸$W_2\cdot H_2\cdot D_2$,其中
    • $W_2=(W_1-F)/S+1$
    • $H_2=(H_1-F)/S+1$
    • $D_2=D_1$
  • 因为对输入进行的是固定函数计算,所以没有引入参数
  • 在汇聚层中很少使用零填充

在实践中,最大池化层通常只有两种形式:一种是$F=3,S=2$,也叫重叠池化(overlapping pooling),另一个更常用的是$F=2,S=2$。对更大感受野进行池化需要的池化尺寸也更大,而且往往对网络有破坏性。

普通池化(General Pooling):max pooling, average pooling, L2-norm pooling, etc.

反向传播:max(x,y)函数的反向传播可以简单理解为将梯度只沿最大的数回传。因此,在向前传播经过池化层的时候,通常会把池中最大元素的索引记录下来(有时这个也叫作道岔(switches)),这样在反向传播的时候就很高效。

不用池化层:通过在卷积层中使用更大的步长来降低数据体的尺寸。Striving for Simplicity: The All Convolutional Net

Normalization Layer

对于不同类型的归一化层,可以参考Alwx的关于cuda-convnet library API的讨论

Fully-connected Layer

在全连接层中,神经元对于前一层中的所有激活数据是全部连接的

Converting FC layers to CONV layers

FC layer和Conv layer

  • 同:计算点积
  • 异:卷积层中的神经元只与输入数据中的一个局部区域连接,并且在卷积列中的神经元共享参数

全连接层转化为卷积层:相较于使用被转化前的原始卷积神经网络对所有36个位置进行迭代计算,使用转化后的卷积神经网络进行一次前向传播计算要高效得多。

Net Surgery:一个使用Caffe演示如何在进行变换的IPython Note教程

ConvNet Architectures

层的排列规律(Layer Patterns)

卷积神经网络最常见的形式就是将一些卷积层和ReLU层放在一起,其后紧跟汇聚层,然后重复如此直到图像在空间上被缩小到一个足够小的尺寸,在某个地方过渡成成全连接层也较为常见。 最常见的卷积神经网络结构如下:

$$Input\to[[Conv\to ReLU]*N\to Pool?]*M\to [FC\to ReLU]*K\to FC$$

其中$*$指的是重复次数,$POOL?$指的是一个可选的汇聚层。其中$N >=0$,通常$N<=3,M>=0,K>=0$,通常$K<3$。

几个小滤波器卷积层的组合比一个大滤波器卷积层好

  • 多个卷积层与非线性的激活层交替的结构,比单一卷积层的结构更能提取出深层的更好的特征
  • 参数更少
  • 不足:在反向传播时,中间的卷积层可能会导致占用更多的内存

层的尺寸设置规律(Layer Sizing Patterns)

输入层:应该能被2整除很多次。

卷积层:应使用小尺寸滤波器($3\times3$或$5\times5$),使用步长$S=1$,对数据进行零填充,从而保持输入数据的空间尺寸不变。一般对于任意$F$,当$P=(F-1)/2$的时候能保持输入尺寸。如果必须使用更大的滤波器尺寸(比如$7\times7$之类),通常只用在第一个面对原始图像的卷积层上。

池化层:负责对输入数据的空间维度进行降采样。

  • 减少尺寸设置的问题:如果使用的步长大于1并且不对卷积层的输入数据使用零填充,那么就必须确认整个卷积神经网络结构的过程中所有的步长和滤波器都尺寸互相吻合。
  • 更小的步长效果更好:步长为1可以让空间维度的降采样全部由池化层负责,卷积层只负责对输入数据体的深度进行变换。
  • 使用零填充:使用零填充除了前面提到的可以让卷积层的输出数据保持和输入数据在空间维度的不变,还可以提高算法性能。如果卷积层值进行卷积而不进行零填充,那么数据体的尺寸就会略微减小,那么图像边缘的信息就会过快地损失掉。

Case studies

  • LeNet
  • Alexnet: AlexNet采用了层叠的卷积层来获取特征
  • ZF Net: ZF Net相比AlexNet,它增加了中间卷积层的尺寸,让第一层的步长和滤波器尺寸更小
  • GoogleNet: GoogleNet实现了一个Inception Module,大大减少了网络中的参数,同时它使用平均池化代替了Convnet顶层的全连接层,把大量不是很重要的参数去除掉了。更新的版本:Inception-v4
  • VGGNet: VGGNet展示出网络的深度是算法优良性能的关键部分
  • ResNet: Residual Network使用了特殊的跳跃链接(skip connections),大量使用了批归一化,且在最后没有使用全连接层。VideoSlidesExperiments in Torch.ResNets are currently by far state of the art Convolutional Neural Network models and are the default choice for using ConvNets in practice. Kaiming He et al. Identity Mappings in Deep Residual Networks for more developments.

Computational Considerations

三种内存占用来源:

  • 来自中间数据体尺寸:卷积神经网络中的每一层中都有激活数据体的原始数值,以及损失函数对它们的梯度(和激活数据体尺寸一致)。通常,大部分激活数据都是在网络中靠前的层中(比如第一个卷积层)。在训练时,这些数据需要放在内存中,因为反向传播的时候还会用到。在测试时,可以让网络在测试运行时候每层都只存储当前的激活数据,然后丢弃前面层的激活数据,这样就能减少巨大的激活数据量
  • 来自参数尺寸:存储参数向量的内存通常需要在参数向量的容量基础上乘以3或者更多。
  • 各种零散的内存占用

Further Readings

和实践相关的拓展资源:

CNN Architecture: