PyTorch重塑张量维度

75

我想将一个形状为(5,)的向量转换成一个形状为(1, 5)的矩阵。

使用numpy,我可以这样做:

>>> import numpy as np
>>> a = np.array([1, 2, 3, 4, 5])
>>> a.shape
(5,)
>>> a = np.reshape(a, (1, 5))
>>> a.shape
(1, 5)
>>> a
array([[1, 2, 3, 4, 5]])

但我如何用PyTorch做到这一点呢?

11个回答

56

使用torch.unsqueeze(input, dim, out=None)函数:

>>> import torch
>>> a = torch.Tensor([1, 2, 3, 4, 5])
>>> a

 1
 2
 3
 4
 5
[torch.FloatTensor of size 5]

>>> a = a.unsqueeze(0)
>>> a

 1  2  3  4  5
[torch.FloatTensor of size 1x5]

23

你可能会使用

a.view(1,5)
Out: 

 1  2  3  4  5
[torch.FloatTensor of size 1x5]

5
请注意,这 不会修改 原始张量 a,它只是创建一个视图。 - kmario23

20

有多种方法可以重塑PyTorch张量。您可以在任何维度的张量上应用这些方法。

让我们从一个二维的2 x 3张量开始:

x = torch.Tensor(2, 3)
print(x.shape)
# torch.Size([2, 3])

为了增强这个问题的稳健性,让我们通过在前面添加一个新维度和在中间添加另一个维度来重新塑造2x3张量,从而产生1x2x1x3张量。
方法1:使用None添加维度
使用NumPy风格的插入None(也称np.newaxis)在任何想要的位置添加维度。请参见此处
print(x.shape)
# torch.Size([2, 3])

y = x[None, :, None, :] # Add new dimensions at positions 0 and 2.
print(y.shape)
# torch.Size([1, 2, 1, 3])

方法二:unsqueeze

使用 torch.Tensor.unsqueeze(i)(又称为 torch.unsqueeze(tensor, i) 或就地版本的 unsqueeze_())在第i个维度上添加一个新的维度。返回的张量与原始张量共享相同的数据。在此示例中,我们可以使用unsqueeze()两次来添加两个新维度。

print(x.shape)
# torch.Size([2, 3])

# Use unsqueeze twice.
y = x.unsqueeze(0) # Add new dimension at position 0
print(y.shape)
# torch.Size([1, 2, 3])

y = y.unsqueeze(2) # Add new dimension at position 2
print(y.shape)
# torch.Size([1, 2, 1, 3])

在PyTorch中,实践中为批次添加一个额外的维度可能很重要,因此您经常会看到unsqueeze(0)
方法3:视图
使用torch.Tensor.view(*shape)指定所有维度。返回的张量与原始张量共享相同的数据。
print(x.shape)
# torch.Size([2, 3])

y = x.view(1, 2, 1, 3)
print(y.shape)
# torch.Size([1, 2, 1, 3])

方法4:重塑

使用torch.Tensor.reshape(*shape)(又名torch.reshape(tensor, shapetuple))来指定所有维度。如果原始数据是连续的且具有相同的步幅,则返回的张量将是输入的视图(共享相同的数据),否则它将是一份副本。此函数类似于NumPy的reshape()函数,因为它允许您定义所有维度并可以返回视图或副本。

print(x.shape)
# torch.Size([2, 3])

y = x.reshape(1, 2, 1, 3)
print(y.shape)
# torch.Size([1, 2, 1, 3])

此外,来自O'Reilly 2019年的书籍Programming PyTorch for Deep Learning,作者写道:
现在你可能想知道view()reshape()之间的区别。答案是,view()作为原始张量的视图操作,因此如果基础数据发生更改,则视图也会更改(反之亦然)。但是,如果所需视图不连续,即它与从头开始创建的所需形状的新张量不共享同一块内存,则view()可能会抛出错误。如果发生这种情况,则必须在使用view()之前调用tensor.contiguous()。但是,reshape()在幕后完成所有工作,因此通常建议使用reshape()而不是view()
第5种方法:resize_
使用就地函数torch.Tensor.resize_(*sizes)修改原始张量。文档说明如下:

警告。这是一种低级方法。存储将被重新解释为C连续的,忽略当前步幅(除非目标大小等于当前大小,在这种情况下张量保持不变)。对于大多数情况,您应该使用view(),它会检查连续性,或者使用reshape(),如果需要,会复制数据。要使用自定义步幅就地更改大小,请参见set_()

print(x.shape)
# torch.Size([2, 3])

x.resize_(1, 2, 1, 3)
print(x.shape)
# torch.Size([1, 2, 1, 3])

我的观察

如果您想添加一个维度(例如添加第0维以进行批处理),则使用unsqueeze(0)。如果要完全更改维度,则使用reshape()

另请参阅:

PyTorch中reshape和view有什么区别?

view()和unsqueeze()有什么区别?

在PyTorch 0.4中,当可能时,建议使用reshape而不是view吗?


16

若要就地修改张量的形状,应使用tensor.resize_()

In [23]: a = torch.Tensor([1, 2, 3, 4, 5])

In [24]: a.shape
Out[24]: torch.Size([5])


# tensor.resize_((`new_shape`))    
In [25]: a.resize_((1,5))
Out[25]: 

 1  2  3  4  5
[torch.FloatTensor of size 1x5]

In [26]: a.shape
Out[26]: torch.Size([1, 5])

在PyTorch中,如果一个操作的末尾有一个下划线(比如tensor.resize_()),那么该操作将对原张量进行就地修改。


此外,你可以在torch张量中简单地使用np.newaxis来增加维度。这里是一个例子:

In [34]: list_ = range(5)
In [35]: a = torch.Tensor(list_)
In [36]: a.shape
Out[36]: torch.Size([5])

In [37]: new_a = a[np.newaxis, :]
In [38]: new_a.shape
Out[38]: torch.Size([1, 5])

6
这个问题已经被彻底解答了,但是我想为经验不足的Python开发者添加一些内容,你可能会发现与view()一起使用*运算符很有帮助。
例如,如果您有特定的张量大小,想要将不同张量的数据符合,可以尝试以下操作:
img = Variable(tensor.randn(20,30,3)) # tensor with goal shape
flat_size = 20*30*3
X = Variable(tensor.randn(50, flat_size)) # data tensor

X = X.view(-1, *img.size()) # sweet maneuver
print(X.size()) # size is (50, 20, 30, 3)

这也适用于numpy的shape
img = np.random.randn(20,30,3)
flat_size = 20*30*3
X = Variable(tensor.randn(50, flat_size))
X = X.view(-1, *img.shape)
print(X.size()) # size is (50, 20, 30, 3)

6
或者您可以使用这个,'-1'表示您不需要指定元素的数量。
In [3]: a.view(1,-1)
Out[3]:

 1  2  3  4  5
[torch.FloatTensor of size 1x5]

5

torch.reshape() 的设计灵感来自于 numpy中的reshape 方法。

它是在 view()torch.resize_() 之后推出的,位于 dir(torch) 包内。

import torch
x=torch.arange(24)
print(x, x.shape)
x_view = x.view(1,2,3,4) # works on is_contiguous() tensor
print(x_view.shape)
x_reshaped = x.reshape(1,2,3,4) # works on any tensor
print(x_reshaped.shape)
x_reshaped2 = torch.reshape(x_reshaped, (-1,)) # part of torch package, while view() and resize_() are not
print(x_reshaped2.shape)

输出:

tensor([ 0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14, 15, 16, 17,
        18, 19, 20, 21, 22, 23]) torch.Size([24])
torch.Size([1, 2, 3, 4])
torch.Size([1, 2, 3, 4])
torch.Size([24])

但您是否知道它也可以作为 squeeze()unsqueeze() 的替代方法使用呢?

x = torch.tensor([1, 2, 3, 4])
print(x.shape)
x1 = torch.unsqueeze(x, 0)
print(x1.shape)
x2 = torch.unsqueeze(x1, 1)
print(x2.shape)
x3=x.reshape(1,1,4)
print(x3.shape)
x4=x.reshape(4)
print(x4.shape)
x5=x3.squeeze()
print(x5.shape)

输出:

torch.Size([4])
torch.Size([1, 4])
torch.Size([1, 1, 4])
torch.Size([1, 1, 4])
torch.Size([4])
torch.Size([4])

2
据我所知,重塑张量的最佳方法是使用 einops。它通过提供简单而优雅的函数解决了各种重塑问题。在您的情况下,代码可能如下所示:
from einops import rearrange
ans = rearrange(tensor,'h -> 1 h')

我强烈建议您尝试一下。

顺便说一下,您可以将其与pytorch/tensorflow/numpy和许多其他库一起使用。


1
import torch
>>>a = torch.Tensor([1,2,3,4,5])
>>>a.size()
torch.Size([5])
#use view to reshape

>>>b = a.view(1,a.shape[0])
>>>b
tensor([[1., 2., 3., 4., 5.]])
>>>b.size()
torch.Size([1, 5])
>>>b.type()
'torch.FloatTensor'

0
假设以下代码:
import torch
import numpy as np
a = torch.tensor([1, 2, 3, 4, 5])

以下三个调用具有完全相同的效果:

res_1 = a.unsqueeze(0)
res_2 = a.view(1, 5)
res_3 = a[np.newaxis,:]
res_1.shape == res_2.shape == res_3.shape == (1,5)  # Returns true

请注意,对于任何生成的张量,如果您修改其中的数据,则也会修改a中的数据,因为它们没有数据副本,而是引用a中的原始数据。
res_1[0,0] = 2
a[0] == res_1[0,0] == 2  # Returns true

另一种做法是使用就地操作resize_
a.shape == res_1.shape  # Returns false
a.reshape_((1, 5))
a.shape == res_1.shape # Returns true

在使用autograd时,要小心使用resize_或其他原地操作。请参阅以下讨论:https://pytorch.org/docs/stable/notes/autograd.html#in-place-operations-with-autograd


你说过另一种方法是使用resize_就地操作,但你的代码使用了reshape_ - stackoverflowuser2010

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接