1.4 自动求导 (Autograd)


文档摘要

1.4 自动求导 (Autograd) PyTorch 自动求导 (Autograd) 详解:深度学习的基石 1.4.1 自动求导的重要性与基本概念 在深入 PyTorch 的 Autograd 之前,我们首先需要理解为什么自动求导在深度学习中如此重要。深度学习模型,特别是神经网络,通常包含大量的参数。训练这些模型的目标是找到一组最优的参数,使得模型在特定任务上表现最佳。这个“最优”通常通过损失函数 (Loss Function) 来衡量,损失函数量化了模型预测结果与真实值之间的差距。 为了最小化损失函数,我们通常采用梯度下降 (Gradient Descent) 或其变体等优化算法。这些算法的核心思想是沿着损失函数梯度的反方向更新模型参数,逐步逼近损失函数的最小值。

1.4 自动求导 (Autograd)

PyTorch 自动求导 (Autograd) 详解:深度学习的基石

1.4.1 自动求导的重要性与基本概念

在深入 PyTorch 的 Autograd 之前,我们首先需要理解为什么自动求导在深度学习中如此重要。深度学习模型,特别是神经网络,通常包含大量的参数。训练这些模型的目标是找到一组最优的参数,使得模型在特定任务上表现最佳。这个“最优”通常通过损失函数 (Loss Function) 来衡量,损失函数量化了模型预测结果与真实值之间的差距。

为了最小化损失函数,我们通常采用梯度下降 (Gradient Descent) 或其变体等优化算法。这些算法的核心思想是沿着损失函数梯度的反方向更新模型参数,逐步逼近损失函数的最小值。梯度 指示了损失函数在参数空间中最陡峭的上升方向,因此梯度的反方向就是损失函数下降最快的方向。

手动计算复杂模型的梯度是一项极其繁琐且容易出错的任务。而自动求导 技术应运而生,它能够自动地、高效地计算复杂函数的梯度,极大地简化了深度学习模型的训练过程。

自动求导的核心思想 是将复杂的数学运算分解为一系列基本运算,并利用链式法则 (Chain Rule) 递归地计算梯度。PyTorch 的 Autograd 引擎正是基于这一思想实现的。

1.4.2 PyTorch Autograd 的核心组件

PyTorch 的自动求导机制主要依赖于以下几个核心组件:

  • torch.Tensor: 张量是 PyTorch 中最基本的数据结构,也是 Autograd 的核心操作对象。当张量被设置为需要追踪梯度时 (requires_grad=True),PyTorch 将会记录应用于该张量的所有操作,并构建一个动态计算图 (Dynamic Computation Graph)

  • 计算图 (Computation Graph): 计算图是一种有向无环图 (DAG),用于表示一系列运算操作。在 PyTorch 中,计算图是动态构建的,这意味着图的结构会随着代码的执行而改变。当执行涉及 requires_grad=True 的张量的操作时,相应的运算会被添加到计算图中。

  • grad_fn: 每个参与计算图的张量(除了由用户直接创建的张量)都有一个 grad_fn 属性。这个属性指向一个函数,该函数知道如何计算该张量相对于其输入的梯度。对于用户直接创建的张量,grad_fnNone

  • backward() 方法: backward() 方法是触发反向传播 (Backpropagation) 的关键。当我们对计算图中的某个张量(通常是损失函数)调用 backward() 方法时,PyTorch 会沿着计算图反向遍历,利用链式法则计算每个参与运算的张量相对于最终张量的梯度,并将梯度存储在张量的 .grad 属性中。

1.4.3 requires_grad=True:启用梯度追踪

要让 PyTorch 追踪张量的梯度,我们需要在创建张量时设置 requires_grad=True。默认情况下,requires_gradFalse,这意味着 PyTorch 不会记录对该张量的操作,也不会计算其梯度。

代码示例 1:启用梯度追踪

import torch # 创建一个需要追踪梯度的张量 x = torch.tensor(2.0, requires_grad=True) w = torch.tensor(3.0, requires_grad=True) b = torch.tensor(1.0, requires_grad=True) # 进行一些运算 y = w * x + b # 查看 y 的 grad_fn print(y.grad_fn)

代码解释:

  • 我们使用 torch.tensor() 创建了三个张量 x, w, b,并将 requires_grad=True 设置为 True。

  • 我们进行了简单的线性运算 y = w * x + b

  • print(y.grad_fn) 输出 <AddBackward0 object at ...>,表明 ygrad_fn 属性指向一个 AddBackward0 对象,这个对象负责计算加法操作的梯度。这说明 PyTorch 已经记录了这次加法运算,并将其加入到计算图中。

图形化表示计算图 (Graph TD):

图解说明:

  • 图中矩形框表示张量 (x, w, b, y)。

  • 圆角矩形框表示运算操作 (MultiplyBackward, AddBackward)。这些后缀 "Backward" 表示这些节点存储的是反向传播时所需的梯度计算函数。

  • 箭头表示数据流动的方向,也隐含了前向传播的路径。

  • 当调用 y.backward() 时,反向传播将从 y 节点开始,沿着箭头的反方向回溯,依次执行 AddBackwardMultiplyBackward 操作,计算梯度。

1.4.4 反向传播:计算梯度

当我们定义好模型和损失函数后,就可以使用 backward() 方法来计算梯度。通常,我们对损失函数调用 backward() 方法,PyTorch 会自动计算损失函数相对于所有 requires_grad=True 的张量的梯度。

代码示例 2:反向传播计算梯度

import torch x = torch.tensor(2.0, requires_grad=True) w = torch.tensor(3.0, requires_grad=True) b = torch.tensor(1.0, requires_grad=True) y = w * x + b loss = (y - 5)**2 # 假设我们的目标值是 5 # 执行反向传播 loss.backward() # 查看梯度 print(x.grad) # ∂loss/∂x print(w.grad) # ∂loss/∂w print(b.grad) # ∂loss/∂b

代码解释:

  • 我们定义了损失函数 loss = (y - 5)**2,目标值是 5。

  • loss.backward() 触发反向传播。PyTorch 会自动计算 loss 相对于 x, w, b 的梯度。

  • print(x.grad), print(w.grad), print(b.grad) 分别输出了 ∂loss/∂x, ∂loss/∂w, ∂loss/∂b 的值。

手动计算梯度验证:

  • y = wx + b = 3x + 1

  • loss = (y - 5)^2 = (3x + 1 - 5)^2 = (3x - 4)^2

  • ∂loss/∂x = 2 * (3x - 4) * 3 = 6 * (3x - 4) = 6 * (3*2 - 4) = 6 * 2 = 12

  • ∂loss/∂w = ∂loss/∂y * ∂y/∂w = 2 * (y - 5) * x = 2 * (3x + 1 - 5) * x = 2 * (3x - 4) * x = 2 * (3*2 - 4) * 2 = 8

  • ∂loss/∂b = ∂loss/∂y * ∂y/∂b = 2 * (y - 5) * 1 = 2 * (3x + 1 - 5) = 2 * (3x - 4) = 2 * (3*2 - 4) = 4

运行代码示例 2,你会得到:

tensor(12.) tensor(8.) tensor(4.)

这与我们手动计算的结果完全一致,验证了 PyTorch Autograd 的正确性。

1.4.5 访问梯度 .grad

在调用 backward() 方法后,我们可以通过张量的 .grad 属性来访问计算得到的梯度。需要注意的是:

  • 梯度累积: 默认情况下,每次调用 backward() 方法计算的梯度会被累加到 .grad 属性中。这意味着,如果在多次迭代中不手动清零梯度,梯度值会不断累积,导致结果错误。因此,在每次反向传播之前,通常需要将梯度清零,可以使用 tensor.grad.zero_() 方法。

  • 只对叶子节点计算梯度: 只有 requires_grad=True叶子节点 (Leaf Node) 张量才会存储梯度。叶子节点是指由用户直接创建的张量,而不是通过运算得到的张量。在上面的例子中,x, w, b 是叶子节点,而 yloss 不是叶子节点。我们可以通过 tensor.is_leaf 属性来判断一个张量是否是叶子节点。

  • 非叶子节点的梯度: 虽然非叶子节点不存储梯度,但它们的 grad_fn 属性仍然存在,并参与反向传播的计算。如果需要访问非叶子节点的梯度(例如,用于调试),可以使用 retain_grad() 方法在反向传播过程中保留非叶子节点的梯度。

代码示例 3:梯度清零和叶子节点

import torch x = torch.tensor(2.0, requires_grad=True) w = torch.tensor(3.0, requires_grad=True) b = torch.tensor(1.0, requires_grad=True) y = w * x + b loss = (y - 5)**2 # 第一次反向传播 loss.backward() print(w.grad) # 第二次反向传播 (不清零梯度) loss.backward() print(w.grad) # 梯度累积了 # 清零梯度 w.grad.zero_() print(w.grad) # 第三次反向传播 (清零梯度后) loss.backward() print(w.grad) print(x.is_leaf) # True print(y.is_leaf) # False

代码解释:

  • 第一次反向传播后,w.grad 为 8.0。

  • 第二次反向传播后,w.grad 累积了之前的梯度,变为 16.0。

  • 使用 w.grad.zero_()w 的梯度清零。注意 zero_() 方法是一个 in-place 操作,会直接修改 w.grad 的值。

  • 第三次反向传播后,由于梯度被清零,w.grad 再次变为 8.0。

  • x.is_leaf 为 True,因为 x 是用户直接创建的张量。

  • y.is_leaf 为 False,因为 y 是通过运算得到的张量。

1.4.6 torch.no_grad()detach():禁用梯度追踪

在某些情况下,我们可能需要临时禁用梯度追踪,例如在推理 (Inference) 阶段,或者在训练过程中固定某些层的参数。PyTorch 提供了两种方法来实现这一目的:

  • torch.no_grad() 上下文管理器: torch.no_grad() 可以作为一个上下文管理器使用,在该上下文管理器内的所有操作都不会被追踪梯度。这通常用于推理阶段,以提高效率并减少内存占用。

  • detach() 方法: detach() 方法可以从计算图中分离出一个张量。返回的新张量与原张量共享数据,但不再追踪梯度。对 detach 后的张量的操作不会被记录到计算图中。detach() 方法常用于在训练过程中分离计算图,例如在实现某些高级训练技巧时。

代码示例 4:torch.no_grad()detach()

import torch x = torch.tensor(2.0, requires_grad=True) w = torch.tensor(3.0, requires_grad=True) # 使用 torch.no_grad() with torch.no_grad(): y_nograd = w * x + 1 print(y_nograd.requires_grad) # False print(y_nograd.grad_fn) # None # 使用 detach() y_detached = w * x + 1 y_detached = y_detached.detach() print(y_detached.requires_grad) # False print(y_detached.grad_fn) # None # 尝试对 y_detached 进行反向传播 (会报错) # y_detached.backward() # 报错:RuntimeError: element 0 of tensors does not require grad and does not have a grad_fn

代码解释:

  • torch.no_grad() 上下文管理器内,y_nogradrequires_grad 变为 False,grad_fn 为 None,说明梯度追踪被禁用。

  • y_detached = y_detached.detach() 创建了一个与 y_detached 共享数据,但不追踪梯度的新张量。y_detached.requires_grad 变为 False,grad_fn 为 None。

  • 尝试对 y_detached 调用 backward() 方法会报错,因为 y_detached 不在计算图中,无法进行反向传播。

1.4.7 In-place 操作的限制

PyTorch 的 Autograd 引擎对 in-place 操作 有一些限制。In-place 操作是指直接修改张量自身数据的操作,例如 +=, *=, index_fill_(), zero_() 等。

In-place 操作可能会破坏计算图,导致梯度计算错误。 因为 Autograd 依赖于计算图来追踪操作和计算梯度,而 in-place 操作可能会覆盖计算图中某些节点所需的数据。

一般情况下,应尽量避免使用 in-place 操作,除非你非常清楚其影响,并且确信不会破坏梯度计算。 如果必须使用 in-place 操作,需要谨慎考虑,并确保在不需要梯度追踪的部分使用。

代码示例 5:In-place 操作的潜在问题

import torch x = torch.tensor(2.0, requires_grad=True) w = torch.tensor(3.0, requires_grad=True) y = w * x y += 1 # In-place 加法 (可能导致问题) loss = (y - 5)**2 try: loss.backward() # 有时会报错,有时不会,取决于具体操作和版本 print("Backward pass successful (might be incorrect gradient)") except RuntimeError as e: print(f"RuntimeError: {e}") # 建议使用 out-of-place 操作 x = torch.tensor(2.0, requires_grad=True) w = torch.tensor(3.0, requires_grad=True) y = w * x y = y + 1 # Out-of-place 加法 (推荐) loss = (y - 5)**2 loss.backward() print("Backward pass successful (correct gradient)")

代码解释:

  • 在示例中,y += 1 是一个 in-place 加法操作。虽然在某些情况下可能不会报错,但它可能会破坏计算图,导致梯度计算不正确。具体是否报错以及梯度是否正确,取决于 PyTorch 的版本和具体的 in-place 操作。

  • 为了避免潜在的问题,推荐使用 out-of-place 操作,例如 y = y + 1,它会创建一个新的张量 y 来存储结果,而不会修改原始的 y 张量,从而保证计算图的完整性。

1.4.8 动态计算图的优势

PyTorch 使用动态计算图,这意味着计算图是在代码执行过程中动态构建的。与静态计算图 (如 TensorFlow 1.x) 相比,动态计算图具有以下优势:

  • 灵活性: 动态计算图允许我们根据不同的输入数据和控制流,构建不同的计算图。这使得 PyTorch 非常适合处理循环神经网络 (RNN)自然语言处理 (NLP) 等任务,因为这些任务的模型结构通常是动态变化的。

  • 易于调试: 动态计算图的构建过程与代码的执行过程一致,这使得调试更加直观和容易。我们可以使用 Python 的标准调试工具 (如 pdb, ipdb) 来逐步执行代码,并观察计算图的构建过程。

  • 更自然的编程体验: 动态计算图更符合 Python 的编程习惯,使得代码编写更加自然和简洁。开发者无需预先定义完整的计算图,而是可以像编写普通 Python 代码一样构建模型。

代码示例 6:动态计算图的灵活性

import torch x = torch.randn(10, 10, requires_grad=True) linear = torch.nn.Linear(10, 1) if torch.rand(1) > 0.5: output = linear(x).relu() # ReLU 激活函数 else: output = linear(x).sigmoid() # Sigmoid 激活函数 loss = output.mean() loss.backward()

代码解释:

  • 根据 torch.rand(1) > 0.5 的结果,代码会动态地选择使用 ReLU 或 Sigmoid 激活函数。

  • 由于计算图是动态构建的,PyTorch 能够根据实际执行的代码路径,构建不同的计算图,并正确地计算梯度。

  • 这种灵活性在静态计算图中是难以实现的,或者需要复杂的控制流机制。

图形化表示动态计算图 (Graph TD - 两种可能的计算图):

情况 1 (ReLU):

情况 2 (Sigmoid):

图解说明:

  • 根据条件判断,计算图会动态地选择包含 ReLU 或 Sigmoid 激活函数。

  • PyTorch Autograd 能够自动处理这种动态性,保证梯度计算的正确性。

1.4.9 总结

PyTorch 的自动求导 (Autograd) 机制是深度学习模型训练的基石。它通过动态计算图和反向传播算法,自动高效地计算模型参数的梯度,极大地简化了深度学习的开发过程。理解 Autograd 的核心概念,掌握 requires_grad=Truebackward().gradtorch.no_grad()detach() 等关键工具,对于深入学习和应用 PyTorch 至关重要。 掌握 Autograd,才能更好地利用 PyTorch 构建和训练各种复杂的深度学习模型,并在实际应用中取得优秀的成果。

希望本文能够帮助读者深入理解 PyTorch 的自动求导机制,并能够将其灵活应用到自己的深度学习项目中。


作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U