为什么我们在 Pytorch 张量上调用 .numpy() 之前先调用 .detach()?

2024-01-06

已经确定的是my_tensor.detach().numpy()是从 a 获取 numpy 数组的正确方法torch tensor. https://stackoverflow.com/questions/55466298/pytorch-cant-call-numpy-on-variable-that-requires-grad-use-var-detach-num

我正在尝试更好地理解原因。

In the 接受的答案 https://stackoverflow.com/a/57014852/1048186对于刚刚链接的问题,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))

我认为理解这里最关键的一点是不同之处之间torch.tensor and np.ndarray:
虽然这两个对象都用于存储 n 维矩阵(又名“张量” https://en.wikipedia.org/wiki/Tensor), torch.tensors有一个额外的“层” - 它存储导致关联的 n 维矩阵的计算图。

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

然而,torch.tensors 的设计目的是在以下情况下使用梯度下降 https://en.wikipedia.org/wiki/Gradient_descent优化,因此它们不仅包含具有数值的张量,还包含(更重要的是)导致这些值的计算图。然后使用该计算图(使用导数的链式法则 https://en.wikipedia.org/wiki/Chain_rule)来计算损失函数相对于用于计算损失的每个自变量的导数。

正如之前所提,np.ndarray对象没有这个额外的“计算图”层,因此,当转换torch.tensor to np.ndarray你必须明确地使用以下方法删除张量的计算图detach()命令。


计算图
从你的comments https://stackoverflow.com/questions/63582590/why-do-we-call-detach-before-calling-numpy-on-a-pytorch-tensor/63869655?noredirect=1#comment112956796_63869655好像这个概念有点模糊。我将尝试用一个简单的例子来说明它。
考虑两个(向量)变量的简单函数,x and 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,我们不需要担心任何图表,我们只需移动forward从输入中,x and w, 计算y进而z.

然而,如果我们不太关心它的价值,会发生什么?z,而是想问这个问题“什么是w that 最小化 z对于给定的x"?
为了回答这个问题,我们需要计算衍生物 of z w.r.t w.
我们怎样才能做到这一点?
使用链式法则 https://en.wikipedia.org/wiki/Chain_rule我们知道dz/dw = dz/dy * dy/dw。也就是说,计算梯度z w.r.t w我们需要搬家backward https://stackoverflow.com/a/57249287/1714410 from z回到w计算gradient我们追踪每一步的操作back https://stackoverflow.com/a/57249287/1714410我们的脚步从z to w。我们追溯的这条“路径”就是计算图 of z它告诉我们如何计算导数zw.r.t 导致的输入z:

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

我们现在可以检查梯度z w.r.t 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>)

since dz/dy = 2*y and dy/dw = x.

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

z
tensor(1.4061, grad_fn=<PowBackward0>)

And

y
tensor(1.1858, grad_fn=<DotBackward>)

如你看到的,y and z不仅存储“远期”值<x, w> or y**2但也计算图 -- the grad_fn当追溯梯度时需要计算导数(使用链式法则)z(输出)到w(输入)。

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

请参见这个答案 https://stackoverflow.com/a/57249287/1714410有关使用追溯导数的更多信息backwrd()功能。


既然两者np.ndarray and torch.tensor有一个共同的“层”存储 n 维数字数组,pytorch 使用相同的存储来节省内存:

numpy() → numpy.ndarray https://pytorch.org/docs/stable/tensors.html#torch.Tensor.numpy
退货self张量作为 NumPy ndarray。这个张量和返回的ndarray共享相同的底层存储。 self 张量的更改将反映在 ndarray 中,反之亦然。

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

torch.from_numpy(ndarray) → Tensor https://pytorch.org/docs/stable/generated/torch.from_numpy.html#torch.from_numpy
从 numpy.ndarray 创建张量。
返回的张量和ndarray共享相同的记忆。对张量的修改将反映在 ndarray 中,反之亦然。

因此,当创建一个np.array from torch.tensor反之亦然,双方都反对参考内存中相同的底层存储。自从np.ndarray不存储/表示与数组关联的计算图,该图应该是明确地删除使用detach()当共享 numpy 和 torch 时希望引用相同的张量。


请注意,如果您出于某种原因希望仅使用 pytorch 进行数学运算而不进行反向传播,则可以使用with torch.no_grad() https://pytorch.org/docs/stable/generated/torch.no_grad.html#torch.no_grad上下文管理器,在这种情况下不会创建计算图torch.tensors and 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
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

为什么我们在 Pytorch 张量上调用 .numpy() 之前先调用 .detach()? 的相关文章

随机推荐