《深度学习入门——自制框架》读书笔记 第一章自动微分
1. 自动微分
step2 创建变量的函数
# 箱子类,存放一个变量数据
class Variable:
def __init__(self, data):
self.data = data
# 函数类的基类
class Function:
# __call__方法是一个特殊的Python方法。
# 定义了这个方法后,当f = Function()时,就可以通过编写f(...)来调用__call__方法了
def __call__(self, input):
x = input.data
y = self.forward(x) # 具体的计算在forward方法中进行
output = Variable(y)
return output
def forward(self, x):
raise NotImplementedError() # 告诉使用了Function类forward方法的人这个方法应该通过继承来实现
# 函数类的具体实现类
class Square(Function):
def forward(self, x):
return x ** 2
if __name__ == "__main__":
x = Variable(10)
f = Square()
y = f(x)
print(type(y))
print(y.data)
step4 数值微分
利用中心差分近似来计算导数:
\[f'(x) \approx \frac{f(x+ \epsilon)-f(x- \epsilon)}{2 \epsilon}
\]
设计numerical_diff函数:
class Variable:
def __init__(self, data):
self.data = data
class Function:
def __call__(self, input):
x = input.data
y = self.forward(x) # 具体的计算在forward方法中进行
output = Variable(y)
return output
def forward(self, x):
raise NotImplementedError() # 告诉使用了Function类forward方法的人这个方法应该通过继承来实现
class Square(Function):
def forward(self, x):
return x ** 2
def numerical_diff(f, x, eps=1e-4):
x0 = Variable(x.data - eps)
x1 = Variable(x.data + eps)
y0 = f(x0)
y1 = f(x1)
return (y1.data - y0.data) / (2 * eps)
if __name__ == "__main__":
f = Square()
x = Variable(2.0)
dy = numerical_diff(f, x)
print(dy)
复合函数的导数
def f(x):
A = Square()
B = Exp()
C = Square()
return C(B(A(x)))
if __name__ == "__main__":
x = Variable(np.array(0.5))
dy = numerical_diff(f, x)
print(dy)
step6 手动进行反向传播
用反向传播计算导数
import numpy as np
class Variable:
def __init__(self, data):
self.data = data
self.grad = None # gradient梯度的缩写
class Function:
def __call__(self, input):
x = input.data
y = self.forward(x) # 具体的计算在forward方法中进行
output = Variable(y)
self.input = input # 保存输入的变量
# 将input设置为实例变量。
# 这样一来,当调用backward方法时,向函数输入的Variable实例就可以作为self.input使用
return output
def forward(self, x):
raise NotImplementedError() # 告诉使用了Function类forward方法的人这个方法应该通过继承来实现
def backward(self, gy):
raise NotImplementedError()
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
class Exp(Function):
def forward(self, x):
return np.exp(x)
def backward(self, gy):
x = self.input.data
gx = np.exp(x) * gy
return gx
if __name__ == "__main__":
A = Square()
B = Exp()
C = Square()
x = Variable(np.array(0.5))
a = A(x)
b = B(a)
y = C(b)
y.grad = np.array(1.0) # dy/dy = 1
b.grad = C.backward(y.grad)
a.grad = B.backward(b.grad)
x.grad = A.backward(a.grad)
print(x.grad)
step7 反向传播的自动化
import numpy as np
class Variable:
def __init__(self, data):
self.data = data
self.grad = None # gradient梯度的缩写
self.creator = None
def set_creator(self, func):
self.creator = func
def backward(self):
f = self.creator # 1. 获取函数
if f is not None:
x = f.input # 2. 获取函数的输入
x.grad = f.backward(self.grad) # 3. 调用函数的backward方法
x.backward() # 调用自己前面那个变量的backward方法(递归)
class Function:
def __call__(self, input):
x = input.data
y = self.forward(x) # 具体的计算在forward方法中进行
output = Variable(y)
output.set_creator(self) # 让输出变量保存创造者信息
self.input = input # 保存输入的变量
# 将input设置为实例变量。
# 这样一来,当调用backward方法时,向函数输入的Variable实例就可以作为self.input使用
self.output = output # 也保存输出变量
return output
def forward(self, x):
raise NotImplementedError() # 告诉使用了Function类forward方法的人这个方法应该通过继承来实现
def backward(self, gy):
raise NotImplementedError()
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
class Exp(Function):
def forward(self, x):
return np.exp(x)
def backward(self, gy):
x = self.input.data
gx = np.exp(x) * gy
return gx
if __name__ == "__main__":
A = Square()
B = Exp()
C = Square()
x = Variable(np.array(0.5))
a = A(x)
b = B(a)
y = C(b)
y.grad = np.array(1.0) # dy/dy = 1
y.backward()
print(x.grad)
step8 从递归到循环
用循环的方式实现backward
修改之后的backward如下
class Variable:
...
def backward(self):
funcs = [self.creator] # 1. 获取函数
while funcs:
f = funcs.pop() # 获取函数
x, y = f.input, f.output # 2. 获取函数的输入
x.grad = f.backward(y.grad) # 3. 调用函数的backward方法
if x.creator is not None:
funcs.append(x.creator) # 将前一个函数添加到列表中
step9 让函数更易用
主要3点改进:
- 把函数类变成python函数
- 简化backward方法
- 让Variable只支持ndarray类型
函数类变成python函数
def square(x):
f = Square()
return f(x)
# 或者
def square(x):
return Square()(x)
这样在调用平方函数时就可以简化为:
y = square(x)
也支持连续调用
y = square(exp(square(x)))
简化backward方法
之前每次调用backward时都得手动设置一次y.grad = np.array(1.0)
,对此可以简化为:
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() # 获取函数
x, y = f.input, f.output # 2. 获取函数的输入
x.grad = f.backward(y.grad) # 3. 调用函数的backward方法
if x.creator is not None:
funcs.append(x.creator) # 将前一个函数添加到列表中
只支持ndarray
需要改三个地方:
- 在Variable类中进行类型判断
if data is not None:
if not isinstance(data, np.ndarray):
raise TypeError('{} is not supported'.format(type(data)))
- 添加as_array函数
def as_array(x):
if np.isscalar(x):
return np.array(x)
return x
之所以需要这个函数,是因为NumPy的工作机制:零维ndarray进行数值运算之后将产生ndarray之外的类型(如numpy.float64、numpy.float32)。
- Function类中的
output = Variable(y)
改为output = Variable(as_array(y))