[NL系列] 计算图的微积分——反向传播
基本上是翻译自 Calculus on Computational Graphs: Backpropagation。作者是 Chris Olah,在2012年获得了Thiel Fellowship,由PayPal共同创办人 Peter Thiel 在2010年提出的用于资助20岁以下优秀青少年的10万美元奖学金,让他们辍学去改变世界。
简介
反向传播 Backpropagation是使得训练深层神经网络模型在计算上易于处理的关键算法。除了在深度网络中应用之外,反向传播在其他领域(天气预报、数值计算等等)作为计算工具功能也很强大,只是在不同领域的称呼不同而已。事实上,反向传播在不同领域至少被“重新发现”过十多次(见Griewank(2010))。一般来说,其独立于领域的名称叫做 反向模式分化 (reverse-mode differentiation) 。
基本上,反向传播是一种快速计算导数的方法。熟练掌握反向传播,不仅在深度学习中有所裨益,在其他更多的数值计算问题中都有很大好处。
计算图
计算图可以很好的描述数学表达式,比如对于
这种计算图在函数式编程里经常会出现,概念上和 依赖图 dependency graphs 以及 调用图 call graphs 很相似。这也是当前流行的深度学习框架Theano的核心(MXnet也是如此)。
我们可以通过给变量赋予初值,并通过从下到上的计算来给这个表达式赋值,比如当设定* a = 2, b = 1 *时
此时表达式被赋值为6。
计算图中的导数
想要理解计算图中的导数,核心是要明白边上的导数是怎么回事。如果 a 变化了一点, c 会怎么变?这称之为 c 对 a 的偏导数。偏导数满足加法律和乘法律,即
![][5]
[5]: https://latex.codecogs.com/svg.latex?\frac{\partial}{\partial&space;a}(a+b)&space;=&space;\frac{\partial&space;a}{\partial&space;a}&space;+&space;\frac{\partial&space;b}{\partial&space;a}&space;=&space;1
![][6]
[6]: https://latex.codecogs.com/svg.latex?\frac{\partial}{\partial&space;u}uv&space;=&space;u\frac{\partial&space;v}{\partial&space;u}&space;+&space;v\frac{\partial&space;u}{\partial&space;u}&space;=&space;v
下面的计算图中,每条边上都标注了偏导数。
那要怎样理解不直接连接的节点之间的相互影响呢?我们看 e 是如何影响 a 的:假设 a 以速度1变化, c 也以速度1变化,同时,c 以速度1变化将会导致 e 以速度2变化。因此 e 以速度 2 随着 a 变化。
一般的法则是,把从一个结点到另一个结点的所有可能路径加起来,每条路径所有经过边的偏导值相乘。比如要计算 e 对 b 的偏导值,我们有
![][7]
[7]: https://latex.codecogs.com/svg.latex?\frac{\partial&space;e}{\partial&space;b}=&space;12&space;+&space;13
这就是 b 如何通过 c 和 d 影响 e 。
这种“把所有路径相加”的想法,其实就是链式法则的另一种思路。
路径分解
只用“所有路径相加”的方法带来的问题是,很容易遇到可能路径数量多到“爆炸”的情况,比如
在这个图中, X 到 Y 有三条路,Y 到 Z 又有三条路,这样的话如果我们要计算 Z 对 X 的偏导数,要做到“所有路径相加”,必须计算9条路径的加法。
![][8]
[8]: https://latex.codecogs.com/svg.latex?\frac{\partial&space;Z}{\partial&space;X}&space;=&space;\alpha\delta&space;+&space;\alpha\epsilon&space;+&space;\alpha\zeta&space;+&space;\beta\delta&space;+&space;\beta\epsilon&space;+&space;\beta\zeta&space;+&space;\gamma\delta&space;+&space;\gamma\epsilon&space;+&space;\gamma\zeta
这幅图里只有9条路径,但是随着图的规模增大,路径数量很容易呈指数增长爆发。
因此,不同于只是简单的把所有路径加起来,我们应该把路径进行分解,即
![][9]
[9]: https://latex.codecogs.com/svg.latex?\frac{\partial&space;Z}{\partial&space;X}&space;=&space;(\alpha&space;+&space;\beta&space;+&space;\gamma)(\delta&space;+&space;\epsilon&space;+&space;\zeta)
这就是 前向模式分化 (forward-mode differentiation) 和 反向模式分化 (reverse-mode differentiation) 的思想,它们通过路径分解高效的求和。不同于 显示 (explicitly) 的分开求解方式,这种思想把每个节点发出的路径合并起来。事实上,两种算法对于每条边都只用了一次。
前向模式分化 (forward-mode differentiation) 从计算图的输入开始一直到图的末端。每个节点把指向其的所有路径加起来,每条路径代表着影响该节点的一个因素。把这些因素都加起来,我们就得到了每个节点受到输入产生的所有影响,这就是导数。
反向模式分化 (reverse-mode differentiation) 则是从计算图的输出开始一直到图的起点。每个节点把从其发出的所有路径加起来。
前向模式分化 (forward-mode differentiation) 跟踪了一个输入是如何影响每个节点的,而 反向模式分化 (reverse-mode differentiation) 则是跟踪了每个节点是如何影响一个输出的。在微积分里,这就指的是 前向模式分化 (forward-mode differentiation) 对每个节点都执行了 对 X 求偏导 的操作,而 反向模式分化 (reverse-mode differentiation) 对每个节点都执行了 Z 对其求偏导 的操作。
计算的……胜利!?
那么 反向模式分化 (reverse-mode differentiation) 的优点在哪?
还是考虑这个例子,我们使用 前向模式分化 (forward-mode differentiation) 从 b 开始往上传播,这样就得到了每个节点对 b 的偏导数。
这样我们就得到了 e 对 b 的偏导数,即计算图的输出对某一个输入的偏导数。
如果从 e 开始一步一步向下执行 反向模式分化 (reverse-mode differentiation) 会怎么样呢?这就得到了 e 对每个节点的偏导数。
可以看到,与 前向模式分化 (forward-mode differentiation) 只得到输出对 某一个输入 的偏导数不同的是,反向模式分化 (reverse-mode differentiation) 得到了 每一个输入 对输出的偏导数。
对于这个图来说,这只提速了两倍(两个输入,一个输出)。然而对于一个有大量输入,却只有少量输出的复杂图来说,这个加速比就很可观了。
在训练神经网络时,我们会考虑计算网络输出与实际的差值(也就是所谓的 代价 )对于所有网络参数的偏导数,将其用到梯度下降当中。由于一个神经网络中经常有上百万、甚至上千万个参数,那么 反向模式分化 (reverse-mode differentiation) (在神经网络中称为 反向传播 (backpropagation) )就可以带来巨大的加速比。
这看起来好像没什么大不了的啊!?
反向传播 (backpropagation) 其实就是链式法则,然而这也像看起来那么简单。想当年发明 反向传播 (backpropagation) 的时候,人们并没有很关注 前馈网络 的研究,也没有发现应该使用偏导数来训练网络。
Those are only obvious once you realize you can quickly calculate derivatives. There was a circular dependency.
结论——你应该明白的东西
- 导数要比你想象的简单很多!
- 反向传播 (backpropagation) 对于理解导数是如何在一个模型中传播的也很有帮助。了解了它之后你就会知道为什么会有 梯度扩散 的问题了。
这篇只是对 反向传播 (backpropagation) 的一个简介,想要了解神经网络中如何更详细的使用请参看 Michael Nielsen’s 的文章。翻译版本在这里有。
译完之后
才发现这篇文章还真是很通俗的介绍了一下反向传播的概念(和优点?感觉也没有说的很全面,应该是提炼出了核心优势吧)。估计还是要继续去看Olah推荐的那篇文章才能有更深刻的理解。