深度学习中的优化算法

深度学习中的优化算法(随机梯度下降法)

在文章中首先介绍固定学习率的优化器(SGD、Momentum),其次是适应学习率算法(Adagrad、Rmsprop、Adadelta、Adam)。

  1. 随机梯度下降法 (SGD)
  2. 动量法(Momentum)
  3. Adagrad 算法
  4. RMSProp 算法
  5. Adadelta 法
  6. Adam 算法

1.随机梯度下降法 (SGD)

Stochastic Gradient Descent
随机梯度下降法非常简单,公式就是:
深度学习中的优化算法
python实现:

def sgd_update(parameters, lr):
    for param in parameters:
        param.data = param.data - lr * param.grad.data

pytorch 中已经为我们内置了随机梯度下降:

optimzier = torch.optim.SGD(net.parameters(), 1e-2)

2.动量法(Momentum)

使用梯度下降法,每次都会朝着目标函数下降最快的方向,这也称为最速下降法。这种更新方法看似非常快,实际上存在一些问题
深度学习中的优化算法
动量法的提出就是为了应对这个问题,我们梯度下降法做一个修改如下:
深度学习中的优化算法
其中 vi 是当前速度, γ 是动量参数,是一个小于 1的正数, η 是学习率

相当于每次在进行参数更新的时候,都会将之前的速度考虑进来,每个参数在各方向上的移动幅度不仅取决于当前的梯度,还取决于过去各个梯度在各个方向上是否一致,如果一个梯度一直沿着当前方向进行更新,那么每次更新的幅度就越来越大,如果一个梯度在一个方向上不断变化,那么其更新幅度就会被衰减,这样我们就可以使用一个较大的学习率,使得收敛更快,同时梯度比较大的方向就会因为动量的关系每次更新的幅度减少,如下图:
深度学习中的优化算法
python实现:

def sgd_momentum(parameters, vs, lr, gamma):
    for param, v in zip(parameters, vs):
        v[:] = gamma * v + lr * param.grad.data
        param.data = param.data - v

pytorch 内置了动量法的实现:

optimizer = torch.optim.SGD(net.parameters(), lr=1e-2, momentum=0.9) # 加动量

3.Adagrad 算法

Adagrad 算法,这个优化算法被称为自适应学习率优化算法,随机梯度下降以及动量法对所有的参数都使用的固定的学习率进行参数更新,但是不同的参数梯度可能不一样,所以需要不同的学习率才能比较好的进行训练,但是这个事情又不能很好地被人为操作,所以 Adagrad 便能够帮助我们做这件事。
Adagrad 的想法,在每次使用一个 batch size 的数据进行参数更新的时候,我们需要计算所有参数的梯度,那么其想法就是对于每个参数,初始化一个变量 s 为 0,然后每次将该参数的梯度平方求和累加到这个变量 s 上,然后在更新这个参数的时候,学习率就变为
深度学习中的优化算法
这里的 ϵ 是为了数值稳定性而加上的,因为有可能 s 的值为 0,那么 0 出现在分母就会出现无穷大的情况,通常 ϵ 取 10的-10次方 ,这样不同的参数由于梯度不同,他们对应的 s 大小也就不同,所以上面的公式得到的学习率也就不同,这也就实现了自适应的学习率。

Adagrad 的核心想法就是,如果一个参数的梯度一直都非常大,那么其对应的学习率就变小一点,防止震荡,而一个参数的梯度一直都非常小,那么这个参数的学习率就变大一点,使得其能够更快地更新
Adagrad 也有一些问题,因为 s 不断累加梯度的平方,所以会越来越大,导致学习率在后期会变得较小,导致收敛乏力的情况,可能无法收敛到表较好的结果。

实现 Adagrad 的算法:

def sgd_adagrad(parameters, sqrs, lr):
    eps = 1e-10
    for param, sqr in zip(parameters, sqrs):
        sqr[:] = sqr + param.grad.data ** 2
        div = lr / torch.sqrt(sqr + eps) * param.grad.data
        param.data = param.data - div

pytorch 内置的 adagrad 的优化算法:

optimizer = torch.optim.Adagrad(net.parameters(), lr=1e-2)

4.RMSProp 算法
RMSprop 是由 Geoff Hinton 在他 Coursera 课程中提出的一种适应性学习率方法,至今仍未被公开发表。前面我们提到了 Adagrad 算法有一个问题,就是学习率分母上的变量 s 不断被累加增大,最后会导致学习率除以一个比较大的数之后变得非常小,这不利于我们找到最后的最优解,所以 RMSProp 的提出就是为了解决这个问题。

RMSProp 仍然会使用梯度的平方量,不同于 Adagrad,其会使用一个指数加权移动平均来计算这个 s,也就是
深度学习中的优化算法
这里 g 表示当前求出的参数梯度,然后最终更新和 Adagrad 是一样的,学习率变成了:
深度学习中的优化算法
这里 α 是一个移动平均的系数,也是因为这个系数,导致了 RMSProp 和 Adagrad 不同的地方,这个系数使得 RMSProp 更新到后期累加的梯度平方较小,从而保证 s 不会太大,也就使得模型后期依然能够找到比较优的结果。

实现 RMSProp 的算法:

def rmsprop(parameters, sqrs, lr, alpha):
    eps = 1e-10
    for param, sqr in zip(parameters, sqrs):
        sqr[:] = alpha * sqr + (1 - alpha) * param.grad.data ** 2
        div = lr / torch.sqrt(sqr + eps) * param.grad.data
        param.data = param.data - div

pytorch 内置的 RMSProp 的优化算法:

optimizer = torch.optim.RMSprop(net.parameters(), lr=1e-3, alpha=0.9)

5.Adadelta 算法
Adadelta 算是 Adagrad 法的延伸,它跟 RMSProp 一样,都是为了解决 Adagrad 中学习率不断减小的问题,RMSProp 是通过移动加权平均的方式,而 Adadelta 也是一种方法,有趣的是,它并不需要学习率这个参数。

Adadelta 跟 RMSProp 一样,先使用移动平均来计算 s
深度学习中的优化算法
这里 ρ 和 RMSProp 中的 α 都是移动平均系数,g 是参数的梯度,然后我们会计算需要更新的参数的变化量
深度学习中的优化算法
Δθ 初始为 0 张量,每一步做如下的指数加权移动平均更新
深度学习中的优化算法
最后参数更新如下
深度学习中的优化算法

实现Adadelta算法

def adadelta(parameters, sqrs, deltas, rho):
    eps = 1e-6
    for param, sqr, delta in zip(parameters, sqrs, deltas):
        sqr[:] = rho * sqr + (1 - rho) * param.grad.data ** 2
        cur_delta = torch.sqrt(delta + eps) / torch.sqrt(sqr + eps) * param.grad.data
        delta[:] = rho * delta + (1 - rho) * cur_delta ** 2
        param.data = param.data - cur_delta

pytorch 内置的 RMSProp 的优化算法:

 optimizer = torch.optim.Adadelta(net.parameters(), rho=0.9)

6.Adam 算法

Adam 是一个结合了动量法和 RMSProp 的优化算法,其结合了两者的优点。
Adam 算法会使用一个动量变量 v 和一个 RMSProp 中的梯度元素平方的移动指数加权平均 s,首先将他们全部初始化为 0,然后在每次迭代中,计算他们的移动加权平均进行更新:
深度学习中的优化算法
在 adam 算法里,为了减轻 v 和 s 被初始化为 0 的初期对计算指数加权移动平均的影响,每次 v 和 s 都做下面的修正
深度学习中的优化算法
这里 t 是迭代次数,可以看到,当 0≤β1,β2≤1的时候,迭代到后期 t 比较大,那么 β1t 和 β2t 就几乎为 0,就不会对 v 和 s 有任何影响了,算法作者建议 β1=0.9, β2=0.999。

最后使用修正之后的 v̂ 和 ŝ 进行学习率的重新计算
深度学习中的优化算法
这里 η 是学习率, epsilon 仍然是为了数值稳定性而添加的常数,最后参数更新有
深度学习中的优化算法
实现 adam 算法

def adam(parameters, vs, sqrs, lr, t, beta1=0.9, beta2=0.999):
    eps = 1e-8
    for param, v, sqr in zip(parameters, vs, sqrs):
        v[:] = beta1 * v + (1 - beta1) * param.grad.data
        sqr[:] = beta2 * sqr + (1 - beta2) * param.grad.data ** 2
        v_hat = v / (1 - beta1 ** t)
        s_hat = sqr / (1 - beta2 ** t)
        param.data = param.data - lr * v_hat / torch.sqrt(s_hat + eps)

pytorch 内置的 adam 的优化算法:

optimizer = torch.optim.Adam(net.parameters(), lr=1e-3)

参考资料:

廖星宇大神的《深度学习入门之pytorch》