深度学习-06-手动进行反向传播
本文是《深度学习入门2-自製框架》 的学习笔记,记录自己学习心得,以及对重点知识的理解。如果内容对你有帮助,请支持正版,去购买正版书籍,支持正版书籍不仅是尊重作者的辛勤劳动,也是鼓励更多优秀作品问世。
当前笔记内容主要为:步骤6 手动进行反向传播 章节的相关理解。
书籍总共分为5个阶段,每个阶段分很多步骤,最终是一步一步实现一个深度学习框架。例如前两个阶段为:
第 1 阶段共包括 10 个步骤 。 在这个阶段,将创建自动微分的机制
第 2 阶段,从步骤11-24,该阶段的主要目标是扩展当前的 DeZero ,使它能够执行更复杂的计算 ,使它能 够处理接收多个输入的函数和返回多个输出的函数
1.Variable 类的功能
Variable 类在原有的基础上,除了data普通值外,应该增加一个grad 的值,用于记录导数值。grad 也是numpy ndarray 类型。为啥是ndarray ,我们上一节讲的是对x 一维的,但是我们后面可能会设计到多维度,每个维度都需要进行求导,所以这里定义的是一个ndarray ,这样就可以表示向量或者矩阵。
class Variable:
def __init__(self, data):
self.data = data
self.grad = None #存储梯度,导数值
2.Function 类的功能
Function 类我们在原有的定义前向计算,表示复合函数的基础上定义一个反向传播方法,并且函数定义一个数据域input, 用以记录每次执行是的输入变量。有了input变量后,每次调用backward 方法后,向函数输入的 Variable 实例就可以作为 self.input使用。
class Function:
def __call__(self, input):
x = input.data
y = self.forward(x)
output = Variable(y)
self.input = input # input 保存每次输入的变量
return output
def forward(self, x):
raise NotImplementedError() # 使用Function 这个方法forward 方法的人 , 这个方法应该通过继承采实现
def backward(self, gy):
raise NotImplementedError()
3.Square 类和Exp 类的功能扩展
按照最新的Function类,为Square 和Exp 类增加反向传播功能
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
4.反向传播的实现
尝试对函数进行推演,正向计算,图:
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)
5.项目代码
项目全部代码如下:
'''
step06.py
手动实现反向传播
'''
import numpy as np
class Variable:
def __init__(self, data):
self.data = data
self.grad = None
class Function:
def __call__(self, input):
x = input.data
y = self.forward(x)
output = Variable(y)
self.input = 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 #方法的参数 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)
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
问题分析:代码里面的 为啥计算梯度的时候,乘以gy?
如果没有理解,可以看下链式法则,这里只有乘以 gy 才把其他的连乘结果带到下一个微分计算里面。
深入理解下链式法则,就懂了这里为啥每个backward 函数乘以一个入参gy
6.总结
本节实现了反向传播,计算微分的计算,当前是手动实现的,后面改造成自动化计算。