人工智能-python-深度学习-自动微分
自动微分:基础概念与应用
自动微分(Autograd)是现代深度学习框架(如PyTorch、TensorFlow)中的一个核心功能。它通过构建计算图并在计算图上自动计算梯度,简化了反向传播算法的实现。以下是自动微分的基本概念及其操作。
1. 基础概念
自动微分指的是通过跟踪计算图中的每一步计算,自动计算目标函数相对于模型参数的梯度。这些计算图是在每次前向传播时动态构建的。基于这个图,系统可以在反向传播时自动计算梯度,而不需要手动推导每个梯度。
1.1 张量
torch中一切皆为张量,属性requires_grad决定是否对其进行梯度计算。默认是False,如需计算梯度则设置为True
1.2 计算图
torch.autograd通过创建一个动态计算图来跟踪张良的操作,每个张量是计算图中的一个节点,节点之间的操作构成图的边。
在Pytorch中,当张量的requiers_grad=Ture时,Pytorch会自动跟踪与该张量相关的所有操作,并构建计算图。每个操作都会生成一个新的张量,并记录其依赖关系。当设置为True时,表示该张量在计算图中需要参与梯度计算,即在反向传播(Backpropagation)过程中惠子dog计算其梯度;当设置为False时,不会计算梯度。
例如:
z=x∗yloss=z.sum()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||vloss
叶子节点:
在 PyTorch 的自动微分机制中,叶子节点(leaf node) 是计算图中:
- 由用户直接创建的张量,并且它的 requires_grad=True。
- 这些张量是计算图的起始点,通常作为模型参数或输入变量。
特征:
- 没有由其他张量通过操作生成。
- 如果参与了计算,其梯度会存储在 leaf_tensor.grad 中。
- 默认情况下,叶子节点的梯度不会自动清零,需要显式调用 optimizer.zero_grad() 或 x.grad.zero_() 清除。
如何判断一个张量是否是叶子节点?
通过 tensor.is_leaf 属性,可以判断一个张量是否是叶子节点。
x = torch.tensor([1.0, 2.0, 3.0], requires_grad=True) # 叶子节点
y = x ** 2 # 非叶子节点(通过计算生成)
z = y.sum()print(x.is_leaf) # True
print(y.is_leaf) # False
print(z.is_leaf) # False
叶子节点与非叶子节点的区别
特性 | 叶子节点 | 非叶子节点 |
---|---|---|
创建方式 | 用户直接创建的张量 | 通过其他张量的运算生成 |
is_leaf 属性 | True | False |
梯度存储 | 梯度存储在 .grad 属性中 | 梯度不会存储在 .grad,只能通过反向传播传递 |
是否参与计算图 | 是计算图的起点 | 是计算图的中间或终点 |
删除条件 | 默认不会被删除 | 在反向传播后,默认被释放(除非 retain_graph=True) |
detach():张量 x 从计算图中分离出来,返回一个新的张量,与 x 共享数据,但不包含计算图(即不会追踪梯度)。
特点:
- 返回的张量是一个新的张量,与原始张量共享数据。
- 对 x.detach() 的操作不会影响原始张量的梯度计算。
- 推荐使用 detach(),因为它更安全,且在未来版本的 PyTorch 中可能会取代 data。
x = torch.tensor([1.0, 2.0, 3.0], requires_grad=True)
y = x.detach() # y 是一个新张量,不追踪梯度y += 1 # 修改 y 不会影响 x 的梯度计算
print(x) # tensor([1., 2., 3.], requires_grad=True)
print(y) # tensor([2., 3., 4.])
反向传播
使用tensor.backward()方法执行反向传播,从而计算张量的梯度。这个过程会自动计算每个张量对损失函数的梯度。例如:调用 loss.backward() 从输出节点 loss 开始,沿着计算图反向传播,计算每个节点的梯度。
梯度
计算得到的梯度通过tensor.grad访问,这些梯度用于优化模型参数,以最小化损失函数。
2. 计算梯度
2.1 标量梯度计算
标量梯度计算指的是计算标量(通常是损失函数)相对于模型参数的梯度。在深度学习中,常见的损失函数(如均方误差、交叉熵等)都是标量值。
import torch# 定义张量
x = torch.tensor(2.0, requires_grad=True)
y = x**2 + 3*x + 1 # 定义标量函数# 计算梯度
y.backward() # 反向传播
print(x.grad) # 输出x的梯度
为何需要标量梯度?
- 在训练过程中,我们需要计算损失函数相对于各个参数的梯度,从而调整模型参数。标量梯度的计算是整个训练过程中优化模型的基础。
2.2 向量梯度计算
向量梯度计算用于计算多维向量函数相对于输入向量的梯度。例如,输出是一个向量时,我们希望计算每个分量的梯度。
# 定义张量
x = torch.tensor([2.0, 3.0], requires_grad=True)
y = x**2 # 计算每个元素的平方# 计算梯度
y.backward(torch.tensor([1.0, 1.0])) # 向量梯度计算
print(x.grad) # 输出x的梯度
为何需要向量梯度?
- 在多输入多输出的情况下,向量梯度计算能有效地描述每个输入对于输出的影响。
2.3 多标量梯度计算
在一些复杂的场景中,损失函数可能有多个标量输出。我们需要计算每个标量输出对参数的梯度。
x = torch.tensor([2.0, 3.0], requires_grad=True)
y1 = x[0]**2 + 3*x[0] + 1
y2 = x[1]**3 + 2*x[1] - 5
y = y1 + y2 # 多标量函数y.backward() # 计算梯度
print(x.grad)
为何需要多标量梯度?
- 多标量梯度有助于处理多任务学习中的梯度计算,特别是当每个任务有不同的损失函数时。
2.4 多向量梯度计算
当输出是多个向量时,我们通常需要计算每个向量对每个输入的梯度。比如在生成对抗网络(GAN)或多任务学习中,常见这种情况。
x = torch.tensor([1.0, 2.0], requires_grad=True)
y1 = x[0]**2 + 3*x[0]
y2 = x[1]**3 + 2*x[1]grad_outputs = torch.tensor([1.0, 1.0]) # 指定多个输出梯度
y1.backward(grad_outputs) # 分别计算y1和y2的梯度
为何需要多向量梯度?
- 计算多个输出的梯度可以帮助我们进行多维度的优化,尤其是在复杂的网络结构中,多个输出有助于提高模型的多样性和鲁棒性。
3. 梯度上下文控制
在深度学习中,常常需要控制梯度计算的上下文,以节省内存或者针对性地优化某些参数。
3.1 控制梯度计算
我们可以通过torch.no_grad()
或with torch.set_grad_enabled(False)
来临时停止梯度计算,这对于不需要计算梯度的操作(例如推理阶段)非常有用。
with torch.no_grad():y = x * 2 # 在此块中,不会计算梯度
为何控制梯度计算?
- 在推理阶段,我们不需要梯度,这样可以节省计算资源和内存。
3.2 累计梯度
在某些情况下,梯度计算需要分多个小批次进行累计。例如,使用小批次训练时,梯度会在每个小批次上累加。
optimizer.zero_grad() # 清空之前的梯度
y.backward() # 累计梯度
optimizer.step() # 更新参数
为何累计梯度?
- 累计梯度可以使得模型在小批次上进行优化,而不丢失总体梯度信息,适用于大规模数据的训练。
3.3 梯度清零
在每次更新前,我们需要清空之前计算的梯度,否则它会在下一步的计算中累加。
optimizer.zero_grad() # 清除上次计算的梯度
为何清零梯度?
- 防止梯度计算的累积影响下一次计算,确保每次计算梯度时的准确性。
4. 案例分析
4.1 求函数最小值
通过计算梯度并使用优化算法(如梯度下降),我们可以找到函数的最小值。
x = torch.tensor(2.0, requires_grad=True)
for _ in range(100):y = x**2 + 3*x + 1y.backward()with torch.no_grad():x -= 0.1 * x.grad # 使用梯度更新xx.grad.zero_() # 清空梯度
为何使用梯度下降求解最小值?
- 通过不断调整参数,沿着梯度方向前进,直到收敛到函数的最小值。
4.2 函数参数求解
如果已知函数并希望通过梯度来求解某些未知参数,可以使用反向传播来更新这些参数。
def func(x):return x**2 - 4*x + 3x = torch.tensor(3.0, requires_grad=True)
for i in range(100):y = func(x)y.backward()x.data -= 0.1 * x.gradx.grad.zero_()
为何求解函数参数?
- 在机器学习中,模型的参数通过梯度计算来优化,进而提高模型的性能。
结论
自动微分的引入让深度学习框架大大简化了梯度计算过程。通过自动计算标量、向量梯度以及控制梯度的计算上下文,开发者可以专注于模型设计而非手动推导梯度公式。