PyTorch 的 Autograd 系统进行一次全面且深入的详解。Autograd 是 PyTorch 的核心,它实现了自动微分,让神经网络训练中的反向传播变得自动化。
一、 Autograd 是什么?
Autograd 是 PyTorch 的自动微分引擎,它的核心功能是:
自动计算张量操作的导数/梯度。
在深度学习中,我们需要计算损失函数相对于模型参数的梯度,然后用这些梯度来更新参数。Autograd 使得我们只需要定义前向传播,它可以自动构建计算图并计算所有必要的梯度。
二、 核心概念与基础
1. 计算图
Autograd 的核心是一个动态计算图,它是一个有向无环图:
- 节点:表示张量或操作(函数)
- 边:表示张量之间的依赖关系
import torch
# 简单的计算图示例
x = torch.tensor(2.0, requires_grad=True)
y = x ** 2 + 3 * x + 1
# 计算图: x -> (x^2, 3*x) -> (x^2 + 3*x + 1) -> y
2. requires_grad 属性
这个布尔值标记决定是否跟踪对该张量的操作并构建计算图。
x = torch.tensor(2.0, requires_grad=True) # 跟踪梯度
y = torch.tensor(3.0) # 不跟踪梯度
print(x.requires_grad) # True
print(y.requires_grad) # False
3. grad_fn 属性
每个非叶子张量都有一个 grad_fn 属性,它指向创建该张量的 Function 对象。
x = torch.tensor(2.0, requires_grad=True)
y = x ** 2
print(y.grad_fn) # <PowBackward0 object at ...>
三、 Autograd 的工作流程
1. 前向传播:构建计算图
当你在 requires_grad=True 的张量上执行操作时,PyTorch 会:
- 执行计算得到结果张量
- 记录执行的操作(在
grad_fn中) - 构建计算图
# 构建计算图的过程
a = torch.tensor(1.0, requires_grad=True)
b = torch.tensor(2.0, requires_grad=True)
c = a * b # grad_fn: <MulBackward0>
d = c + 1 # grad_fn: <AddBackward0>
e = d ** 2 # grad_fn: <PowBackward0>
2. 反向传播:计算梯度
调用 .backward() 方法时,Autograd 会:
- 从该张量开始,反向遍历计算图
- 对每个节点计算梯度
- 将梯度累积到叶子节点的
.grad属性中
# 继续上面的例子
e.backward() # 触发反向传播
print(a.grad) # d(e)/d(a) = 2*(a*b+1)*b = 2*(1*2+1)*2 = 12
print(b.grad) # d(e)/d(b) = 2*(a*b+1)*a = 2*(1*2+1)*1 = 6
四、 叶子张量与非叶子张量
1. 定义
- 叶子张量:用户直接创建的张量,不是通过操作产生的
- 非叶子张量:通过对叶子张量进行操作得到的张量
x = torch.tensor(2.0, requires_grad=True) # 叶子张量
y = x ** 2 # 非叶子张量
z = y + 1 # 非叶子张量
2. 梯度保留策略
默认情况下:
- 叶子张量:梯度保存在
.grad属性中 - 非叶子张量:梯度不保留(为了节省内存)
x = torch.tensor(2.0, requires_grad=True)
y = x ** 2
y.backward()
print(x.grad) # 有值: 4.0
print(y.grad) # None (非叶子张量)
要保留非叶子张量的梯度:
y.retain_grad() # 显式要求保留梯度
y.backward()
print(y.grad) # 现在有值: 1.0
五、 .backward() 方法详解
1. 标量张量的反向传播
对于标量张量(0维),直接调用 .backward():
x = torch.tensor(2.0, requires_grad=True)
y = x ** 2
y.backward() # 等价于 y.backward(torch.tensor(1.0))
print(x.grad) # 4.0
2. 非标量张量的反向传播
对于非标量张量,需要提供 gradient 参数(通常称为 grad_tensors):
x = torch.tensor([1.0, 2.0], requires_grad=True)
y = x ** 2 # y = [1, 4]
# 假设我们有 l = sum(v * y),其中 v 是权重向量
v = torch.tensor([0.1, 0.2])
y.backward(gradient=v) # 计算 d(l)/d(x)
print(x.grad) # d(l)/d(x) = 2 * x * v = [0.2, 0.8]
理解 gradient 参数:
- 它表示上游梯度
- 对于标量损失,默认为
1.0(因为d(loss)/d(loss) = 1) - 对于向量,它定义了如何将向量输出"缩减"为标量
六、 梯度累积与清零
1. 梯度累积
PyTorch 默认会累加梯度,这在某些场景下有用(如梯度累积):
x = torch.tensor(2.0, requires_grad=True)
for _ in range(3):
y = x ** 2
y.backward()
print(f"Grad after step {_}: {x.grad}")
# 输出: 4.0, 8.0, 12.0 (梯度不断累加)
2. 梯度清零
在大多数训练场景中,我们需要在每个 batch 前清零梯度:
# 标准训练循环
optimizer = torch.optim.SGD([x], lr=0.1)
for epoch in range(num_epochs):
optimizer.zero_grad() # 清零梯度
output = model(data)
loss = criterion(output, target)
loss.backward() # 计算梯度
optimizer.step() # 更新参数
七、 控制梯度跟踪
1. torch.no_grad() 上下文管理器
禁用梯度跟踪,用于推理或中间计算:
x = torch.tensor(2.0, requires_grad=True)
with torch.no_grad():
y = x ** 2 # y.requires_grad = False
z = y + 1 # 不会跟踪梯度
print(y.requires_grad) # False
2. detach() 方法
从计算图中分离张量,创建不跟踪梯度的新张量:
x = torch.tensor(2.0, requires_grad=True)
y = x ** 2
y_detached = y.detach() # 创建新张量,不跟踪梯度
print(y_detached.requires_grad) # False
# 原张量不受影响
print(y.requires_grad) # True
3. requires_grad_() 方法
原地修改张量的 requires_grad 属性:
x = torch.tensor(2.0)
x.requires_grad_(True) # 启用梯度跟踪
print(x.requires_grad) # True
八、 高级 Autograd 特性
1. 高阶梯度计算
Autograd 支持计算高阶导数(需要设置 create_graph=True):
x = torch.tensor(2.0, requires_grad=True)
y = x ** 3
# 计算一阶导
dy_dx = torch.autograd.grad(y, x, create_graph=True)[0]
print(dy_dx) # 3*x^2 = 12.0
# 计算二阶导
d2y_dx2 = torch.autograd.grad(dy_dx, x)[0]
print(d2y_dx2) # 6*x = 12.0
2. 自定义 Autograd Function
你可以创建自定义的自动微分函数:
class MyReLU(torch.autograd.Function):
@staticmethod
def forward(ctx, input):
ctx.save_for_backward(input) # 保存输入供反向传播使用
return input.clamp(min=0)
@staticmethod
def backward(ctx, grad_output):
input, = ctx.saved_tensors
grad_input = grad_output.clone()
grad_input[input < 0] = 0 # ReLU的导数
return grad_input
# 使用自定义函数
x = torch.tensor([-1.0, 2.0], requires_grad=True)
y = MyReLU.apply(x) # 使用 .apply() 调用
y.backward(torch.tensor([1.0, 1.0]))
print(x.grad) # [0., 1.]
3. torch.autograd.grad()
另一种计算梯度的方法,不修改 .grad 属性:
x = torch.tensor(2.0, requires_grad=True)
y = x ** 2
# 计算梯度,但不累积到 x.grad
dy_dx = torch.autograd.grad(y, x)[0]
print(dy_dx) # 4.0
print(x.grad) # None (未被修改)
九、 内存管理与性能优化
1. retain_graph 参数
默认情况下,.backward() 后会释放计算图。如果需要多次反向传播:
x = torch.tensor(2.0, requires_grad=True)
y1 = x ** 2
y2 = x ** 3
# 第一次反向传播
y1.backward(retain_graph=True)
print(f"First grad: {x.grad}") # 2x = 4.0
# 第二次反向传播(需要 retain_graph=True)
y2.backward()
print(f"Second grad: {x.grad}") # 4.0 + 3x^2 = 4.0 + 12.0 = 16.0
2. 梯度检查点
对于大模型,使用梯度检查点来节省内存:
from torch.utils.checkpoint import checkpoint
def custom_forward(x):
# 复杂的计算
return x ** 2
x = torch.tensor(2.0, requires_grad=True)
y = checkpoint(custom_forward, x) # 节省内存但增加计算时间
十、 调试技巧
1. 检查梯度
# 注册钩子来检查梯度
def grad_hook(grad):
print(f"Gradient: {grad}")
x = torch.tensor(2.0, requires_grad=True)
x.register_hook(grad_hook) # 在反向传播时打印梯度
y = x ** 2
y.backward()
2. 检测数值问题
# 检测梯度爆炸/消失
torch.autograd.set_detect_anomaly(True)
try:
# 你的训练代码
loss.backward()
except RuntimeError as e:
print("Autograd异常:", e)
十一、 总结
PyTorch 的 Autograd 系统是一个强大而灵活的自动微分引擎,其核心特性包括:
- 动态计算图:根据需要构建,支持动态网络结构
- 延迟执行:只在需要时计算梯度
- 灵活控制:可以精细控制梯度跟踪和计算
- 高性能:底层由 C++ 实现,优化良好
理解 Autograd 的工作原理对于:
- 调试梯度相关问题
- 实现复杂模型结构
- 优化训练性能
- 开发自定义层和损失函数
都至关重要。通过掌握这些概念,你就能更好地利用 PyTorch 进行深度学习研究和开发。
3250

被折叠的 条评论
为什么被折叠?



