第5章 - 卷积神经网络 - 书籍
本文最后更新于:3 个月前
[√] 第5章 - 卷积神经网络 - 书籍
卷积神经网络(Convolutional Neural Network,CNN)是受生物学上感受野机制的启发
而提出的。
目前的卷积神经网络一般是由卷积层、汇聚层和全连接层交叉堆叠而成的前馈神经网络
,有三个结构上的特性:局部连接、权重共享以及汇聚。这些特性使得卷积神经网络具有一定程度上的平移、缩放和旋转不变性。
和前馈神经网络相比,卷积神经网络的参数更少
。
卷积神经网络主要应用在图像和视频分析的任务上
,其准确率一般也远远超出了其他的神经网络模型。近年来卷积神经网络也广泛地应用到自然语言处理、推荐系统等领域。
在学习本章内容前,建议您先阅读《神经网络与深度学习》第5章:卷积神经网络的相关内容,关键知识点如 图5.1 所示,以便更好的理解和掌握书中的理论知识在实践中的应用方法。
本实践基于 《神经网络与深度学习》第5章:卷积神经网络 相关内容进行设计,主要包含两部分:
- 模型解读:介绍卷积的原理、卷积神经网络的网络结构、残差连接的原理以及残差网络的网络结构,并使用简单卷积神经网络和残差网络,完成手写数字识别任务;
- 案例与实践:基于残差网络ResNet18完成CIFAR-10图像分类任务。
[√] B ~> 5.1 - 卷积
全连接网络会出现的问题:(alec)
- 模型参数过多,容易发生过拟合。 在全连接前馈网络中,隐藏层的每个神经元都要跟该层所有输入的神经元相连接。随着隐藏层神经元数量的增多,参数的规模也会急剧增加,导致整个神经网络的训练效率非常低,也很容易发生过拟合。
- 难以提取图像中的局部不变性特征。 自然图像中的物体都具有局部不变性特征,比如尺度缩放、平移、旋转等操作不影响其语义信息。而全连接前馈网络很难提取这些局部不变性特征。
卷积神经网络有三个结构上的特性:局部连接、权重共享和汇聚。这些特性使得卷积神经网络具有一定程度上的平移、缩放和旋转不变性。和前馈神经网络相比,卷积神经网络的参数也更少。因此,通常会使用卷积神经网络来处理图像信息。(alec)
卷积是分析数学中的一种重要运算,常用于信号处理或图像处理任务。本节以二维卷积为例来进行实践。
[√] D => 5.1.1 - 二维卷积运算
在机器学习和图像处理领域,卷积的主要功能是在一个图像(或特征图)上滑动一个卷积核,通过卷积操作得到一组新的特征。
alec:
通过卷积得到一组新的特征
在计算卷积的过程中,需要进行卷积核的翻转,而这也会带来一些不必要的操作和开销。因此,在具体实现上,一般会以数学中的互相关(Cross-Correlatio)运算来代替卷积。
alec:
具体实现上,一般会以互相关操作来代替真正的卷积
卷积的主要作用是抽取特征,是否翻转并不影响特征抽取的能力
卷积核是可学习的参数
在神经网络中,卷积运算的主要作用是抽取特征,卷积核是否进行翻转并不会影响其特征抽取的能力。特别是当卷积核是可学习的参数时,卷积和互相关在能力上是等价的。因此,很多时候,为方便起见,会直接用互相关来代替卷积。
说明:
在本案例之后的描述中,除非特别声明,卷积一般指“互相关”。
对于一个输入矩阵$\mathbf X\in\Bbb{R}^{M\times N}$和一个滤波器$\mathbf W \in\Bbb{R}^{U\times V}$,它们的卷积为
$$y_{i,j}=\sum_{u=0}^{U-1} \sum_{v=0}^{V-1} w_{uv}x_{i+u,j+v}。(5.1)$$
图5.2 给出了卷积计算的示例。
经过卷积运算后,最终输出矩阵大小则为
$$M’ = M - U + 1,(5.2)$$
$$N’ = N - V + 1.(5.3)$$
alec:
此处可以记忆帮助为,当卷积核大小为1x1的时候,输出的大小为M’ = M = M - 1 + 1,N’ = N = N - 1 + 1
可以发现,使用卷积处理图像,会有以下两个特性:
- 在卷积层(假设是第$l$层)中的每一个神经元都只和前一层(第$l-1$层)中某个局部窗口内的神经元相连,构成一个局部连接网络,这也就是卷积神经网络的局部连接特性。
- 由于卷积的主要功能是在一个图像(或特征图)上滑动一个卷积核,所以作为参数的卷积核$\mathbf W \in\Bbb{R}^{U\times V}$对于第$l$层的所有的神经元都是相同的,这也就是卷积神经网络的权重共享特性。
alec:
每一个神经元都只和前一层(第$l-1$层)中某个局部窗口内的神经元相连,构成一个局部连接网络
[√] D => 5.1.2 - 二维卷积算子
在本书后面的实现中,算子都继承paddle.nn.Layer
,并使用支持反向传播的飞桨API进行实现,这样我们就可以不用手工写backward()
的代码实现。
根据公式(5.1),我们首先实现一个简单的二维卷积算子,代码实现如下:
1 |
|
1 |
|
[√] D => 5.1.3 - 二维卷积的参数量和计算量
[√] F -> 参数量
由于二维卷积的运算方式为在一个图像(或特征图)上滑动一个卷积核,通过卷积操作得到一组新的特征。所以参数量仅仅与卷积核的尺寸有关,对于一个输入矩阵$\mathbf X\in\Bbb{R}^{M\times N}$和一个滤波器$\mathbf W \in\Bbb{R}^{U\times V}$,卷积核的参数量为$U\times V$。
假设有一幅大小为$32\times 32$的图像,如果使用全连接前馈网络进行处理,即便第一个隐藏层神经元个数为1,此时该层的参数量也高达$1025$个,此时该层的计算过程如 图5.3 所示。
alec:
使用全连接由前一层推得后一层的一个神经元,假设前一层的图像大小为32*32,而不算三个图层通道,也需要参数量为32 * 32 + 1 = 1025个参数
可以想像,随着隐藏层神经元数量的变多以及层数的加深,使用全连接前馈网络处理图像数据时,参数量会急剧增加。
如果使用卷积进行图像处理,当卷积核为$3\times 3$时,参数量仅为$9$,相较于全连接前馈网络,参数量少了非常多。
alec:
卷积运算方式的形象化理解就是,首先其实二维和一维在本质上没区别,只不过是把一行改成一行一行的;其次,全连接的方式,就是相当远给上一层的每个特征点一个VIP的待遇,即32x32个像素点每个像素点都配一个权重参数,当使用卷积的时候,相当于一个固定大小的卷积核,反复的运用在一个32x32的图像上,反复利用,因此节省了参数。(为什么可以共享呢?因为图像具有平移的特征不变形等,因此可以使用共享卷积核的方式来提取特征。)
[√] F -> 计算量
在卷积神经网络中运算时,通常会统计网络总的乘加运算次数作为计算量(FLOPs,floating point of operations),来衡量整个网络的运算速度。对于单个二维卷积,计算量的统计方式为:
alec:
计算量(FLOPs,浮点运算次数)是用来衡量运算次数的,比如不同算力的显卡,FLOPs就不同。
对于输出得到的特征图,一个点是通过一次卷积运算得来的,假设卷积核大小是$U\times V$,则需要$U\times V$ 次
$M’\times N’$大小的特征图需要的运算次数为$$FLOPs=M’\times N’\times U\times V。$$
$$FLOPs=M’\times N’\times U\times V。(5.4)$$
其中$M’\times N’$表示输出特征图的尺寸,即输出特征图上每个点都要与卷积核$\mathbf W \in\Bbb{R}^{U\times V}$进行$U\times V$次乘加运算。对于一幅大小为$32\times 32$的图像,使用$3\times 3$的卷积核进行运算可以得到以下的输出特征图尺寸:
$$M’ = M - U + 1 = 30$$
$$N’ = N - V + 1 = 30$$
此时,计算量为:
$$FLOPs=M’\times N’\times U\times V=30\times 30\times 3\times 3=8100$$
[√] D => 5.1.4 - 感受野
[√] F -> 感受野定义
输出特征图上每个点的数值,是由输入图片上大小为$U\times V$的区域的元素与卷积核每个元素相乘再相加得到的,所以输入图像上$U\times V$区域内每个元素数值的改变,都会影响输出点的像素值。我们将这个区域叫做输出特征图上对应点的感受野。
感受野内每个元素数值的变动,都会影响输出点的数值变化。比如$3\times3$卷积对应的感受野大小就是$3\times3$,如 图5.4 所示。
alec:
输出特征图上的一个点就代表一个神经元计算得来的。有多少个特征点,就相当于有多少个神经元。神经元从前面一层收集信息,然后当兴奋达到阈值的时候,触发冲动,通过激活函数,得到一个值往后传。
对于全连接来说,该输出值是通过前面一层的全部的像素点计算得来的,对于卷积来说,该特征点,只是由卷积核收集的卷积核当前位置对应的感受野内的像素点的信息计算得来的。
而当通过两层$3\times3$的卷积之后,感受野的大小将会增加到$5\times5$,如 图5.5 所示。
因此,当增加卷积网络深度的同时,感受野将会增大,输出特征图中的一个像素点将会包含更多的图像语义信息
。
alec:
随着层数的加深,深层特征图上的一个像素点,层层递进之后,这个像素点能够感受到输入图片的一个很大的区域。即感受野大。
因此这个深层像素点含有的是更高维的特征,而浅层像素点由于感受野小,只能含有低维的特征。
感受野可以理解为每层特征上的一个点在输入层图像上能够感受多大范围的区域。
浅层的特征图上的像素点感受的小感受野,随着层层递进,深层的一个像素点能够感受一个大范围内的信息。
[√] D => 5.1.5 - 卷积的变种
在卷积的标准定义基础上,还可以引入卷积核的滑动步长和零填充来增加卷积的多样性,从而更灵活地进行特征抽取。
[√] F -> 5.1.5.1 - 步长
在卷积运算的过程中,有时会希望跳过一些位置来降低计算的开销,也可以把这一过程看作是对标准卷积运算输出的下采样。
在计算卷积时,可以在所有维度上每间隔$S$个元素计算一次,$S$称为卷积运算的步长(Stride),也就是卷积核在滑动时的间隔。
alec:
我们在一幅图片缩小之后,仍然知道这个图片想要表达的信息,这种现象可以看出图像的像素信息具有冗余性。
因此为了减少计算的开销,可以在卷积计算的过程中,对于被卷积核计算的特征图像,可以跳着卷积,以降低计算开销。
在所有维度上每间隔$S$个元素计算一次,$S$称为卷积运算的步长(Stride),也就是卷积核在滑动时的间隔。
这种也可以看做是对标准卷积的一个下采样。
在卷积的时候,通过步长来进行卷积是一种下采样操作;
然后通过池化来精炼信息,也可以看做是一种下采样操作;
下采样的目的都是精简、提取主要的信息,去除冗余性。
此时,对于一个输入矩阵$\mathbf X\in\Bbb{R}^{M\times N}$和一个滤波器$\mathbf W \in\Bbb{R}^{U\times V}$,它们的卷积为
$$y_{i,j}=\sum_{u=0}^{U-1} \sum_{v=0}^{V-1} w_{uv}x_{i\times S+u,j\times S+v},(5.5)$$
在二维卷积运算中,当步长$S=2$时,计算过程如 图5.6 所示。
alec:
带步长的卷积计算公式,可以通过上面这个图像为8x8,步长为2,卷积核大小为2x2的来记忆。
[√] F -> 5.1.5.2 - 零填充(Zero Padding)
在卷积运算中,还可以对输入用零进行填充使得其尺寸变大。根据卷积的定义,如果不进行填充,当卷积核尺寸大于1时,输出特征会缩减。对输入进行零填充则可以对卷积核的宽度和输出的大小进行独立的控制。
alec:
通过零填充来控制输出特征图的大小。
在二维卷积运算中,零填充(Zero Padding)是指在输入矩阵周围对称地补上$P$个$0$。
图5.7 为使用零填充的示例。
对于一个输入矩阵$\mathbf X\in\Bbb{R}^{M\times N}$和一个滤波器$\mathbf W \in\Bbb{R}^{U\times V}$,,步长为$S$,对输入矩阵进行零填充,那么最终输出矩阵大小则为
$$M’ = \frac{M + 2P - U}{S} + 1,(5.6)$$
$$N’ = \frac{N + 2P - V}{S} + 1.(5.7)$$
alec:
输出矩阵的大小,
M’ = (M + 2P - U) / S + 1。M是原图宽,P是填充边宽,U是卷积核大小,S是步长,+1。
引入步长和零填充后的卷积,参数量和计算量的统计方式与之前一致,参数量与卷积核的尺寸有关,为:$U\times V$,计算量与输出特征图和卷积核的尺寸有关,为:
$$FLOPs=M’\times N’\times U\times V=(\frac{M + 2P - U}{S} + 1)\times (\frac{N + 2P - V}{S} + 1)\times U\times V。(5.8)$$
一般常用的卷积有以下三类:
- 窄卷积:步长$S=1$,两端不补零$P=0$,卷积后输出尺寸为:
$$M’ = M - U + 1,(5.9)$$
$$N’ = N - V + 1.(5.10)$$
- 宽卷积:步长$S=1$,两端补零$P=U-1=V-1$,卷积后输出尺寸为:
$$M’ = M + U - 1,(5.11)$$
$$N’ = N + V - 1.(5.12)$$
- 等宽卷积:步长$S=1$,两端补零$P=\frac{(U-1)}{2}=\frac{(V-1)}{2}$,卷积后输出尺寸为:
$$M’ = M,(5.13)$$
$$N’ = N.(5.14)$$
alec:
在步长为1的时候,根据填充的长短,分为窄卷积(不填充)、等宽卷积、宽卷积。
通常情况下,在层数较深的卷积神经网络,比如:VGG、ResNet中,会使用等宽卷积保证输出特征图的大小不会随着层数的变深而快速缩减。例如:当卷积核的大小为$3\times 3$时,会将步长设置为$S=1$,两端补零$P=1$,此时,卷积后的输出尺寸就可以保持不变。在本章后续的案例中,会使用ResNet进行实验。
alec:
卷积神经网络的层数很深的时候,如果使用窄卷积,那么随着层数的加深,特征图会越来越小,变得很小。因此一般层数深的网络,会使用等宽卷积以保证特征图大小不变。等宽卷积,2P - U + 1 = 0,即填充和损耗相抵消了。P = (U-1)/2。
比如常见的3x3的卷积核,等宽卷积,则填充为P = (U-1)/2 = 1。
alec:
层数较深的卷积神经网络,比如VGG、ResNet
[√] D => 5.1.6 - 带步长和零填充的二维卷积算子
引入步长和零填充后,二维卷积算子代码实现如下:
1 |
|
1 |
|
从输出结果看出,使用$3\times3$大小卷积,padding
为1,当stride
=1时,模型的输出特征图可以与输入特征图保持一致;当stride
=2时,输出特征图的宽和高都缩小一倍。
[√] D => 5.1.7 - 使用卷积运算完成图像边缘检测任务
alec:
拉布拉斯算子常用语图像边缘检测提取
在图像处理任务中,常用拉普拉斯算子对物体边缘进行提取,拉普拉斯算子为一个大小为$3 \times 3$的卷积核,中心元素值是$8$,其余元素值是$-1$。
考虑到边缘其实就是图像上像素值变化很大的点的集合,因此可以通过计算二阶微分得到,当二阶微分为0时,像素值的变化最大。此时,对$x$方向和$y$方向分别求取二阶导数:
$$\frac{\delta^2 I}{\delta x^2} = I(i, j+1) - 2I(i,j) + I(i,j-1),(5.15)$$
$$\frac{\delta^2 I}{\delta y^2} = I(i+1, j) - 2I(i,j) + I(i-1,j).(5.16)$$
完整的二阶微分公式为:
$$\nabla^2I = \frac{\delta^2 I}{\delta x^2} + \frac{\delta^2 I}{\delta y^2} = - 4I(i,j) + I(i,j-1) + I(i, j+1) + I(i+1, j) + I(i-1,j),(5.17)$$
上述公式也被称为拉普拉斯算子,对应的二阶微分卷积核为:
$$\begin{bmatrix}
0 & 1 & 0 \
1 & -4 & 1 \
0 & 1 & 0 \
\end{bmatrix}$$
对上述算子全部求反也可以起到相同的作用,此时,该算子可以表示为:
$$\begin{bmatrix}
0 & -1 & 0 \
-1 & 4 & -1 \
0 & -1 & 0 \
\end{bmatrix}$$
也就是一个点的四邻域拉普拉斯的算子计算结果是自己像素值的四倍减去上下左右的像素的和,将这个算子旋转$45°$后与原算子相加,就变成八邻域的拉普拉斯算子,也就是一个像素自己值的八倍减去周围一圈八个像素值的和,做为拉普拉斯计算结果,此时,该算子可以表示为:
$$\begin{bmatrix}
-1 & -1 & -1 \
-1 & 8 & -1 \
-1 & -1 & -1 \
\end{bmatrix}$$
下面我们利用上面定义的Conv2D
算子,构造一个简单的拉普拉斯算子,并对一张输入的灰度图片进行边缘检测,提取出目标的外形轮廓。
alec:
对拉普拉斯算子来说,如果覆盖的像素全部一样,那么最终的卷积结果就是0.检测不到东西。如果此处是高频信息,那么这个算子就能检测到东西。拉普拉斯算子卷积核所有的参数权重之和为0,因此可以检测出不均衡的高频信息。
1 |
|
从输出结果看,使用拉普拉斯算子,目标的边缘可以成功被检测出来。
[√] B ~> 5.2 - 卷积神经网络的基础算子
从上图可以看出,卷积网络是由多个基础的算子组合而成。下面我们先实现卷积网络的两个基础算子:卷积层算子和汇聚层算子。
[√] D => 5.2.1 - 卷积算子
卷积层是指用卷积操作来实现神经网络中一层。为了提取不同种类的特征,通常会使用多个卷积核一起进行特征提取。
alec:
为了提取不同种类的特征,通常会使用多个卷积核一起进行特征提取。
[√] F -> 5.2.1.1 多通道卷积
在前面介绍的二维卷积运算中,卷积的输入数据是二维矩阵。但实际应用中,一幅大小为$M\times N$的图片中的每个像素的特征表示不仅仅只有灰度值的标量,通常有多个特征,可以表示为$D$维的向量,比如RGB三个通道的特征向量。因此,图像上的卷积操作的输入数据通常是一个三维张量,分别对应了图片的高度$M$、宽度$N$和深度$D$,其中深度$D$通常也被称为输入通道数$D$。如果输入如果是灰度图像,则输入通道数为1;如果输入是彩色图像,分别有$R、G、B$三个通道,则输入通道数为3。
此外,由于具有单个核的卷积每次只能提取一种类型的特征,即输出一张大小为$U\times V$的特征图(Feature Map)。而在实际应用中,我们也希望每一个卷积层能够提取多种不同类型的特征,所以一个卷积层通常会组合多个不同的卷积核来提取特征,经过卷积运算后会输出多张特征图,不同的特征图对应不同类型的特征。输出特征图的个数通常将其称为输出通道数$P$。
alec:
(1)输入图像的特征通道数,比如RGB图像的三层图像通道,称为
输入通道数D
(2)一个卷积核只能提取一种类型的特征信息,因此我们在一个卷积层中,组合
多个不同的卷积核
,这样可以提取多张特征图,在一层卷积层中,放置P个卷积核,能够提取P种特征,将这P个输出特征图的个数称为输出通道数P
(3)举例:RGB图像,3层通道,3x128x128,使用16个大小为3x3的卷积核,进行等宽卷积,得到16x128x128,其中每张128x128都是一类特定于卷积核的特征。
说明:
《神经网络与深度学习》将Feature Map翻译为“特征映射”,这里翻译为“特征图”。
假设一个卷积层的输入特征图$\mathbf X\in \mathbb{R}^{D\times M\times N}$,其中$(M,N)$为特征图的尺寸,$D$代表通道数;卷积核为$\mathbf W\in \mathbb{R}^{P\times D\times U\times V}$,其中$(U,V)$为卷积核的尺寸,$D$代表输入通道数,$P$代表输出通道数。
说明:
在实践中,根据目前深度学习框架中张量的组织和运算性质,这里特征图的大小为$D\times M\times N$,和《神经网络与深度学习》中$M\times N \times D$的定义并不一致。
相应地,卷积核$W$的大小为$\mathbb{R}^{P\times D\times U\times V}$。
[√] F -> 一张输出特征图的计算
对于$D$个输入通道,分别对每个通道的特征图$\mathbf X^d$设计一个二维卷积核$\mathbf W^{p,d}$,并与对应的输入特征图$\mathbf X^d$进行卷积运算,再将得到的$D$个结果进行加和,得到一张输出特征图$\mathbf Z^p$。计算方式如下:
$$
\mathbf Z^p = \sum_{d=1}^D \mathbf W^{p,d} \otimes \mathbf X^d + b^p,(5.18)
$$
$$
\mathbf Y^p = f(\mathbf Z^p)。(5.19)
$$
其中$p$表示输出特征图的索引编号,$\mathbf W^{p,d} \in \mathbb{R}^{U\times V}$为二维卷积核,$b^p$为标量偏置,$f(·)$为非线性激活函数,一般用ReLU函数。
说明:
在代码实现时,通常将非线性激活函数放在卷积层算子外部。
公式(5.13)对应的可视化如图5.9所示。
[√] F -> 多张输出特征图的计算
对于大小为$D\times M\times N$的输入特征图,每一个输出特征图都需要一组大小为$\mathbf W\in \mathbb{R}^{D\times U\times V}$的卷积核进行卷积运算。使用$P$组卷积核分布进行卷积运算,得到$P$个输出特征图$\mathbf Y^1, \mathbf Y^2,\cdots,\mathbf Y^P$。然后将$P$个输出特征图进行拼接,获得大小为$P\times M’ \times N’$的多通道输出特征图。上面计算方式的可视化如下图5.10所示。
[√] F -> 5.2.1.2 - 多通道卷积层算子
根据上面的公式,多通道卷积卷积层的代码实现如下:
1 |
|
1 |
|
[√] F -> 5.2.1.3 - 卷积算子的参数量和计算量
参数量
对于大小为$D\times M\times N$的输入特征图,使用$P$组大小为$\mathbf W\in \mathbb{R}^{D\times U\times V}$的卷积核进行卷积运算,参数量计算方式为:
$$
parameters = P \times D \times U \times V + P.(5.20)
$$
其中,最后的$P$代表偏置个数。例如:输入特征图大小为$3\times 32\times 32$,使用$6$组大小为$3\times 3\times 3$的卷积核进行卷积运算,参数量为:
$$
parameters = 6 \times 3 \times 3 \times 3 + 6= 168.
$$
计算量
对于大小为$D\times M\times N$的输入特征图,使用$P$组大小为$\mathbf W\in \mathbb{R}^{D\times U\times V}$的卷积核进行卷积运算,计算量计算方式为:
$$FLOPs=M’\times N’\times P\times D\times U\times V + M’\times N’\times P。(5.21)$$
其中$M’\times N’\times P$代表加偏置的计算量,即输出特征图上每个点都要与$P$组卷积核$\mathbf W\in \mathbb{R}^{D\times U\times V}$进行$U\times V\times D$次乘法运算后再加上偏置。比如对于输入特征图大小为$3\times 32\times 32$,使用$6$组大小为$3\times 3\times 3$的卷积核进行卷积运算,计算量为:
$$FLOPs=M’\times N’\times P\times D\times U\times V + M’\times N’\times P= 30\times 30\times 3\times 3\times 6\times 3 + 30\times 30\times 6= 151200$$
[√] D => 5.2.2 - 汇聚层算子
alec:
汇聚层的作用是进行特征选择,降低特征数量,从而减少参数数量。
汇聚之后特征图会变小,提取出精简主要的信息。
汇聚层的作用是进行特征选择,降低特征数量,从而减少参数数量。由于汇聚之后特征图会变得更小,如果后面连接的是全连接层,可以有效地减小神经元的个数,节省存储空间并提高计算效率。
常用的汇聚方法有两种,分别是:平均汇聚和最大汇聚。(均匀池化和最大池化)
- 平均汇聚:将输入特征图划分为$2\times2$大小的区域,对每个区域内的神经元活性值取平均值作为这个区域的表示;
- 最大汇聚:使用输入特征图的每个子区域内所有神经元的最大活性值作为这个区域的表示。
图5.11 给出了两种汇聚层的示例。
汇聚层输出的计算尺寸与卷积层一致,对于一个输入矩阵$\mathbf X\in\Bbb{R}^{M\times N}$和一个运算区域大小为$U\times V$的汇聚层,步长为$S$,对输入矩阵进行零填充,那么最终输出矩阵大小则为
$$M’ = \frac{M + 2P - U}{S} + 1,(5.20)$$
$$N’ = \frac{N + 2P - V}{S} + 1.(5.21)$$
alec:
池化层和卷积层的输出矩阵大小的计算公式是一样的
由于过大的采样区域会急剧减少神经元的数量,也会造成过多的信息丢失。目前,在卷积神经网络中比较典型的汇聚层是将每个输入特征图划分为$2\times2$大小的不重叠区域,然后使用最大汇聚的方式进行下采样。
alec:
池化中经典的池化方式是使用2*2大小
由于汇聚是使用某一位置的相邻输出的总体统计特征代替网络在该位置的输出,所以其好处是当输入数据做出少量平移时,经过汇聚运算后的大多数输出还能保持不变。比如:当识别一张图像是否是人脸时,我们需要知道人脸左边有一只眼睛,右边也有一只眼睛,而不需要知道眼睛的精确位置,这时候通过汇聚某一片区域的像素点来得到总体统计特征会显得很有用。这也就体现了汇聚层的平移不变特性。
alec:
图像稍微平移之后,汇聚之后基本还是那张图像,体现了汇聚层的平移不变性。
[√] F -> 汇聚层的参数量和计算量
由于汇聚层中没有参数,所以参数量为$0$;
最大汇聚中,没有乘加运算,所以计算量为$0$,
而平均汇聚中,输出特征图上每个点都对应了一次求平均运算。
使用飞桨实现一个简单的汇聚层,代码实现如下:
1 |
|
1 |
|
[√] 5.3 - 基于LeNet实现手写体数字识别实验
在本节中,我们实现经典卷积网络LeNet-5,并进行手写体数字识别任务。
[√] 5.3.1 - 数据
手写体数字识别是计算机视觉中最常用的图像分类任务,让计算机识别出给定图片中的手写体数字(0-9共10个数字)。由于手写体风格差异很大,因此手写体数字识别是具有一定难度的任务。
我们采用常用的手写数字识别数据集:MNIST数据集。MNIST数据集是计算机视觉领域的经典入门数据集,包含了60,000个训练样本和10,000个测试样本。这些数字已经过尺寸标准化并位于图像中心,图像是固定大小($28\times28$像素)。图5.12给出了部分样本的示例。
为了节省训练时间,本节选取MNIST数据集的一个子集进行后续实验,数据集的划分为:
- 训练集:1,000条样本
- 验证集:200条样本
- 测试集:200条样本
MNIST数据集分为train_set、dev_set和test_set三个数据集,每个数据集含两个列表分别存放了图片数据以及标签数据。比如train_set包含:
- 图片数据:[1 000, 784]的二维列表,包含1 000张图片。每张图片用一个长度为784的向量表示,内容是 $28\times 28$ 尺寸的像素灰度值(黑白图片)。
- 标签数据:[1 000, 1]的列表,表示这些图片对应的分类标签,即0~9之间的数字。
观察数据集分布情况,代码实现如下:
1 |
|
1 |
|
可视化观察其中的一张样本以及对应的标签,代码如下所示:
1 |
|
[√] 5.3.1.1 - 数据预处理
图像分类网络对输入图片的格式、大小有一定的要求,数据输入模型前,需要对数据进行预处理操作,使图片满足网络训练以及预测的需要。本实验主要应用了如下方法:
- 调整图片大小:LeNet网络对输入图片大小的要求为 $32\times 32$ ,而MNIST数据集中的原始图片大小却是 $28\times 28$ ,这里为了符合网络的结构设计,将其调整为$32 \times 32$;
- 规范化: 通过规范化手段,把输入图像的分布改变成均值为0,标准差为1的标准正态分布,使得最优解的寻优过程明显会变得平缓,训练过程更容易收敛。
在飞桨中,提供了部分视觉领域的高层API,可以直接调用API实现简单的图像处理操作。通过调用paddle.vision.transforms.Resize
调整大小;调用paddle.vision.transforms.Normalize
进行标准化处理;使用paddle.vision.transforms.Compose
将两个预处理操作进行拼接。
alec:
- 通过resize调整图像大小
- 通过normalize标准化处理图像,使得图像像素的分布变为标准正态分布
- 使用compose将两个操作拼接
代码实现如下:
1 |
|
将原始的数据集封装为Dataset类,以便DataLoader调用。
1 |
|
1 |
|
[√] 5.3.2 - 模型构建
LeNet-5虽然提出的时间比较早,但它是一个非常成功的神经网络模型。
基于LeNet-5的手写数字识别系统在20世纪90年代被美国很多银行使用,用来识别支票上面的手写数字。LeNet-5的网络结构如图5.13所示。
我们使用上面定义的卷积层算子和汇聚层算子构建一个LeNet-5模型。
这里的LeNet-5和原始版本有4点不同:
- C3层没有使用连接表来减少卷积数量。
- 汇聚层使用了简单的平均汇聚,没有引入权重和偏置参数以及非线性激活函数。
- 卷积层的激活函数使用ReLU函数。
- 最后的输出层为一个全连接线性层。
网络共有7层,包含3个卷积层、2个汇聚层以及2个全连接层的简单卷积神经网络接,受输入图像大小为$32\times 32=1, 024$,输出对应10个类别的得分。
具体实现如下:
alec:
所谓的维度,不过就是一层层的每个箱子里面套了几个箱子,所以不要畏难。
比如,[1,1,32,32] 这个数据,
就是一个32x32的图片,放到一个箱子里,然后再放到一个箱子里
[2,3,32,32]
这个就是3个箱子里分别放一张32x32的图片,然后这个三个箱子在变成两份分别放到两个箱子里
lenet5,3个卷积层、2个全连接层,一共5层
1 |
|
下面测试一下上面的LeNet-5模型,构造一个形状为 [1,1,32,32]的输入数据送入网络,观察每一层特征图的形状变化。代码实现如下:
1 |
|
1 |
|
从输出结果看,
- 对于大小为$32 \times32$的单通道图像,先用6个大小为$5 \times5$的卷积核对其进行卷积运算,输出为6个$28 \times28$大小的特征图;
- 6个$28 \times28$大小的特征图经过大小为$2 \times2$,步长为2的汇聚层后,输出特征图的大小变为$14 \times14$;
- 6个$14 \times14$大小的特征图再经过16个大小为$5 \times5$的卷积核对其进行卷积运算,得到16个$10 \times10$大小的输出特征图;
- 16个$10 \times10$大小的特征图经过大小为$2 \times2$,步长为2的汇聚层后,输出特征图的大小变为$5 \times5$;
- 16个$5 \times5$大小的特征图再经过120个大小为$5 \times5$的卷积核对其进行卷积运算,得到120个$1 \times1$大小的输出特征图;
- 此时,将特征图展平成1维,则有120个像素点,经过输入神经元个数为120,输出神经元个数为84的全连接层后,输出的长度变为84。
- 再经过一个全连接层的计算,最终得到了长度为类别数的输出结果。
考虑到自定义的Conv2D
和Pool2D
算子中包含多个for
循环,所以运算速度比较慢。飞桨框架中,针对卷积层算子和汇聚层算子进行了速度上的优化,这里基于paddle.nn.Conv2D
、paddle.nn.MaxPool2D
和paddle.nn.AvgPool2D
构建LeNet-5模型,对比与上边实现的模型的运算速度。代码实现如下:
1 |
|
测试两个网络的运算速度。
1 |
|
1 |
|
可以看出,paddle中的模型速度,要远快于自定义的模型速度
这里还可以令两个网络加载同样的权重,测试一下两个网络的输出结果是否一致。
1 |
|
1 |
|
可以看到,输出结果是一致的。
这里还可以统计一下LeNet-5模型的参数量和计算量。
[√] 参数量
按照公式(5.18)进行计算,可以得到:
- 第一个卷积层的参数量为:$6 \times 1 \times 5 \times 5 + 6 = 156$;
- 第二个卷积层的参数量为:$16 \times 6 \times 5 \times 5 + 16 = 2416$;
- 第三个卷积层的参数量为:$120 \times 16 \times 5 \times 5 + 120= 48120$;
- 第一个全连接层的参数量为:$120 \times 84 + 84= 10164$;
- 第二个全连接层的参数量为:$84 \times 10 + 10= 850$;
所以,LeNet-5总的参数量为$61706$。
在飞桨中,还可以使用paddle.summary
API自动计算参数量。
1 |
|
1 |
|
可以看到,结果与公式推导一致。
[√] 计算量
按照公式(5.19)进行计算,可以得到:
- 第一个卷积层的计算量为:$28\times 28\times 5\times 5\times 6\times 1 + 28\times 28\times 6=122304$;
- 第二个卷积层的计算量为:$10\times 10\times 5\times 5\times 16\times 6 + 10\times 10\times 16=241600$;
- 第三个卷积层的计算量为:$1\times 1\times 5\times 5\times 120\times 16 + 1\times 1\times 120=48120$;
- 平均汇聚层的计算量为:$16\times 5\times 5=400$
- 第一个全连接层的计算量为:$120 \times 84 = 10080$;
- 第二个全连接层的计算量为:$84 \times 10 = 840$;
所以,LeNet-5总的计算量为$423344$。
在飞桨中,还可以使用paddle.flops
API自动统计计算量。
1 |
|
1 |
|
可以看到,结果与公式推导一致。
[√] 5.3.3 - 模型训练
使用交叉熵损失函数,并用随机梯度下降法作为优化器来训练LeNet-5网络。
用RunnerV3在训练集上训练5个epoch,并保存准确率最高的模型作为最佳模型。
1 |
|
1 |
|
可视化观察训练集与验证集的损失变化情况。
1 |
|
[√] 5.3.4 - 模型评价
使用测试数据对在训练过程中保存的最佳模型进行评价,观察模型在测试集上的准确率以及损失变化情况。
1 |
|
1 |
|
[√] 5.3.5 - 模型预测
同样地,我们也可以使用保存好的模型,对测试集中的某一个数据进行模型预测,观察模型效果。
1 |
|
[√] 5.4 - 基于残差网络的手写体数字识别实验
alec:
- 残差网络是给非线性层增加直连边
- 目的地为了缓解网络层数很深的时候,梯度消失问题
- 残差单元在一个或多个神经层f()的输入和输出之间加上一个直连边。
- 在残差网络中,将目标函数$h(x)$拆为了两个部分:恒等函数$x$和残差函数$h(x)-x$,其中恒等函数直接将输入送到输出那里,然后和残差函数加起来
- 典型的残差单元是由多个级联的卷积层和一个直连边组成。
- (网络层数加深,深层的特征图都是含有的高级特征,但是可能高层也需要底层的一些特征,因此通过残差直连边传过来,这样才能充分的利用数据去学习信息,否则高层拿到的都是前一层传过来的,信息获取不充分可能就会被蒙蔽导致数据分析、提取不正确)、
- 一个残差网络通常有很多个残差单元堆叠而成。
- ResNet18是一个非常经典的残差网络。
残差网络(Residual Network,ResNet)是在神经网络模型中给非线性层增加直连边的方式来缓解梯度消失问题,从而使训练深度神经网络变得更加容易。
在残差网络中,最基本的单位为残差单元。
假设$f(\mathbf x;\theta)$为一个或多个神经层,残差单元在$f()$的输入和输出之间加上一个直连边。
不同于传统网络结构中让网络$f(x;\theta)$去逼近一个目标函数$h(x)$,在残差网络中,将目标函数$h(x)$拆为了两个部分:恒等函数$x$和残差函数$h(x)-x$
$$
\mathrm{ResBlock}_f(\mathbf x) = f(\mathbf x;\theta) + \mathbf x,(5.22)
$$
其中$\theta$为可学习的参数。
一个典型的残差单元如图5.14所示,由多个级联的卷积层和一个跨层的直连边组成。
一个残差网络通常有很多个残差单元堆叠而成。下面我们来构建一个在计算机视觉中非常典型的残差网络:ResNet18,并重复上一节中的手写体数字识别任务。
[√] 5.4.1 - 模型构建
在本节中,我们先构建ResNet18的残差单元,然后在组建完整的网络。
[√] 5.4.1.1 - 残差单元
alec:
- 残差单元包裹的非线性层的输入和输出形状大小应该一致。如果一个卷积层的输入特征图和输出特征图的通道数不一致,则其输出与输入特征图无法直接相加。
- 如何使得残差单元的输入输出大小形状一致:可以使用1×1大小的卷积将输入特征图的通道数映射为与级联卷积输出特征图的一致通道数。
- 1$\times$1 卷积的作用:
- 不考虑输入数据局部信息之间的关系
- 实现信息的跨通道整合
- 实现数据的通道数降维或升维
这里,我们实现一个算子ResBlock
来构建残差单元,其中定义了use_residual
参数,用于在后续实验中控制是否使用残差连接。
残差单元包裹的非线性层的输入和输出形状大小应该一致。如果一个卷积层的输入特征图和输出特征图的通道数不一致,则其输出与输入特征图无法直接相加。为了解决上述问题,我们可以使用$1 \times 1$大小的卷积将输入特征图的通道数映射为与级联卷积输出特征图的一致通道数。
$1 \times 1$卷积:与标准卷积完全一样,唯一的特殊点在于卷积核的尺寸是$1 \times 1$,也就是不去考虑输入数据局部信息之间的关系,而把关注点放在不同通道间。通过使用$1 \times 1$卷积,可以起到如下作用:
- 实现信息的跨通道交互与整合。考虑到卷积运算的输入输出都是3个维度(宽、高、多通道),所以$1 \times 1$卷积实际上就是对每个像素点,在不同的通道上进行线性组合,从而整合不同通道的信息;
- 对卷积核通道数进行降维和升维,减少参数量。经过$1 \times 1$卷积后的输出保留了输入数据的原有平面结构,通过调控通道数,从而完成升维或降维的作用;
- 利用$1 \times 1$卷积后的非线性激活函数,在保持特征图尺寸不变的前提下,大幅增加非线性。
alec:
- 在残差单元中,如果残差函数的输出和输入形状不一致,则通过1x1卷积,改变直连边输入数据的形状,使得残差函数的输出和输入的形状一致,然后才能相加。注意使用1x1改变的是输入在直连边的形状,不是改变的残差函数的输出的形状。
- 残差块中每个卷积层的后面,接一个批量规范化层
1 |
|
alec:
残差单元的基本结构:
Y = A + B,A是残差函数的输出,B是直连边的输出
A’ = 输入 -> 等宽卷积 -> 批量规范化 -> 激活函数 -> 等宽卷积 -> 批量规范化
B’ = 输入 (-> 1x1卷积调整形状 -> 批量规范化)
Y = (A’ + B’)-> 激活函数
[√] 5.4.1.2 - 残差网络的整体结构
alec:
残差网络就是将多个残差单元串联起来的深网络。
残差网络就是将很多个残差单元串联起来构成的一个非常深的网络。ResNet18 的网络结构如图5.16所示。
其中为了便于理解,可以将ResNet18网络划分为6个模块:
- 第一模块:包含了一个步长为2,大小为$7 \times 7$的卷积层,卷积层的输出通道数为64,卷积层的输出经过批量归一化、ReLU激活函数的处理后,接了一个步长为2的$3 \times 3$的最大汇聚层;
- 第二模块:包含了两个残差单元,经过运算后,输出通道数为64,特征图的尺寸保持不变;
- 第三模块:包含了两个残差单元,经过运算后,输出通道数为128,特征图的尺寸缩小一半;
- 第四模块:包含了两个残差单元,经过运算后,输出通道数为256,特征图的尺寸缩小一半;
- 第五模块:包含了两个残差单元,经过运算后,输出通道数为512,特征图的尺寸缩小一半;
- 第六模块:包含了一个全局平均汇聚层,将特征图变为$1 \times 1$的大小,最终经过全连接层计算出最后的输出。
ResNet18模型的代码实现如下:
定义模块1:
1 |
|
定义模块二到模块五。
1 |
|
封装模块二到模块五。
1 |
|
定义完整网络。
1 |
|
这里同样可以使用paddle.summary
统计模型的参数量。
1 |
|
1 |
|
使用paddle.flops
统计模型的计算量。
1 |
|
1 |
|
==验证残差连接对卷积神经网络的促进作用:==
为了验证残差连接对深层卷积神经网络的训练可以起到促进作用,接下来先使用ResNet18(use_residual设置为False)进行手写数字识别实验,再添加残差连接(use_residual设置为True),观察实验对比效果。
alec:
resnet18,包含输入1个卷积层、8个残差单元(16个卷积层)、1个全连接层,合计18层
[√] 5.4.2 - 没有残差连接的ResNet18
为了验证残差连接的效果,先使用没有残差连接的ResNet18进行实验。
[√] 5.4.2.1 - 模型训练
使用训练集和验证集进行模型训练,共训练5个epoch。在实验中,保存准确率最高的模型作为最佳模型。代码实现如下
1 |
|
1 |
|
[√] 5.4.2.2 - 模型评价
使用测试数据对在训练过程中保存的最佳模型进行评价,观察模型在测试集上的准确率以及损失情况。代码实现如下
1 |
|
1 |
|
从输出结果看,对比LeNet-5模型评价实验结果,准确率下降、损失升高。得出结论,网络层级加深后,训练效果不升反降。
lenet5的评价分数:
[Test] accuracy/loss: 0.8600/0.4435
[√] 5.4.3 - 带残差连接的ResNet18
[√] 5.4.3.1 - 模型训练
使用带残差连接的ResNet18重复上面的实验,代码实现如下:
1 |
|
1 |
|
[√] 5.4.3.2 - 模型评价
使用测试数据对在训练过程中保存的最佳模型进行评价,观察模型在测试集上的准确率以及损失情况。
1 |
|
1 |
|
对比lenet5和不加残差连接的resnet18,模型的准确率和损失都有提升。
[√] 5.4.4 - 与高层API实现版本的对比实验
对于Reset18这种比较经典的图像分类网络,飞桨高层API中都为大家提供了实现好的版本,大家可以不再从头开始实现。这里为高层API版本的resnet18模型和自定义的resnet18模型赋予相同的权重,并使用相同的输入数据,观察输出结果是否一致。
1 |
|
1 |
|
可以看到,高层API版本的resnet18模型和自定义的resnet18模型输出结果是一致的,也就说明两个模型的实现完全一样。