利用计算图的正向传播和反向传播高效地计算导数

房东的猫 提交于 2020-01-31 00:48:35

# 1.计算图的正向传播与反向传播

计算图

计算图就是一种表示计算过程的数据结构图。计算图通过节点表示某种运算,用带箭头的边表示运算的方向,箭头上标有与运算相关的数据。上图就是一张计算图。

正向传播

上图中黑色箭头表示的就是正向传播。将输入数据x通过某种运算f转化为输出数据y,再传给下一个节点(或输出为最后结果)。

反向传播

上图中灰色箭头表示的就是反向传播。沿着与正向传播相反的方向,将输入数据E乘以局部导数∂y/∂x后传给下一个节点(或输出为最后结果)。

# 2.高效地计算导数

举个例子

式子z = (x+y)^2可以看作是由z = t^2和t = x+y构成的。将其用如下计算图表示出来

根据链式法则可以作进一步转换,得到下图

我们可以发现通过先进行一次正向传播再进行一次反向传播,我们可以得到各个变量的导数值。

这个求导过程是高效的。原因如下

1.中间变量可以重复利用。(如∂z/∂t在计算∂z/∂x和∂z/∂y时都有用到)

2.节点负责封装解析性求局部导数的过程。(没有像数值微分那样用定义求导,而是用解析性公式求导。如x^2的导数是2x)

 

下面用python实现上述例子的求解导数过程:

class AddLayer:
	def __init__(self):
		pass   # 表示什么也不运行
	
	def forward(self, x, y):  # 前向函数,入口参数为加法的两个参数
		return x+y
		
	def backward(self, dout): # 反向函数,入口参数为上游传来的导数dout
		return dout,dout  # x+y关于x的偏导数是1,关于y的偏导数也是1,所以输出的dx,dy应该为dout*1,dout*1
		
class SquareLayer:
	def __init__(self):
		self.x = None     # 由于反向传播时,会用到正向传播的入口参数,所以设置一个成员变量来保存该入口参数
		
	def forward(self,x):      # 前向函数
		self.x = x
		return self.x**2
	
	def backward(self,dout):  # 反向函数,入口参数为上游传来的导数dout
		return dout*2*self.x  # x^2关于x的导数是2x,所以输出的dx应该为dout*2*x

# 测试		
addlayer = AddLayer()         # 创建加法层实例对象和平方层实例对象
squarelayer = SquareLayer()

t = addlayer.forward(3,4)     # 设置x,y为3,4,进行整个计算图的正向传播
z = squarelayer.forward(t)

dz = 1  # z关于z的导数是1
dt = squarelayer.backward(dz) # 进行整个计算图的反向传播
dx,dy = addlayer.backward(dt)

print(dx,dy)                  # 打印出计算结果(z关于x和y的偏导数)

运行结果如下

综上所述,这个高效的求导数方法就被如此实现了。

 

# 本博客参考了《深度学习入门——基于Python的理论与实现》(斋藤康毅著,陆宇杰译),特在此声明。

 

 

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!