36.2 深度学习研究中的应用示例 有很多与深度学习相关的研究使用了 double backprop。比如参考文献 [21] 的论文中优化了图 36-1 中的式子。
L = E x ~ ∼ P g [ D ( x ~ ) ] − E x ∼ P r [ D ( x ) ] + λ E x ^ ∼ P x ^ [ ( ∥ ∇ x ^ D ( x ^ ) ∥ 2 − 1 ) 2 ] L = \underset {\tilde {\boldsymbol {x}} \sim \mathbb {P} _ {g}} {\mathbb {E}} [ D (\tilde {\boldsymbol {x}}) ] - \underset {\boldsymbol {x} \sim \mathbb {P} _ {r}} {\mathbb {E}} [ D (\boldsymbol {x}) ] + \lambda \underset {\hat {\boldsymbol {x}} \sim \mathbb {P} _ {\hat {\boldsymbol {x}}}} {\mathbb {E}} \left[ \left(\| \nabla_ {\hat {\boldsymbol {x}}} D (\hat {\boldsymbol {x}}) \| _ {2} - 1\right) ^ {2} \right] L = x ~ ∼ P g E [ D ( x ~ )] − x ∼ P r E [ D ( x )] + λ x ^ ∼ P x ^ E [ ( ∥ ∇ x ^ D ( x ^ ) ∥ 2 − 1 ) 2 ] 图36-1中需要注意的是在要优化的式子中引入了梯度(梯度是对张量的各个元素的导数)这一点。该梯度可以通过第1次反向传播求出。之后使用梯度计算函数 L L L ,为了优化函数 L L L ,再进行第2次反向传播。
double backprop 就以这种方式应用于最新的研究。除了 WGAN-GP, MAML(参考文献 [22]) 和 TRPO(参考文献 [23]) 等著名研究都使用了 double
backprop的功能。
TRPO使用double backprop来计算黑塞矩阵和向量的积。使用double backprop可以使计算更有效率。接下来的专栏部分将介绍黑塞矩阵和向量的积。
★★★★★★★★ 以上就是第3阶段的内容。在这个阶段,我们修改DeZero实现了double backprop,由此能够求出高阶导数,并实现牛顿法。从下一个步骤开始,我们将以实现神经网络为目标修改当前的DeZero。
专栏:牛顿法和double backprop的补充知识 本专栏将对第3阶段的内容进行补充说明。首先会介绍输入为向量时的牛顿法,之后介绍牛顿法之外的其他方法,最后介绍double backprop的实际应用示例。本专栏使用了大量的数学表达式,难度较高。如果读者觉得难以理解,可以跳过,继续阅读后面的章节(本专栏的内容与后面的内容没有太大关联)。
多变量函数的牛顿法 我们在第3阶段实现了牛顿法,当时用牛顿法求出了式子 y = x 4 − 2 x 2 y = x^4 - 2x^2 y = x 4 − 2 x 2 的最小值。从式子中可以看出,输入变量只有 x x x 。因此,准确来说,我们实现的是输入变量为一个变量(标量)时的牛顿法。
现在来看看输入是多维数组时的牛顿法。这里考虑输入变量是向量 x \pmb{x} x ,函数为 y = f ( x ) y = f(\pmb{x}) y = f ( x ) 的情况。假设 x \pmb{x} x 是向量,有 n n n 个元素,即 x = ( x 1 , x 2 , … , x n ) \pmb{x} = (x_{1}, x_{2}, \dots, x_{n}) x = ( x 1 , x 2 , … , x n ) 。
关于符号的字体,本书采用的表示方法为如果变量是向量,则使用黑斜体;如果变量是标量,则使用普通字体。
C.1是对 y = f ( x ) y = f(\pmb {x}) y = f ( x ) 应用牛顿法后的式子。
x ← x − [ ∇ 2 f ( x ) ] − 1 ∇ f ( x ) (C.1) \boldsymbol {x} \leftarrow \boldsymbol {x} - \left[ \nabla^ {2} f (\boldsymbol {x}) \right] ^ {- 1} \nabla f (\boldsymbol {x}) \tag {C.1} x ← x − [ ∇ 2 f ( x ) ] − 1 ∇ f ( x ) ( C.1 ) 首先来解释一下这些符号。式子C.1中的 ∇ f ( x ) \nabla f(\pmb{x}) ∇ f ( x ) 表示梯度。梯度是对 x \pmb{x} x 的各元素的导数。 ∇ f ( x ) \nabla f(\pmb{x}) ∇ f ( x ) 的元素如下所示。
∇ f ( x ) = ( ∂ f ∂ x 1 ∂ f ∂ x 2 ⋮ ∂ f ∂ x n ) (C.2) \nabla f (\boldsymbol {x}) = \left( \begin{array}{c} \frac {\partial f}{\partial x _ {1}} \\ \frac {\partial f}{\partial x _ {2}} \\ \vdots \\ \frac {\partial f}{\partial x _ {n}} \end{array} \right) \tag {C.2} ∇ f ( x ) = ∂ x 1 ∂ f ∂ x 2 ∂ f ⋮ ∂ x n ∂ f ( C.2 ) 另外, ∇ 2 f ( x ) \nabla^2 f(\pmb{x}) ∇ 2 f ( x ) 是黑塞矩阵,黑塞矩阵的式子如下所示。
∇ 2 f ( x ) = ( ∂ 2 f ∂ x 1 2 ∂ 2 f ∂ x 1 ∂ x 2 … ∂ 2 f ∂ x 1 ∂ x n ∂ 2 f ∂ x 2 ∂ x 1 ∂ 2 f ∂ x 2 2 … ∂ 2 f ∂ x 2 ∂ x n ⋮ ⋮ ⋱ ⋮ ∂ 2 f ∂ x n ∂ x 1 ∂ 2 f ∂ x n ∂ x 2 … ∂ 2 f ∂ x n 2 ) (C.3) \nabla^ {2} f (\boldsymbol {x}) = \left( \begin{array}{c c c c} \frac {\partial^ {2} f}{\partial x _ {1} ^ {2}} & \frac {\partial^ {2} f}{\partial x _ {1} \partial x _ {2}} & \dots & \frac {\partial^ {2} f}{\partial x _ {1} \partial x _ {n}} \\ \frac {\partial^ {2} f}{\partial x _ {2} \partial x _ {1}} & \frac {\partial^ {2} f}{\partial x _ {2} ^ {2}} & \dots & \frac {\partial^ {2} f}{\partial x _ {2} \partial x _ {n}} \\ \vdots & \vdots & \ddots & \vdots \\ \frac {\partial^ {2} f}{\partial x _ {n} \partial x _ {1}} & \frac {\partial^ {2} f}{\partial x _ {n} \partial x _ {2}} & \dots & \frac {\partial^ {2} f}{\partial x _ {n} ^ {2}} \end{array} \right) \tag {C.3} ∇ 2 f ( x ) = ∂ x 1 2 ∂ 2 f ∂ x 2 ∂ x 1 ∂ 2 f ⋮ ∂ x n ∂ x 1 ∂ 2 f ∂ x 1 ∂ x 2 ∂ 2 f ∂ x 2 2 ∂ 2 f ⋮ ∂ x n ∂ x 2 ∂ 2 f … … ⋱ … ∂ x 1 ∂ x n ∂ 2 f ∂ x 2 ∂ x n ∂ 2 f ⋮ ∂ x n 2 ∂ 2 f ( C.3 ) 如式子C.3所示,黑塞矩阵是对 x \pmb{x} x 的两个元素的导数。由于它是两个元素的组合,所以被定义为矩阵的形式。
梯度 ∇ f ( x ) \nabla f(\pmb{x}) ∇ f ( x ) 也可以写成 ∂ f ∂ x \frac{\partial f}{\partial \pmb{x}} ∂ x ∂ f 。黑塞矩阵 ∇ f ( x ) \nabla f(\pmb{x}) ∇ f ( x ) 也可以写成 ∂ 2 f ∂ x ∂ x T \frac{\partial^2 f}{\partial \pmb{x} \partial \pmb{x}^{\mathrm{T}}} ∂ x ∂ x T ∂ 2 f 。
式子C.1利用梯度和黑塞矩阵更新 x \pmb{x} x (式子C.1的 [ ∇ 2 f ( x ) ] − 1 [\nabla^2 f(\pmb {x})]^{-1} [ ∇ 2 f ( x ) ] − 1 表示黑塞矩阵 ∇ 2 f ( x ) \nabla^{2}f(\pmb {x}) ∇ 2 f ( x ) 的逆矩阵)。这时, x \pmb{x} x 会在梯度方向上更新,并通过黑塞矩阵的逆矩阵调整移动距离。利用黑塞矩阵这一二阶导数的信息,可以使输入变量更积极地前进,更快到达目的地。遗憾的是,牛顿法很少在机器学习,特别是神经网络中使用。
牛顿法的问题 在机器学习等领域,牛顿法有一个很大的问题。这个问题就是当参数数量增加时,牛顿法的黑塞矩阵,准确来说是黑塞矩阵的逆矩阵,其计算复杂度会变大。具体来说,当参数数量为 n n n 时,需要数量级为 n 2 n^2 n 2 的内存空间。另外, n × n n \times n n × n 逆矩阵的计算需要数量级为 n 3 n^3 n 3 的计算量。
神经网络的参数数量超过100万是很常见的事情。如果用牛顿法更新100万个参数,则需要一个大小为100万 × 100 \times 100 × 100 万的黑塞矩阵,然而,很少有内存能容纳如此庞大的矩阵。
由于牛顿法在很多情况下不是一个现实的方案,所以有人提出其他方法来
代替它,其中就有拟牛顿法。拟牛顿法是近似牛顿法中的黑塞矩阵逆矩阵的方法的总称(拟牛顿法指的不是某个具体的方法)。人们已经提出了几种具体的方法,其中最著名的是L-BFGS,它仅根据梯度来近似黑塞矩阵。PyTorch实现了L-BFGS(参考文献[20]),我们可以尝试使用它。不过目前在深度学习领域,SGD、Momentum、Adam等只使用梯度进行优化的算法才是主流,L-BFGS等拟牛顿法并不常用。
double backprop的用途:黑塞矩阵和向量的积 最后笔者对double backprop的内容进行补充。double backprop可以用来计算黑塞矩阵和向量的积。前面提到过,当元素数量很多时,计算黑塞矩阵的开销是巨大的。但是,如果只需要黑塞矩阵和向量的积这一“结果”,我们则可以使用double backprop来快速计算它。
假设有 y = f ( x ) y = f(\pmb{x}) y = f ( x ) 和 v \pmb{v} v , ∇ 2 f ( x ) \nabla^2 f(\pmb{x}) ∇ 2 f ( x ) 是黑塞矩阵,求 ∇ 2 f ( x ) v \nabla^2 f(\pmb{x})\pmb{v} ∇ 2 f ( x ) v ,即黑塞矩阵 ∇ 2 f ( x ) \nabla^2 f(\pmb{x}) ∇ 2 f ( x ) 和向量 v \pmb{v} v 的积。为此,我们需要将式子变换成下面这样。
∇ 2 f ( x ) v = ∇ ( v T ∇ f ( x ) ) (C.4) \nabla^ {2} f (\boldsymbol {x}) \boldsymbol {v} = \nabla \left(\boldsymbol {v} ^ {\mathrm {T}} \nabla f (\boldsymbol {x})\right) \tag {C.4} ∇ 2 f ( x ) v = ∇ ( v T ∇ f ( x ) ) ( C.4 ) 只要把左右两边的元素写出来,就可以看出该变换是成立的。这里将向量的元素数量限制为2。展开式子后,结果如下所示。
∇ 2 f ( x ) v = ( ∂ 2 f ∂ x 1 2 ∂ 2 f ∂ x 1 ∂ x 2 ∂ 2 f ∂ x 2 ∂ x 1 ∂ 2 f ∂ x 2 2 ) ( v 1 v 2 ) = ( ∂ 2 f ∂ x 1 2 v 1 + ∂ 2 f ∂ x 1 ∂ x 2 v 2 ∂ 2 f ∂ x 2 ∂ x 1 v 1 + ∂ 2 f ∂ x 2 2 v 2 ) \begin{array}{l} \nabla^ {2} f (\boldsymbol {x}) \boldsymbol {v} = \left( \begin{array}{c c} \frac {\partial^ {2} f}{\partial x _ {1} ^ {2}} & \frac {\partial^ {2} f}{\partial x _ {1} \partial x _ {2}} \\ \frac {\partial^ {2} f}{\partial x _ {2} \partial x _ {1}} & \frac {\partial^ {2} f}{\partial x _ {2} ^ {2}} \end{array} \right) \left( \begin{array}{c} v _ {1} \\ v _ {2} \end{array} \right) \\ = \left( \begin{array}{c} \frac {\partial^ {2} f}{\partial x _ {1} ^ {2}} v _ {1} + \frac {\partial^ {2} f}{\partial x _ {1} \partial x _ {2}} v _ {2} \\ \frac {\partial^ {2} f}{\partial x _ {2} \partial x _ {1}} v _ {1} + \frac {\partial^ {2} f}{\partial x _ {2} ^ {2}} v _ {2} \end{array} \right) \\ \end{array} ∇ 2 f ( x ) v = ( ∂ x 1 2 ∂ 2 f ∂ x 2 ∂ x 1 ∂ 2 f ∂ x 1 ∂ x 2 ∂ 2 f ∂ x 2 2 ∂ 2 f ) ( v 1 v 2 ) = ( ∂ x 1 2 ∂ 2 f v 1 + ∂ x 1 ∂ x 2 ∂ 2 f v 2 ∂ x 2 ∂ x 1 ∂ 2 f v 1 + ∂ x 2 2 ∂ 2 f v 2 ) ∇ ( v T ∇ f ( x ) ) = ∇ ( ( v 1 v 2 ) ( ∂ f ∂ x 1 ∂ f ∂ x 2 ) ) = ∇ ( ∂ f ∂ x 1 v 1 + ∂ f ∂ x 2 v 2 ) = ( ∂ 2 f ∂ x 1 2 v 1 + ∂ 2 f ∂ x 1 ∂ x 2 v 2 ∂ 2 f ∂ x 2 ∂ x 1 v 1 + ∂ 2 f ∂ x 2 2 v 2 ) \begin{array}{l} \nabla (\boldsymbol {v} ^ {\mathrm {T}} \nabla f (\boldsymbol {x})) = \nabla (\left( \begin{array}{c c} v _ {1} & v _ {2} \end{array} \right) \left( \begin{array}{c} \frac {\partial f}{\partial x _ {1}} \\ \frac {\partial f}{\partial x _ {2}} \end{array} \right)) \\ = \nabla \left(\frac {\partial f}{\partial x _ {1}} v _ {1} + \frac {\partial f}{\partial x _ {2}} v _ {2}\right) \\ = \left( \begin{array}{c} \frac {\partial^ {2} f}{\partial x _ {1} ^ {2}} v _ {1} + \frac {\partial^ {2} f}{\partial x _ {1} \partial x _ {2}} v _ {2} \\ \frac {\partial^ {2} f}{\partial x _ {2} \partial x _ {1}} v _ {1} + \frac {\partial^ {2} f}{\partial x _ {2} ^ {2}} v _ {2} \end{array} \right) \\ \end{array} ∇ ( v T ∇ f ( x )) = ∇ ( ( v 1 v 2 ) ( ∂ x 1 ∂ f ∂ x 2 ∂ f ) ) = ∇ ( ∂ x 1 ∂ f v 1 + ∂ x 2 ∂ f v 2 ) = ( ∂ x 1 2 ∂ 2 f v 1 + ∂ x 1 ∂ x 2 ∂ 2 f v 2 ∂ x 2 ∂ x 1 ∂ 2 f v 1 + ∂ x 2 2 ∂ 2 f v 2 ) 虽然这里将向量的元素数量限制为2,但其实我们很容易就能将其扩展到元素数量为 n n n 的情况。由此可知,式子C.4成立。现在再来看一下式子C.4。式子C.4的右项表示先求出向量 v \pmb{v} v 与梯度 ∇ f ( x ) \nabla f(\pmb{x}) ∇ f ( x ) 的积,即向量的内积,然后针对结果进一步求梯度。于是,我们就不需要再创建黑塞矩阵,由此可以提高计算效率。
现在我们尝试用DeZero来求黑塞矩阵和向量的积。下面是一个使用元素数量为2的向量进行计算的例子(这里提前使用了F.matmul函数来计算矩阵的乘积)。
import numpy as np
fromdezero import Variable
importdezero-functionsasF
$\mathbf{x} =$ Variable(np.array([1.0,2.0]))
$\mathbf{v} =$ Variable(np.array([4.0,5.0]))
deff(x): $\mathrm{t} = \mathrm{x}$ $^{**}2$ $\mathbf{y} = \mathbf{F}$ .sum(t) return y
$y = f(x)$
y-backwardcreate_graph=True)
gx=x.grad
x.cleargrad()
$\mathbf{z} = \mathbf{F}$ .matmul(v,gx)
z.backup()
print(x.grad)运行结果
variable([8.10.])
上面的代码相当于式子 ∇ ( v T ∇ f ( x ) ) \nabla (\pmb{v}^{\mathrm{T}}\nabla f(\pmb{x})) ∇ ( v T ∇ f ( x )) 。 v T ∇ f ( x ) \pmb{v}^{\mathrm{T}}\nabla f(\pmb{x}) v T ∇ f ( x ) 的计算相当于 z = F z = F z = F .matmul(v, gx)。使用z.backup()进一步求z的梯度,这样就能求出黑塞矩阵和向量的积了。顺便说一下,上面输出的是正确的结果。以上就是本专栏的内容。
第4阶段 创建神经网络 前面我们主要处理的变量是标量,但在机器学习领域,张量(多维数组)扮演着重要的角色。第4阶段的目标是将DeZero扩展到机器学习,尤其是神经网络领域。为此,我们首先要使其能够用张量进行计算。
在机器学习中,计算导数的工作往往很复杂,不过DeZero已经具备了自动微分的基础能力。因此,接下来我们要做的工作在技术上并不难。之后,我们的主要任务是基于DeZero自动微分的能力,增加机器学习所需的功能。完成这些工作后,我们将检验DeZero的实力,尝试用它解决一些机器学习的问题。
在本阶段,我们花费大量时间打造的DeZero会在深度学习(神经网络)领域开花结果。到这一阶段结束时,DeZero将成长为真正的深度学习框架。下面让我们进入第4阶段!
步骤37 处理张量 前面,我们处理的变量主要是标量。但在机器学习中,向量和矩阵等张量才是主角。本步骤将讨论使用张量时需要注意的地方,并为扩展DeZero做准备。此外,通过本步骤,我们还会得知张量可以直接用于此前已经实现的DeZero函数。