PyTorch迁移学习遇RuntimeError:张量无梯度属性问题排查
问题描述
在PyTorch中进行ResNet18迁移学习时,将主干网络所有参数的requires_grad设为False,仅启用全连接层的梯度计算,但执行loss.backward()时触发以下错误:
RuntimeError: element 0 of tensors does not require grad and does not have a grad_fn
用户怀疑问题与GPU访问有关,相关代码及报错堆栈如下:
代码片段
import torchvision.models as models model_ft = models.resnet18(weights='IMAGENET1K_V1') for param in model_ft.parameters(): param.requires_grad = False for param in model_ft.fc.parameters(): param.requires_grad = True num_ftrs = model_ft.fc.in_features fc_layer = nn.Linear(in_features=num_ftrs, out_features=1) model_ft.fc = fc_layer model_ft = model_ft.to(device) criterion = nn.MSELoss() optimizer_ft = optim.Adam(model_ft.parameters(), lr=0.001) exp_lr_scheduler = lr_scheduler.StepLR(optimizer_ft, step_size=7, gamma=0.1) model_ft = simpleTrainingLoop(model_ft, train_data_loader, criterion, optimizer_ft, exp_lr_scheduler) def simpleTrainingLoop(model, dataloader_train, criterion, optimizer, scheduler, num_epoch = 1): since = time.time() for e in range(num_epoch): running_loss = 0 for inputs, labels in dataloader_train: inputs = inputs.to(device) labels = labels.to(device) optimizer.zero_grad() outputs = model(inputs) _,preds = torch.max(outputs,1) loss = criterion(preds,labels) # if we can know which layer has the problem will be better loss.backward() optimizer.step() running_loss += loss.item() * inputs.size(0) scheduler.step() train_loss = running_loss/train_size print(f' training loss: {train_loss}') time_elapsed = time.time() - since print(f'training complete in {time_elapsed // 60:.0f}m {time_elapsed % 60:.0f}s')
报错堆栈
c:\Users\LangZheZR\Desktop\projects\eye_on_ground_challenge\test_CNN_image_tabular_concat.ipynb Cell 11 line 2 21 optimizer_ft = optim.Adam(model_ft.parameters(), lr=0.001) 22 exp_lr_scheduler = lr_scheduler.StepLR(optimizer_ft, step_size=7, gamma=0.1) ---> 23 model_ft = simpleTrainingLoop(model_ft, 24 train_data_loader, 25 criterion, 26 optimizer_ft, 27 exp_lr_scheduler) c:\Users\LangZheZR\Desktop\projects\eye_on_ground_challenge\test_CNN_image_tabular_concat.ipynb Cell 11 line 2 18 loss = criterion(preds,labels) 19 # if we can know which layer has the problem will be better ---> 20 loss.backward() 21 optimizer.step() 22 running_loss += loss.item() * inputs.size(0) File c:\Python311\Lib\site-packages\torch\_tensor.py:492, in Tensor.backward(self, gradient, retain_graph, create_graph, inputs) 482 if has_torch_function_unary(self): 483 return handle_torch_function( 484 Tensor.backward, 485 (self,), (...) 490 inputs=inputs, ... 257 allow_unreachable=True, 258 accumulate_grad=True, 259 ) RuntimeError: element 0 of tensors does not require grad and does not have a grad_fn
问题原因
该错误与GPU访问无关,核心问题出在训练循环的逻辑上:
torch.max()的错误使用:torch.max(outputs,1)返回的preds是argmax运算的结果,属于不可逆操作,生成的张量没有梯度信息(无grad_fn)。用这个无梯度张量计算损失后,反向传播时找不到梯度回传的路径,直接触发报错。- 冗余的参数设置:先将原全连接层的
requires_grad设为True,之后直接替换了整个全连接层,新层默认requires_grad=True,之前的设置完全多余,但不影响功能。 - 优化器参数冗余:传入了所有模型参数,但主干网络参数
requires_grad=False会被PyTorch自动忽略,虽不报错但效率较低。
修复方案
1. 核心修复:移除torch.max()的错误使用
直接用模型输出outputs计算损失,不要用argmax后的结果。如果是回归任务(使用MSELoss),需注意维度匹配:
# 替换训练循环中的对应行 outputs = model(inputs) # 去掉torch.max,直接用outputs计算损失,squeeze消除多余维度 loss = criterion(outputs.squeeze(), labels)
2. 规范模型初始化流程(可选)
先冻结主干网络,再替换全连接层,避免冗余操作:
model_ft = models.resnet18(weights='IMAGENET1K_V1') # 先冻结所有主干参数 for param in model_ft.parameters(): param.requires_grad = False # 替换全连接层,新层默认requires_grad=True num_ftrs = model_ft.fc.in_features model_ft.fc = nn.Linear(num_ftrs, 1)
3. 优化器只传入需要更新的参数(可选)
仅将全连接层参数传给优化器,提升训练效率:
optimizer_ft = optim.Adam(model_ft.fc.parameters(), lr=0.001)
修复后的完整代码
import torch import torch.nn as nn import torch.optim as optim from torchvision import models from torch.optim import lr_scheduler import time # 初始化模型并修改全连接层 model_ft = models.resnet18(weights='IMAGENET1K_V1') # 冻结主干网络所有参数 for param in model_ft.parameters(): param.requires_grad = False # 替换全连接层(新层默认requires_grad=True) num_ftrs = model_ft.fc.in_features model_ft.fc = nn.Linear(num_ftrs, 1) model_ft = model_ft.to(device) criterion = nn.MSELoss() # 仅传入全连接层参数给优化器 optimizer_ft = optim.Adam(model_ft.fc.parameters(), lr=0.001) exp_lr_scheduler = lr_scheduler.StepLR(optimizer_ft, step_size=7, gamma=0.1) def simpleTrainingLoop(model, dataloader_train, criterion, optimizer, scheduler, num_epoch = 1): since = time.time() # 获取训练集总大小 train_size = len(dataloader_train.dataset) for e in range(num_epoch): running_loss = 0 # 显式设置模型为训练模式 model.train() for inputs, labels in dataloader_train: inputs = inputs.to(device) labels = labels.to(device) optimizer.zero_grad() outputs = model(inputs) # 直接用模型输出计算损失,匹配维度 loss = criterion(outputs.squeeze(), labels) loss.backward() optimizer.step() running_loss += loss.item() * inputs.size(0) scheduler.step() train_loss = running_loss/train_size print(f'训练损失: {train_loss}') time_elapsed = time.time() - since print(f'训练完成,耗时 {time_elapsed // 60:.0f}m {time_elapsed % 60:.0f}s') return model model_ft = simpleTrainingLoop(model_ft, train_data_loader, criterion, optimizer_ft, exp_lr_scheduler)
额外说明
- 如果是分类任务,需将全连接层输出维度改为对应类别数,损失函数换成
nn.CrossEntropyLoss(),此时无需对outputs做squeeze,CrossEntropyLoss可直接接受模型输出和标签。 - 训练时显式设置
model.train()更稳妥,确保模型处于训练模式(比如启用Dropout、BatchNorm的训练行为)。
内容的提问来源于stack exchange,提问作者Xiaofan Lu
相关产品推荐
相关产品推荐

