15.2_当前的DeZero
15.2 当前的 DeZero
当前的DeZero会按照图15-4所示的顺序进行反向传播吗?先来看看当前Variable类的实现代码。注意看下面的阴影部分。
steps/step14.py
class Variable: def backward(self): if self.grad is None: self.grad $=$ np.ones_like(self.data) funcs $=$ [self creator] while funcs: f $=$ funcs.pop() gys $=$ [output.grad for output in f.outputs] gxs $=$ f.backup(\*gys) if not isinstance(gxs,tuple): gxs $=$ (gxs,) for x,gx in zip(f-inputs,gxs): if x.grad is None: x.grad $=$ gx else: x.grad $=$ x.grad +gx if x creator is not None: func.append(x creator)需要注意的是funcs列表。在while循环中,我们将待处理的候选函数添加到了funcs列表的末尾(funcs.append(x creator)),然后,从列表末尾取出下一个要处理的函数(funcs.pop())。根据这个处理流程,反向传播会按照图15-5所示的流程进行。

图15-5 当前DeZero的反向传播的流程
从图15-5可以看出,要处理的函数的顺序为D、C、A、B、A。问题有两个:一个是C后面是A;一个是函数A的反向传播被调用了两次。对比一下前面的代码,看看为什么会出现这样的问题。
首先,D被添加到funcs列表中,流程从状态[D]开始。接下来取出函数D,然后将D的输入变量(Dinputs)的创造者B和C添加到funcs列表中(图15-6)。

图15-6 函数D的反向传播(右侧为funcs列表相关的代码)
此时funcs列表的值是[B,C]。之后,该列表的最后一项C会被取出。然后C的输入变量的创造者A会被添加到列表中。此时,funcs列表的值变为B,A。

图15-7 函数C的反向传播和相应的代码
最后取出了列表末尾的A,问题就出在这里。这里本应该取出B,结果取出了A。

到目前为止,我们处理的都是笔直的计算图。对于这样的计算图,我们可以从列表中取出元素,无须考虑要处理的函数的顺序。这是因为从列表取出元素时,列表中总是只有一个元素。