我正在使用一个相对较小的数据集训练一个BERT模型,并且不能丢弃任何标记样本,因为它们都必须用于训练。由于GPU内存限制,我使用梯度累积来训练更大的批次(例如32)。根据PyTorch文档,梯度累积的实现如下:
scaler = GradScaler()for epoch in epochs: for i, (input, target) in enumerate(data): with autocast(): output = model(input) loss = loss_fn(output, target) loss = loss / iters_to_accumulate # 累积缩放后的梯度。 scaler.scale(loss).backward() if (i + 1) % iters_to_accumulate == 0: # 可以在这里取消缩放(例如,允许剪裁未缩放的梯度) scaler.step(optimizer) scaler.update() optimizer.zero_grad()
然而,如果你使用例如110个训练样本,批次大小为8,累积步数为4(即有效批次大小为32),这种方法只会训练前96个样本(即32 x 3),即浪费了14个样本。为了避免这种情况,我想修改代码如下(注意最后的if语句的变化):
scaler = GradScaler()for epoch in epochs: for i, (input, target) in enumerate(data): with autocast(): output = model(input) loss = loss_fn(output, target) loss = loss / iters_to_accumulate # 累积缩放后的梯度。 scaler.scale(loss).backward() if (i + 1) % iters_to_accumulate == 0 or (i + 1) == len(data): # 可以在这里取消缩放(例如,允许剪裁未缩放的梯度) scaler.step(optimizer) scaler.update() optimizer.zero_grad()
这样做正确吗?是否真的如此简单,或者会有什么副作用?对我来说似乎很简单,但我以前从未见过这种做法。任何帮助都将不胜感激!
回答:
正如@某人已经提到的,当使用DataLoader
时,如果底层数据集的大小不能被批次大小整除,默认行为是最后一个批次较小:
drop_last
(bool, optional) – 设置为True以丢弃最后一个不完整的批次,如果数据集大小不能被批次大小整除。如果为False且数据集大小不能被批次大小整除,则最后一个批次将较小。(默认:False
)
你的计划基本上是实现了梯度累积与drop_last=False
相结合 – 也就是说,最后一个批次比其他批次小。
因此,原则上使用不同的批次大小进行训练并没有错。
然而,你需要在代码中修复一些东西:
损失是平均在小批次上的。因此,如果你按照通常的方式处理小批次,你不需要担心这一点。然而,当累积梯度时,你通过将损失除以iters_to_accumulate
来显式地进行累积:
loss = loss / iters_to_accumulate
在最后一个小批次(较小的尺寸)中,你需要更改iter_to_accumulate
的值以反映这个较小的微批次大小!
我提出了这个修改后的代码,将训练循环分成两个:一个是外部循环在小批次上,另一个是内部循环在每个小批次上累积梯度。注意,如何使用iter
在DataLoader
上帮助将训练循环分成两个:
scaler = GradScaler()for epoch in epochs: bi = 0 # 索引批次 # 外循环在小批次上 data_iter = iter(data) while bi < len(data): # 确定此批次的范围 nbi = min(len(data), bi + iters_to_accumulate) # 内循环在微批次的项目上 - 累积梯度 for i in range(bi, nbi): input, target = data_iter.next() with autocast(): output = model(input) loss = loss_fn(output, target) loss = loss / (nbi - bi) # 除以真正的批次大小 # 累积缩放后的梯度。 scaler.scale(loss).backward() # 完成微批次循环 - 梯度已累积,我们可以进行优化步骤。 # 可以在这里取消缩放(例如,允许剪裁未缩放的梯度) scaler.step(optimizer) scaler.update() optimizer.zero_grad() bi = nbi