学习初稿 · 第 11 课。 本篇已开放网页阅读,用于先建立完整认识。文中手算值和示意记录不代表真实训练成绩;真实数据训练、预训练权重和板端测试以本篇具体说明为准,本篇已附代码练习,实际验证范围见文末与包内记录。
每轮看到全部数据,不等于只更新一次
假设训练集有 103 张图片,每批 16 张。一次前向、反向和更新处理一批;遍历完训练集称为一轮,即一个 epoch。最后一批只有 7 张,如果不丢弃,它仍然可以参与一次更新。
因此每轮有 ceil(103/16)=7 次更新,训练 5 轮共有 35 次更新。参数数量由网络结构决定,不会因为批次从 16 改成 32 就翻倍。
|
这里先假设每批更新一次,没有梯度累积和提前退出。如果丢弃最后一批,则每轮是 6 次更新,而且这一轮并没有使用全部 103 张图。
batch size 同时影响资源与优化
批次变大通常增加中间激活占用,也会改变梯度估计的统计性质。在固定轮数下,批次更大往往意味着更新次数更少,因此“轮数相同”不代表优化过程相同。
小批次不一定更慢,大批次也不一定提高泛化。硬件利用率、数据加载、输入大小和网络结构共同决定速度。遇到内存不足,减小 batch 是常见处理,但应记录这一变化,不能继续假装与原配置完全可比。
梯度累积可以多批累积后更新一次,近似扩大参与更新的样本量。不过带有 BatchNorm、随机层或其他批次相关行为时,不能认为它与直接使用大批次严格等价。
学习率调度改变的是训练过程
固定学习率容易观察;分阶段降低学习率可用于后期细化;余弦调度等方法按某种曲线改变更新尺度。调度器何时调用必须明确:每轮一次和每批一次会得到完全不同的实际曲线。
同一个训练计划,最好把实际学习率写进日志,而不只保存“使用了某调度器”。恢复训练时还要恢复调度器进度,否则可能在训练中途重新回到初始学习率。
warmup 是开始时逐步增加学习率的安排,不是所有小网络必需的步骤。第一版实验先用固定学习率跑通,再一次改变一个因素,观察效果。
统计损失时不要让小批次占过大权重
若每批损失是该批样本的平均值,统计整个集合应按批次样本数加权。最后一批 7 张和前面 16 张不该有相同权重。
|
如果损失使用类别权重、忽略标签或特殊 reduction,分母可能需要按实际损失定义处理。这里的样本加权公式适用于本系列无类别权重的普通分类均值。
训练中记录的平均损失来自参数不断更新的不同时间点,验证损失来自一轮结束后的固定模型,两者不是完全相同条件下的测量。训练还可能用了随机增强,因此不必要求每轮验证损失都大于训练损失。
设计一份最小实验记录
记录数据划分版本、随机种子、模型、批次、优化器、学习率、轮数、实际更新次数和验证结果。随机种子有助于复现,但跨设备、库版本和非确定性内核仍可能产生差别。
比较 batch=16 与 batch=32 时,先说明是在相同轮数、相同更新数还是相同时间预算下比较。三个问题都合理,但回答不同:数据遍历次数相同、优化机会相同、计算预算相同不能混为一谈。
日志中可以按每轮输出训练损失、验证损失、验证准确率、学习率与耗时。不要只保留最后一行,否则无法判断中途是否过拟合、发散或出现明显波动。
练习
- 256 张图,batch=64,训练 20 轮,每批更新一次,共多少次?
- 两批分别是 4 张、1 张,平均损失为 1 和 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。
