本系列文章是个人阅读PyTorch中文文档时对重点的总结与思考。
原文例子
可以使用requires_grad来控制是否使用梯度。
控制梯度
如果有一个单一的输入操作需要梯度,它的输出也需要梯度。相反,只有所有输入都不需要梯度,输出才不需要。如果其中所有的变量都不需要梯度进行,后向计算不会在子图中执行。
import torch
# 创建随机张量
x = torch.randn(5, 5) # 注意:原文档这里使用的是Variable现在已经不再使用
y = torch.randn(5, 5)
z = torch.randn(5, 5, requires_grad=True) # 指定z需要梯度
# 进行操作,a是x和y的和,不需要梯度
a = x + y
# 检查a是否需要梯度
print(a.requires_grad) # 输出: False
# 将a与需要梯度的z相加,结果b将需要梯度
b = a + z
# 检查b是否需要梯度
print(b.requires_grad) # 输出: True
冻结模型以微调
这个标志特别有用,当您想要冻结部分模型时,或者您事先知道不会使用某些参数的梯度。例如,如果要对预先训练的CNN进行优化,只要切换冻结模型中的requires_grad
标志就足够了,直到计算到最后一层才会保存中间缓冲区,其中的仿射变换将使用需要梯度的权重并且网络的输出也将需要它们。
model = torchvision.models.resnet18(pretrained=True)
for param in model.parameters():
param.requires_grad = False
# Replace the last fully-connected layer
# Parameters of newly constructed modules have requires_grad=True by default
model.fc = nn.Linear(512, 100)
# model.fc通常指的是模型中最后一层全连接层(也称为线性层)
# Optimize only the classifier
optimizer = optim.SGD(model.fc.parameters(), lr=1e-2, momentum=0.9)
-
model = torchvision.models.resnet18(pretrained=True)
:这行代码加载了一个预训练的ResNet-18模型。参数pretrained=True
表示使用在ImageNet数据集上预训练好的权重。 -
for param in model.parameters(): param.requires_grad = False
:这个循环遍历模型的所有参数,并将requires_grad
属性设置为False
。这意味着在反向传播过程中,这些参数的梯度将不会被计算,因此它们在训练过程中不会更新。 -
model.fc = nn.Linear(512, 100)
:这行代码替换了ResNet-18模型最后的全连接层。原始的全连接层通常用于分类1000个类别(ImageNet数据集)。这里,你创建了一个新的全连接层nn.Linear(512, 100)
,其中512是输入特征的数量(ResNet-18最后一个卷积层的输出特征数量),100是新的目标类别数量。 -
optimizer = optim.SGD(model.fc.parameters(), lr=1e-2, momentum=0.9)
:这行代码创建了一个随机梯度下降(SGD)优化器,只针对新替换的全连接层的参数进行优化。学习率设置为1e-2
,动量设置为0.9
。
通过这种方式,你可以在保持原始模型权重不变的情况下,只训练新添加的全连接层,以便将模型适应到一个新的、类别更少的数据集上。这种技术通常称为"微调"(fine-tuning),是一种节省计算资源和训练时间的有效方法。
volatile
在PyTorch早期版本中,volatile
是一个上下文管理器,用于指示PyTorch在该上下文中运行的代码不需要梯度计算。这通常用于推理模式,即当模型已经训练完成,你只需要使用模型进行预测而不需要进行梯度计算以更新模型的权重。
使用volatile
可以减少内存消耗,因为不需要存储梯度信息。然而,自PyTorch 0.2版本以后,volatile
已经被弃用,取而代之的是使用torch.no_grad()
上下文管理器来实现相同的功能。
相关话题
model.parameters()
在PyTorch中,model.parameters()
是一个返回模型中所有参数的迭代器。这些参数包括模型的权重(weights)和偏置(biases),它们是模型在训练过程中需要学习的变量。
当你调用model.parameters()
时,你得到的是一个生成器,它遍历模型中所有的参数。这些参数是torch.Tensor
对象,并且通常具有requires_grad=True
属性,这意味着它们的值在反向传播时会自动计算梯度,以便进行梯度下降优化。
例如,如果你有一个模型实例model
,你可以使用以下方式遍历所有参数:
for param in model.parameters():
print(param)
这将打印出模型中每个参数的详细信息,包括它们的大小、是否需要梯度等。
在某些情况下,你可能只对模型的特定部分的参数感兴趣,例如只关注卷积层的参数或者只关注全连接层的参数。PyTorch允许你通过指定模块名称来过滤参数。例如:
for param in model.features.parameters():
print(param)
在这个例子中,model.features
可能是指模型中的卷积层部分,parameters()
将只返回这部分的参数。
此外,如果你之前通过代码修改了某些参数的requires_grad
属性,使用model.parameters()
仍然会返回所有参数,但它们的requires_grad
状态可能会有所不同。这在微调预训练模型时非常有用,因为你可以冻结(不需要梯度)某些层的参数,只训练模型的特定部分。
从后向中排除子图
在深度学习中,"从后向中排除子图"通常指的是在反向传播过程中故意忽略某些部分的梯度计算。这可以通过多种方式实现,例如使用PyTorch中的torch.no_grad()
上下文管理器或者requires_grad
属性。
以下是一些常见的方法来从反向传播中排除子图:
1.使用torch.no_grad()
上下文管理器:
当你在torch.no_grad()
上下文管理器中执行操作时,PyTorch会停止计算梯度。这意味着在这个上下文中创建的张量或进行的操作都不会跟踪梯度。
with torch.no_grad():
# 在这个块中的所有操作都不会跟踪梯度
subgraph_output = some_operation(input_tensor)
2.设置requires_grad=False
:
你可以显式地设置张量的requires_grad
属性为False
,这样即使在梯度计算被启用的情况下,这些张量也不会参与梯度的计算。
input_tensor.requires_grad = False
output = some_operation(input_tensor)
3.使用detach()
方法:
使用detach()
可以从当前的计算图中分离出一个张量,使其不再依赖于原始的计算图。这意味着这个张量及其后续操作不会影响原始图的梯度计算。
detached_tensor = input_tensor.detach()
output = some_operation(detached_tensor)
4.使用with torch.enable_grad()
和with torch.no_grad()
:
这两个上下文管理器分别用于启用和禁用梯度计算。你可以嵌套使用它们来精确控制哪些部分的计算需要梯度。
with torch.enable_grad():
# 启用梯度计算
main_output = model(input_tensor)
with torch.no_grad():
# 禁用梯度计算
subgraph_output = some_subgraph(input_tensor)
使用上述方法,你可以灵活地控制反向传播过程中哪些部分的计算需要被考虑,哪些可以被排除,从而优化内存使用、计算效率或实现特定的训练策略。
根据场景打开或关闭梯度
关闭梯度跟踪和打开梯度跟踪的使用场景主要取决于你是在进行模型的训练还是进行模型的推理(预测)。以下是一些具体的使用场景:
需要打开梯度跟踪的场景(训练阶段):
-
模型训练 :在模型训练过程中,需要计算损失函数关于模型参数的梯度,以便通过反向传播更新参数。此时,所有模型参数的
requires_grad
属性应该设置为True
。 -
梯度更新 :在优化器(如SGD、Adam等)的
step()
方法调用之前,需要确保梯度已经计算完成,以便正确更新模型参数。 -
梯度累积:在某些训练策略中,如累积梯度更新,可能需要在多个小批量数据上累积梯度,然后再执行一次更新。这种情况下,梯度计算是必需的。
-
自定义梯度计算:如果你需要实现自定义的梯度计算或者使用梯度裁剪等技术,梯度跟踪是必要的。
需要关闭梯度跟踪的场景(推理阶段或其他):
-
模型预测 :在模型推理阶段,你只需要使用训练好的模型进行前向传播,不需要进行梯度计算。此时,可以使用
torch.no_grad()
上下文管理器来禁用梯度计算。 -
评估模型:在评估模型性能时(如计算验证集上的准确率),通常不需要梯度信息,因此可以关闭梯度跟踪以节省内存和计算资源。
-
冻结模型部分 :在微调或迁移学习中,可能需要冻结模型的某些部分,只训练新添加的层或最后几层。这时,可以将这些层之外的参数的
requires_grad
属性设置为False
。 -
内存优化:在处理非常大的模型或数据时,梯度跟踪可能会消耗大量内存。在这种情况下,如果某些操作不需要梯度,可以关闭梯度跟踪以减少内存使用。
-
计算图优化:在某些情况下,为了优化计算图的性能,可能需要在特定的操作或子图中禁用梯度计算。
-
使用预训练模型:当你使用预训练模型进行特定任务的预测或特征提取时,通常不需要梯度信息。
示例代码:
# 训练时
model.train() # 设置模型为训练模式
optimizer.zero_grad() # 清空梯度
outputs = model(inputs) # 前向传播
loss = loss_fn(outputs, targets) # 计算损失
loss.backward() # 反向传播,计算梯度
optimizer.step() # 更新模型参数
# 预测时
model.eval() # 设置模型为评估模式
with torch.no_grad(): # 关闭梯度跟踪
predictions = model(inputs) # 前向传播,进行预测