qinghuan
← 回到知识探索

AI 直觉 / 动手实验

训练十轮,到底看了多少数据、改了几次参数?

用十条数据、每批四条的小账本,分清 epoch、batch 和 step,并把最后不足一批的情况算进去。

展开本文目录
  1. 先动手看看
  2. 整份过一轮,一次拿一批,更新另记一次
  3. 把尾批和尾部更新都写进账本
  4. 两批之后才更新,不等于已经更新两次
  5. 读日志前,先问它在数什么
  6. 记住这三点
  7. 别把例子用过头
  8. 自己试一小步
  9. 查原始资料
  10. 找一份资料继续练
开始前,会这些就够了
  • 能阅读 Python 列表和循环
  • 知道训练会读取数据并调整模型参数
遇到不熟悉的词,先看这 2 个解释
训练轮次
对固定的数据,按既定取数规则走完一轮,通常记为一个 epoch。它描述一轮的范围,不直接等于处理了多少批、更新了多少次参数。
学习率
在常见的梯度更新中,程序先算出参数变化对目标的影响。学习率用来缩放本次调整的幅度,像控制步子大小的设置,不表示一轮读多少数据。
打开完整术语小抄 ↗

有人说“训练了十轮”,有人说“跑了一千步”。这两个数字不能直接比较:一轮有多少条数据、一次拿多少条、拿几次才调整参数,都会影响总工作量。

先把模型想成一个边看例子边修正的程序。本文只给例子分组和记账,不计算梯度、不更新任何模型;借此把常见训练日志里的三种计数拆开。

看了几批,准备更新几次?

把一份固定名单按顺序分批,再把几批合成一次计划更新。每轮结束都结清剩余批次,不留到下一轮;这里仅做计数,不计算梯度或训练模型。

不足一整批的尾部怎么处理?

不勾选时保留尾批。已经保留的批次即使凑不满一次累积,也会在这一轮结束时安排更新。

2 轮合计:样本访问 20 次,数据批次 6 批,计划更新 4 次。

重复轮数
2 轮
累计样本访问
20 次
累计数据批次
6 批
累计计划更新
4 次

样本编号始终为 #1 到 #10。重复读取同一条会再次计为访问,并没有增加不同样本的数量。

每一轮,都按这张账本结清

每轮使用 10 条,形成 3 批,安排 2 次更新。固定名单与规则不变,下面的同一轮计划重复 2 次。

每张大框是一项计划更新,里面的小块是它包含的数据批。编号帮助核对有没有漏算或重复。

  1. 计划更新 18 条样本
    • 第 1 批 · 4 条样本 #1–#4满批
    • 第 2 批 · 4 条样本 #5–#8满批

    4 + 4 = 8 条,汇总 2 批。

    已凑满约定的 2 批,在这里结算。

  2. 计划更新 22 条样本
    • 第 3 批 · 2 条样本 #9–#10保留的尾批,不足整批

    2 = 2 条,汇总 1 批。

    本轮收尾:未凑满约定的 2 批,也在这里结算,不带到下一轮。

这里不计算平均损失,也没有执行优化器。样本数相加只是在记访问量,不是把各批平均值再平均;真实训练需要按样本或有效位置正确加权,还要区分计划更新、跳过更新与参数实际是否改变。本文也没有打乱、重复抽样或多进程分片。

整份过一轮,一次拿一批,更新另记一次

在一份固定、有明确结尾的数据上,按既定取数规则走完一轮,通常记作一个 epoch。一次送去计算的一组例子叫 batch,batch size 是这组有多少条。step 则必须看上下文:可能指处理一批,也可能指执行一次优化器更新;阅读日志时先找它在哪里加一。

下面限定单进程、顺序读取、每条数据一轮最多用一次。十条数据每批四条,保留结尾时分成四、四、二;丢掉不足四条的尾批时,只处理前八条。后一种也可以结束这一轮,但不能说十条都参与了计算。

把尾批和尾部更新都写进账本

本例再约定:每处理两批,安排一次参数更新;每轮结束时,有剩余批次也立即安排一次,不带到下一轮。于是保留尾批时,第一次更新汇总八条,最后一次只汇总两条。这里的 update_samples 记录每次计划更新用了几条。

代码只生成这份计划。第二行改变的是丢弃尾批的规则;第三行把“每轮都结清”的计划重复两轮,sample_visits 数的是累计处理次数,同一条在下一轮再读会再计一次。

小实验 · python
def plan_epoch(samples, batch_size, accumulate, drop_last=False):
    if type(samples) is not int or samples < 0:
        raise ValueError('samples must be a nonnegative integer')
    if any(type(x) is not int or x < 1 for x in (batch_size, accumulate)):
        raise ValueError('batch_size and accumulate must be positive integers')
    if type(drop_last) is not bool:
        raise ValueError('drop_last must be a boolean')

    sizes = [min(batch_size, samples - start)
             for start in range(0, samples, batch_size)]
    if drop_last and sizes and sizes[-1] < batch_size:
        sizes.pop()
    # Every remaining group is settled, including the end-of-epoch remainder.
    update_samples = [sum(sizes[start:start + accumulate])
                      for start in range(0, len(sizes), accumulate)]
    return sizes, update_samples


if __name__ == '__main__':
    for name, drop in [('keep tail', False), ('drop tail', True)]:
        batches, updates = plan_epoch(10, 4, 2, drop)
        print(f'{name}: batches={batches}, update_samples={updates}')
    batches, updates = plan_epoch(10, 4, 2)
    print(f'2 epochs, keep tail: batches={2 * len(batches)}, '
          f'planned_updates={2 * len(updates)}, sample_visits={2 * sum(batches)}')

怎样运行:保存为 training_counts.py,用 Python 3.10 或更新版本运行:python training_counts.py。仅用标准库,不联网,不训练模型。

运行后对照结果
keep tail: batches=[4, 4, 2], update_samples=[8, 2]
drop tail: batches=[4, 4], update_samples=[8]
2 epochs, keep tail: batches=6, planned_updates=4, sample_visits=20

两批之后才更新,不等于已经更新两次

真实训练可以先计算一小批数据对参数的调整信号,也就是梯度,把几批的信号积起来,再交给优化器执行一次更新。这叫梯度累积。数据批次计数每批都增加,更新计数则要等到累积结束;两种 step 的数字从这里开始分开。

“每批四条、积两批”只有凑满时才对应八条。尾批、空数据或不足一个累积窗口都会改变实际数量。本例把尾部也结算是一项明确的选择;真实代码可能跨轮保留或丢弃,需要检查。算平均损失时还要按实际样本或有效位置加权,不能把这个计数程序当作一套梯度实现。

读日志前,先问它在数什么

有些任务的数据一直流入,或者程序主动规定每轮只跑若干批。此时 epoch 更像一个报告区间;例如 Keras 的 steps_per_epoch 可以指定何时结束一轮,不能只看轮数就推断所有数据都被看过。抽样允许重复、多进程分片等情况也超出了本文的顺序取数模型。

一份容易核对的日志应分别写累计处理的样本或有效 token、数据批次、计划更新和实际执行的更新。混合精度训练可能因无效梯度跳过某次更新,本例没有这种机制。即使计数相同,也不能据此断言训练质量、运行时间或参数数值相同。

记住这三点

  • epoch 数轮次,batch 描述一组数据,step 的具体含义要从程序和日志约定确认。
  • 梯度累积让读取批次数与参数更新次数分开,尾批和尾部累积必须说明如何处理。
  • 比较训练投入时,同时记录实际处理量和更新次数;单看“十轮”还不够。

别把例子用过头

  • 本例仅统计单进程、顺序读取的固定数据,没有训练、梯度计算或分布式数据加载。
  • 计划调用更新,不等于参数数值一定改变;跳过更新、零学习率等情况还需另查实际执行。
  • 这里空数据返回空计划,不代表任何训练框架都会接受空训练集;重复访问量也不等于不同样本数。

自己试一小步

把数据改成十三条,每批五条、累计两批后更新,两轮的账应该怎样记?

  1. 先用纸列出每轮各批的大小,分别考虑保留尾批与丢掉尾批。
  2. 沿用每轮结束立即结算的规则,标出每次计划更新包含哪些批次。
  3. 修改代码核对两轮的批次数、计划更新次数与累计样本访问量。

做到这些,就算完成

  • 是否清楚指出了哪部分数据被丢掉,或哪次更新没有凑满?
  • 是否区分了数据批次计数、更新计数和不同样本数?
  • 是否写明尾部不跨轮保留,且没有把计数结果当作训练效果?

查原始资料

想确认一个细节,先看这份资料对应的部分,不必从头读完所有文档。

  • Keras:Model.fit 的 epoch 与 steps_per_epoch (新标签页)↗Keras · 英文

    Model.fit 的数据迭代、epoch 定义以及 steps_per_epoch 对每轮结束位置的约定。

    2026-09-26 已读 fit 签名与 batch_size、epochs、steps_per_epoch 参数。本文未运行 Keras,也没有把其默认批量参数当作所有框架的统一规则。

  • PyTorch 2.14:DataLoader 的自动分批与 drop_last (新标签页)↗PyTorch · 英文

    batch_size 按批读取样本,drop_last 控制不足一批的尾部;多进程 IterableDataset 的行为需要另看约定。

    2026-09-26 已读 Automatic batching、DataLoader 的 batch_size/drop_last 参数及长度估计警告。本文标准库模型只模拟单进程的固定列表计数。

  • PyTorch 2.14:梯度累积与更新时机 (新标签页)↗PyTorch · 英文

    累积多个微批的梯度后再执行更新,混合精度检查可能跳过更新;数据迭代与更新时机不同。

    2026-09-26 已读 Gradient accumulation 小节及相邻的 scaler.step 跳过说明。本文没有实现 AMP 或梯度,尾部结算是本文自定规则,不声称官方示例自动处理尾部。

找一份资料继续练

先选一项,按下面的范围继续学;不必从头读完整门课程。