我正在做一个批量大小为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:尝试第二次向后遍历图形,但缓冲区已被释放 . 第一次向后调用时指定retain_graph = True .
我试过以下方式,
第一种方式(失败)
我阅读了一些关于此错误消息的帖子,但无法完全理解 . 更改 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
图像进行反向渐变,并更新渐变一次,以使训练速度更快?我知道我的代码几乎是正确的,但我只是不知道如何改变它 .
1 回答
您在这里遇到的问题与PyTorch如何在不同的传递上累积渐变有关 . (有关类似问题的另一篇文章,请参阅here)让我们看看当您拥有以下形式的代码时会发生什么:
这里,当
loss_total
在不同的迭代中具有以下值时,我们执行向后传递:所以当你每次在
total_loss
上调用.backward()
时,你实际上在loss(x1, y1)
上调用了.backward()
四次! (和loss(x2, y2)
三次,等等) .将其与其他帖子中讨论的内容相结合,即为了优化内存使用,PyTorch将在调用
.backward()
时释放附加到变量的图形(从而破坏连接x1
到y1
,x2
到y2
等的渐变),你可以看看错误信息是什么意思 - 你尝试多次向后传递一次丢失,但是在第一次传递之后基础图被释放了 . (当然,除非指定retain_graph=True
)至于你尝试过的具体变化:第一种方式:在这里,你将永远积累(即总结 - 再次,见另一篇文章)渐变,与它们(可能)加起来
inf
. 第二种方式:在这里,通过执行loss.data
将loss
转换为张量,删除Variable
包装器,从而删除梯度信息(因为只有变量保持渐变) . 第三种方式:在这里,你只需要通过每个xk, yk
元组,因为你立即执行一个backprop步骤,完全避免了上述问题 .解决方案:我还没有测试过,但是根据我收集的内容,解决方案应该非常简单:在每个批处理的开头创建一个新的
total_loss
对象,然后将所有损失加总到该对象中,然后执行一个最终的backprop步骤在末尾 .