深度学习-07-反向传播的自动化
本文是《深度学习入门2-自製框架》 的学习笔记,记录自己学习心得,以及对重点知识的理解。如果内容对你有帮助,请支持正版,去购买正版书籍,支持正版书籍不仅是尊重作者的辛勤劳动,也是鼓励更多优秀作品问世。
当前笔记内容主要为:步骤7 反向传播的自动化 章节的相关理解。
书籍总共分为5个阶段,每个阶段分很多步骤,最终是一步一步实现一个深度学习框架。例如前两个阶段为:
第 1 阶段共包括 10 个步骤 。 在这个阶段,将创建自动微分的机制
第 2 阶段,从步骤11-24,该阶段的主要目标是扩展当前的 DeZero ,使它能够执行更复杂的计算 ,使它能 够处理接收多个输入的函数和返回多个输出的函数
1.为反向传播的自动化创造条件
之前我们在实现反向传播的时候,我们是手动编写进行反向传播计算的代码,这意味着我们每次都要编写这些代码。例如下面的:
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)
b.grad = C.backward(y.grad)
a.grad = B.backward(b.grad)
x.grad = A.backward(a.grad)
print(x.grad)
如果计算图不一样,那么我们每次就需要白那些不一样的代码来计算反向传播。
图:不同计算图的例子。
那我们就开始思考如果自动化这块内容?书中讲解了一种机制:无论普通的计算流程(正向传播)中是什么样的计算,反向传播都能 自动进行 。这里引入一个概念:Define- by-Run。
Define-by-Run;是在深度学习中进行计算时 ,在计算之间建立"连接"的机 制 。 这种机制也称为动态计算图
计算图都是流水线式的计算 。 因此,只要以列表的形式记录函数的顺序,就可以通过反向回溯自动进行反向传播
在实现反向传播的自动化之前,我们先思考一下变量和函数之间的关系。解决方案主要从这里入手。我们需要考察变量和函数的关系并且用代码表示出来。
函数的变量包括"输入变量 "(input) 和"输出变量"(output)。函数是变量的 "父母" 如果没有父母,说明这个变量是用户的输入变量。
修改Variable 定义,函数和变量之间的"连接,让这个"连接"在执行普通计算(正向传播)的那一刻创建。
class Variable:
def __init__(self, data):
self.data = data
self.grad = None
self.creator = None
def set_creator(self,func):
self.creator = func
上面代码,定义了熟悉 creator 标识是那个函数。并且定义了set 方法,来进行变量与函数之间的关联。
修改 Function 定义:
class Function:
def __call__(self, input):
x = input.data
y = self.forward(x)
output = Variable(y)
output.set_creator(self) # 输出者保存创造者对象
self.input = input
self.output = output # 保存输出者。我是创造者的信息,这是动态建立 "连接"这 一 机制的核心
return output
def forward(self, x):
raise NotImplementedError() # 使用Function 这个方法forward 方法的人 , 这个方法应该通过继承采实现
def backward(self, gy):
raise NotImplementedError()
在函数执行时,就设置输出变量的 creator 为”自己“。并且将输出保存起来。
经过上面的操作后,变量和函数之间建立的联系,并且这两种联系的建立时代码执行过程中创建的。
有了连接,我们手动检查下,计算下反向遍历计算图
A = Square()
B = Exp()
C = Square()
x = Variable(np.array(0.5))
a = A(x)
b = B(a)
y = C(b)
assert y.creator == C
assert y.creator.input == b
assert y.creator.input.creator == B
assert y.creator.input.creator.input == a
assert y.creator.input.creator.input.creator == A
assert y.creator.input.creator.input.creator.input == x
没有抛出异常,这意味着 assert 语句的所有条件都得到了满足。
2.尝试反向传播
利用上面的变量与函数之间的关系。进行反向传播计算。
先 y ---> b
y.grad = np.array(1.0)
C = y.creator # 获取函数
b = C.input # 获取函数的输入
b.grad = C.backward(y.grad) # 调用函数的backward 方法
然后 b ---> a
B = b.creator
b = B.input
a.grad = B.backward(b.grad)
最后 a----> x
A = a.creator
x = A.input
x.grad = A.backward(a.grad)
print(x.grad)
其实每一步的规律是:
1 获取函数
2 获取函数的输入
3 调用函数的 backward 方法
最后执行的结果为
3.29744
3.增加backward 方法
前面这些反向传播的代码可以看山,它们有着相同的处理流程准确 来说,是从一个变量到前一个变量的反向传播逻辑相同。
修改Variable 类,增加一个新的方法-backward
class Variable:
def __init__(self, data):
self.data = data
self.grad = None
self.creator = None
def set_creator(self,func):
self.creator = func
def backward(self):
f = self.creator # 获取函数
if f is not None:
x = f.input # 获取函数的输入
x.grad =f.backward(self.grad) # 调用函数的backward() 方法
x.backward() # 调用自己前面的那个变量的 backward()方法
4.项目代码
'''
step07.py
自动实现反向传播
'''
import numpy as np
class Variable:
def __init__(self, data):
self.data = data
self.grad = None
self.creator = None
def set_creator(self,func):
self.creator = func
def backward(self):
f = self.creator # 获取函数
if f is not None:
x = f.input # 获取函数的输入
x.grad =f.backward(self.grad) # 调用函数的backward() 方法
x.backward() # 调用自己前面的那个变量的 backward()方法
class Function:
def __call__(self, input):
x = input.data
y = self.forward(x)
output = Variable(y)
output.set_creator(self) # 输出者保存创造者对象
self.input = 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 #方法的参数 gy 是 一个 ndarray 实例 , 它是从输出传播而来的导数 。
return gx
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
if __name__ == '__main__':
A = Square()
B = Exp()
C = Square()
x = Variable(np.array(0.5))
a = A(x)
b = B(a)
y = C(b)
assert y.creator == C
assert y.creator.input == b
assert y.creator.input.creator == B
assert y.creator.input.creator.input == a
assert y.creator.input.creator.input.creator == A
assert y.creator.input.creator.input.creator.input == x
# 反向传播
y.grad = np.array(1.0)
C = y.creator # 获取函数
b = C.input # 获取函数的输入
b.grad = C.backward(y.grad) # 调用函数的backward 方法
B = b.creator
b = B.input
a.grad = B.backward(b.grad)
A = a.creator
x = A.input
x.grad = A.backward(a.grad)
print(x.grad)
# 自动反向传播
x = Variable(np.array(0.5))
a = A(x)
b = B(a)
y = C(b)
y.grad = np.array(1.0)
y.back()
print(x.grad)
5.总结
经过此小节的代码丰富,目前我们可以执行自动反向传播计算。