Mxnet (29): AdaGrad算法

元气小坏坏 提交于 2020-10-03 10:51:46

1. 稀疏特征和学习率

在模型训练中会有稀疏特征(很少发生的特征),在自然语言模型中常见,比如,与 “学习”相比“预处理”更加少见。同时在其他领域也会用到,例如计算广告和个性化的协同过滤。因为只有少数人感兴趣的事情很多。长尾经济很受用。

在学习率下降的情况下,我们可能最终会遇到以下情况:常见特征的参数收敛到其最佳值相当快,而对于罕见特征,在确定最佳值之前,我们仍然缺乏足够频繁地观察它们的情况。换句话说,对于频繁使用的功能,学习率降低得太慢,对于不频繁使用的功能,学习率降低得太快。

解决此问题的一种可行方法是计算我们看到特定功能的次数,并将其用作调整学习率调度器。

2.AdaGrad算法

AdaGrad算法会使用一个小批量随机梯度 g t g_t gt 通过 s t s_t st累计过去梯度的方差:

g t = ∂ w l ( y t , f ( x t , w ) ) , s t = s t − 1 + g t 2 , w t = w t − 1 − η s t + ϵ ⋅ g t . \begin{aligned} \mathbf{g}_t & = \partial_{\mathbf{w}} l(y_t, f(\mathbf{x}_t, \mathbf{w})), \\ \mathbf{s}_t & = \mathbf{s}_{t-1} + \mathbf{g}_t^2, \\ \mathbf{w}_t & = \mathbf{w}_{t-1} - \frac{\eta}{\sqrt{\mathbf{s}_t + \epsilon}} \cdot \mathbf{g}_t. \end{aligned} gtstwt=wl(yt,f(xt,w)),=st1+gt2,=wt1st+ϵ ηgt.

η \eta η 是学习率 以及 ϵ \epsilon ϵ 是一个常数用来确保我们不会除以 0 0 0。 初始化 s 0 = 0 \mathbf{s}_0 = \mathbf{0} s0=0

像动量一样,需要用于跟踪的辅助变量,不过在这里,我们需要考虑每个坐标一个单独的学习率。

s t \mathbf{s}_t st中积累梯度的平方意味着 s t \mathbf{s}_t st 基本以现行速率增长 (实际上比线性慢一些,因为梯度最初会减小). 累加变量 s t \mathbf{s}_t st 出现在学习率的分母项中。因此,如果目标函数有关自变量中某个元素的偏导数一直都较大,那么该元素的学习率将下降较快;反之,如果目标函数有关自变量中某个元素的偏导数一直都较小,那么该元素的学习率将下降较慢。然而,由于 s t \mathbf{s}_t st 一直在累加按元素平方的梯度,自变量中每个元素的学习率在迭代过程中一直在降低(或不变)。所以,当学习率在迭代早期降得较快且当前解依然不佳时,AdaGrad算法在迭代后期由于学习率过小,可能较难找到一个有用的解。我们使用与以前相同的函数做实验:

f ( x ) = 0.1 x 1 2 + 2 x 2 2 . f(\mathbf{x}) = 0.1 x_1^2 + 2 x_2^2. f(x)=0.1x12+2x22.

我们将使用以前相同的学习率测试 Adagrad η = 0.4 \eta = 0.4 η=0.4. 可见, 自变量的迭代轨迹更加平滑。由于积累 s t \boldsymbol{s}_t st, 学习率会持续下降, 因此自变量在迭代的后期不会移动太多。

from d2l import mxnet as d2l
from mxnet import np, npx, autograd, init, gluon
from mxnet.gluon import nn
import plotly.graph_objs as go
import plotly.express as px
import pandas as pd
import math
npx.set_np()


def train_2d(trainer, steps=20):  
    """优化二维目标"""
    # s1和s2是内部状态变量
    x1, x2, s1, s2 = -5, -2, 0, 0
    results = [[x1], [x2]]
    for i in range(steps):
        x1, x2, s1, s2 = trainer(x1, x2, s1, s2)
        results[0].append(x1)
        results[1].append(x2)
    return results

def show_trace_2d(f, results): 
    """2D可视化"""
    fig = go.Figure()
    y_max =max(results[1]) +1  if 1 < max(results[1]) < 6 else 2
    x1, x2 = np.meshgrid(np.arange(-5.5, 1.0, 0.1),  np.arange(-3.5, y_max, 0.1))
    fig.add_trace(go.Contour(x=x1[0].tolist(), y=x2.T[0].tolist(), z=f(x1, x2).tolist(),showscale=False , colorscale='YlGnBu'))
    fig.add_trace(go.Scatter(x=results[0], y=results[1] , mode='lines+markers', marker={
   
   'size':8}))
    fig.update_layout(width=500, height = 380, xaxis_title='x1', yaxis_title='x2')
    fig.show()
    
    
def adagrad_2d(x1, x2, s1, s2):
    eps = 1e-6
    g1, g2 = 0.2 * x1, 4 * x2
    s1 += g1 ** 2
    s2 += g2 ** 2
    x1 -= eta / math.sqrt(s1 + eps) * g1
    x2 -= eta / math.sqrt(s2 + eps) * g2
    return x1, x2, s1, s2

def f_2d(x1, x2):
    return 0.1 * x1 ** 2 + 2 * x2 ** 2

eta = 0.4
show_trace_2d(f_2d, train_2d(adagrad_2d))

在这里插入图片描述

随着我们将学习率提高到 2 我们看到了更好的行为。这已经表明,即使在无噪声的情况下,学习率的降低也可能是相当大的,因此我们需要确保参数正确收敛。

eta = 2
show_trace_2d(f_2d, train_2d(adagrad_2d))

在这里插入图片描述

3. 实现AdaGrad算法

就像动量法一样,Adagrad需要保持与参数形状相同的状态变量。

def init_adagrad_states(feature_dim):
    s_w = np.zeros((feature_dim, 1))
    s_b = np.zeros(1)
    return (s_w, s_b)

def adagrad(params, states, hyperparams):
    eps = 1e-6
    for p, s in zip(params, states):
        s[:] += np.square(p.grad)
        p[:] -= hyperparams['lr'] * p.grad / np.sqrt(s + eps)  # 通过s积累影响学习率作用

我们使用更高的学习率来训练模型。

def get_data(batch_size=10, n=1500):
    data = np.genfromtxt(d2l.download('airfoil'),  dtype=np.float32, delimiter='\t')
    data = (data - data.mean(axis=0)) / data.std(axis=0)
    data_iter = d2l.load_array((data[:n, :-1], data[:n, -1]), batch_size, is_train=True)
    return data_iter, data.shape[1]-1

def train(trainer_fn, states, hyperparams, data_iter, feature_dim, num_epochs=2):
    # 初始化
    w = np.random.normal(scale=0.01, size=(feature_dim, 1))
    b = np.zeros(1)
    w.attach_grad()
    b.attach_grad()
    net, loss = lambda X: d2l.linreg(X, w, b), d2l.squared_loss
    # 训练
    n, timer = 0, d2l.Timer()
    epoch_lst, loss_lst  = [], []
    for _ in range(num_epochs):
        for X, y in data_iter:
            with autograd.record():
                l = loss(net(X), y).mean()
            l.backward()
            trainer_fn([w, b], states, hyperparams)
            n += X.shape[0]
            if n % 200 == 0:
                timer.stop()
                epoch_lst.append(n/X.shape[0]/len(data_iter))
                loss_lst.append(d2l.evaluate_loss(net, data_iter, loss))
                timer.start()
    print(f'loss: {loss_lst[-1]:.3f}, {timer.avg():.3f} sec/epoch')
    fig = px.line(pd.DataFrame([loss_lst], columns=epoch_lst, index=['loss']).T, width=600, height=380)
    fig.update_layout(xaxis_range=[0,num_epochs], yaxis_range=[0.22, 0.35], xaxis_title='epoch', yaxis_title='loss')
    fig.show()
    
    return timer.cumsum(), loss_lst

batch_size选做10,学习率为0.1。

data_iter, feature_dim = get_data(batch_size=10)
train(adagrad, init_adagrad_states(feature_dim), {
   
   'lr': 0.1}, data_iter, feature_dim);

# loss: 0.244, 0.041 sec/epoch

在这里插入图片描述

4.简化实现

调用Gluon中调用Adagrad算法api, 使用Trainer算法实例adagrad。

def train_concise(tr_name, hyperparams, data_iter, num_epochs=2):
    # 初始化
    net = nn.Sequential()
    net.add(nn.Dense(1))
    net.initialize(init.Normal(sigma=0.01))
    trainer = gluon.Trainer(net.collect_params(), tr_name, hyperparams)
    loss = gluon.loss.L2Loss()
    n, timer = 0, d2l.Timer()
    epoch_lst, loss_lst  = [], []
    for _ in range(num_epochs):
        for X, y in data_iter:
            with autograd.record():
                l = loss(net(X), y)
            l.backward()
            trainer.step(X.shape[0])
            n += X.shape[0]
            if n % 200 == 0:
                timer.stop()
                epoch_lst.append(n/X.shape[0]/len(data_iter))
                loss_lst.append(d2l.evaluate_loss(net, data_iter, loss))
                timer.start()
    print(f'loss: {loss_lst[-1]:.3f}, {timer.avg():.3f} sec/epoch')
    fig = px.line(pd.DataFrame([loss_lst], columns=epoch_lst, index=['loss']).T, width=600, height=380)
    fig.update_layout(xaxis_range=[0,num_epochs], yaxis_range=[0.22, 0.35], xaxis_title='epoch', yaxis_title='loss')
    fig.show()
    
train_concise('adagrad', {
   
   'learning_rate': 0.1}, data_iter)

在这里插入图片描述

5. 参考

https://d2l.ai/chapter_optimization/adagrad.html

6. 代码

github

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!