adagrad-scratch.md 6.4 KB
Newer Older
A
Aston Zhang 已提交
1
# Adagrad——从零开始
2 3


A
Aston Zhang 已提交
4
在我们之前介绍过的优化算法中,无论是梯度下降、随机梯度下降、小批量随机梯度下降还是使用动量法,目标函数自变量的每一个元素在相同时刻都使用同一个学习率来自我迭代。
5

A
Aston Zhang 已提交
6
举个例子,假设目标函数为$f$,自变量为一个多维向量$[x_1, x_2]^\top$,该向量中每一个元素在更新时都使用相同的学习率。例如在学习率为$\eta$的梯度下降中,元素$x_1$和$x_2$都使用相同的学习率$\eta$来自我迭代:
7 8

$$
A
Aston Zhang 已提交
9 10
x_1 \leftarrow x_1 - \eta \frac{\partial{f}}{\partial{x_1}}, \\
x_2 \leftarrow x_2 - \eta \frac{\partial{f}}{\partial{x_2}}.
11 12
$$

A
Aston Zhang 已提交
13
如果让$x_1$和$x_2$使用不同的学习率自我迭代呢?实际上,Adagrad就是一个在迭代过程中不断自我调整学习率,并让模型参数中每个元素都使用不同学习率的优化算法 [1]。
14

A
Aston Zhang 已提交
15
下面,我们将介绍Adagrad算法。关于本节中涉及到的按元素运算,例如标量与向量计算以及按元素相乘$\odot$,请参见[“数学基础”](../chapter_appendix/math.md)一节。
16 17 18 19


## Adagrad算法

A
Aston Zhang 已提交
20
Adagrad的算法会使用一个小批量随机梯度按元素平方的累加变量$\boldsymbol{s}$,并将其中每个元素初始化为0。在每次迭代中,首先计算小批量随机梯度$\boldsymbol{g}$,然后将该梯度按元素平方后累加到变量$\boldsymbol{s}$:
21

A
Aston Zhang 已提交
22
$$\boldsymbol{s} \leftarrow \boldsymbol{s} + \boldsymbol{g} \odot \boldsymbol{g}. $$
23

A
Aston Zhang 已提交
24
然后,我们将目标函数自变量中每个元素的学习率通过按元素运算重新调整一下:
25

A
Aston Zhang 已提交
26
$$\boldsymbol{g}^\prime \leftarrow \frac{\eta}{\sqrt{\boldsymbol{s} + \epsilon}} \odot \boldsymbol{g},$$
27

A
Aston Zhang 已提交
28
其中$\eta$是初始学习率且$\eta > 0$,$\epsilon$是为了维持数值稳定性而添加的常数,例如$10^{-7}$。我们需要注意其中按元素开方、除法和乘法的运算。这些按元素运算使得目标函数自变量中每个元素都分别拥有自己的学习率。
29

A
Aston Zhang 已提交
30
最后,自变量的迭代步骤与小批量随机梯度下降类似。只是这里梯度前的学习率已经被调整过了:
31

A
Aston Zhang 已提交
32
$$\boldsymbol{x} \leftarrow \boldsymbol{x} - \boldsymbol{g}^\prime.$$
33 34


A
Aston Zhang 已提交
35
## Adagrad的特点
36

A
Aston Zhang 已提交
37
需要强调的是,小批量随机梯度按元素平方的累加变量$\boldsymbol{s}$出现在含调整后学习率的梯度$\boldsymbol{g}^\prime$的分母项。因此,如果目标函数有关自变量中某个元素的偏导数一直都较大,那么就让该元素的学习率下降快一点;反之,如果目标函数有关自变量中某个元素的偏导数一直都较小,那么就让该元素的学习率下降慢一点。然而,由于$\boldsymbol{s}$一直在累加按元素平方的梯度,自变量中每个元素的学习率在迭代过程中一直在降低(或不变)。所以,当学习率在迭代早期降得较快且当前解依然不佳时,Adagrad在迭代后期由于学习率过小,可能较难找到一个有用的解。
38 39 40 41 42 43 44 45 46 47 48


## Adagrad的实现

Adagrad的实现很简单。我们只需要把上面的数学公式翻译成代码。

```{.python .input  n=1}
def adagrad(params, sqrs, lr, batch_size):
    eps_stable = 1e-7
    for param, sqr in zip(params, sqrs):
        g = param.grad / batch_size
A
Aston Zhang 已提交
49 50
        sqr[:] += g.square()
        param[:] -= lr * g / (sqr + eps_stable).sqrt()
51 52 53 54
```

## 实验

A
Aston Zhang 已提交
55
首先,导入本节中实验所需的包。
56

A
Aston Zhang 已提交
57 58 59
```{.python .input}
%config InlineBackend.figure_format = 'retina'
%matplotlib inline
A
imports  
Aston Zhang 已提交
60
import mxnet as mx
A
Aston Zhang 已提交
61
from mxnet import autograd, nd
A
imports  
Aston Zhang 已提交
62
import numpy as np
A
Aston Zhang 已提交
63
import sys
A
Aston Zhang 已提交
64 65 66
sys.path.append('..')
import utils
```
67

A
rmsprop  
Aston Zhang 已提交
68
实验中,我们以之前介绍过的线性回归为例。设数据集的样本数为1000,我们使用权重`w`为[2, -3.4],偏差`b`为4.2的线性回归模型来生成数据集。该模型的平方损失函数即所需优化的目标函数,模型参数即目标函数自变量。
A
Aston Zhang 已提交
69

A
rmsprop  
Aston Zhang 已提交
70
我们把梯度按元素平方的累加变量初始化为和模型参数形状相同的零张量。
A
Aston Zhang 已提交
71

A
Aston Zhang 已提交
72
```{.python .input  n=2}
73 74 75 76 77
# 生成数据集。
num_inputs = 2
num_examples = 1000
true_w = [2, -3.4]
true_b = 4.2
A
Aston Zhang 已提交
78 79 80
features = nd.random.normal(scale=1, shape=(num_examples, num_inputs))
labels = true_w[0] * features[:, 0] + true_w[1] * features[:, 1] + true_b
labels += nd.random.normal(scale=0.01, shape=labels.shape)
81 82 83

# 初始化模型参数。
def init_params():
A
Aston Zhang 已提交
84
    w = nd.random.normal(scale=1, shape=(num_inputs, 1))
85 86 87 88 89 90 91 92 93 94
    b = nd.zeros(shape=(1,))
    params = [w, b]
    sqrs = []
    for param in params:
        param.attach_grad()
        # 把梯度按元素平方的累加变量初始化为和参数形状相同的零张量。
        sqrs.append(param.zeros_like())
    return params, sqrs
```

A
Aston Zhang 已提交
95
优化函数`optimize`[“梯度下降和随机梯度下降”](gd-sgd-scratch.md)一节中的类似。需要指出的是,这里的初始学习率`lr`无需自我衰减。
96 97

```{.python .input  n=3}
A
Aston Zhang 已提交
98
net = utils.linreg
A
Aston Zhang 已提交
99
loss = utils.squared_loss
100

A
Aston Zhang 已提交
101 102
def optimize(batch_size, lr, num_epochs, log_interval):
    [w, b], sqrs = init_params()
A
Aston Zhang 已提交
103
    ls = [loss(net(features, w, b), labels).mean().asnumpy()]
A
Aston Zhang 已提交
104
    for epoch in range(1, num_epochs + 1):
A
Aston Zhang 已提交
105 106
        for batch_i, (X, y) in enumerate(
            utils.data_iter(batch_size, num_examples, features, labels)):
107
            with autograd.record():
A
Aston Zhang 已提交
108 109
                l = loss(net(X, w, b), y)
            l.backward()
110
            adagrad([w, b], sqrs, lr, batch_size)
A
Aston Zhang 已提交
111
            if batch_i * batch_size % log_interval == 0:
A
Aston Zhang 已提交
112
                ls.append(loss(net(features, w, b), labels).mean().asnumpy())
A
Aston Zhang 已提交
113
    print('w:', w, '\nb:', b, '\n')
A
Aston Zhang 已提交
114 115
    es = np.linspace(0, num_epochs, len(ls), endpoint=True)
    utils.semilogy(es, ls, 'epoch', 'loss')
116 117
```

A
Aston Zhang 已提交
118
最终,优化所得的模型参数值与它们的真实值较接近。
119 120

```{.python .input  n=4}
A
Aston Zhang 已提交
121
optimize(batch_size=10, lr=0.9, num_epochs=3, log_interval=10)
122 123
```

A
Aston Zhang 已提交
124
## 小结
125

A
Aston Zhang 已提交
126 127
* Adagrad在迭代过程中不断调整学习率,并让目标函数自变量中每个元素都分别拥有自己的学习率。
* 使用Adagrad时,自变量中每个元素的学习率在迭代过程中一直在降低(或不变)。
128 129 130 131


## 练习

A
Aston Zhang 已提交
132 133
* 在介绍Adagrad的特点时,我们提到了它可能存在的问题。你能想到什么办法来应对这个问题?

134

A
Aston Zhang 已提交
135
## 扫码直达[讨论区](https://discuss.gluon.ai/t/topic/2273)
A
Aston Zhang 已提交
136 137

![](../img/qr_adagrad-scratch.svg)
A
Aston Zhang 已提交
138 139 140 141 142


## 参考文献

[1] Duchi, J., Hazan, E., & Singer, Y. (2011). Adaptive Subgradient Methods for Online Learning and Stochastic Optimization. Journal of Machine Learning Research, 12(Jul), 2121-2159.