看到了一个讲反向传播计算图的很棒的博客,看的过程顺手翻译了一下。
原文在这里:Calculus on Computational Graphs: Backpropagation。
介绍
反向传播是使训练深度模型在计算上易于处理的关键算法。 对于现代神经网络而言,相对于朴素贝叶斯的实现,它可以使梯度下降的训练速度提高千万倍。 这相当于花一周时间训练和花费200,000年的时间训练模型的区别。
除了用于深度学习之外,反向传播在许多其他领域也是一个强大的计算工具,从天气预报到分析数值稳定性 - 它只是名称不同而已。 事实上,该算法在不同领域至少被重复发明了十次(参见Griewank(2010))。 一般来说,与具体应用无关的名称应该是“反向模式微分”。
从根本上说,这是一种快速求导的技术。 无论是在深度学习中,还是在各种数值计算环境中,这都是一个必不可少的窍门。
计算图
计算图是思考数学表达式的好方法。
例如,考虑表达式 : e = (a+b)*(b+1)。
这里有三个操作:两个加法和一个乘法。 为了帮助我们讨论这个问题,我们引入两个中间变量c和d,以便每个表达式的输出都有一个变量。 我们现在有:
- c= a+ b
- d= b+ 1
- e = c * d
为了创建计算图,我们将这些运算连同输入变量一起放入节点中。 当一个节点的值是另一个节点的输入时,箭头从一个节点指向另一个节点。
这些图形总是出现在计算机科学中,特别是在讨论功能性程序时。 它们与依赖图和调用图的概念密切相关。它们也是深受欢迎的深度学习框架Theano的核心抽象。
我们可以通过将输入变量设置为特定值,然后通过图形自底向上的计算节点值来对表达式求值。 例如,我们设置a = 2和b = 1.
计算结果这个表达式的值是6。
计算图中的导数
理解计算图上导数的关键是理解边上的导数。如果a直接影响c,那么我们想知道它是如何影响c的。 如果a改变一点,c怎么改变? 我们称之为c关于a的偏导数。
为了计算这个图中的各个偏导数,我们需要借助加法和乘法的求导公式:
下图标出了每个边上的导数。
如果我们想了解那些不是直接相连的节点是如何相互影响的呢? 让我们考虑一下e是如何被a影响的吧。 如果我们以1的速度改变a,c也以1的速度改变。相应地,c以1的速度改变导致e以2的速度改变。因此e相对于a以1 * 2的速率改变。一般规则是:如果存在从节点i到节点j的路径,将该条路径经过的每条边上的导数相乘,得到i通过该条路径对j产生的影响,将从节点i到节点j的所有可能路径的影响值相加,得到j关于i的导数。 例如,为了计算e关于b的导数,进行如下计算:
这解释了b如何通过c影响e以及b如何通过d来影响e。
这种 “路径求和”的一般规则只是对多元链式规则的不同思考方式。
路径分解
只是简单的“路径求和”的问题在于,很容易导致可能的路径数量的组合爆炸。
在上面的图中,有三条从X到Y的路径,还有三条从Y到Z的路径。如果我们想通过在所有路径上求和得到导数∂Z/∂X,我们需要对3 * 3 = 9条路径求和:
虽然上面的例子只有九条路径,但据此很容易类推出,随着图形变得越来越复杂,路径的数量会呈指数级增长。
比单纯对路径进行简单的求和更好的一种做法是,将它们因式分解:
这就是“前向模式微分”和“反向模式微分”的出处,它们是通过因式分解来进行高效的路径求和的算法。并不是显示地对所有路径求和,而是通过在每个节点处把路径合并到一起来更高效地计算和。 实际上,这两种算法都访问每条边刚好一次!
前向模式微分从图形的一个输入开始,向终点移动。在每个节点上,它对所有流入的路径求和。流入的每条路径都代表着输入影响该节点的一种方式。通过将它们相加,我们得到这个节点受输入影响的总体方式,也就是它的导数。
虽然你可能从来没有从图的角度来思考过它,前向模式微分和你在微积分导论课程上隐式地学习的要做的事情非常相似。
与此相反,反向模式微分从图的输出开始,向开始点移动。 在每个节点上,它合并所有源自该节点的路径。
前向模式微分跟踪一个输入如何影响每个节点。 反向模式微分跟踪每个节点如何影响一个输出。 也就是说,前向模式的微分将运算符∂/∂X应用于每个节点,而反向模式微分则将运算符∂Z/∂应用于每个节点。
计算的胜利
在这一点上,你可能会想知道为什么会有人关心反向模式微分。 它看起来像是一个做与前向模式微分同样事情的奇怪方式。 反向模式有一些它自己的优势吗?
再次考虑我们的原始示例:
我们可以从b开始使用向上的前向模式微分。 这给了我们每个节点关于b的导数。
我们已经计算了∂e/∂b,它是我们的输出关于其中一个输入的导数。
如果我们从e向下做反向模式微分会怎么样? 这给了我们e关于每个节点的导数:
当我说反向模式微分给了我们e对于每个节点的导数时,我确实在说每个节点!我们同时得到了∂e/∂a和∂e/∂b,e对于两个输入的导数。前向模式微分给我们输出相对于单个输入的导数,但反向模式微分给了我们所有输入的导数。
对于这个图,这只是两倍速的一个因式分解,但想象一个具有一百万个输入和一个输出的函数。前向模式微分将要求我们通过这张图一百万次才能获得导数。反向模式微分可以让他们一举成功!一百万倍速的分解是不是很棒呢?
在训练神经网络时,我们将cost(一个描述神经网络表现如何差的值)视为参数(描述网络行为方式的数字)的函数。我们想要计算cost对于所有参数的导数,以用于梯度下降。现在,神经网络中通常有数百万甚至上千万的参数。所以,反向模式微分,在神经网络中被称为反向传播,给我们带来了巨大的加速!
(是否有前向模式微分更有意义的情况呢?是的,有!反向模式给出一个输出相对于所有输入的导数,前向模式给出了所有输出关于一个输入的导数,如果一个函数具有很多输出,则前向模式微分会快很多)。
这很平凡吗?
当我第一次明白后向传播是什么时,我的反应是:“哦,那只是链式规则!我们怎么花了那么长时间才弄明白?“我不是唯一一个有这种反应的人。如果你问“在前馈神经网络中是否有一种聪明的方法来计算导数?”,看起来回答这个答案并不困难。
但我认为这比看起来要困难得多。你看,在反向传播发明的时候,人们并不十分关注我们研究的前馈神经网络。导数是训练它们的正确方式这点也不是很明显。只有你意识到你可以快速计算导数时,这些才是显而易见的。这里有一个循环依赖。
更糟糕的是,将零散的循环依赖关系作为不可能实现的偶然想法是很容易的。用导数工具训练神经网络?你会想当然的认为,那只会陷入局部最小。显然,计算所有这些导数会很昂贵。只有当我们知道这种方法有效,我们才不会立即开始列出这么做不可能的原因。
这是后见之明的好处。一旦你确定了这个问题,最困难的工作就已经完成了。
结论
导数的计算比你想象的要便宜。这是这篇文章帮你删除的主要教训。事实上,它们直觉上并不便宜,我们这些愚蠢的人类不得不不断重新发现这一事实。在深度学习中理解这一点非常重要。在其他领域这也是一件非常有用的事情,如果它还不是常识,那就更是如此。
还有其他课程吗?我认为有。
反向传播也是理解导数如何流经模型的有用工具。这对推理为什么某些模型难以优化很有帮助。这个问题的经典的例子是循环神经网络中梯度消失的问题。
最后,我声称有一个广泛的算法教训可以从这些技术中剥离出来。反向传播和前向模式微分使用一对强大的技巧(线性化和动态规划)来超出人们想象地更有效地计算导数。如果你真的了解这些技巧,你可以使用它们来有效地计算其他涉及导数的有趣表达式。我们将在稍后的博客文章中对此进行探讨。
这篇文章给出了一个非常抽象的反向传播处理。我强烈建议你阅读Michael Nielsen关于它的章节,进行一次精彩的讨论,更具体地聚焦于神经网络。