文章目录
自动微分
一、核心概念
-
计算图(Computation Graph) 动态构建的有向图,节点表示张量,边表示运算操作(如加减乘除、激活函数等)。用于记录张量的计算路径,为反向传播提供依据。
例如:
z = x ∗ y l o s s = z . s u m ( ) z = x * y\\loss = z.sum() z=x∗yloss=z.sum()在上述代码中,x 和 y 是输入张量,即叶子节点,z 是中间结果,loss 是最终输出。每一步操作都会记录依赖关系:
z = x * y:z 依赖于 x 和 y。
loss = z.sum():loss 依赖于 z。
这些依赖关系形成了一个动态计算图,如下所示:
x y \ / \ / \ / z | | v loss
-
requires_grad 张量的属性(布尔值),默认为
False
。若设为True
,PyTorch 会跟踪该张量的所有运算,以便后续计算梯度。 -
grad 属性 存储通过反向传播计算出的梯度值(如
x.grad
表示损失函数对x
的梯度)。梯度会累积,多次反向传播前需用zero_()
清零。 -
反向传播(backward ()) 从标量(如损失函数值)出发,沿计算图反向遍历,根据链式法则计算所有
requires_grad=True
的张量的梯度,结果存入grad
。例如:调用 loss.backward() 从输出节点 loss 开始,沿着计算图反向传播,计算每个节点的梯度。 -
叶子节点 :叶子节点是用户直接创建 且
requires_grad=True
的张量(如输入数据、模型参数),其is_leaf
属性为True
,梯度会被保留以用于参数更新;非叶子节点则是通过张量运算生成的中间变量,is_leaf
为False
,梯度在反向传播后自动释放,以节省内存。特征 叶子节点(Leaf Node) 非叶子节点(Non-leaf Node) 创建方式 用户直接定义(非运算生成) 通过张量运算生成(中间变量) is_leaf
True
False
梯度保留 默认保留(用于参数更新) 反向传播后自动释放(节省内存) 典型示例 输入数据、模型参数( requires_grad=True
)卷积 / 线性层输出、激活函数结果等 梯度调试 无需额外操作 需调用 retain_grad()
强制保留梯度
二、梯度计算
使用tensor.backward()方法执行反向传播,从而计算张量的梯度
标量对向量的梯度
数学原理:
- 对于标量输出 L 和参数矩阵 W,梯度 ∂L/∂W 与 W 同形
- 每个元素 wij 的梯度表示:∂L/∂wij = lim(Δ→0) [L(wij+Δ) - L(wij)]/Δ
标量是形状为()
的张量(如单个损失值),可直接调用backward()
计算梯度。
计算 (y = x_1^2 + x_2^2 + x_3^2) 对 (x = [x_1, x_2, x_3]) 的梯度(理论梯度为 ([2x_1, 2x_2, 2x_3]))。
python
import torch
# 定义输入张量,设置requires_grad=True以跟踪梯度
x = torch.tensor([1.0, 2.0, 3.0], requires_grad=True)
# 构建计算图:y是标量(x的平方和)
y = torch.sum(x **2) # y = 1² + 2² + 3² = 14.0
# 反向传播:计算y对所有requires_grad=True的张量的梯度
y.backward()
# 查看梯度(结果存储在x.grad中)
print("x的梯度:", x.grad) # 输出:tensor([2., 4., 6.])(与理论一致)
# 梯度会累积,再次计算前需清零
x.grad.zero_()
print("清零后x的梯度:", x.grad) # 输出:tensor([0., 0., 0.])
向量对向量的梯度
当输出为张量(非标量)时,需用雅可比矩阵 描述梯度关系。PyTorch 提供torch.autograd.functional.jacobian
实现。
数学定义:
- 对于函数 f: ℝⁿ → ℝᵐ,雅可比矩阵 J 是 m×n 矩阵:
J = [∂f₁/∂x₁ ... ∂f₁/∂xₙ]... ...
∂fₘ/∂x₁ ... ∂fₘ/∂xₙ
计算 (y = [2x_1, 3x_2, 4x_3]) 对 (x = [x_1, x_2, x_3]) 的梯度(理论雅可比矩阵为对角矩阵 (diag(2, 3, 4)))。
python
import torch
from torch.autograd import functional
# 定义输入向量
x = torch.tensor([1.0, 2.0, 3.0])
# 定义函数:输入x(向量),输出y(向量)
def func(x):
return torch.tensor([2*x[0], 3*x[1], 4*x[2]])
# 计算雅可比矩阵:形状为 (y的长度, x的长度)
jacobian = functional.jacobian(func, x)
print("雅可比矩阵:\n", jacobian)
# 输出:
# tensor([[2., 0., 0.],
# [0., 3., 0.],
# [0., 0., 4.]])
矩阵对向量的梯度
计算 (y = W \cdot x)(W 为 3×2 矩阵,x 为 2 维向量)对 x 的梯度(理论梯度为 (W^T))。
python
import torch
from torch.autograd import functional
# 定义矩阵W和向量x
W = torch.tensor([[1.0, 2.0],
[3.0, 4.0],
[5.0, 6.0]]) # 3×2矩阵
x = torch.tensor([7.0, 8.0]) # 2维向量
# 定义函数:y = W与x的矩阵乘法(结果为3维向量)
def func(x):
return torch.matmul(W, x)
# 计算雅可比矩阵(y对x的梯度)
jacobian = functional.jacobian(func, x)
print("雅可比矩阵(y对x的梯度):\n", jacobian)
print("理论梯度(W的转置):\n", W.T) # 两者完全一致
三、梯度上下文控制
用于推理、参数冻结等场景,避免不必要的梯度计算,节省内存。
1. 控制梯度计算(torch.no_grad())
- 功能 :创建上下文环境,内部所有张量运算不跟踪梯度(
requires_grad
临时失效),适用于推理、验证阶段。 - 原理:禁用计算图构建,减少内存占用,加快前向计算。
python
import torch
x = torch.tensor([1.0, 2.0], requires_grad=True)
# 正常计算:跟踪梯度
y = x **2 # y.requires_grad = True
# 上下文内:不跟踪梯度
with torch.no_grad():
z = x** 2 # z.requires_grad = False
print(y.requires_grad) # True(上下文外正常跟踪)
print(z.requires_grad) # False(上下文内禁用)
2. 累计梯度
- 功能 :多次调用
backward()
时,梯度会自动累加(grad
属性累加新梯度),适用于 "大批次拆分小批次" 训练(如显存不足时)。 - 原理 :梯度本质是张量的
grad
属性,每次反向传播会将新梯度加到该属性上,而非覆盖。
python
import torch
x = torch.tensor([1.0], requires_grad=True)
# 第一次反向传播
y1 = x **2
y1.backward()
print(x.grad) # tensor([2.])(首次梯度)
# 第二次反向传播(未清零,梯度累加)
y2 = x** 2
y2.backward()
print(x.grad) # tensor([4.])(2+2,累计结果)
3. 梯度清零(zero_())
- 功能 :清除张量的
grad
属性值(置为 0),避免累计梯度干扰下一次计算,是训练中每次参数更新前的必要操作。 - 原理 :通过 in-place 操作将
grad
张量填充为 0,重置梯度状态。
py
import torch
x = torch.tensor([1.0], requires_grad=True)
# 第一次反向传播
y = x **2
y.backward()
print(x.grad) # tensor([2.])
# 清零梯度
x.grad.zero_()
print(x.grad) # tensor([0.])(已清零)
# 第二次反向传播(梯度重新计算,不累计)
y = x** 2
y.backward()
print(x.grad) # tensor([2.])(新梯度,未受之前影响)
【求函数最小值】
py
import torch
def gradient_descent():
x = torch.tensor(2.0,requires_grad=True,dtype=torch.float64)
# 迭代次数
epochs = 100
# 学习率
lr = 0.01
for epoch in range(epochs):
y = x ** 2
# 梯度清零
if x.grad is not None:
x.grad.zero_()
# 反向传播
y.backward()
# 修改x:不参与梯度运算
with torch.no_grad():
x -= lr * x.grad
print(f'epoch:{epoch},x:{x}')
if __name__ == '__main__':
gradient_descent()
【函数参数求解】
py
import torch
def test01():
# 定义x,y
x = torch.tensor([1,2,3],requires_grad=True,dtype=torch.float64)
y = torch.tensor([4,5,6],requires_grad=True,dtype=torch.float64)
# 定义模型参数
w = torch.tensor([1.0],requires_grad=True)
b = torch.tensor([1.0],requires_grad=True)
# 迭代次数
epochs = 100
# 学习率
lr = 0.01
for epoch in range(epochs):
# 前向传播:计算预测值
y_pred = w * x + b
# 损失函数
loss = ((y_pred - y)**2).mean()
# 梯度清零
if w.grad is not None and b.grad is not None:
w.grad.zero_()
b.grad.zero_()
# 反向传播
loss.backward()
# 修改参数
with torch.no_grad():
w -= lr * w.grad
b -= lr * b.grad
print(f'epoch:{epoch},w:{w},b:{b},loss:{loss}')
if __name__ == '__main__':
test01()