- 人工智能
- 深度学习
- 机器学习
- 教程
【免费下载链接】d2l-zh
《动手学深度学习》:面向中文读者、能运行、可讨论。中英文版被70多个国家的500多所大学用于教学。
Adadelta 是 AdaGrad 家族中一个非常特别的变体:它通过两个泄漏平均状态变量,把"每坐标自适应的步长"推进到了极致——甚至不再需要显式的学习率参数。本文将基于 d2l-zh 仓库chapter_optimization/adadelta_origin.md的完整推导与三种深度学习框架(MXNet、PyTorch、TensorFlow)的手写实现,结合 d2l/torch.py 等源码中的训练辅助函数,讲清 Adadelta 的数学原理、状态变量设计与收敛行为,读完即可在真实数据集上复现并替换默认优化器。
从 AdaGrad 到 Adadelta:为什么需要"无学习率"的算法
在《动手学深度学习》优化章节中,AdaGrad(见 chapter_optimization/adagrad_origin.md)率先实现了逐坐标自适应的学习率,但它有一个致命弱点:状态向量 $\mathbf{s}_t$ 以 $\mathbf{s}t = \mathbf{s}{t-1} + \mathbf{g}_t^2$ 的方式无界累加,导致有效学习率按 $\mathcal{O}(t^{-1/2})$ 的预定节奏持续衰减。对凸问题这尚可接受,对深度学习中常见的非凸问题则可能过早收敛停滞。
RMSProp(见 chapter_optimization/rmsprop_origin.md)给出的修复方案是把无界累加替换为泄漏平均:
$$\mathbf{s}t \leftarrow \gamma \mathbf{s}{t-1} + (1-\gamma) \mathbf{g}_t^2,$$
再配合一个独立调度的学习率 $\eta$ 完成更新。Adadelta(论文出自 Zeiler,2012,见仓库 d2l.bib 引用条目)则更近一步:它把学习率本身也去掉,直接用"参数过去变化的幅度"来校准未来的步长。正如原文档所述,Adadelta 的步长来源于自身的更新量——这是它与 AdaGrad、RMSProp 最本质的分野。
Adadelta 算法原理:两个泄漏平均状态变量
Adadelta 为每个参数维护两个状态变量:
- $\mathbf{s}_t$:梯度二阶矩的泄漏平均(与 RMSProp 中 $\mathbf{s}_t$ 的作用一致);
- $\Delta\mathbf{x}_t$:模型参数变化量$\mathbf{g}_t'$ 的二阶矩的泄漏平均,这是 Adadelta 独有的变量。
原文档特意说明:这里沿用作者论文中的原始记号($\rho$、$\mathbf{s}_t$、$\Delta\mathbf{x}_t$),以便与各类出版物和实现保持一致——在动量、AdaGrad、RMSProp、Adadelta 中扮演同类角色的变量使用不同希腊字母并没有本质必要。
第一步:泄漏平均梯度二阶矩
给定衰减系数 $\rho$,梯度二阶矩的更新与 RMSProp 完全一致:
$$\mathbf{s}t = \rho , \mathbf{s}{t-1} + (1 - \rho) , \mathbf{g}_t^2.$$
第二步:重缩放梯度与参数更新
与 RMSProp 的区别在于:参数更新不再使用 $\eta / \sqrt{\mathbf{s}_t + \epsilon} \odot \mathbf{g}_t$,而是使用"重缩放梯度" $\mathbf{g}_t'$:
$$\mathbf{x}t = \mathbf{x}{t-1} - \mathbf{g}_t',$$
其中重缩放梯度的定义为:
$$\mathbf{g}t' = \frac{\sqrt{\Delta\mathbf{x}{t-1} + \epsilon}}{\sqrt{\mathbf{s}_t + \epsilon}} \odot \mathbf{g}_t.$$
这里分子上的 $\sqrt{\Delta\mathbf{x}{t-1} + \epsilon}$ 扮演了"自适应学习率"的角色:过去参数更新幅度越大($\Delta\mathbf{x}{t-1}$ 越大),未来的步长就越大;而分母 $\sqrt{\mathbf{s}_t + \epsilon}$ 继续执行 RMSProp 式的梯度幅度归一化。
第三步:更新变化量二阶矩
$\Delta\mathbf{x}_{t-1}$ 是重缩放梯度 $\mathbf{g}_t'$ 平方的泄漏平均,初始化为 $\Delta\mathbf{x}_0 = 0$,每一步递推为:
$$\Delta\mathbf{x}t = \rho , \Delta\mathbf{x}{t-1} + (1 - \rho) , {\mathbf{g}_t'}^2.$$
数值稳定性常数 $\epsilon$
原文档在代码实现中固定取 $\epsilon = 10^{-5}$,用于防止除零以及避免步长过大(RMSProp 中通常取 $10^{-6}$,Adadelta 略大一些)。当 $\Delta\mathbf{x}_{t-1}$ 和 $\mathbf{s}_t$ 都较小时,$\epsilon$ 直接影响初始步长,因此不宜省略。
从零手写实现:三种框架的状态管理与原地更新
原文档给出了 MXNet、PyTorch、TensorFlow 三套完整实现。每套实现包含两个函数:init_adadelta_states负责初始化状态,adadelta负责更新参数与状态。
MXNet 实现
%matplotlib inline from d2l import mxnet as d2l from mxnet import np, npx npx.set_np() def init_adadelta_states(feature_dim): s_w, s_b = d2l.zeros((feature_dim, 1)), d2l.zeros(1) delta_w, delta_b = d2l.zeros((feature_dim, 1)), d2l.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): # In-place updates via [:] s[:] = rho * s + (1 - rho) * np.square(p.grad) g = (np.sqrt(delta + eps) / np.sqrt(s + eps)) * p.grad p[:] -= g delta[:] = rho * delta + (1 - rho) * g * gPyTorch 实现
%matplotlib inline from d2l import torch as d2l import torch def init_adadelta_states(feature_dim): s_w, s_b = d2l.zeros((feature_dim, 1)), d2l.zeros(1) delta_w, delta_b = d2l.zeros((feature_dim, 1)), d2l.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-place updates via [:] 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_()TensorFlow 实现
%matplotlib inline from d2l import tensorflow as d2l import tensorflow as tf def init_adadelta_states(feature_dim): s_w = tf.Variable(d2l.zeros((feature_dim, 1))) s_b = tf.Variable(d2l.zeros(1)) delta_w = tf.Variable(d2l.zeros((feature_dim, 1))) delta_b = tf.Variable(d2l.zeros(1)) return ((s_w, delta_w), (s_b, delta_b)) def adadelta(params, grads, states, hyperparams): rho, eps = hyperparams['rho'], 1e-5 for p, (s, delta), grad in zip(params, states, grads): s[:].assign(rho * s + (1 - rho) * tf.math.square(grad)) g = (tf.math.sqrt(delta + eps) / tf.math.sqrt(s + eps)) * grad p[:].assign(p - g) delta[:].assign(rho * delta + (1 - rho) * g * g)实现要点解读
| 代码片段 | 数学对应 | 说明 |
|---|---|---|
s[:] = rho * s + (1 - rho) * square(grad) | $\mathbf{s}t = \rho\mathbf{s}{t-1} + (1-\rho)\mathbf{g}_t^2$ | 梯度二阶矩的泄漏平均 |
g = sqrt(delta + eps) / sqrt(s + eps) * grad | $\mathbf{g}t' = \frac{\sqrt{\Delta\mathbf{x}{t-1}+\epsilon}}{\sqrt{\mathbf{s}_t+\epsilon}} \odot \mathbf{g}_t$ | 重缩放梯度,替代学习率 |
p[:] -= g | $\mathbf{x}t = \mathbf{x}{t-1} - \mathbf{g}_t'$ | 参数更新,注意没有lr项 |
delta[:] = rho * delta + (1 - rho) * g * g | $\Delta\mathbf{x}t = \rho\Delta\mathbf{x}{t-1} + (1-\rho){\mathbf{g}_t'}^2$ | 变化量二阶矩回填,供下一步使用 |
三个框架版本的关键区别在于梯度获取方式与原地更新语法:MXNet 通过p.grad直接取梯度;PyTorch 必须在torch.no_grad()上下文中原地修改参数,并在更新后手动p.grad.data.zero_()清零梯度;TensorFlow 则通过tf.Variable承载状态,用.assign完成原地赋值。这些细节与各框架的自动微分语义严格对应,从仓库其他章节(如 chapter_optimization/rmsprop_origin.md 中的rmsprop实现)可以看到完全一致的编码模式。
收敛行为与 $\rho$ 的作用:一次真实数据集实验
原文档强调:取 $\rho = 0.9$ 时,每个参数更新对应的历史半衰期约为 $1/(1-\rho) = 10$ 步,"这通常工作得很好"。利用仓库 d2l/torch.py 提供的辅助函数即可一键复现:
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)从源码看,get_data_ch11(d2l/torch.py)会下载airfoil(翼型噪声)数据集并做标准化,返回前 1500 个样本的小批量迭代器与特征维度;train_ch11(d2l/torch.py)则初始化一个线性回归模型(参数w、b通过正态分布std=0.01初始化),在 2 个 epoch 内用squared_loss训练并打印损失与每 epoch 耗时。这一套"数据集构造 + 训练循环 + 动画绘制"的机制在 MXNet(d2l/mxnet.py)、TensorFlow(d2l/tensorflow.py)、PaddlePaddle(d2l/paddle.py)中均有同构实现,四框架实验可互相印证。
由于 Adadelta 的步长由 $\sqrt{\Delta\mathbf{x}_{t-1} + \epsilon}$ 驱动,训练初期 $\Delta\mathbf{x}$ 从 0 起步,步长天然较小;随着优化推进,自适应机制会依据历史变化幅度自动"校准"步长,因此无需像 RMSProp 那样额外调度 $\eta$。
简洁实现:一行调用内置优化器
手写版本之外,原文档还演示了如何用框架内置的 Adadelta 一行完成同样的训练,这正是 d2l/torch.py 中train_concise_ch11的设计目的——它内部构造一个nn.Linear(5, 1)网络、用nn.MSELoss作为损失,并通过optimizer.step()驱动训练。
MXNet(Trainer类)
d2l.train_concise_ch11('adadelta', {'rho': 0.9}, data_iter)PyTorch(torch.optim.Adadelta)
trainer = torch.optim.Adadelta d2l.train_concise_ch11(trainer, {'rho': 0.9}, data_iter)TensorFlow(tf.keras.optimizers.Adadelta)
# adadelta is not converging at default learning rate # but it's converging at lr = 5.0 trainer = tf.keras.optimizers.Adadelta d2l.train_concise_ch11(trainer, {'learning_rate':5.0, 'rho': 0.9}, data_iter)值得注意:TensorFlow 的简洁实现必须显式传入learning_rate=5.0才能收敛(原文档注释明确说明"adadelta is not converging at default learning rate but it's converging at lr = 5.0")。原因在于tf.keras.optimizers.Adadelta的工程实现并未像手写版那样完全消除学习率因子,而是保留了可调的learning_rate(默认值较小),因此当 $\rho=0.9$ 时默认步长不足以推进。这也印证了原文档习题 3 的设问——"Adadelta 真的完全没有学习率吗?"——至少在工程实现的层面,不同框架对该算法的落地细节并不完全一致,动手实验远比纸面推导重要。
总结
- Adadelta 没有显式学习率参数:它用参数自身的变化量($\Delta\mathbf{x}_{t-1}$)来校准步长,替代了需要人工调度的 $\eta$。
- 需要两个状态变量:$\mathbf{s}_t$ 存储梯度二阶矩,$\Delta\mathbf{x}_t$ 存储参数变化量的二阶矩,二者缺一不可。
- 使用泄漏平均维护统计量:$\rho$ 控制历史窗口,$\rho=0.9$ 对应约 10 步的半衰期,是实践中稳健的默认选择。
延伸练习与思考
原文档给出了四道练习,这里结合仓库上下文补充探索方向:
- 调整 $\rho$ 观察效果:分别取 $\rho \in {0.5, 0.9, 0.99}$ 重跑上述实验,观察损失曲线。$\rho$ 越接近 1,历史记忆越长,步长变化越平滑;$\rho$ 过小则自适应机制接近"只看上一步",稳定性下降。
- 不使用 $\mathbf{g}_t'$ 的等价实现:试把更新写作 $\mathbf{x}t = \mathbf{x}{t-1} - \frac{\sqrt{\Delta\mathbf{x}_{t-1}+\epsilon}}{\sqrt{\mathbf{s}_t+\epsilon}}\odot\mathbf{g}_t$,直接在一步内完成重缩放与更新。这样省去中间量、减少一次内存往返,是节省存储与数值误差的常见优化技巧。
- "无学习率"是否名副其实:正如 TensorFlow 简洁实现所揭示的,工程实现往往仍保留学习率因子。可以尝试构造病态目标函数(如高度非凸、梯度稀疏的问题)检验 Adadelta 是否真的不依赖任何手动步长。
- 与 AdaGrad、RMSProp 对比收敛行为:Adadelta 同时继承了 RMSProp 的泄漏平均归一化与"以变化量为步长"的自适应机制,可对照 chapter_optimization/adagrad_origin.md 与 chapter_optimization/rmsprop_origin.md 中的二维轨迹可视化函数(
d2l.train_2d与d2l.show_trace_2d,见 d2l/torch.py)在同一目标函数上并排观察三者轨迹差异。
完整的 Adadelta 章节位于 chapter_optimization/adadelta_origin.md,并收录于《动手学深度学习》优化算法章节;配套的airfoil数据集加载、训练循环与动画工具均可从 d2l/torch.py 中直接复用。
- 人工智能
- 深度学习
- 机器学习
- 教程
【免费下载链接】d2l-zh
《动手学深度学习》:面向中文读者、能运行、可讨论。中英文版被70多个国家的500多所大学用于教学。
相关推荐
d2l-en 优化算法精讲:Adadelta 自适应学习率方法(无学习率优化器)
d2l en 优化算法精讲:Adadelta 自适应学习率方法(无学习率优化器) 导读 Adadelta 是《动手学深度学习》(d2l en)优化算法章节中继
文档教程人工智能深度学习NLP计算机视觉强化学习《动手学深度学习》优化算法全景解析:从梯度下降到 Adam 与学习率调度
《动手学深度学习》优化算法全景解析:从梯度下降到 Adam 与学习率调度 《动手学深度学习》(d2l zh)在"优化算法"一章( 章节索引 https://li
人工智能深度学习机器学习教程《动手学深度学习》(d2l-zh)微积分入门:导数、梯度与链式法则
《动手学深度学习》(d2l zh)微积分入门:导数、梯度与链式法则 本节是《动手学深度学习》(d2l zh)预备知识章节之一(对应仓库文件 chapter_pr
人工智能深度学习机器学习教程
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考