购买
下载掌阅APP,畅读海量书库
立即打开
畅读海量书库
扫码下载掌阅APP

步骤4
数值微分

现在,我们已经实现了 Variable 类和 Function 类。实现这些类的目的是自动微分。在本步骤中,我们会先复习导数,并尝试用一种叫作数值微分(numerical differentiation)的方法来求导。从下一个步骤开始,我们将实现一种替代数值微分的更高效的算法—— 反向传播

导数不仅仅在机器学习领域,在其他领域也很重要。从流体力学、金融工程到气象模拟、工程设计优化,很多领域需要进行导数计算,而且这些领域实际上都在使用自动微分的功能。

4.1 什么是导数

什么是导数?简单地说,导数是变化率的一种表示方式。比如某个物体的位置相对于时间的变化率就是位置的导数,即速度。速度相对于时间的变化率就是速度的导数,即加速度。像这样,导数表示的是变化率,它被定义为在极短时间内的变化量。函数 处的导数可用下面的式子表示。

式子4.1 中的 表示极限,意思是 应尽可能地接近0。 式子4.1 中的 图4-1 中通过两点的直线的斜率。

图4-1 曲线 和通过其两点的直线

图4-1 所示,函数 两点之间的变化率为 。让 的值尽可能地接近0,就可以求出 处的变化率。这就是 的导数。另外,在 的可导区间内,对于该区间内的任何 式子4.1 都成立。因此, 式子4.1 中的 也是一个函数,我们称之为 导函数

4.2 数值微分的实现

下面根据导数的定义式,即 式子4.1 来实现求导。需要注意的是,计算机不能处理极限值。因此,这里的 表示一个近似值。例如我们可以用 (= 1e-4 )这种非常小的值来计算 式子4.1 。利用微小的差值获得函数变化量的方法叫作 数值微分

数值微分使用非常小的值 求出的是真的导数的近似值。因此,这个值包含误差。中心差分近似是减小近似值误差的一种方法。中心差分近似计算的不是 的差,而是 的差。 图4-2 中的粗线表示的就是中心差分近似。

图4-2 比较真的导数、前向差分近似和中心差分近似

图4-2 所示,计算过 这两点的直线的斜率的方法称为前向差分近似,计算 这两点间斜率的方法称为中心差分近似,中心差分近似实际产生的误差更小。这一点的证明超出了本书的范围,不过我们从图4-2中直线的斜率可以直观地得出这个结论。中心差分近似的直线斜率为 (注意分母是 )。

中心差分近似比前向差分近似更接近真的导数这个结论可以通过泰勒展开来证明。相关证明过程请阅读参考文献[1]等资料。

下面我们来实现使用中心差分近似求数值微分的函数,该函数的名称为 numerical_diff(f, x, eps=1e-4) 。这里的参数 f 是被求导的函数,是 Function 类的实例。参数 x 为求导的变量,是 Variable 类的实例。参数 eps 代表一个微小的值,默认值是 1e-4 eps 是epsilon的缩写)。数值微分可通过以下代码实现。

steps/step04.py

def numerical_diff(f, x, eps=1e-4):
    x0=Variable(x.data-eps)
    x1=Variable(x.data+eps)
    y0=f(x0)
    y1=f(x1)
    return (y1.data-y0.data)/(2 * eps)

只要注意 Variable 实例变量 data 中包含实际数据即可。下面我们使用这个函数求 步骤3 中实现的 Square 类的导数。

steps/step04.py

f=Square()
x=Variable(np.array(2.0))
dy=numerical_diff(f, x)
print(dy)
运行结果
4.000000000004

由上面的运行结果可知, 时计算得到的导数值为4.000000000004。不包含误差的导数值是4.0,可以说这个结果大体正确。

导数也可以通过解析解的方式求解。解析解的方式求解是指只通过式子的变形推导出答案。在上面的例子中,根据导数的公式可知, 的导数为 求导的符号)。因此, 处的导数为4.0。这个4.0是不包含误差的值。前面的数值微分结果虽然不是正好为4.0,但我们可以看出误差是相当小的。

4.3 复合函数的导数

我们在前面接触的是 这种简单的函数。接下来尝试对复合函数求导。下面求 的导数 。代码如下所示。

steps/step04.py

def f(x):
    A=Square()
    B=Exp()
    C=Square()
    return C(B(A(x)))

x=Variable(np.array(0.5))
dy=numerical_diff(f, x)
print(dy)
运行结果
3.2974426293330694

上面的代码将一系列的计算组合成了一个名为 f 的函数。函数在Python中也是对象,所以可以作为参数传给其他函数。在上面的例子中,函数 f 作为参数传给了 numerical_diff 函数。

运行上面的代码,得到的导数是3.297...。这意味着,如果 x 从0.5变成一个微小的值, y 值的变化幅度将是这个微小值的3.297...倍。

现在我们已经成功实现了“自动”求导。只要用代码来定义要完成的计算(前面的例子定义了函数 f ),程序就会自动求出导数。使用这种方法,无论多么复杂的函数组合,程序都能自动求出导数。今后函数的种类越来越多的话,不管是什么计算,只要是可微函数,程序就能求出它的导数。不过遗憾的是,数值微分的方法存在一些问题。

4.4 数值微分存在的问题

数值微分的结果包含误差。在多数情况下,这个误差非常小,但在一些情况下,计算产生的误差可能会很大。

数值微分的结果中容易包含误差的主要原因是“精度丢失”。中心差分近似等求差值的方法计算的是相同量级数值之间的差,但由于精度丢失,计算结果中会出现有效位数减少的情况。以有效位数为4的情况为例,在计算两个相近的值之间的差时,比如1.234-1.233,其结果为0.001,有效位数只有1位。本来可能是1.234...-1.233...=0.001434...之类的结果,但由于精度丢失,结果变成0.001。同样的情况也会发生在数值微分的差值计算中,精度丢失使结果更容易包含误差。

数值微分更严重的问题是计算成本高。具体来说,在求多个变量的导数时,程序需要计算每个变量的导数。有些神经网络包含几百万个以上的变量(参数),通过数值微分对这么多的变量求导是不现实的。这时,反向传播就派上了用场。从下一个步骤开始,笔者将介绍反向传播。

另外,数值微分可以轻松实现,并能计算出大体正确的数值。而反向传播是一种复杂的算法,实现时容易出现bug。我们可以使用数值微分的结果检查反向传播的实现是否正确。这种做法叫作 梯度检验(gradientchecking) ,它是一种将数值微分的结果与反向传播的结果进行比较的方法。 步骤10 实现了梯度检验。 M2sJzI1ShgaI6oIkihuq/See7c5gaGcGTLehr+0eNr3Uf/xzXmvx/NrlRVPgpJyE

点击中间区域
呼出菜单
上一章
目录
下一章
×