《深度学习入门——自制框架》读书笔记 第二章 用自然的代码表达
2. 用自然的代码表达
step12 可变长参数(正向)
主要是解决多输入多输出问题
例如Add函数:
class Add(Function):
def forward(self, x0, x1):
y = x0 + x1
return y
def add(x0, x1):
return Add()(x0, x1)
对Function类的改造如下:
class Function:
def __call__(self, *inputs):
xs = [x.data for x in inputs]
ys = self.forward(*xs) # 使用*号解包
if not isinstance(ys, tuple): # 对非元组情况的额外处理
ys = (ys,)
outputs = [Variable(as_array(y)) for y in ys]
for output in outputs:
output.set_creator(self)
self.inputs = inputs # 保存输入的变量
self.outputs = outputs
return outputs if len(outputs) > 1 else outputs[0]
# 若输出是标量,则直接返回标量,否则以列表形式返回
step13 可变长参数(反向)
多输入输出函数backward的实现
\[\begin{align}
& z = x + y \nonumber \\
\nonumber \\
& \frac{\partial z}{\partial x} = 1, \frac{\partial z}{\partial y} = 1 \nonumber
\end{align}
\]
所以对于add函数,反向传播给自变量的导数值就是因变量的导数值
class Add(Function):
def forward(self, x0, x1):
y = x0 + x1
return y
# ======== 添加backward代码 ==========
def backward(self, gy):
return gy, gy
让Variable类支持多输入输出的backward
class Variable:
...
def backward(self):
if self.grad is None:
self.grad = np.ones_like(self.data)
# ones_like创建一个ndarray实例,其形状和数据类型与self.data相同,数据全为1
# 即若self=[0.2 0.22],则创建实例[1, 1]
funcs = [self.creator] # 1. 获取函数
while funcs:
f = funcs.pop() # 获取函数
# =================== 修改部分 ============================
gys = [output.grad for output in f.outputs]
gxs = f.backward(*gys)
if not isinstance(gxs, tuple):
gxs = (gxs,)
for x,gxs in zip(f.inputs, gxs):
x.grad = gxs
if x.creator is not None:
funcs.append(x.creator)
# ========================================================
为单输入函数(Square和Exp)打个补丁
class Square(Function):
...
def backward(self, gy):
x = self.inputs[0].data # 修改前为 x =self.input.data
gx = 2 * x * gy
return gx