首页 / PyTorch 入门教程 / 模型评估与验证集

PyTorch 入门教程

模型评估与验证集

本教程共 60 篇 · 第 17 篇 · 更新于 2026-08-17 · 约 4 分钟阅读

PyTorch模型评估验证集过拟合早停混淆矩阵深度学习入门

本节目标:搞懂为什么训练集上分数高不算数,学会用验证集判断模型的真实水平,并识别过拟合信号。

训练得好,不等于考得好

前面我们写完训练循环,就盯着训练 loss 一路下降。停一下,这里有个陷阱。

loss 下降只说明模型在训练数据上表现变好。它可能只是在「背答案」,而不是真的学会了规律。就像学生把课本背得滚瓜烂熟,换一套题就露馅。

怎么识破?拿模型没见过的数据考它。这就是验证集(validation set)存在的意义。

三份数据:训练、验证、测试

正规流程要把数据切成三份:

  1. 训练集(training set):用来更新模型参数,就是训练循环里用的那份
  2. 验证集(validation set):训练过程中定期「小测」,帮我们调超参数、判断过拟合
  3. 测试集(test set):训练全部结束后只碰一次,用来汇报模型的最终水平

三者的关系,像练习册、模拟考和高考。练习册天天刷,模拟考用来查漏补缺,高考成绩才是最终答卷。

Warning

测试集只能碰一次。拿测试集反复调参,它就变成了「见过的题」,最终分数会虚高。

常见比例是 8:2 或 7:3,数据量大时验证集比例可以更小。划分数据用 random_split 很方便:

from torch.utils.data import random_split

dataset = ...  # 你的完整数据集
train_size = int(0.8 * len(dataset))
val_size = len(dataset) - train_size
train_set, val_set = random_split(dataset, [train_size, val_size])
Warning

划分前先确认数据集顺序是打乱的。有些数据集按类别排列,直接切前 80% 当训练集,验证集里可能全是一个类别,评估结果就废了。

评估的标准姿势

一轮完整的评估,按顺序做这几步:

  1. 切到评估模式:model.eval()
  2. torch.no_grad() 包住循环,关掉梯度追踪
  3. 遍历验证集,累加预测结果
  4. 算指标:准确率、平均 loss

验证和训练有两个关键区别:关掉梯度、切换模式。

model.eval()                     # 切到评估模式
with torch.no_grad():            # 不追踪梯度
    for inputs, labels in val_loader:
        outputs = model(inputs)
        ...

为什么要这样?第 16 章提过,Dropout 和 BatchNorm 在两种模式下行为不同。忘了调 model.eval(),验证分数会莫名变差。torch.no_grad() 则省显存、跑得快,验证不需要梯度。

准确率怎么算

分类任务最直观的指标是准确率(accuracy):预测对的样本数除以总样本数。

correct = 0
total = 0
with torch.no_grad():
    for inputs, labels in val_loader:
        outputs = model(inputs)
        _, predicted = torch.max(outputs, 1)   # 取分数最高的类别
        total += labels.size(0)
        correct += (predicted == labels).sum().item()

print(f"准确率: {100 * correct / total:.2f}%")

torch.max(outputs, 1) 返回两个东西:最大值和它的下标。_ 是占位符,我们只要下标,那就是预测的类别。

Note

分类网络输出的是每个类别的分数,不是直接给出答案。取分数最高的下标当预测,这个模式后面会反复出现。

准确率之外,验证 loss 同样值得记下来。它和训练 loss 一起看,才构成完整的故事。单独盯着训练 loss,只能看到模型对自己人有多熟。

看懂两条 loss 曲线

把训练 loss 和验证 loss 画在一起,能读出模型状态。四种典型情况:

  • 两条都降:正常,模型在学真东西
  • 训练降、验证升:过拟合(overfitting),模型开始背答案
  • 两条都不降:欠拟合(underfitting),模型太简单或学习率不对
  • 曲线剧烈震荡:学习率太大,或 batch 太小
Note

过拟合的信号是「验证 loss 掉头上升」。拐点一出现,就该停手或加正则化,具体做法第 27 章细讲。

画曲线时有个小技巧:每个 batch 的 loss 跳来跳去,直接画像噪声。把每 50 个 batch 的平均值画上去,曲线会平滑得多,趋势一眼可见。

Tip

这些曲线还可以写进 TensorBoard,看交互式图表。第 30 章会讲怎么用 SummaryWriter 把标量、图像都记录进去。

早停:见好就收

验证 loss 先降后升,最省事的办法就是盯住它:不降了就停,顺手把最好的那版模型存下来。这叫早停(early stopping)。

best_loss = float('inf')
patience = 5
wait = 0

for epoch in range(100):
    val_loss = evaluate()          # 你自己实现的验证函数,返回平均验证 loss

    if val_loss < best_loss:
        best_loss = val_loss
        wait = 0
        torch.save(model.state_dict(), "best.pt")   # 存最佳模型
    else:
        wait += 1
        if wait >= patience:
            print(f"早停:验证 loss 连续 {patience} 轮没降")
            break

patience 是容忍度:连续几轮没改善就停。设太小容易误停,太大浪费时间,一般取 5 到 10。

实际项目里,很多人不等早停,而是每轮都存模型,训练结束后挑验证分数最高的那版用。思路都一样:最终上线的是「验证集上表现最好的模型」,而不是「训练最后一轮的模型」。

混淆矩阵:错在哪一类

准确率只给一个总数,看不出模型在哪些类别上犯迷糊。混淆矩阵(confusion matrix)把真实类别和预测类别交叉统计,一眼看清。

拿猫狗二分类举例,矩阵有四块区域:预测对的两块在对角线上,剩下的两块分别是「把猫认成狗」和「把狗认成猫」。对角线越大越好,说明分得对。

用 sklearn 算很快:

from sklearn.metrics import confusion_matrix

cm = confusion_matrix(y_true, y_pred)
print(cm)   # 行是真实类别,列是预测类别
Tip

类别不平衡时准确率会骗人。比如数据里 99% 是猫,全猜猫也有 99% 的准确率。这时候要去看精确率、召回率,后面章节再展开。

常见坑速查

  • 忘写 model.eval():Dropout 还在随机丢神经元,验证分数偏低且每次不一样
  • 验证时没包 no_grad():慢、费显存,极端情况会把显存撑爆
  • 拿测试集当验证集调参:最终汇报的分数虚高,到了真实环境露馅
  • 只记准确率不记 loss:准确率不涨但 loss 在降时,模型其实还在学,别急着停

小结

模型评估就三句话:数据分三份,评估关梯度,盯着验证 loss 找拐点。测试集只碰一次,剩下的交给正则化和调参技巧。