40

已经确定这my_tensor.detach().numpy()是从张量中获取 numpy 数组的正确方法torch

我试图更好地理解为什么。

在刚刚链接的问题的公认答案中,Blupon 指出:

除了实际值定义之外,您还需要将张量转换为不需要梯度的另一个张量。

在他链接到的第一次讨论中,albanD 指出:

这是预期的行为,因为移动到 numpy 会破坏图形,因此不会计算梯度。

如果你实际上不需要梯度,那么你可以显式 .detach() 需要 grad 的 Tensor 得到一个内容相同但不需要 grad 的张量。然后可以将这个其他张量转换为 numpy 数组。

在他链接到的第二次讨论中,apaszke 写道:

变量不能转换为 numpy,因为它们是保存操作历史的张量的包装器,而 numpy 没有这样的对象。您可以使用 .data 属性检索变量持有的张量。然后,这应该可以工作:var.data.numpy()。

我研究了 PyTorch 的自分化库的内部工作原理,但我仍然对这些答案感到困惑。为什么它会破坏图表以移动到 numpy?是因为 numpy 数组上的任何操作都不会在 autodiff 图中被跟踪吗?

什么是变量?它与张量有什么关系?

我觉得这里需要一个彻底的高质量 Stack-Overflow 答案,向尚不了解自分化的 PyTorch 新用户解释其原因。

特别是,我认为通过一个图来说明图表并显示此示例中的断开连接是如何发生的会很有帮助:

import torch

tensor1 = torch.tensor([1.0,2.0],requires_grad=True)

print(tensor1)
print(type(tensor1))

tensor1 = tensor1.numpy()

print(tensor1)
print(type(tensor1))
4

3 回答 3

67

我认为这里要理解的最关键的一点是a和之间的区别: 虽然两个对象都用于存储 n 维矩阵(又名“张量”),但还有一个额外的“层”——它存储了导致关联的n维矩阵。torch.tensornp.ndarray
torch.tensors

因此,如果您只对在矩阵上执行数学运算的高效且简单的方法感兴趣,np.ndarray或者torch.tensor可以互换使用。

然而,torch.tensors 被设计用于梯度下降优化的上下文中,因此它们不仅包含具有数值的张量,而且(更重要的是)包含导致这些值的计算图。然后使用该计算图(使用导数的链式法则)计算损失函数对每个用于计算损失的自变量的导数。

如前所述,np.ndarray对象没有这个额外的“计算图”层,因此,在将 a 转换为时torch.tensornp.ndarray您必须使用命令显式删除张量的计算图。detach()


计算图
从您的评论看来,这个概念有点模糊。我将尝试用一个简单的例子来说明它。
考虑两个(向量)变量的简单函数,x并且w

x = torch.rand(4, requires_grad=True)
w = torch.rand(4, requires_grad=True)

y = x @ w  # inner-product of x and w
z = y ** 2  # square the inner product

如果我们只对 的值感兴趣z,我们不必担心任何图形,我们只需从输入和向前移动,然后计算和。xwyz

但是,如果我们不太关心 的值z,而是想问“什么是给定w最小化 ”的问题,会发生什么?zx
要回答这个问题,我们需要计算wrt的数。 我们怎么能做到这一点? 使用链式法则我​​们知道。也就是说,要计算wrt的梯度,我们需要从back向后移动到计算每一步的操作梯度,因为我们追溯我们的步骤 from tozw

dz/dw = dz/dy * dy/dwzwzwzw. 我们追溯的这条“路径”是计算图z它告诉我们如何计算z导致的输入的导数z

z.backward()  # ask pytorch to trace back the computation of z

我们现在可以检查zwrt的梯度w

w.grad  # the resulting gradient of z w.r.t w
tensor([0.8010, 1.9746, 1.5904, 1.0408])

请注意,这完全等于

2*y*x
tensor([0.8010, 1.9746, 1.5904, 1.0408], grad_fn=<MulBackward0>)

因为dz/dy = 2*ydy/dw = x

沿路径的每个张量都存储其对计算的“贡献”:

z
tensor(1.4061, grad_fn=<PowBackward0>)

y
tensor(1.1858, grad_fn=<DotBackward>)

如您所见,yz不仅存储 或 的“前向”值,<x, w>y**2存储计算图——grad_fn当追溯从z(输出)到w(输入)的梯度时,计算导数(使用链式法则)所需的.

这些grad_fn是必不可少的组成部分torch.tensors,没有它们就无法计算复杂函数的导数。然而,np.ndarrays 根本没有这个能力,他们也没有这个信息。

有关使用函数追溯导数的更多信息,请参阅此答案。backwrd()


由于两者np.ndarraytorch.tensor都有一个共同的“层”来存储数字数组,pytorch 使用相同的存储来节省内存:

numpy() → numpy.ndarray
self将张量作为 NumPy ndarray 返回。这个张量和返回的 ndarray共享相同的底层存储。自张量的变化将反映在 ndarray 中,反之亦然。

另一个方向也以同样的方式工作:

torch.from_numpy(ndarray) → Tensor
从 numpy.ndarray 创建一个张量。
返回的张量和 ndarray共享相同的内存。对张量的修改将反映在 ndarray 中,反之亦然。

因此,当创建np.arrayfromtorch.tensor或反之亦然时,两个对象都引用内存中相同的底层存储。由于np.ndarray不存储/表示与数组关联的计算图,因此当共享 numpy 和 torch 希望引用相同的张量时,应显式删除此图。detach()


请注意,如果您出于某种原因希望仅将 pytorch 用于数学运算而无需反向传播,则可以使用with torch.no_grad()上下文管理器,在这种情况下,不会创建计算图,并且torch.tensors 和np.ndarrays 可以互换使用。

with torch.no_grad():
  x_t = torch.rand(3,4)
  y_np = np.ones((4, 2), dtype=np.float32)
  x_t @ torch.from_numpy(y_np)  # dot product in torch
  np.dot(x_t.numpy(), y_np)  # the same dot product in numpy
于 2020-09-13T10:23:37.630 回答
8

我问,为什么它会破坏图表以移动到 numpy?是因为 numpy 数组上的任何操作都不会在 autodiff 图中被跟踪吗?

是的,新张量不会通过 a 连接到旧张量grad_fn,因此对新张量的任何操作都不会将梯度带回旧张量。

写作my_tensor.detach().numpy()只是说,“我将根据这个张量在一个 numpy 数组中的值进行一些非跟踪计算。”

Dive into Deep Learning (d2l) 教科书有一个很好的部分描述了 detach() 方法,尽管它没有讨论为什么在转换为 numpy 数组之前分离是有意义的。


感谢 jodag 帮助回答这个问题。正如他所说,变量已过时,因此我们可以忽略该评论。

我认为到目前为止我能找到的最佳答案是在jodag 的文档链接中:

要阻止张量跟踪历史记录,您可以调用 .detach() 将其从计算历史记录中分离出来,并防止跟踪未来的计算。

在我在问题中引用的 albanD 的评论中:

如果你实际上不需要梯度,那么你可以显式 .detach() 需要 grad 的 Tensor 得到一个内容相同但不需要 grad 的张量。然后可以将这个其他张量转换为 numpy 数组。

换句话说,该detach方法的意思是“我不想要梯度”,并且不可能通过numpy操作来跟踪梯度(毕竟,这就是 PyTorch 张量的用途!)

于 2020-08-25T18:13:04.077 回答
2

这是张量 -> numpy 数组连接的一个小展示:

import torch
tensor = torch.rand(2)
numpy_array = tensor.numpy()
print('Before edit:')
print(tensor)
print(numpy_array)

tensor[0] = 10

print()
print('After edit:')
print('Tensor:', tensor)
print('Numpy array:', numpy_array)

输出:

Before edit:
Tensor: tensor([0.1286, 0.4899])
Numpy array: [0.1285522  0.48987144]

After edit:
Tensor: tensor([10.0000,  0.4899])
Numpy array: [10.        0.48987144]

第一个元素的值由张量和 numpy 数组共享。在张量中将其更改为 10 也会在 numpy 数组中更改它。

于 2020-09-15T21:17:41.323 回答