• 作者:老汪软件技巧
  • 发表时间:2024-09-21 00:04
  • 浏览量:

引言

反向传播算法是深度学习模型训练中不可或缺的计算技术,它使得模型训练在计算上变得高效可行。在现代神经网络中,反向传播能够将梯度下降的训练速度提升大约一千万倍。

反向传播的应用不仅限于深度学习领域,它还是一个强大的计算工具,广泛应用于天气预报、数值稳定性分析等多个领域,尽管在这些领域它可能以不同的名称出现。事实上,这个算法在不同领域被独立发明了数十次(参见 Griewank (2010))。在更广泛的背景下,它被称为“反向模式微分”。

本质上,反向传播是一种快速计算导数的技术。无论是在深度学习还是其他数值计算场景中,掌握这一技巧都是至关重要的。

计算图

计算图是理解和操作数学表达式的强大工具。以表达式e=(a+b)×(b+1)e=(a+b)×(b+1)e=(a+b)×(b+1) 为例,它包含三种操作:两个加法和一个乘法。为了便于讨论,引入两个中间变量 ccc 和 ddd,使得每个操作的结果都对应一个变量:

c=a+bd=b+1e=c∗dc = a + b \\ d = b + 1 \\ e = c * d c=a+bd=b+1e=c∗d

构建计算图时,我们将这些操作和输入变量表示为节点。如果一个节点的值是另一个节点的输入,则存在一个从前者指向后者的箭头。

这种图在计算机科学中十分常见,尤其是在函数式编程的讨论中。它们与依赖图和调用图的概念密切相关,并且是流行的深度学习框架Theano的核心抽象。

我们可以通过为输入变量赋予特定值,并沿着图计算节点来评估表达式。例如,设置 a=2a = 2a=2 和 b=1 b = 1b=1:

e=(2+1)∗(1+1)=3∗2=6e = (2+1) * (1+1) = 3 * 2 = 6e=(2+1)∗(1+1)=3∗2=6

因此,表达式的计算结果为6。

计算图上的导数

理解计算图上的导数,关键在于掌握节点之间边的导数。例如,如果 a 直接影响 c,我们关心的是 a 的变化如何影响 c。换句话说,我们想知道 c 对 a 的偏导数。

为了在计算图中评估偏导数,我们使用求和规则和乘积规则:

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

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

在下面的图中,每个边上都标注了导数。

如如果我们想要理解不直接相连的节点是如何相互影响的,比如 e 如何受 a 的影响,我们可以这样分析:如果我们以速度 1 改变 a,c 也以速度 1 改变。而 c 以速度 1 改变会导致 e 以速度 2 改变。因此,e 相对于 a 的变化率是 1×21 \times 21×2。

一般规则是,从一个节点到另一个节点的所有可能路径求和,将路径上每条边上的导数相乘。例如,要得到 e 相对于 b 的导数,我们计算:

∂e∂b=1×2+1×3\frac{\partial e}{\partial b} = 1 \times 2 + 1 \times 3∂b∂e​=1×2+1×3

这考虑了 b 通过 c 影响 e 的方式,以及它通过 d 影响 e 的方式。

这种“路径求和”规则实际上是多变量链式法则的另一种表达方式。

路径分解

在计算图中使用“路径求和”来计算导数时,一个主要挑战是路径数量可能迅速增加,导致计算复杂。

如上图所示,从 X到 Y有三个路径,从 Y 到 Z 又有另外三个路径。如果想通过求和所有路径来得到导数∂Z∂X\frac{\partial Z}{\partial X}∂X∂Z​,我们需要对3×3=93 \times 3 = 9 3×3=9 条路径进行求和:

∂Z∂X=αδ+αϵ+αζ+βδ+βϵ+βζ+γδ+γϵ+γζ\frac{\partial Z}{\partial X} = \alpha\delta + \alpha\epsilon + \alpha\zeta + \beta\delta + \beta\epsilon + \beta\zeta + \gamma\delta + \gamma\epsilon + \gamma\zeta∂X∂Z​=αδ+αϵ+αζ+βδ+βϵ+βζ+γδ+γϵ+γζ

虽然这里只有九条路径,但随着计算图变得更复杂,路径数量可能会呈指数级增长。

为了更高效地处理这个问题,我们可以将路径求和进行分解:

∂Z∂X=(α+β+γ)(δ+ϵ+ζ)\frac{\partial Z}{\partial X} = (\alpha + \beta + \gamma)(\delta + \epsilon + \zeta)∂X∂Z​=(α+β+γ)(δ+ϵ+ζ)

前向模式微分和反向模式微分是两种高效计算路径求和的算法,它们通过在每个节点合并路径来减少计算量。实际上,这两种算法都确保每条边只被计算一次。

前向模式微分从图的输入开始,向输出端移动。在每个节点,它累加所有输入路径的导数。这些路径代表了输入以不同方式影响该节点的多种途径。将它们相加,我们可以得到输入对该节点影响的总导数。

如果你上过微积分课程,可能会发现前向模式微分与你学到的内容非常相似,尽管你可能没有从计算图的角度考虑过。

另一方面,反向模式微分则从图的输出开始,向输入端移动。在每个节点,它合并所有从该节点出发的路径。

前向模式微分追踪一个输入如何影响每个节点,而反向模式微分追踪每个节点如何影响一个输出。换句话说,前向模式微分对每个节点应用算子 ∂∂X\frac{\partial}{\partial X} ∂X∂​,而反向模式微分对每个节点应用算子 ∂Z∂\frac{\partial Z}{\partial }∂∂Z​。

计算上的胜利

你可能会好奇,为什么人们会关注反向模式微分,它看起来像是在做前向模式同样的事情,只是方式不同。那么,它的优势在哪里呢?

让我们再次考虑我们最初的示例:

使用从 b 开始的前向模式微分,我们可以得到每个节点相对于 b 的导数。

我们已经计算了 ∂e∂b\frac{\partial e}{\partial b}∂b∂e​,即输出相对于一个输入的导数。

如果我们从 e 开始进行反向模式微分,我们将得到 e 相对于每个节点的导数:

当我们说反向模式微分给出了 e 相对于每个节点的导数时,我们确实是说每个节点。我们得到了 ∂e∂a\frac{\partial e}{\partial a}∂a∂e​和∂e∂b\frac{\partial e}{\partial b} ∂b∂e​,即 e 相对于两个输入的导数。前向模式微分提供了输出相对于单一输入的导数,而反向模式微分提供了所有这些导数。

对于这个图,这只是两倍的加速,但想象一下,如果一个函数有一百万个输入和一个输出。前向模式微分将要求我们一百万次遍历图以获得导数。反向模式微分可以一次性得到它们全部!一百万倍的加速是相当可观的!

在训练神经网络时,我们认为损失(描述神经网络性能有多差的值)是参数(描述网络行为的数字)的函数。我们希望计算损失相对于所有参数的导数,以用于梯度下降。现在,神经网络中通常有数百万甚至数千万的参数。因此,反向模式微分,在神经网络的背景下称为反向传播,给我们提供了巨大的加速!

(那么前向模式微分有没有更有意义的例子?是的,有的!当反向模式给出一个输出相对于所有输入的导数时,前向模式给出了所有输出相对于单一输入的导数。如果有人有一个有很多输出的函数,前向模式微分可以快得多。)

导数计算的历史与洞见

当我最初理解反向传播的概念时,第一反应是:“这不过是链式法则的应用而已!我们怎么花了这么长时间才意识到?”这种反应并不少见。如果你问:“有没有巧妙的方法来计算前馈神经网络中的导数?”答案似乎并不复杂。

然而,我认为这个问题实际上比它看起来要复杂得多。在反向传播被发明的年代,人们并没有专注于我们现在所研究的前馈神经网络,而且使用导数来训练这些网络并不是一个显而易见的方法。只有当你意识到可以高效计算这些导数时,这种方法才变得显而易见。

更糟糕的是,人们很容易在初步思考时就认为循环依赖的任何部分都是不可能实现的。用导数来训练神经网络?肯定会陷入局部最小值。而且,计算所有这些导数显然会非常耗时。我们之所以没有立即开始列举这种方法可能不奏效的原因,只是因为我们知道它实际上是有效的。

这就是事后聪明的好处,一旦问题被正确定义,最困难的部分就已经解决了。

结论

在深入探索前向模式微分与反向模式微分之后,我们得到了一个核心认识:导数的计算成本远比我们预想的要低。这一发现在深度学习领域尤为重要,同样也适用于其他需要高效计算导数的场合。

前向模式微分

前向模式微分(Forward Mode Differentiation)是一种计算导数的方法,它按照计算图的顺序,从输入到输出逐个计算每个节点的导数。在机器学习和神经网络的背景下,这意味着你计算输出相对于每个输入变量的偏导数。

工作原理

计算图:首先,将计算过程表示为一个计算图。计算图是一系列计算步骤的图形表示,其中的节点表示变量,边表示操作。正向传播:在正向传播阶段,计算图从输入到输出执行计算,计算图中每步的结果。计算导数:在前向模式微分中,同时计算每个操作的局部导数(或雅可比矩阵),并将这些导数沿着计算图正向传播,直到达到输出。累积导数:在传播过程中,每个节点的导数会累积前一步的导数和当前操作的局部导数。

特点

应用场景

反向模式微分

反向模式微分(Reverse Mode Differentiation)是一种在计算图中从输出向输入方向计算导数的方法。这种方法在深度学习和神经网络训练中尤为重要,因为它允许高效地计算损失函数相对于网络参数的梯度,这是梯度下降和其他优化算法的基础。

工作原理

计算图:首先,将计算过程表示为一个计算图。计算图是一系列计算步骤的图形表示,其中的节点表示变量,边表示操作。正向传播:在正向传播阶段,计算图从输入到输出执行计算,计算图中每步的结果。反向传播:在反向模式微分中,我们从输出开始,反向计算输出相对于每个参数的导数。这个过程遵循链式法则,每一步的导数计算依赖于前一步的结果。累积梯度:在反向传播过程中,每个节点的梯度会累积前一步的梯度和当前操作的局部导数。

特点

应用场景

效率对比

选择前向模式微分还是反向模式微分取决于具体问题的需求。在大多数深度学习应用中,由于模型参数的数量通常远大于输入数量,反向模式微分是首选方法。然而,在某些特定的优化问题或当需要计算雅可比矩阵时,前向模式微分可能更合适。理解这两种方法的差异对于选择合适的自动微分策略至关重要。

反向传播和前向模式微分展示了如何通过线性化和动态规划等技巧来高效计算导数。理解这些技术可以帮助我们更有效地计算涉及导数的其他表达式。

反向传播不仅是一种计算工具,它还提供了一个视角,帮助我们理解导数在模型中的流动方式,这对于解决某些模型难以优化的问题至关重要,如循环神经网络中的梯度消失问题。

参考