在程序中求导数和微分一般有四种方式:
1. 手动求微分:采用纯人工方式,与计算机无关,这种我们不进行讨论
2. 数值方法:这种方式利用导数的定义,直接求解微分值
3. 符号微分法:通过解析式找到函数导数的表达式,将其转化为计算机程序
4. 自动微分:采用类似有向图的计算来求解微分值
在这里我们以一个较为复杂的多项式函数为例,来说明三种计算方式的不同。函数定义为:
@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)的导数定义为:
def dp_numeric_diff(x):
delta_x = 0.0001
return (dp1_f1(x+delta_x)-dp1_f1(x))/delta_x
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=4⋅l1⋅(1−l1) l 2 = 4 ⋅ l 1 ⋅ ( 1 − l 1 ) ,我们一方面可以根据这个值求出 l2 l 2 的值,同时也可以求出
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求出的结果是一致的,都是比较精确的结果,而数值微分的结果会有一定的误差。