AdaGrad 和 RMSProp 优化器原理

我们在使用梯度下降法时应该发现了不同参数分量在更新时使用相同的学习率。注意:不同分量使用的梯度可能是不同的。例如:我们现在有参数向量\((x_1, x_2, x_3)\),它有 3 个分量,使用梯度下降法更新参数时都使用相同的学习率 lr。

AdaGrad 优化方法的核心思想是,对于不同的分量使用不同的学习率,即: 更新参数分量 \(x_1\) 时使用 \(lr_1\) 学习率,更新参数分量 \(x_2\) 时使用 \(lr_2\) 学习率,它们的学习率可以是不同的。

Momentum 是从梯度的角度进行优化,而 AdaGrad 则是从学习率的角度进行优化。

1. AdaGrad 方法

AdaGrad 是如何计算出不同分量的梯度?

其计算步骤如下:

  1. 初始化学习率 α、初始化参数 θ、小常数 σ = 1e-6
  2. 初始化梯度累积变量 s = 0
  3. 从训练集中采样 m 个样本的小批量,计算梯度 g
  4. 累积平方梯度 s = s + g ⊙ g,⊙ 表示各个分量相乘
  5. 学习率 α 的计算公式如下:

我们接下来举个计算例子:

  1. 参数初值为 [1];
  2. 梯度值为:[2];
  3. 学习率为 0.1;
  4. 小常数为:1e-10

我们可以通过 PyTorch 来看看每一次迭代的累计平方梯度值:

import torch
import torch.optim as optim
import numpy as np


if __name__ == '__main__':

    # 构造初始参数
    param = torch.tensor([1], dtype=torch.float32)
    # 设置梯度值
    param.grad = torch.tensor([2], dtype=torch.float32)
    # 使用 RMSProp 优化器
    optimizer = optim.Adagrad([param], lr=0.1, eps=1e-10)

    # 第一次累计梯度平方
    optimizer.step()
    print(optimizer.state)

    # 第二次累计梯度平方
    optimizer.step()
    print(optimizer.state)

    # 第三次累计梯度平方
    optimizer.step()
    print(optimizer.state)

程序输出结果:

defaultdict(<class 'dict'>, {tensor([0.9000]): {'step': 1, 'sum': tensor([4.])}})
defaultdict(<class 'dict'>, {tensor([0.8293]): {'step': 2, 'sum': tensor([8.])}})
defaultdict(<class 'dict'>, {tensor([0.7716]): {'step': 3, 'sum': tensor([12.])}})

我们发现,其 s 值的累计确实是公式的计算过程。第一次时, s = 2 ** 2 = 4,第二次时 ,s = 4 + 2 ** 2 = 8,第三次时, s = 8 + 2 ** 2 = 12。

对应的学习率可以通过上面的公式计算得出:

学习率获得之后,就可以使用梯度下降公式计算得出更新后的参数,如下图计算:

和我们的程序计算结果是一样的,其余的 step 跟我们的计算过程一样,先计算学习率,再应用梯度下降公式计算即可。

2. RMSProp 方法

AdaGrad 的缺点?

我们在计算过程中,应该能够发现,随着 step 的增加,累计的平方梯度值将会变得越来越大,即分母会过快的变得很大。当模型学习到后期时,可能会使得学习率过早、过量的变小。我们知道当学习率很小时,参数基本也更新不动了,相当于模型虽然在学习中,但是无法学习到新的东西了。

那么,问题的原因其实就是累计平方梯度的方法太过于暴力了。所以,RMSProp 方法对于这一点进行了改进。使用移动加权平均来代替平方的方法。我们知道移动加权平均会使得累计的梯度更加平滑。

注意:RMSProp 其实就是对 AdaGrad 的改进版本,而且仅仅是对累计梯度的方法进行了改进。

上述公式中的圈点表示对应分量的平方。我们仍然使用上面的初始参数来举个计算例子:

  1. 参数初值为 [1];
  2. 梯度值为:[2];
  3. 学习率为 0.1;
  4. 小常数为:1e-8
  5. β平滑系数值为:0.9

第一次累计的梯度值为:(1-0.9) * 2 ** 2 = 0.4
第二次累计的梯度值为:0.9 * 0.4 + (1-0.9) * 2**2 = 0.36 + 0.4 = 0.76
第三次累计的梯度值为:0.9 * 0.76 + (1-0.9) * 2 ** 2 = 1.084

那么第一次,得到的学习率为:0.1 / (np.sqrt(0.4) + 1e-8) = 0.15811,更新后的参数为:1 – 0.15811 * 2 = 0.68378。

示例程序如下:

import torch
import torch.optim as optim
import numpy as np


if __name__ == '__main__':

    # 构造初始参数
    param = torch.tensor([1], dtype=torch.float32)
    # 设置梯度值
    param.grad = torch.tensor([2], dtype=torch.float32)
    # 使用 RMSProp 优化器,alpha 对应的就是 β 值
    optimizer = optim.RMSprop([param], lr=0.1, eps=1e-8, alpha=0.9)

    # 第一次累计梯度平方
    optimizer.step()
    print(optimizer.state)

    # 第二次累计梯度平方
    optimizer.step()
    print(optimizer.state)

    # 第三次累计梯度平方
    optimizer.step()
    print(optimizer.state)

程序运行结果:

defaultdict(<class 'dict'>, {tensor([0.6838]): {'step': 1, 'square_avg': tensor([0.4000])}})
defaultdict(<class 'dict'>, {tensor([0.4544]): {'step': 2, 'square_avg': tensor([0.7600])}})
defaultdict(<class 'dict'>, {tensor([0.2623]): {'step': 3, 'square_avg': tensor([1.0840])}})

RMSProp 通过引入衰减系数 β,控制历史梯度对历史梯度信息获取的多少. 被证明在神经网络非凸条件下的优化更好,学习率衰减更加合理一些。

需要注意的是:AdaGrad 和 RMSProp 都是对于不同的参数分量使用不同的学习率,如果某个参数分量的梯度值较大,则对应的学习率就会较小,如果某个参数分量的梯度较小,则对应的学习率就会较大一些。

无论是 AdaGrad 还是 RMSProp,其学习率计算过程中都依赖于历史累计的梯度值。所以,在经过了许多 step 之后,也会累计一些梯度值,那么学习率就会整体呈现越来越小的趋势变化,这也符合我们的基本基本期望,开始时我们距离最优目标仍较远,可以使用较大的学习率,加快训练速度,随着迭代次数的增加,学习率逐渐下降。

未经允许不得转载:一亩三分地 » AdaGrad 和 RMSProp 优化器原理