RuntimeError: Trying to backward through the graph a second time (or directly access saved variable

用pytorch的时候发生了这个错误,写下来避免以后再次入坑。感谢这次坑让我对预训练模型的使用有了更清楚的认识。

RuntimeError: Trying to backward through the graph a second time (or directly access saved variables after they have already been freed). Saved intermediate values of the graph are freed when you call .backward() or autograd.grad(). Specify retain_graph=True if you need to backward through the graph a second time or if you need to access saved variables after calling backward.

简单说一下问题就是因为某个带有梯度信息的变量在被执行了一次后,这些梯度信息就被计算图释放掉了,而我们的代码却尝试第二次反向传播的时候来访问这些变量(梯度信息)。

原因

每个人的原因也许不同。
我这里的原因就是将Embedding写在了训练模型的循环之外。
下面是我的错误代码,也就是最开始的代码示例。
可以看到首先对整个词典进行Embedding再去训练,就会出现上面的错误。

net = nn.Linear(32, 2)
Loss = nn.CrossEntropyLoss()
sample = torch.tensor([[1, 2, 3, 3],
                       [3, 2, 1, 5],
                       [4, 5, 9, 3]])
target = torch.ones((12,)).to(torch.long)  # 每个词的类别
print(sample)
embedding = nn.Embedding(10, 32)
embed_sample = embedding(sample)  # torch.Size([3, 4, 32])

net.train()
optimizer = torch.optim.Adam(net.parameters(), lr=1e-3)
for i in range(100):
    pred = net(embed_sample)    # torch.Size([3, 4, 2])
    pred = pred.reshape(-1, 2)  # torch.Size([12, 2])
    loss = Loss(pred, target)   # 计算损失
    optimizer.zero_grad()       # 清零梯度
    loss.backward()             # 反向传播
    optimizer.step()            # 更新梯度
    print(i+1, loss)

#输出
---------------------------------------------------------------------------
1 tensor(0.7125, grad_fn=<NllLossBackward>)
RuntimeError                              Traceback (most recent call last)
D:Tempipykernel_83122990520637.py in <cell line: 3>()
      7     #sum_loss                   #一个epoch所有损失和
      8     optimizer.zero_grad()       #清零梯度
----> 9     loss.backward()             #反向传播
     10     optimizer.step()            #更新梯度
     11     print(i+1,loss)

E:anacondalibsite-packagestorch_tensor.py in backward(self, gradient, retain_graph, create_graph, inputs)
    253                 create_graph=create_graph,
    254                 inputs=inputs)
--> 255         torch.autograd.backward(self, gradient, retain_graph, create_graph, inputs=inputs)
    256 
    257     def register_hook(self, hook):

E:anacondalibsite-packagestorchautograd__init__.py in backward(tensors, grad_tensors, retain_graph, create_graph, grad_variables, inputs)
    145         retain_graph = create_graph
    146 
--> 147     Variable._execution_engine.run_backward(
    148         tensors, grad_tensors_, retain_graph, create_graph, inputs,
    149         allow_unreachable=True, accumulate_grad=True)  # allow_unreachable flag

RuntimeError: Trying to backward through the graph a second time (or directly access saved variables after they have already been freed). Saved intermediate values of the graph are freed when you call .backward() or autograd.grad(). Specify retain_graph=True if you need to backward through the graph a second time or if you need to access saved variables after calling backward.

这就是因为这个词嵌入是静态的,我们第一次反向传播的时候已经将它释放掉了,所以进入第二次循环进行反向传播的时候,就报错了。

解决方法

解决方法就很容易了,我们只需要将它移到循环内部就好了。向下面代码所示。

net = nn.Linear(32, 2)
Loss = nn.CrossEntropyLoss()
sample = torch.tensor([[1, 2, 3, 3],
                       [3, 2, 1, 5],
                       [4, 5, 9, 3]])
target = torch.ones((12,)).to(torch.long)  # 每个词的类别
print(sample)
embedding = nn.Embedding(10, 32)
net.train()
optimizer = torch.optim.Adam(net.parameters(), lr=1e-3)
for i in range(100):
    #写到这里就好了
    embed_sample = embedding(sample)  #torch.Size([3, 4, 32])  
    pred = net(embed_sample)    # torch.Size([3, 4, 2])
    pred = pred.reshape(-1, 2)  # torch.Size([12, 2])
    loss = Loss(pred, target)   # 计算损失
    optimizer.zero_grad()       # 清零梯度
    loss.backward()             # 反向传播
    optimizer.step()            # 更新梯度
    print(i+1, loss)

Embedding不训练?

这样做导致的问题就是Embedding()中的参数不参与训练了(可自行验证),至于为什么,
因为optimizer = torch.optim.Adam(net.parameters(), lr=1e-3)中并没有添加embedding的参数。
解决方法就是将embedding的参数加进去。
optimizer =torch.optim.Adam(list(embedding.parameters())+list(net.parameters()), lr=1e-3)

(水一下代码)

net = nn.Linear(32, 2)
Loss = nn.CrossEntropyLoss()
sample = torch.tensor([[1, 2, 3, 3],
                       [3, 2, 1, 5],
                       [4, 5, 9, 3]])
target = torch.ones((12,)).to(torch.long)  # 每个词的类别
print(sample)
embedding = nn.Embedding(10, 32)
print(list(embedding.parameters()))
net.train()
optimizer = torch.optim.Adam(list(embedding.parameters())+list(net.parameters()), lr=1e-3)
for i in range(100):
    #写到这里就好了
    embed_sample = embedding(sample)  #torch.Size([3, 4, 32])  
    pred = net(embed_sample)    # torch.Size([3, 4, 2])
    pred = pred.reshape(-1, 2)  # torch.Size([12, 2])
    loss = Loss(pred, target)   # 计算损失
    optimizer.zero_grad()       # 清零梯度
    loss.backward()             # 反向传播
    optimizer.step()            # 更新梯度
    print(i+1, loss)
print(list(embedding.parameters())) #对比一下之前的

Embedding直接加入到Net中

看完上面这些,我想大家都能想到的,就是直接将Embedding层直接加入到net

net = nn.Sequential(nn.Embedding(10, 32),
                    nn.Linear(32, 2))
Loss = nn.CrossEntropyLoss()
sample = torch.tensor([[1, 2, 3, 3],
                       [3, 2, 1, 5],
                       [4, 5, 9, 3]])
target = torch.ones((12,)).to(torch.long)  # 每个词的类别
print(sample)
print(list(net[0].parameters()))  #就是Embedding层的参数
net.train()
optimizer = torch.optim.Adam(net.parameters(), lr=1e-3)
for i in range(100):
    #写到这里就好了
    pred = net(sample)    # torch.Size([3, 4, 2])
    pred = pred.reshape(-1, 2)  # torch.Size([12, 2])
    loss = Loss(pred, target)   # 计算损失
    optimizer.zero_grad()       # 清零梯度
    loss.backward()             # 反向传播
    optimizer.step()            # 更新梯度
    print(i+1, loss)
print(list(net[0].parameters())) #对比一下之前的

启发——不使用Embedding的梯度信息

如果我现在加载一个预训练模型,而且不想它参与更新梯度
那我直接就在optimizer = torch.optim.Adam(net.parameters(), lr=1e-3)中不加这个参数就好了。

但是这样的话,这个预训练模型的参数还是会参与到反向传播的过程中(不然为什么最开始的时候会报错呢),这样的话其实增加了电脑的开销了,我们想让这个预训练模型不参与到反向传播过程,也就是让它没有梯度信息就好了。

方法1:

1.使用 .detach 使得输入我们net的词嵌入向量没有预训练模型的梯度信息就好了。(我这里的话就是net(embed_sample.detach()))

(再水水代码)

net = nn.Linear(32, 2)
Loss = nn.CrossEntropyLoss()
sample = torch.tensor([[1, 2, 3, 3],
                       [3, 2, 1, 5],
                       [4, 5, 9, 3]])
target = torch.ones((12,)).to(torch.long)  # 每个词的类别
print(sample)
embedding = nn.Embedding(10, 32)
embed_sample = embedding(sample)  #torch.Size([3, 4, 32])
net.train()
optimizer = torch.optim.Adam(net.parameters(), lr=1e-3)
for i in range(100):
    #写到这里就好了
    pred = net(embed_sample.detach())    # torch.Size([3, 4, 2])
    pred = pred.reshape(-1, 2)  # torch.Size([12, 2])
    loss = Loss(pred, target)   # 计算损失
    optimizer.zero_grad()       # 清零梯度
    loss.backward()             # 反向传播
    optimizer.step()            # 更新梯度
    print(i+1, loss)

方法2:

2.使用 with torch.no_grad() 使得我们使用预训练模型生成词嵌入向量的时候就不保存梯度信息。

net = nn.Linear(32, 2)
Loss = nn.CrossEntropyLoss()
sample = torch.tensor([[1, 2, 3, 3],
                       [3, 2, 1, 5],
                       [4, 5, 9, 3]])
target = torch.ones((12,)).to(torch.long)  # 每个词的类别
print(sample)
embedding = nn.Embedding(10, 32)
with torch.no_grad():
    embed_sample = embedding(sample)  #torch.Size([3, 4, 32])
net.train()
optimizer = torch.optim.Adam(net.parameters(), lr=1e-3)
for i in range(100):
    #写到这里就好了
    pred = net(embed_sample)    # torch.Size([3, 4, 2])
    pred = pred.reshape(-1, 2)  # torch.Size([12, 2])
    loss = Loss(pred, target)   # 计算损失
    optimizer.zero_grad()       # 清零梯度
    loss.backward()             # 反向传播
    optimizer.step()            # 更新梯度
    print(i+1, loss)

本图文内容来源于网友网络收集整理提供,作为学习参考使用,版权属于原作者。
THE END
分享
二维码
< <上一篇
下一篇>>