在训练的时候增大了批次大小,然后学习率不变,期待能获得相似的训练结果并且缩短训练时间的。但是事实却是训练时间确实有缩短,但损失函数的下降却不如调整之前那么好了。这意味着更改每次传入的批次大小,即使学习率没变(随机种子这些也都没变),但由于间接的影响,模型在同一个数据集上的“学习能力”却发生了变化。

增大Batch Size之后发生了什么?

那在其他超参数不变的时候,增大Batch Size会发生什么呢?一个显著的变化就是模型每次会在一个batch里面加载更多的样本,然后前向传播之后再反向传播计算平均损失。也就意味着增大Batch Size,会使得损失方差更小,因为它每个批次对更多的样本进行了平均。

从一个极端的角度看,设置batch_size=1时,如果有一个错误标注的样本被混入数据集中了,那对于已经学到一些分类特征的模型来说,恰好加载到它的那一个批次损失将会非常大;在加载下一个正常样本时,损失又会变小到正常水平。反映到损失曲线的变化上就是损失函数突然出现一个锯齿形状的变化。由于batch_size是1,这个非常大的损失是直接作用于模型权重上了,会使得模型在这个批次上处于一个“被误导”的状态。但由于后面的样本大部分都是正确标注的,所以这个误差会被后面的训练纠正。

但当设置batch_size非常大时,那个错误标注样本的损失被大部分的正确样本稀释掉,所以这个误导模型的损失没有产生太大的影响,模型向着一个比较纯净的梯度方向在移动。

但反过来想,如果那个损失大的样本并不是错误标注的样本,只是分类难度高于数据集平均水平的难样本的话,那在batch_size=1时,模型能更好的接受这个难样本的损失然后进行调整;但batch_size非常大的时候,大部分的样本被模型正确分对,难样本虽然损失大,但损失被这个批次的样本稀释后就变小了,似乎模型更难根据这个难样本进行调整了。

上述两种情况的结果是相似的,就是当批量大小增加时,每个批次的损失的方差会变小,模型的损失下降曲线会变得更加光滑

还有一个问题,就是在训练的时候,一般是固定训练集训练的轮次而不是训练的步数(固定训练步数的做法在预训练过程中比较常见,但微调的时候因为数据集大小差异很大,一般是对训练轮数做限制来防止过拟合)。那固定训练的轮次之后,大的批量意味着更少的步数,也就是模型在使用更大的batch_size的时候会经历更少次的权重更新(这也是大批量能加快训练速度的原因之一)。

更大的Batch Size下学习率该怎么变化呢?

那结合这两点来看,大的batch_size让每次损失都更能反映整个数据集上的平均损失了,受异常点的影响更小了,但同时,更大的batch_size也意味着修改权重的机会更少了。从直觉上来讲,训练时用更大批量的设置可能需要更大的学习率,因为它更新的不频繁,而且每个批量损失相对小批量来说更能代表数据集上的真实损失。

可以量化这两者的变化关系吗?

已经有一些研究提出了在使用大的批量代替小批量同时保存训练和泛化精度的办法,Goyal等提出了一个在实验上被证明有效的Linear Scaling Rule

Linear Scaling Rule: When the minibatch size is multiplied by \(k\), multiply the learning rate by \(k\).

这个线性缩放定律就是说batch_size增大了k倍,那学习率相应的也要增加k倍,这样可以匹配在不同batch_size下的训练精度。

当然这只是一种有实验佐证的说法,在深度学习社区也有另外一种观点(来源),就是batch_size增大k倍,那学习率应该增大\(\sqrt{k}\)倍来匹配不同batch_size下的训练精度。

其实到这里,问题就已经有了一个初步的答案,不管是k倍还是\(\sqrt{k}\)倍,都意味着在增大batch_size后我们应当调大学习率使得精度与现在的训练相匹配。

增大学习率后训练失败了

Goyal等在论文里指出,对于大batch_size来说,当网络快速变化的时候,线性缩放规则失效,这通常发生在训练的早期阶段。作者们也给出了解决方案,就是在训练时设计适当的warmup来实现。warmup即预热,在训练开始的时候使用更小的学习率,然后在训练的过程中逐渐将学习率从一个比较小的值提升到预定的学习率上。

所以如果增大batch_size后将学习率调大之后训练的损失飞了,可以试着使用warmup来调整一下早期的学习率。

学习率和批量大小存在关系的其他证据

Smith等的这篇论文Don't Decay the Learning Rate, Increase the Batch Size里指出了在训练过程中降低学习率和增加批量大小都能够在训练集和测试集上获得相同的学习曲线。那也就反过来印证了,增大批量大小就等效于减少学习率了,所以如果其他超参数不变时,如果要增加批量大小,那应该适当加大学习率来抵消这种影响。

我是很佩服这种调查清楚的精神,不过这个作者说在训练期间增加批量大小可以获得更大的并行性和更短的训练时间我有点迷糊。如果显存还有剩的可以用来增大批量大小,那说明一开始那个批量就没有太利用完所有的显存,那可以设计更大的学习率和更大的批量大小来在最开始就充分利用显存来获得更短的训练时间;如果显存没剩的了,增加批量大小好像就OOM了?

不过现在很多大模型训练也依然是降低学习率啦,估计是那时候大批量训练很具有挑战性,这个作者提出的可能是一种折衷的方案,在训练的过程中逐渐增加批量既没有早期大批量训飞loss的风险又可以在后期逐渐把硬件资源利用起来。现在可能通过warmup前期的loss问题缓解了,因此现在的模型依然是最开始就把显存利用起来然后后期缩减学习率的做法。

为什么增大Batch Size呢?

那为什么好好的要增大Batch Size,一般的话是为了更快的训练速度。它的提速主要来源于下面两个方面:

一是可以增加并行性。因为深度学习模型往往都是比较深的,这样下一层的计算就不得不等到上一层的算完才能开始。所以如果每层中的计算没有占满GPU,那就会造成GPU并行效率的浪费。但如果一个批次中有很多样本,一个层的计算已经占满了整个GPU的计算资源,那这样GPU的计算资源就被充分利用了,也就会显得计算快了。

二是可以减少模型权重更新次数。上面说到batch_size大了之后训练的步数就会变小,那对模型权重的更新次数也会少,就把权重更新的时间省下来一些,也会显得训练变快了。

把Batch Size也当作超参数吧!

这是很多深度学习论文的做法。也许会疑惑,如果线性缩放策略真能奏效,那么调batch size可以等效于调学习率。但事实是学习率不变的情况下,调batch_size也能看到模型性能有改动。调学习率可能确实能等效于调批量大小,但如果把批量大小当超参数的话,学习率就能少设置一些候选也能搜索比较大的超参数空间。

所以,因为我们的终极目标是在数据集上获得更好的指标,所以不要不去调批量大小,就当是在调学习率一样从2的倍数开始调,万一效果更好了呢。

大概就这么多了吧,总结一下,如果batch_size增大了,要保持训练的精度不变的话,不妨试试把学习率往上调一些(\(k\)倍或\(\sqrt{k}\)倍)。

参考资料:

  1. 【AI不惑境】学习率和batchsize如何影响模型的性能?
  2. Relation Between Learning Rate and Batch Size
  3. Accurate, Large Minibatch SGD: Training ImageNet in 1 Hour
  4. Don't Decay the Learning Rate, Increase the Batch Size