news 2026/10/2 21:01:55

《动手学深度学习》Adadelta 优化算法全解:无需学习率的自适应梯度方法(d2l-zh)

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
《动手学深度学习》Adadelta 优化算法全解:无需学习率的自适应梯度方法(d2l-zh)
  • 人工智能
  • 深度学习
  • 机器学习
  • 教程

【免费下载链接】d2l-zh

《动手学深度学习》:面向中文读者、能运行、可讨论。中英文版被70多个国家的500多所大学用于教学。

项目地址:https://gitcode.com/GitHub_Trending/d2/d2l-zh
点击查看免费下载

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 * g

PyTorch 实现

%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 步的半衰期,是实践中稳健的默认选择。

延伸练习与思考

原文档给出了四道练习,这里结合仓库上下文补充探索方向:

  1. 调整 $\rho$ 观察效果:分别取 $\rho \in {0.5, 0.9, 0.99}$ 重跑上述实验,观察损失曲线。$\rho$ 越接近 1,历史记忆越长,步长变化越平滑;$\rho$ 过小则自适应机制接近"只看上一步",稳定性下降。
  2. 不使用 $\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$,直接在一步内完成重缩放与更新。这样省去中间量、减少一次内存往返,是节省存储与数值误差的常见优化技巧。
  3. "无学习率"是否名副其实:正如 TensorFlow 简洁实现所揭示的,工程实现往往仍保留学习率因子。可以尝试构造病态目标函数(如高度非凸、梯度稀疏的问题)检验 Adadelta 是否真的不依赖任何手动步长。
  4. 与 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多所大学用于教学。

项目地址:https://gitcode.com/GitHub_Trending/d2/d2l-zh
点击查看免费下载

相关推荐

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/2 21:01:26

上位机与PLC:替代还是协同?工业控制选型与实战解析

干工控这行的人,几乎都绕不开一个问题:上位机到底能不能替代PLC?尤其是项目预算吃紧、想直接用PC统一管理的场景,这个念头基本每个工程师都动过。我见过不少刚入行的朋友,上来就问“我能不能直接用C#写个界面&#xff…

作者头像 李华
网站建设 2026/10/2 21:00:06

长沙小学数学培训,四五年级是关键提升期,赏识培训给出答案

长沙小学数学培训,四五年级是关键。四年级开始,小数加减、平行四边形与梯形、周期问题、乘法原理初步等内容陆续登场。五年级则进入小数乘除、分数加减、简易方程、数论进阶等更深层次的内容。这个阶段,孩子从“具象计算”向“抽象计算”过渡…

作者头像 李华
网站建设 2026/10/2 20:59:12

本地部署AI编程助手Codex:Docker环境搭建与DeepSeek模型接入实战

1. 为什么要在本地跑 Codex 而不是只用网页版很多人第一次接触 Codex 都是在浏览器里敲几行提示词,看着它补全代码、解释报错,觉得挺方便。但只要你真正把它当成日常开发的一部分,很快就会撞上几个绕不开的问题:网络延迟导致补全卡…

作者头像 李华