如何计算大量图像的损失,然后反向传播平均损失并更新网络权重

how to calculate loss over a number of images and then back propagate the average loss and update network weight

我正在执行批处理大小为 1 的任务,即每个批处理仅包含 1 张图像。所以我必须做手动批处理:当累计损失的数量达到一个数时,平均损失,然后进行反向传播。 我的原始代码是:

real_batchsize = 200

for epoch in range(1, 5):
    net.train()

    total_loss = Variable(torch.zeros(1).cuda(), requires_grad=True)

    iter_count = 0
    for batch_idx, (input, target) in enumerate(train_loader):

        input, target = Variable(input.cuda()), Variable(target.cuda())
        output = net(input)

        loss = F.nll_loss(output, target)

        total_loss = total_loss + loss

        if batch_idx % real_batchsize == 0:
            iter_count += 1

            ave_loss = total_loss/real_batchsize
            ave_loss.backward()
            optimizer.step()

            if iter_count % 10 == 0:
                print("Epoch:{}, iteration:{}, loss:{}".format(epoch,
                                                           iter_count,
                                                           ave_loss.data[0]))
            total_loss.data.zero_() 
            optimizer.zero_grad()

这段代码会给出错误信息

RuntimeError: Trying to backward through the graph a second time, but the buffers have already been freed. Specify retain_graph=True when calling backward the first time.

我试过下面的方法,

第一种方式(失败)

我阅读了一些关于此错误消息的 post,但无法完全理解。把ave_loss.backward()改成ave_loss.backward(retain_graph=True)防止报错,但是损失没有改善很快就变成了nan

第二种方式(失败)

我也试过改total_loss = total_loss + loss.data[0],这样也能防止报错信息。但损失总是一样的。所以一定是哪里出了问题。

第三条路(成功)

按照 this post 中的说明,对于每张图像的损失,我将损失除以 real_batchsize 并对其进行反向传播。当输入图像的数量达到 real_batchsize 时,我使用 optimizer.step() 进行一次参数更新。随着训练过程的进行,损失正在缓慢减少。但是训练速度真的很慢,因为我们对每张图片都进行了反向传播。

我的问题

我的错误消息是什么意思?另外,为什么第一种方式和第二种方式不起作用?如何正确编写代码,使我们可以每 real_batchsize 张图像反向传播梯度并更新一次梯度,从而使训练速度更快?我知道我的代码几乎是正确的,但我只是不知道如何更改它。

您在这里遇到的问题与 PyTorch 如何在不同的传递过程中累积梯度有关。 (有关类似问题的另一个 post,请参阅 ) 那么让我们看看当您使用以下形式的代码时会发生什么:

loss_total = Variable(torch.zeros(1).cuda(), requires_grad=True)
for l in (loss_func(x1,y1), loss_func(x2, y2), loss_func(x3, y3), loss_func(x4, y4)):
    loss_total = loss_total + l
    loss_total.backward()

在这里,当 loss_total 在不同的迭代中具有以下值时,我们进行反向传播:

total_loss = loss(x1, y1)
total_loss = loss(x1, y1) + loss(x2, y2)
total_loss = loss(x1, y1) + loss(x2, y2) + loss(x3, y3)
total_loss = loss(x1, y1) + loss(x2, y2) + loss(x3, y3) + loss(x4, y4)

所以当你每次在 total_loss 上调用 .backward() 时,你实际上在 loss(x1, y1) 上调用了 .backward() 四次! (并且 loss(x2, y2) 三次,等等)。

将其与另一个 post 中讨论的内容相结合,即为了优化内存使用,PyTorch 将在调用 .backward() 时释放附加到变量的图形(从而破坏连接的梯度x1y1x2y2 等),您可以看到错误消息的含义 - 您尝试多次向后传递损失,但是基础图在第一次通过后被释放。 (当然除非指定retain_graph=True

至于您尝试过的具体变体: 第一种方式:在这里,您将永远累积(即总结 - 再次,查看其他 post)梯度,它们(可能)加起来为 inf。 第二种方式:在这里,您通过执行 loss.dataloss 转换为张量,移除 Variable 包装器,从而删除梯度信息(因为只有变量保存梯度)。 第三种方式:在这里,您只对每个 xk, yk 元组执行一次传递,因为您立即执行了反向传播步骤,从而完全避免了上述问题。

解决方案:我没有测试过,但据我所知,解决方案应该非常简单:在每个批次的开头创建一个新的 total_loss 对象,然后将所有损失加起来对象,然后在最后执行 one 最后的反向传播步骤。