计算机程序的求导机制分为四种
- 人工计算出导数,并写成代码形式
- 数值计算微分,即取极限求导数
- 符号微分,使用表达式来计算导数
- 自动微分
比如下面的程序所示
import numpy as np
def sigmoid(x):
"""
Compute sigmoid Function
"""
return 1 / (1+np.exp(-x))
def sigmoid_grad(x):
return np.exp(-x) / np.square(1 + np.exp(-x))
def sigmoid_numerical_grad(x, eps=1e-6):
return (sigmoid(x+eps) - sigmoid(x)) / eps
print("Handcraft Diff is: ", sigmoid_grad(0))
print("Numerical Diff is: ", sigmoid_numerical_grad(0))
Handcraft Diff is: 0.25
Numerical Diff is: 0.2500000000349445
这几种方法存在一定问题,比如人工计算微分,当模型变大时,微分计算耗时长,容易出错;而数值微分看起来简单,但由于浮点数特性,容易出现截断错误,另外它对于梯度缩放能力较差,而机器学习模型通常比较大。符号微分能很好避免前面两者的错误,但是容易受表达式膨胀的问题
自动微分技术,指的是通过在代码执行过程中,积累数值,来计算导数。
在深度学习中,模型通过反向传播算法来在网络空间中,寻找梯度下降方向,寻找目标函数,也就是我们常说的 loss function 的最小值。
section2
上图展示的是一个正向传播和反向传播的过程
自动微分并不是数值微分
自动微分具有数值微分和符号微分的特点,但是具有不同之处
数值微分的形式如数学定义
d
f
(
x
)
d
x
≈
f
(
x
+
δ
)
−
f
(
x
)
δ
\frac{df(x)}{dx} \approx \frac{f(x+\delta)-f(x)}{\delta}
dxdf(x)≈δf(x+δ)−f(x)
但是对于n维数组,则计算复杂度为 O(n),而且这个
δ
\delta
δ 选择需要十分小心,过大,则数值计算的梯度不够。
数值微分由于浮点数计算原因,存在截断和近似错误
其中forward计算diff的公式为上面的公式,center计算diff的公式为
d
f
(
x
)
d
x
≈
f
(
x
−
δ
)
−
f
(
x
+
δ
)
2
∗
δ
\frac{df(x)}{dx} \approx \frac{f(x-\delta)-f(x+\delta)}{2*\delta}
dxdf(x)≈2∗δf(x−δ)−f(x+δ)
最大的痛处在于,现代深度学习模型参数量庞大,数值微分引入的计算复杂度会使整个反向传播,更新参数的过程十分低效
自动微分不是符号微分
符号微分,是对原始公式进行操作,从而得到对应导数的表达式,如
d
(
f
(
x
)
+
g
(
x
)
)
d
x
−
>
d
(
f
(
x
)
)
d
x
+
d
(
g
(
x
)
)
d
x
\frac{d(f(x)+g(x))}{dx} ->\frac{d(f(x))}{dx} + \frac{d(g(x))}{dx}
dxd(f(x)+g(x))−>dxd(f(x))+dxd(g(x))
简单的表达式,符号微分很简单,直观。但随着表达式复杂起来,符号微分得到的表达式是会很复杂的,也是常说的表达式膨胀问题 expression swell
自动微分
自动微分的主要思想就是, 所有导数运算到最后,就是一组有限初等运算组合而成。此外,自动微分还能处理复杂的表达形式,比如分支,循环,回溯等
下面我们来看一个例子,这是函数
f
(
x
1
,
x
2
)
=
l
n
(
x
1
)
+
x
1
∗
x
2
−
s
i
n
(
x
2
)
f(x1, x2) = ln(x1) + x1*x2 - sin(x2)
f(x1,x2)=ln(x1)+x1∗x2−sin(x2)
的计算图
在前向求导模式中,就是经过表达式运算得到对应的值,从左至右推得对应的导数
我们假设只对x1求导,那么就有如下的求导过程(右侧)
然后我们可以构造一个雅各比矩阵
对应每一步求导,就相当于雅各比矩阵乘上一个向量
雅各比矩阵补充
假设我们有一个三维的向量
X
=
[
x
1
,
x
2
,
x
3
]
Y
=
X
2
X = [x1 , x2,x3] \\Y = X^2
X=[x1,x2,x3]Y=X2
那么对Y 求导,结果不是 2 X ,因为X是一个向量,求导得到的是一个雅各比矩阵
可参考 https://zhuanlan.zhihu.com/p/65609544
DUAL NUMBERS 对偶数求导法
不清楚
反向模式
主要是利用链式法则的变换
在 大量输入 的情况下,前向求导模式每次计算都需要O(n)。而反向求导从后往前推,相比前向模式,其计算量小
所以 Pytorch 这类框架禁掉了 tensor 对 tensor的求导。它backward一定是从一个scalar开始backward。而scalar标量值就是1个,因此反向传播在计算量上更具优势!
反向传播的雅各比矩阵,则是转置后,乘上一个张量
根据前面的计算量比较,我们可以得知,当输入比较少的时候(即雅各比矩阵比较窄,但是比较高)则适合前向模式
当输入比较多的时候(即雅各比矩阵比较宽,但是比较矮)则适合后向模式