4.6 施工监理:训练监控、验证与早停


4.6 施工监理:训练监控、验证与早停

本节摘要:训练不是点火就走,监理环节决定微调的最终成色。本节讲三件监理工具:曲线判读(训练与验证曲线的四种病态形态)、验证集的正确用法(分层切分、绝不参与训练决策之外的任何计算)、早停与检查点(耐心参数怎么设、存什么、何时恢复)。末尾给出一张曲线形态诊断卡,第 6 章的事故排查将直接复用它。

曲线的四种病态

微调过程中每一轮记录训练损失、训练精度、验证损失、验证精度四条曲线。健康形态是:训练损失单调下降、验证损失同步下降并在某轮触底、验证精度在验证损失最低点附近达峰。病态有四种典型形态:

  • 形态一(过拟合):训练损失持续降、验证损失先降后升。经典病,微调小数据的头号敌人,对策是早停加正则
  • 形态二(欠拟合/学习率不足):两条曲线都下不去,几轮后近似平台。学习率过小或冻结过深,参照 4.4 节诊断表
  • 形态三(学习率过大):曲线剧烈震荡甚至发散。降峰值、加预热
  • 形态四(数据问题):训练曲线完美、验证曲线从第一轮就异常差。优先怀疑数据泄漏或验证集分布漂移,而不是调参
import numpy as np # 四种形态的合成曲线与自动判读 rng = np.random.default_rng(5) epochs = np.arange(1, 21) def noise(scale=0.01): return rng.normal(0, scale, 20) healthy = {"train": 0.5*np.exp(-0.2*epochs)+0.1+noise(), "val": 0.55*np.exp(-0.2*epochs)+0.15+noise()} overfit = {"train": 0.5*np.exp(-0.3*epochs)+0.08+noise(), "val": np.maximum(0.3, 0.6*np.exp(-0.25*epochs))+0.02*epochs+noise(0.02)} plateau = {"train": 0.6 - 0.001*epochs + noise(), "val": 0.62 - 0.0008*epochs + noise()} for name, d in [("健康", healthy), ("过拟合", overfit), ("平台", plateau)]: gap = d["train"][-1] - d["val"][-1] val_best = d["val"].min() print(f"{name}: 末期训练-验证差 {gap:+.3f}, 验证最优 {val_best:.3f}") # 典型输出: # 健康: 末期训练-验证差 -0.049, 验证最优 0.161 # 过拟合: 末期训练-验证差 -0.522, 验证最优 0.305 <- 验证回升即过拟合信号 # 平台: 末期训练-验证差 -0.018, 验证最优 0.604 <- 双双高位即欠拟合信号

验证集的正确用法

监理的合法性取决于验证集的质量。工程纪律四条:

  1. 分层切分:分类任务按类别比例切,小数据集上随机切可能出现某类验证集只有个位数样本,指标方差大到不可用
  2. 切分先于一切:数据增强、统计估计(归一化参数)都只能在训练部分上做,验证集要模拟"从未见过"的状态
  3. 只用它做选择,不用它做训练:验证集指导早停与超参选择没问题,但反复"验证集上看效果—改模型—再看"的循环里,验证集信息会泄漏进决策,长期下来验证精度虚高。最终裁决必须留一个从没参与过任何决策的测试集
  4. 分布对齐:验证集要反映上线后的真实分布(时段、渠道、设备),否则监理在监控一个假目标
import numpy as np def stratified_split(labels, val_ratio=0.2, seed=7): """按类别分层切分,保证验证集类别比例与整体一致""" rng = np.random.default_rng(seed) train_idx, val_idx = [], [] for cls in np.unique(labels): idx = np.where(labels == cls)[0] rng.shuffle(idx) n_val = max(1, int(len(idx) * val_ratio)) val_idx.extend(idx[:n_val]); train_idx.extend(idx[n_val:]) return np.array(sorted(train_idx)), np.array(sorted(val_idx)) labels = np.repeat([0, 1, 2, 3], [50, 30, 15, 5]) # 不均衡的四类 tr, va = stratified_split(labels) print(f"训练 {len(tr)} 条 / 验证 {len(va)} 条") # 输出: 训练 81 条 / 验证 19 条 for cls in range(4): print(f"类别 {cls}: 验证占比 {np.mean(labels[va]==cls):.2%} / 整体占比 {np.mean(labels==cls):.2%}") # 输出: # 类别 0: 验证占比 50.00% / 整体占比 50.00% # 类别 1: 验证占比 30.00% / 整体占比 30.00% # 类别 2: 验证占比 10.53% / 整体占比 15.00% # 类别 3: 验证占比 5.26% / 整体占比 5.00% # 分层切分下稀有类在验证集里也保住了代表权

早停与检查点

早停的逻辑:验证指标连续若干轮(耐心值)不创新优就停止训练,并恢复到历史最优的那一步。配套的检查点策略决定"恢复"是否可行。

import copy class EarlyStopper: """监理的核心器件:耐心计数 + 最优检查点保管""" def __init__(self, patience=3, min_delta=0.0): self.patience = patience # 容忍几轮不提升 self.min_delta = min_delta # 多少算“提升” self.best = -float("inf") self.wait = 0 self.best_state = None def step(self, metric, model) -> bool: """每轮验证后调用;返回 True 表示应停止训练""" if metric > self.best + self.min_delta: self.best, self.wait = metric, 0 self.best_state = copy.deepcopy(model.state_dict()) # 保管最优快照 return False self.wait += 1 return self.wait >= self.patience import torch.nn as nn model = nn.Linear(10, 2) stopper = EarlyStopper(patience=3) for epoch, acc in enumerate([0.70, 0.78, 0.75, 0.74, 0.76, 0.73]): if stopper.step(acc, model): print(f"epoch {epoch}: {stopper.wait} 轮无提升,早停触发,最优 {stopper.best:.2f}") break # 输出: epoch 5: 3 轮无提升,早停触发,最优 0.78 # model.load_state_dict(stopper.best_state) 即可回到最优状态

耐心值的经验:微调轮数本来就少,耐心设二到四;耐心过大会把后面的过拟合轮次也跑完,白白浪费时间与最优点附近的机会。检查点除了模型权重,还应记录轮次、优化器状态(恢复训练用)、验证指标与超参配置——第 6 章排查事故时要靠这些复盘。

⚠️ 常见坑:用验证损失最低点还是验证精度最高点做早停标准,前后不一致。损失对置信度敏感、精度对决策边界敏感,两者最优轮次常常错开一至两轮——开工前定死用哪个,全程不换。

💡 关键直觉:监理的本质是"用不会参与训练的数据,给训练过程提供一个没有利益冲突的旁观者视角"。任何让旁观者沾上训练的事(泄漏、反复试探)都会让监理失明。

曲线形态诊断卡

曲线形态 判定 第一处方 深入章节
训练降、验证先降后升 过拟合 早停回最优、加正则 本节与 4.5
双曲线高位平台 欠拟合 提学习率、解冻更深 4.3 与 4.4
双曲线震荡 步长过大或批过小 降学习率、裁剪、加批次 4.4 与 4.5
训练完美、验证全程差 数据问题 查泄漏与分布对齐 6.3

本节要点回顾

  • 四曲线四形态:过拟合、欠拟合、震荡、数据问题,各有第一处方,别拿调参治数据的病
  • 验证纪律:分层切分、切分先于一切、只做选择不做训练、最终裁决留给独立测试集
  • 早停器件:耐心二到四、最优快照保管、停后恢复三件套(权重、优化器、配置)
  • 标准一致性:早停认损失还是认精度,开工前定死全程不换
  • 本节位置:施工与监理齐了,第 5 章进工地实操,把第 4 章全部工艺串成完整流程

作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U