contiguous原意:邻近的,一个tensor是.is_contiguous() is True,说明该数组按照行展开和底层元数据的存储顺序是一致的。
Tensor多维数据的存储,是以多维数组按照行展开成一维数组,作为元数据,原信息里保存了他的形状。调用数据的时候,是按照 stride 取数据的。比如一个三行五列的Tensor,他的stride就是[5,1]。指的是取该维度下一个数据时,要走的步长。
如果要访问矩阵中的下一个元素都是通过偏移来实现,这个偏移量称为步长(stride[1])
为什么需要contiguous?
当我们调用.view()时,如果该数组之前被.permute()或.transpose()过,虽然这两个操作不会改变数组的元数据,但是会新建一个Tensor元信息,该元信息里声明了stride,就导致后续调用.view()时理想的情况是按照transpose后的tensor进行view展开,但实际上会按照底层元数据进行变形。view 仅在底层数组上使用指定的形状进行变形。而且上述直接调用会报错:
view size is not compatible with input tensor's size and stride
Contiguous如何作用的?
t2 = t.transpose(0,1)
t3 = t2.contiguous()
相当于重新开辟了一块内存,t3的元数据是按照变化后的行展开存储的。这样的话,.view()访问底层数组时,就不会产生元信息stride和元数据对应不上的问题了,也不会报错了。
什么时候需要Contiguous?
情况一:
当后续操作改变了元数据,但实际上不想改变原始Tensor的话,需要深拷贝或contiguous,新建内存存储新tensor。如:
x = torch.randn(3, 2)
y = torch.transpose(x, 0, 1)
print("修改前:")
print("x-", x)
print("y-", y)
print("\n修改后:")
y[0, 0] = 11
print("x-", x)
print("y-", y)
运行结果:
修改前:
x- tensor([[-0.5670, -1.0277],
[ 0.1981, -1.2250],
[ 0.8494, -1.4234]])
y- tensor([[-0.5670, 0.1981, 0.8494],
[-1.0277, -1.2250, -1.4234]])
修改后:
x- tensor([[11.0000, -1.0277],
[ 0.1981, -1.2250],
[ 0.8494, -1.4234]])
y- tensor([[11.0000, 0.1981, 0.8494],
[-1.0277, -1.2250, -1.4234]])
如果对y使用了.contiguous(),改变y的值时,x不会有任何影响!
情况二:
后续需要使用读取元数据的方法,如.view(),仅在底层数组上使用指定的形状进行变形,这时候如果前面使用了transpose、permute等改变元信息但不改变元数据的函数的话,就会出现元信息stride和实际形状不对应,view报错。