Pytorch 重塑张量维度

Posted

技术标签:

【中文标题】Pytorch 重塑张量维度【英文标题】:Pytorch reshape tensor dimension 【发布时间】:2017-09-05 19:40:31 【问题描述】:

例如,我有一个维度为 (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 张量(和变量)来做到这一点。我不想切换回 numpy 并再次切换到 Torch 变量,因为它会丢失反向传播信息。

这是我在 Pytorch 中所拥有的

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

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

>>> a.size()
(5L,)
>>> a_var = variable(a)
>>> a_var = Variable(a)
>>> a_var.size()
(5L,)
.....do some calculation in forward function
>>> a_var.size()
(5L,)

现在我希望它的大小为 (1, 5)。 如何在不丢失 grad 信息的情况下调整变量中 pytorch 张量的尺寸或重塑其尺寸。 (因为我会在后退之前输入另一个模型)

【问题讨论】:

【参考方案1】:

使用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]

【讨论】:

【参考方案2】:

你可能会使用

a.view(1,5)
Out: 

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

【讨论】:

请注意,这不会修改原始张量a。它只是创建一个视图。【参考方案3】:

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

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

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

为了给这个问题增加一些鲁棒性,让我们重塑 2 x 3 张量,在前面添加一个新维度,在中间添加另一个维度,生成一个 1 x 2 x 1 x 3 张量。

方法一:用None添加维度

在任何你想要的地方使用 NumPy 风格的 insertion of None (aka np.newaxis) to add dimensions。见here。

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])

方法2:解压

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

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 的实践中,adding an extra dimension for the batch 可能很重要,所以你可能经常看到unsqueeze(0)

方法三:查看

使用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])

方法四:重塑

使用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() 可能会抛出错误;也就是说,如果从头开始创建所需形状的新张量,它不会共享相同的内存块。如果发生这种情况,您必须先致电tensor.contiguous(),然后才能使用view()。然而,reshape() 在幕后完成了所有这些工作,所以总的来说,我建议使用reshape() 而不是view()

方法五:调整大小_

使用就地函数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()

另见:

What's the difference between reshape and view in pytorch?

What is the difference between view() and unsqueeze()?

In PyTorch 0.4, is it recommended to use reshape than view when it is possible?

【讨论】:

【参考方案4】:

对于张量形状的就地修改,你应该使用 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_()),则该操作会对原始张量进行 in-place 修改。


此外,您可以在 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])

【讨论】:

【参考方案5】:

或者你可以使用它,'-1' 意味着你不必指定元素的数量。

In [3]: a.view(1,-1)
Out[3]:

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

【讨论】:

【参考方案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)

【讨论】:

【参考方案7】:

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])

【讨论】:

【参考方案8】:
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'

【讨论】:

【参考方案9】:

据我所知,重塑张量的最佳方法是使用einops。它通过提供简单而优雅的功能解决了各种重塑问题。在你的情况下,代码可以写成

from einops import rearrange
ans = rearrange(tensor,'h -> 1 h')

我强烈建议您尝试一下。

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

【讨论】:

【参考方案10】:

假设以下代码:

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

小心使用resize_autograd 的其他就地操作。请参阅以下讨论:https://pytorch.org/docs/stable/notes/autograd.html#in-place-operations-with-autograd

【讨论】:

您声明另一种方法是使用 resize_ in place 操作,但您的代码使用reshape_【参考方案11】:
import torch
t = torch.ones((2, 3, 4))
t.size()
>>torch.Size([2, 3, 4])
a = t.view(-1,t.size()[1]*t.size()[2])
a.size()
>>torch.Size([2, 12])

【讨论】:

以上是关于Pytorch 重塑张量维度的主要内容,如果未能解决你的问题,请参考以下文章

使用 pyTorch 张量沿一个特定维度和 3 维张量进行索引

pytorch张量数据索引切片与维度变换操作大全(非常全)

PyTorch: 张量的拼接切分索引

PyTorch 命名张量(Named Tensors)的介绍

PyTorch:tensor-张量维度操作(拼接维度扩展压缩转置重复……)

PyTorch-20 命名张量(Named Tensors)的介绍