一万条数据,为什么不等全看完再改参数?

用一份一万条样本的假设数据,拆开 batch、step 和 epoch:一次看多少、多久改一次、整份数据看几遍,为什么它们决定了不同的训练过程。

一万条数据,为什么不等全看完再改参数?

训练模型时,有一个很容易被忽略的问题:既然要根据错误调整参数,为什么不把所有题都做完,算出一个最可靠的方向,再统一修改?只看几十条就动手,不怕被这几十条带偏吗?

我觉得这个困惑比记住 batch、step、epoch 的英文更值得先弄清。反向传播解决的是“这次错误怎样影响每个参数”,但它没有替我们决定:这次究竟包含多少道题,以及算完之后什么时候真的改参数。

假设我们有一万张图片,要训练一个简单分类器。下面所有样本数量和计算结果都是教学例子,不是某个模型的实测成绩。我们先把最常见的情况说清楚:单张显卡、每批计算一次平均损失、每批结束就更新参数,不做梯度累积。

一批、一小步、一整遍,数的是三件事

把一万张图分成每份一百张,这一份就是一个 batch,批大小是一百。模型先用当前参数预测这批图片,再计算平均损失,通过反向传播得到梯度,最后由优化器更新参数。

这里把一次优化器更新叫一个 step。更新后,下一百张图遇到的已经是略有变化的模型。它不是先交一百份各自独立的修改意见,再顺序改一百次,而是先把这批意见合起来,只改一次。

这样做满一百步,整份一万张图刚好被使用一次,才叫一个 epoch。训练十个 epoch,就是把这份数据使用十遍;在上述条件下,一共更新一千次。每遍通常会重新打乱顺序,避免模型总按固定类别或固定难度接触样本。

同一万条数据分成一百个批次,每批一百条;每批更新一次,一整遍共更新一百次

注意这三个计数的单位。batch 关心“一次带来多少样本”,step 关心“参数改了多少次”,epoch 关心“数据走过多少遍”。它们之间能换算,是因为我们提前限定了训练方式,并不是三个词可以互相替换。

如果每批改成五百张,一遍只需要二十步;仍然训练十遍,更新次数就从一千变成两百。两次训练都看了十万张次图片,但中途获得调整机会的次数完全不同。只看日志里的“训练十轮”,还不能断言它们投入了相同的优化过程。

一万这个数刚好能整除。若有一万零三条数据,每批一百,保留最后三条的小批次,一遍是一百零一步;如果选择丢掉不足一批的尾部,一遍就只有一百步。读日志时,“一遍”还得结合采样器和数据加载器的实际规则理解。

概念和基本换算可以对照 Google 的机器学习课程。

不等全看完,是因为方向不必每次都算到最精确

全量计算并没有原则上的错误。如果数据很小,完全可以每次看完所有样本,再更新一次。困难在于数据变多以后,每得到一个方向都要付出很长的等待。

可以把梯度想成每道题对参数提出的局部修改意见。这里“局部”很重要:它说的是模型处在当前参数位置时,往哪个方向动一点能让这道题的损失减少,并不是永久有效的建议。

如果批次是从数据里合理随机抽出的,一部分样本的平均意见就能估计全体意见。估计有误差,却可能已经足够让训练往前走。与其花很久计算一个很精确的方向,然后只迈一步,有时不如接受粗一点的方向,早点迈步,再在新位置重新估计。

用一个能手算的极简模型看这个差别。模型只有一个参数 w,无论输入什么都输出 w;四个目标数分别是零、二、四、六。每条样本的损失定义为“预测误差的平方除以二”,那么该样本对 w 的梯度就是 w 减去目标数。

当 w 等于零时,四条梯度分别是零、负二、负四、负六,平均是负三。若学习率是零点一,用全部四条算一次,更新后的 w 就是零点三。

现在把前两条作为一批。在相同起点,它们的平均梯度是负一,更新后 w 变成零点一。接着计算后两条时,必须使用新的零点一:平均梯度变成负四点九,再更新,w 到了零点五九。

四条样本合在一起更新一次得到零点三;分成两批顺序更新两次得到零点五九,第二批在新参数处计算

两种方案看了相同数据,却得到了不同参数。关键不在少算了哪条题,而在第二批到来之前模型已经动过。批大小改变的不只是显存占用,也改变了优化的节奏和路径。

这个例子不能证明两小步永远优于一大步。把顺序换一下、学习率换一下、损失地形换一下,结果都可能改变。它只负责说明:把批大小翻倍、再说“反正数据一样”,不足以证明两次训练等价。

小批次会晃,大批次也有代价

单条题的意见可能非常偏。比如这一小批图片碰巧都很暗,模型会收到一组偏向暗图的信号;另一批恰好都是明亮图片,方向又变了。把更多样本平均起来,通常能减少这种由抽样带来的波动。

但这个好处依赖样本确实带来不同信息。如果一批里塞满近乎重复的图片,数量翻倍不等于信息翻倍。数据本身有系统偏差时,整份数据算得再精确,也只是更精确地沿着有偏的目标走。

小批次下训练损失上下跳动,并不自动说明程序错了:每一步考的题变了,模型位置也变了。要判断训练是否改善,应当配合在固定验证集上、使用一致评估方式得到的结果。不要拿这一批容易题的低损失,直接和下一批难题的高损失比较,就宣布模型退步。

另一方面,大批次常能让硬件一次并行做更多计算,减少某些固定开销被反复支付的次数。但它也要保存更多样本对应的中间结果,显存需求可能增长;达到设备的有效利用范围之后,继续加大,也未必继续提高每秒处理量。

所以“快”至少有三种说法:每秒处理多少样本,跑完一遍要多久,达到同一验证目标总共要多久。第一项变好,不保证第三项变好。某个配置吞吐更高,却需要更多数据或不同的学习率安排才能达到同样效果,最终总耗时仍可能更长。

我会特别警惕只比较每秒步数。一小步处理一百张,一大步处理五百张,两个 step 的工作量本来就不同。比较速度时先统一单位;比较效果时再统一验证标准和训练预算。否则一张很好看的曲线,可能只是换了横轴的含义。

显存装不下,为什么可以分几次算再一起改?

假设想让每次更新参考一百张图,但显存一次只能装下二十五张。一个常见办法是梯度累积:第一小批计算梯度后先不更新,也不清空;接着算第二、第三、第四小批,最后把它们的贡献合起来,再更新一次。

这时一次装入显卡的二十五张,常被叫作 micro-batch。四个小批次合起来,对应一次更新的一百张样本,也就是这里的有效批大小。原来“一个 batch 就是一个 step”的直觉在这里失效了。

四个小批次沿用同一份参数,梯度按比例累加,第四次反向计算结束后才执行一次更新

上面的四个目标数也能验证这个区别。如果前两条和后两条都在 w 等于零处计算,它们的平均梯度分别是负一和负五。两个等大的小批次再取平均,还是负三;只在最后更新一次,就仍得到零点三,而不是零点五九。

差别就在“先不动参数”。梯度累积允许把一大批的计算拆开,但每次更新之间要处理的样本总量仍在那里。它主要解决一次装不下的问题,不会凭空免去剩下那几批的计算。

还有一个容易漏掉的细节:若每个小批次的损失本来就取了平均,四次直接相加会得到四个平均值的和,而不是所有样本的平均值。对四个等大的小批次,通常需要把每次损失除以四,或做等价的梯度归一化;最后不足四批的尾部,还需要按实际情况处理。

若小批次大小不相等,简单“再除以批次数”也不够。二十五条和五条的平均意见不应该各占一半,要按样本数加权。对于按 token 计算损失的语言模型,还要注意实际参与损失计算的有效 token 数,不能只数文本条数。

在损失可按样本相加、归一化一致、参数保持不变等条件下,这种方式能得到与大批次接近的梯度。但带有依赖整批统计量的运算、随机性或不同数值计算顺序时,不能笼统承诺完全相同。PyTorch 的 梯度累积示例 展示了按有效批次安排归一化和更新的方式。

看到训练配置,我会先把账算清楚

现在回到那一万张图片。如果配置写着“每次二十五张,累积四次,训练十遍”,在数据刚好整除且单卡的条件下,每遍有四百次小批次计算、一百次参数更新,总计一千次更新。日志如果显示四千步,先确认它数的是小批次迭代,还是优化器更新,别急着判断训练多跑了四倍。

再看学习率安排。如果学习率每五百次更新降低一次,把批大小加大以后,每遍的更新数减少,同一个“五百”可能落在更晚的 epoch。保存检查点、验证频率和预热长度也可能跟着改变。改批大小时只盯住那个数字,很容易顺手改变整套训练日程。

理解训练代码时,可以先找三个位置:哪里取一批数据,哪里调用反向传播,哪里真正调用优化器的更新方法。再看梯度在哪清空。它们的相对位置,比配置项叫 batch 还是 mini-batch 更能解释实际行为。PyTorch 的参数优化教程 把这几步放在了同一个训练循环里。

如果换成多张卡一起训练,还要分清每张卡的批大小与所有卡合起来的批大小。假设四张卡各处理二十五条互不重复的数据,并且每次更新前正确合并各卡的梯度,这一次更新参考的就是一百条。再累积四次,才是一共四百条。这个乘法依赖具体的并行和采样方式,不能看见“四张卡”就不加条件地套用。

这也解释了为什么把单卡配置原样搬到多卡,效果有时会变。屏幕上每卡的数字没动,但全局每次更新参考的数据量可能已经改变;同样看完一遍,更新次数和原来不同。先算清全局批大小,再讨论学习率是否需要调整,比直接照搬某个固定倍数更可靠。

至于 epoch,也不是越多越好。重复训练会继续压低训练集上的误差,却可能让模型记住这些题的特殊细节,对新题反而变差。是否继续训练,要看验证结果和任务目标,而不是认为“旧题又做了一遍,总该更聪明一点”。数据使用次数记录了投入,没有替我们保证收益。

最后还要留一个边界:epoch 很适合描述一份固定、有限的数据集。若训练不断从流中取新数据,或按比例重复采样,所谓“看完一遍”就未必有唯一含义。这时累计样本数、有效 token 数和更新次数,往往更能说明训练究竟走了多远。

我现在再看这几个词,会先把它们翻成三个问题:一次听多少意见,多久调整一次,旧题重做了几遍。数字都能调大,但它们增加的是不同的东西。知道自己增加了什么,才知道训练变快、变慢或者变得不一样,究竟该从哪里找原因。

评论