自动求导

前言

求导概念:数值求导,符号求导,自动求导

模式:正向传播,反向求导。雅可比原理

实现:表达式计算图,操作符重载,源码转换AST

求导

符号求导

通过求导法则将原式展开,转换为导数表达式

优势:精确数值结果

劣势:表达式膨胀

数值求导

利用有限差分与极限进行近似

优势:实现简单

劣势:由于浮点数范围导致计算不精确,计算复杂度高,对近似所用的极小数要求高

会引起截断误差与舍入误差

自动求导

所有的数值计算都由有限的基本运算组成,基本运算的导数表达式都是已知的,通过链式求导法则将数值计算各部分组合成整体。

优势:数值精度高,无表达式膨胀。

劣势:需要存储中间结果,内存占用大

要实现自动求导,需要表达式追踪,用于追踪记录计算过程的中间变量。

如,对于下列公式:

在这里插入图片描述

有:

在这里插入图片描述

正向传播

以下为正向地计算函数值的过程,按照计算法则进行计算。

在这里插入图片描述

根据这个过程,我们可以将原函数的计算过程转换为一个有向无环图,其中每一个节点都是一个中间结果,每一个(组)边都是一个运算。

在这里插入图片描述

要通过链式法则将 f 对 x1 的导数展开,需要计算 x1 到 f 的所有路径,并将其相加。

第一条路径为
x1→v−1→v1→v4→v5→f x_1\rightarrow v_{-1}\rightarrow v_1\rightarrow v_4\rightarrow v_5\rightarrow f x1v1v1v4v5f
第二条路径为
x1→v−1→v2→v4→v5→f x_1\rightarrow v_{-1}\rightarrow v_2\rightarrow v_4\rightarrow v_5\rightarrow f x1v1v2v4v5f
现在计算第一条路径
∂v−1∂x1=1∂v1∂v−1=1v−1=1x1∂v4∂v1=1∂v5∂v4=1∂f∂v5=1∴∂f∂x11=1∗1∗1∗1x1∗1=1x1 \begin{aligned} \frac{\partial v_{-1}}{\partial x_1} &= 1\\ \frac{\partial v_1}{\partial v_{-1}} &= \frac{1}{v_{-1}} = \frac{1}{x_1}\\ \frac{\partial v_4}{\partial v_1} &= 1\\ \frac{\partial v_5}{\partial v_4} &= 1\\ \frac{\partial f}{\partial v_5} &= 1\\ \therefore {\frac{\partial f}{\partial x_1}}_1 &= 1*1*1*\frac{1}{x_1}*1 = \frac{1}{x_1} \end{aligned} x1v1v1v1v1v4v4v5v5fx1f1=1=v11=x11=1=1=1=111x111=x11
第二条路径
∂v−1∂x1=1∂v2∂v−1=v0=x2∂v4∂v2=1∂v5∂v4=1∂f∂v5=1∴∂f∂x12=1∗1∗1∗x2∗1=x2 \begin{aligned} \frac{\partial v_{-1}}{\partial x_1} &= 1\\ \frac{\partial v_2}{\partial v_{-1}} &= v_0=x_2\\ \frac{\partial v_4}{\partial v_2} &= 1\\ \frac{\partial v_5}{\partial v_4} &= 1\\ \frac{\partial f}{\partial v_5} &= 1\\ \therefore {\frac{\partial f}{\partial x_1}}_2 &= 1*1*1*x_2*1 = x_2 \end{aligned} x1v1v1v2v2v4v4v5v5fx1f2=1=v0=x2=1=1=1=111x21=x2
最后的结果等于两条路径相加
∂f∂x1=1x1+x2 \frac {\partial f}{\partial x_1}=\frac{1}{x_1}+x_2 x1f=x11+x2
其中可以观察到两条路径中仅有v−1→v5v_{-1} \rightarrow v_5v1v5之间的内容不同,故可以简化计算为
∂f∂x1=1∗1∗1∗(1x1+x2)∗1 \frac {\partial f}{\partial x_1}=1*1*1*(\frac{1}{x_1}+x_2)*1 x1f=111(x11+x2)1
这样就完成了对x1x_1x1的一次正向传播,对x2x_2x2的过程与之相似。

总结:正向传播通过对每一个输入变量按照计算图顺序进行跟踪,计算图中每个节点对前一个节点的导数,以计算对每个变量的导数。

反向传播

在这里插入图片描述

计算过程如下:

1. 初始化
∂f∂f=1 \frac{\partial f}{\partial f}=1 ff=1

2. 梯度反向传播
传播到 v5:∂f∂v5=∂v5∂v5=1从 v5 传播到 v4:∂f∂v4=∂v5∂v4⋅∂f∂v5=1从 v5 传播到 v3:∂f∂v3=∂v5∂v3⋅∂f∂v5=−1从 v4 传播到 v1:∂f∂v1=∂v4∂v1⋅∂f∂v4=1从 v4 传播到 v2:∂f∂v2=∂v4∂v2⋅∂f∂v4=1 \begin{aligned} &\text{传播到 } v_5: \quad \frac{\partial f}{\partial v_5}=\frac{\partial v_5}{\partial v_5}=1 \\ &\text{从 } v_5 \text{ 传播到 } v_4: \quad \frac{\partial f}{\partial v_4}=\frac{\partial v_5}{\partial v_4} \cdot \frac{\partial f}{\partial v_5}=1 \\ &\text{从 } v_5 \text{ 传播到 } v_3: \quad \frac{\partial f}{\partial v_3}=\frac{\partial v_5}{\partial v_3} \cdot \frac{\partial f}{\partial v_5}=-1 \\ &\text{从 } v_4 \text{ 传播到 } v_1: \quad \frac{\partial f}{\partial v_1}=\frac{\partial v_4}{\partial v_1} \cdot \frac{\partial f}{\partial v_4}=1 \\ &\text{从 } v_4 \text{ 传播到 } v_2: \quad \frac{\partial f}{\partial v_2}=\frac{\partial v_4}{\partial v_2} \cdot \frac{\partial f}{\partial v_4}=1 \end{aligned} 传播到 v5:v5f=v5v5=1 v5 传播到 v4:v4f=v4v5v5f=1 v5 传播到 v3:v3f=v3v5v5f=1 v4 传播到 v1:v1f=v1v4v4f=1 v4 传播到 v2:v2f=v2v4v4f=1

3. 梯度累加 (针对输入变量)

关于 v−1v_{-1}v1 (即 x1x_1x1) 的梯度:
来自 v1:∂v1∂v−1⋅∂f∂v1=1x1⋅1=1x1来自 v2:∂v2∂v−1⋅∂f∂v2=v0⋅1=x2∴ ∂f∂v−1=1x1+x2 \begin{aligned} &\text{来自 } v_1: \quad \frac{\partial v_1}{\partial v_{-1}} \cdot \frac{\partial f}{\partial v_1} = \frac{1}{x_1} \cdot 1 = \frac{1}{x_1} \\ &\text{来自 } v_2: \quad \frac{\partial v_2}{\partial v_{-1}} \cdot \frac{\partial f}{\partial v_2} = v_0 \cdot 1 = x_2 \\ &\therefore \ \frac{\partial f}{\partial v_{-1}} = \frac{1}{x_1} + x_2 \end{aligned} 来自 v1:v1v1v1f=x111=x11来自 v2:v1v2v2f=v01=x2 v1f=x11+x2

关于 v0v_0v0 (即 x2x_2x2) 的梯度:
来自 v2:∂v2∂v0⋅∂f∂v2=v−1⋅1=x1来自 v3:∂v3∂v0⋅∂f∂v3=cos⁡(v0)⋅(−1)=−cos⁡(x2)∴ ∂f∂v0=x1−cos⁡(x2) \begin{aligned} &\text{来自 } v_2: \quad \frac{\partial v_2}{\partial v_0} \cdot \frac{\partial f}{\partial v_2} = v_{-1} \cdot 1 = x_1 \\ &\text{来自 } v_3: \quad \frac{\partial v_3}{\partial v_0} \cdot \frac{\partial f}{\partial v_3} = \cos(v_0) \cdot (-1) = -\cos(x_2) \\ &\therefore \ \frac{\partial f}{\partial v_0} = x_1 - \cos(x_2) \end{aligned} 来自 v2:v0v2v2f=v11=x1来自 v3:v0v3v3f=cos(v0)(1)=cos(x2) v0f=x1cos(x2)

4. 最终梯度
∂f∂x1=1x1+x2,∂f∂x2=x1−cos⁡(x2) \frac{\partial f}{\partial x_1} = \frac{1}{x_1} + x_2, \quad \frac{\partial f}{\partial x_2} = x_1 - \cos(x_2) x1f=x11+x2,x2f=x1cos(x2)
这样就完成了一次反向传播。

总结:反向传播通过从输出开始需要计算结果对图中每个节点的导数,以计算对每个变量的导数。

雅可比矩阵

在反向传播时,对于函数
y⃗=f(x⃗) \vec y=f(\vec{x}) y=f(x)
其中
f:Rn→Rm f:\boldsymbol R^n\rightarrow\boldsymbol R^m f:RnRm
那么向量yyy关于向量xxx的梯度可以表示为雅可比矩阵:
Jf=[∂y∂x1⋯∂y∂xn]=[∂y1∂x1⋯∂y1∂xn⋮⋱⋮∂ym∂x1⋯∂ym∂xn] \boldsymbol{J}_{f}=\left[\begin{array}{lll} \frac{\partial \boldsymbol{y}}{\partial x_{1}} & \cdots & \frac{\partial \boldsymbol{y}}{\partial x_{n}} \end{array}\right]=\left[\begin{array}{ccc} \frac{\partial y_{1}}{\partial x_{1}} & \cdots & \frac{\partial y_{1}}{\partial x_{n}} \\ \vdots & \ddots & \vdots \\ \frac{\partial y_{m}}{\partial x_{1}} & \cdots & \frac{\partial y_{m}}{\partial x_{n}} \end{array}\right] Jf=[x1yxny]=x1y1x1ymxny1xnym
每一行为一个输出值yyy,每一列为一个输入xxx。行数为mmm,列数为nnn

将向量vvv设置为关于以下函数的梯度
l=g(y⃗)v⃗=[∂l∂y1 ⋯ ∂l∂ym]T l=g(\vec y)\\ \vec {\boldsymbol v}={\left[ \begin{array}{} \frac {\partial l}{\partial y_1} \ \cdots\ \frac{\partial l}{\partial y_m} \end{array}{} \right]}^T l=g(y)v=[y1l  yml]T
那雅可比矩阵与向量vvv的乘积就是函数l中关于x的梯度
JT⋅v⃗=[∂y1∂x1⋯∂y1∂xn⋮⋱⋮∂ym∂x1⋯∂ym∂xn]T⋅[dldy1⋮dldym]=[∂y1∂x1⋅dldy1+⋯+∂ym∂x1⋅dldym⋮∂y1∂xn⋅dldy1+⋯+∂ym∂xn⋅dldym] \mathbf{J}^T \cdot \vec{\mathbf{v}} = \begin{bmatrix} \frac{\partial y_1}{\partial x_1} & \cdots & \frac{\partial y_1}{\partial x_n} \\ \vdots & \ddots & \vdots \\ \frac{\partial y_m}{\partial x_1} & \cdots & \frac{\partial y_m}{\partial x_n} \end{bmatrix}^T \cdot \begin{bmatrix} \frac{dl}{dy_1} \\ \vdots \\ \frac{dl}{dy_m} \end{bmatrix}= \begin{bmatrix} \frac{\partial y_1}{\partial x_1} \cdot \frac{dl}{dy_1} + \cdots + \frac{\partial y_m}{\partial x_1} \cdot \frac{dl}{dy_m} \\ \vdots \\ \frac{\partial y_1}{\partial x_n} \cdot \frac{dl}{dy_1} + \cdots + \frac{\partial y_m}{\partial x_n} \cdot \frac{dl}{dy_m} \end{bmatrix} JTv=x1y1x1ymxny1xnymTdy1dldymdl=x1y1dy1dl++x1ymdymdlxny1dy1dl++xnymdymdl
最终得到的结果为
[∂l∂x1⋮∂l∂xn] \begin{bmatrix} \frac{\partial l}{\partial x_1} \\ \vdots \\ \frac{\partial l}{\partial x_n} \end{bmatrix} x1lxnl
其中每个元素代表损失函数对每个输入变量的总梯度。

正向传播时对雅可比矩阵再转置。

综上,当m>nm>nm>n,即输出比输入多时,适合用正向传播。

m<nm<nm<n,即输入比输出多时,适合用反向传播。

而在神经网络中,输入数据集规模极大,故更常用反向传播。

自动求导的实现方法

表达式法

封装基本的表达式及其求导表达式作为库函数;

运行时记录基本表达式和相应的组合关系;

链式法则对基本表达式的求导结果进行组合。

例如:

实现以下表达式计算
f(x,y,z)=(x+y)z f(x,y,z)=\frac{(x+y)}{z} f(x,y,z)=z(x+y)
手动将表达式函数分解为库函数中的基本表达式组合
a=x+yb=xy a=x+y\\ b=\frac{x}{y} a=x+yb=yx
利用库函数中定义对应表达式的数学求导法则和链式法则
∂a=∂x+∂y∂b=∂xy−∂y∗xy2 \partial a = \partial x+\partial y\\ \partial b=\frac{\partial x}{y}-\partial y*\frac{x}{y^2} a=x+yb=yxyy2x
优势:实现简单,可用任意编程语言

劣势:使用库函数进行编程,无法使用原语言运算表达式

操作符重载法

利用语言多态特性,使用操作符重载基本运算表达式;

运行时将表达式和相应的组合关系记录到tapetapetape中;

遍历tapetapetape,对其中的基本运算操作进行求导;

链式法则对基本表达式的求导结果进行组合。

优势:实现简单,语言具有多态性,易用性高

劣势:显式的构造tapetapetape数据结构和对tapetapetape进行读写;额外数据结构和操作的引入,不利于高阶求导;if、while等控制流表达式难以通过操作符重载。

源码转换法

分析获得语言程序的AST表达式;

基于AST完成基本表达式的分解和求导;

遍历AST得到基本表达式的依赖关系;

链式法则对基本表达式的求导结果进行组合。

优势:丰富数据类型与语言原生操作;无额外数据结构,易于实现高阶求导;求导结果以代码形式存在,方便分布式系统计算。

劣势:代码理解难,复杂度高

Logo

腾讯云面向开发者汇聚海量精品云计算使用和开发经验,营造开放的云计算技术生态圈。

更多推荐