标签:
Pytorch、训练验证流程、准确率、数据集划分、model.eval()
简介:真实项目不能只跑训练loss!必须划分训练集、验证集,每轮训练后在验证集上评估模型效果,防止过拟合。本文搭建一套工业界标准的训练+验证模板,可直接复用。
一、前言
前面已经掌握张量、自动求导、网络搭建、损失优化、Dataset/DataLoader、模型保存加载。
之前的代码只有训练,缺少验证环节,存在几个问题:
- 无法判断模型是否过拟合(训练loss很低,陌生数据效果很差)
- 看不到准确率指标(分类任务最关心acc)
- 没有标准化的epoch循环结构
标准流程:
每个epoch:
- 训练阶段:开启
model.train(),前向、反向、更新参数 - 验证阶段:开启
model.eval(),torch.no_grad(),只计算loss和指标,不更新权重 - 一轮结束后打印训练loss、验证loss、验证准确率
- 根据验证指标保存最优模型
二、数据集划分:train / val
两种常用方案:
- 手动划分文件列表(适合自己的图片数据集)
torch.utils.data.random_split直接划分数据集(快速原型首选)
from torch.utils.data import random_split
# 原始完整数据集
full_dataset = datasets.MNIST(root="./data", train=True, transform=trans, download=True)
# 按照 8:2 划分训练集、验证集
train_size = int(0.8 * len(full_dataset))
val_size = len(full_dataset) - train_size
train_dataset, val_dataset = random_split(full_dataset, [train_size, val_size])
train_loader = DataLoader(train_dataset, batch_size=64, shuffle=True)
val_loader = DataLoader(val_dataset, batch_size=64, shuffle=False)
✅ 训练集 shuffle=True;验证集 shuffle=False,保证评估稳定
三、准确率计算逻辑(分类任务)
模型输出:batch × class_num,是各个类别的logits
torch.max(outputs, dim=1)取出每个样本预测分数最大的类别索引- 和真实label对比,统计相等的数量,除以总样本数得到准确率
outputs = model(imgs)
preds = torch.max(outputs, dim=1)[1]
correct = torch.sum(preds == labels)
acc = correct.item() / labels.size(0)
四、完整可运行代码(MNIST手写数字分类)
import torch
import torch.nn as nn
from torch.utils.data import DataLoader, random_split
from torchvision import datasets, transforms
# 1. 设备自动选择
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
print(f"use device: {device}")
# 2. 数据预处理
trans = transforms.Compose([
transforms.ToTensor(),
transforms.Normalize((0.1307,), (0.3081,))
])
# 3. 数据集划分
full_data = datasets.MNIST(root="./data", train=True, transform=trans, download=True)
train_ds, val_ds = random_split(full_data, [int(0.8*len(full_data)), len(full_data)-int(0.8*len(full_data))])
train_loader = DataLoader(train_ds, batch_size=64, shuffle=True)
val_loader = DataLoader(val_ds, batch_size=64, shuffle=False)
# 4. 简单网络
class MnistNet(nn.Module):
def __init__(self):
super().__init__()
self.fc = nn.Sequential(
nn.Flatten(),
nn.Linear(28*28, 128),
nn.ReLU(),
nn.Linear(128, 10)
)
def forward(self, x):
return self.fc(x)
model = MnistNet().to(device)
loss_fn = nn.CrossEntropyLoss()
opt = torch.optim.Adam(model.parameters(), lr=1e-3)
# 5. 主训练循环
epoch_num = 10
best_val_acc = 0.0
for epoch in range(epoch_num):
# ========== 训练阶段 ==========
model.train()
train_loss_sum = 0.0
for imgs, labels in train_loader:
imgs, labels = imgs.to(device), labels.to(device)
outputs = model(imgs)
loss = loss_fn(outputs, labels)
opt.zero_grad()
loss.backward()
opt.step()
train_loss_sum += loss.item()
train_loss_avg = train_loss_sum / len(train_loader)
# ========== 验证阶段 ==========
model.eval()
val_loss_sum = 0.0
correct_sum = 0
total_num = 0
with torch.no_grad():
for imgs, labels in val_loader:
imgs, labels = imgs.to(device), labels.to(device)
outputs = model(imgs)
loss = loss_fn(outputs, labels)
val_loss_sum += loss.item()
preds = torch.max(outputs, dim=1)[1]
correct_sum += torch.sum(preds == labels)
total_num += labels.size(0)
val_loss_avg = val_loss_sum / len(val_loader)
val_acc = correct_sum.item() / total_num
print(f"【Epoch {epoch+1}/{epoch_num}】")
print(f"train_loss:{train_loss_avg:.4f} | val_loss:{val_loss_avg:.4f} | val_acc:{val_acc:.4f}\n")
# 保存最优模型
if val_acc > best_val_acc:
best_val_acc = val_acc
torch.save(model.state_dict(), "mnist_best.pth")
print(f"✅ 保存最优模型,当前最佳acc:{best_val_acc:.4f}")
五、关键要点拆解
- device 迁移:数据、模型都要
.to(device),否则报设备不匹配 - model.train() / model.eval()
- train:BN、Dropout生效,会更新参数统计量
- eval:BN、Dropout固定,只做推理
- with torch.no_grad():验证阶段关闭计算图,不占用显存,不计算梯度
- 指标更新:累计loss在每个batch相加,最后除以loader长度得到平均loss
- 最优模型保存逻辑:按验证集指标保存,不是按训练loss保存,这是对抗过拟合的关键
六、过拟合判断(看日志)
| 现象 | 结论 |
|---|---|
| train_loss下降,val_loss同步下降 | 正常收敛 |
| train_loss持续下降,val_loss先降后升 | 过拟合!停止训练、加正则、扩增数据 |
| train_loss很高、val_loss很高 | 欠拟合:网络容量不足、学习率不合适 |
七、常见踩坑点
- 验证集忘记加
torch.no_grad()→ 显存暴涨,速度变慢 - 验证阶段还写
loss.backward()、opt.step()→ 偷偷更新权重,验证集泄露 - 准确率计算直接用batch累加,没有按总样本归一化
- random_split划分后,transform是共享的;如果训练和验证要不同增强,不能用random_split
- 把测试集当成验证集:测试集只能最后一次性评估,训练过程绝对不能碰
347



被折叠的 条评论
为什么被折叠?



