PyTorch中的“视图”方法如何工作?
python
pytorch
tensor
torch
9
0

我对以下代码片段中的view()方法感到困惑。

class Net(nn.Module):
    def __init__(self):
        super(Net, self).__init__()
        self.conv1 = nn.Conv2d(3, 6, 5)
        self.pool  = nn.MaxPool2d(2,2)
        self.conv2 = nn.Conv2d(6, 16, 5)
        self.fc1   = nn.Linear(16*5*5, 120)
        self.fc2   = nn.Linear(120, 84)
        self.fc3   = nn.Linear(84, 10)

    def forward(self, x):
        x = self.pool(F.relu(self.conv1(x)))
        x = self.pool(F.relu(self.conv2(x)))
        x = x.view(-1, 16*5*5)
        x = F.relu(self.fc1(x))
        x = F.relu(self.fc2(x))
        x = self.fc3(x)
        return x

net = Net()

我的困惑是关于以下几行。

x = x.view(-1, 16*5*5)

tensor.view()函数做什么?我已经在很多地方看到了它的用法,但是我不明白它是如何解释其参数的。

如果将负值作为参数提供给view()函数会怎样?例如,如果我调用tensor_variable.view(1, 1, -1)什么?

谁能用一些例子解释view()函数的主要原理?

参考资料:
Stack Overflow
收藏
评论
共 6 个回答
高赞 时间 活跃

视图功能旨在重塑张量。

说你有张量

import torch
a = torch.range(1, 16)

a是具有1到16(包括)的16个元素的张量。如果要重塑此张量以使其为4 x 4张量,则可以使用

a = a.view(4, 4)

现在a将是4 x 4张量。 请注意,在重塑后,元素的总数需要保持不变。将张量a重塑为3 x 5张量是不合适的。

参数-1的含义是什么?

如果在某些情况下您不知道要多少行,但是确定了列数,则可以将其指定为-1。 ( 请注意,您可以将其扩展到具有更大尺寸的张量。只有一个轴值可以为-1 )。这是一种告诉库的方法:“给我一个具有这么多列的张量,然后您计算出实现这一点所必需的适当行数”。

可以在上面给出的神经网络代码中看到。在正向函数中的x = self.pool(F.relu(self.conv2(x)))之后,您将拥有16个深度的特征图。您必须将其展平以将其分配给完全连接的层。因此,您告诉pytorch重塑所获得的张量,使其具有特定的列数,并告诉它自己决定行数。

在numpy和pytorch之间绘制相似之处, view类似于numpy的重塑功能。

收藏
评论

让我们做一些例子,从简单到困难。

  1. view方法返回一个张量,该张量具有与self张量相同的数据(这意味着返回的张量具有相同数量的元素),但形状不同。例如:

     a = torch.arange(1, 17) # a's shape is (16,) a.view(4, 4) # output below 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 [torch.FloatTensor of size 4x4] a.view(2, 2, 4) # output below (0 ,.,.) = 1 2 3 4 5 6 7 8 (1 ,.,.) = 9 10 11 12 13 14 15 16 [torch.FloatTensor of size 2x2x4] 
  2. 假设-1不是参数之一,则将它们相乘时,结果必须等于张量中的元素数量。如果执行以下操作: a.view(3, 3) ,它将引发RuntimeError因为形状(3 x 3)对于具有16个元素的输入无效。换句话说:3 x 3不等于16而是9。

  3. 您可以使用-1作为传递给函数的参数之一,但只能使用一次。所有发生的事情是该方法将为您完成如何填充该维度的数学运算。例如a.view(2, -1, 4)等效于a.view(2, 2, 4) 。 [16 /(2 x 4)= 2]

  4. 请注意,返回的张量共享相同的数据 。如果您在“视图”中进行了更改,那么您正在更改原始张量的数据:

     b = a.view(4, 4) b[0, 2] = 2 a[2] == 3.0 False 
  5. 现在,对于更复杂的用例。该文档说,每个新视图维都必须是原始维的子空间,或者只能是跨度d,d + 1,...,d + k ,它们满足以下所有i = 0,...的连续性条件。 ..,k-1,stride [i] = stride [i +1] x size [i +1] 。否则,需要先调用contiguous()才能查看张量。例如:

     a = torch.rand(5, 4, 3, 2) # size (5, 4, 3, 2) a_t = a.permute(0, 2, 3, 1) # size (5, 3, 2, 4) # The commented line below will raise a RuntimeError, because one dimension # spans across two contiguous subspaces # a_t.view(-1, 4) # instead do: a_t.contiguous().view(-1, 4) # To see why the first one does not work and the second does, # compare a.stride() and a_t.stride() a.stride() # (24, 6, 2, 1) a_t.stride() # (24, 2, 1, 6) 

    注意,对于a_t ,因为24!= 2 x 3 ,所以stride [0]!= stride [1] x size [1]

收藏
评论

参数-1的含义是什么?

您可以将-1读取为参数的动态数量或“任何内容”。因此, view()只能有一个参数-1

如果您询问x.view(-1,1)则将输出张量形状[anything, 1]具体取决于x的元素数量。例如:

import torch
x = torch.tensor([1, 2, 3, 4])
print(x,x.shape)
print("...")
print(x.view(-1,1), x.view(-1,1).shape)
print(x.view(1,-1), x.view(1,-1).shape)

将输出:

tensor([1, 2, 3, 4]) torch.Size([4])
...
tensor([[1],
        [2],
        [3],
        [4]]) torch.Size([4, 1])
tensor([[1, 2, 3, 4]]) torch.Size([1, 4])
收藏
评论

weights.reshape(a, b)将返回一个新的张量,该张量具有与大小(a,b)的权重相同的数据,因为它将数据复制到内存的另一部分。

weights.resize_(a, b)返回具有不同形状的相同张量。但是,如果新形状导致的元素数量少于原始张量,则某些元素将从张量中删除(但不会从内存中删除)。如果新形状导致的元素数量多于原始张量,则新元素将在内存中未初始化。

weights.view(a, b)将返回一个新的张量,其数据与大小为(a,b)的权重相同

收藏
评论

torch.Tensor.view()

简单地说, torch.Tensor.view()这是由启发numpy.ndarray.reshape()numpy.reshape()造成张量的新视图 ,只要新的形状与原来的形状相容的张量。

让我们通过一个具体的例子来详细了解这一点。

In [43]: t = torch.arange(18) 

In [44]: t 
Out[44]: 
tensor([ 0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14, 15, 16, 17])

使用形状(18,)张量t只能为以下形状创建新视图

(1, 18)或等效地(1, -1)(-1, 18)
(2, 9)或等效地(2, -1)(-1, 9)
(3, 6)或等效地(3, -1)(-1, 6)
(6, 3)或等效(6, -1)(-1, 3)
(9, 2)或等效的(9, -1)(-1, 2)
(18, 1)或等效的(18, -1)(-1, 1)

正如我们从上述形状元组中已经观察到的那样,形状元组的元素的乘积(例如2*9 3*6等) 必须始终等于原始张量中元素的总数(在我们的张量中为18例)。

要观察的另一件事是,我们在每个形状元组的某个位置使用了-1 。通过使用-1 ,我们懒于自己进行计算,而是将任务委托给PyTorch在创建新视图时对该形状进行该值的计算。需要注意的重要一件事是,在形状元组中只能使用单个-1 。其余值应由我们明确提供。其他PyTorch会抛出RuntimeError来抱怨:

RuntimeError:只能推断一个维度

因此,对于所有上述形状,PyTorch将始终返回原始张量t新视图 。这基本上意味着,它仅针对所请求的每个新视图更改张量的步幅信息。

下面是一些示例,说明每个新视图如何改变张量的步幅。

# stride of our original tensor `t`
In [53]: t.stride() 
Out[53]: (1,)

现在,我们将看到新视图的大步前进:

# shape (1, 18)
In [54]: t1 = t.view(1, -1)
# stride tensor `t1` with shape (1, 18)
In [55]: t1.stride() 
Out[55]: (18, 1)

# shape (2, 9)
In [56]: t2 = t.view(2, -1)
# stride of tensor `t2` with shape (2, 9)
In [57]: t2.stride()       
Out[57]: (9, 1)

# shape (3, 6)
In [59]: t3 = t.view(3, -1) 
# stride of tensor `t3` with shape (3, 6)
In [60]: t3.stride() 
Out[60]: (6, 1)

# shape (6, 3)
In [62]: t4 = t.view(6,-1)
# stride of tensor `t4` with shape (6, 3)
In [63]: t4.stride() 
Out[63]: (3, 1)

# shape (9, 2)
In [65]: t5 = t.view(9, -1) 
# stride of tensor `t5` with shape (9, 2)
In [66]: t5.stride()
Out[66]: (2, 1)

# shape (18, 1)
In [68]: t6 = t.view(18, -1)
# stride of tensor `t6` with shape (18, 1)
In [69]: t6.stride()
Out[69]: (1, 1)

这就是view()函数的魔力。它只是改变(原始)张量为每个新的视图的进步,只要新的视图的形状与原来的形状相容。

从跨步元组可能会观察到的另一件有趣的事情是,在形状元组的第0 位置的元素的值等于在形状元组的第一个位置的元素的值。

In [74]: t3.shape 
Out[74]: torch.Size([3, 6])
                        |
In [75]: t3.stride()    |
Out[75]: (6, 1)         |
          |_____________|

这是因为:

In [76]: t3 
Out[76]: 
tensor([[ 0,  1,  2,  3,  4,  5],
        [ 6,  7,  8,  9, 10, 11],
        [12, 13, 14, 15, 16, 17]])

步幅(6, 1)说,从一个元素到下一个元素沿0 维度,我们必须或采取6个步骤。 (即从去06 ,人们必须采取6个步骤)。但是从一个元件到去在第1个维度的下一个元素,我们只需要仅一个步骤(用于例如去从23 )。

因此,步幅信息是如何从内存访问元素以执行计算的核心。


torch.reshape()

该函数将返回一个视图,并且与新使用的torch.Tensor.view()完全相同,只要新形状与原始张量的形状兼容即可。否则,它将返回一个副本。

但是, torch.reshape()的注释警告:

连续的输入和具有兼容步幅的输入可以在不复制的情况下进行重塑,但是一个不应该依赖于复制与查看行为。

收藏
评论

我发现x.view(-1, 16 * 5 * 5)等效于x.flatten(1) ,其中参数1指示扁平化过程从第一维开始(不扁平化``样本''维)如您所见,后一种用法在语义上更加清晰并且易于使用,因此我更喜欢flatten()

收藏
评论
新手导航
  • 社区规范
  • 提出问题
  • 进行投票
  • 个人资料
  • 优化问题
  • 回答问题

关于我们

常见问题

内容许可

联系我们

@2020 AskGo
京ICP备20001863号