导读:我们在研究与应用深度学习时,会碰到一个无法绕过的内容,就是微分求导,再具体点其实就是反向传播。如果我们只是简单地应用深度学习、搭搭模型,那么可以不用深究。但是如果想深入的从工程上了解深度学习及对应框架的实现,那么了解程序是如何进行反向传播,自动微分就十分重要了。本文将一步步的从简单的手动求导一直谈到自动微分。
我们目前可以将微分划分为四大类:Manual Differentiation、Symbolic Differentiation、Numeric Differentiation、Automatic Differentiation。
Manual Differentiation正如字面意思所写,就是手动对函数求导。
比如下式就是个简单的函数。
对x求导可得:
对y求导可得:
这个例子很见到,只要了解基本的求导公式,都可以很迅速的解决。但是当我们面临的是一个极其复杂的公式时,整个过程将会非常繁琐,而且容易出错。
为了避免人力的介入,首先提出了 Symbolic Differentiation 即符号微分。
下面也是个简单的例子:
符号微分方式如下图:
这个例子很简单,但是对于复杂函数会生成非常大的图,很难简化,有一定的性能问题。
更重要的是,符号微分无法对任意代码定义的函数进行求导,如下:
def my_func(a, b):
z = 0
for i in range(1000):
z = a * np.cos(z + i) + z * n
return z
那怎样才能对任意函数进行求导呢,于是引出了Numeric Differentiation,顾名思义就是用数值去近似计算,求微分。
我们知道:
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) # 24.000039999805264
df_dy = derivative(f, 3, 4, 0, 0.00001) # 10.000000000331966
数值微分里边,如上边的代码,我们至少需要执行三次f(x,y),当我们的参数有1000个时就至少要执行1001次,在大规模的神经网络模型中,这样的做法是很低效的。不过数值微分比较简单,可以用来对手动求导的值进行验证。
Autodiff算是在之前提到的这些方案基础之上得到的一个兼具性能和数值稳定的方案。
1. Forward-Mode Autodiff
forward-mode autodiff 比数值微分更准确,但是也有同样的问题,就是有n个参数要走n次图
2. Reverse-Mode Autodiff
为了解决Forward-Mode中需要根据参数进行图遍历的问题,提出了Reverse-Mode Autodiff方案。这也就是我们常说的链式法则、反向传播算法(machine learning中我们常常将其等价)。
3. 相关工程实现
Autograd将numpy包了一层,提供与numpy相同的基础ops,但自己内部创建了计算图
import autograd.numpy as np
from autograd import grad
def logistic(z):
# 等价形式
# np.reciprocal(np.add(1, np.exp(np.negative(z))))
return 1. / (1. + np.exp(-z))
print(logistic(1.5)) # 0.8175744761936437
print(grad(logistic)(1.5)) # 0.14914645207033284
① Vector-Jacobian Products
雅克比矩阵:
vector-Jacobian product (VJP):
对于每一个基本操作都需要定义一个VJP。
primitive_vjps = defaultdict(dict)
def defvjp(fun, *vjps, **kwargs):
argnums = kwargs.get('argnums', count())
for argnum, vjp in zip(argnums, vjps):
primitive_vjps[fun][argnum] = vjp
defvjp(anp.negative, lambda g, ans, x: -g)
defvjp(anp.exp, lambda g, ans, x: ans * g)
defvjp(anp.log, lambda g, ans, x: g / x)
defvjp(anp.tanh, lambda g, ans, x: g / anp.cosh(x) **2)
defvjp(anp.sinh, lambda g, ans, x: g * anp.cosh(x))
defvjp(anp.cosh, lambda g, ans, x: g * anp.sinh(x))
tracing the forward pass to build the computation graph上边的代码是截取了autodidact的部分代码,像negative基本操作,求出倒数就是原来基础上取负。整个过程可以划分为三块。
再具体的细节就不粘贴了,有兴趣的同学可以访问我之前贴出的链接,几百行代码,麻雀虽小五脏俱全,非常适合学习。
本文来自 DataFun 社区
原文链接:
领取专属 10元无门槛券
私享最新 技术干货