



上一个步骤介绍了反向传播的机制。本步骤将扩展现有的 Variable 类和 Function 类,实现通过反向传播来求导的功能。首先是 Variable 类。
下面实现支持反向传播的 Variable 类。为此,我们要扩展 Variable 类,除普通值( data )之外,增加与之对应的导数值( grad )。阴影部分是新增加的代码。
上面的代码在类中增加了一个新的实例变量 grad 。实例变量 data 和 grad 都被设置为NumPy的多维数组( ndarray )。另外, grad 被初始化为 None ,我们要在通过反向传播实际计算导数时将其设置为求出的值。
梯度(gradient)是对包含多个变量的向量和矩阵等求导的导数。因此
Variable
类中增加了一个名为
grad
的变量,
grad
是gradient的缩写。
接下来是 Function 类。在前面的步骤中, Function 类实现了进行普通计算的正向传播( forward 方法)的功能。在此基础上,我们新增以下两个功能。
下面的代码实现了这两个功能。
在上面的代码中, __call__ 方法将输入的 input 设置为实例变量。这样一来,当调用 backward 方法时,向函数输入的 Variable 实例就可以作为 self.input 使用。
接下来实现具体函数的反向传播(
backward
)。首先从计算平方的
Square
类开始。由于
的导数是
,所以这个类可以按照如下方式实现。
steps/step06.py
class Square(Function):
def forward(self, x):
y=x ** 2
return y
def backward(self, gy):
x=self.input.data
gx=2 * x * gy
return gx
上面的代码增加了用于反向传播的
backward
方法。这个方法的参数
gy
是一个
ndarray
实例,它是从输出传播而来的导数。
backward
返回的结果是通过这个参数传播来的导数和“
的导数”的乘积。这个返回结果会进一步向输入方向传播。
接下来是计算
的
Exp
类。由于
,所以这个类可以按下面的方式实现。
steps/step06.py
class Exp(Function):
def forward(self, x):
y=np.exp(x)
return y
def backward(self, gy):
x=self.input.data
gx=np.exp(x) * gy
return gx
这样就做好准备工作了。下面我们尝试通过反向传播对 图6-1 的计算求导。
图6-1 进行反向传播的复合函数
首先编写 图6-1 所示的正向传播的代码。
steps/step06.py
A=Square() B=Exp() C=Square() x=Variable(np.array(0.5)) a=A(x) b=B(a) y=C(b)
接着通过反向传播计算 y 的导数。为此,我们需要按照与正向传播相反的顺序调用各函数的 backward 方法。 图6-2 是这个反向传播计算的计算图。
图6-2 反向传播的计算图
从 图6-2 可以看出各个函数的 backward 方法的调用顺序,也能看出应该将 backward 方法的结果赋给哪个变量的 grad 。下面是反向传播的实现。
steps/step06.py
y.grad=np.array(1.0) b.grad=C.backward(y.grad) a.grad=B.backward(b.grad) x.grad=A.backward(a.grad) print(x.grad)
3.297442541400256
反向传播从
开始。因此,我们将输出
y
的导数设为
np.array(1.0)
。之后,只要按照
C
→
B
→
A
的顺序调用
backward
方法即可。这样就可以对各变量求出导数。
运行上面的代码后,得到的 x.grad 的结果是 3.297442541400256 。这就是 y 对 x 的导数。顺带一提, 步骤4 的数值微分的结果是3.2974426293330694,这两个结果几乎一样。这说明反向传播的实现是正确的,更准确地说,这个实现大概率是正确的。
这样就完成了反向传播的实现。虽然我们得到了正确的运行结果,但是反向传播的顺序 C → B → A 是通过编码手动指定的。在下一个步骤,我们会把这项工作自动化。