深度学习基础——自动求导
自动求导
前言
求导概念:数值求导,符号求导,自动求导
模式:正向传播,反向求导。雅可比原理
实现:表达式计算图,操作符重载,源码转换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
x1→v−1→v1→v4→v5→f
第二条路径为
x1→v−1→v2→v4→v5→f
x_1\rightarrow v_{-1}\rightarrow v_2\rightarrow v_4\rightarrow v_5\rightarrow f
x1→v−1→v2→v4→v5→f
现在计算第一条路径
∂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}
∂x1∂v−1∂v−1∂v1∂v1∂v4∂v4∂v5∂v5∂f∴∂x1∂f1=1=v−11=x11=1=1=1=1∗1∗1∗x11∗1=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}
∂x1∂v−1∂v−1∂v2∂v2∂v4∂v4∂v5∂v5∂f∴∂x1∂f2=1=v0=x2=1=1=1=1∗1∗1∗x2∗1=x2
最后的结果等于两条路径相加
∂f∂x1=1x1+x2
\frac {\partial f}{\partial x_1}=\frac{1}{x_1}+x_2
∂x1∂f=x11+x2
其中可以观察到两条路径中仅有v−1→v5v_{-1} \rightarrow v_5v−1→v5之间的内容不同,故可以简化计算为
∂f∂x1=1∗1∗1∗(1x1+x2)∗1
\frac {\partial f}{\partial x_1}=1*1*1*(\frac{1}{x_1}+x_2)*1
∂x1∂f=1∗1∗1∗(x11+x2)∗1
这样就完成了对x1x_1x1的一次正向传播,对x2x_2x2的过程与之相似。
总结:正向传播通过对每一个输入变量按照计算图顺序进行跟踪,计算图中每个节点对前一个节点的导数,以计算对每个变量的导数。
反向传播

计算过程如下:
1. 初始化
∂f∂f=1
\frac{\partial f}{\partial f}=1
∂f∂f=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:∂v5∂f=∂v5∂v5=1从 v5 传播到 v4:∂v4∂f=∂v4∂v5⋅∂v5∂f=1从 v5 传播到 v3:∂v3∂f=∂v3∂v5⋅∂v5∂f=−1从 v4 传播到 v1:∂v1∂f=∂v1∂v4⋅∂v4∂f=1从 v4 传播到 v2:∂v2∂f=∂v2∂v4⋅∂v4∂f=1
3. 梯度累加 (针对输入变量)
关于 v−1v_{-1}v−1 (即 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:∂v−1∂v1⋅∂v1∂f=x11⋅1=x11来自 v2:∂v−1∂v2⋅∂v2∂f=v0⋅1=x2∴ ∂v−1∂f=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:∂v0∂v2⋅∂v2∂f=v−1⋅1=x1来自 v3:∂v0∂v3⋅∂v3∂f=cos(v0)⋅(−1)=−cos(x2)∴ ∂v0∂f=x1−cos(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)
∂x1∂f=x11+x2,∂x2∂f=x1−cos(x2)
这样就完成了一次反向传播。
总结:反向传播通过从输出开始需要计算结果对图中每个节点的导数,以计算对每个变量的导数。
雅可比矩阵
在反向传播时,对于函数
y⃗=f(x⃗)
\vec y=f(\vec{x})
y=f(x)
其中
f:Rn→Rm
f:\boldsymbol R^n\rightarrow\boldsymbol R^m
f:Rn→Rm
那么向量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=[∂x1∂y⋯∂xn∂y]=∂x1∂y1⋮∂x1∂ym⋯⋱⋯∂xn∂y1⋮∂xn∂ym
每一行为一个输出值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=[∂y1∂l ⋯ ∂ym∂l]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}
JT⋅v=∂x1∂y1⋮∂x1∂ym⋯⋱⋯∂xn∂y1⋮∂xn∂ymT⋅dy1dl⋮dymdl=∂x1∂y1⋅dy1dl+⋯+∂x1∂ym⋅dymdl⋮∂xn∂y1⋅dy1dl+⋯+∂xn∂ym⋅dymdl
最终得到的结果为
[∂l∂x1⋮∂l∂xn]
\begin{bmatrix}
\frac{\partial l}{\partial x_1} \\
\vdots \\
\frac{\partial l}{\partial x_n}
\end{bmatrix}
∂x1∂l⋮∂xn∂l
其中每个元素代表损失函数对每个输入变量的总梯度。
正向传播时对雅可比矩阵再转置。
综上,当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+∂y∂b=y∂x−∂y∗y2x
优势:实现简单,可用任意编程语言
劣势:使用库函数进行编程,无法使用原语言运算表达式
操作符重载法
利用语言多态特性,使用操作符重载基本运算表达式;
运行时将表达式和相应的组合关系记录到tapetapetape中;
遍历tapetapetape,对其中的基本运算操作进行求导;
链式法则对基本表达式的求导结果进行组合。
优势:实现简单,语言具有多态性,易用性高
劣势:显式的构造tapetapetape数据结构和对tapetapetape进行读写;额外数据结构和操作的引入,不利于高阶求导;if、while等控制流表达式难以通过操作符重载。
源码转换法
分析获得语言程序的AST表达式;
基于AST完成基本表达式的分解和求导;
遍历AST得到基本表达式的依赖关系;
链式法则对基本表达式的求导结果进行组合。
优势:丰富数据类型与语言原生操作;无额外数据结构,易于实现高阶求导;求导结果以代码形式存在,方便分布式系统计算。
劣势:代码理解难,复杂度高
更多推荐
所有评论(0)