首页
学习
活动
专区
工具
TVP
发布
精选内容/技术社群/优惠产品,尽在小程序
立即前往

深度学习中的微分

导读:我们在研究与应用深度学习时,会碰到一个无法绕过的内容,就是微分求导,再具体点其实就是反向传播。如果我们只是简单地应用深度学习、搭搭模型,那么可以不用深究。但是如果想深入的从工程上了解深度学习及对应框架的实现,那么了解程序是如何进行反向传播,自动微分就十分重要了。本文将一步步的从简单的手动求导一直谈到自动微分。

我们目前可以将微分划分为四大类:Manual Differentiation、Symbolic Differentiation、Numeric Differentiation、Automatic Differentiation。

Manual Differentiation

Manual Differentiation正如字面意思所写,就是手动对函数求导。

比如下式就是个简单的函数。

对x求导可得:

对y求导可得:

这个例子很见到,只要了解基本的求导公式,都可以很迅速的解决。但是当我们面临的是一个极其复杂的公式时,整个过程将会非常繁琐,而且容易出错。

Symbolic Differentiation

为了避免人力的介入,首先提出了 Symbolic Differentiation 即符号微分。

下面也是个简单的例子:

符号微分方式如下图:

这个例子很简单,但是对于复杂函数会生成非常大的图,很难简化,有一定的性能问题。

更重要的是,符号微分无法对任意代码定义的函数进行求导,如下:

代码语言:javascript
复制
def my_func(a, b):
    z = 0
    for i in range(1000):
        z = a * np.cos(z + i) + z * n
    return z

Numeric Differentiation

那怎样才能对任意函数进行求导呢,于是引出了Numeric Differentiation,顾名思义就是用数值去近似计算,求微分。

我们知道:

代码语言:javascript
复制
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次,在大规模的神经网络模型中,这样的做法是很低效的。不过数值微分比较简单,可以用来对手动求导的值进行验证。

Automatic Differentiation

Autodiff算是在之前提到的这些方案基础之上得到的一个兼具性能和数值稳定的方案。

1. Forward-Mode Autodiff

  • Forward-Mode是数值微分和符号微分的结合
  • 引入二元数ε, ϵ**2 = 0 ( ϵ ≠ 0)
  • 所以f(x,y) 在(3,4)点对x的导数为ϵ前面的系数

forward-mode autodiff 比数值微分更准确,但是也有同样的问题,就是有n个参数要走n次图

2. Reverse-Mode Autodiff

为了解决Forward-Mode中需要根据参数进行图遍历的问题,提出了Reverse-Mode Autodiff方案。这也就是我们常说的链式法则、反向传播算法(machine learning中我们常常将其等价)。

  • 第一遍,先从输入到输出,进行前向计算
  • 第二遍,从输出到输入,进行反向计算求偏导
  • Reverse-Mode Autodiff在有大量输入,少量输出时是非常有力的方案
  • 一次正向,一次反向可以计算所有输入的偏导
  • 一个简易教学版的Autograd的实现: https://github.com/mattjj/autodidact

3. 相关工程实现

Autograd将numpy包了一层,提供与numpy相同的基础ops,但自己内部创建了计算图

代码语言:javascript
复制
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。

代码语言:javascript
复制
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基本操作,求出倒数就是原来基础上取负。整个过程可以划分为三块。

  • vector-Jacobian products for primitive ops
  • backwards pass

再具体的细节就不粘贴了,有兴趣的同学可以访问我之前贴出的链接,几百行代码,麻雀虽小五脏俱全,非常适合学习。

Reference

  1. Géron, Aurélien. “Hands-On Machine Learning with Scikit-Learn and TensorFlow” https://www.cs.toronto.edu/~rgrosse/courses/csc321_2018/slides/lec10.pdf
  2. Autograd的实现: https://github.com/mattjj/autodidact

本文来自 DataFun 社区

原文链接

https://mp.weixin.qq.com/s?__biz=MzU1NTMyOTI4Mw==&mid=2247494948&idx=1&sn=bd68530672340d263b1b62e25772de3b&chksm=fbd75f48cca0d65e9f808a04da399b62f2c2eeda309513fd04ceb16ac14b1123e2894065a530&scene=27#wechat_redirect

  • 发表于:
  • 本文为 InfoQ 中文站特供稿件
  • 首发地址https://www.infoq.cn/article/7tfXxah6mfZpHVhK0SoC
  • 如有侵权,请联系 cloudcommunity@tencent.com 删除。

扫码

添加站长 进交流群

领取专属 10元无门槛券

私享最新 技术干货

扫码加入开发者社群
领券