从 numpy 创建张量时缺少梯度

Lack of gradient when creating tensor from numpy

有人可以向我解释以下行为吗?

import torch
import numpy as np

z = torch.tensor(np.array([1., 1.]), requires_grad=True).float()

def pre_main(z):
  return z * 3.0
  
  
x = pre_main(z)
x.backward(torch.tensor([1., 1.]))
print(z.grad)

打印:

None

同时:

import torch
import numpy as np

z = torch.tensor([1., 1.], requires_grad=True).float()

def pre_main(z):
  return z * 3.0
  
  
x = pre_main(z)
x.backward(torch.tensor([1., 1.]))
print(z.grad)

打印:

tensor([3., 3.])

为什么从 numpy 数组构建时我的渐变被破坏了?我该如何解决这个问题?

可以先将numpy数组转为tensor,然后指定需要的梯度:

z = torch.from_numpy(np.array([1., 1.])).float() 
z = torch.tensor(z, requires_grad=True)
  
x = pre_main(z)
x.backward(torch.tensor([1., 1.]))
print(z.grad)

多一点挖掘表明,如果在创建初始张量后删除浮点数转换 .float(),则梯度计算有效。如果可行,您可以通过在 numpy 数组本身中指定 dtype 来完成此操作:

z = torch.tensor(np.array([1., 1.], dtype=np.float32), requires_grad=True)
x = pre_main(z)
x.backward(torch.tensor([1., 1.]))
print(z.grad)

您的渐变未被破坏:grad returns None 因为它从未保存在 grad 属性上。这是因为 non-leaf 张量在反向传播过程中没有存储它们的梯度。因此,您在 运行 您的第一个代码段时收到的警告消息:

UserWarning: The .grad attribute of a Tensor that is not a leaf Tensor is being accessed. Its .grad attribute won't be populated during autograd.backward().

当您的 z 张量定义为:

时就是这种情况
>>> z = torch.tensor(np.array([1., 1.]), requires_grad=True).float()
>>> z.is_leaf
False

相比于:

>>> z = torch.tensor([1., 1.], requires_grad=True).float()
>>> z.is_leaf
True

表示后者的梯度值在z.grad.

但请注意:

>>> z = torch.tensor(np.array([1., 1.]), requires_grad=True)
>>> z.is_leaf
True

为了进一步解释这一点:当张量第一次被初始化时,它是一个叶节点(.is_leaf returns True)。一旦你在它上面应用了一个函数(这里 .float() 是一个 in-place 运算符)它就不再是叶子了,因为它有 parents in计算图。

所以真的,没有什么可以修复...不过你可以做的是确保在调用向后传递时将渐变保存在 z.grad 上。所以,第二个问题归结为如何store/accessnon-leaf节点上的梯度?


现在,如果您想在 .backward() 调用时存储渐变。您可以按照警告消息中的说明使用 retain_grad()

z = torch.tensor(np.array([1., 1.]), requires_grad=True).float()
z.retain_grad()

或者,因为我们希望它是叶节点,所以通过使用 FloatTensornumpy.array 转换为 torch.Tensor 来解决它:

z = torch.FloatTensor(np.array([1., 1.]))
z.requires_grad=True

或者,您可以坚持使用 torch.tensor 并提供 dtype:

z = torch.tensor(np.array([1., 1.]), dtype=torch.float64, requires_grad=True)