我认为这里要理解的最关键的一点是a和之间的区别:
虽然两个对象都用于存储 n 维矩阵(又名“张量”),但还有一个额外的“层”——它存储了导致关联的n维矩阵。torch.tensor
np.ndarray
torch.tensors
因此,如果您只对在矩阵上执行数学运算的高效且简单的方法感兴趣,np.ndarray
或者torch.tensor
可以互换使用。
然而,torch.tensor
s 被设计用于梯度下降优化的上下文中,因此它们不仅包含具有数值的张量,而且(更重要的是)包含导致这些值的计算图。然后使用该计算图(使用导数的链式法则)计算损失函数对每个用于计算损失的自变量的导数。
如前所述,np.ndarray
对象没有这个额外的“计算图”层,因此,在将 a 转换为时torch.tensor
,np.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
,我们不必担心任何图形,我们只需从输入和向前移动,然后计算和。x
w
y
z
但是,如果我们不太关心 的值z
,而是想问“什么是给定w
的最小化 ”的问题,会发生什么?z
x
要回答这个问题,我们需要计算wrt的导数。
我们怎么能做到这一点?
使用链式法则我们知道。也就是说,要计算wrt的梯度,我们需要从back向后移动到计算每一步的操作梯度,因为我们追溯我们的步骤 from toz
w
dz/dw = dz/dy * dy/dw
z
w
z
w
z
w
. 我们追溯的这条“路径”是计算图,z
它告诉我们如何计算z
导致的输入的导数z
:
z.backward() # ask pytorch to trace back the computation of z
我们现在可以检查z
wrt的梯度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*y
和dy/dw = x
。
沿路径的每个张量都存储其对计算的“贡献”:
z
tensor(1.4061, grad_fn=<PowBackward0>)
和
y
tensor(1.1858, grad_fn=<DotBackward>)
如您所见,y
它z
不仅存储 或 的“前向”值,<x, w>
还y**2
存储计算图——grad_fn
当追溯从z
(输出)到w
(输入)的梯度时,计算导数(使用链式法则)所需的.
这些grad_fn
是必不可少的组成部分torch.tensors
,没有它们就无法计算复杂函数的导数。然而,np.ndarray
s 根本没有这个能力,他们也没有这个信息。
有关使用函数追溯导数的更多信息,请参阅此答案。backwrd()
由于两者np.ndarray
和torch.tensor
都有一个共同的“层”来存储数字数组,pytorch 使用相同的存储来节省内存:
numpy() → numpy.ndarray
self
将张量作为 NumPy ndarray
返回。这个张量和返回的 ndarray共享相同的底层存储。自张量的变化将反映在 ndarray 中,反之亦然。
另一个方向也以同样的方式工作:
torch.from_numpy(ndarray) → Tensor
从 numpy.ndarray 创建一个张量。
返回的张量和 ndarray共享相同的内存。对张量的修改将反映在 ndarray 中,反之亦然。
因此,当创建np.array
fromtorch.tensor
或反之亦然时,两个对象都引用内存中相同的底层存储。由于np.ndarray
不存储/表示与数组关联的计算图,因此当共享 numpy 和 torch 希望引用相同的张量时,应显式删除此图。detach()
请注意,如果您出于某种原因希望仅将 pytorch 用于数学运算而无需反向传播,则可以使用with torch.no_grad()
上下文管理器,在这种情况下,不会创建计算图,并且torch.tensor
s 和np.ndarray
s 可以互换使用。
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