36.2_深度学习研究中的应用示例

36.2 深度学习研究中的应用示例

有很多与深度学习相关的研究使用了 double backprop。比如参考文献 [21] 的论文中优化了图 36-1 中的式子。

L=Ex~Pg[D(x~)]ExPr[D(x)]+λEx^Px^[(x^D(x^)21)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]

图36-1中需要注意的是在要优化的式子中引入了梯度(梯度是对张量的各个元素的导数)这一点。该梯度可以通过第1次反向传播求出。之后使用梯度计算函数 LL ,为了优化函数 LL ,再进行第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=x42x2y = x^4 - 2x^2 的最小值。从式子中可以看出,输入变量只有 xx 。因此,准确来说,我们实现的是输入变量为一个变量(标量)时的牛顿法。

现在来看看输入是多维数组时的牛顿法。这里考虑输入变量是向量 x\pmb{x} ,函数为 y=f(x)y = f(\pmb{x}) 的情况。假设 x\pmb{x} 是向量,有 nn 个元素,即 x=(x1,x2,,xn)\pmb{x} = (x_{1}, x_{2}, \dots, x_{n})

关于符号的字体,本书采用的表示方法为如果变量是向量,则使用黑斜体;如果变量是标量,则使用普通字体。

C.1是对 y=f(x)y = f(\pmb {x}) 应用牛顿法后的式子。

xx[2f(x)]1f(x)(C.1)\boldsymbol {x} \leftarrow \boldsymbol {x} - \left[ \nabla^ {2} f (\boldsymbol {x}) \right] ^ {- 1} \nabla f (\boldsymbol {x}) \tag {C.1}

首先来解释一下这些符号。式子C.1中的 f(x)\nabla f(\pmb{x}) 表示梯度。梯度是对 x\pmb{x} 的各元素的导数。 f(x)\nabla f(\pmb{x}) 的元素如下所示。

f(x)=(fx1fx2fxn)(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}

另外, 2f(x)\nabla^2 f(\pmb{x}) 是黑塞矩阵,黑塞矩阵的式子如下所示。

2f(x)=(2fx122fx1x22fx1xn2fx2x12fx222fx2xn2fxnx12fxnx22fxn2)(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}

如式子C.3所示,黑塞矩阵是对 x\pmb{x} 的两个元素的导数。由于它是两个元素的组合,所以被定义为矩阵的形式。

梯度 f(x)\nabla f(\pmb{x}) 也可以写成 fx\frac{\partial f}{\partial \pmb{x}} 。黑塞矩阵 f(x)\nabla f(\pmb{x}) 也可以写成 2fxxT\frac{\partial^2 f}{\partial \pmb{x} \partial \pmb{x}^{\mathrm{T}}}

式子C.1利用梯度和黑塞矩阵更新 x\pmb{x} (式子C.1的 [2f(x)]1[\nabla^2 f(\pmb {x})]^{-1} 表示黑塞矩阵 2f(x)\nabla^{2}f(\pmb {x}) 的逆矩阵)。这时, x\pmb{x} 会在梯度方向上更新,并通过黑塞矩阵的逆矩阵调整移动距离。利用黑塞矩阵这一二阶导数的信息,可以使输入变量更积极地前进,更快到达目的地。遗憾的是,牛顿法很少在机器学习,特别是神经网络中使用。

牛顿法的问题

在机器学习等领域,牛顿法有一个很大的问题。这个问题就是当参数数量增加时,牛顿法的黑塞矩阵,准确来说是黑塞矩阵的逆矩阵,其计算复杂度会变大。具体来说,当参数数量为 nn 时,需要数量级为 n2n^2 的内存空间。另外, n×nn \times n 逆矩阵的计算需要数量级为 n3n^3 的计算量。

神经网络的参数数量超过100万是很常见的事情。如果用牛顿法更新100万个参数,则需要一个大小为100万 ×100\times 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})v\pmb{v}2f(x)\nabla^2 f(\pmb{x}) 是黑塞矩阵,求 2f(x)v\nabla^2 f(\pmb{x})\pmb{v} ,即黑塞矩阵 2f(x)\nabla^2 f(\pmb{x}) 和向量 v\pmb{v} 的积。为此,我们需要将式子变换成下面这样。

2f(x)v=(vTf(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。展开式子后,结果如下所示。

2f(x)v=(2fx122fx1x22fx2x12fx22)(v1v2)=(2fx12v1+2fx1x2v22fx2x1v1+2fx22v2)\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}
(vTf(x))=((v1v2)(fx1fx2))=(fx1v1+fx2v2)=(2fx12v1+2fx1x2v22fx2x1v1+2fx22v2)\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}

虽然这里将向量的元素数量限制为2,但其实我们很容易就能将其扩展到元素数量为 nn 的情况。由此可知,式子C.4成立。现在再来看一下式子C.4。式子C.4的右项表示先求出向量 v\pmb{v} 与梯度 f(x)\nabla f(\pmb{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.])

上面的代码相当于式子 (vTf(x))\nabla (\pmb{v}^{\mathrm{T}}\nabla f(\pmb{x}))vTf(x)\pmb{v}^{\mathrm{T}}\nabla f(\pmb{x}) 的计算相当于 z=Fz = F .matmul(v, gx)。使用z.backup()进一步求z的梯度,这样就能求出黑塞矩阵和向量的积了。顺便说一下,上面输出的是正确的结果。以上就是本专栏的内容。

第4阶段

创建神经网络

前面我们主要处理的变量是标量,但在机器学习领域,张量(多维数组)扮演着重要的角色。第4阶段的目标是将DeZero扩展到机器学习,尤其是神经网络领域。为此,我们首先要使其能够用张量进行计算。

在机器学习中,计算导数的工作往往很复杂,不过DeZero已经具备了自动微分的基础能力。因此,接下来我们要做的工作在技术上并不难。之后,我们的主要任务是基于DeZero自动微分的能力,增加机器学习所需的功能。完成这些工作后,我们将检验DeZero的实力,尝试用它解决一些机器学习的问题。

在本阶段,我们花费大量时间打造的DeZero会在深度学习(神经网络)领域开花结果。到这一阶段结束时,DeZero将成长为真正的深度学习框架。下面让我们进入第4阶段!

步骤37

处理张量

前面,我们处理的变量主要是标量。但在机器学习中,向量和矩阵等张量才是主角。本步骤将讨论使用张量时需要注意的地方,并为扩展DeZero做准备。此外,通过本步骤,我们还会得知张量可以直接用于此前已经实现的DeZero函数。