【问题标题】:Final step of PyTorch Gradient Accumulation for small datasetsPyTorch Gradient Accumulation 小数据集的最后一步
【发布时间】:2021-04-26 18:35:32
【问题描述】:

我正在一个相对较小的数据集上训练 BERT 模型,并且不能丢失任何标记样本,因为它们必须全部用于训练。由于 GPU 内存的限制,我使用梯度累积来训练更大的批次(例如 32)。根据PyTorch documentation,梯度累加的实现方式如下:

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

        # Accumulates scaled gradients.
        scaler.scale(loss).backward()

        if (i + 1) % iters_to_accumulate == 0:
            # may unscale_ here if desired (e.g., to allow clipping unscaled gradients)

            scaler.step(optimizer)
            scaler.update()
            optimizer.zero_grad()

但是,如果您正在使用例如110 个训练样本,batch size 8 和累积 step 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

        # Accumulates scaled gradients.
        scaler.scale(loss).backward()

        if (i + 1) % iters_to_accumulate == 0 or (i + 1) == len(data):
            # may unscale_ here if desired (e.g., to allow clipping unscaled gradients)

            scaler.step(optimizer)
            scaler.update()
            optimizer.zero_grad()

这是正确的,真的那么简单,还是会有任何副作用?这对我来说似乎很简单,但我以前从未见过它。任何帮助表示赞赏!

【问题讨论】:

  • 嗨 Andrea,据我所知,如果在您的数据加载器中使用 drop_last=False,它将在训练结束时创建一个较小的批次(包含剩余的样本)。因此,我认为在开始这个 for 循环之前将 iter_to_accumulate 增加 1 应该可以解决它。
  • 查看我对 Shai 的回复。我不确定这如何回答我的问题

标签: python machine-learning deep-learning nlp pytorch


【解决方案1】:

正如Lucas Ramos 已经提到的,当使用DataLoader 时,基础数据集的大小不能被批次大小整除,默认行为是具有较小的最后一批:

drop_last(bool,可选)- 如果数据集大小不能被批次大小整除,则设置为 True 以删除最后一个不完整的批次。如果 False 并且数据集的大小不能被批大小整除,则最后一批将更小。 (默认:False

您的计划基本上是结合drop_last=False 实施梯度累积 - 即最后一批比其他所有批次都小。
因此,原则上不同批次大小的训练没有任何问题。

但是,您需要在代码中修复一些问题:
损失是小批量的平均。因此,如果您以通常的方式处理小批量,则无需担心。但是,在累积梯度时,您可以通过将损失除以 iters_to_accumulate 来明确地做到这一点:

loss = loss / iters_to_accumulate

在最后一个 minibatch(较小的大小)中,您需要更改 iter_to_accumulate 的值以反映这个较小的 minibatch 大小!

我提出了这个修改后的代码,将训练循环分为两个:小批量的外循环,以及每个小批量累积梯度的内循环。请注意使用 iter over the DataLoader 如何帮助将训练循环分成两部分:

scaler = GradScaler()

for epoch in epochs: 
    bi = 0  # index batches
    # outer loop over minibatches
    data_iter = iter(data)
    while bi < len(data):
        # determine the range for this batch
        nbi = min(len(data), bi + iters_to_accumulate)
        # inner loop over the items of the mini batch - accumulating gradients
        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)  # divide by the true batch size

            # Accumulates scaled gradients.
            scaler.scale(loss).backward()
        # done mini batch loop - gradients were accumulated, we can make an optimizatino step.
        
        # may unscale_ here if desired (e.g., to allow clipping unscaled gradients)
        scaler.step(optimizer)
        scaler.update()
        optimizer.zero_grad()
        bi = nbi 

【讨论】:

  • 嗨@Shai 我不确定这如何回答我的问题。我已经在使用drop_last = False。但是,我看不出这与我的问题有何关系,即在使用梯度累积时,最后一组批次没有采取权重更新步骤。确保所有样本都包含在一个批次中并且没有一个被丢弃并不能保证在梯度累积期间对该批次进行权重更新。让我知道这是否清楚,否则我将编辑我的问题以澄清这一点,谢谢
  • 非常感谢@Shai 的更新。如果你不介意的话,还有一件事我不明白。首先,在我的示例中(我应该更清楚)变量dataDataLoader 实例,而不是torch.Tensor,所以它不可下标,即我不能做data[bi:nbi],但是这不是必需的,因为DataLoader 上的__iter__ 方法可以轻松处理最后一个较小的小批量。我确实喜欢您更改iter_to_accumulate 的好方法。但是,我不明白您为什么删除了 if 语句并在每次前向传递时都进行梯度更新
  • 抱歉,我明白为什么可以避免使用 if 语句,因为您使用了两个循环。我想我唯一需要解决的是data[bi:nbi] 的问题,它不起作用。也许我可以通过调用list 函数来绕过它,使DataLoader 可下标,即for i, (input, target) in enumerate(list(data)[bi:nbi]): 你觉得这个解决方案怎么样?
  • 剩下的问题是DataLoader实例,即data,以随机顺序返回样本,所以如果我有一个梯度累积步骤,比如4,for循环正在做@第一次是987654350@,然后是data[4:8]data[8:12]等。不能保证所有的训练样本都会被覆盖一次,因为每次取样本都是随机的,所以有的可能会出现不止一次,有些可能永远不会出现
  • @andrea "random order" of DataLoader 表示它随机播放示例,而不是随机绘制。
【解决方案2】:

我很确定我以前见过这样做。查看来自 Pytorch Lightning 的 this code(函数 _accumulated_batches_reached_num_training_batches_reachedshould_accumulate)。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2017-12-05
    • 2021-09-04
    • 2013-12-31
    • 2023-02-15
    • 2020-06-21
    • 2019-09-10
    • 1970-01-01
    相关资源
    最近更新 更多