在火炬中,重塑和视图有什么区别?

在 numpy 中,我们使用 ndarray.reshape()来重新设置数组的形状。

我注意到,在 pytch 中,人们使用 torch.view(...)的目的是相同的,但同时,也有一个 torch.reshape(...)存在。

所以我想知道它们之间的区别是什么,什么时候我应该使用它们中的任何一个?

103215 次浏览

torch.view已经存在很长时间了。它将返回一个张量与新的形状。返回的张量将与原始张量共享基础数据。 看看 文件

另一方面,似乎 torch.reshape 最近在0.4版本中引入了。根据 文件,这种方法将

返回一个张量,该张量具有与输入相同的数据和元素数,但具有指定的形状。如果可能,返回的张量将是一个输入视图。否则,它将是一个副本。具有兼容跨步的连续输入和输入可以在不复制的情况下进行重塑,但是您不应该依赖于复制和查看行为。

这意味着 torch.reshape可能返回原始张量的一个副本或视图。您不能指望它返回视图或副本。开发商表示:

如果您需要一个拷贝使用 clone ()如果您需要相同的存储使用视图()。重塑()的语义是,它可能共享存储,也可能不共享存储,而您事先并不知道。

另一个区别是 reshape()既可以操作连续张量也可以操作非连续张量,而 view()只能操作连续张量。也请参阅 给你关于 contiguous的含义。

虽然 torch.viewtorch.reshape都被用来重塑张量,这里是它们之间的区别。

  1. 顾名思义,torch.view只是创建了原始张量的 风景。新张量将 一直都是与原张量共享其数据。这意味着如果你改变原始张量,重塑后的张量将会改变,反之亦然。
>>> z = torch.zeros(3, 2)
>>> x = z.view(2, 3)
>>> z.fill_(1)
>>> x
tensor([[1., 1., 1.],
[1., 1., 1.]])
  1. 为了确保新张量总是与原张量共享其数据,torch.view对两个张量[ 医生]的形状施加了一些邻接约束。这往往不是一个问题,但有时 torch.view抛出一个错误,即使形状的两个张量是兼容的。这是一个著名的反例。
>>> z = torch.zeros(3, 2)
>>> y = z.t()
>>> y.size()
torch.Size([2, 3])
>>> y.view(6)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
RuntimeError: invalid argument 2: view size is not compatible with input tensor's
size and stride (at least one dimension spans across two contiguous subspaces).
Call .contiguous() before .view().
  1. torch.reshape不强加任何连续性约束,但也不保证数据共享。新张量可以是原张量的一个视图,也可以是一个全新的张量。
>>> z = torch.zeros(3, 2)
>>> y = z.reshape(6)
>>> x = z.t().reshape(6)
>>> z.fill_(1)
tensor([[1., 1.],
[1., 1.],
[1., 1.]])
>>> y
tensor([1., 1., 1., 1., 1., 1.])
>>> x
tensor([0., 0., 0., 0., 0., 0.])

译者:
如果你只是想重塑张量,使用 torch.reshape。如果您还关心内存使用情况,并希望确保两个张量共享相同的数据,请使用 torch.view

Tensor.reshape()更加健壮。它可以在任何张量上工作,而 Tensor.view()只能在 t.is_contiguous()==Truet张量上工作。

要解释非连续和连续是另一回事,但是如果你调用 t.contiguous(),你总是可以使张量 t连续,然后你可以调用 view()而没有错误。

View ()将尝试改变张量的形状,同时保持底层数据分配相同,因此数据将在两个张量之间共享。如果需要,重塑()将创建一个新的底层内存分配。

让我们创建一个张量:

a = torch.arange(8).reshape(2, 4)

initial 2D tensor

内存的分配方式如下(它是 C 连续,即行之间存储在一起) :

initial 2D tensor's memory allocation

Stride ()给出进入每个维度中的下一个元素所需的字节数:

a.stride()
(4, 1)

我们希望它的形状变成(4,2) ,我们可以使用 view:

a.view(4,2)

after view to switch the dimensions

基础数据分配没有改变,张量仍然是 C 连续:

memory allocation after switch

a.view(4, 2).stride()
(2, 1)

让我们尝试使用 a.t ()。 Transose ()不修改底层内存分配,因此 a.t ()不是连续的。

a.t().is_contiguous()
False

after transpose

memory allocation after transpose

虽然它不是连续的,但是步长信息足以在张量上迭代

a.t().stride()
(1, 4)

View ()不再有效:

a.t().view(2, 4)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
RuntimeError: view size is not compatible with input tensor's size and stride (at least one dimension spans across two contiguous subspaces). Use .reshape(...) instead.

下面是我们希望通过使用 view (2,4)获得的形状:

after transpose and reshape

内存分配是什么样子的?

memory allocation without reshape

步长大概是(4,2) ,但是我们必须在到达终点后回到张量的起点。没用的。

在这种情况下,重塑()将创建一个新的张量,使用不同的内存分配,使转置连续:

memory allocation with reshape or contiguous

注意,我们可以使用 view 来分割转置的第一个维度。 与在已接受答案和其他答案中所说的不同,view ()可以在非连续张量上操作!

a.t().view(2, 2, 2)

after transpose and view 2, 2, 2

memory allocation after transpose

a.t().view(2, 2, 2).stride()
(2, 1, 4)

根据文件 :

要查看张量,新的视图大小必须与 它的原始大小和步幅,也就是说,每个新的视图维度必须 要么是原始维度的子空间,要么只是跨越 原始维度 d,d + 1,... ,d + k 满足以下条件 Something i = d,... ,d + k-1,
Stride [ i ] = stride [ i + 1] × size [ i + 1]

这是因为应用 view (2,2,2)后的前两个维度是转置的第一个维度的子空间。

我想说这里的答案在技术上是正确的,但是存在 reshape还有另一个原因。通常认为 pytorch比其他框架更方便,因为它更接近于 pythonnumpy。有趣的是,这个问题涉及到 numpy

让我们看看 pytorch中的 sizeshapesize是一个函数,因此可以像 x.size()那样调用它。pytorch中的 shape不是函数。在 numpy中你有 shape,它是 shape2的一个函数-你使用它 x.shape。所以把它们放在 pytorch中很方便。如果您来自 numpy,那么最好使用相同的函数。