# Adadelta

🏷 sec_adadelta

Adadelta 是 AdaGrad 的另一种变体( :numref: sec_adagrad ),
主要区别在于前者减少了学习率适应坐标的数量。
此外,广义上 Adadelta 被称为没有学习率,因为它使用变化量本身作为未来变化的校准。
Adadelta 算法是在 :cite: Zeiler.2012 中提出的。

# Adadelta 算法

简而言之,Adadelta 使用两个状态变量,st\mathbf{s}_t 用于存储梯度二阶导数的泄露平均值,Δxt\Delta\mathbf{x}_t 用于存储模型本身中参数变化二阶导数的泄露平均值。请注意,为了与其他出版物和实现的兼容性,我们使用作者的原始符号和命名(没有其它真正理由让大家使用不同的希腊变量来表示在动量法、AdaGrad、RMSProp 和 Adadelta 中用于相同用途的参数)。

以下是 Adadelta 的技术细节。鉴于参数 du jour 是ρ\rho,我们获得了与 :numref: sec_rmsprop 类似的以下泄漏更新:

st=ρst1+(1ρ)gt2.\begin{aligned} \mathbf{s}_t & = \rho \mathbf{s}_{t-1} + (1 - \rho) \mathbf{g}_t^2. \end{aligned}

与 :numref: sec_rmsprop 的区别在于,我们使用重新缩放的梯度gt\mathbf{g}_t' 执行更新,即

xt=xt1gt.\begin{aligned} \mathbf{x}_t & = \mathbf{x}_{t-1} - \mathbf{g}_t'. \\ \end{aligned}

那么,调整后的梯度gt\mathbf{g}_t' 是什么?我们可以按如下方式计算它:

\begin{aligned} \mathbf{g}_t' & = \frac{\sqrt{\Delta\mathbf{x}_{t-1} + \epsilon}}{\sqrt{\{\mathbf{s}_t + \epsilon}}} \odot \mathbf{g}_t, \\ \end{aligned}

其中Δxt1\Delta \mathbf{x}_{t-1} 是重新缩放梯度的平方gt\mathbf{g}_t' 的泄漏平均值。我们将Δx0\Delta \mathbf{x}_{0} 初始化为00,然后在每个步骤中使用gt\mathbf{g}_t' 更新它,即

Δxt=ρΔxt1+(1ρ)gt2,\begin{aligned} \Delta \mathbf{x}_t & = \rho \Delta\mathbf{x}_{t-1} + (1 - \rho) {\mathbf{g}_t'}^2, \end{aligned}

ϵ\epsilon(例如10510^{-5} 这样的小值)是为了保持数字稳定性而加入的。

# 代码实现

Adadelta 需要为每个变量维护两个状态变量,即st\mathbf{s}_tΔxt\Delta\mathbf{x}_t。这将产生以下实现。

%matplotlib inline
import torch
from d2l import torch as d2l
def init_adadelta_states(feature_dim):
    s_w, s_b = torch.zeros((feature_dim, 1)), torch.zeros(1)
    delta_w, delta_b = torch.zeros((feature_dim, 1)), torch.zeros(1)
    return ((s_w, delta_w), (s_b, delta_b))
def adadelta(params, states, hyperparams):
    rho, eps = hyperparams['rho'], 1e-5
    for p, (s, delta) in zip(params, states):
        with torch.no_grad():
            # In-placeupdatesvia[:]
            s[:] = rho * s + (1 - rho) * torch.square(p.grad)
            g = (torch.sqrt(delta + eps) / torch.sqrt(s + eps)) * p.grad
            p[:] -= g
            delta[:] = rho * delta + (1 - rho) * g * g
        p.grad.data.zero_()

对于每次参数更新,选择ρ=0.9\rho = 0.9 相当于 10 个半衰期。由此我们得到:

data_iter, feature_dim = d2l.get_data_ch11(batch_size=10)
d2l.train_ch11(adadelta, init_adadelta_states(feature_dim),
               {'rho': 0.9}, data_iter, feature_dim);
loss: 0.243, 0.019 sec/epoch

svg

为了简洁实现,我们只需使用高级 API 中的 Adadelta 算法。

trainer = torch.optim.Adadelta
d2l.train_concise_ch11(trainer, {'rho': 0.9}, data_iter)
loss: 0.243, 0.014 sec/epoch

svg

# 小结

  • Adadelta 没有学习率参数。相反,它使用参数本身的变化率来调整学习率。
  • Adadelta 需要两个状态变量来存储梯度的二阶导数和参数的变化。
  • Adadelta 使用泄漏的平均值来保持对适当统计数据的运行估计。

# 练习

  1. 调整ρ\rho 的值,会发生什么?
  2. 展示如何在不使用gt\mathbf{g}_t' 的情况下实现算法。为什么这是个好主意?
  3. Adadelta 真的是学习率为 0 吗?能找到 Adadelta 无法解决的优化问题吗?
  4. 将 Adadelta 的收敛行为与 AdaGrad 和 RMSProp 进行比较。

Discussions