11. PyTorch 梯度计算与反向传播
在 PyTorch 中,梯度计算与反向传播是自动求导机制的核心功能,它们使得我们能够高效地训练神经网络模型。通过自动求导,PyTorch 能够根据计算图自动计算出每个张量的梯度,从而为优化算法提供必要的信息。本节将详细介绍 PyTorch 中梯度计算与反向传播的工作原理、使用方法以及一些常见的问题和解决方法。
11.1 梯度计算与反向传播的工作原理
在 PyTorch 中,梯度计算与反向传播是基于计算图的。当一个张量的 requires_grad
属性被设置为 True
时,PyTorch 会自动跟踪该张量的所有操作,并构建一个动态计算图。这个计算图记录了张量之间的依赖关系以及操作的顺序。在执行反向传播时,PyTorch 会从计算图的输出节点开始,沿着计算图的反向路径,逐层计算每个张量的梯度。
例如,假设我们有一个简单的计算过程 ( y = (x + 2)^2 ),其中 ( x ) 是一个输入张量,且 ( x ) 的 requires_grad
属性被设置为 True
。在这个计算过程中,PyTorch 会构建一个计算图,如下所示:
x -> (x + 2) -> (x + 2)^2 -> y
当调用 y.backward()
时,PyTorch 会从 ( y ) 开始,沿着计算图的反向路径,逐层计算每个张量的梯度。具体来说,反向传播的过程如下:
-
计算 ( y ) 关于 ( (x + 2)^2 ) 的梯度:
[
\frac{\partial y}{\partial (x + 2)^2} = 1
] -
计算 ( (x + 2)^2 ) 关于 ( (x + 2) ) 的梯度:
[
\frac{\partial (x + 2)^2}{\partial (x + 2)} = 2(x + 2)
] -
计算 ( (x + 2) ) 关于 ( x ) 的梯度:
[
\frac{\partial (x + 2)}{\partial x} = 1
] -
最终,计算 ( y ) 关于 ( x ) 的梯度:
[
\frac{\partial y}{\partial x} = \frac{\partial y}{\partial (x + 2)^2} \cdot \frac{\partial (x + 2)^2}{\partial (x + 2)} \cdot \frac{\partial (x + 2)}{\partial x} = 1 \cdot 2(x + 2) \cdot 1 = 2(x + 2)
]
在实际代码中,这个过程是自动完成的:
import torch
# 定义输入张量,并设置 requires_grad=True
x = torch.tensor(2.0, requires_grad=True)
# 定义计算过程
y = (x + 2) ** 2
# 反向传播
y.backward()
# 查看梯度
print("x 的梯度:", x.grad)
输出结果为:
x 的梯度: tensor(8.)
11.2 梯度计算与反向传播的使用方法
11.2.1 调用 backward()
方法
在 PyTorch 中,梯度计算是通过调用 backward()
方法完成的。backward()
方法会从调用它的张量开始,沿着计算图的反向路径,逐层计算每个张量的梯度。默认情况下,backward()
方法会计算标量张量的梯度。如果需要计算向量张量的梯度,则需要提供一个与输出张量形状相同的张量作为参数。
例如,假设我们有一个向量张量 ( y = [y_1, y_2] ),其中 ( y_1 = x_1^2 ) 和 ( y_2 = x_2^2 ),且 ( x ) 是一个输入张量,其形状为 ([2])。为了计算 ( y ) 关于 ( x ) 的梯度,我们需要提供一个与 ( y ) 形状相同的张量作为参数:
import torch
# 定义输入张量,并设置 requires_grad=True
x = torch.tensor([2.0, 3.0], requires_grad=True)
# 定义计算过程
y = x ** 2
# 反向传播
y.backward(torch.tensor([1.0, 1.0]))
# 查看梯度
print("x 的梯度:", x.grad)
输出结果为:
x 的梯度: tensor([4., 6.])
11.2.2 梯度累加
在某些情况下,我们可能需要对多个损失函数进行反向传播,并累加梯度。PyTorch 允许我们在调用 backward()
方法时,不清空梯度信息,从而实现梯度累加。
例如,假设我们有两个损失函数 ( L_1 ) 和 ( L_2 ),它们分别依赖于同一个输入张量 ( x )。我们可以通过多次调用 backward()
方法,并将梯度累加起来:
import torch
# 定义输入张量,并设置 requires_grad=True
x = torch.tensor([2.0, 3.0], requires_grad=True)
# 定义两个损失函数
L1 = x ** 2
L2 = x ** 3
# 第一次反向传播
L1.backward(torch.tensor([1.0, 1.0]), retain_graph=True)
# 第二次反向传播
L2.backward(torch.tensor([1.0, 1.0]))
# 查看梯度
print("x 的梯度:", x.grad)
输出结果为:
x 的梯度: tensor([12., 45.])
在上述代码中,retain_graph=True
参数的作用是保留计算图,以便进行多次反向传播。
11.3 常见问题与解决方法
11.3.1 RuntimeError: element 0 of tensors does not require grad
这个错误通常发生在调用 backward()
方法时,计算图中的某些张量的 requires_grad
属性被设置为 False
。为了解决这个问题,需要确保所有参与计算的张量的 requires_grad
属性都被设置为 True
。
例如:
import torch
# 定义输入张量,并设置 requires_grad=True
x = torch.tensor([2.0, 3.0], requires_grad=True)
# 定义计算过程
y = x ** 2
# 调用 backward() 方法
y.backward(torch.tensor([1.0, 1.0]))
# 查看梯度
print("x 的梯度:", x.grad)
如果某些张量的 requires_grad
属性被设置为 False
,则会报错:
import torch
# 定义输入张量,并设置 requires_grad=True
x = torch.tensor([2.0, 3.0], requires_grad=True)
# 定义计算过程
y = x ** 2
# 将 y 的 requires_grad 属性设置为 False
y.requires_grad_(False)
# 调用 backward() 方法
y.backward(torch.tensor([1.0, 1.0]))
输出结果为:
RuntimeError: element 0 of tensors does not require grad
11.3.2 RuntimeError: one of the variables needed for gradient computation has been modified in place
这个错误通常发生在对参与计算的张量进行原地操作(in-place operation)时。PyTorch 不允许对参与计算的张量进行原地操作,因为这会破坏计算图的完整性。为了解决这个问题,需要避免对参与计算的张量进行原地操作。
例如:
import torch
# 定义输入张量,并设置 requires_grad=True
x = torch.tensor([2.0, 3.0], requires_grad=True)
# 定义计算过程
y = x ** 2
# 对 y 进行原地操作
y += 1
# 调用 backward() 方法
y.backward(torch.tensor([1.0, 1.0]))
输出结果为:
RuntimeError: one of the variables needed for gradient computation has been modified in place
为了避免这个错误,可以使用非原地操作:
import torch
# 定义输入张量,并设置 requires_grad=True
x = torch.tensor([2.0, 3.0], requires_grad=True)
# 定义计算过程
y = x ** 2
**更多技术文章见公众号: 大城市小农民**