【问题标题】:Getting pytorch backward's RuntimeError: Trying to backward through the graph a second time... when slicing a tensor [duplicate]让 pytorch 向后运行时错误:尝试第二次向后遍历图形...切片张量时 [重复]
【发布时间】:2021-11-19 03:53:57
【问题描述】:

运行代码 sn-p (PyTorch 1.7.1; Python 3.8),

import numpy as np
import torch

def batch_matrix(vector_pairs, factor=2):
    baselen = len(vector_pairs[0]) // factor
    split_batch = []

    for j in range(factor):
        for i in range(factor):
            start_j = j * baselen
            end_j = (j+1) * baselen if j != factor - 1 else None
            start_i = i * baselen
            end_i = (i+1) * baselen if i != factor - 1 else None

            mini_pairs = vector_pairs[start_j:end_j, start_i:end_i, :]
            split_batch.append(mini_pairs)
    return split_batch

def concat_matrix(vectors_):
    vectors = vectors_.clone()
    seq_len, dim_vec = vectors.shape
    project_x = vectors.repeat((1, 1, seq_len)).reshape(seq_len, seq_len, dim_vec)
    project_y = project_x.permute(1, 0, 2)
    matrix = torch.cat((project_x, project_y), dim=-1)
    matrix_ = matrix.clone()

    return matrix_

if __name__ == "__main__":
    vector_list = []
    for i in range(10):
        vector_list.append(torch.randn((5,), requires_grad=True))
    vectors = torch.stack(vector_list, dim=0)
    pmatrix = concat_matrix(vectors)

    factor = np.ceil(vectors.shape[0]/6).astype(int)
    batched_feats = batch_matrix(pmatrix, factor=factor)

    for i in batched_feats:
        i = i + 5
        print(i.shape)
        summed = torch.sum(i)
        summed.backward()

我得到如下输出和错误:

torch.Size([5, 5, 10])
torch.Size([5, 5, 10])
Traceback (most recent call last):
  File "/home/user/PycharmProjects/project/run.py", line 43, in <module>
    summed.backward()
  File "/home/user/anaconda3/envs/diff/lib/python3.8/site-packages/torch/tensor.py", line 221, in backward
    torch.autograd.backward(self, gradient, retain_graph, create_graph)
  File "/home/user/anaconda3/envs/diff/lib/python3.8/site-packages/torch/autograd/__init__.py", line 130, in backward
    Variable._execution_engine.run_backward(
RuntimeError: Trying to backward through the graph a second time, but the saved intermediate results have already been freed. Specify retain_graph=True when calling backward the first time.

我已阅读有关该问题的所有现有帖子,但自己无法解决。在backward() 中传递retain_graph=True 修复了提供的sn-p 中的问题,但是,sn-p 只是大型网络的一个过度简化的版本,其中retain_graph=True 将错误更改为以下内容:

RuntimeError:梯度计算所需的变量之一已被inplace操作修改:[torch.FloatTensor [3000, 512]],即TBackward的输出0,版本3;而是预期的版本 2。提示:使用 torch.autograd.set_detect_anomaly(True) 启用异常检测以查找未能计算其梯度的操作。

我尝试设置torch.autograd.set_detect_anomaly(True) 并确定故障点,但我尝试的所有方法都失败了,错误仍然存​​在。

我怀疑如果我能理解当前情况下的错误原因,那么它将帮助我在实际代码库中解决这个错误。

因此,我想了解为什么backward()batched_feats 中的前两个张量工作正常,而对第三个张量却失败了?如果有人可以帮助我查看已释放的中间结果的重用情况,我将不胜感激。

非常感谢!

【问题讨论】:

  • @bobcat 感谢您的链接。但是,我已经看到了这个问题,即使它是相同的错误,但上下文是不同的。这个问题有一个 GRU,它的状态,没有被分离,需要向后两次。这不适用于我的问题。在我的例子中,我不明白为什么在一个释放的张量上调用了两次 back。
  • 你用的不是GRU,但原理是一样的。

标签: python deep-learning pytorch autograd


【解决方案1】:

在反向传播之后,叶节点的梯度存储在它们的Tensor.grad 属性中。默认情况下释放非叶节点的梯度(即错误所指的中间结果),因为 PyTorch 假设您不需要它们。在您的示例中,您的叶节点是从 torch.randn() 创建的 vector_list 中的叶节点。

默认情况下,连续多次调用backward() 会通过求和累加梯度(这对循环神经网络很有用)。当现有的中间结果已被释放时,这是有问题的;叶节点的梯度没有;并且对backward() 的调用涉及一些与之前对backward() 的调用相同的叶节点和中间结果。这是您面临的问题;您的一些张量切片引用了相同的基础张量,并且您没有将调用 backward() 之间的所有相关梯度归零,但您隐式地将中间梯度归零。

如果您希望通过求和累积叶节点中的梯度,只需像这样向后调用:summed.backward(retain_graph = True)

但是,如果您希望独立计算批次的梯度(而不是 w.r.t. vector_list 中的叶节点),那么您可以在每次迭代开始时分离批次。这将防止梯度通过它们一直传播到vector_list 中的公共叶节点(即它们自己成为自己图中的叶节点)。分离张量会禁用渐变,因此您必须手动重新启用它们:

for i in batched_feats:
    i = i.detach()
    i.requires_grad = True
    j = i + 5
    print(j.shape)
    summed = torch.sum(j)
    summed.backward()
    print(i.grad) # Prints the gradients stored in i

这就是一些数据加载器的工作方式;他们从磁盘加载数据,将它们转换为张量,执行增强/其他预处理,然后分离它们,以便它们可以用作新计算图中的叶节点。如果应用程序开发人员想要计算梯度 w.r.t.数据张量,它们不必保存中间结果,因为数据张量已被分离并因此用作叶节点。

【讨论】:

  • 感谢您的回答。我想知道是否可以使用张量的不同不相交切片作为不同批次的输入。我面临的问题是,由于梯度仅与原始张量(而不是切片)相关联,因此在批量使用第一个切片后,中间结果被释放,并且我得到了两次向后执行的错误。
  • 我的主网如下。输入通过特征提取器模型。由于可变长度的输入序列,生成的特征在时间维度上可以是可变长度的。如果特征的长度(在时间维度上)小于阈值,我将它们作为一批传递给 LSTM 模型,否则我将特征张量分成 4 个不相交的部分,并将它们作为 4 个批次发送到 LSTM。 LSTM 和特征提取器需要联合训练,所以我希望梯度流过切片。如何避免两次后向错误,不同批次使用特征切片
  • retain_graph=True 没有解决主网问题。我在使用它时收到RuntimeError: one of the variables needed for gradient computation has been modified by an inplace operation ... 错误。我怀疑这又是由于在循环中计算切片并分配给相同的变量mini_pairs。我尝试克隆切片,但没有帮助。我已经被这个问题困扰了很长时间,并且已经尽我所能尝试了一切。你是唯一一个花时间回答我问题的好心陌生人。如果您能进一步指导我,我将不胜感激。非常感谢!
  • 我在运行retain_graph=True 提供的 sn-p 时没有遇到任何错误,因此运行时错误肯定是在其他地方引起的(可能在特征提取器中?)您可以从替换开始用替代方法就地操作,看看错误是否消失。我也不认为这里进行的任何克隆都有任何帮助。最后,如果您使用retain_graph=True 运行,请在使用完它们后格外小心地将梯度归零(例如optimizer.zero_grad())。
猜你喜欢
  • 2021-03-11
  • 2021-11-18
  • 2020-11-07
  • 2023-02-26
  • 1970-01-01
  • 2020-10-06
  • 1970-01-01
  • 2020-07-15
  • 2018-06-24
相关资源
最近更新 更多