1. 动态计算图基础
PyTorch使用动态计算图(Dynamic Computation Graph),也称为"定义-by-run"的图构建方式。计算图在代码运行时动态构建,每次前向传播都会创建一个新的计算图,这使得PyTorch能够灵活处理可变长度的输入和动态控制流。
示例1:基础计算图构建
import torch
x = torch.tensor(3.0, requires_grad=True)
y = torch.tensor(4.0, requires_grad=True)
z = x**2 + y**3
z.backward()
print(x.grad) # dz/dx = 2x = 6.0
print(y.grad) # dz/dy = 3y² = 48.0
解释:
requires_grad=True 告诉PyTorch需要跟踪对该张量的所有操作,以便后续计算梯度
计算图是在执行操作时动态构建的,不是预先定义的
backward() 方法会从调用它的张量开始,沿着计算图反向传播梯度
对于标量输出(如这里的z),可以直接调用backward()。如果是向量输出,需要提供gradient参数
梯度计算结果会累积存储在.grad属性中,如果多次运行backward(),梯度会累加
每次重新计算前,通常需要手动清零梯度(使用x.grad.zero_()),否则梯度会不断累积
详细注释:
# 创建一个值为3.0的标量张量,并设置requires_grad=True以启用梯度跟踪
# PyTorch会记录所有对此张量的操作以便后续计算梯度
x = torch.tensor(3.0, requires_grad=True)
# 创建一个值为4.0的标量张量,同样启用梯度跟踪
# 这个张量也会被自动微分系统跟踪
y = torch.tensor(4.0, requires_grad=True)
# 定义一个计算操作:z = x的平方 + y的立方
# PyTorch会动态构建计算图,记录这个计算过程:
# 1. 计算x的平方 (x**2 → 9.0)
# 2. 计算y的立方 (y**3 → 64.0)
# 3. 将两个结果相加 (9.0 + 64.0 → 73.0)
# 计算图会保留这些操作的函数关系,以便后续梯度计算
z = x**2 + y**3
# 对z进行反向传播,自动计算所有需要梯度的变量的梯度
# PyTorch会沿着计算图反向传播:
# 1. 计算dz/dz = 1.0
# 2. 计算加法操作的梯度:两个分支都是1.0
# 3. 计算幂操作的梯度:
# - 对于x**2: dz/dx = 2x = 6.0
# - 对于y**3: dz/dy = 3y² = 48.0
# 计算得到的梯度会存储在各自张量的.grad属性中
z.backward()
# 打印x的梯度值
# 根据计算,dz/dx = 2x = 2*3 = 6.0
# 这个值存储在x.grad中
print(x.grad) # 输出: tensor(6.)
# 打印y的梯度值
# 根据计算,dz/dy = 3y² = 3*(4²) = 48.0
# 这个值存储在y.grad中
print(y.grad) # 输出: tensor(48.)
示例2:动态控制流
def dynamic_flow(x):
if x.sum() > 0:
return x * 2
else:
return x / 2
x = torch.randn(3, requires_grad=True)
y = dynamic_flow(x)
y.sum().backward()
print(x.grad) # 梯度会根据条件分支动态变化
解释:
动态图特性:PyTorch会在运行时根据实际数据决定计算路径,并记录对应的操作到计算图中
梯度计算:
如果走x*2分支,计算图记录的是乘法操作,导数为2
如果走x/2分支,计算图记录的是除法操作,导数为0.5
示例输出:
假设x = [0.5, -0.2, 1.0],sum=1.3 >0 → 走x*2分支 → 梯度为[2., 2., 2.]
假设x = [-1.0, -0.5, 0.3],sum=-1.2 <0 → 走x/2分支 → 梯度为[0.5, 0.5, 0.5]
详细注释:
# 定义一个带有条件分支的函数,PyTorch能够动态处理这种控制流
def dynamic_flow(x):
# 计算输入x所有元素的和,并根据结果决定执行哪个分支
if x.sum() > 0: # 如果x的元素和大于0
# 执行这个分支时,PyTorch会记录"乘以2"的操作到计算图中
return x * 2
else: # 如果x的元素和小于等于0
# 执行这个分支时,PyTorch会记录"除以2"的操作到计算图中
return x / 2
# 创建一个包含3个随机数的张量,并启用梯度跟踪
# randn生成的是标准正态分布随机数(均值为0,标准差为1)
x = torch.randn(3, requires_grad=True)
# 将x传入dynamic_flow函数,得到输出y
# 这个过程中会根据x的实际值动态选择计算路径
# PyTorch会记录实际执行的操作路径,构建对应的计算图
y = dynamic_flow(x)
# 对y的所有元素求和,然后进行反向传播
# sum()将向量转换为标量,这是backward()的要求(或提供gradient参数)
y.sum().backward()
# 打印x的梯度
# 梯度的具体值取决于之前执行的是哪个分支:
# 1. 如果执行了x*2分支,梯度将是2.0(对每个元素)
# 2. 如果执行了x/2分支,梯度将是0.5(对每个元素)
print(x.grad) # 梯度会根据条件分支动态变化
2. 自动微分(Autograd)
PyTorch的autograd系统自动计算导数。当设置requires_grad=True
时,PyTorch会跟踪所有对该张量的操作,并在反向传播时自动计算梯度。
示例1:简单自动微分
a = torch.tensor([2., 3.], requires_grad=True)
b = torch.tensor([6., 4.], requires_grad=True)
Q = 3*a**3 - b**2
external_grad = torch.tensor([1., 1.])
Q.backward(gradient=external_grad)
print(a.grad) # dQ/da = 9a² = [36., 81.]
print(b.grad) # dQ/db = -2b = [-12., -8.]
解释:
多元素张量的梯度计算:PyTorch会自动处理向量/矩阵的逐元素梯度计算
external_grad的作用:当输出是多维时,这个参数决定了各维度在梯度计算中的权重
链式法则应用:PyTorch自动应用链式法则计算复合函数的导数
梯度公式推导:
对于3a³,导数是9a²(因为3*3a²)
对于-b²,导数是-2b
详细注释:
# 创建一个浮点型张量[2.0, 3.0],并启用梯度计算
# requires_grad=True表示需要计算这个张量的梯度
a = torch.tensor([2., 3.], requires_grad=True)
# 创建另一个浮点型张量[6.0, 4.0],同样启用梯度计算
b = torch.tensor([6., 4.], requires_grad=True)
# 定义计算图的前向传播公式:Q = 3a³ - b²
# PyTorch会动态构建计算图,记录以下操作:
# 1. 对a逐元素立方:a³ → [8., 27.]
# 2. 乘以3:3a³ → [24., 81.]
# 3. 对b逐元素平方:b² → [36., 16.]
# 4. 最后相减:3a³ - b² → [-12., 65.]
Q = 3*a**3 - b**2
# 定义反向传播的"外部梯度",这里设为[1., 1.]表示对Q的两个输出分量都有相等的权重
# 在多输出情况下,这个参数决定了各输出分量在梯度计算中的权重
external_grad = torch.tensor([1., 1.])
# 执行反向传播,计算梯度