多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

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

《动手学深度学习》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论文出自 Zeiler2012见仓库 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_size10) d2l.train_ch11(adadelta, init_adadelta_states(feature_dim), {rho: 0.9}, data_iter, feature_dim)从源码看get_data_ch11d2l/torch.py会下载airfoil翼型噪声数据集并做标准化返回前 1500 个样本的小批量迭代器与特征维度train_ch11d2l/torch.py则初始化一个线性回归模型参数w、b通过正态分布std0.01初始化在 2 个 epoch 内用squared_loss训练并打印损失与每 epoch 耗时。这一套数据集构造 训练循环 动画绘制的机制在 MXNetd2l/mxnet.py、TensorFlowd2l/tensorflow.py、PaddlePaddled2l/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()驱动训练。MXNetTrainer类d2l.train_concise_ch11(adadelta, {rho: 0.9}, data_iter)PyTorchtorch.optim.Adadeltatrainer torch.optim.Adadelta d2l.train_concise_ch11(trainer, {rho: 0.9}, data_iter)TensorFlowtf.keras.optimizers.Adadelta# adadelta is not converging at default learning rate # but its 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_rate5.0才能收敛原文档注释明确说明adadelta is not converging at default learning rate but its converging at lr 5.0。原因在于tf.keras.optimizers.Adadelta的工程实现并未像手写版那样完全消除学习率因子而是保留了可调的learning_rate默认值较小因此当 $\rho0.9$ 时默认步长不足以推进。这也印证了原文档习题 3 的设问——Adadelta 真的完全没有学习率吗——至少在工程实现的层面不同框架对该算法的落地细节并不完全一致动手实验远比纸面推导重要。总结Adadelta 没有显式学习率参数它用参数自身的变化量$\Delta\mathbf{x}_{t-1}$来校准步长替代了需要人工调度的 $\eta$。需要两个状态变量$\mathbf{s}_t$ 存储梯度二阶矩$\Delta\mathbf{x}_t$ 存储参数变化量的二阶矩二者缺一不可。使用泄漏平均维护统计量$\rho$ 控制历史窗口$\rho0.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多所大学用于教学。项目地址https://gitcode.com/GitHub_Trending/d2/d2l-zh点击查看免费下载相关推荐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),仅供参考
返回列表