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


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://xiaobaiha.gitbook.io/tech-share/machinelearning/adam-you-hua-qi-xiang-jie.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
