PyTorch ResNet图像分割训练损失为零的问题排查
嘿,我仔细看了你的问题和代码,发现几个关键错误直接导致了“损失全程为0但准确率虚高到0.99”的诡异情况,咱们一步步来搞定它:
核心错误分析
1. 标签(y)的错误压缩
你代码里写了y = y[:, 0,0],这直接把原本形状为[Nbatch, H, W]的像素级二值掩码,硬改成了每个batch只取一个单点的标签。CrossEntropyLoss在这种维度不匹配的情况下,要么计算异常,要么如果这个单点刚好是模型总能“蒙对”的类别(比如占比极高的背景类),损失就会一直保持为0。
2. 准确率计算完全脱离模型预测
你计算预测结果时写的是_, preds = torch.max(X, 1)——这里的X是输入的RGB图像啊!不是模型输出的prediction!这就导致你统计的“准确率”完全和模型性能无关,只是基于图像通道的最大值随机生成的数值,所以才会出现离谱的0.99准确率。
3. 模型输出不符合分割任务要求
ResNet默认是为图像分类设计的,输出是[N, num_classes]的全局分类结果,但图像分割需要输出和输入同尺寸的[N, num_classes, H, W]特征图(每个像素对应类别概率)。如果你的模型没做适配(比如没加上采样/转卷积层),那模型输出和标签维度完全不匹配,损失计算必然出问题。
4. 损失计算的细节漏洞
你用了reduce=False得到每个像素的损失,但后续计算running_loss += loss.data[0]只取了第一个元素,再结合前面的标签错误,这个损失值根本不能反映真实训练状态。
具体修复步骤
第一步:修正标签与预测结果的处理
把这几行错误代码彻底替换:
# 删掉这行致命的标签压缩代码:y = y[:, 0,0] # 从模型输出prediction中取类别,而不是输入图像X _, preds = torch.max(prediction, 1) # 得到[N, H, W]的像素级预测标签
第二步:把ResNet改成适配分割的模型
直接用原生ResNet做分割是行不通的,需要把它改成“编码器+解码器”的结构,比如:
import torchvision.models as models from torch import nn class ResNetSeg(nn.Module): def __init__(self, num_classes=2): super().__init__() # 加载预训练ResNet,去掉最后两层(全局池化+全连接) self.encoder = nn.Sequential(*list(models.resnet50(pretrained=True).children())[:-2]) # 添加解码器,通过转卷积把特征图上采样到输入尺寸 self.decoder = nn.Sequential( nn.ConvTranspose2d(2048, 1024, kernel_size=2, stride=2), nn.ReLU(inplace=True), nn.ConvTranspose2d(1024, 512, kernel_size=2, stride=2), nn.ReLU(inplace=True), nn.ConvTranspose2d(512, 256, kernel_size=2, stride=2), nn.ReLU(inplace=True), nn.ConvTranspose2d(256, 128, kernel_size=2, stride=2), nn.ReLU(inplace=True), nn.ConvTranspose2d(128, 64, kernel_size=2, stride=2), nn.ReLU(inplace=True), # 最后输出2个通道对应两类目标 nn.Conv2d(64, num_classes, kernel_size=1) ) def forward(self, x): x = self.encoder(x) x = self.decoder(x) return x
第三步:修正损失与准确率的计算逻辑
因为是像素级分割,要统计所有像素的正确数,而不是单个batch的单点值:
# 损失计算:CrossEntropyLoss会自动匹配[N, 2, H, W]的预测和[N, H, W]的标签 loss = criterion(prediction, y) loss_mean = loss.mean() # 取整个batch的平均损失 # 统计所有像素中预测正确的数量 running_corrects += torch.sum(preds == y.data) # 总像素数 = 数据集样本数 × 图像高 × 图像宽 total_pixels = len(dataLoader[phase].dataset) * patch_size * patch_size epoch_acc = running_corrects.double() / total_pixels
第四步:检查类别权重与损失函数设置
你的类别权重计算逻辑是对的(平衡类别不平衡),但要确保dataset["train"].numpixels是正确的两类像素数量统计。如果没有需要忽略的类别,建议去掉ignore_index参数,避免不必要的干扰。
额外调试建议
- 训练前先打印
prediction、y的形状,确保prediction是[N, 2, patch_size, patch_size],和y的[N, patch_size, patch_size]完全匹配。 - 先跑1-2个小batch,手动检查损失值、预测结果是否合理,比如损失是否有变化,预测结果是否和标签有相关性。
内容的提问来源于stack exchange,提问作者Sarah

