自动微分采用一套与常规机器学习和深度学习不同的符号体系,我们只有熟悉了这个符号体系,才能比较轻松的看懂自动微分的文章。本篇博文将向大家介绍自动微分中使用的符号体系。
我们以下面这个函数为例,讲解一下自动微分的符号表示法:
y=f(x1,x2)=logx1+x1x2−sinx2y=f(x1,x2)=logx1+x1x2−sinx2
对自动微分采用一种比较特别的符号表示法,与Bingio的《Deep Learning》书中MLP章节中的表示法类似,采用三段表示法。
- 输入向量
我们假设自变量维度为n,在这个例子中n=2,我们用vv来表示自变量:,以本例为例,我们用v−1v−1表示x1x1,用v0v0表示x2x2。 - 中间变量
我们同样用vv表示中间变量,假设共有个中间变量,表示为:vi,i=1,2,...,lvi,i=1,2,...,l - 输出向量
我们用yy来表示输入向量,假设输出向量维度为,表示为:yk−i=vl−i,i=k−1,...,0yk−i=vl−i,i=k−1,...,0,还以本例为例,输出向量只有1维,则k=1k=1,则y1=vly1=vl。
我们先来讲前向模式,这种模式即可求出计算图的输出,同时也可以求出导数值。但是如果以深度学习的角度来看,前向模式就只需要完成计算出计算图中各节点的值就可以了。而求导数则由反向模式来实现。
我们首先给输入节点赋值:v−1=x1v−1=x1和v0=x2v0=x2
接着我们计算v1v1节点:v1=logv−1=logx1v1=logv−1=logx1
我们再计算v2v2节点:v2=v−1v0=x1x2v2=v−1v0=x1x2
我们再来计算v3v3节点:v3=v1+v2=logx1+x1x2v3=v1+v2=logx1+x1x2
计算v4v4节点:v4=−sinv0=−sinx2v4=−sinv0=−sinx2
计算输出节点v5v5:y1=v5=v3+v4=logx1+x1x2−sinx2y1=v5=v3+v4=logx1+x1x2−sinx2
至此我们就计算出了计算图中所有节点的值。
下面我们来介绍在正向模式下导数的计算。假设我们想求∂y∂x1∂y∂x1的值,我们也是由输入开始计算。
对v−1v−1节点:∂v−1∂x1=1∂v−1∂x1=1,因为v−1=x1v−1=x1
对v0v0节点:∂v0∂x1=0∂v0∂x1=0,因为v0=x2v0=x2其与x1x1无关。
对v1v1节点:∂v1∂x1=∂∂x1logx1=1x1∂v1∂x1=∂∂x1logx1=1x1
对v2v2节点:∂v2∂x1=∂∂x1x1x2=x2∂v2∂x1=∂∂x1x1x2=x2
对v3v3节点:∂v3∂x1=∂v1∂x1+∂v2∂x1=1x1+x2∂v3∂x1=∂v1∂x1+∂v2∂x1=1x1+x2
对v4v4节点:∂v4∂x1=∂∂x1sinx2=0∂v4∂x1=∂∂x1sinx2=0
对v5v5节点:∂v5∂x1=∂v3∂x1−∂v4∂x1=1x1+x2∂v5∂x1=∂v3∂x1−∂v4∂x1=1x1+x2
由上面的计算可以看出,我们每次前向计算,只能计算输入向量一维的导数,如果输入向量有n=2n=2维,则需要计算两次,当nn很大时,这种方法的效率就会比较低了。
对于深度学习问题,我们通常会研究Jacobian矩阵,假设输入向量,而输出向量用y∈Rky∈Rk,则Jacobian矩阵定义为:
J=⎡⎣⎢⎢⎢∂y1∂x1...∂yk∂x1∂y1∂x2...∂yk∂x2.........∂y1∂xn...∂yk∂xn⎤⎦⎥⎥⎥J=[∂y1∂x1∂y1∂x2...∂y1∂xn............∂yk∂x1∂yk∂x2...∂yk∂xn]
我们可以看出,一次前向计算,可以求出Jacobian矩阵的一列数据。
为了讨论方便,我们这里假设x1=2x1=2且x2=5x2=5,我们首先按照前向模式计算出各节点的值,如下图所示:
我们再来看导数部分。
对v5v5节点有v5=v3−v4v5=v3−v4,我们首先求∂v5∂v3=∂(v3−v4)∂v3=1∂v5∂v3=∂(v3−v4)∂v3=1,将结果写在v3v3指向v5v5的边上。
我们再来求∂v5∂v4=∂(v3−v4)∂v4=−1∂v5∂v4=∂(v3−v4)∂v4=−1,将结果写在v4v4指向v5v5的边上。
我们再来求∂v4∂v0=∂sinv0∂v0=cosv0=0.284∂v4∂v0=∂sinv0∂v0=cosv0=0.284,将结果写在v0v0指向v4v4的边上。
我们再来求∂v3∂v1=∂(v1+v2)∂v1=1∂v3∂v1=∂(v1+v2)∂v1=1,将结果写在v1v1指向v3v3的边上。
我们再来求∂v3∂v2=∂(v1+v2)∂v2=1∂v3∂v2=∂(v1+v2)∂v2=1,将结果写在v2v2指向v3v3的边上。
我们再来求∂v1∂v−1=∂logv−1∂v−1=1x1=0.5∂v1∂v−1=∂logv−1∂v−1=1x1=0.5,将结果写在v−1v−1指向v1v1的边上。
我们再来求∂v2∂v−1=∂(v−1v0)∂v−1=v0=5∂v2∂v−1=∂(v−1v0)∂v−1=v0=5,将结果写在v−1v−1指向v2v2的边上。
我们再来求∂v2∂v0=∂(v−1v0)∂v0=v−1=2∂v2∂v0=∂(v−1v0)∂v0=v−1=2,将结果写在v0v0指向v2v2的边上。
至此我们已经求出了所有步的偏导数的值,我们计算∂y1∂x1∂y1∂x1就是从y1y1开始,反向走回x1x1节点,可能有多条路径,对每一条路径,将每个边上的值连乘,最后将多条路径的值相加,即可求出∂y1∂x1∂y1∂x1的值。∂y1∂x2∂y1∂x2的值与此类似。
如图所示,从y1y1走到x1x1共有两条路径,分别为:
v5→v3→v1→v−1v5→v3→v1→v−1:1∗1∗0.5=0.51∗1∗0.5=0.5
v5→v3→v2→v−1v5→v3→v2→v−1:1∗1∗5=5.01∗1∗5=5.0
所以∂y1∂x1=0.5+5.0=5.5∂y1∂x1=0.5+5.0=5.5。
用同样的方法我们可以计算∂y1∂x2∂y1∂x2的值。由y1y1∂y1∂x2∂y1∂x2到x2x2的路径也有两条:
v5→v3→v2→v0v5→v3→v2→v0:1∗1∗2=2.01∗1∗2=2.0
v5→v4→v0v5→v4→v0:(−1)∗0.284=−0.284(−1)∗0.284=−0.284
所以y1y1∂y1∂x2=2.0+(−0.284)=1.716∂y1∂x2=2.0+(−0.284)=1.716。
相对于正向模式而言,反向模式可以通过一次反向传输,就计算出所有偏导数,而这对于深度学习中的如多层感知器(MLP)模型来说,非常方便,而且中间的偏导数计算只需计算一次,减少了重复计算的工作量,当然这是以增加存储量需求为代价的。
在本篇博文中,我们详细讲解了自动微分概念,自动微分概念是一个比较老的概念,但是将其引入深度学习领域,还是一个新鲜事务,这就是最近Yann Lecun提到的“深度学习已死,可微分编程永生”中的技术。在下一篇博文中,我们将向大家介绍自动微分在深度学习中的应用。