自动微分原理与示例
自动微分原理与示例
机器学习的同学在学习过程中会经常遇到一个问题,那就是对目标函数进行求微分,线性回归这类简单的就不说、复杂的如神经网络类那些求导过程。
本文介绍了五种微分方式,最后两种才是自动微分。
前两种方法求出了原函数对应的导函数,后三种方法只是求出了某一点的导数。
假设原函数是f(x,y)=x2y+y+2,需要求其偏导数 和,以便应用于梯度下降等算法。
1、手工求导
首先准备一张纸和一支笔,根据上学时候学到的求导法则,开始计算。最终得到的结果。
缺点是对于复杂函数容易出错。这一计算过程可由计算机帮完成,这就是符号微分。
这个小例子比较简单,口算即可得到答案,但如果方程比较复杂那就难说了。幸好有自动求导的方法,例如符号求导方法。
2、符号微分(Symbolic Differentiation)
如图1所示,使用符号微分的方法,计算函数g(x,y)=5+xy的偏导数。图左侧代表函数g(x,y),右侧代表g(x,y)关于x的偏导数
(同样的,可以求得)。
图1. 符号微分
算法首先求叶子节点关于x的偏导数,然后沿着树向上,求得其他节点关于自变量的偏导数。这与手工求导所使用的规则是一样的。
如果函数复杂,算法生成的树将十分庞大,性能不高。而且无法对很随意的代码求导,例如:
def my_func(a, b):
z = 0
for i in range(100):
z = a * np.cos(z + i) + z * np.sin(b - i)
return z
3、数值微分(Numerical Differentiation)
这是根据导数的定义来求解的。函数h(x)h(x)在x0x0点的导数为:
取一个很小的ε,带入公式进行计算即可。方法所得结果不够精确,参数过多时计算量也比较大。但是计算起来很简单,可用于校验手工算出的导数是否正确。
如果有1000个参数,至少需要调用h(x)1001词,来求得所有偏导数。
导数的定义是当自变量的增量趋于零时,因变量的增量与自变量的增量之商的极限。
其中εε是一个无穷小的数,所以我们可以计算在x=3,y=4这一点对x的偏导数,,对应的代码如下:
def f(x, y):
return x**2*y + y + 2
def derivative(f, x, y, x_eps, y_eps):
return (f(x + x_eps, y + y_eps) - f(x, y)) / (x_eps + y_eps)
df_dx = derivative(f, 3, 4, 0.00001, 0)
df_dy = derivative(f, 3, 4, 0, 0.00001)
>>print(df_dx)
24.000039999805264
>>print(df_dy)
4、前向自动微分(Forward-Mode Autodiff)
算法依赖一个虚数(dual numbers,这让我想起来oracle的虚表。难度dual可以表示虚无的意思?) ε,满足,但ε≠0(姑且理解为一阶无穷小吧)。
由于ε是无穷小,因此满足h(a+bε)=h(a)+b×h′(a)ε。因此,算出h(a+ε)可以同时得到h(a)和h′(a),如图2所示。
图2. 前向自动微分
上图值计算了,同样的方法可以算的。
如果有1000个参数,需要遍历上图1000次,来求得所有偏导数。
5、反向自动微分(Reverse-Mode Autodiff)
这是TensorFlow所采用的自动微分算法。如图3所示,算法首先前向(也就是从输入到输出)计算每个节点的值,然后反向(从输出到输入)计算所有的偏导数。
图3. 反向自动微分
反向计算时应用链式求导法则:
由于n7n7就是输出节点,f=n7,因此。
算法强大且精确,尤其是输入很多,输出很少时。假如函数有10个输出(不管输入是1千,2万还是更多),求得所有偏导数需要对上图遍历11次。
各个算法比较:
参考链接:
https://www.cnblogs.com/royhoo/p/Autodiff.html
https://www.cnblogs.com/wxshi/p/8007106.html