You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

MLP训练中权重未更新但损失下降问题排查求助

MLP训练中损失稳步下降但模型权重完全未更新,原因是什么?

我正在训练一个处理64*64尺寸图像的MLP,采用MSELoss(即$|\text{output} - \text{input}|^2$)作为损失函数。训练过程中出现异常:损失稳步下降,可每轮的模型权重并未更新。

相关代码

模型定义:

class MLP(nn.Module):
    def __init__(self, size_list):
        super(MLP, self).__init__()
        layers = []
        self.size_list = size_list
        for i in range(len(size_list) - 2):
            layers.append(nn.Linear(size_list[i],size_list[i+1]))
            layers.append(nn.ReLU())
        layers.append(nn.Linear(size_list[-2], size_list[-1]))
        self.net = nn.Sequential(*layers)
    def forward(self, x):
        return self.net(x)
model_1 = MLP([4096, 64, 4096])

训练每轮的函数:

def train_epoch(model, train_loader, criterion, optimizer):
    model.train()
    model.to(device)
    running_loss = 0.0
    start_time = time.time()
    # train batch
    for batch_idx, (data) in enumerate(train_loader):
        optimizer.zero_grad()
        data = data.to(device)
        outputs = model(data)
        loss = criterion(outputs, data)
        running_loss += loss.item()
        loss.backward()
        optimizer.step()
    end_time = time.time()
    weight_ll = model.net[0].weight
    running_loss /= len(train_loader)
    print('Training Loss: ', running_loss, 'Time: ',end_time - start_time, 's')
    return running_loss, outputs, weight_ll

训练流程:

n_epochs = 20
Train_loss = []
weights=[]
criterion = nn.MSELoss()
optimizer = optim.SGD(model_1.parameters(), lr = 0.1)
for i in range(n_epochs):
    train_loss, output, weights_ll = train_epoch(model_1, trainloader, criterion, optimizer)
    Train_loss.append(train_loss)
    weights.append(weights_ll)
    print('='*20)

异常现象

我打印第0轮和第19轮第一层全连接层的权重,发现完全一致:

tensor([ 0.0086, 0.0069, -0.0048, ..., -0.0082, -0.0115, -0.0133], grad_fn=<SelectBackward>)
tensor([ 0.0086, 0.0069, -0.0048, ..., -0.0082, -0.0115, -0.0133], grad_fn=<SelectBackward>)

请问这可能是什么原因导致的?


问题排查与解决

这问题我之前也碰到过几次,核心原因大概率是模型设备迁移的逻辑错误,咱们一步步拆解:

1. 致命问题:每轮训练都重复移动模型到设备,导致optimizer绑定的参数失效

你在train_epoch函数里每次都调用model.to(device),这会导致一个很隐蔽的问题:

  • PyTorch中,model.to(device)会返回一个新的模型实例(如果原模型在CPU,现在要移到GPU,就会创建参数的GPU副本)
  • 你的optimizer是在训练前绑定到初始model_1的参数上的,但每轮训练时,实际训练的是model.to(device)生成的新模型,优化器更新的是这个新模型的参数,而你保存和打印的是原始model_1的参数——自然看不到任何变化!
  • 至于损失下降,是因为每轮训练的新模型确实在学习,但训练结束后这个临时模型就被丢弃了,原始模型完全没被更新。

解决办法

把模型移到设备的操作只执行一次,放在训练循环外面:

# 训练前先把模型移到目标设备,只做一次
model_1.to(device)

n_epochs = 20
Train_loss = []
weights=[]
criterion = nn.MSELoss()
optimizer = optim.SGD(model_1.parameters(), lr = 0.1)
for i in range(n_epochs):
    train_loss, output, weights_ll = train_epoch(model_1, trainloader, criterion, optimizer)
    Train_loss.append(train_loss)
    weights.append(weights_ll)
    print('='*20)

然后修改train_epoch函数,删掉model.to(device)这一行:

def train_epoch(model, train_loader, criterion, optimizer):
    model.train()
    # 去掉这一行:model.to(device)
    running_loss = 0.0
    start_time = time.time()
    # train batch
    for batch_idx, (data) in enumerate(train_loader):
        optimizer.zero_grad()
        data = data.to(device)
        outputs = model(data)
        loss = criterion(outputs, data)
        running_loss += loss.item()
        loss.backward()
        optimizer.step()
    end_time = time.time()
    weight_ll = model.net[0].weight
    running_loss /= len(train_loader)
    print('Training Loss: ', running_loss, 'Time: ',end_time - start_time, 's')
    return running_loss, outputs, weight_ll

2. 次要问题:权重保存的方式可能导致观察误差

你现在用weights.append(weights_ll)保存的是张量的引用,而不是当前权重的副本。后续权重更新时,之前保存的引用会指向最新的权重值(不过在你的问题里因为第一个原因,这个没体现出来)。建议保存时生成副本,避免混淆:

# 保存权重时 detach 并克隆,保留当前状态
weights.append(weights_ll.detach().clone())

打印权重时,也建议去掉梯度信息,方便对比:

print(weights_ll.detach().cpu().numpy())

3. 额外验证:学习率与优化器的合理性

你的学习率设为0.1,用SGD优化器,对于MSE损失的自编码器任务来说是合理的,但如果解决第一个问题后还是权重不更新,可以尝试:

  • 调大学习率(比如0.5),或者换用Adam优化器(自适应学习率,更容易收敛)
  • 检查数据是否被正确归一化(比如像素值是否在0-1或-1到1之间,避免梯度消失)

内容的提问来源于stack exchange,提问作者lakshjaisinghani

火山引擎 最新活动