神经网络,从大学时候就知道,后面上课的时候老师也讲过,但是感觉从来没有真正掌握,总是似是而非,比较模糊,好像懂,其实并不懂。

在开始推导之前,需要先做一些准备工作,推导中所使用的神经网络如上图所示。一个神经网络由多个层 (layer) 构成,每一层有若干个节点 (node),最左边是输入层,中间的层被称为隐含层,最右边是输出层;上一层节点与下一层节点之间,都有边相连,代表上一层某个节点为下一层某个节点贡献的权值。通常为了使得网络能够模拟复杂的关系,上一层的节点乘以权值矩阵得到下一层节点后,需要再经过激活函数对得到的各个节点的值进行非线性化处理。激活函数可以选用很多的形式,这里使用 sigmoid 函数,表达式如下:

\[ \begin{equation} f(x)=\frac{1}{1+\exp(-x)} \end{equation}\label{eq1} \]

之所以使用 sigmoid 函数,一个很重要的原因就是 「mathematical convenience」,sigmoid 函数的导数很好计算, \[\begin{equation} f'(x)=f(x)(1-f(x)) \end{equation}\label{eq2}\]

接下来对推导中使用的符号做一个详细的说明,使推导的过程清晰易懂。我们用 \(L\) 代表网络的层数,用 \(S_l\) 代表第 \(l\) 层的节点的个数;用 \(z^{(1)}\)\(a^{(1)}\) 表示第 \(l\) 层经过激活函数前/后的节点向量 (\(z^{(l)}_i\)\(a^{(l)}_i\) 代表经过激活函数前/后节点 \(i\) 的值),根据以上的表示,\(a^{(1)}\) 代表网络的输入 \(x\), \(a^{(L)}\) 代表网络的输出 \(y\), 也就是上图中的 \(h_{W,b}(x)\);用 \(W^{(l)}\) 表示第 \(l\) 层与第 \(l+1\) 层之间的权值形成的矩阵,\(W^{(l)}_{ij}\) 代表 \(l\) 层的节点 \(j\)\(l+1\) 层的节点 \(i\) 之间的权重(注意这种表示方式),用 \(b^{(l)}\) 代表 \(l\) 层到 \(l+1\)层之间的偏置向量。

有了上面的符号化表示,神经网络各层之间的关系,可以用简洁的向量矩阵形式来表达如下:

\[\begin{align} z^{(l+1)} &= W^{(l)}a^{(l)}+b^{(l)} \label{eq3} \\ a^{(l+1)} &= f\left(z^{(l+1)}\right) \label{eq4} \end{align}\]

根据以上的式子,我们就可以计算网络中每一层各个节点的值了,上述的过程称为前向传播 (forward propagation) 过程,在深度学习库中通常都被叫做 「forward」。

通常,网络刚创建好时,我们随机初始化每两层之间的权值矩阵以及偏置向量,但是这样得到的网络,输出与实际的值差距太大。使用神经网络的目的当然是想要网络的输出与实际的值差距尽可能小,随机初始化网络,显然不能满足这个目的,但是如何调整各层之间的权值矩阵以及偏置呢,这并不是一个很简单的问题,下面要推导的反向传播 (backward propagation) 算法就是解决这个问题的利器。

正式开始推导

通常来说,如果想要得到一个较好的网络,需要有一批已知的训练数据,假设我们现在总共有\(m\)个样本,即

\[\left\{(x^{(i)}, y^{(i)})\right\}, i= 1,2,\ldots,m\]

对于每一个样本来说,我们优化的目标为 :

\[\begin{equation} J\left(W,b;x^{(i)}, y^{(i)}\right) = \min_{W,b} \frac{1}{2}\left\lVert h\left(x^{(i)}\right)- y^{(i)} \right\rVert^2 \end{equation}\label{eq5}\]

公式 \(\eqref{eq5}\) 中,为了简化,我们用 \(h(x^{(i)})\) 表示 $h_{W,b}(x^{(i)}) $

对于所有样本来说,我们需要最小化的目标函数为:

\[\begin{equation}\begin{aligned} J(W, b) &= \frac{1}{m}\sum_{i=1}^{m}J\left(W,b;x^{(i)}, y^{(i)}\right) + \frac{\lambda}{2}\sum_{l=1}^{L-1}\left\lVert W^{(l)} \right\rVert_{F}^{2}\\ &= \frac{1}{m}\sum_{i=1}^{m}\frac{1}{2}\left\lVert h(x^{(i)}) - y^{(i)}\right\rVert^2 + \frac{\lambda}{2}\sum_{l=1}^{L-1}\left\lVert W^{(l)} \right\rVert_{F}^{2} \end{aligned}\end{equation}\label{eq6}\]

上述优化目标函数 \(\eqref{eq6}\),并不简单是各个样本优化目标的和,而是由两项构成:第一项为误差项,第二项称为正则项(也称为 weight decay term,用来控制各层权值矩阵的元素大小,防止权值矩阵过大,网络出现过拟合,这和曲线拟合中对参数使用正则道理是一样的,只不过曲线拟合中,参数是向量,这里的参数是矩阵。我们使用 \(F\) 范数的平方来约束权重矩阵元素的大小,正则项前面的系数 \(\lambda\) (称为weight decay parameter)用来控制正则项与误差项之间的权重。另外,一般来说,只对权值矩阵进行正则,不对偏置进行正则。

关于F范数的一点小知识

为了计算目标函数对权重矩阵的偏导,有必要对 \(F\) 范数 (也称为 \(F\)-norm) 有所了解。假设矩阵 \(A\) 是实数矩阵,大小为 \(m\times n\),其 \(F\) 范数用公式可以表示为:

\[\begin{equation} \lVert A \rVert_F = \sqrt{\sum_{i=1}^{m}\sum_{j=1}^{n} (a_{ij})^2} \end{equation}\label{eq7}\]

另外关于矩阵 \(A\)\(F\) 范数对矩阵 \(A\) 如何求导,有如下公式: \[\begin{equation}\frac{\partial \lVert A \rVert_F^2 }{\partial A}= 2A\end{equation}\label{eq8}\]

抽丝剥茧,不断深入

我们优化网络的目标是计算各层的权值矩阵以及偏置向量,使得优化目标函数取得最小值。根据梯度下降算法,可以计算目标函数对各个参数的偏导,采用迭代方式来更新参数,最终得到最优的参数值。但是事实上,上述函数是非凸函数,梯度下降算法并不一定能够得到 global optimum,一般只能得到 local optimum。实际中得到的结果一般都是比较接近最优结果,在可以接受的范围之内,所以才使用梯度下降算法来优化神经网络。另外实际优化过程中,还有一些技巧,譬如加入 momentum 项,使得目标函数能够跳出 local optimum 点,从而得到 global optimum。在实际的深度学习训练中,一般都会使用 momentum 来加快收敛的速度,这里仅讨论最基本情况,对增加 momentum 项的情况不予讨论。

如果已经求得目标函数对各个函数的偏导数,那么各个参数的更新公式如下:

\[ \begin{align} W_{ij}^{(l)} &= W_{ij}^{(l)} - \alpha \frac{\partial J(W,b)}{\partial W_{ij}^{(l)}} \label{eq9} \\ b_i^{(l)} &= b_i^{(l)} - \alpha \frac{\partial J(W, b)}{\partial b_i^{(l)}} \label{10} \end{align}\]

以上的式子中,\(\alpha\) 称为学习率 (learning rate),用来控制权重和偏置项的更新幅度,如果 \(\alpha\) 太大,网络的参数收敛速度快,但是可能出现来回震荡的情况,甚至不能收敛;如果 \(\alpha\) 太小,网络收敛速度太慢,训练时间长。需要说明,权重矩阵以及偏置向量的学习率可以不一样,根据需要分别设置,实际上,Caffe 就是这么做的,可以在 prototxt 里面指定每层的权重以及偏置的学习率,其他的深度学习框架也可以类似设置。

从上面的公式可以看出,现在的关键是,如何计算目标函数对权重矩阵以及偏置项各个元素的偏导,根据目标函数 \(J(W, b)\) 的计算公式 \(\eqref{eq6}\),可以得到目标函数 \(J(W, b)\) 对权重矩阵以及偏置项各元素的导数:

\[\begin{align} \frac{\partial J(W, b) }{\partial W_{ij}^{(l)}} &= \left[\frac{1}{m}\sum_{i=1}^{m}\frac{\partial J\left(W, b; x^{(i)}, y^{(i)}\right)}{\partial W_{ij}^{(l)}} \right] + \lambda W_{ij}^{(l)} \label{eq11}\\ \frac{\partial J(W, b) }{\partial b_i^{(l)}} &= \frac{1}{m}\sum_{i=1}^{m}\frac{\partial J\left(W, b; x^{(i)}, y^{(i)}\right)}{\partial b_i^{(l)}} \label{eq12} \end{align}\]

上面两个公式中,公式 \(\eqref{eq11}\) 后半部分可以参考前面对于矩阵范数求导的公式 \(\eqref{eq8}\) 得到。

观察以上公式,接下来的问题就是,如何求取样本目标函数对于权重矩阵以及偏置向量的偏导,也就是如何求 \(\frac{\partial}{\partial W_{ij}^{(l)}}J\left(W,b;x^{(i)},y^{(i)}\right)\) 以及 \(\frac{\partial}{\partial b_{i}^{(l)}}J\left(W,b;x^{(i)},y^{(i)}\right)\)?如果得到每个样本的目标函数对于各个参数的偏导,整个问题就解决了。

这就要用到我们前面所说的 back-propagation 的思想了,当我们把一个样本输入到网络,通过前向传播,得到最终的输出,最终输出与实际的值之间有误差,然后我们通过某种有组织有规律的方式把误差一层一层向前传播,得到误差相对于每一层参数的偏导,这是求解该问题的核心思想。

为了便于推导,再引入变量 \(\delta_{i}^{(l)}=\frac{\partial}{\partial z_{i}^{(l)}}J\left(W,b;x^{(i)},y^{(i)}\right)\), 即最终的误差对每一层节点经过激活函数前的变量的偏导,用它来衡量某一层某个节点对最终误差的贡献量。

计算辅助变量的值

对于最后一层(第 \(L\) 层),我们可以很方便的计算 \(\delta_i^{(L)}\),详细推导如下:

\[\begin{equation}\begin{aligned} \delta_{i}^{(L)}&=\frac{\partial}{\partial z_{i}^{(L)}}\left(\frac{1}{2}\left\lVert y - h(x) \right\rVert^2\right) \\ &= \frac{\partial}{\partial a_{i}^{(L)}}\left(\frac{1}{2}\left\lVert y - h(x) \right\rVert^2\right)\cdot \frac{\partial a_{i}^{(L)}}{\partial z_{i}^{(L)}}\\ &=\frac{1}{2}\left[ \frac{\partial}{\partial a_{i}^{(L)}}\sum_{j=1}^{S_L} \left(y_j - a_j^{(L)}\right)^2 \right] \cdot \frac{\partial a_{i}^{(L)}}{\partial z_{i}^{(L)}}\\ &=-\left(y_{i}-a_{i}^{(L)}\right)f'\left(z_{i}^{(L)}\right) \end{aligned}\end{equation}\label{eq13}\]

上述公式 \(\eqref{eq13}\) 中,\(f'\left(z_{i}^{(L)}\right)=a^{(L)}_{i}\left(1-a^{(L)}_{i}\right)\) ,对于其他层也是如此计算,不再赘述。 对于其他层(\(l=L-1,L-2,\cdots,2\))的辅助变量,计算就不那么容易了,因为输出误差并不直接和这些层的节点相关,所以我们需要构造关系,利用微积分里面的链式法则 (chain rule),具体计算过程如下:

\[\begin{equation}\begin{aligned} \delta_{i}^{(l)}&=\frac{\partial}{\partial z_{i}^{(l)}}J(W,b;x,y) \\ &=\sum_{j=1}^{s_{l+1}}\frac{\partial J}{\partial z_{j}^{(l+1)}}\cdot \frac{\partial z_{j}^{(l+1)}}{\partial z_{i}^{(l)}}\\ &=\sum_{j=1}^{s_{l+1}}\delta_{j}^{(l+1)}\cdot W_{ji}^{(l)}f'\left(z_{i}^{(l)}\right)\\ &=\left(\sum_{j=1}^{s_{l+1}} W_{ji}^{(l)}\delta_{j}^{(l+1)}\right)\cdot f'\left(z_{i}^{(l)}\right) \end{aligned}\end{equation}\label{eq14}\]

为了便于书写,上面公式\(\eqref{eq14}\)中,\(J(W,b;x,y)=J\)。求误差对 \(l\) 层某个节点的偏导,无法直接求解,因为误差只和最后一层的节点有直接关系,但是如果我们已经知道了误差相对于下一层 (也就是 \(l+1\) 层) 节点的偏导,而下一层节点和本层 (\(l\) 层)直接相关,那么整个链条就可以打通了。关于如何由第一行得到第二行,我起初并没有正确得到,后来结合网上给的参考结果,逐渐想通如何计算。微积分中有针对多个中间变量的链式法则,其思想为:如果目标变量 (dependent varialbe)是三个中间变量 (intermediate variable) \(P,Q,R\) 的函数,而这三个中间变量又是自变量 (independent variable) \(x\) 的函数,那么很容易证明下面的式子成立,

\[\begin{equation} \frac{\partial \, obj}{\partial x}=\frac{\partial \, obj}{\partial P}\cdot \frac{\partial P}{\partial x}+\frac{\partial \, obj}{\partial Q}\cdot \frac{\partial Q}{\partial x}+\frac{\partial \, obj}{\partial R}\cdot \frac{\partial R}{\partial x}\end{equation}\label{eq15}\]

上述公式 \(\eqref{eq14}\) 中第二行的求和符号就是这么来的,起初推导时少了求和符号,只求了误差相对于下一层节点 \(i\) 的偏导,没有意识到下一层的每个节点其实与上一层的每个节点都有关系, 利用链式法则,我们就可以很容易求得误差相对于本层的偏导,这就是误差反传的思想。

根据我们前面的定义,上面的公式 \(\eqref{eq14}\),第二行求和符号里面的第一项 \(\frac{\partial J}{\partial z_{j}^{(l+1)}}\),等于 \(\delta_{j}^{(l+1)}\)。第二项如何显式表达出来呢?结合公式 \(\eqref{eq3}\)\(\eqref{eq4}\),具体如下:

\[\begin{equation}\begin{aligned} z_{j}^{(l+1)}&= \left [\sum_{i=1}^{s_l} W_{ji}^{(l)}\cdot a_{i}^{(l)} \right]+b_{j}^{(l)} \\ &=\left [\sum_{i=1}^{s_l} W_{ji}^{(l)}\cdot f\left(z_i^{(l)}\right)\right]+b_{j}^{(l)} \end{aligned}\end{equation}\label{eq16}\]

有了公式 \(\eqref{eq16}\),那么公式 \(\eqref{eq14}\) 第二行第二项偏导就很容易得到了:

\[\begin{equation}\frac{\partial z_{j}^{(l+1)}}{\partial z_{i}^{(l)}}=W_{ji}^{(l)}f'(z_{i}^{(l)})\end{equation}\]

这就是公式 \(\eqref{eq14}\) 第三行第二项的来历。

计算误差相对于矩阵元素和偏置向量元素的偏导

有了以上的铺垫,我们现在可以计算误差相对于矩阵元素以及偏置向量元素的偏导了。

\[\begin{equation}\begin{aligned} \frac{\partial J}{\partial W_{ij}^{(l)}}&=\frac{\partial J}{\partial z_{i}^{(l+1)}}\cdot\frac{\partial z_{i}^{(l+1)}}{\partial W_{ij}^{(l)}} \\ &= \delta_{i}^{(l+1)}\cdot a_{j}^{(l)} \end{aligned}\end{equation}\]

\[\begin{equation}\begin{aligned} \frac{\partial J}{\partial b_{i}^{(l)}}&=\frac{\partial J}{\partial z_{i}^{(l+1)}}\cdot\frac{\partial z_{i}^{(l+1)}}{\partial b_{i}^{(l)}}\\ &= \delta_{i}^{(l+1)}\cdot 1 \\ &= \delta_{i}^{(l+1)} \end{aligned}\end{equation}\]

向量化表示

对于输出层: \[\begin{equation}\delta^{(L)}=-\left(y-a^{(L)}\right)\cdot f'\left(z^{(L)}\right)\end{equation}\]

对于其他层 (\(l=L-1,L-2,\cdots,2\)): \[\begin{equation}\delta^{(l)}=\left[\left(W^{(l)}\right)^{T}\delta^{(l+1)}\right]\cdot f'\left(z^{(l)}\right)\end{equation}\]

权重以及偏置更新公式:

\[\begin{align}\frac{\partial J}{\partial W^{(l)}} &= \delta^{(l+1)}\left(a^{(l)}\right)^{T}\\ \frac{\partial J}{\partial b^{(l)}} &= \delta^{(l+1)}\end{align}\]

把所有公式整合在一起

现在我们可以把所有的公式结合在一起,得出最终的参数更新公式了。

  1. 初始化,对于所有层 (\(l=1,2,\cdots,L-1\)),令 \(\Delta W^{(l)}=0\), \(\Delta b^{(l)}=0\),前一项是一个矩阵,后一项是一个向量,分别代表对权重矩阵以及偏置向量的更新量。

  2. 对于一个batch的所有训练样本 (for i=1 to m)
    • 使用误差反传计算 \(\nabla_{W^{(l)}}J\left(W,b;x^{(i)},y^{(i)}\right)\)\(\nabla_{b^{(l)}}J\left(W,b;x^{(i)},y^{(i)}\right)\)
    • \(\Delta W^{(l)}:= \Delta W^{(l)}+\nabla_{W^{(l)}}J\left(W,b;x^{(i)},y^{(i)}\right)\)
    • \(\Delta b^{(l)}:= \Delta b^{(l)}+\nabla_{b^{(l)}}J\left(W,b;x^{(i)},y^{(i)}\right)\)
  3. 更新参数 \[\begin{align} W^{(l)} &= W^{(l)}-\alpha\left[\left(\frac{1}{m}\Delta W^{(l)}\right) + \lambda W^{(l)} \right] \nonumber \\ b^{(l)} &= b^{(l)}-\alpha\left[\frac{1}{m}\Delta b^{(l)}\right] \nonumber \end{align} \]

至此,误差反传以及参数更新的全部内容完成!

参考