【发布时间】:2018-09-13 13:20:55
【问题描述】:
在 numpy 中,我们使用ndarray.reshape() 来重塑数组。
我注意到在 pytorch 中,人们使用torch.view(...) 来达到同样的目的,但同时也存在一个torch.reshape(...)。
所以我想知道它们之间有什么区别以及何时应该使用它们中的任何一个?
【问题讨论】:
标签: pytorch
在 numpy 中,我们使用ndarray.reshape() 来重塑数组。
我注意到在 pytorch 中,人们使用torch.view(...) 来达到同样的目的,但同时也存在一个torch.reshape(...)。
所以我想知道它们之间有什么区别以及何时应该使用它们中的任何一个?
【问题讨论】:
标签: pytorch
view() 将尝试改变张量的形状,同时保持底层数据分配相同,因此数据将在两个张量之间共享。如有必要,reshape() 将创建一个新的底层内存分配。
让我们创建一个张量:
a = torch.arange(8).reshape(2, 4)
内存分配如下(它是 C 连续的,即行彼此相邻存储):
stride() 给出到每个维度中的下一个元素所需的字节数:
a.stride()
(4, 1)
我们希望它的形状变成(4, 2),我们可以使用view:
a.view(4,2)
底层数据分配没有改变,张量还是C连续:
a.view(4, 2).stride()
(2, 1)
让我们试试 a.t()。 Transpose() 不会修改底层内存分配,因此 a.t() 不是连续的。
a.t().is_contiguous()
False
虽然不连续,但步幅信息足以迭代张量
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)得到的形状:
内存分配是什么样的?
步幅类似于 (4, 2),但我们必须在到达结尾后回到张量的开头。它不起作用。
在这种情况下,reshape() 将创建一个具有不同内存分配的新张量,以使转置连续:
请注意,我们可以使用视图来拆分转置的第一维。 与接受的答案和其他答案中所说的不同,view() 可以对不连续的张量进行操作!
a.t().view(2, 2, 2)
a.t().view(2, 2, 2).stride()
(2, 1, 4)
According to the documentation:
对于要查看的张量,新的视图大小必须与 它的原始大小和步幅,即每个新的视图维度必须 要么是原始维度的子空间,要么只跨越 满足以下条件的原始尺寸 d, d+1, ..., d+k ∀i=d,…,d+k−1,
步幅[i]=步幅[i+1]×尺寸[i+1]
这是因为应用 view(2, 2, 2) 后的前两个维度是转置的第一个维度的子空间。
【讨论】:
contiguous 的含义,这意味着索引一行中的所有下一个数字是否连续。顺便说一句,b.t().is_contiguous() 有一个小错字,可能是a.t().is_contiguous(),谢谢!
我想说这里的答案在技术上是正确的,但reshape 的存在还有另一个原因。 pytorch 通常被认为比其他框架更方便,因为它更接近 python 和 numpy。有意思的是问题涉及numpy。
让我们看看pytorch 中的size 和shape。 size 是一个函数,因此您可以像 x.size() 一样称呼它。 pytorch 中的 shape 不是函数。在numpy 中,你有shape,它不是一个函数——你使用它x.shape。因此,将它们都放在pytorch 中很方便。如果您来自numpy,那么使用相同的功能会很好。
【讨论】:
Tensor.reshape() 更健壮。它适用于任何张量,而 Tensor.view() 仅适用于张量 t 其中t.is_contiguous()==True。
解释非连续和连续是另一回事,但如果你调用t.contiguous(),你总是可以使张量t连续,然后你可以调用view()而不会出错。
【讨论】:
torch.view 已经存在很长时间了。它将返回一个具有新形状的张量。返回的张量将与原始张量共享基础数据。
请参阅documentation here。
另一方面,似乎torch.reshape has been introduced recently in version 0.4。根据document,这个方法会
返回一个张量,其数据和元素数量与输入相同,但具有指定的形状。如果可能,返回的张量将是输入的视图。否则,它将是副本。连续输入和具有兼容步幅的输入可以在不复制的情况下重新调整,但您不应依赖于复制与查看行为。
这意味着torch.reshape 可能会返回原始张量的副本或视图。您不能指望返回视图或副本。根据开发者的说法:
如果您需要副本,请使用 clone(),如果您需要相同的存储,请使用 view()。 reshape() 的语义是它可能共享也可能不共享存储,而您事先并不知道。
另一个区别是reshape() 可以对连续张量和非连续张量进行操作,而view() 只能对连续张量进行操作。关于contiguous的含义,另见here。
【讨论】:
虽然torch.view 和torch.reshape 都用于重塑张量,但这里是它们之间的区别。
torch.view 仅创建原始张量的视图。新张量将始终与原始张量共享其数据。这意味着,如果你改变原始张量,重塑后的张量也会改变,反之亦然。>>> z = torch.zeros(3, 2)
>>> x = z.view(2, 3)
>>> z.fill_(1)
>>> x
tensor([[1., 1., 1.],
[1., 1., 1.]])
torch.view 对两个张量 [docs] 的形状施加了一些邻接约束。通常这不是问题,但有时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().
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.])
TL;DR:
如果您只想重塑张量,请使用torch.reshape。如果您还关心内存使用情况并希望确保两个张量共享相同的数据,请使用torch.view。
【讨论】:
x 和y 都是连续的)。也许这可以澄清?也许对 when reshape 复制和不复制的评论会有所帮助?