TensorFlow可微编程实践1---自动微分简介

在程序中求导数和微分一般有四种方式:
1. 手动求微分:采用纯人工方式,与计算机无关,这种我们不进行讨论
2. 数值方法:这种方式利用导数的定义,直接求解微分值
3. 符号微分法:通过解析式找到函数导数的表达式,将其转化为计算机程序
4. 自动微分:采用类似有向图的计算来求解微分值

在这里我们以一个较为复杂的多项式函数为例,来说明三种计算方式的不同。函数定义为:

l1=x l 1 = x

ln+1=4ln(1ln) l n + 1 = 4 ⋅ l n ⋅ ( 1 − l n )

f(x)=l4=64x(1x)(12x)2(18x+8x2)2 f ( x ) = l 4 = 64 ⋅ x ⋅ ( 1 − x ) ⋅ ( 1 − 2 x ) 2 ⋅ ( 1 − 8 x + 8 x 2 ) 2

程序中可以定义为:

@tf.custom_gradient
def f3(x, n):
    v = tf.pow(x, n)
    def grad(dy):
        return (dy* (n*tf.pow(x, n-1)) ).numpy()
    return v.numpy(), grad

def dp1_f1(x):
    return 64*x*(1-x)*f3(1-2*x,2)*f3(1-8*x+8*x*x, 2)

这里需要特别注意,TensorFlow Eager Execution API不能处理诸如math.pow这类函数的导数,因此需要我们定义新的函数,告诉TensorFlow怎样求导,所以我们定义了f3函数,并定义了其导数的计算方法。
- 数值方法
我们知道在高等数学中,对函数y=f(x)的导数定义为:

dydx=limΔx0f(x+Δx)f(x)Δx d y d x = lim Δ x → 0 f ( x + Δ x ) − f ( x ) Δ x

根据这个定义,我们可以求任意复杂函数的导数,但是对于复杂的尤其是多维函数,由于计算机数字表示的有限精度问题,这种方式存在运算量大而且精度不高的问题,通常只用于验证我们用其他方法求出的微分值的正确性。

def dp_numeric_diff(x):
    delta_x = 0.0001
    return (dp1_f1(x+delta_x)-dp1_f1(x))/delta_x
  • 符号微分法
    这种方法的核心是通过人工求出函数的微分的解析式,然后编制为程序,最后通过程序来计算函数的微分值。这种方式的精度最高,但是通常函数微分解析式非常复杂,求解过程中极易出错,同时这种方式仅适合可以写出解析式的情况,对于隐式方程形式,就无法求解了。
    我们可以求出导数的解析表达式为:
    dydx=128x(1x)(8+16x)(12x)2+64(1x)(12x)2(18x+8x2)264x(12x)2(18x+8x2)2256x(1x)(12x)(18x+8x2)2 d y d x = 128 x ( 1 − x ) ( − 8 + 16 x ) ( 1 − 2 x ) 2 + 64 ( 1 − x ) ( 1 − 2 x ) 2 ( 1 − 8 x + 8 x 2 ) 2 − 64 x ( 1 − 2 x ) 2 ( 1 − 8 x + 8 x 2 ) 2 − 256 x ( 1 − x ) ( 1 − 2 x ) ( 1 − 8 x + 8 x 2 ) 2

    具体的程序实现为:
def dp_symbolic_diff(x):
    return 128*x*(1 - x)*(-8 + 16*x)*( math.pow((1 - 2*x), 2) )*
            (1 - 8*x + 8*x*x)+ 
            (64*x* math.pow((1 -2*x), 2) )*math.pow((1 - 8*x + 8*x*x), 2) -
            256*x*(1 - x)*(1 - 2*x)*math.pow((1 - 8*x+ 8*x*x), 2)
  • 自动微分
    下面我们来用自动微分方法来求解这个问题。首先我们画出这个问题对应的计算图,如下图所示:
    这里写图片描述

图中的l1就是自变量x,根据定义可知 l2=4l1(1l1) l 2 = 4 ⋅ l 1 ⋅ ( 1 − l 1 ) ,我们一方面可以根据这个值求出 l2 l 2 的值,同时也可以求出

l1=x l 1 = x

l2=4l1(1l1) l 2 = 4 ⋅ l 1 ⋅ ( 1 − l 1 )

所以l2的导数为:
dl2dl1=(4l1(1l1))=(4l14l21)=4l18l1l1 d l 2 d l 1 = ( 4 ⋅ l 1 ⋅ ( 1 − l 1 ) ) ′ = ( 4 ⋅ l 1 − 4 ⋅ l 1 2 ) ′ = 4 l 1 ′ − 8 l 1 l 1 ′

同理对l3的导数为:
dl3dl2=(4l2(1l2))=(4l24l22)=4l28l2l2 d l 3 d l 2 = ( 4 ⋅ l 2 ⋅ ( 1 − l 2 ) ) ′ = ( 4 ⋅ l 2 − 4 ⋅ l 2 2 ) ′ = 4 l 2 ′ − 8 l 2 l 2 ′

同理对l4的导数为:
dl4dl3=(4l3(1l3))=(4l34l23)=4l38l3l3 d l 4 d l 3 = ( 4 ⋅ l 3 ⋅ ( 1 − l 3 ) ) ′ = ( 4 ⋅ l 3 − 4 ⋅ l 3 2 ) ′ = 4 l 3 ′ − 8 l 3 l 3 ′

根据上面的计算图,我们采用普通的Python程序即可计算出导数值:

def dp_ad_python(x):
    (v, dv) = (x, 1)
    for i in range(3):
        (v, dv) = (4*v*(1-v), 4*dv-8*v*dv)
    return (v, dv)

运行以上程序,计算结果为:
这里写图片描述

如图所示,符号微分和自动微分算出的结果是一致的,而数据微分的结果与其略有不同,说明数值微分还是有误差的。
如果采用TensorFlow Eager Execution API来进行计算,代码如下所示:

def dp_ad_tfe(x):
    #tf.enable_eager_execution()
    tfe = tf.contrib.eager
    grad_lx = tfe.gradients_function(dp1_f1)
    x = 3.0
    y = dp1_f1(x)
    rst = grad_lx(x)
    return y, rst[0]

我们采用如下代码来调用这些微分方法:

def test(args={}):
    x = 3.0
    y = dp1_f1(x)
    print('函数值:{0}'.format(y))
    numeric_diff = dp_numeric_diff(x)
    print('数值微分:{0}'.format(numeric_diff))
    symbolic_diff = dp_symbolic_diff(x)
    print('符号微分:{0}'.format(symbolic_diff))
    y, dv = dp_ad_python(x)
    print('自动微分:{0}'.format(dv))
    v, d = dp_ad_tfe(x)
    print('TFE:{0}'.format(d))

其结果如下所示:
这里写图片描述
由此可以看出,采用符号微分、纯Python自动微分、TensorFlow Eager Execution API求出的结果是一致的,都是比较精确的结果,而数值微分的结果会有一定的误差。

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值