梯度下降法是机器学习中一种常用到的算法,是一种求解的最优化算法。主要解决求最小值问题,其基本思想在于不断地逼近最优点,每一步的优化方向就是梯度的方向。

一个梯度下降的经典例子

梯度下降法的基本思想可以类比为一个下山的过程。假设一个人被困在山上,需要从山上下来(i.e. 找到山的最低点,也就是山谷)。但此时山上的浓雾很大,导致可视度很低。因此,下山的路径就无法确定,他必须利用自己周围的信息去找到下山的路径。这个时候,他就可以利用梯度下降算法来帮助自己下山。具体来说就是,以他当前的所处的位置为基准,寻找这个位置最陡峭的地方,然后朝着山的高度下降的地方走,同理,如果我们的目标是上山,也就是爬到山顶,那么此时应该是朝着最陡峭的方向往上走。然后每走一段距离,都反复采用同一个方法,最后就能成功的抵达山谷。

可以看出,梯度下降不一定能够找到全局的最优解,有可能是一个局部最优解。当然,如果损失函数是凸函数,梯度下降法得到的解就一定是全局最优解。

梯度

梯度是一个向量,向量有方向,我们说的梯度方向,其实就是函数变化率最大的方向,这就是梯度的几何意义。比如函数f(x,y), 分别对x,y求偏导数,求得的梯度向量就是(∂f/∂x, ∂f/∂y)T,简称grad f(x,y)或者▽f(x,y)。对于在点(x0,y0)的具体梯度向量就是(∂f/∂x0, ∂f/∂y0)T.或者▽f(x0,y0),如果是3个参数的向量梯度,就是(∂f/∂x, ∂f/∂y,∂f/∂z)T,以此类推。

梯度的公式如下:

 这里注意三点:
 1)梯度是一个向量,即有方向有大小;
 2)梯度的方向是最大方向导数的方向;
 3)梯度的值(这里指的是模)是最大方向导数(导数本身就是值)  

梯度下降

梯度下降算法背后的原理:
目标函数J(θ)关于参数θ的梯度是目标函数上升最快的方向。对于最小化优化问题,只需要将参数沿着梯度相反的方向前进一个步长,就可以实现目标函数的下降。即给定一个与参数 θ 有关的目标函数J(θ), 求使得 J 最小的参数θ.我们把这个步长又称为学习速率η。

参数更新公式如下:

其中∇θJ(θ)是参数的梯度,根据计算目标函数J(θ)采用数据量的不同,梯度下降算法又可以分为批量梯度下降算法(Batch Gradient Descent)随机梯度下降算法(Stochastic GradientDescent)小批量梯度下降算法(Mini-batch Gradient Descent)。对于批量梯度下降算法,其J(θ)是在整个训练集上计算的,如果数据集比较大,可能会面临内存不足问题,而且其收敛速度一般比较慢。随机梯度下降算法是另外一个极端,J(θ)是针对训练集中的一个训练样本计算的,又称为在线学习,即得到了一个样本,就可以执行一次参数更新。所以其收敛速度会快一些,但是有可能出现目标函数值震荡现象,因为高频率的参数更新导致了高方差。小批量梯度下降算法是折中方案,选取训练集中一个小批量样本计算J(θ),这样可以保证训练过程更稳定,而且采用批量训练方法也可以利用矩阵计算的优势。这是目前最常用的梯度下降算法。

一维梯度下降

我们先以简单的一维梯度下降为例,解释梯度下降算法可能降低目标函数值的原因。假设连续可导的函数 f:ℝ→ℝ 的输入和输出都是标量。给定绝对值足够小的数 ϵ ,根据泰勒展开公式,我们得到以下的近似:

这里 f′(x) 是函数 f 在 x 处的梯度。一维函数的梯度是一个标量,也称导数。
接下来,找到一个常数 η>0 ,使得 足够小,那么可以将 ϵ 替换为 −ηf′(x) 并得到:

如果导数 f′(x)≠0 ,那么 ηf′(x)2>0 ,所以

这意味着,如果通过

来迭代 x ,函数 f(x) 的值可能会降低。因此在梯度下降中,我们先选取一个初始值 x 和常数 η>0 ,然后不断通过上式来迭代 x ,直到达到停止条件,例如
的值已足够小或迭代次数已达到某个值。

下面我们以目标函数为例来看一看梯度下降是如何工作的。虽然我们知道最小化 f(x) 的解为 x=0 ,这里依然使用这个简单函数来观察 x 是如何被迭代的。首先,导入本节实验所需的包或模块。

1
2
3
4
5
%matplotlib inline
import d2lzh as d2l
import math
from mxnet import nd
import numpy as np

接下来使用 x=10 作为初始值,并设 η=0.2 。使用梯度下降对 x 迭代10次,可见最终 x 的值较接近最优解。

1
2
3
4
5
6
7
8
9
10
def gd(eta):
x = 10
results = [x]
for i in range(10):
x -= eta * 2 * x # f(x) = x * x的导数为f'(x) = 2 * x
results.append(x)
print('epoch 10, x:', x)
return results

res = gd(0.2)

epoch 10, x: 0.06046617599999997

下面将绘制出自变量 x 的迭代轨迹。

1
2
3
4
5
6
7
8
9
10
def show_trace(res):
n = max(abs(min(res)), abs(max(res)), 10)
f_line = np.arange(-n, n, 0.1)
d2l.set_figsize()
d2l.plt.plot(f_line, [x * x for x in f_line])
d2l.plt.plot(res, [x * x for x in res], '-o')
d2l.plt.xlabel('x')
d2l.plt.ylabel('f(x)')

show_trace(res)

学习率η

上述梯度下降算法中的正数 η 通常叫作学习率。这是一个超参数,需要人工设定。如果使用过小的学习率,会导致 x 更新缓慢从而需要更多的迭代才能得到较好的解。

下面展示使用学习率 η=0.05 时自变量 x 的迭代轨迹。可见,同样迭代10次后,当学习率过小时,最终 x 的值依然与最优解存在较大偏差。

1
show_trace(gd(0.05))

epoch 10, x: 3.4867844009999995

如果使用过大的学习率, 可能会过大从而使前面提到的一阶泰勒展开公式不再成立:这时我们无法保证迭代 x 会降低 f(x) 的值。

举个例子,当设学习率 η=1.1 时,可以看到 x 不断越过(overshoot)最优解 x=0 并逐渐发散。

1
show_trace(gd(1.1))

epoch 10, x: 61.917364224000096

小结

  1. 使用适当的学习率,沿着梯度反方向更新自变量可能降低目标函数值。梯度下降重复这一更新过程直到得到满足要求的解。
  2. 学习率过大或过小都有问题。一个合适的学习率通常是需要通过多次实验找到的。