如何计算大量图像的损失,然后反向传播平均损失并更新网络权重
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()
时释放附加到变量的图形(从而破坏连接的梯度x1
到 y1
、x2
到 y2
等),您可以看到错误消息的含义 - 您尝试多次向后传递损失,但是基础图在第一次通过后被释放。 (当然除非指定retain_graph=True
)
至于您尝试过的具体变体:
第一种方式:在这里,您将永远累积(即总结 - 再次,查看其他 post)梯度,它们(可能)加起来为 inf
。
第二种方式:在这里,您通过执行 loss.data
将 loss
转换为张量,移除 Variable
包装器,从而删除梯度信息(因为只有变量保存梯度)。
第三种方式:在这里,您只对每个 xk, yk
元组执行一次传递,因为您立即执行了反向传播步骤,从而完全避免了上述问题。
解决方案:我没有测试过,但据我所知,解决方案应该非常简单:在每个批次的开头创建一个新的 total_loss
对象,然后将所有损失加起来对象,然后在最后执行 one 最后的反向传播步骤。
我正在执行批处理大小为 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()
时释放附加到变量的图形(从而破坏连接的梯度x1
到 y1
、x2
到 y2
等),您可以看到错误消息的含义 - 您尝试多次向后传递损失,但是基础图在第一次通过后被释放。 (当然除非指定retain_graph=True
)
至于您尝试过的具体变体:
第一种方式:在这里,您将永远累积(即总结 - 再次,查看其他 post)梯度,它们(可能)加起来为 inf
。
第二种方式:在这里,您通过执行 loss.data
将 loss
转换为张量,移除 Variable
包装器,从而删除梯度信息(因为只有变量保存梯度)。
第三种方式:在这里,您只对每个 xk, yk
元组执行一次传递,因为您立即执行了反向传播步骤,从而完全避免了上述问题。
解决方案:我没有测试过,但据我所知,解决方案应该非常简单:在每个批次的开头创建一个新的 total_loss
对象,然后将所有损失加起来对象,然后在最后执行 one 最后的反向传播步骤。