卷积神经网络前向与反向传播

在本文中我们首先对CNN前向传播算法进行总结;接着在基于CNN前向传播算法的基础上,对CNN的反向传播算法做一个总结。在阅读本文前,建议先研究DNN的反向传播算法:这个是一位大佬的博客深度神经网络(DNN)反向传播算法(BP)

回顾CNN的结构

在上一篇里,我们已经讲到了CNN的结构,包括输出层,若干的卷积层+ReLU激活函数,若干的池化层,DNN全连接层,以及最后的用Softmax激活函数的输出层。这里我们用一个彩色的汽车样本的图像识别再从感官上回顾下CNN的结构。图中的CONV即为卷积层,POOL即为池化层,而FC即为DNN全连接层(不管是Pytorch还是TensorFlow全连接层前面必须加上flatten层,变成一维向量),包括了我们上面最后的用Softmax激活函数的输出层。

从上图可以看出,要理顺CNN的前向传播算法,重点是输入层的前向传播,卷积层的前向传播以及池化层的前向传播。而DNN全连接层和用Softmax激活函数的输出层的前向传播算法我们在讲DNN时已经讲到了。

CNN输入层前向传播到卷积层

输入层的前向传播是CNN前向传播算法的第一步。一般输入层对应的都是卷积层,因此我们标题是输入层前向传播到卷积层。

我们这里还是以图像识别为例。

先考虑最简单的,样本都是二维的黑白图片。这样输入层$X$就是一个矩阵,矩阵的值等于图片的各个像素位置的值。这时和卷积层相连的卷积核$W$就也是矩阵。

如果样本都是有RGB的彩色图片,这样输入$X$就是3个矩阵,即分别对应R,G和B的矩阵,或者说是一个张量。这时和卷积层相连的卷积核$W$就也是张量,对应的最后一维的维度为3.即每个卷积核都是3个子矩阵组成

同样的方法,对于3D的彩色图片之类的样本,我们的输入$X$可以是4维,5维的张量,那么对应的卷积核$W$也是个高维的张量。

不管维度多高,对于我们的输入,前向传播的过程可以表示为:

其中,上标代表层数,星号代表卷积,而$b$代表我们的偏置, $σ$为激活函数,这里一般都是ReLU。

和DNN的前向传播比较一下,其实形式非常的像,只是我们这儿是张量的卷积,而不是矩阵的乘法。同时由于$W$是张量,那么同样的位置,$W$参数的个数就比DNN多很多了。

为了简化我们的描述,本文后面如果没有特殊说明,我们都默认输入是3维的张量,即用RBG可以表示的彩色图片。

这里需要我们自己定义的CNN模型参数是:

1) 一般我们的卷积核不止一个,比如有$K$个,那么我们输入层的输出,或者说第二层卷积层的对应的输入就$K$个。

2) 卷积核中每个子矩阵的的大小,一般我们都用子矩阵作为方阵的卷积核,比如$F\times F$的子矩阵。

3) 填充padding(以下简称$P$),我们卷积的时候,为了可以更好的识别边缘,一般都会在输入矩阵在周围加上若干圈的0再进行卷积,加多少圈则$P$为多少。

4) 步幅stride(以下简称$S$),即在卷积过程中每次移动的像素距离大小。

这些参数我们在上一篇都有讲述。

隐藏层前向传播到卷积层

现在我们再来看普通隐藏层前向传播到卷积层时的前向传播算法。

假设隐藏层的输出是$M$个矩阵对应的三维张量,则输出到卷积层的卷积核也是$M$个子矩阵对应的三维张量。这时表达式和输入层的很像,也是

其中,上标代表层数,星号代表卷积,而$b$代表我们的偏倚, $σ$为激活函数,这里一般都是ReLU。

也可以写成M个子矩阵子矩阵卷积后对应位置相加的形式,即:

和上一节唯一的区别仅仅在于,这里的输入是隐藏层来的,而不是我们输入的原始图片样本形成的矩阵。

需要我们定义的CNN模型参数也和上一节一样,这里我们需要定义卷积核的个数$K$,卷积核子矩阵的维度$F$,填充大小$P$以及步幅$S$。

隐藏层前向传播到池化层

池化层的处理逻辑是比较简单的,我们的目的就是对输入的矩阵进行缩小概括。比如输入的若干矩阵是$N \times N$维的,而我们的池化大小是$k \times k$的区域,则输出的矩阵都是$\frac{N}{k} \times \frac{N}{k}$维的。

这里需要需要我们定义的CNN模型参数是:

1)池化区域的大小$k$

2)池化的标准,一般是MAX或者Average。

隐藏层前向传播到全连接层

由于全连接层就是普通的DNN模型结构,因此我们可以直接使用DNN的前向传播算法逻辑,即:

这里的激活函数一般是sigmoid或者tanh。

经过了若干全连接层之后,最后的一层为Softmax输出层。此时输出层和普通的全连接层唯一的区别是,激活函数是softmax函数。

这里需要需要我们定义的CNN模型参数是:

1)全连接层的激活函数

2)全连接层各层神经元的个数

CNN前向传播算法小结

有了上面的基础,我们现在总结下CNN的前向传播算法。

输入:1个图片样本,CNN模型的层数L和所有隐藏层的类型,对于卷积层,要定义卷积核的大小$K$,卷积核子矩阵的维度$F$,填充大小$P$,步幅$S$。对于池化层,要定义池化区域大小$k$和池化标准(MAX或Average),对于全连接层,要定义全连接层的激活函数(输出层除外)和各层的神经元个数。

输出:CNN模型的输出$a^L$

1) 根据输入层的填充大小P,填充原始图片的边缘,得到输入张量$a^1$。

2)初始化所有隐藏层的参数$W,b$

3)for $l$=2 to $L-1$:

  a) 如果第$l$层是卷积层,则输出为  

  b) 如果第$l$层是池化层,则输出为$ a^l= pool(a^{l-1})$, 这里的pool指按照池化区域大小$k$和池化标准将输入张量缩小的过程。

  c) 如果第$l$层是全连接层,则输出为

4)对于输出层第L层:

以上就是CNN前向传播算法的过程总结。有了CNN前向传播算法的基础,我们后面再来理解CNN的反向传播算法就简单多了。

回顾DNN的反向传播算法

我们首先回顾DNN的反向传播算法。在DNN中,我们是首先计算出输出层的$\delta^L$:

利用数学归纳法,用$\delta^{l+1}$的值一步步的向前求出第l层的$\delta^l$,表达式为:

有了$\delta^l$的表达式,从而求出$W,b$的梯度表达式:

有了$W,b$梯度表达式,就可以用梯度下降法来优化$W,b$,求出最终的所有$W,b$的值。

现在我们想把同样的思想用到CNN中,很明显,CNN有些不同的地方,不能直接去套用DNN的反向传播算法的公式。

CNN的反向传播算法思想

要套用DNN的反向传播算法到CNN,有几个问题需要解决:

1)池化层没有激活函数,这个问题倒比较好解决,我们可以令池化层的激活函数为$\sigma(z) =
z$,即激活后就是自己本身。这样池化层激活函数的导数为1.

2)池化层在前向传播的时候,对输入进行了压缩,那么我们现在需要向前反向推导$\delta^{l-1}$,这个推导方法和DNN完全不同。

3) 卷积层是通过张量卷积,或者说若干个矩阵卷积求和而得的当前层的输出,这和DNN很不相同,DNN的全连接层是直接进行矩阵乘法得到当前层的输出。这样在卷积层反向传播的时候,上一层的$\delta^{l-1}$递推计算方法肯定有所不同。

4)对于卷积层,由于$W$使用的运算是卷积,那么从$\delta^l$推导出该层的所有卷积核的$W,b$的方式也不同。

从上面可以看出,问题1比较好解决,但是问题2,3,4就需要好好的动一番脑筋了,而问题2,3,4也是解决CNN反向传播算法的关键所在。另外大家要注意到的是,DNN中的$a_l,z_l$都只是一个向量,而我们CNN中的$a_l,z_l$都是一个张量,这个张量是三维的,即由若干个输入的子矩阵组成。

下面我们就针对问题2,3,4来一步步研究CNN的反向传播算法。

在研究过程中,需要注意的是,由于卷积层可以有多个卷积核,各个卷积核的处理方法是完全相同且独立的,为了简化算法公式的复杂度,我们下面提到卷积核都是卷积层中若干卷积核中的一个

下文中的符号表:

  • $\delta_l$:损失函数$J(W,b,x,y)$对于$z^l$的偏导数
  • $z^l$:第$l$层没经过激活函数的输出
  • $a^l$:第$l$层经过激活函数后的输出

已知池化层的$\delta^l$,推导上一隐藏层的$\delta^{l-1}$

我们首先解决上面的问题2,如果已知池化层的$\delta^l$,推导出上一隐藏层的$\delta^{l-1}$。

我们先把 pooling 层也放回网络连接的形式中:

红色神经元是前一层的响应结果,一般是卷积后再用激活函数处理。绿色的神经元表示池化层。很明显,池化层主要是起到降维的作用,一般我们会用MAX或者Average对输入进行池化,池化的区域大小已知;而且,由于池化层没有参数$W$和$b$需要学习,也不存在步长的概念,因此,当得到池化层的误差项$\delta^l$后,我们只需要直接计算上一层的误差项 $\delta^{l-1}$ 即可。要注意的一点是,由于池化一般会降维,因此传回去的误差矩阵要调整维度,即要从缩小后的误差$\delta^l$,还原前一次较大区域对应的误差,我们称这个操作为即 $upsample$。这样,误差传播的公式原型大概是:

其中,$\delta^{l-1}$、$\delta^l$与$z^{l-1}$可以是张量,不一定是向量。

$upsample$操作也就是在反向传播时,我们首先会把$\delta^l$的所有子矩阵矩阵大小还原成池化之前的大小,然后如果是MAX,则把$\delta^l$的所有子矩阵的各个池化局域的值放在之前做前向传播算法得到最大值的位置。如果是Average,则把$\delta^l$的所有子矩阵的各个池化局域的值取平均后放在还原后的子矩阵位置。这个过程一般叫做upsample

用一个例子可以很方便的表示:假设我们的池化区域大小是2x2。$\delta^l$的第k个子矩阵为:

由于池化区域为2x2,我们先将$\delta_k^l$的还原到上一层的维度:,即变成:

如果是MAX,则是经过一个$max()$函数,对应的导数为:

假设我们之前在前向传播时记录的最大值位置分别是左上,右下,右上,左下,则转换后的矩阵为:

如果是Average,我们是把一个范围内的响应值取平均后,作为一个 pooling unit 的结果。可以认为是经过一个$average()$函数,即 $average(x)=\frac{1}{m}\sum_{k=1}^m x_k$。在本例中,$m=4$。则对每个 $x_k$ 的导数均为:

转换后的矩阵为:

这样我们就得到了上一层 $\frac{\partial J(W,b)}{\partial a_k^{l-1}}$的值,要得到$\delta_k^{l-1}$:

其中,upsample函数完成了池化误差矩阵放大与误差重新分配的逻辑。

我们概括下,对于张量$\delta^{l-1}$,我们有:

已知卷积层的$\delta^l$,推导上一隐藏层的$\delta^{l-1}$

对于卷积层的反向传播,我们首先回忆下卷积层的前向传播公式:

其中$n_{in}$为上一隐藏层的输入子矩阵个数。

在DNN中,我们知道$\delta^{l-1}$和$\delta^{l}$的递推关系为:

因此要推导出$\delta^{l-1}$和$\delta^{l}$的递推关系,必须计算$\frac{\partial z^{l}}{\partial z^{l-1}}$的梯度表达式。 注意到$z^{l}$和$z^{l-1}$的关系为:

因此我们有:

这里的式子其实和DNN的类似,区别在于对于含有卷积的式子求导时,卷积核被旋转了180度。即式子中的$rot180()$,翻转180度的意思是上下翻转一次,接着左右翻转一次。在DNN中这里只是矩阵的转置。那么为什么呢?由于这里都是张量,直接推演参数太多了。我们以一个简单的例子说明为啥这里求导后卷积核要翻转。

假设我们$l-1$层的输出$a^{l-1}$是一个3x3矩阵,第$l$层的卷积核$W^l$是一个2x2矩阵,采用1像素的步幅,则输出$z^{l}$是一个2x2的矩阵。我们简化$b^l$都是0,则有

我们列出$a,W,z$的矩阵表达式如下:

利用卷积的定义,很容易得出:

更进一步,我们还可以把上面的等式表示成下图:

上图的网络结构中,左边青色的神经元表示 $a_{11}$ 到 $a_{33}$,中间橙色的表示 $z_{11}$ 到 $z_{22}$。需要注意的是,青色和橙色神经元之间的权值连接用了不同的颜色标出,紫色线表示 $w_{11}$,蓝色线表示 $w_{12}$,依此类推。这样一来,如果你熟悉 BP 链式法则的套路的话,你可能已经懂了卷积层的 BP 是怎么操作的了。因为卷积层其实就是一种特殊的连接层,它是部分连接的,而且参数也是共享的。

接着我们模拟反向求导:

从上式可以看出,损失函数$J$关于$a^{l-1}$的梯度误差$\nabla a^{l-1}$,等于第$l$层的梯度误差乘以$\frac{\partial z^{l}}{\partial a^{l-1}}$,而$\frac{\partial z^{l}}{\partial a^{l-1}}$对应上面的例子中相关联的$w$的值。假设我们的$z$矩阵对应的反向传播误差是$\delta_{11}, \delta_{12}, \delta_{21}, \delta_{22}$组成的2x2矩阵,则利用上面梯度的式子和4个等式,我们可以分别写出$\nabla a^{l-1}$的9个标量的梯度。 比如对于$a_{11}$的梯度,由于在4个等式中$a_{11}$只和$z_{11}$有乘积关系,从而我们有:

对于$a_{12}$的梯度,由于在4个等式中$a_{12}$和$z_{12},z_{11}$有乘积关系,从而我们有:

同样的道理我们得到:

这上面9个式子其实可以用一个矩阵卷积的形式表示,即:

为了符合梯度计算,我们在误差矩阵周围填充了一圈0,此时我们将卷积核翻转后和反向传播的梯度误差进行卷积,就得到了前一次的梯度误差。这个例子直观的介绍了为什么对含有卷积的式子反向传播时,卷积核要翻转180度的原因。

将上式写成矩阵的形式如下:

又因为

最后一项之所以使用$\odot$,证明如下,将$\frac{\partial J}{\partial a^{l-1}}\frac{\partial a^{l-1}}{\partial z^{l-1}} $写成离散形式。

从上式可以看出,$\frac{\partial J}{\partial a^{l-1}}$与$\frac{\partial a^{l-1}}{\partial z^{l-1}}$是对应元素相乘的形式。所以得证。

以上就是卷积层的误差反向传播过程。

已知卷积层的$\delta^l$,推导该层的$W,b$的梯度

好了,我们现在已经可以递推出每一层的梯度误差$\delta^l$了,对于全连接层,可以按DNN的反向传播算法求该层$W,b$的梯度,而池化层并没有$W,b$,也不用求$W,b$的梯度。只有卷积层的$W,b$需要求出。

注意到卷积层$z$和$W,b$的关系为:

因此我们有:

注意到此时卷积核并没有反转,主要是此时是层内的求导,而不是反向传播到上一层的求导。具体过程我们可以分析一下。

和上小节一样的一个简化的例子,这里输入是矩阵,不是张量,那么对于第l层,某个个卷积核矩阵W的导数可以表示如下:

上式的证明如下:

又因为:

由于在$\frac{\partial J(W,b)}{\partial W_{pq}^{l}} $中可以将$p,q$视为常数,求$\frac{\partial z_{ij}^l}{\partial W_{pq}^l}$偏导的时候,$z_{ij}^l$中只有$ W_{pq}^l$项与之相关,所以在这种情况下,可以得到下式:

假设我们输入$a$是4x4的矩阵,卷积核$W$是3x3的矩阵,输出$z$是2x2的矩阵,那么反向传播的$z$的梯度误差$\delta$也是2x2的矩阵。

那么根据上面的式子,我们有:

最终我们可以一共得到9个式子。整理成矩阵形式后可得:

从而可以清楚的看到这次我们为什么没有反转的原因。

而对于b,则稍微有些特殊,因为$\delta^l$是三维张量,而$b$只是一个向量,不能像DNN那样直接和$\delta^l$相等。通常的做法是将$\delta^l$的各个子矩阵的项分别求和(子矩阵即一个特征图),得到一个误差向量,即为$b$的梯度:

证明过程如下,因为对于CNN而言,输入特征经过一个卷积核输出一个特征图,每一个特征图对应一个偏置,而一个batch之内的相同位置特征图的偏置是同一个偏置。因此对于上面简单例子,如果加上偏置的话,最终得到的式子如下:

假设我们$l-1$层的输出$a^{l-1}$是一个3x3矩阵,第$l$层的卷积核$W^l$是一个2x2矩阵,采用1像素的步幅,则输出$z^{l}$是一个2x2的矩阵。此时$b^l$是一个标量,则有

我们列出$a,W,z$的矩阵表达式如下:

利用卷积的定义,很容易得出:

此时,因为这个简单的例子中,只存在一个$b$的标量,$z_{11}、z_{12}、z_{21}、z_{22}$均包含的是同一个$b$,因此$\frac{\partial z}{\partial b}$可以看成是对每一个$z$分量对$b$求导在求和

所以可以得到下面的式子:

CNN反向传播算法总结

现在我们总结下CNN的反向传播算法,以最基本的批量梯度下降法为例来描述反向传播算法。

输入:m个图片样本,CNN模型的层数L和所有隐藏层的类型,对于卷积层,要定义卷积核的大小K,卷积核子矩阵的维度F,填充大小P,步幅S。对于池化层,要定义池化区域大小k和池化标准(MAX或Average),对于全连接层,要定义全连接层的激活函数(输出层除外)和各层的神经元个数。梯度迭代参数迭代步长$\alpha$,最大迭代次数MAX与停止迭代阈值$\epsilon$

输出:CNN模型各隐藏层与输出层的$W,b$

1) 初始化各隐藏层与输出层的各$W,b$的值为一个随机值。

2)for iter to 1 to MAX:

  2-1) for i =1 to m:

    a) 将CNN输入$a^1$设置为$x_i$对应的张量

    b) for $l$=2 to L-1,根据下面3种情况进行前向传播算法计算:

      b-1) 如果当前是全连接层:则有$a^{i,l} = \sigma(z^{i,l}) = \sigma(W^la^{i,l-1} + b^{l})$

      b-2) 如果当前是卷积层:则有$a^{i,l} = \sigma(z^{i,l}) = \sigma(W^l*a^{i,l-1} + b^{l})$
    
    b-3) 如果当前是池化层:则有$ a^{i,l}= pool(a^{i,l-1})$,这里的pool指按照池化区域大小k和池化标准将输入张量缩小的过程。

    c) 对于输出层第L层: $ a^{i,L}= softmax(z^{i,L}) = softmax(W^{L}a^{i,L-1} +b^{L})$

    d) 通过损失函数计算输出层的$\delta^{i,L}$

    e) for $l$= L-1 to 2, 根据下面3种情况进行进行反向传播算法计算:

      e-1) 如果当前是全连接层:$\delta^{i,l} = (W^{l+1})^T\delta^{i,l+1}\odot \sigma^{‘}(z^{i,l})$

      e-2) 如果当前是卷积层:$\delta^{i,l} = \delta^{i,l+1}*rot180(W^{l+1}) \odot
\sigma^{‘}(z^{i,l}) $

      e-3) 如果当前是池化层:$\delta^{i,l} = upsample(\delta^{i,l+1})
\odot \sigma^{‘}(z^{i,l})$

  2-2) for $l$ = 2 to L,根据下面2种情况更新第$l$层的$W^l,b^l$:

    2-2-1) 如果当前是全连接层:$W^l = W^l -\alpha \sum\limits_{i=1}^m \delta^{i,l}(a^{i,l-1})^T $, $b^l = b^l -\alpha \sum\limits_{i=1}^m \delta^{i,l}$

    2-2-2) 如果当前是卷积层,对于每一个卷积核有:$W^l = W^l -\alpha \sum\limits_{i=1}^m \delta^{i,l}*rot180(a^{i, l-1}) $, $b^l = b^l -\alpha \sum\limits_{i=1}^m \sum\limits_{u,v}(\delta^{i,l})_{u,v}$

  2-3) 如果所有$W,b$的变化值都小于停止迭代阈值$\epsilon$,则跳出迭代循环到步骤3。

3)输出各隐藏层与输出层的线性关系系数矩阵$W$和偏倚向量$b$。

参考资料:

卷积神经网络(CNN)反向传播算法
CNN的反向传播
yolo反向传播源码分析

------ 本文结束------
坚持原创技术分享,您的支持将鼓励我继续创作!

欢迎关注我的其它发布渠道