针对小数据集的PyTorch梯度累积的最后步骤

我正在使用一个相对较小的数据集训练一个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的值以反映这个较小的微批次大小!

我提出了这个修改后的代码,将训练循环分成两个:一个是外部循环在小批次上,另一个是内部循环在每个小批次上累积梯度。注意,如何使用iterDataLoader帮助将训练循环分成两个:

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 

Related Posts

使用LSTM在Python中预测未来值

这段代码可以预测指定股票的当前日期之前的值,但不能预测…

如何在gensim的word2vec模型中查找双词组的相似性

我有一个word2vec模型,假设我使用的是googl…

dask_xgboost.predict 可以工作但无法显示 – 数据必须是一维的

我试图使用 XGBoost 创建模型。 看起来我成功地…

ML Tuning – Cross Validation in Spark

我在https://spark.apache.org/…

如何在React JS中使用fetch从REST API获取预测

我正在开发一个应用程序,其中Flask REST AP…

如何分析ML.NET中多类分类预测得分数组?

我在ML.NET中创建了一个多类分类项目。该项目可以对…

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注