AI-深度学习

从零搭建视觉模型 11:批次、训练轮次与学习率

展开合集目录
  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:发展历史、主流分类与当前格局
从零搭建视觉模型 11:批次、训练轮次与学习率的封面

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

每轮看到全部数据,不等于只更新一次

假设训练集有 103 张图片,每批 16 张。一次前向、反向和更新处理一批;遍历完训练集称为一轮,即一个 epoch。最后一批只有 7 张,如果不丢弃,它仍然可以参与一次更新。

因此每轮有 ceil(103/16)=7 次更新,训练 5 轮共有 35 次更新。参数数量由网络结构决定,不会因为批次从 16 改成 32 就翻倍。

import math

samples, batch_size, epochs = 103, 16, 5
steps = math.ceil(samples / batch_size)
print(steps, steps * epochs)
print('drop_last=True:', samples // batch_size)

这里先假设每批更新一次,没有梯度累积和提前退出。如果丢弃最后一批,则每轮是 6 次更新,而且这一轮并没有使用全部 103 张图。

batch size 同时影响资源与优化

批次变大通常增加中间激活占用,也会改变梯度估计的统计性质。在固定轮数下,批次更大往往意味着更新次数更少,因此“轮数相同”不代表优化过程相同。

小批次不一定更慢,大批次也不一定提高泛化。硬件利用率、数据加载、输入大小和网络结构共同决定速度。遇到内存不足,减小 batch 是常见处理,但应记录这一变化,不能继续假装与原配置完全可比。

梯度累积可以多批累积后更新一次,近似扩大参与更新的样本量。不过带有 BatchNorm、随机层或其他批次相关行为时,不能认为它与直接使用大批次严格等价。

学习率调度改变的是训练过程

固定学习率容易观察;分阶段降低学习率可用于后期细化;余弦调度等方法按某种曲线改变更新尺度。调度器何时调用必须明确:每轮一次和每批一次会得到完全不同的实际曲线。

同一个训练计划,最好把实际学习率写进日志,而不只保存“使用了某调度器”。恢复训练时还要恢复调度器进度,否则可能在训练中途重新回到初始学习率。

warmup 是开始时逐步增加学习率的安排,不是所有小网络必需的步骤。第一版实验先用固定学习率跑通,再一次改变一个因素,观察效果。

统计损失时不要让小批次占过大权重

若每批损失是该批样本的平均值,统计整个集合应按批次样本数加权。最后一批 7 张和前面 16 张不该有相同权重。

累计损失 = 每批平均损失 × 该批样本数,再求和
集合平均损失 = 累计损失 ÷ 总样本数
准确率 = 总正确数 ÷ 总样本数

如果损失使用类别权重、忽略标签或特殊 reduction,分母可能需要按实际损失定义处理。这里的样本加权公式适用于本系列无类别权重的普通分类均值。

训练中记录的平均损失来自参数不断更新的不同时间点,验证损失来自一轮结束后的固定模型,两者不是完全相同条件下的测量。训练还可能用了随机增强,因此不必要求每轮验证损失都大于训练损失。

设计一份最小实验记录

记录数据划分版本、随机种子、模型、批次、优化器、学习率、轮数、实际更新次数和验证结果。随机种子有助于复现,但跨设备、库版本和非确定性内核仍可能产生差别。

比较 batch=16 与 batch=32 时,先说明是在相同轮数、相同更新数还是相同时间预算下比较。三个问题都合理,但回答不同:数据遍历次数相同、优化机会相同、计算预算相同不能混为一谈。

日志中可以按每轮输出训练损失、验证损失、验证准确率、学习率与耗时。不要只保留最后一行,否则无法判断中途是否过拟合、发散或出现明显波动。

练习

  1. 256 张图,batch=64,训练 20 轮,每批更新一次,共多少次?
  2. 两批分别是 4 张、1 张,平均损失为 1 和 3,集合损失是多少?
  3. batch 翻倍、epoch 不变,更新次数是否通常不变?
参考答案

每轮 4 次,共 80 次。集合损失为 (4×1+1×3)/5=1.4,不是 2。批次翻倍通常减少每轮更新次数,需要考虑最后一批与丢弃策略,不能只比较 epoch。

下一课使用真实图片建立第一份分类基线;此时我们已经知道每一条训练日志究竟在统计什么。

综合实践

想把这些知识接成一套可运行的代码,可以进入实现 MLP:从损失计算、训练到 ONNX 推理。实践包含自生成数据、训练与验证、模型保存、独立推理、ONNX 导出和数值对照;遇到问题可按实践中的索引返回对应理论章节。

配套代码练习

实验:批次计数与加权平均

查看完整代码:main.py
"""批次计数与加权平均。运行方式与练习见 README.md。"""
import math
size, batch, epochs = 103, 32, 3
batches = [min(batch,size-i) for i in range(0,size,batch)]
means = [1.0,0.8,0.6,2.0]
weighted = sum(n*v for n,v in zip(batches,means))/size
naive = sum(means)/len(means)
assert batches == [32,32,32,7]
assert math.ceil(size/batch)*epochs == 12
print('batch sizes:',batches,'updates:',len(batches)*epochs)
print('样本加权损失:',weighted,'批次直接平均:',naive)
for epoch in range(epochs): print('epoch',epoch+1,'lr',0.1*0.5**epoch)
下载代码

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

cd 'content/posts/AI-深度学习/从零搭建视觉模型/11-批次轮次与学习率/code' && python3 main.py

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

观察目标: 每轮 4 次更新,共 12 次;加权损失约 0.88155,直接平均 1.1。

动手修改: 启用 drop_last 后重新计算更新次数和丢弃样本数;把 batch 改成 16 时同时更新构造的批次均值。

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


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