反向传播简介

简介

目标:反向传播是利用链式法则递归计算表达式的梯度的方法。理解反向传播过程及其精妙之处,对于理解、实现、设计和调试神经网络非常关键

问题陈述:这节的核心问题是:给定函数$f(x)$ ,其中$x$是输入数据的向量,需要计算函数$f$关于$x$的梯度,也就是$\nabla f(x)$。

目标:之所以关注上述问题,是因为在神经网络中$f$对应的是损失函数$(L)$,输入$x$里面包含训练数据和神经网络的权重。举个例子,损失函数可以是SVM的损失函数,输入则包含了训练数据$(x_i,y_i),i=1…N$、权重$W$和偏差$b$。注意训练集是给定的(在机器学习中通常都是这样),而权重是可以控制的变量。因此,即使能用反向传播计算输入数据$x_i$上的梯度,但在实践为了进行参数更新,通常也只计算参数(比如$W,b$)的梯度。然而$x_i$的梯度有时仍然是有用的:比如将神经网络所做的事情可视化便于直观理解的时候,就能用上。

简单表达式和理解梯度

从简单表达式入手可以为复杂表达式打好符号和规则基础。先考虑一个简单的二元乘法函数$f(x,y)=xy$。对两个输入变量分别求偏导数还是很简单的:

解释:牢记这些导数的意义:函数变量在某个点周围的极小区域内变化,而导数就是变量变化导致的函数在该方向上的变化率。

注意等号左边的分号和等号右边的分号不同,不是代表分数。相反,这个符号表示操作符$\frac{d}{dx}$被应用于函数$f$,并返回一个不同的函数(导数)。对于上述公式,可以认为$h$值非常小,函数可以被一条直线近似,而导数就是这条直线的斜率。换句话说,每个变量的导数指明了整个表达式对于该变量的值的敏感程度。比如,若$x=4,y=-3$,则$f(x,y)=-12$,$x$的导数$\frac{\partial f}{\partial x}=-3$。这就说明如果将变量$x$的值变大一点,整个表达式的值就会变小(原因在于负号),而且变小的量是$x$变大的量$h$的三倍。通过重新排列公式可以看到这一点$f(x+h)=f(x)+h \frac{df(x)}{dx}$。同样,因为$\frac{\partial f}{\partial y}=4$,可以知道如果将$y$,那么函数的输出也将增加(原因在于正号),且增加量是$4h$。

函数关于每个变量的导数指明了整个表达式对于该变量的敏感程度。

如上所述,梯度$\nabla f$是偏导数的向量,所以有$\nabla f(x)=[\frac{\partial f}{\partial x},\frac{\partial f}{\partial y}]=[y,x]$。即使是梯度实际上是一个向量,仍然通常使用类似“_x上的梯度_”的术语,而不是使用如“_x的偏导数_”的正确说法,原因是因为前者说起来简单。

加法操作求导

这就是说,无论其值如何,$x,y$的导数均为1。这是有道理的,因为无论增加$x,y$中任一个的值,函数$f$的值都会增加,并且增加的变化率独立于$x,y$的具体值(情况和乘法操作不同)。

最大值操作求导

取最大值操作也是常常使用的:

上式是说,如果该变量比另一个变量大,那么梯度是1,反之为0。例如,若$x=4,y=2$,那么max是4,所以函数对于$y$就不敏感。也就是说,在$y$上增加$h$,函数还是输出为4,所以梯度是0:因为对于函数输出是没有效果的。当然,如果给$y$增加一个很大的量,比如大于2,那么函数$f$值就变化了,但是导数并没有指明输入量有巨大变化情况对于函数的效果,他们只适用于输入量变化极小时的情况,因为定义已经指明:$lim_{h\to 0}$。

使用链式法则计算复合表达式

$f(x,y,z)=(x+y)z$

现在考虑更复杂的包含多个函数的复合函数,比如$f(x,y,z)=(x+y)z$。虽然这个表达足够简单,可以直接微分,但是在此使用一种有助于直观理解反向传播的方法。将公式分成两部分:$q=x+y$和$f=qz$。在前面已经介绍过如何对这分开的两个公式进行计算,因为数$f$是$q$和$z$相乘,所以$\displaystyle\frac{\partial f}{\partial q}=z,\frac{\partial f}{\partial z}=q$,又因为$q$是$x$加$y$,所以$\displaystyle\frac{\partial q}{\partial x}=1,\frac{\partial q}{\partial y}=1$。然而,并不需要关心中间量$q$的梯度,因为$\frac{\partial f}{\partial q}$没有用。相反,函数$f$关于$x,y,z$的梯度才是需要关注的。链式法则指出将这些梯度表达式链接起来的正确方式是相乘,比如$\displaystyle\frac{\partial f}{\partial x}=\frac{\partial f}{\partial q}\frac{\partial q}{\partial x}$。在实际操作中,这只是简单地将两个梯度数值相乘,示例代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 设置输入值
x = -2; y = 5; z = -4

# 进行前向传播
q = x + y # q becomes 3
f = q * z # f becomes -12

# 进行反向传播:
# 首先回传到 f = q * z
dfdz = q # df/dz = q, 所以关于z的梯度是3
dfdq = z # df/dq = z, 所以关于q的梯度是-4
# 现在回传到q = x + y
dfdx = 1.0 * dfdq # dq/dx = 1. 这里的乘法是因为链式法则
dfdy = 1.0 * dfdq # dq/dy = 1

最后得到变量的梯度[dfdx, dfdy, dfdz],它们告诉我们函数f对于变量[x, y, z]的敏感程度。这是一个最简单的反向传播。一般会使用一个更简洁的表达符号,这样就不用写df了。这就是说,用dq来代替dfdq,且总是假设梯度是关于最终输出的。
这次计算可以被可视化为如下计算线路图像

上图的真实值计算线路展示了计算的视觉化过程。前向传播从输入计算到输出(绿色),反向传播从尾部开始,根据链式法则递归地向前计算梯度(显示为红色),一直到网络的输入端。可以认为,梯度是从计算链路中回流。

Sigmoid例子

现在看看一个表达式:

在后面的课程中可以看到,这个表达式描述了一个含输入x和权重w的2维的神经元,该神经元使用了_sigmoid激活_函数。但是现在只是看做是一个简单的输入为x和w,输出为一个数字的函数。这个函数是由多个门组成的。除了上文介绍的加法门,乘法门,取最大值门,还有下面这4种:

其中,函数$f_c$使用对输入值进行了常量$c$的平移,$f_a$将输入值扩大了常量$a$倍。它们是加法和乘法的特例,但是这里将其看做一元门单元,因为确实需要计算常量$c,a$的梯度。整个计算线路如下:

使用sigmoid激活函数的2维神经元的例子。输入是[x0, x1],可学习的权重是[w0, w1, w2]。一会儿会看见,这个神经元对输入数据做点积运算,然后其激活数据被sigmoid函数挤压到0到1之间。

图中,$*-1$与exp之间的 -0.2结果计算如下:

在上面的例子中可以看见一个函数操作的长链条,链条上的门都对wx的点积结果进行操作。该函数被称为sigmoid函数$\sigma (x)$。sigmoid函数关于其输入的求导是可以简化的(使用了在分子上先加后减1的技巧):

$\displaystyle\sigma(x)=\frac{1}{1+e^{-x}}$

$\displaystyle\to\frac{d\sigma(x)}{dx}=\frac{e^{-x}}{(1+e^{-x})^2}=(\frac{1+e^{-x}-1}{1+e^{-x}})(\frac{1}{1+e^{-x}})=(1-\sigma(x))\sigma(x)$

可以看到梯度计算简单了很多。举个例子,sigmoid表达式输入为1.0,则在前向传播中计算出输出为0.73。根据上面的公式,局部梯度为(1-0.73)*0.73~=0.2,和之前的计算流程比起来,现在的计算使用一个单独的简单表达式即可。因此,在实际的应用中将这些操作装进一个单独的门单元中将会非常有用。该神经元反向传播的代码实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
w = [2,-3,-3] # 假设一些随机数据和权重
x = [-1, -2]

#前向传播
dot = w[0]*x[0] + w[1]*x[1] + w[2]
f = 1.0 / (1 + math.exp(-dot)) # sigmoid函数

#对神经元反向传播
ddot = (1 - f) * f # 点积变量的梯度, 使用sigmoid函数求导
#在这里ddot是w_0x_0+w_1x_1+w_2整体对结果的,所以需要回传回去
dx = [w[0] * ddot, w[1] * ddot] # 回传到x
dw = [x[0] * ddot, x[1] * ddot, 1.0 * ddot] # 回传到w
#完成!得到输入的梯度

神经网络例子

如图所示的神经网络

前向传播

对于节点$h_1$来说,$h_1$的净输入$net_{h_1}$如下:

接着对$net_{h_1}$做一个sigmoid函数得到节点$h_1$的输出:

类似的,我们能得到节点$h_2$、$o_1$、$o_2$的输出$out_{h_2}$、$out_{o_1}$、$out_{o_2}$

误差

得到结果后,整个神经网络的输出误差可以表示为:

其中$output$就是刚刚通过前向传播算出来的$out_{o_1}$、$out_{o_2}$;$target$是节点$o_1$、$o_2$的目标值。$E_{total}$用来衡量二者的误差。

这个$E_{total}$也可以认为是cost function,不过这里省略了防止overfit的regularization term($\sum{w_i^2})$

展开得到

后向传播

1) 对输出层的$w_5$
通过梯度下降调整$w_5$,需要求$\frac{\partial {E_{total}}}{\partial {w_5}}$,由链式法则:

如下图所示:

注意:${out_{o_1}}=f(net_{o_1})$。但是这个地方是对$net_{o_1}$求导,而不是对$f$求导,因为$net_{o_1}$跟$w$有关,而$f$与$w$无关,也可以理解为$w$为变量,f类似于卷积操作不是变量,反向传播要对变量进行求偏导数。

以上3个相乘得到梯度

之后就可以用这个梯度训练了:

很多教材比如Stanford的课程,会把中间结果$\frac{\partial {E_{total}}}{\partial {net_{o_1}}}=\frac{\partial {E_{total}}}{\partial {out_{o_1}}}\frac{\partial {out_{o_1}}}{\partial {net_{o_1}}}$记做$\delta_{o_1}$,表示这个节点对最终的误差需要负多少责任。。所以有$\frac{\partial {E_{total}}}{\partial {w_5}}=\delta_{o_1}out_{h_1}$。

2) 对隐藏层的$w_1$
通过梯度下降调整$w_1$,需要求$\frac{\partial {E_{total}}}{\partial {w_1}}$,由链式法则:

如下图所示:

参数$w_1$影响了$net_{h_1}$,进而影响了$out_{h_1}$,之后又影响到$E_{o_1}$、$E_{o_2}$。

求解每个部分:

其中

这里$\delta_{o_1}$之前计算过。

$\frac{\partial {E_{o_2}}}{\partial {out_{h_1}}}$的计算也类似,所以得到

$\frac{\partial {E_{total}}}{\partial {w_1}}$的链式中其他两项如下:

相乘得到

得到梯度后,就可以对$w_1$迭代了:

在前一个式子里同样可以对$\delta_{h_1}$进行定义,

所以整个梯度可以写成

DNN例子

上面已经介绍了神经网络的例子,这里详细推导反向传播中DNN所有的权重、偏置对应的值。

关于该例子中一些公式的详细推导,可以看我的另外一篇笔记——深度神经网络(DNN)

$\partial C / \partial w$中$C$是损失函数,而$w$是权重,这个表达式告诉我们,当我们改变权重和偏差时,损失函数的变化有多快

我们假设$w^l_{jk}$为第$(l-1)^{\rm th}$层中第$k$个元素到第$l^{\rm th}$层中第$j^{\rm th}$个元素的权重,如下图所示:

我们使用$b^l_j$代表第$l^{\rm th}$层中第$j$个元素的偏置,而$a^l_j$第$l^{\rm th}$层中第$j$个元素的激活值。如下图所示:

因此可以得到下式,其中$k$的取值范围为第$(l-1)^{\rm th}$层所有的神经元总数目。

从表达式中可以看出来,在DNN中每一个输出都对应一个偏置,偏置的数量与该层输出的神经元数目有关。而在CNN中,输入特征经过一个卷积核输出一个特征图,每一个特征图对应一个偏置,而一个batch之内的相同位置特征图的偏置是同一个偏置。之所以这样,跟偏置的作用是增加表达式的非线性有关。思考,对于DNN或者CNN而言,每一个输出都是线性组合,为了增加非线性,直接在输出增加非线性即可。若对于每一个权重都增加非线性,会浪费计算量,并且偏置都是常数,一个常数可以看成是由多个常数之和。

将此式用矩阵形式,我们规定对于每一层$l$都对应一个权重$w^l$,即$w^l$是连接到第$l$层的权重,即第$j$行和第$k$列中的条目是$ w^{l}_{jk}$。同样的,对于每一层$l$,我们定义一个偏置$b^l$,即偏置向量由$b^l_j$组成。$a^l_j$组成$a^l$。最后我们需要对$\sigma$完成向量化,记做$\sigma(v)$,其中$\sigma(v)_j = \sigma(v_j)$。例如我们的函数为$f(x) = x^2$,向量化$f$有如下表达式:

因此公式$({23})$可以写成

这个表达式为我们提供了一种更全面的思考一层中的激活值与上一层中的激活值之间的关系的方法:我们只需将权重矩阵与上一层激活值相乘,然后添加偏置向量,最后应用$σ$函数。这样就可以使用矩阵形式进行运算,编程中可以使用大量的库。

如果我们使用的是$w^l_{kj}$,假设第$(l-1)^{\rm th}$层的维度为$m$,而第$l^{\rm th}$层的维度为$n$,则$w$的维度为$m \times n$,第$a^{l-1}$层的维度为$m \times 1$,我们写成矩阵形式的化就需要写成$a^{l} = \sigma((w^l)^T a^{l-1}+b^l)$,即对$w$加上转置。

当计算$a^l$的时候,我们需要计算中间变量$z^l \equiv w^l a^{l-1}+b^l$,我们称$z^l$为层$l$中神经元的加权输入。因此公式$({25})$也可以写成$a^l = \sigma(z^l)$。$z^l$也可以拆开为$z^l_j = \sum_k w^l_{jk} a^{l-1}_k+b^l_j$。$z^l_j$含义为第$l$层第$j$个加权输入。

损失函数的两个假设

这里我们定义激活函数:

其中,$n$是训练样本的总个数。我们关于损失函数的第一个假设是$C$可以写成$C = \frac{1}{n} \sum_x C_x$,$C_x$为每个独立训练样本$x$的损失值,具体表达式为$C_x = \frac{1}{2} |y-a^L |^2$。之所以做这样的假设,是因为反向传播经常让求每个训练样本的偏导数$\partial C_x / \partial w$和$\partial C_x / \partial b$。通过对$\partial C_x / \partial w$和$\partial C_x / \partial b$求平均,我们可以得到$\partial C / \partial w$和$\partial C / \partial b$。为了表达的简便,我们使用$C$代替$C_x$。

我们关于损失函数的第二个假设是,它可以写成神经网络输出的函数:

例如,我们这里的平方误差函数就可以满足条件:

这里不把损失函数看成是$y$的函数,是因为$x$固定的那么$y$也是固定的,也就是说对于神经网络这不是一个可以学习的参数。因此,把损失函数看做是$a^L$的函数更有意义。而$y$仅仅是确定损失函数值的一个参数。

反向传播的四个方程

反向传播实际上计算的是$\partial C / \partial w^l_{jk}$与$\partial C / \partial b^l_j$。在这之前,我们需要计算中间量$\delta^l_j$,它是第$l^{\rm th}$层第$j^{\rm th}$个神经元的损失值。

为了理解误差是如何定义的,想象一下我们的神经网络中有一个恶魔:

恶魔所在的位置是第$l$层的第$j^{\rm th}$个神经元位置,当神经元的输入进来时,恶魔就会扰乱神经元的运作,对于神经元的加权输入增加变化$\Delta z^l_j$,则我们需要计算的是$\sigma(z^l_j+\Delta z^l_j)$而不是$\sigma(z^l_j)$。这种变化会在网络的后续层中传播,最终导致总损失函数值的变化$\frac{\partial C}{\partial z^l_j} \Delta z^l_j$。

现在假设恶魔是好的,他试图帮助你降低损失函数值。假设$\frac{\partial C}{\partial z^l_j}$有一个比较大的值,无论是正还是负,主要$\Delta z^l_j$的符号与$\frac{\partial C}{\partial z^l_j}$相反,总损失函数值的变化$\frac{\partial C}{\partial z^l_j} \Delta z^l_j$就会小于零。神经网络就会得到优化。当$\frac{\partial C}{\partial z^l_j}$的值接近零的时候,通过对$z^l_j$干扰,不能使得损失函数值下降。这时神经网络已经取得最优解。受到这个启发,$\frac{\partial C}{\partial z^l_j}$是神经元的损失值。

第$l^{\rm th}$层第$j^{\rm th}$个神经元的损失值$\delta^l_j$可以如下定义:

我们定义$\delta^l$为层$l$误差的向量形式。之所以恶魔选择改变$z^l_j$和$\frac{\partial C}{\partial z^l_j}$,而不是$a^l_j$和$\frac{\partial C}{\partial a^l_j}$,是因为选择后者可以得到相同的结论,但是会使得反向传播公式比较复杂。

输出层的$\delta^L$

$\partial C / \partial a^L_j$衡量损失函数随第$j$次输出的激活函数值而变化的速度有多快。第二个$\sigma’(z^L_j)$衡量激活函数$σ$在$z^L_j$处变化的速度。

这个式子中$z^L_j$在前向传播过程中已经被计算,而之前假设了损失函数可以写成输出的形式,$\partial C / \partial a^L_j$也就很好计算。例如损失函数为$C = \frac{1}{2} \sum_j (y_j-a^L_j)^2$,则$\partial C / \partial a^L_j = (a_j^L-y_j)$。这个也很好计算。

关于该式子的推导,可以将分子写成$\frac{1}{2} \left\{(y_1-a^L_1)^2+(y_2-a^L_2)^2+\dots \right\}$,这是一个标量,上式可以写为$\partial C / \partial a^L_j = \partial( \frac{1}{2} \left\{(y_1-a^L_1)^2+(y_2-a^L_2)^2+\dots \right\})/ \partial a^L_j$。当$j=1$的时候,分子还是求和形式,但是只有$\frac{1}{2}(y_1-a^L_1)^2$与$a^L_1$有关,因此求导可得 $(a_1^L-y_1)$。将$j=1$进行扩展,可以得到上面式子。

为了方便计算,我们将公式$({BP1})$写成矩阵形式

其中,$\nabla_a C$是$\partial C / \partial a^L_j$向量形式。又因为$\nabla_a C = (a^L-y)$,因此$({BP1})$的完整矩阵形式

推导:

\begin{eqnarray}
\delta^L_j = \frac{\partial C}{\partial z^L_j} = \sum_k \frac{\partial C}{\partial a^L_k} \frac{\partial a^L_k}{\partial z^L_j}
\tag{36}\end{eqnarray}

因为$k \neq j$的时候,$\partial a^L_k / \partial z^L_j$消失。所以上式可以写为:

因为$a^L_j = \sigma(z^L_j)$,所以右边第二项可以写为$\sigma’(z^L_j)$,可得下式得证。

已知$\delta^{l+1}$推导$\delta^l$

证明:

假设$z^{l+1}_k$的维度为$n \times 1$,$z^l_k$的维度为$m \times 1$。根据分母布局,$\frac{\partial z^{l+1}_k}{\partial z^l_j}$的维度为$m \times n$,而$\delta^{l+1}_k$的维度为$n \times 1$。为了保证矩阵可以正常相乘,这里需要将$\delta^{l+1}_k$放到后面。

又因为

所以

因为最终$\frac{\partial z^{l+1}_k}{\partial z^l_j}$的维度为$m \times n$,我们按照分母布局对上式进行分析,分析过程如下:

对于结果中第一行,即当$j=1$的时候,取$k=1$,求导结果为$w_{11}^{l+1}\sigma’(z^l_1)$;取$k=2$,求导结果为$w_{21}^{l+1}\sigma’(z^l_1)$;取$k=n$,求导结果为$w_{n1}^{l+1}\sigma’(z^l_1)$。

对于结果中第二行,即当$j=2$的时候,取$k=1$,求导结果为$w_{12}^{l+1}\sigma’(z^l_2)$;取$k=2$,求导结果为$w_{22}^{l+1}\sigma’(z^l_2)$;取$k=n$,求导结果为$w_{n2}^{l+1}\sigma’(z^l_2)$。

对于结果中第m行,即当$j=m$的时候,取$k=1$,求导结果为$w_{1m}^{l+1}\sigma’(z^l_m)$;取$k=2$,求导结果为$w_{2m}^{l+1}\sigma’(z^l_m)$;取$k=n$,求导结果为$w_{nm}^{l+1}\sigma’(z^l_m)$。

综合上述分析,可以得到最终的结果$ w^{l+1}_{kj} \sigma’(z^l_j)$。

代回方程$({42})$

损失函数对偏置的偏导

上式也可以简记为:

损失函数对权重的偏导

上式简记为

可以用图表示出来即:

当$a_{\rm in} \approx 0$的时候,$\partial C / \partial w$也会变得很小,这时神经网络的学习速度会下降。

式子$({BP4})$证明过程如下:

因为$z^l_j= \sum_k w_{jk}a_k^{l-1}+b_j= w_{j1}a_1^{l-1} + w_{j2}a_2^{l-1} + \dots +b_j $,所以有如下式子,其中分子始终是一个求和的形式,只是求和的项与$j$取值、$k$的个数有关。

当$j=1$,$k$依次从1取到第$l-1$层总个数,上式的结果依次为$a_1^{l-1},a_2^{l-1},\dots$

当$j=2$,$k$依次从1取到第$l-1$层总个数,上式的结果依次为$a_1^{l-1},a_2^{l-1},\dots$

可以看出来,结果只和$k$的取值有关,而和$j$的取值无关。所以可得

总结

这4个公式总结如下:

从这4个式子中,我们考虑\begin{eqnarray}
\delta^L = \Sigma’(z^L) \nabla_a C,
\tag{33}\end{eqnarray}中的$\sigma’(z^L_j)$,当$\sigma$为sigmoid函数的时候,$\sigma(z^L_j)$的值接近与0或1,这时偏导数$\sigma’(z^L_j) \approx 0$。在这种情况下,通常会说输出神经元已经饱和,这时,权重停止了学习(或学习缓慢)。类似的观点也适用于输出神经元的偏差。

同样的情况也适用于$({BP2})$中的$\sigma’(z^l)$,这个时候,$\delta^l_j$会变得很小,这又意味着,输入到饱和神经元的任何权值都会慢慢地学习

当然,上述讨论不适用于当${w^{l+1}}^T \delta^{l+1}$特别大可以弥补$\sigma’(z^l_j)$的小的情形。上述讨论只是一种总体趋势。

综上所述,我们了解到,如果输入神经元处于低激活状态,或者输出神经元处于饱和状态,则权值的学习速度较慢。这上面的观察结果可以帮助我们设计较好的激活函数。

反向传播方程的另外一种形式

$({BP1})$和$({BP2})$含有Hadamard积,然而当对Hadamard积不是很了解的时候,可以写成矩阵的形式方便大家理解。

$({BP1})$可以写成下面的形式:

其中,$\Sigma’(z^L)$方阵,对角元素是$\sigma’(z^L_j)$,非对角元素值为零。

$({BP2})$可以写成下面的形式:

通过以上两个式子,可以得到

上述这种矩阵的表示方式可能更好理解,并且数值计算的时候,方便。

反向传播算法

1) 输入$x$:即$a^1$

2) 前向计算:对于每一个$l = 2, 3, \ldots, L$,计算$z^{l} = w^l a^{l-1}+b^l$,并且$a^{l} = \sigma(z^{l})$。

3) 计算$\delta^L$:$\delta^{L} = \nabla_a C \odot \sigma’(z^L)$

4) 损失反向传播:对于每一个$l = L-1, L-2, \ldots, 2$,计算$\delta^{l} = ((w^{l+1})^T \delta^{l+1}) \odot
\sigma’(z^{l})$

5) 输出:计算梯度,$\frac{\partial C}{\partial w^l_{jk}} = a^{l-1}_k \delta^l_j$和$\frac{\partial C}{\partial b^l_j} = \delta^l_j$。

正如我前面所描述的,反向传播算法计算单个训练样本$C=Cx$的损失函数的梯度。在实际应用中,通常将反向传播与随机梯度下降等学习算法相结合,对许多训练样本计算梯度。特别是,给出了一小批——m个训练样本,下面的算法应用基于该小批的梯度下降学习步骤:

1) 输入一批训练样本

2) 对于每个训练样本$x$,即$a^{x,1}$,并执行下面步骤:

2.1) 前向计算:对于每一个$l = 2, 3, \ldots, L$,计算$z^{x,l} = w^l a^{x,l-1}+b^l$,并且$a^{x,l} = \sigma(z^{x,l})$。

2.2) 输出误差$\delta^{x,L}$:计算向量$\delta^{x,L} = \nabla_a C_x \odot \sigma’(z^{x,L})$

2.3) 损失反向传播:对于每一个$l = L-1, L-2, \ldots, 2$,计算$\delta^{x,l} = ((w^{l+1})^T \delta^{x,l+1})
\odot \sigma’(z^{x,l})$

3) 梯度下降:对于每一个$l = L, L-1, \ldots, 2$,根据$w^l \rightarrow w^l-\frac{\eta}{m} \sum_x \delta^{x,l} (a^{x,l-1})^T$更新权重,根据$b^l \rightarrow b^l-\frac{\eta}{m} \sum_x \delta^{x,l}$更新偏置。其中$\eta$是学习率,$m$是训练样本的总个数。

反向传播的直观理解

反向传播是一个优美的局部过程。在整个计算线路图中,每个门单元都会得到一些输入并立即计算两个东西:1. 这个门的输出值,和2.其输出值关于输入值的局部梯度。门单元完成这两件事是完全独立的,它不需要知道计算线路中的其他细节。然而,一旦前向传播完毕,在反向传播的过程中,门单元门将最终获得整个网络的最终输出值在自己的输出值上的梯度。链式法则指出,门单元应该将回传的梯度乘以它对其的输入的局部梯度,从而得到整个网络的输出对该门单元的每个输入值的梯度。

这里对于每个输入的乘法操作是基于链式法则的。该操作让一个相对独立的门单元变成复杂计算线路中不可或缺的一部分,这个复杂计算线路可以是神经网络等。

下面通过3.1例子来对这一过程进行理解。加法门收到了输入[-2, 5],计算输出是3。既然这个门是加法操作,那么对于两个输入的局部梯度都是+1。网络的其余部分计算出最终值为-12。在反向传播时将递归地使用链式法则,算到加法门(是乘法门的输入)的时候,知道加法门的输出的梯度是-4。如果网络如果想要输出值更高,那么可以认为它会想要加法门的输出更小一点(因为负号),而且还有一个4的倍数。继续递归并对梯度使用链式法则,加法门拿到梯度,然后把这个梯度分别乘到每个输入值的局部梯度(就是让-4乘以xy的局部梯度,x和y的局部梯度都是1,所以最终都是-4)。可以看到得到了想要的效果:如果x,y减小(它们的梯度为负),那么加法门的输出值减小,这会让乘法门的输出值增大。

因此,反向传播可以看做是门单元之间在通过梯度信号相互通信,只要让它们的输入沿着梯度方向变化,无论它们自己的输出值在何种程度上升或降低,都是为了让整个网络的输出值更高

反向传播直观理解思考

一个展示反向传播的例子。加法操作将梯度相等地分发给它的输入。取最大操作将梯度路由给更大的输入。乘法门拿取输入激活数据,对它们进行交换,然后乘以梯度。

从上例可知:

加法门单元把输出的梯度相等地分发给它所有的输入,这一行为与输入值在前向传播时的值无关。这是因为加法操作的局部梯度都是简单的+1,所以所有输入的梯度实际上就等于输出的梯度,因为乘以1.0保持不变。上例中,加法门把梯度2.00不变且相等地路由给了两个输入。

取最大值门单元对梯度做路由。和加法门不同,取最大值门将梯度转给其中一个输入,这个输入是在前向传播中值最大的那个输入。这是因为在取最大值门中,最高值的局部梯度是1.0,其余的是0。上例中,取最大值门将梯度2.00转给了z变量,因为z的值比w高,于是w的梯度保持为0。

乘法门单元相对不容易解释。它的局部梯度就是输入值,但是是相互交换之后的,然后根据链式法则乘以输出值的梯度。上例中,x的梯度是-4.00x2.00=-8.00。

非直观影响及其结果。注意一种比较特殊的情况,如果乘法门单元的其中一个输入非常小,而另一个输入非常大,那么乘法门的操作将会不是那么直观:它将会把大的梯度分配给小的输入,把小的梯度分配给大的输入。在线性分类器中,权重和输入是进行点积$w^Tx_i$,这说明输入数据的大小对于权重梯度的大小有影响。例如,在计算过程中对所有输入数据样本$x_i$乘以1000,那么权重的梯度将会增大1000倍,这样就必须降低学习率来弥补。这就是为什么数据预处理关系重大,它即使只是有微小变化,也会产生巨大影响。对于梯度在计算线路中是如何流动的有一个直观的理解,可以帮助读者调试网络。

反向传播的优点

式子$e=(a+b)*(b+1)$可以用如下计算图表达:

前向模式求导( forward-mode differentiation) :从前向后。先求X对Y的总影响 $(\alpha + \beta + \gamma)$再乘以Y对Z的总影响 $(\delta + \epsilon + \zeta)$。

另一种,反向模式求导(reverse-mode differentiation) 则是从后向前。先求Y对Z的影响再乘以X对Y的影响。

前向求导模式追踪一个输入如何影响每一个节点(对每一个节点进行$\frac{\partial}{\partial X}$操作)反向求导模式追踪每一个节点如何影响一个输出(对每一个节点进行 $\frac{\partial Z}{\partial}$操作)。

反向求导模式(反向传播算法)的重要性:

让我们再次考虑前面的例子:

如果用前向求导模式:关于b向前求导一次


如果用反向求导模式:向后求导

前向求导模式只得到了关于输入b的偏导$ \frac{\partial e}{\partial b}$ ,还需要再次求解关于输入a的偏导$\frac{\partial e}{\partial a}$(运算2遍)。而反向求导一次运算就得到了e对两个输入a,b的偏导$\frac{\partial e}{\partial a}, \frac{\partial e}{\partial b}$(运算1遍)。上面的比较只看到了2倍的加速。但如果有1亿个输入1个输出,意味着前向求导需要操作1亿遍才得到所有关于输入的偏导,而反向求导则只需一次运算,1亿倍的加速。

当我们训练神经网络时,把“损失“ 看作 ”权重参数“ 的函数,需要计算”损失“关于每一个”权重参数“的偏导数(然后用梯度下降法学习)。 神经网络的权重参数可以是百万甚至过亿级别。因此 反向求导模式(反向传播算法)可以极大的加速学习。

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

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