跳到主要内容

训练监控与诊断

本节定位

很多训练失败并不是“模型不够强”,而是:

  • 数据有问题
  • 学习率不对
  • 训练过程在悄悄崩

所以训练时最关键的能力之一不是“会开训”,而是:

看得懂训练过程到底出了什么问题。

学习目标

  • 理解训练监控应该看哪些关键信号
  • 学会区分过拟合、欠拟合、学习率异常等常见问题
  • 通过可运行示例建立训练曲线诊断直觉
  • 学会把问题定位到数据、优化器或模型结构层

先建立一张地图

如果你已经学过训练循环和调参,这一节最自然的续接就是:

  • 训练循环告诉你“怎么训”
  • 调参告诉你“先调什么”
  • 这一节开始回答“训坏了时到底先查什么”

所以训练诊断不是补充阅读,而是训练工程里最核心的排障能力之一。

训练诊断最适合新人的方式不是“靠经验猜”,而是先把问题拆成几层:

这个顺序会让你少走很多弯路。

一个更适合新人的总类比

你可以把训练诊断理解成:

  • 医生先看体征,再判断病因

如果你一看到 loss 不对就立刻换模型,
就很像:

  • 还没量体温就先换整套治疗方案

更稳的方式通常是:

  • 先看现象
  • 再做初步归类
  • 最后再定位真正根因

一、为什么训练诊断这么重要?

1.1 因为训练失败很少会直接报“真正原因”

你更常看到的是:

  • loss 不降
  • 验证集突然变差
  • 准确率卡住

但这些只是现象,不是根因。

1.2 真正的诊断要回答

  • 是学习率问题?
  • 是数据问题?
  • 是过拟合?
  • 还是模型容量不够?

1.3 这节最值得先记的,不是故障名称,而是什么?

最值得先记的是:

训练现象和根因不是同一个东西。

比如:

  • loss 不降 只是现象
  • 真正根因可能是学习率、标签、梯度、数据分布、模型容量

一旦你把“现象”和“根因”分开,排障会理性很多。


二、先看一个最常见的诊断入口:训练曲线

history = [
{"epoch": 1, "train_loss": 0.95, "val_loss": 0.98},
{"epoch": 2, "train_loss": 0.72, "val_loss": 0.81},
{"epoch": 3, "train_loss": 0.51, "val_loss": 0.79},
{"epoch": 4, "train_loss": 0.35, "val_loss": 0.92},
]

for row in history:
print(row)

2.1 这个例子里最明显的信号是什么?

  • train_loss 一直下降
  • val_loss 先降后升

这通常非常像:

  • 过拟合

2.2 为什么训练曲线是第一入口?

因为它是最早暴露问题的地方,
而且很多问题都能先从曲线形状看出端倪。

2.3 第一次看曲线时,最值得先盯哪三件事?

  1. 训练集有没有在学
  2. 验证集有没有同步变好
  3. 两条曲线是不是开始明显分叉

只要先盯这三件事,很多问题都能先做第一轮归类。

2.4 一个很适合初学者先记的故障定位表

现象更可能先查什么
train / val 都很差欠拟合、模型容量、特征不足
train 很好,val 变差过拟合、数据量不足、正则化不够
loss 大幅震荡学习率过大、batch 太小、梯度不稳
模型总预测同一类标签问题、类别不平衡、实现 bug

这个表很适合新人,因为它能把“看到曲线后完全不知道怎么办”,先变成一组可执行的排查起点。


三、几种常见问题长什么样?

3.1 欠拟合

典型表现:

  • train_loss 高
  • val_loss 也高
  • 两边都下不去

3.2 过拟合

典型表现:

  • train_loss 继续下降
  • val_loss 开始变差

3.3 学习率太大

典型表现:

  • loss 抖动
  • 甚至突然爆炸

3.4 学习率太小

典型表现:

  • loss 在降,但极慢
  • 很久没有明显进展

四、除了 loss,还该看什么?

4.1 梯度是否异常

例如:

  • 梯度爆炸
  • 梯度消失

4.2 预测分布是否异常

例如:

  • 模型总预测同一类
  • 置信度极端偏斜

4.3 数据本身是否有问题

例如:

  • 标签错误
  • 类别不平衡
  • 训练/验证分布差异大

4.4 一个新人可直接照抄的排查顺序

当训练出问题时,可以优先按这个顺序看:

  1. 训练和验证曲线
  2. 学习率设置
  3. 输入和标签是否对齐
  4. 模型预测是不是塌到同一类
  5. 梯度和参数更新是否异常

这样通常比一上来换模型更有效。

4.5 为什么这套顺序比“先换模型”更稳?

因为很多训练问题的根因并不在模型结构,而在更前面:

  • 数据
  • 标签
  • 学习率
  • 训练流程

如果这些没先排清,就算换更复杂模型,问题常常还在。


五、一个最小诊断规则示例

def diagnose(train_losses, val_losses):
if train_losses[-1] > 0.8 and val_losses[-1] > 0.8:
return "可能欠拟合"
if train_losses[-1] < train_losses[0] and val_losses[-1] > min(val_losses):
return "可能过拟合"
if max(train_losses) - min(train_losses) > 1.5:
return "可能学习率过大或训练不稳定"
return "需要结合更多信号判断"


train_losses = [0.95, 0.72, 0.51, 0.35]
val_losses = [0.98, 0.81, 0.79, 0.92]

print(diagnose(train_losses, val_losses))

5.1 这个例子不是要替代人工判断

它主要是在帮你建立一种很重要的诊断习惯:

  • 先从现象归类
  • 再去找可能原因

5.2 再看一个最小“训练日志检查表”示例

training_log = {
"train_loss": [1.2, 0.8, 0.5, 0.3],
"val_loss": [1.1, 0.9, 0.95, 1.1],
"prediction_pattern": "mostly_one_class",
}


def first_check(log):
if log["prediction_pattern"] == "mostly_one_class":
return "先检查标签分布、类别不平衡和输出层实现。"
if log["val_loss"][-1] > min(log["val_loss"]):
return "先按过拟合方向排查。"
return "先继续看学习率和梯度。"


print(first_check(training_log))

这个示例很适合新人,因为它会帮助你把训练诊断从:

  • 模糊感觉

变成:

  • 有顺序的排查动作

六、最容易踩的坑

6.1 误区一:只看最终准确率

这样你很难理解训练过程中发生了什么。

6.2 误区二:一看 loss 不降就立刻换模型

很多时候问题根本不在模型结构。

6.3 误区三:觉得训练问题只能靠经验猜

其实很多问题都能通过:

  • 曲线
  • 统计
  • 样本检查

有系统地定位。

七、训练时最值得固定保存哪些东西

  • 每个 epoch 的 train / val loss
  • 关键指标
  • 最好和最坏的样本预测
  • 一份当前超参数配置

如果把它做成项目或实验记录,最值得展示什么

最值得展示的通常不是:

  • 最后一个准确率

而是:

  1. train / val 曲线
  2. 你对问题的第一轮诊断
  3. 你先改了什么
  4. 改完后曲线和指标怎么变

这样别人会更容易看出:

  • 你理解的是训练排障
  • 不只是会按下开始训练按钮

这些记录会让你后面复盘时非常轻松。

7.1 为什么“最坏样本”常常比平均分更有价值?

因为平均分只能告诉你“整体大概怎样”,
但最坏样本更能暴露:

  • 模型最怕哪类输入
  • 标签是不是有问题
  • 数据分布是否存在边角案例

所以很多真正有效的改进,往往不是从总指标里看出来的,而是从最差样本里看出来的。


小结

这节最重要的是建立一个训练诊断直觉:

先从 loss 曲线和验证表现识别问题类型,再把根因逐步定位到学习率、数据、泛化或模型容量。

只要这个习惯建立起来,训练就不会再只是“开盲盒”。

这节最该带走什么

  • 曲线是入口,不是结论
  • 现象和根因要分开看
  • 先排数据和训练流程,再大改模型
  • 训练诊断能力本身就是深度学习工程能力

练习

  1. 自己构造一组“欠拟合”曲线,看看 diagnose 会不会变化。
  2. 为什么说训练集和验证集的差距,是诊断里非常关键的信号?
  3. 如果模型总预测同一类,你会优先怀疑哪些问题?
  4. 想一想:为什么训练诊断能力本身就是工程能力?