AI-深度学习

从零搭建视觉模型 13:过拟合、数据增强与正则化

展开合集目录
  1. 从零搭建视觉模型 01:图片进入模型后发生了什么
  2. 从零搭建视觉模型 02:Python 起步,写给 C/C++ 开发者
  3. 从零搭建视觉模型 03:从像素到张量的基础概念
  4. 从零搭建视觉模型 04:图片如何变成张量
  5. 从零搭建视觉模型 05:MLP 入门,从手算到图片输入
  6. 从零搭建视觉模型 06:亲手搭建一个小型 CNN
  7. 从零搭建视觉模型 07:模型怎样预测类别:分数、概率与决策
  8. 从零搭建视觉模型 08:让模型开始学习,损失、梯度与训练循环
  9. 从零搭建视觉模型 09:数据集、标签与数据划分
  10. 从零搭建视觉模型 10:损失函数与优化器:怎样把错误变成更新
  11. 从零搭建视觉模型 11:批次、训练轮次与学习率
  12. 从零搭建视觉模型 12:训练第一个真实图片分类器
  13. 从零搭建视觉模型 13:过拟合、数据增强与正则化
  14. 从零搭建视觉模型 14:验证、测试与错误分析
  15. 从零搭建视觉模型 15:保存模型、恢复训练与独立推理
  16. 从零搭建视觉模型 · 综合实践:实现 MLP,从损失计算、训练到 ONNX 推理
  17. 从零搭建视觉模型 16:ResNet 与残差连接
  18. 从零搭建视觉模型 17:预训练、迁移学习与微调
  19. 从零搭建视觉模型 18:轻量模型与计算成本
  20. 从零搭建视觉模型 19:目标检测与 YOLO:从类别到目标框
  21. 从零搭建视觉模型 20:图像分割与 U-Net:给每个像素一个判断
  22. 从零搭建视觉模型 21:注意力与视觉 Transformer
  23. 从零搭建视觉模型 22:从训练模型到 ONNX
  24. 从零搭建视觉模型 23:量化与精度对比
  25. 从零搭建视觉模型 24:从摄像头输入到部署验收
  26. 从零搭建视觉模型 25:发展历史、主流分类与当前格局
从零搭建视觉模型 13:过拟合、数据增强与正则化的封面

学习初稿 · 第 13 课。 本篇已开放网页阅读,用于先建立完整认识。文中手算值和示意记录不代表真实训练成绩;真实数据训练、预训练权重和板端测试以本篇具体说明为准,本篇已附代码练习,实际验证范围见文末与包内记录。

训练越来越好,陌生图片反而变差

继续使用飞机与汽车分类。假设一组人为构造的记录如下:训练损失从 0.5 降到 0.1,验证损失先从 0.6 降到 0.4,后来升到 0.7。模型更适应训练样本,却未继续改善独立样本表现,这可能是过拟合。

这只是解释现象的示意,不是前一课的实测结果。判断前还应确认验证处理固定、类别映射正确、集合独立,排除评估代码错误。曲线只有几次波动时,也不能立即给出确定结论。

过拟合与欠拟合的处理不同

训练与验证都差,可能是模型容量不足、优化不到位、数据质量差或任务定义不合理。训练明显更好而验证较差,才更值得检查泛化差距。增加训练轮数对这两种情况可能有相反作用。

先问模型是否在利用不稳定线索。例如汽车全在灰色地面、飞机全在蓝天,模型可能学背景颜色而非物体。增强能覆盖部分变化,但不能取代真正多样的数据。若现场常出现夜晚,单纯把白天图片亮度调低不等于已经拥有真实夜间成像分布。

数据增强的底线是保持目标语义

分类中常见的随机裁剪、翻转与颜色变化,要根据任务决定。飞机或汽车水平翻转通常不改变类别;识别文字或左右方向时则可能改变标签。裁剪过强如果把目标完全移走,原来的整图标签就不再可靠。

对于检测和分割,几何增强还必须同步变换框或掩码。图片翻转了,标签仍留在旧位置,会主动教给模型错误的对应关系。后续课程会具体处理这些输出形式。

下面只检查“翻转两次恢复原图”的张量关系,不依赖任何数据下载:

import torch

image = torch.arange(12).reshape(1, 3, 4)
flipped = torch.flip(image, dims=[2])
restored = torch.flip(flipped, dims=[2])
assert torch.equal(image, restored)
print(image[0, 0].tolist(), flipped[0, 0].tolist())

实际图像增强可使用 Torchvision 的变换工具;随机参数、数据类型与插值方式要与任务匹配。Torchvision transforms 文档

正则化不只是增加随机性

权重衰减对参数施加收缩倾向;Dropout 在训练中随机屏蔽部分激活,并按设计处理尺度;早停根据验证表现选择停止时机或最佳检查点。它们分别作用于参数、激活和模型选择过程。

model.eval() 会改变 Dropout、BatchNorm 等层的行为,但不等于关闭梯度;torch.no_grad() 则关闭梯度记录,不会自动切换这些层的模式。两者承担不同职责,评估时通常都需要。

正则化过强可能导致模型无法学会训练任务。比如激活被大量丢弃、颜色被改得面目全非、权重衰减过大,都可能同时损害训练和验证结果。不是“加得越多越专业”。

一次只改变一个因素

建立四组对照:原始基线、只加适度增强、只改权重衰减、再组合两者。保持数据划分、输入规则、模型初始化策略和评价流程一致。至少记录训练曲线、验证指标、错误样本与时间成本。

如果随机性影响较大,可使用多个种子重复比较,并报告均值和波动。不要从多次尝试中只挑最好的一次,却把它当成稳定效果。资源有限时也应清楚注明单次运行的局限。

增强仅用于训练。验证与测试需要确定的预处理,使同一输入对应可比较的结果。多裁剪评估等额外策略是独立的评测配置,使用时应明确说明计算预算。

练习

  1. 一个数字 6 的分类器是否可以随意旋转 180 度?
  2. no_grad() 后忘了 eval(),Dropout 是否一定停止随机丢弃?
  3. 模型在训练集与验证集都很差,是否应该马上增加 Dropout?
参考答案

不应随意旋转,可能改变为另一个数字或形成不合理样本。no_grad() 不切换训练模式,因此不能保证。两边都差时先检查输入、标签、优化和容量,增加 Dropout 可能进一步妨碍拟合。

下一课把“效果变好了”拆成明确的指标和错误类型,避免只依赖一条总准确率。

配套代码练习

实验:增强、Dropout 与早停

查看完整代码:main.py
"""增强、Dropout 与早停。运行方式与练习见 README.md。"""
import torch
from torch import nn
torch.manual_seed(7)
image = torch.arange(12).reshape(1,3,4).float()/11
flipped = image.flip(-1)
assert torch.equal(flipped.flip(-1),image)
print('原图第一行:',image[0,0].tolist(),'翻转:',flipped[0,0].tolist())
drop = nn.Dropout(0.5)
x = torch.ones(1000)
train = drop(x)
drop.eval()
assert torch.equal(drop(x),x)
print('训练时置零比例:',float((train==0).float().mean()))
# 示意验证损失,用于观察早停,不是实测训练曲线。
values = [0.9,0.7,0.6,0.64,0.68,0.7]
best, stale, best_epoch = float('inf'),0,0
for epoch,value in enumerate(values,1):
    if value < best: best,stale,best_epoch = value,0,epoch
    else: stale += 1
    if stale >= 2:
        print('早停轮次:',epoch,'应恢复轮次:',best_epoch)
        break
assert best_epoch == 3 and epoch == 5
下载代码

在项目根目录执行;下载包解压后,在解压目录执行 python3 main.py。

cd 'content/posts/AI-深度学习/从零搭建视觉模型/13-过拟合数据增强与正则化/code' && python3 main.py

本篇已附独立实验包,在代码卡片中查看源码、下载或复制运行命令。解压后执行 python3 main.py;依赖安装见包内 README.md。

观察目标: 连续两次没有改善时在第 5 轮停止,选择第 3 轮;eval 下 Dropout 保持输入不变。

动手修改: 将 patience 从 2 改为 3;解释数字字符分类为什么不适合任意水平翻转。

所有输入均在本篇代码中构造;运行范围和验证记录见包内 verification.md。


完整阅读目录 · 上一篇 · 下一篇