利用计算图的正向传播和反向传播高效地计算导数
程序员文章站
2022-05-21 22:24:03
...
# 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的理论与实现》(斋藤康毅著,陆宇杰译),特在此声明。