为什么这个例子中的这个tensor.unfold方法会增加一个维度呢?

Why does this tensor.unfold method in this example add a dimension?

我正在熟悉 https://pytorch.org/docs/stable/tensors.html#torch.Tensor.unfold

中的 Pytorch 展开方法

我看了他们的例子

>>> x = torch.arange(1., 8)
>>> x
tensor([ 1.,  2.,  3.,  4.,  5.,  6.,  7.])
>>> x.unfold(0, 2, 1)
tensor([[ 1.,  2.],
        [ 2.,  3.],
        [ 3.,  4.],
        [ 4.,  5.],
        [ 5.,  6.],
        [ 6.,  7.]])

我在上面了解到,当我们在维度 0 中展开时,我们一次取大小 2 的块,步长 1 因此,结果是一个 ar运行不同chunk的gement,分别是[1., 2.][2., 3.]等。由于最后有 6 个块,这些块将放在一起,最终形状为 (6,2).

不过,我还有另一个例子运行如下所示。

In [115]: s = torch.arange(20).view(1,10,2)

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

In [117]: s.unfold(0,1,1)
Out[117]:
tensor([[[[ 0],
          [ 1]],

         [[ 2],
          [ 3]],

         [[ 4],
          [ 5]],

         [[ 6],
          [ 7]],

         [[ 8],
          [ 9]],

         [[10],
          [11]],

         [[12],
          [13]],

         [[14],
          [15]],

         [[16],
          [17]],

         [[18],
          [19]]]])

In [119]: s.unfold(0,1,1).shape
Out[119]: torch.Size([1, 10, 2, 1])

所以你看到我原来的张量是 (1,10,2) 的形状,我要求使用参数 s.unfold(0, 1, 1).

展开操作

根据前面示例的原始理解,我假设这意味着在维度 0 中,我们一次获取 1 个块,步长 1。因此,当我们进入维度 0 时,我们看到我们只有一个大小 (10,2) 的块。所以输出应该刚刚采用这个块,可能它应该刚刚添加一个维度来包装这个块并给我一个大小 (1, 10, 2) 的输出。

但是,它给了我一个大小为 (1, 10, 2, 1) 的输出。为什么最后多了一个维度呢?有人可以直观地详细说明吗?

文档指出:

An additional dimension of size size is appended in the returned tensor.

其中 size 是您指定的块的大小(第二个参数)。根据定义,它总是添加一个额外的维度,无论您选择什么尺寸,它都会保持一致。仅仅因为维度的大小为 1,并不意味着它应该被自动省略。

关于这背后的直觉,让我们考虑一下,我们创建一个包含所有块的列表,而不是 return 使用最后一个维度表示块的张量。为简单起见,我们将其限制为步长为 1 的第一个维度。

import torch
from typing import List


def list_chunks(tensor: torch.Tensor, size: int) -> List[torch.Tensor]:
    chunks = []
    for i in range(tensor.size(0) - size + 1):
        chunks.append(tensor[i : i + size])
    return chunks


x = torch.arange(1.0, 8)
s = torch.arange(20).view(1, 10, 2)

# As expected, a list with 6 elements, as there are 6 chunks.
list_chunks(x, 2)
# => [tensor([1., 2.]),
#     tensor([2., 3.]),
#     tensor([3., 4.]),
#     tensor([4., 5.]),
#     tensor([5., 6.]),
#     tensor([6., 7.])]

# The list has only a single element, as there is only a single chunk.
# But it's still a list.
list_chunks(s, 1)
# => [tensor([[[ 0,  1],
#              [ 2,  3],
#              [ 4,  5],
#              [ 6,  7],
#              [ 8,  9],
#              [10, 11],
#              [12, 13],
#              [14, 15],
#              [16, 17],
#              [18, 19]]])]

我特意包含了类型注释,以便更清楚地说明我们对该函数的期望。如果只有一个块,它将是一个只有一个元素的列表,因为它始终是一个块列表。

您期望的是一种不同的行为,即当只有一个块时,您需要单个块而不是列表。这将改变实现如下。

from typing import List, Union


def list_chunks(tensor: torch.Tensor, size: int) -> Union[List[torch.Tensor], torch.Tensor]:
    chunks = []
    for i in range(tensor.size(0) - size + 1):
        chunks.append(tensor[i : i + size])
    # If it's a single chunk, return just the chunk itself
    if len(chunks) == 1:
        return chunks[0]
    else:
        return chunks

随着这一变化,任何使用此功能的人现在都需要考虑两种情况。如果您不区分列表和单个块(张量),您将得到意想不到的结果,例如遍历块将改为遍历张量的第一维。

编程直观的方法总是return一个块列表,torch.unfold做同样的事情,但它不是一个块列表,而是一个张量,其中最后一个维度可以被视为块列表。