正向/反向求导原理刨析
什么是正向求导?反向求导?给我一个复杂的函数求解作为例子
正向求导(Forward differentiation)和反向求导(Backward differentiation)都是自动微分(Automatic Differentiation)的技术。自动微分是计算机科学中一种用于求解函数导数的方法。正向求导和反向求导都可以高效地计算函数的导数,但它们在计算方式和适用场景上有所不同。
正向求导(Forward differentiation):
正向求导是从输入变量开始,沿着计算图逐步计算每个中间变量的导数。它适合于那些输入变量较少、输出变量较多的情况。
反向求导(Backward differentiation):
反向求导是从输出变量开始,沿着计算图逐步计算每个中间变量的导数。它适合于那些输入变量较多、输出变量较少的情况,如神经网络的训练过程中的反向传播(Backpropagation)算法。
假设我们有一个计算图,包含以下表达式:
a = x1 * x2 b = x3 + x4 c = a - b f = c^2
x1 ──┐
├─ [*] ─ a ─┐
x2 ──┘ │
├─ [-] ─ c ─ [^2] ─ f
x3 ──┐ │
├─ [+] ─ b ─┘
x4 ──┘
我们希望求解该计算图中的导数 df/dx1, df/dx2, df/dx3, df/dx4。下面分别使用正向求导和反向求导来计算导数。
正向求导(Forward differentiation):
- 首先,初始化导数值:dx1 = 1, dx2 = 0, dx3 = 0, dx4 = 0。
- 计算 a 的导数:da/dx1 = x2, da/dx2 = x1 * dx2 + dx1 * 1 = x1
- 计算 b 的导数:db/dx3 = 1, db/dx4 = 1
- 计算 c 的导数:dc/dx1 = da/dx1 = x2, dc/dx2 = da/dx2 = x1, dc/dx3 = -db/dx3 = -1, dc/dx4 = -db/dx4 = -1
- 计算 f 的导数:df/dx1 = 2c * dc/dx1 = 2c * x2, df/dx2 = 2c * dc/dx2 = 2c * x1, df/dx3 = 2c * dc/dx3 = -2c, df/dx4 = 2c * dc/dx4 = -2c
反向求导(Backward differentiation):
- 计算 f 关于 c 的导数:df/dc = 2c
- 计算 c 关于 a, b 的导数:dc/da = 1, dc/db = -1
- 计算 a 关于 x1, x2 的导数:da/dx1 = x2, da/dx2 = x1
- 计算 b 关于 x3, x4 的导数:db/dx3 = 1, db/dx4 = 1
- 应用链式法则计算最终导数:df/dx1 = df/dc * dc/da * da/dx1 = 2c * 1 * x2, df/dx2 = 2c * 1 * x1, df/dx3 = df/dc * dc/db * db/dx3 = 2c * (-1) * 1 = -2c, df/dx4 = 2c * (-1) * 1 = -2c
正向求导是从输入变量开始,按照计算图的顺序逐步计算导数。而反向求导则是从输出变量开始,按照计算图的逆序逐步计算导数。正向求导适用于输入变量较少、输出变量较多的情况;反向求导则适用于输入变量较多、输出变量较少的情况。
在这个例子中,正向求导首先计算了 a、b、c 和 f 的导数,然后根据链式法则得到关于 x1、x2、x3 和 x4 的导数。而反向求导则从输出变量 f 开始,依次计算关于 c、a、b 的导数,最后得到关于x1、x2、x3 和 x4 的导数。整个过程与正向求导相反,但最终都能得到正确的导数值。
总结一下,正向求导和反向求导的主要区别在于计算导数的顺序。正向求导从输入变量开始,按照计算图的顺序逐步计算导数;而反向求导从输出变量开始,按照计算图的逆序逐步计算导数。这两种方法在不同的场景下有各自的优势。
反向求导要存储中间变量是吗,这是为什么?
是的,反向求导(Backward differentiation)通常需要存储中间变量。这主要是因为在反向求导过程中,我们需要使用链式法则(Chain Rule)来计算每个中间变量的梯度。在这个过程中,我们需要用到前向传播(Forward pass)计算得到的中间变量的值,以及这些中间变量关于其他变量的梯度。
在神经网络的训练中,反向传播(Backpropagation)算法是利用反向求导计算梯度的典型应用。反向传播算法的过程如下:
- 前向传播:从输入层到输出层按顺序计算每一层的输出(中间变量)。
- 计算损失函数(Loss function)。
- 反向传播:从输出层开始,按逆序计算每一层的梯度。在这个过程中,我们需要用到前向传播得到的中间变量的值。
因此,在反向传播过程中,我们需要存储前向传播计算得到的中间变量的值,以便计算梯度。存储中间变量会占用额外的内存,但这是实现反向求导的必要开销。实际上,深度学习框架(如 TensorFlow 和 PyTorch)已经为我们优化了这个过程,使得存储和计算梯度变得高效。
在上面的例子中,反向求导存储了哪些中间变量?
在上面的例子中,我们有以下计算图:
a = x1 * x2 b = x3 + x4 c = a - b f = c^2
我们希望求解该计算图中的导数 df/dx1, df/dx2, df/dx3, df/dx4。在反向求导过程中,我们需要存储以下中间变量:
- a: 计算导数 da/dx1 和 da/dx2 时需要使用 a 的值。
- b: 计算导数 db/dx3 和 db/dx4 时需要使用 b 的值。
- c: 计算导数 dc/da、dc/db 以及 df/dc 时需要使用 c 的值。
因此,在这个例子中,我们需要存储 a、b 和 c 这三个中间变量。在实际应用中,深度学习框架会自动地存储和管理这些中间变量,以便在反向传播过程中计算梯度。
更复杂的例子
输入:x
隐藏层1:h1 = sigmoid(x*W1 + b1)
隐藏层2:h2 = tanh(h1*W2 + b2)
输出:y = softmax(h2*W3 + b3)
损失函数:cross_entropy(y, y_true)
计算图:
x ──┐
├─ [*] ── W1
│
├─ [+] ── b1
│
└─ [sigmoid] ── h1 ──┐
├─ [*] ── W2
│
├─ [+] ── b2
│
└─ [tanh] ───── h2 ──┐
├─ [*] ── W3
│
├─ [+] ── b3
│
└─ [softmax] ── y
│
y_true ──────────────────────────────────────────────┘
│
└─ [cross_entropy] ─ L
在这个神经网络示例中,我们有以下计算过程:
- 隐藏层1:h1 = sigmoid(x * W1 + b1)
- 隐藏层2:h2 = tanh(h1 * W2 + b2)
- 输出层:y = softmax(h2 * W3 + b3)
- 损失函数:L = cross_entropy(y, y_true)
我们的目标是计算 dL/dx,即损失函数 L 对输入 x 的梯度。
为了简化问题,我们先讨论一个更通用的情况:计算 dL/dh1、dL/dh2 和 dL/dy。在实际应用中,我们通常关心这些梯度,以便更新网络中的权重和偏置。然后,我们将讨论如何计算 dL/dx。
正向求导: 正向求导通常不是计算神经网络中梯度的首选方法,因为它需要分别计算每个权重和偏置对损失函数的梯度,计算复杂度较高。然而,为了说明正向求导的过程,我们仍然可以逐步计算梯度。
我会列出计算损失函数 L 关于输入 x、权重 W1、W2、W3 的梯度的正向求导过程。这里不涉及偏置项的梯度计算,但可以通过类似的方法进行。
- 计算 df/dx:
- 计算 dL/dy:根据交叉熵损失函数的定义,求解损失函数 L 关于 y 的导数。
- 计算 dy/dh2:求解 softmax 函数关于 h2 的导数。
- 计算 dh2/dh1:求解 tanh 函数关于 h1 的导数。
- 计算 dh1/dx:求解 sigmoid 函数关于 x 的导数。
- 应用链式法则计算 dL/dx:dL/dx = dL/dy * dy/dh2 * dh2/dh1 * dh1/dx。
- 计算 dL/dW1:
- 计算 dL/dh1:根据上面计算的 dL/dx,求解 dL/dh1。
- 计算 dh1/dW1:求解 sigmoid 函数关于 W1 的导数。
- 应用链式法则计算 dL/dW1:dL/dW1 = dL/dh1 * dh1/dW1。
- 计算 dL/dW2:
- 计算 dL/dh2:根据上面计算的 dL/dx,求解 dL/dh2。
- 计算 dh2/dW2:求解 tanh 函数关于 W2 的导数。
- 应用链式法则计算 dL/dW2:dL/dW2 = dL/dh2 * dh2/dW2。
- 计算 dL/dW3:
- 计算 dL/dy:根据交叉熵损失函数的定义,求解损失函数 L 关于 y 的导数。
- 计算 dy/dW3:求解 softmax 函数关于 W3 的导数。
- 应用链式法则计算 dL/dW3:dL/dW3 = dL/dy * dy/dW3。
正向求导的过程较为繁琐,计算效率较低。在神经网络中,我们通常使用反向求导(反向传播)来计算梯度,因为它能更高效地计算损失函数关于权重和偏置的梯度。
反向求导(反向传播): 反向传播是一种更高效的计算神经网络中梯度的方法。计算过程如下:
- 计算 dL/dy: 根据交叉熵损失函数的定义,我们可以求解损失函数 L 关于 y 的导数。
- 计算 dy/d(h2 * W3 + b3): 计算 softmax 函数关于 h2 * W3 + b3 的导数。
- 计算 d(h2 * W3 + b3)/dh2: 计算 h2 * W3 + b3 关于 h2 的导数。
- 计算 dL/dh2: 应用链式法则计算 dL/dh2:dL/dh2 = dL/dy * dy/d(h2 * W3 + b3) * d(h2 * W3 + b3)/dh2。
- 计算 dh2/d(h1 * W2 + b2): 计算 tanh 函数关于 h1 * W2 + b2 的导数。
- 计算 d(h1 * W2 + b2)/dh1: 计算 h1 * W2 + b2 关于 h1 的导数。
- 计算 dL/dh1: 应用链式法则计算 dL/dh1:dL/dh1 = dL/dh2 * dh2/d(h1 * W2 + b2) * d(h1 * W2 + b2)/dh1。
- 计算 dh1/d(x * W1 + b1): 计算 sigmoid 函数关于 x * W1 + b1 的导数。
- 计算 d(x * W1 + b1)/dx: 计算 x * W1 + b1 关于 x 的导数。
- 计算 dL/dx: 应用链式法则计算 dL/dx:dL/dx = dL/dh1 * dh1/d(x * W1 + b1) * d(x * W1 + b1)/dx。
- 计算 d(x * W1 + b1)/dW1: 计算 x * W1 + b1 关于 W1 的导数。
- 计算 dL/dW1: 应用链式法则计算 dL/dW1:dL/dW1 = dL/dh1 * dh1/d(x * W1 + b1) * d(x * W1 + b1)/dW1。
- 计算 d(h1 * W2 + b2)/dW2: 计算 h1 * W2 + b2 关于 W2 的导数。
- 计算 dL/dW2: 应用链式法则计算 dL/dW2:dL/dW2 = dL/dh2 * dh2/d(h1 * W2 + b2) * d(h1 * W2 + b2)/dW2。
- 计算 d(h2 * W3 + b3)/dW3: 计算 h2 * W3 + b3 关于 W3 的导数。
- 计算 dL/dW3: 应用链式法则计算 dL/dW3:dL/dW3 = dL/dy * dy/d(h2 * W3 + b3) * d(h2 * W3 + b3)/dW3。
通过以上步骤,我们已经计算了损失函数 L 关于输入 x、权重 W1、W2、W3 的梯度。接下来,我们还可以计算关于偏置项 b1、b2、b3 的梯度。
-
计算 d(x * W1 + b1)/db1: 计算 x * W1 + b1 关于 b1 的导数。
-
计算 dL/db1: 应用链式法则计算 dL/db1:dL/db1 = dL/dh1 * dh1/d(x * W1 + b1) * d(x * W1 + b1)/db1。
-
计算 d(h1 * W2 + b2)/db2: 计算 h1 * W2 + b2 关于 b2 的导数。
-
计算 dL/db2: 应用链式法则计算 dL/db2:dL/db2 = dL/dh2 * dh2/d(h1 * W2 + b2) * d(h1 * W2 + b2)/db2。
-
计算 d(h2 * W3 + b3)/db3: 计算 h2 * W3 + b3 关于 b3 的导数。
-
计算 dL/db3: 应用链式法则计算 dL/db3:dL/db3 = dL/dy * dy/d(h2 * W3 + b3) * d(h2 * W3 + b3)/db3。
现在我们已经计算了损失函数 L 关于所有权重(W1、W2、W3)和偏置项(b1、b2、b3)的梯度。利用这些梯度,我们可以使用梯度下降法或其他优化算法来更新神经网络的权重和偏置。
然后,我们可以使用梯度下降法或其他优化算法更新网络的权重和偏置,以达到训练神经网络的目的。
QA
Q:正向求导是否只用正着遍历一遍计算图,而反向求导需要先正着遍历一遍计算每个导数的值保存下来,再反着遍历一遍?
A:是的,您的理解是正确的。
正向求导(正向传播)是指从输入层到输出层的顺序计算导数。在计算过程中,需要正向遍历计算图,逐步计算每个节点的导数。这种方法计算梯度的效率较低,因为在求解复杂神经网络的梯度时,可能会产生很多重复计算。
反向求导(反向传播)是一种更高效的计算神经网络中梯度的方法。它首先进行一次正向传播,计算并保存中间结果。然后从输出层反向遍历到输入层,利用链式法则计算损失函数关于权重和偏置的梯度。在这个过程中,已经计算过的中间导数会被重复利用,从而降低了计算复杂度。
因此,在实际应用中,我们通常采用反向求导(反向传播)方法来计算神经网络中的梯度。
Q:可以说出它们的时间复杂度和空间复杂度具体是什么吗?
A:在这里,我们将讨论正向求导(正向传播)和反向求导(反向传播)在计算梯度时的时间复杂度和空间复杂度。但是,需要注意的是,这些复杂度依赖于特定神经网络的结构和实现方法,因此以下给出的是一个一般性的分析。
时间复杂度:
- 正向求导:由于正向求导需要从输入层到输出层计算每个节点的导数,其时间复杂度通常是 O(n^2),其中 n 是神经网络中节点的数量。这主要是因为在计算过程中可能存在大量重复计算。
- 反向求导:反向求导(反向传播)方法的时间复杂度通常是 O(n),其中 n 是神经网络中节点的数量。这是因为反向传播只需从输出层反向遍历到输入层一次,避免了大量重复计算。
空间复杂度:
- 正向求导:正向求导的空间复杂度通常是 O(n),其中 n 是神经网络中节点的数量。在计算过程中,正向求导需要存储每个节点的导数。
- 反向求导:反向求导(反向传播)方法的空间复杂度也通常是 O(n),其中 n 是神经网络中节点的数量。虽然反向传播需要存储中间结果,但是在计算过程中,中间结果会被依次计算和释放,因此空间复杂度与正向求导相当。
综上所述,在神经网络中计算梯度时,反向求导(反向传播)方法具有较低的时间复杂度和相当的空间复杂度,因此它是更高效的计算方法。
Q:为什么正向求导会有重复计算?如果之前已经算过了某个导数,下次也不会去计算这个导数,在哪里重复计算了呢?
A:抱歉,我在之前的回答中没有表述清楚。实际上,正向求导并不会产生重复计算。正向求导和反向求导都是对链式法则的应用,区别在于计算顺序和重复使用中间结果。
在正向求导中,我们从输入层逐层向输出层计算导数。然而,当我们需要计算网络中多个参数(如权重和偏置)的梯度时,我们可能需要在某些节点上多次应用链式法则。虽然这并不会导致重复计算,但正向求导在计算大型神经网络时可能效率较低,因为它需要为每个参数计算完整的导数。
相比之下,反向求导(反向传播)方法更高效地计算神经网络的梯度。在反向传播过程中,我们首先进行一次正向传播,计算并保存中间结果。然后从输出层反向遍历到输入层,利用链式法则计算损失函数关于权重和偏置的梯度。在这个过程中,已经计算过的中间导数会被重复利用,从而降低了计算复杂度。因此,反向传播算法在计算神经网络中的梯度时具有较高的效率。
总之,正向求导并不会产生重复计算,但它在计算大型神经网络的梯度时可能效率较低。而反向求导(反向传播)方法能更高效地计算神经网络中的梯度。
Q:正向求导是不是每求一个导数,就要遍历整个图,而反向求导只用遍历对应的链?
A:对于正向求导和反向求导,它们的遍历方式有所不同。
在正向求导中,确实需要对整个计算图进行遍历。正向求导从输入层开始,逐层向输出层计算导数。当我们需要计算网络中多个参数(如权重和偏置)的梯度时,我们需要对每个参数进行完整的导数计算。这意味着,对于每个参数,我们需要从输入层到输出层遍历整个计算图。因此,在某些情况下,正向求导可能效率较低。
反向求导(反向传播)方法则更高效。在反向传播过程中,我们首先进行一次正向传播,计算并保存中间结果。然后从输出层反向遍历到输入层,利用链式法则计算损失函数关于权重和偏置的梯度。在这个过程中,已经计算过的中间导数会被重复利用。因此,反向传播算法仅需要遍历一次正向传播路径,而不需要对每个参数单独进行遍历。
简而言之,正向求导需要对整个计算图进行遍历,而反向求导仅需要遍历正向传播路径。因此,反向求导(反向传播)在计算神经网络中的梯度时具有较高的效率。
Leave a comment