PyTorch - 连续()

blu*_*nox 52 neural-network contiguous deep-learning lstm pytorch

我在github上看到了这个LSTM语言模型的例子(链接).它一般来说对我来说非常清楚.但是我仍然在努力理解调用的内容contiguous(),这在代码中会多次发生.

例如,在代码输入的第74/75行中,创建LSTM的目标序列.数据(存储在其中ids)是二维的,其中第一维是批量大小.

for i in range(0, ids.size(1) - seq_length, seq_length):
    # Get batch inputs and targets
    inputs = Variable(ids[:, i:i+seq_length])
    targets = Variable(ids[:, (i+1):(i+1)+seq_length].contiguous())
Run Code Online (Sandbox Code Playgroud)

举个简单的例子,当使用批量大小为1和seq_length10时inputs,targets看起来像这样:

inputs Variable containing:
0     1     2     3     4     5     6     7     8     9
[torch.LongTensor of size 1x10]

targets Variable containing:
1     2     3     4     5     6     7     8     9    10
[torch.LongTensor of size 1x10]
Run Code Online (Sandbox Code Playgroud)

所以一般来说我的问题是,contiguous()我需要什么以及为什么需要它?

此外,我不明白为什么该方法被调用目标序列而不是输入序列,因为两个变量都包含相同的数据.

怎么可能targetsinputs不连续的,仍然是连续的?

编辑: 我试图忽略调用contiguous(),但这会在计算损失时导致错误消息.

RuntimeError: invalid argument 1: input is not contiguous at .../src/torch/lib/TH/generic/THTensor.c:231
Run Code Online (Sandbox Code Playgroud)

所以显然contiguous()在这个例子中调用是必要的.

(为了保持这种可读性,我避免在这里发布完整代码,可以使用上面的GitHub链接找到它.)

提前致谢!

Shi*_*hah 119

在PyTorch中Tensor上的操作很少,它们并没有真正改变张量的内容,而只是如何将索引转换为张量到字节的位置.这些操作包括:

narrow(),view(),expand()transpose()

例如:当你调用时transpose(),PyTorch不会生成具有新布局的新张量,它只是修改Tensor对象中的元信息,因此偏移和步幅是新形状.转置张量和原始张量确实共享记忆!

x = torch.randn(3,2)
y = torch.transpose(x, 0, 1)
x[0, 0] = 42
print(y[0,0])
# prints 42
Run Code Online (Sandbox Code Playgroud)

这就是连续概念的用武之地.上面x是连续的,但y不是因为它的内存布局不同于从头开始制作的相同形状的张量.请注意,单词"contiguous"有点误导,因为它不是张量的内容在断开的内存块周围展开.这里的字节仍然分配在一个内存块中,但元素的顺序是不同的!

当你调用时contiguous(),它实际上会生成张量的副本,因此元素的顺序与从头开始创建的相同形状的张量相同.

通常你不需要担心这个.如果PyTorch期望连续张量,但如果不是那么你会得到RuntimeError: input is not contiguous,然后你只需要添加一个调用contiguous().

  • 什么时候我们**需要**需要调用“连续”? (5认同)
  • 我无法肯定地回答这个问题,但我的猜测是,一些PyTorch代码使用C++中实现的操作的高性能矢量化实现,并且此代码不能使用Tensor的元信息中指定的任意偏移/跨步.这只是猜测. (3认同)
  • 为什么被调用者不能简单地自己调用“contigious()”? (3认同)
  • @CharlieParker我也想知道需要“连续”的情况。我将其作为问题发布[此处](/sf/ask/4888827261/)。 (3认同)
  • 另一种流行的张量操作是“permute”,它也可能返回非“连续”张量。 (2认同)

Keh*_*eho 65

如果一维数组的[0, 1, 2, 3, 4]项在内存中彼此相邻排列,则该一维数组是连续的,如下所示:

连续内存分配

如果存储它的内存区域如下所示,则它不是连续的:

非连续分配

对于二维或更多维数组,项目也必须彼此相邻,但顺序遵循不同的约定。让我们考虑下面的二维数组:

>>> t = torch.tensor([[0, 1, 2, 3], [4, 5, 6, 7], [8, 9, 10, 11]])
Run Code Online (Sandbox Code Playgroud)

二维数组

如果行像这样彼此相邻存储,则内存分配是C 连续的:

二维记忆

这就是 PyTorch 认为是连续的。

>>> t.is_contiguous()
True
Run Code Online (Sandbox Code Playgroud)

PyTorch 的 Tensor 类方法stride()给出了要跳过以获取每个维度中的下一个元素的字节数

>>> t.stride()
(4, 1)
Run Code Online (Sandbox Code Playgroud)

我们需要跳过 4 个字节才能转到下一行,但只需要跳过一个字节即可转到同一行中的下一个元素。

正如其他答案中所述,某些 Pytorch 操作不会更改内存分配,只会更改元数据。

例如转置方法。让我们转置张量:

二维数组

内存分配没有改变:

二维内存不连续

但大步却做到了:

>>> t.T.stride()
(1, 4)
Run Code Online (Sandbox Code Playgroud)

我们需要跳过 1 个字节才能转到下一行,需要跳过 4 个字节才能转到同一行中的下一个元素。张量不再是 C 连续的(实际上是Fortran 连续的:每列彼此相邻存储)

>>> t.T.is_contiguous()
False
Run Code Online (Sandbox Code Playgroud)

contiguous()将重新安排内存分配,使张量是 C 连续的:

二维连续内存

>>> t.T.contiguous().stride()
(3, 1)
Run Code Online (Sandbox Code Playgroud)

一些操作,例如reshape()view(),会对底层数据的连续性产生不同的影响。


pat*_*_ai 25

pytorch文档:

连续的()→张量

Returns a contiguous tensor containing the same data as self 
Run Code Online (Sandbox Code Playgroud)

张量.如果自张量是连续的,则此函数返回自张量.

contiguous在这里是指在内存中是连续的.因此该contiguous函数根本不会影响目标张量,它只是确保它存储在连续的内存块中.

  • 因此,显然pytorch要求损失中的目标在记忆中是连续的,但神经网络的输入不需要满足这个要求. (2认同)
  • 太好了谢谢!我认为这对我来说很有意义,我注意到contiguous()也被应用到前向函数中的输出数据(当然以前是输入),因此在计算损失时输出和目标都是连续的。非常感谢! (2认同)

小智 8

正如前面的回答一样,contigous()分配了连续的内存块,当我们将张量传递给c或c ++后端代码时,它会很有用,其中张量作为指针传递


小智 6

tensor.contiguous()将创建张量的副本,并且副本中的元素将以连续方式存储在内存中。当我们首先对张量进行transpose()并对其进行整形(查看)时,通常需要contiguous()函数。首先,让我们创建一个连续的张量:

aaa = torch.Tensor( [[1,2,3],[4,5,6]] )
print(aaa.stride())
print(aaa.is_contiguous())
#(3,1)
#True
Run Code Online (Sandbox Code Playgroud)

stride()返回(3,1)的意思是:当沿着第一维移动每一步(逐行)时,我们需要在内存中移动3步。沿第二维(逐列)移动时,我们需要在内存中移动1步。这表明张量中的元素是连续存储的。

现在我们尝试将come函数应用于张量:

bbb = aaa.transpose(0,1)
print(bbb.stride())
print(bbb.is_contiguous())

ccc = aaa.narrow(1,1,2)   ## equivalent to matrix slicing aaa[:,1:3]
print(ccc.stride())
print(ccc.is_contiguous())


ddd = aaa.repeat(2,1 )   # The first dimension repeat once, the second dimension repeat twice
print(ddd.stride())
print(ddd.is_contiguous())

## expand is different from repeat  if a tensor has a shape [d1,d2,1], it can only be expanded using "expand(d1,d2,d3)", which
## means the singleton dimension is repeated d3 times
eee = aaa.unsqueeze(2).expand(2,3,3)
print(eee.stride())
print(eee.is_contiguous())

fff = aaa.unsqueeze(2).repeat(1,1,8).view(2,-1,2)
print(fff.stride())
print(fff.is_contiguous())

#(1, 3)
#False
#(3, 1)
#False
#(3, 1)
#True
#(3, 1, 0)
#False
#(24, 2, 1)
#True
Run Code Online (Sandbox Code Playgroud)

好的,我们可以发现transpose(),narrow()和张量切片以及expand()将使生成的张量不连续。有趣的是,repeat()和view()不会使其不连续。所以现在的问题是:如果我使用不连续张量会怎样?

答案是view()函数不能应用于不连续的张量。这可能是因为view()要求将张量连续存储,以便它可以在内存中进行快速整形。例如:

bbb.view(-1,3)
Run Code Online (Sandbox Code Playgroud)

我们将得到错误:

---------------------------------------------------------------------------
RuntimeError                              Traceback (most recent call last)
<ipython-input-63-eec5319b0ac5> in <module>()
----> 1 bbb.view(-1,3)

RuntimeError: invalid argument 2: view size is not compatible with input tensor's size and stride (at least one dimension spans across two contiguous subspaces). Call .contiguous() before .view(). at /pytorch/aten/src/TH/generic/THTensor.cpp:203
Run Code Online (Sandbox Code Playgroud)

为了解决这个问题,只需将contiguous()添加到不连续的张量中,以创建连续的副本,然后应用view()

bbb.contiguous().view(-1,3)
#tensor([[1., 4., 2.],
        [5., 3., 6.]])
Run Code Online (Sandbox Code Playgroud)


pro*_*sti 6

接受的答案太好了,我试图欺骗transpose()功能效果。我创建了两个可以检查samestorage()和 的函数contiguous

def samestorage(x,y):
    if x.storage().data_ptr()==y.storage().data_ptr():
        print("same storage")
    else:
        print("different storage")
def contiguous(y):
    if True==y.is_contiguous():
        print("contiguous")
    else:
        print("non contiguous")
Run Code Online (Sandbox Code Playgroud)

我检查并得到了这个结果作为表格:

职能

您可以查看下面的检查器代码,但让我们举一个当张量不连续时的示例。我们不能简单地调用view()那个张量,我们需要reshape()它或者我们也可以调用.contiguous().view().

x = torch.randn(3,2)
y = x.transpose(0, 1)
y.view(6) # RuntimeError: view size is not compatible with input tensor's size and stride (at least one dimension spans across two contiguous subspaces). Use .reshape(...) instead.
  
x = torch.randn(3,2)
y = x.transpose(0, 1)
y.reshape(6)

x = torch.randn(3,2)
y = x.transpose(0, 1)
y.contiguous().view(6)
Run Code Online (Sandbox Code Playgroud)

还要注意的是,有些方法最终会创建连续不连续的张量。有些方法可以对同一个存储进行操作,有些方法flip()会在返回之前创建一个新的存储(读取:克隆张量)。

检查器代码:

import torch
x = torch.randn(3,2)
y = x.transpose(0, 1) # flips two axes
print("\ntranspose")
print(x)
print(y)
contiguous(y)
samestorage(x,y)

print("\nnarrow")
x = torch.randn(3,2)
y = x.narrow(0, 1, 2) #dim, start, len  
print(x)
print(y)
contiguous(y)
samestorage(x,y)

print("\npermute")
x = torch.randn(3,2)
y = x.permute(1, 0) # sets the axis order
print(x)
print(y)
contiguous(y)
samestorage(x,y)

print("\nview")
x = torch.randn(3,2)
y=x.view(2,3)
print(x)
print(y)
contiguous(y)
samestorage(x,y)

print("\nreshape")
x = torch.randn(3,2)
y = x.reshape(6,1)
print(x)
print(y)
contiguous(y)
samestorage(x,y)

print("\nflip")
x = torch.randn(3,2)
y = x.flip(0)
print(x)
print(y)
contiguous(y)
samestorage(x,y)

print("\nexpand")
x = torch.randn(3,2)
y = x.expand(2,-1,-1)
print(x)
print(y)
contiguous(y)
samestorage(x,y)
Run Code Online (Sandbox Code Playgroud)