反向传播算法图解

本文来自Colah's blog,原文链接为http://colah.github.io/posts/2015-08-Backprop/,转载请注明。

简介

    反向传播是训练深度模型的一种关键算法。在现代的神经网络中,相比于简单的实现,它可以使梯度下降的训练速度提高一千万倍。这其中的差异好比花费一周和200000年时间来训练模型。除了在深度学习中的应用外,方向传播算法在其他领域也是一种强有力的计算工具,如天气预报和数值稳定性分析,仅仅是名字不同而已。从根本上说,反向传播算法是一种快速计算导数的方法,不仅仅是应用在深度学习中,还应用于各种数值计算的情景中。

计算图

    计算图是一种思考数学表达式的很好的方法。例如,考虑表达式$e=(a+b)\times (b+1)$,其中有三个数学操作:两个加法和一个乘法。为讲清这件事,我们引入了两个中间变量,$c$和$d$,如此,每个函数的输出都有一个变量,即:

$c=a+b \\ d=b+1\\ e=c\times d$

    为了创建计算图,我们将每个数学运算操作连同相应输入变量一起放到节点中,当一个节点值是另一个节点输入时,箭头由该节点指向另一个节点。通过上式可构建计算图:

 

 

     这类图一直出现在计算机科学中,尤其是函数程序中。他们与依赖图和调用图的概念有着非常密切的关系,也是深度学习框架Theano的核心抽象。我们可以通过将输入变量设置为特定值并通过图的计算节点来计算表达式。例如,令$a=2, b=1$,则表达式的值为6,计算过程见下图。

计算图上的导数

    理解计算图上的导数,关键是理解边上的导数。若$a$直接影响 $c$,那么我们想知道它是如何影响$c$的。如果$a$改变一点,那么$c$是如何产生相应变化的?我们称之为$c$相对于$a$的偏导。为计算图中的偏导数,我们需要先了解以下两个规则:

 $\frac{\partial}{\partial a}(a+b)=\frac{\partial a}{\partial a}+\frac{\partial b}{\partial a}=1$

$\frac{\partial}{\partial u}(uv)=u\frac{\partial v}{\partial u}+v\frac{\partial u}{\partial u}=v$

    因此,我们可以计算每条边上的偏导数,如下图所示。

    那么,如果我们想要知道互不直接相连的节点间是如何影响彼此的,该怎么办?我们来考虑$a$是如何影响$e$的。如果我们以1的速度来改变$a$,$c$的变化速度也是1,那么,$c$以1的速度变化,会导致$e$以2的速度变化。因此,相对于$a$,$e$的变化率为$1\times 2$。

一般的规则是,从一个节点到另一个节点的所有可能路径值进行相加,每条路径上的值等于该路径上所有导数的乘积。例如,为计算$e$相对于$b$的偏导数,我们有:

$\frac{\partial}{\partial b}e=\frac{\partial e}{\partial c}\frac{\partial c}{\partial b}+\frac{\partial e}{\partial d}\frac{\partial d}{\partial b}=2\times 1+3\times 1=5$

    这就解释了$b$如何通过$c$和$d$来影响$e$。这种“沿所有路径求和”的一般规则只是对多元链式法则的另一种思考。

路径的因式分解

    “沿所有路径求和”问题随着可能路径数增大,容易导致组合爆炸。

    在上图中,从$X$到$Y$,从$Y$到$Z$分别都有3条路径。如果我们想求偏导$\frac{\partial Z}{\partial X}$,则需要求和$3\times 3=9$条路径,即:

$\frac{\partial Z}{\partial X}=\alpha\delta+\alpha\epsilon+\alpha\zeta+\beta\delta+\beta\epsilon+\beta\zeta+\gamma\delta+\gamma\epsilon+\gamma\zeta$

    上面仅有9条路径,但是当图变得更加复杂时,路径的数量很容易指数增长。与其简单求和所有路径,不如对它们进行因式分解:

$\frac{\partial Z}{\partial X}=(\alpha+\beta+\gamma)(\delta+\epsilon+\zeta)$

    这就出现了“正向模式微分”和“反向模式微分”,他们能够利用因式分解对路径进行高效求和。他们不是显式地对所有路径进行相加,而是在每个节点上后向合并路径,从而有效地计算相同的和。事实上,两种算法能遍历所有边,并且每条边只计算一次。

    “正向模式微分”始于图的输入并向结尾移动。在每个节点中,它对所有流入路径进行求和,其中的每条路径都代表输入影响节点的一个形式。通过对他们进行求和,我们得到了节点被输入影响的全部形式,即导数。

    而“反向模式微分”是始于图的输出并向起始位置移动。在每个节点中,它融合所有起始于该节点的路径。

   

    总而言之,“正向模式微分”跟踪一个输入怎么影响所有节点,“反向模式微分”追踪每个节点如何影响输出。也就是说,“正向模式微分”对每个节点应用算子$\frac{\partial}{\partial X}$,而“反向模式微分”对每个节点应用算子$\frac{\partial Z}{\partial}$。

成功计算

    在这一点上,你可能会想知道为什么会有人关心“反向模式微分”。它好像是一种奇怪的方式,做着与“正向模式微分”相同的事。它究竟有什么优势?让我们再次思考原来的例子:

    我们可以从$b$往上使用“正向模式微分”,这给出了每个节点相对于$b$的导数。

    由此我们计算出$\frac{\partial e}{\partial b}$,即输出对所有输入中某一个的导数。

    那么,如果我们从$e$往下做“反向模式微分”呢?它给出了输出$e$对每个节点的导数:

    当我说到“反向模式微分”给出了输出$e$对每个节点的导数时,我是真真正正指每个节点。“正向模式微分”仅仅给出了输出对某个节点的导数,而“反向模式微分”却给出了输出对所有节点的微分。

    在这个图中,只是做了简单的因式分解进行计算加速。但是想象一个有一百万个输入和一个输出的函数。“正向模式微分”需要我们遍历一百万次图来获得输出对所有节点的导数,而“反向模式微分”只需要遍历一次。一百万的加速系统是相当不错的!

    当训练神经网络时,我们考虑cost成本函数(描述一个神经网络的性能)作为网络参数(权重等)的函数。在梯度下降法计算过程中,我们希望得到成本相对于每个参数的导数。现在,神经网络中常有上百万甚至上千万的参数,因此,“反向模式微分”,在神经网络的背景下被称作“反向传播”算法,给了我们一个巨大的加速性能。

    (当然,“正向模式微分”也有其用武之处,在一个有大量输出的函数中,“正向模式微分”将会更加快速!)

 

 

posted @ 2017-11-29 17:05  知多少1994  阅读(61)  评论(0)    收藏  举报