ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

《动手学深度学习》自动微分(autograd)实战:从计算图到反向传播的梯度计算全解析

《动手学深度学习》自动微分(autograd)实战:从计算图到反向传播的梯度计算全解析 人工智能深度学习机器学习教程【免费下载链接】d2l-zh《动手学深度学习》面向中文读者、能运行、可讨论。中英文版被70多个国家的500多所大学用于教学。项目地址https://gitcode.com/GitHub_Trending/d2/d2l-zh点击查看免费下载自动微分automatic differentiation是几乎所有深度学习优化算法的基石它让框架能够自动构建计算图、记录前向计算并通过反向传播一次性填出每个参数的偏导数。本文以《动手学深度学习》d2l-zh预备知识章节的autograd专题文档为核心结合仓库中 d2l/torch.py、d2l/mxnet.py 等工具库源码系统讲解梯度存储、标量与向量反向传播、计算分离detach、以及带 Python 控制流的梯度计算并给出可运行的三框架MXNet / PyTorch / TensorFlow代码。读完本文你将掌握在任何深度学习框架中算梯度的标准四步法并理解其背后的计算图原理。为什么要自动微分从手工求导到计算图正如 微积分章节 所介绍的求导是优化问题的核心我们训练模型本质上是在不断最小化一个损失函数。虽然求导的计算本身只需要基本的微积分但对于复杂的深层模型手工推导并更新每一个参数的偏导数是一件很痛苦的事情而且经常容易出错。深度学习框架通过自动计算导数即自动微分来加快这一过程。实际运行时系统会基于我们设计的模型构建一个计算图computational graph跟踪哪些数据通过哪些操作组合起来产生输出。自动微分使系统能够随后反向传播梯度backpropagate——这里的反向传播指的是跟踪整个计算图逐个填充关于每个参数的偏导数。在 预备知识章节 的导读中作者也明确指出幸运的是autograd包会自动计算微分。仓库中d2l/mxnet.py、d2l/torch.py、d2l/tensorflow.py等工具库的几乎所有训练函数都依赖这一机制本文将在最后一节以源码为证展开说明。一个简单的例子对 y 2xᵀx 求导作为演示例子假设我们要对函数 $y 2\mathbf{x}^{\top}\mathbf{x}$ 关于列向量 $\mathbf{x}$ 求导。首先创建变量x并赋初始值# MXNet from mxnet import autograd, np, npx npx.set_np() x np.arange(4.0) x# PyTorch import torch x torch.arange(4.0) x# TensorFlow import tensorflow as tf x tf.range(4, dtypetf.float32) x梯度存储attach_grad / requires_grad_ / tf.Variable在计算 $y$ 关于 $\mathbf{x}$ 的梯度之前需要一个地方来存储梯度。文档强调了一个工程细节不要在每次对一个参数求导时都重新分配内存因为我们经常会成千上万次地更新同一批参数每次都分配新内存会很快耗尽内存。注意标量函数关于向量 $\mathbf{x}$ 的梯度本身是向量且与 $\mathbf{x}$ 形状相同。# MXNet调用 attach_grad 为张量的梯度分配内存grad 属性初始化为 0 x.attach_grad() x.grad# PyTorchrequires_grad_ 等价于 x torch.arange(4.0, requires_gradTrue)默认 grad 为 None x.requires_grad_(True) x.grad# TensorFlow将张量包装为 Variable 才能记录梯度 x tf.Variable(x)记录计算并触发反向传播接下来计算 $y$然后调用反向传播函数求出梯度# MXNet把代码放入 autograd.record 作用域以建立计算图 with autograd.record(): y 2 * np.dot(x, x) y# PyTorch y 2 * torch.dot(x, x) y# TensorFlow把所有计算记录在磁带tape上 with tf.GradientTape() as t: y 2 * tf.tensordot(x, x, axes1) y由于x是长度为 4 的向量x与x做内积得到标量y。随后调用反向传播函数并打印梯度# MXNet / PyTorch y.backward() x.grad# TensorFlow注意这里从磁带中取梯度 x_grad t.gradient(y, x) x_grad函数 $y 2\mathbf{x}^{\top}\mathbf{x}$ 关于 $\mathbf{x}$ 的梯度应为 $4\mathbf{x}$可以用下面的比较式快速验证x.grad 4 * x # MXNet / PyTorch x_grad 4 * x # TensorFlow梯度会被覆盖PyTorch 默认累加现在计算x的另一个函数# MXNet再次计算的新梯度会覆盖旧值 with autograd.record(): y x.sum() y.backward() x.grad# PyTorch默认会累积梯度需要先清除旧值再重新 backward x.grad.zero_() y x.sum() y.backward() x.grad# TensorFlow每次新建 tape梯度自然被新计算覆盖 with tf.GradientTape() as t: y tf.reduce_sum(x) t.gradient(y, x)这里体现了三个框架的默认差异MXNet 与 TensorFlow 的新一次反向计算会覆盖旧梯度而 PyTorch 默认累积梯度因此连续反向传播前需要手动zero_()。这也是 d2l/torch.py 中所有训练循环都会先调用updater.zero_grad()或param.grad.zero_()的原因。非标量变量的反向传播批内偏导数之和当y不是标量时向量y关于向量x的导数最自然的解释是一个矩阵对更高阶、更高维的y和x求导结果甚至可能是高阶张量。但在实践中当我们对向量调用 backward 时目标通常不是计算微分矩阵而是计算一批训练样本中每个样本的偏导数之和——这正对应批量训练的损失函数场景。# MXNet对向量 y 调用 backward 时会先对 y 的元素求和生成标量再计算该标量对 x 的梯度 with autograd.record(): y x * x # y 是向量 y.backward() x.grad # 等价于 y sum(x * x)# PyTorch对非标量调用 backward 需传入 gradient 参数指明被微分函数对 self 的梯度 # 本例只需求偏导数之和因此传入全 1 梯度即可y.sum().backward() 是等价写法 x.grad.zero_() y x * x y.sum().backward() x.grad# TensorFlowt.gradient(y, x) 与 y tf.reduce_sum(x * x) 等价 with tf.GradientTape() as t: y x * x t.gradient(y, x)理解这一语义至关重要深度学习中几乎所有的loss都是对批量内各样本损失求和或求平均后的标量因此对向量反向传播自动求和的行为恰好与批量训练的数学需求一致。仓库源码中可见这种模式的直接应用例如 d2l/torch.py 的train_epoch_ch3中使用内置优化器时调用l.mean().backward()使用自定义优化器时调用l.sum().backward()。分离计算detach把中间变量当常数有时我们希望把某些计算移出被记录的计算图。例如y是x的函数z又是y和x的函数。假设我们想计算z关于x的梯度但出于某种原因希望把y视为常数只考虑x在y被计算之后发挥的作用。此时可以分离y返回一个与y值相同的新变量u但它丢弃了计算图中如何计算y的全部信息——梯度不会向后流经u到x。因此下面的反向传播计算的是z u * x关于x的偏导数把u当常数而不是z x * x * x关于x的偏导数# MXNet with autograd.record(): y x * x u y.detach() z u * x z.backward() x.grad u# PyTorch x.grad.zero_() y x * x u y.detach() z u * x z.sum().backward() x.grad u# TensorFlow设置 persistentTrue 以便在同一 tape 上多次调用 t.gradient with tf.GradientTape(persistentTrue) as t: y x * x u tf.stop_gradient(y) z u * x x_grad t.gradient(z, x) x_grad u由于y的计算仍然被记录在图中我们随后依然可以单独对y反向传播得到y x * x关于x的导数 $2x$# MXNet y.backward() x.grad 2 * x# PyTorch x.grad.zero_() y.sum().backward() x.grad 2 * x# TensorFlow t.gradient(y, x) 2 * x分离计算是工程中的常用技巧。例如在 d2l/torch.py 的train_epoch_ch8中循环神经网络训练时会调用state.detach_()目的正是切断时间步之间的梯度传播路径只保留需要学习的梯度流。Python 控制流的梯度计算while、if 与任意函数调用自动微分的一个突出好处是即使构建函数的计算图需要穿过复杂的 Python 控制流条件、循环或任意函数调用我们仍然可以计算出结果变量的梯度。下面的函数f中while循环的迭代次数和if分支的走向都取决于输入a的值# MXNet def f(a): b a * 2 while np.linalg.norm(b) 1000: b b * 2 if b.sum() 0: c b else: c 100 * b return c# PyTorch def f(a): b a * 2 while b.norm() 1000: b b * 2 if b.sum() 0: c b else: c 100 * b return c# TensorFlow def f(a): b a * 2 while tf.norm(b) 1000: b b * 2 if tf.reduce_sum(b) 0: c b else: c 100 * b return c计算梯度# MXNet a np.random.normal() a.attach_grad() with autograd.record(): d f(a) d.backward()# PyTorch a torch.randn(size(), requires_gradTrue) d f(a) d.backward()# TensorFlow a tf.Variable(tf.random.normal(shape())) with tf.GradientTape() as t: d f(a) d_grad t.gradient(d, a) d_grad如何验证结果正确注意函数f在输入a上是分段线性的对于任意a都存在某个常数标量k使得f(a) k * a其中k的取值取决于输入a。因此可以用d / a来验证梯度a.grad d / a # MXNet / PyTorch d_grad d / a # TensorFlow这说明了自动微分与数值方法、符号求导的一个本质区别框架记录的是实际执行过的操作序列因此即使控制流动态改变了计算路径梯度依然精确匹配实际路径的导数而非对函数形式的整体求导。从源码看自动微分在训练循环中的真实应用自动微分并非孤立概念它渗透在 d2l-zh 仓库的每一个训练函数中。以 PyTorch 工具库 d2l/torch.py 为例自定义 SGD 更新sgd(params, lr, batch_size)d2l/torch.py在torch.no_grad()上下文中执行param - lr * param.grad / batch_size更新后立即param.grad.zero_()清零——这正是梯度存储、反向传播、读取梯度、清空再复用四步法在训练循环中的落地从零实现的训练函数train_epoch_ch3d2l/torch.py对每个小批量调用l.mean().backward()内置优化器或l.sum().backward()自定义优化器随后由优化器读取梯度并更新参数循环神经网络训练train_epoch_ch8d2l/torch.py在每个时间步初始化后对状态张量调用detach_()并配合grad_clipping做梯度裁剪优化算法验证train_ch11d2l/torch.py直接以torch.normal(..., requires_gradTrue)创建带梯度的参数w、b每轮l.backward()后调用对应的优化器状态更新函数。MXNet 工具库 d2l/mxnet.py 同样大量使用autograd.record()作用域与w.attach_grad()的配对模式。你在后续章节中会反复看到这一模式先在参数上附加梯度再在record/GradientTape中记录计算然后执行反向传播最后读取并更新梯度。掌握了本文的自动微分原理就等于拿到了阅读全书所有训练代码的钥匙。小结深度学习框架可以自动计算导数我们首先将梯度附加到想要计算偏导数的变量上然后记录目标值的计算执行反向传播函数并访问得到的梯度梯度存储要复用而非频繁分配内存标量函数关于向量的梯度与向量形状相同对非标量反向传播默认等价于先求和再求导契合批量训练损失需要把中间结果当常数时用detach()/tf.stop_gradient()切断梯度流计算图包含 Python 控制流时仍可正确求导且结果可用d / a这类解析关系验证。练习来自原文档为什么计算二阶导数比一阶导数的开销更大在运行反向传播函数之后立即再次运行它看看会发生什么。在控制流的例子中我们计算d关于a的导数如果将变量a更改为随机向量或矩阵此时f(a)不再是标量结果会发生什么如何分析重新设计一个求控制流梯度的例子运行并分析结果。令 $f(x) \sin(x)$绘制 $f(x)$ 和 $\frac{df(x)}{dx}$ 的图像其中后者不使用 $f(x) \cos(x)$。延伸阅读本文对应文档的原文与中文翻译autograd_origin.md、autograd.md前置数学基础微积分、线性代数自动微分在模型训练中的首次实战线性回归从零实现、softmax 回归从零实现梯度分离技巧的进阶应用循环神经网络从零实现运行环境配置请参考安装指南赞分享人工智能深度学习机器学习教程【免费下载链接】d2l-zh《动手学深度学习》面向中文读者、能运行、可讨论。中英文版被70多个国家的500多所大学用于教学。项目地址https://gitcode.com/GitHub_Trending/d2/d2l-zh点击查看免费下载相关推荐MXNet autograd 自动微分实战指南从梯度计算到自定义反向传播MXNet autograd 自动微分实战指南从梯度计算到自定义反向传播 本教程是 MXNet Crash Course 快速入门系列的第三步围绕 mxne深度学习人工智能机器学习分布式训练前向传播、反向传播与计算图以《动手学深度学习》单隐藏层MLP为例精讲梯度流动前向传播、反向传播与计算图以《动手学深度学习》单隐藏层MLP为例精讲梯度流动 导读 反向传播backpropagation是训练所有深度神经网络的核心算法人工智能深度学习机器学习教程开发你的第一个Claude插件基于GitHub_Trending/cl/claude-plugins-official的实践教程开发你的第一个Claude插件基于GitHub_Trending/cl/claude plugins official的实践教程 欢迎来到Claude插件开发AI 插件开发工具插件系统上一篇markitdown 一键把 PDF、Word、PPT 批量转成 Markdown 的免费开源工具下一篇技术深度解析霞鹜文楷开源中文字体的完整开发指南创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表