(一)
1.卷积神经网络(CNN)简介
直接贴出这张CNN最经典的图。从图中也可以出,其实CNN和传统的深度神经网络相差不多,唯一的区别就在于神经网络中相邻两层的连接方式。那么,问题来了,为什么CNN要选择不一样的连接方式呢(神经网络中使用的为全连接方式,即相邻两层之间所有神经元都有连接)???
首先是数据数量级问题,我们知道在处理图像是输入为所选图像的像素矩阵,这个数据量是非常大的。而若使用全连接网络不难想象我们需要学习的参数会有多爆炸,直接导致计算速度的减慢。另外,参数增多还很容易导致overfitting问题。所以需要一个更为合理的神经网络结构来有效地减少神经网络中参数个数。
接下来就具体地介绍CNN的模型结构。
2.CNN的基本结构
前一节的图对于结构的描述可能没那么明显,这里就另外找了一副更加明了的示意图如下:
(1)输入层(input layer)
图中是一个图形识别的CNN模型。可以看出最左边的船的图像就是我们的输入层,计算机理解为输入若干个矩阵,这点和DNN基本相同。
(2)卷积层(Convolution Layer)
这个是CNN特有的,卷积层中每一个结点的输入只是上一层神经网络的一小块,这个小块常用大小有3x3和5x5.一般来说,通过卷积层处理过的节点会使得矩阵变的更深。卷积层的激活函数使用的是ReLU。我们在DNN中介绍过ReLU的激活函数,它其实很简单,就是ReLU(x)=max(0,x)。我们后面专门来讲。
(3)池化层(pooling layer)
在卷积层后面是池化层(Pooling layer),这个也是CNN特有的,我们后面也会专门来讲。需要注意的是,池化层没有激活函数。他不会改变三维矩阵的深度,但是可以缩小矩阵的大小,从而达到减少整个网络中参数的目的。
卷积层+池化层的组合可以在隐藏层出现很多次,上图中出现两次。而实际上这个次数是根据模型的需要而来的。当然我们也可以灵活使用使用卷积层+卷积层,或者卷积层+卷积层+池化层的组合,这些在构建模型的时候没有限制。但是最常见的CNN都是若干卷积层+池化层的组合,如上图中的CNN结构。
(4)全连接层(Fully Connected Layer)& Softmax层
在若干卷积层+池化层后面是全连接层(Fully Connected Layer, 简称FC),全连接层其实就是我们讲的DNN结构,只是输出层使用了Softmax激活函数来做图像识别的分类,这点和DNN中也一样。
s
(
i
,
j
)
=
(
X
∗
W
)
(
i
,
j
)
=
∑
m
∑
n
x
(
i
−
m
,
j
−
n
)
w
(
m
,
n
)
s(i,j)=(X*W)(i,j)=\sum_{m}\sum_{n}x(i-m,j-n)w(m,n)
s(i,j)=(X∗W)(i,j)=∑m∑nx(i−m,j−n)w(m,n)
3.数学上的卷积
首先,我们去学习卷积层的模型原理,在学习卷积层的模型原理前,我们需要了解什么是卷积,以及CNN中的卷积是什么样子的。
微积分中卷积的表达式为: S ( t ) = ∫ x ( t − a ) w ( a ) d a S(t)=\int x(t-a)w(a)da S(t)=∫x(t−a)w(a)da
其离散形式为: s ( t ) = ∑ a x ( t − a ) w ( a ) s(t)=\sum_{a}x(t-a)w(a) s(t)=∑ax(t−a)w(a)
矩阵形式为(其中*表示卷积操作):
s ( t ) = ( X ∗ W ) ( t ) s(t)=(X*W)(t) s(t)=(X∗W)(t)
如果是二维的卷积,则表达式为: s ( i , j ) = ( X ∗ W ) ( i , j ) = ∑ m ∑ n x ( i − m , j − n ) w ( m , n ) s(i,j)=(X*W)(i,j)=\sum_{m}\sum_{n}x(i-m,j-n)w(m,n) s(i,j)=(X∗W)(i,j)=∑m∑nx(i−m,j−n)w(m,n)
在CNN中,虽然我们也是说卷积,但是我们的卷积公式和严格意义数学中的定义稍有不同,比如对于二维的卷积,定义为:
s ( i , j ) = ( X ∗ W ) ( i , j ) = ∑ m ∑ n x ( i + m , j + n ) w ( m , n ) s(i,j)=(X*W)(i,j)=\sum_{m}\sum_{n}x(i+m,j+n)w(m,n) s(i,j)=(X∗W)(i,j)=∑m∑nx(i+m,j+n)w(m,n)
这个式子虽然从数学上讲不是严格意义上的卷积,但是大牛们都这么叫了,那么我们也跟着这么叫了。后面讲的CNN的卷积都是指的上面的最后一个式子
其中,我们叫W为我们的卷积核,而X则为我们的输入。如果X是一个二维输入的矩阵,而W也是一个二维的矩阵。但是如果X是多维张量,那么W也是一个多维的张量。
4.CNN中的卷积
有了卷积的基本知识,我们现在来看看CNN中的卷积,假如是对图像卷积,回想我们的上一节的卷积公式,其实就是对输入的图像的不同局部的矩阵和卷积核矩阵各个位置的元素相乘,然后相加得到。
举个例子如下,图中的输入是一个二维的3x4的矩阵,而卷积核是一个2x2的矩阵。这里我们假设卷积是一次移动一个像素来卷积的,那么首先我们对输入的左上角2x2局部和卷积核卷积,即各个位置的元素相乘再相加,得到的输出矩阵S的S00的元素,值为aw+bx+ey+fz。接着我们将输入的局部向右平移一个像素,现在是(b,c,f,g)四个元素构成的矩阵和卷积核来卷积,这样我们得到了输出矩阵S的S01的元素,同样的方法,我们可以得到输出矩阵S的S02,S10,S11,S12的元素。
最终我们得到卷积输出的矩阵为一个2x3的矩阵S。
再举一个动态例子,我们有下面这个绿色的5x5输入矩阵,卷积核是一个下面这个黄色的3x3的矩阵。卷积的步幅是一个像素,过程如下:
前面几个例子都是简单的二维矩阵,卷积的过程比较简单,那么如果输入是多维的呢?
再举个例子:多维卷积操作https://cs231n.github.io/assets/conv-demo/index.html
大家打开这个例子可以看到,这里面输入是3个7x7的矩阵。实际上原输入是3个5x5的矩阵。只是在原来的输入周围加上了1的padding,即将周围都填充一圈的0,变成了3个7x7的矩阵。例子里面使用了两个卷积核,我们先关注于卷积核W0。和上面的例子相比,由于输入是3个7x7的矩阵,或者说是7x7x3的张量,则我们对应的卷积核W0也必须最后一维是3的张量,这里卷积核W0的单个子矩阵维度为3x3。那么卷积核W0实际上是一个3x3x3的张量。同时和上面的例子比,这里的步幅为2,也就是每次卷积后会移动2个像素的位置。最终的卷积过程和上面的2维矩阵类似,上面是矩阵的卷积,即两个矩阵对应位置的元素相乘后相加。这里是张量的卷积,即两个张量的3个子矩阵卷积后,再把卷积的结果相加后再加上偏置b。7x7x3的张量和3x3x3的卷积核张量W0卷积的结果是一个3x3的矩阵。由于我们有两个卷积核W0和W1,因此最后卷积的结果是两个3x3的矩阵。或者说卷积的结果是一个3x3x2的张量。
对于卷积后的输出,一般会通过ReLU激活函数,将输出的张量中的小于0的位置对应的元素值都变为0。
经过卷积层输出矩阵尺寸?
参考斯坦福cs231n课程sildes可以看出,其中N为输入矩阵尺寸,F为过滤器矩阵尺寸,stride为过滤器在输入矩阵上的滑动间隔。
经过上述操作的输出矩阵尺寸会有所减小,如果想要保持与原矩阵相同的尺寸,可以采用一种叫padding的操作。
总结一下
5.CNN中的池化层
相比卷积层的复杂,池化层则要简单的多,所谓的池化,个人理解就是对输入张量的各个子矩阵进行压缩。假如是2x2的池化,那么就将子矩阵的每2x2个元素变成一个元素,如果是3x3的池化,那么就将子矩阵的每3x3个元素变成一个元素,这样输入矩阵的维度就变小了。所以池化层可以非常有效地缩小矩阵的尺寸,从而减少最后全连接层中的参数,可以加快计算速度的同时也有防止过拟合的作用。
要想将输入子矩阵的每nxn个元素变成一个元素,那么需要一个池化标准。常见的池化标准有2个,MAX或者是Average。即取对应区域的最大值或者平均值作为池化后的元素值。
下面这个例子采用取最大值的池化方法。同时采用的是2x2的池化。步幅为2。
首先对红色2x2区域进行池化,由于此2x2区域的最大值为6.那么对应的池化输出位置的值为6,由于步幅为2,此时移动到绿色的位置去进行池化,输出的最大值为8.同样的方法,可以得到黄色区域和蓝色区域的输出值。最终,我们的输入4x4的矩阵在池化后变成了2x2的矩阵。进行了压缩。
7. CNN中的转置卷积
参考https://arxiv.org/pdf/1603.07285.pdf
及其相关github:https://github.com/vdumoulin/conv_arithmetic/blob/master/README.md
8. Pytorch中卷积相关API
深度网络中卷积相关层主要有卷积层(Conv)、池化层(Pool)等,这些层在实际使用中可分为一维、二维和三维,池化方式可以分为平均池化、最大值池化、自适应池化等。卷积层除了常用的前向卷积外,还有逆卷积(TransposeConv)。
卷积层(Conv)
CLASS
torch.nn.``Conv2d
(in_channels, out_channels, kernel_size, stride=1, padding=0, dilation=1, groups=1, bias=True)
关于一维和二维卷积的区别:
- Conv1d的卷积核只是在一个方向(维度)上移动, 而Conv2d的卷积核在两个维度上移动
(二)
在上一篇【Deep learning】卷积神经网络CNN结构中我们简单地介绍了CNN的结构。接下来我们看看这种结构的CNN模型是怎么运行的,包括CNN的前向传播和反向传播算法。
1.CNN前向传播算法
(1)输入层前向传播到卷积层
输入层的前向传播是CNN前向传播算法的第一步。一般输入层对应的都是卷积层,因此我们标题是输入层前向传播到卷积层。
我们这里还是以图像识别为例。先考虑最简单的,样本都是二维的黑白图片。这样输入层X就是一个矩阵,矩阵的值等于图片的各个像素位置的值。这时和卷积层相连的卷积核W就也是矩阵。如果样本都是有RGB的彩色图片,这样输入X就是3个矩阵,即分别对应R,G和B的矩阵,或者说是一个张量。这时和卷积层相连的卷积核W就也是张量,对应的最后一维的维度为3.即每个卷积核都是3个子矩阵组成。同样的方法,对于3D的彩色图片之类的样本,我们的输入X可以是4维,5维的张量,那么对应的卷积核W也是个高维的张量。不管维度多高,对于我们的输入,前向传播的过程可以表示为:
a
2
=
σ
(
z
2
)
=
σ
(
a
1
∗
W
2
+
b
2
)
a^2=\sigma\left(z^2\right)=\sigma\left(a^1*W^2+b^2\right)
a2=σ(z2)=σ(a1∗W2+b2)
其中,上标代表层数,星号代表卷积,而b代表我们的偏倚, σ为激活函数,这里一般都是ReLU。
和DNN的前向传播比较一下,其实形式非常的像,只是我们这儿是张量的卷积,而不是矩阵的乘法。同时由于W是张量,那么同样的位置,W参数的个数就比DNN多很多了。为了简化我们的描述,本文后面如果没有特殊说明,我们都默认输入是3维的张量,即用RBG可以表示的彩色图片
这里需要我们自己定义的CNN模型参数是:
1) 一般我们的卷积核不止一个,比如有K个,那么我们输入层的输出,或者说第二层卷积层的对应的输入就K个。
2) 卷积核中每个子矩阵的的大小,一般我们都用子矩阵为方阵的卷积核,比如FxF的子矩阵。
3) 填充padding(以下简称P),我们卷积的时候,为了可以更好的识别边缘,一般都会在输入矩阵在周围加上若干圈的0再进行卷积,加多少圈则P为多少。
4) 步幅stride(以下简称S),即在卷积过程中每次移动的像素距离大小。
(2)隐藏层前向传播到卷积层
现在我们再来看普通隐藏层前向传播到卷积层时的前向传播算法。
假设隐藏层的输出是M个矩阵对应的三维张量,则输出到卷积层的卷积核也是M个子矩阵对应的三维张量。这时表达式和输入层的很像,也是:
a l = σ ( z l ) = σ ( a l − 1 ∗ W l + b l ) a^l=\sigma\left(z^l\right)=\sigma\left(a^{l-1}*W^l+b^l\right) al=σ(zl)=σ(al−1∗Wl+bl)
和上一节唯一的区别仅仅在于,这里的输入是隐藏层来的,而不是我们输入的原始图片样本形成的矩阵。
需要我们定义的CNN模型参数也和上一节一样,这里我们需要定义卷积核的个数K,卷积核子矩阵的维度F,填充大小P以及步幅S
(3)隐藏层前向传播到池化层
池化层的处理逻辑是比较简单的,我们的目的就是对输入的矩阵进行缩小概括。比如输入的若干矩阵是NxN维的,而我们的池化大小是kxk的区域,则输出的矩阵都是N/k × N/k维的。
这里需要需要我们定义的CNN模型参数是:
1)池化区域的大小k
2)池化的标准,一般是MAX或者Average。
(4)隐藏层前向传播到FC层
由于全连接层就是普通的DNN模型结构,因此我们可以直接使用DNN的前向传播算法逻辑,即:
a l = σ ( z l ) = σ ( W l a l − 1 + b l ) a^l=\sigma\left(z^l\right)=\sigma\left(W^la^{l-1}+b^l\right) al=σ(zl)=σ(Wlal−1+bl)
这里的激活函数一般是sigmoid或者tanh。经过了若干全连接层之后,最后的一层为Softmax输出层。此时输出层和普通的全连接层唯一的区别是,激活函数是softmax函数。
这里需要需要我们定义的CNN模型参数是:
1)全连接层的激活函数
2)全连接层各层神经元的个数
CNN前向传播算法小结:
2. CNN反向传播算法
对于CNN反向传播,首先要理解DNN反向传播算法。举个栗子:
现在我们把同样的思想用到CNN中,很明显,CNN有些不同的地方,不能直接去套用DNN的反向传播算法的公式。
要套用DNN的反向传播算法到CNN,有几个问题需要解决:
1)池化层没有激活函数,没有需要学习的参数。这个问题倒比较好解决,我们可以令池化层的激活函数为σ(z)=z,即激活后就是自己本身。这样池化层激活函数的导数为1.
2)池化层在前向传播的时候,对输入进行了压缩,那么我们现在需要向前反向推导δl−1,这个推导方法和DNN完全不同。
3) 卷积层是通过张量卷积,或者说若干个矩阵卷积求和而得的当前层的输出,这和DNN很不相同,DNN的全连接层是直接进行矩阵乘法得到当前层的输出。这样在卷积层反向传播的时候,上一层的δl−1递推计算方法肯定有所不同。
4)对于卷积层,由于W使用的运算是卷积,那么从δl推导出该层的所有卷积核的W,b的方式也不同。
从上面可以看出,问题1比较好解决,但是问题2,3,4就需要好好的动一番脑筋了,而问题2,3,4也是解决CNN反向传播算法的关键所在。另外大家要注意到的是,DNN中的al,zl都只是一个向量,而我们CNN中的al,zl都是一个张量,这个张量是三维的,即由若干个输入的子矩阵组成。
下面我们就针对问题2,3,4来一步步研究CNN的反向传播算法。
在研究过程中,需要注意的是,由于卷积层可以有多个卷积核,各个卷积核的处理方法是完全相同且独立的,为了简化算法公式的复杂度,我们下面提到卷积核都是卷积层中若干卷积核中的一个。
(1)已知池化层的 δ l \boldsymbol{\delta}^{l} δl,推导上一隐藏层的 δ l − 1 \boldsymbol{\delta}^{l-1} δl−1
在前向传播算法时,池化层一般我们会用MAX或者Average对输入进行池化,池化的区域大小已知。现在我们反过来,要从缩小后的误差 δ l \boldsymbol{\delta}^{l} δl,还原前一次较大区域对应的误差。
在反向传播时,我们首先会把 δ l \boldsymbol{\delta}^{l} δl的所有子矩阵矩阵大小还原成池化之前的大小,然后如果是MAX,则把 δ l \boldsymbol{\delta}^{l} δl的所有子矩阵的各个池化局域的值放在之前做前向传播算法得到最大值的位置。如果是Average,则把 δ l \boldsymbol{\delta}^{l} δl的所有子矩阵的各个池化局域的值取平均后放在还原后的子矩阵位置。这个过程一般叫做upsample。
用一个例子可以很方便的表示:假设我们的池化区域大小是2x2。
δ k l = ( 2 8 4 6 ) \delta_k^l=\begin{pmatrix}2&8\\4&6\end{pmatrix} δkl=(2486)
由于池化区域为2x2,我们先将δlk做还原,即变成: ( 0 0 0 0 0 2 8 0 0 4 6 0 0 0 0 0 ) \begin{pmatrix}0&0&0&0\\0&2&8&0\\0&4&6&0\\0&0&0&0\end{pmatrix} 0000024008600000
如果是MAX,假设我们之前在前向传播时记录的最大值位置分别是左上,右下,右上,左下,则转换后的矩阵为: ( 2 0 0 0 0 0 0 8 0 4 0 0 0 0 6 0 ) \begin{pmatrix}2&0&0&0\\0&0&0&8\\0&4&0&0\\0&0&6&0\end{pmatrix} 2000004000060800
如果是Average,则进行平均:转换后的矩阵为: ( 0.5 0.5 2 2 0.5 0.5 2 2 1 1 1.5 1.5 1 1 1.5 1.5 ) \left(\begin{array}{cccc}0.5&0.5&2&2\\0.5&0.5&2&2\\1&1&1.5&1.5\\1&1&1.5&1.5\end{array}\right) 0.50.5110.50.511221.51.5221.51.5
这样我们就得到了上一层 ∂ J ( W , b ) ∂ a k l − 1 \frac{\partial J(W,b)}{\partial a{k}^{l-1}} ∂akl−1∂J(W,b)的值,要得到上一层的误差:
δ k l − 1 = ∂ J ( W , b ) ∂ a k l − 1 ∂ a k l − 1 ∂ z k l − 1 = u p s a m p l e ( δ k l ) ⊙ σ ′ ( z k l − 1 ) \delta_k^{l-1}=\frac{\partial J(W,b)}{\partial a_k^{l-1}}\frac{\partial a_k^{l-1}}{\partial z_k^{l-1}}=upsample\left(\delta_k^l\right)\odot\sigma^{\prime}\left(z_k^{l-1}\right) δkl−1=∂akl−1∂J(W,b)∂zkl−1∂akl−1=upsample(δkl)⊙σ′(zkl−1)
其中upsample函数表示池化层的误差传递,后面的求导是池化层前一层的误差传递
总结一下,对于 δ l \boldsymbol{\delta}^{l} δl有: δ l − 1 = u p s a m p l e ( δ l ) ⊙ σ ′ ( z l − 1 ) \delta^{l-1}=upsample\left(\delta^l\right)\odot\sigma^{\prime}\left(z^{l-1}\right) δl−1=upsample(δl)⊙σ′(zl−1)
me}\left(z_k^{l-1}\right)$
其中upsample函数表示池化层的误差传递,后面的求导是池化层前一层的误差传递
总结一下,对于 δ l \boldsymbol{\delta}^{l} δl有: δ l − 1 = u p s a m p l e ( δ l ) ⊙ σ ′ ( z l − 1 ) \delta^{l-1}=upsample\left(\delta^l\right)\odot\sigma^{\prime}\left(z^{l-1}\right) δl−1=upsample(δl)⊙σ′(zl−1)
[外链图片转存中…(img-5eXroajy-1693484505339)]