"视图"方法在PyTorch中如何工作?

Was*_*mad 144 python memory torch pytorch tensor

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()
Run Code Online (Sandbox Code Playgroud)

我的困惑在于以下几行.

x = x.view(-1, 16*5*5)
Run Code Online (Sandbox Code Playgroud)

tensor.view()功能有什么作用?我已经在许多地方看到了它的用法,但我无法理解它如何解释它的参数.

如果我将负值作为参数给view()函数会发生什么?例如,如果我打电话会发生什么tensor_variable.view(1, 1, -1)

任何人都可以view()通过一些例子解释功能的主要原理吗?

Kas*_*yap 208

视图函数旨在重塑张量.

说你有一个张量

import torch
a = torch.range(1, 16)
Run Code Online (Sandbox Code Playgroud)

a是一个张量,有16个元素从1到16(包括).如果你想重塑这个张量使其成为一个4 x 4张量,那么你可以使用

a = a.view(4, 4)
Run Code Online (Sandbox Code Playgroud)

现在a将是一个4 x 4张量.请注意,在重新整形后,元素的总数需要保持不变.重塑张a3 x 5张量是不恰当的.

参数-1是什么意思?

如果有任何情况你不知道你想要多少行,但确定列数,那么你可以用-1指定它.(请注意,您可以将其扩展到具有更多尺寸的张量.只有一个轴值可以为-1).这是告诉库的一种方式:"给我一个具有这么多列的张量,并计算实现这一点所需的适当行数".

这可以在您上面给出的神经网络代码中看到.x = self.pool(F.relu(self.conv2(x)))在前向功能中的行之后,您将拥有一个16深度的特征映射.您必须将其展平以将其提供给完全连接的图层.因此,您告诉pytorch重新构造您获得的具有特定列数的张量,并告诉它自己决定行数.

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

  • **"视图类似于numpy的重塑"** - 为什么他们不在PyTorch中称它为"reshape"?! (75认同)
  • @MaxB与重塑不同,"视图"返回的新张量与原始张量共享基础数据,因此它实际上是对旧张量的视图,而不是创建一个全新的张量. (35认同)
  • @blckbird"reshape总是复制内存.查看永远不会复制内存." https://github.com/torch/cutorch/issues/98 (24认同)
  • @qihqi**"与重塑不同"**不正确. (7认同)
  • @MaxB你有这个来源吗?我也读过齐齐说的话。 (2认同)
  • @CharlieParker将拉平张量。 (2认同)
  • @devinbost**Torch**重塑总是复制内存.**NumPy**重塑没有. (2认同)
  • Torch reshape 并不总是返回副本。这是一个非常简单的检查。与 numpy 相同 (2认同)
  • Reshape 可以是**视图或副本**。来自文档:*“如果可能,返回的张量将是输入的**视图**。否则,它将是**副本**。”* (2认同)

Jad*_*mas 26

让我们举一些例子,从简单到更难.

  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]
    
    Run Code Online (Sandbox Code Playgroud)
  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
    
    Run Code Online (Sandbox Code Playgroud)
  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)
    
    Run Code Online (Sandbox Code Playgroud)

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


uke*_*emi 21

view() 通过“拉伸”或“挤压”张量的元素到你指定的形状来重塑张量:

在此处输入图片说明


如何view()工作?

首先让我们看看引擎盖下的张量是什么:

在此处输入图片说明 在此处输入图片说明
张量及其基础 storage 例如,右手张量(形状(3,2))可以从左手张量计算出来 t2 = t1.view(3,2)

在这里,您可以看到 PyTorch 通过添加 ashapestride属性将底层连续内存块转换为类似矩阵的对象来生成张量:

  • shape 说明每个维度的长度
  • stride 说明在到达每个维度中的下一个元素之前您需要在内存中采取多少步骤

view(dim1,dim2,...)返回相同基础信息的视图,但重新整形为形状张量dim1 x dim2 x ...(通过修改shapestride属性)。

请注意,这隐含地假设新旧维度具有相同的乘积(即旧张量和新张量具有相同的体积)。


火炬-1

-1是 PyTorch 的别名,用于“在其他都已指定的情况下推断此维度”(即原始产品与新产品的商)。它是从numpy.reshape().

因此,t1.view(3,2)在我们的示例中将等价于t1.view(3,-1)or t1.view(-1,2)


kma*_*o23 14

torch.Tensor.view()

简单地说,torch.Tensor.view()numpy.ndarray.reshape()or 的启发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])
Run Code Online (Sandbox Code Playgroud)

有了这个张量t的形状(18,),新观点可以为以下形状创建:

(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*93*6等)的乘法必须始终等于原始张量中元素的总数(18在我们的示例中)。

另一件要观察的事情是我们-1在每个形状元组的一个地方使用了 a 。通过使用 a -1,我们懒得自己进行计算,而是将任务委托给 PyTorch 在它创建新视图时计算形状的值。需要注意的一件重要事情是我们只能-1在形状元组中使用单个。其余值应由我们明确提供。否则 PyTorch 会抛出一个RuntimeError:

运行时错误:只能推断一维

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

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

# stride of our original tensor `t`
In [53]: t.stride() 
Out[53]: (1,)
Run Code Online (Sandbox Code Playgroud)

现在,我们将看到新视图的进展:

# 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)
Run Code Online (Sandbox Code Playgroud)

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

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

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

这是因为:

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

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

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


火炬.reshape()

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

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

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


小智 5

让我们尝试通过以下示例来理解视图:

    a=torch.range(1,16)

print(a)

    tensor([ 1.,  2.,  3.,  4.,  5.,  6.,  7.,  8.,  9., 10., 11., 12., 13., 14.,
            15., 16.])

print(a.view(-1,2))

    tensor([[ 1.,  2.],
            [ 3.,  4.],
            [ 5.,  6.],
            [ 7.,  8.],
            [ 9., 10.],
            [11., 12.],
            [13., 14.],
            [15., 16.]])

print(a.view(2,-1,4))   #3d tensor

    tensor([[[ 1.,  2.,  3.,  4.],
             [ 5.,  6.,  7.,  8.]],

            [[ 9., 10., 11., 12.],
             [13., 14., 15., 16.]]])
print(a.view(2,-1,2))

    tensor([[[ 1.,  2.],
             [ 3.,  4.],
             [ 5.,  6.],
             [ 7.,  8.]],

            [[ 9., 10.],
             [11., 12.],
             [13., 14.],
             [15., 16.]]])

print(a.view(4,-1,2))

    tensor([[[ 1.,  2.],
             [ 3.,  4.]],

            [[ 5.,  6.],
             [ 7.,  8.]],

            [[ 9., 10.],
             [11., 12.]],

            [[13., 14.],
             [15., 16.]]])
Run Code Online (Sandbox Code Playgroud)

-1 作为参数值是计算 x 的值的简单方法,前提是我们知道 y、z 的值,或者在 3d 的情况下反之亦然,对于 2d 来说,也是计算 x 的值的简单方法,前提是我们知道知道 y 的值,反之亦然。