Pytorch - 推断所有图像并逐批反向传播

Ten*_*rye 6 python deep-learning pytorch

我有一个特殊的用例,我必须将推理和反向传播分开:我必须将所有图像和切片输出推断为批次,然后逐批反向传播。我并不需要更新网络的权重。

我将cifar10_tutorial 的片段修改为以下内容来模拟我的问题:j是一个变量,用于表示由我自己的逻辑返回的索引,我想要一些变量的梯度。

for epoch in range(2):  # loop over the dataset multiple times

    for i, data in enumerate(trainloader, 0):
        # get the inputs
        inputs, labels = data
        inputs.requires_grad = True

        # zero the parameter gradients
        optimizer.zero_grad()

        # forward + backward + optimize
        outputs = net(inputs)

        for j in range(4): # j is given by external logic in my own case

            loss = criterion(outputs[j, :].unsqueeze(0), labels[j].unsqueeze(0))

            loss.backward()

            print(inputs.grad.data[j, :]) # what I really want
Run Code Online (Sandbox Code Playgroud)

我收到以下错误:

RuntimeError: 试图再次向后浏览图表,但缓冲区已被释放。第一次向后调用时指定 retain_graph=True 。

我的问题是:

  1. 根据我的理解,这个问题的出现是因为第一回繁殖向后全outputsoutputs[1,:].unsqueeze(0)被释放,第二回繁殖失败。我对吗?

  2. 就我而言,如果我设置了retain_graph=True,根据这篇文章,代码运行会越来越慢吗?

  3. 有没有更好的方法来实现我的目标?

blu*_*nox 3

    \n
  1. 是的,你是对的。outputs当您已经通过第一次(第一次迭代)反向传播时,缓冲区将被释放,并且它将在接下来的时间(循环的下一次迭代)失败,因为此时此计算所需的数据已被删除。

  2. \n
  3. 是的,图表会变得越来越大,因此它可能会变慢,具体取决于 GPU (或 CPU)的使用情况和您的网络。我用过一次,速度慢得多,但这很大程度上取决于您的网络架构。但肯定的是,有内存比没有内存需要更多的内存retain_graph=True

  4. \n
  5. 根据您的身体状况outputslabels体型,您应该能够立即计算出您outputs所有的损失:labels

    \n\n
    criterion(outputs, labels)\n
    Run Code Online (Sandbox Code Playgroud)\n\n

    那么你必须跳过j-循环,这也会使你的代码更快。也许您需要重塑(或view)您的数据,但这应该可以正常工作。

    \n\n

    如果由于某种原因您无法做到这一点,您可以手动总结张量的损失并backward在循环后调用。这应该也可以正常工作,但比上面的解决方案慢。

    \n\n

    所以你的代码看起来像这样:

    \n\n
    # init loss tensor\nloss = torch.tensor(0.0) # move to GPU if you're using one\n\nfor j in range(4):\n    # summing up your loss for every j\n    loss += criterion(outputs[j, :].unsqueeze(0), labels[j].unsqueeze(0))\n    # ...\n# calling backward on the summed loss - getting gradients\nloss.backward()\n# as you call backward now only once on the outputs\n# you shouldn't get any error and you don't have to use retain_graph=True\n
    Run Code Online (Sandbox Code Playgroud)
  6. \n
\n\n

编辑:

\n\n

损失的累积和稍后向后调用是完全等价的,这是一个有和没有累积损失的小例子:

\n\n

首先创建一些数据data

\n\n
# w in this case will represent a very simple model\n# I leave out the CE and just use w to map the output to a scalar value\nw = torch.nn.Linear(4, 1)\ndata = [torch.rand(1, 4) for j in range(4)]\n
Run Code Online (Sandbox Code Playgroud)\n\n

data好像:

\n\n
[tensor([[0.4593, 0.3410, 0.1009, 0.9787]]),\n tensor([[0.1128, 0.0678, 0.9341, 0.3584]]),\n tensor([[0.7076, 0.9282, 0.0573, 0.6657]]),\n tensor([[0.0960, 0.1055, 0.6877, 0.0406]])]\n
Run Code Online (Sandbox Code Playgroud)\n\n

让我们首先像您正在做的那样,j分别为每个迭代调用向后:

\n\n
# code for directly applying backward\n# zero the weights layer w\nw.zero_grad()\nfor j, inp in enumerate(data):\n    # activate grad flag\n    inp.requires_grad = True\n    # remove / zero previous gradients for inputs\n    inp.grad = None\n    # apply model (only consists of one layer in our case)\n    loss = w(inp)\n    # calling backward on every output separately\n    loss.backward()\n    # print out grad\n    print('Input:', inp)\n    print('Grad:', inp.grad)\n    print()\nprint('w.weight.grad:', w.weight.grad)\n
Run Code Online (Sandbox Code Playgroud)\n\n

这是每个输入的打印输出以及模型各自的梯度和梯度。在我们的简化案例中的层w

\n\n
Input: tensor([[0.4593, 0.3410, 0.1009, 0.9787]], requires_grad=True)\nGrad: tensor([[-0.0999,  0.2665, -0.1506,  0.4214]])\n\nInput: tensor([[0.1128, 0.0678, 0.9341, 0.3584]], requires_grad=True)\nGrad: tensor([[-0.0999,  0.2665, -0.1506,  0.4214]])\n\nInput: tensor([[0.7076, 0.9282, 0.0573, 0.6657]], requires_grad=True)\nGrad: tensor([[-0.0999,  0.2665, -0.1506,  0.4214]])\n\nInput: tensor([[0.0960, 0.1055, 0.6877, 0.0406]], requires_grad=True)\nGrad: tensor([[-0.0999,  0.2665, -0.1506,  0.4214]])\n\nw.weight.grad: tensor([[1.3757, 1.4424, 1.7801, 2.0434]])\n
Run Code Online (Sandbox Code Playgroud)\n\n

现在,我们不再为每次迭代j向后调用一次,而是累积值并调用backward总和并比较结果:

\n\n
# init tensor for accumulation\nloss = torch.tensor(0.0)\n# zero layer gradients\nw.zero_grad()\nfor j, inp in enumerate(data):\n    # activate grad flag\n    inp.requires_grad = True\n    # remove / zero previous gradients for inputs\n    inp.grad = None\n    # apply model (only consists of one layer in our case)\n    # accumulating values instead of calling backward\n    loss += w(inp).squeeze()\n# calling backward on the sum\nloss.backward()\n\n# printing out gradients \nfor j, inp in enumerate(data):\n    print('Input:', inp)\n    print('Grad:', inp.grad)\n    print()\nprint('w.grad:', w.weight.grad)\n
Run Code Online (Sandbox Code Playgroud)\n\n

让我们看一下结果:

\n\n
Input: tensor([[0.4593, 0.3410, 0.1009, 0.9787]], requires_grad=True)\nGrad: tensor([[-0.0999,  0.2665, -0.1506,  0.4214]])\n\nInput: tensor([[0.1128, 0.0678, 0.9341, 0.3584]], requires_grad=True)\nGrad: tensor([[-0.0999,  0.2665, -0.1506,  0.4214]])\n\nInput: tensor([[0.7076, 0.9282, 0.0573, 0.6657]], requires_grad=True)\nGrad: tensor([[-0.0999,  0.2665, -0.1506,  0.4214]])\n\nInput: tensor([[0.0960, 0.1055, 0.6877, 0.0406]], requires_grad=True)\nGrad: tensor([[-0.0999,  0.2665, -0.1506,  0.4214]])\n\nw.grad: tensor([[1.3757, 1.4424, 1.7801, 2.0434]])\n
Run Code Online (Sandbox Code Playgroud)\n\n

比较结果时我们可以看到两者是相同的。
这是一个非常简单的示例,但是我们可以看到,调用backward()每个张量并对张量求和,然后调用backward()对于输入和权重的结果梯度而言是等效的。

\n\n

当您按3中所述同时对所有j使用 CE 时,您可以使用该标志来归档与上面相同的行为,并对 CE 值进行求和,默认值为 \xe2\x80\x98mean\xe2\x80\x99,这可能会导致略有不同的结果。reduction='sum'

\n