
场景:在训练神经网络时,常常遇到优化器不更新模型参数,尽管梯度计算正常且没有出现 NaN。最开始我们以为是数据问题或模型设计问题,但深入排查发现,问题其实出在梯度更新环节:比如没有清空梯度、模型参数没有正确传递到优化器、或优化器没有正确调用 step() 等。本文将通过复盘这一问题的根本原因,给出解决方案并提供可复现实验。
torch.no_grad() 或其他方式确保梯度计算正常,但在 optimizer.step() 之后模型的参数并未更新。None 或 0,无法成功反向传播。保存为 optimizer_update_bug.py,CPU 可直接运行。
# optimizer_update_bug.py
import torch
import torch.nn as nn
import torch.optim as optim
import torch.nn.functional as F
torch.manual_seed(0)
class SimpleMLP(nn.Module):
def __init__(self):
super(SimpleMLP, self).__init__()
self.fc1 = nn.Linear(28 * 28, 128)
self.fc2 = nn.Linear(128, 10)
def forward(self, x):
x = torch.flatten(x, 1)
x = F.relu(self.fc1(x))
x = self.fc2(x)
return x
def make_loader(n=1024, batch_size=64):
X = torch.randn(n, 28, 28)
y = torch.randint(0, 10, (n,))
dataset = torch.utils.data.TensorDataset(X, y)
return torch.utils.data.DataLoader(dataset, batch_size=batch_size, shuffle=True)
def train(model, dataloader, optimizer, epochs=10):
for epoch in range(epochs):
model.train()
total_loss = 0
correct = 0
total = 0
for data, target in dataloader:
optimizer.zero_grad() # 清空梯度
output = model(data) # 前向传播
loss = F.cross_entropy(output, target) # 计算损失
loss.backward() # 反向传播
optimizer.step() # 更新参数
total_loss += loss.item()
_, predicted = output.max(1)
correct += (predicted == target).sum().item()
total += target.size(0)
avg_loss = total_loss / len(dataloader)
accuracy = 100. * correct / total
print(f"Epoch {epoch+1}, Loss: {avg_loss:.4f}, Accuracy: {accuracy:.2f}%")
def experiment():
model = SimpleMLP()
optimizer = optim.SGD(model.parameters(), lr=0.1)
dataloader = make_loader()
# 错误的优化器参数更新
optimizer.zero_grad() # 此处应该被注释,避免清空梯度后未进行更新
for epoch in range(5):
train(model, dataloader, optimizer) # 训练 5 轮
print("Training completed.")
if __name__ == "__main__":
experiment()你会发现:
optimizer.zero_grad() 或把其放在错误的位置,模型的梯度没有被清空,因此训练过程中模型的梯度一直在累加,导致参数更新不正常。optimizer.step(),则会导致模型参数没有更新,训练看似进行了,但实际没有任何进展。optimizer.step() 前确保调用 optimizer.zero_grad(),否则,梯度将累积并可能导致不正确的参数更新。optimizer.zero_grad() # 确保每次开始计算新的梯度时,梯度清零backward() 之后调用 optimizer.step(),并确保 model.parameters() 被传递给优化器。optimizer.step() # 更新参数.grad 属性来确保每个参数都有梯度。for param in model.parameters():
if param.grad is None:
print(f"Parameter {param} has no gradient.")optimizer = optim.SGD(model.parameters(), lr=0.1) # 调整学习率optimizer.zero_grad() 是训练循环中唯一需要的梯度清空操作。for epoch in range(epochs):
model.train()
for data, target in dataloader:
optimizer.zero_grad() # 确保每次梯度计算前清空旧梯度
output = model(data)
loss = F.cross_entropy(output, target)
loss.backward()
optimizer.step() # 更新参数确保每次反向传播后,调用 optimizer.step() 来更新模型参数,并在每轮训练前调用 optimizer.zero_grad() 清空梯度。
# 修复训练过程中的梯度更新问题
optimizer.zero_grad() # 清空梯度
loss.backward() # 反向传播
optimizer.step() # 更新模型参数optimizer.zero_grad() 清空梯度。for param in model.parameters():
if param.grad is None:
print(f"Parameter {param} has no gradient.")for param_group in optimizer.param_groups:
print("Current learning rate:", param_group['lr'])import torch
print(f"Memory allocated: {torch.cuda.memory_allocated()} bytes")optimizer.zero_grad() 为什么必须在每次训练之前调用? optimizer.zero_grad() 用于清空上一次的梯度,否则在每次反向传播时,梯度将累积,导致参数更新不准确。optimizer.step() 什么时候应该调用? optimizer.step() 应该在每次 loss.backward() 之后调用,用来更新模型的参数。每次调用 step() 后,都需要清空梯度。loss.backward() 时,计算的梯度会被加到已有的梯度中。为了防止梯度的累积,需要调用 optimizer.zero_grad() 清空之前的梯度。optimizer.step() 会发生什么? 如果没有调用 optimizer.step(),即使梯度计算正常,模型的参数也不会更新,导致训练没有实际进展。None? 可以通过访问每个模型参数的 .grad 属性来检查是否计算了梯度。如果为 None,则说明该参数没有计算梯度。在神经网络训练过程中,正确的梯度更新步骤是训练是否有效的关键。通过确保每次反向传播后调用 optimizer.step() 来更新模型参数,并在每次开始新一轮时调用 optimizer.zero_grad() 来清空旧梯度,我们可以确保训练过程的正确性与稳定性。通过本文提供的可复现实验和修复模板,你可以轻松排查和解决训练过程中由于梯度更新不当导致的潜在问题,确保模型能够稳定收敛并实现最佳性能
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。