# Adam优化器详解

最近在实验室搞的和某公司的校企合作面试中被问到有关Adam优化器的问题。笔者之前也总结过PyTorch中的几种常见优化器的接口和用法，但没有对原理进行详解。于是抽出时间对Adam优化器的细节进行了仔细的学习。

## Adam的发展

Adam于2014年12月Kingma和Lei Ba两位学者提出，结合了AdaGrad和RMSProp的优点，对梯度的均值和方差进行综合考虑，计算更新的步长。

### AdaGrad

我们都知道在一般的SGD算法中，参数每个维度都是依据相同的人为设定的梯度进行优化。

事实证明这种方法很多时候不能满足需要，这是由于不同维度上的梯度值并不一致。采取相同的学习率很多时候会造成某些梯度大的维度过早出现发散。

AdaGrad根据自变量在每个维度的梯度值的大小来调整各个维度上的学习率，从而避免统一的学习率难以适应所有维度的问题。

AdaGrad优化器维护一个变量s\_t, 并用这个变量来控制学习率的变化。如果用x表示参数，g表示梯度，则s\_t的更新公式如下：

```
s_t = s_(t-1) + g_t⊙g_t 
```

其中⊙表示逐个元素相乘，实际上就是得到了梯度的平方数值。很明显随着迭代次数的增加，s\_t不断增加。

参数的更新公式如下：

```
x_t = x_(t-1) + [η / √(s_t + ε)] ⊙ g_t
```

其中η表示超参数基准学习率，ε通常设置为一个常数值，是为了保证数值的稳定，以及防止分母出现负值。该更新公式会随着迭代次数的增加不断降低学习率，同时由于s\_t是一个长度为参数个输的向量，每个参数的学习率下降程度都是动态调整的。

通过pytorch进行一个简单的实现：

```python
import torch
import torch.nn as nn

model = nn.Parameter(torch.randn(10, 1))

# 特征数量为10，10个样本。
x = torch.randn(10, 10)
y = torch.randn(10)
eta = 0.01
s = torch.zeros(10, 1)
for epoch in range(100):
    f = y - x.mm(model)
    f.backward()
    g = model.weight.grad
    model.weight = model.weight - (eta / torch.sqrt(s + 1)) * g
    s = s + g * g
```

adagrad主要缺点在于当梯度较大的时候，s\_t会迅速变大，从而导致η迅速减小。这可能使得模型的训练停滞不前，寻找不到最佳的结果。

### RMSProp

RMSProp和AdaGrad十分类似，区别在于为了避免学习率过早地降得太低，采用了一个超参数γ来控制梯度数值对于s\_t的影响：

```
s_t = γ · s_(t-1) + （1 - γ) ` (g_t⊙g_t)
```

当γ值较大的时候，s\_t的变化也会较小，从而模型可以以比较均匀的速度优化。当γ值较小的时候，s\_t变化比较剧烈，从而学习率也会迅速减小，模型可以放慢优化速度以寻找最优解。

γ的引入使得使用者可以更加方便地控制学习率移动的幅度， 这种方法被称为指数加权移动平均（exponentially weighted moving average）。

### AdaDelta

另一种进行指数加权移动平均的算法是AdaDelta算法。在RMSProp的基础上，该算法不再需要提供η这个基础学习率超参数。而是对参数的实际变化值进行指数加权移动平均。如下所示：

```
x_t = x_(t-1) + [√(Δx + ε) / √(s_t + ε)] ⊙ g_t
```

Δx初始化为0，然后通过下式进行迭代更新：

```
x_t = ρ · x_(t-1) + (1 - ρ) g' ⊙ g'
```

其中g'是经过学习率调整后的实际参数变化值。ρ和γ通常取相同的值。

## Adam算法具体思路和实现

Adam(Adaptive moment estimation)算法是综合了RMSProp中的指数加权平均和SGD中的动量法思路。Adam采用两个不同的超参数β1和β2来控制动量以及RMSP中s\_t的更新。

```
v_t = β1 · v_(t-1) + (1 - β1) · g_t
s_t = β2 · s_(t-1) + (1 - β2) · (g_t⊙g_t)
```

此外通过计算设计者发现在t时刻，各时刻动量的权值之和为1 - β1^t。为了保证在t较小时该值也能接近1，Adam算法会进行一次梯度修正。即：

```
v_t = v_t / (1 - β1^t)
s_t = s_t / (1 - β2^t)
```

每一轮实际梯度为

```
g_t' = η · v_t / (√s_t + ε)
```

Adam综合了动量法和指数加权平均的办法。从而也就综合了两者的优点。前者是参考了物理学中动量的概念，使得过去的梯度下降结果对本次梯度产生一定的影响，后者则是一定程度上避免了参数更新的发散。
