PyTorch训练时验证损失低于训练损失,代码实现是否存在问题?
问题排查与解答
核心问题定位
你怀疑的val_loss缩进本身没有语法错误,但是你的训练、验证损失统计逻辑,以及验证阶段的模型配置存在明显缺陷,是导致你观察到验证损失持续低于训练损失的核心原因。
1 现有代码的两处错误
1.1 损失统计逻辑错误
- 训练损失:你当前存入
train_losses的是每个epoch最后一个训练批次的损失,不是整个epoch所有训练批次的平均损失。训练过程中每个batch都会更新模型参数,越靠后的batch模型效果越好、损失越低,仅用最后一个batch的损失无法代表整个epoch的平均训练水平。 - 验证损失:同理,你存入
val_losses的是验证集最后一个批次的损失,不是整个验证集的平均损失。如果验证集最后一个批次刚好是难度较低的样本,计算出来的损失自然会低于训练集最后一个批次的损失,无法反映真实的泛化表现。
1.2 验证阶段未切换模型模式
你在验证前没有调用model.eval(),如果你的模型里有Dropout、BatchNorm等训练和推理阶段行为不同的层,会导致验证阶段的计算逻辑和训练阶段不一致,也会造成损失偏差。
2 验证损失低于训练损失的正常场景说明
排除代码错误的前提下,验证损失低于训练损失也属于正常情况,常见触发原因包括:
- 正则项(如Dropout、L2正则)只在训练阶段生效,验证阶段关闭后模型表现更好
- 验证集样本难度整体低于训练集
- 训练集和验证集划分时存在分布偏移,验证集特征更简单
3 修复后的代码参考
def train_model(model, train_loader,val_loader,lr): "Model training" epochs=100 criterion = nn.MSELoss() optimizer = torch.optim.Adam(model.parameters(), lr=lr, weight_decay=1e-5) # Reduce learning rate if no improvement is observed after 10 Epochs. scheduler = torch.optim.lr_scheduler.ReduceLROnPlateau(optimizer, 'min', patience=2, verbose=True) train_losses = [] val_losses = [] for epoch in range(epochs): # 每个epoch开始前切换到训练模式 model.train() epoch_train_loss = 0.0 train_batch_count = 0 for data in train_loader: y_pred = model.forward(data) loss1 = criterion(y_pred[:, 0], data.y[0]) loss2 = criterion(y_pred[:,1], data.y[1]) train_loss = 0.8*loss1+0.2*loss2 optimizer.zero_grad() train_loss.backward() optimizer.step() # 累加所有批次的训练损失 epoch_train_loss += train_loss.detach().numpy() train_batch_count += 1 # 存储整个epoch的平均训练损失 train_losses.append(epoch_train_loss / train_batch_count) # 验证前切换到推理模式 model.eval() epoch_val_loss = 0.0 val_batch_count = 0 with torch.no_grad(): for data in val_loader: y_val = model.forward(data) loss1 = criterion(y_val[:,0], data.y[0]) loss2 = criterion(y_val[:,1], data.y[1]) val_loss = 0.8*loss1+0.2*loss2 # 累加所有批次的验证损失 epoch_val_loss += val_loss.detach().numpy() val_batch_count +=1 # 存储整个验证集的平均损失 avg_val_loss = epoch_val_loss / val_batch_count val_losses.append(avg_val_loss) # 启用学习率调度器 scheduler.step(avg_val_loss) print(f'Epoch: {epoch}, train_loss: {train_losses[epoch]:.3f} , val_loss: {val_losses[epoch]:.3f}') return train_losses, val_losses
内容的提问来源于stack exchange,提问作者James Arten
相关产品推荐
相关产品推荐

