torch.contiguous()方法语义上是“连续的”,经常与torch.permute()、torch.transpose()、torch.view()方法一起使用,要理解这样使用的缘由,得从pytorch多维数组的低层存储开始说起:
touch.view()方法对张量改变“形状”其实并没有改变张量在内存中真正的形状,可以理解为:
- view方法没有拷贝新的张量,没有开辟新内存,与原张量共享内存;
- view方法只是重新定义了访问张量的规则,使得取出的张量按照我们希望的形状展现。
pytorch与numpy在存储MxN的数组时,均是按照行优先将数组拉伸至一维存储,比如对于一个二维张量
// An highlighted block
t = torch.tensor([[2, 1, 3], [4, 5, 9]])
在内存中实际上是
[2, 1, 3, 4, 5, 9]
按照行优先原则,数字在语义和在内存中都是连续的,当我们使用torch.transpose()方法或者torch.permute()方法对张量翻转后,改变了张量的形状
// An highlighted block
t2 = t.transpose(0, 1)
t2
tensor([[2,4],
[1,5],
[3,9])
此时如果对t2使用view方法,会报错:
原因是:改变了形状的t2语义上是3行2列的,在内存中还是跟t一样,没有改变,导致如果按照语义的形状进行view拉伸,数字不连续,此时torch.contiguous()方法就派上用场了
// An highlighted block
t3 = t2.contiguous()
t3
tensor([[2,4],
[1,5],
[3,9])
可以看到t3与t2一样,都是3行2列的张量,此时再对t3使用view方法:
// An highlighted block
t3.view(-1)
tensor([2, 4, 1, 5, 3, 9])
t3已经按照语义的形状展开了,t2与t共享内存是怎样的呢?
// An highlighted block
t.view(-1)
tensor([2, 1, 3, 4, 5, 9])
可以看出contiguous方法改变了多维数组在内存中的存储顺序,以便配合view方法使用
torch.contiguous()方法首先拷贝了一份张量在内存中的地址,然后将地址按照形状改变后的张量的语义进行排列。