adagrad-scratch.md 6.7 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


## Adagrad的实现

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

```{.python .input  n=1}
A
Aston Zhang 已提交
45
# Adagrad算法。
46 47 48 49 50 51 52 53 54 55 56
def adagrad(params, sqrs, lr, batch_size):
    eps_stable = 1e-7
    for param, sqr in zip(params, sqrs):
        g = param.grad / batch_size
        sqr[:] += nd.square(g)
        div = lr * g / nd.sqrt(sqr + eps_stable)
        param[:] -= div
```

## 实验

A
Aston Zhang 已提交
57
首先,导入实验所需的包。
58

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

A
Aston Zhang 已提交
73 74 75 76
实验中,我们以之前介绍过的线性回归为例。我们使用权重w为[2, -3.4],偏差b为4.2的线性回归模型来生成数据集。数据集的样本数为1000。目标函数为平方损失函数。

我们把梯度按元素平方的累加变量初始化为和参数形状相同的零张量。

A
Aston Zhang 已提交
77
```{.python .input  n=2}
78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99
# 生成数据集。
num_inputs = 2
num_examples = 1000
true_w = [2, -3.4]
true_b = 4.2
X = nd.random_normal(scale=1, shape=(num_examples, num_inputs))
y = true_w[0] * X[:, 0] + true_w[1] * X[:, 1] + true_b
y += .01 * nd.random_normal(scale=1, shape=y.shape)

# 初始化模型参数。
def init_params():
    w = nd.random_normal(scale=1, shape=(num_inputs, 1))
    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 已提交
100
优化函数`optimize`[“梯度下降和随机梯度下降”](gd-sgd-scratch.md)一节中的类似。需要指出的是,这里的初始学习率`lr`无需自我衰减。
101 102

```{.python .input  n=3}
A
Aston Zhang 已提交
103 104
net = utils.linreg
squared_loss = utils.squared_loss
105

A
Aston Zhang 已提交
106 107
def optimize(batch_size, lr, num_epochs, log_interval):
    [w, b], sqrs = init_params()
A
Aston Zhang 已提交
108
    y_vals = [squared_loss(net(X, w, b), y).mean().asnumpy()]
A
Aston Zhang 已提交
109 110
    print('batch size', batch_size)
    for epoch in range(1, num_epochs + 1):
A
Aston Zhang 已提交
111 112
        for batch_i, (features, label) in enumerate(
            utils.data_iter(batch_size, num_examples, X, y)):
113
            with autograd.record():
A
Aston Zhang 已提交
114 115
                output = net(features, w, b)
                loss = squared_loss(output, label)
116 117
            loss.backward()
            adagrad([w, b], sqrs, lr, batch_size)
A
Aston Zhang 已提交
118
            if batch_i * batch_size % log_interval == 0:
119
                y_vals.append(squared_loss(net(X, w, b), y).mean().asnumpy())
A
Aston Zhang 已提交
120 121
        print('epoch %d, learning rate %f, loss %.4e'
              % (epoch, lr, y_vals[-1]))
122
    # 为了便于打印,改变输出形状并转化成numpy数组。
A
Aston Zhang 已提交
123
    print('w:', w.reshape((1, -1)).asnumpy(), 'b:', b.asscalar(), '\n')
A
Aston Zhang 已提交
124
    x_vals = np.linspace(0, num_epochs, len(y_vals), endpoint=True)
A
Aston Zhang 已提交
125
    utils.semilogy(x_vals, y_vals, 'epoch', 'loss')
126 127
```

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

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

A
Aston Zhang 已提交
134
## 小结
135

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


## 练习

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

144

A
Aston Zhang 已提交
145 146 147 148 149
## 讨论

欢迎扫码直达[本节内容讨论区](https://discuss.gluon.ai/t/topic/2273)

![](../img/qr_adagrad-scratch.svg)
A
Aston Zhang 已提交
150 151 152 153 154


## 参考文献

[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.