7.1_为反向传播的自动化创造条件

7.1 为反向传播的自动化创造条件

在实现反向传播的自动化之前,我们先思考一下变量和函数之间的关系。首先从函数的角度来考虑,即思考“从函数的角度如何看待变量”。从函数的角度来看,变量是以输入和输出的形式存在的。如图7-2左图所示,函数的变量包括“输入变量”(input)和“输出变量”(output)(图中的虚线表示引用)。


图7-2 从函数的角度来看其与变量的关系(左图)和从变量的角度来看其与函数的关系(右图)

那么从变量的角度来看,函数是什么样的呢?这里要强调的是变量是由函数“创造”的。也就是说,函数是变量的“父母”,是creator(创造者)。如果变量没有作为创造者的函数,我们就可以认为它是由非函数创造的,比如用户给出的变量。

下面在代码中实现图7-2所示的函数和变量之间的“连接”。我们让这个“连接”在执行普通计算(正向传播)的那一刻创建。为此,先在Variable类中添加以下代码。

steps/step07.py

class Variable: def__init__(self,data): self.data  $\equiv$  data self.grad  $=$  None self creator  $\equiv$  None def setCreator(self,func): self creator  $\equiv$  func

上面的代码添加了一个名为creator的实例变量,之后添加了用于设置creator的set creator方法。下面我们在Function类中添加以下代码。

steps/step07.py

class Function: def__call__(self,input): x  $=$  input.data y  $=$  self.forward(x) output  $=$  Variable(y) output.setCreator(self)#让输出变量保存创造者信息 self.output  $\equiv$  input #也保存输出变量 returnoutput

上面的代码通过正向传播的计算,创建了一个名为output的Variable实例。对于创建的output变量,代码让它保存了“我(函数本身)是创造者”的信息。这是动态建立“连接”这一机制的核心。为了兼顾下一个步骤,这里将输出

设置为实例变量output。

DeZero 的动态计算图的原理是在执行实际的计算时,在变量这个“箱子”里记录它的“连接”。Chainer 和 PyTorch 也采用了类似的机制。

这样一来,Variable和Function之间就有了“连接”,我们就可以反向遍历计算图了。具体的实现代码如下所示。

A == Square()

B = Exp()

C = Square()

x=\mathbf{x} = Variable(np.array(0.5))

a = A(x)

b = B(a)

y=C(b)y = C(b)

# 反向遍历计算图的节点

assert y creator == C
assert y creator_input == b
assert y creator_inputCreator == B
assert y creator_inputCreator_input == a
assert y creator_inputCreator_inputCreator == A
assert y creator_inputCreator_inputCreator_input == x

首先介绍一下assert(断言)语句,它的用法是assert...。如果这里的...不为True,就会抛出异常。因此可以使用assert语句来检查条件是否得到满足。上面的代码在运行时没有发生任何问题(没有抛出异常),这意味着assert语句的所有条件都得到了满足。

上面的代码通过Variable实例变量creator找到前一个Function,然后通过Function的实例变量input找到前一个Variable。它们的连接方式如图7-3所示。


图7-3 以y为起点反向遍历计算图

如图7-3所示,计算图是由函数和变量之间的“连接”构建而成的。更重要的是,这个“连接”是在计算实际发生的时候(数据在正向传播中流转的时候)形成的。变量和函数连接的这个特征就是Define-by-Run。换言之,“连接”是通过数据的流转建立起来的。

图7-3这种带有“连接”的数据结构叫作连接节点。节点是构成图的一个元素,连接则代表对另一个节点的引用。也就是说,我们用了一个叫作“连接节点”的数据结构来表示计算图。