相关阅读
Pytorch基础https://blog.csdn.net/weixin_45791458/category_12457644.html?spm=1001.2014.3001.5482
在Pytorch中,view是Tensor的一个重要方法,用于返回一个改变了形状,但数据和数据的顺序与原来一致的新张量,但是新张量需要与原张量共用底层存储(称为视图)。
Tensor.view(*shape) → Tensor
view方法的语法如下所示:
Tensor.view(*shape) → Tensor
shape (torch.Size or int...) – the desired size
view的基本用法,与reshape相同,所以可以参考下面的博文。
Pytorch基础:Tensor的reshape方法https://blog.csdn.net/weixin_45791458/article/details/133445832?csdn_share_tail=%7B%22type%22%3A%22blog%22%2C%22rType%22%3A%22article%22%2C%22rId%22%3A%22133445832%22%2C%22source%22%3A%22weixin_45791458%22%7D 本文的重点旨在阐明一个被很多人(九成以上)误解的细节:View方法也可以对非连续张量使用!
首先简单介绍连续张量的定义并给出一些简单的例子:
1、如果简单来说,一个连续张量就是,张量底层的数据线性存储顺序和张量数据的遍历顺序一致,此处的遍历顺序是指先遍历列,再遍历行(如有更多的维度,以此类推)。
2、如果用严格的定义,连续张量的各个维度大小和步长需要满足:对于一个有n+1维的张量(维度索引从0到n),任何的i属于0到n-1满足stride[i]=stride[i+1]×size[i+1],其中size[i]指的是第i维的大小,stride[i]指的是第i维的步长(其实就是从第i维的数据,存储时跨越的数据数)。其中stride[n]必须是1,即最后一个维度的数据必须是相邻存储的。
import torch
a=torch.rand(2,3)
print(a)
print(list(a.size()))
print(a.stride())
print(a.is_contiguous())
输出:
tensor([[0.4409, 0.5075, 0.8291],
[0.9381, 0.6066, 0.5888]])
[2, 3]
(3, 1)
True
a张量的线性存储顺序为0.4409, 0.5075, 0.8291 0.9381, 0.6066, 0.5888,因此a张量第0维的步长为3,即0.4409, 0.5075, 0.8291分别与0.9381, 0.6066, 0.5888间隔的数据数,且满足stride[0]=stride[1]×size[1],因此a是一个连续张量,可以使用.is_contiguous()方法鉴别。
import torch
a=torch.rand(2,3)
b=a.t()
print(b)
print(list(b.size()))
print(b.stride())
print(b.is_contiguous())
输出:
tensor([[0.4409, 0.9381],
[0.5075, 0.6066],
[0.8291, 0.5888]])
[3, 2]
(1, 3)
False
b张量为a张量转置后的结果,它与a共享底层的线性存储,因此b张量第0维的步长为1,即0.4409, 0.9381分别到0.5075, 0.6066分别到0.8291, 0.5888的间隔的数据数,不满足步长和维度大小的要求,因此是一个非连续张量。
View常常被错误地认为只能对连续张量使用,因为非连续张量使用view方法时,有时会报错,例如对于上面的张量b。
import torch
a=torch.rand(2,3)
b=a.t()
print(b.is_contiguous())
c=b.view(1,6)
输出:
False
Traceback (most recent call last):
File "/home/zhangchen/Vit(quant)/test.py", line 8, in <module>
c=b.view(1,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.
但这并不能说明,所有的非连续张量都无法使用view方法,下面时Pytorch官网对于view方法的解释:
torch.Tensor.view — PyTorch 2.4 documentation
可以看出,view方法对变换后维度有要求,满足其一即可:
1、新的维度是原维度的子空间。
2、新的维度跨越了原来满足张量连续性条件的那些维度。
其中就是第二点,造成了很多人误以为只有连续性张量才能使用view,因为一个连续性张量一定满足该条件。
但是第一点却常被人忽视,什么叫新的维度是原维度的子空间呢?其实这是一个类似于因式分解的概念,下面举例说明。
import torch
a=torch.rand(4,6)
b=a.t()
print(list(b.shape))
print(b.is_contiguous())
c=b.view(2,3,2,2)
print(list(c.shape))
输出:
[6, 4]
False
[2, 3, 2, 2]
其中,非连续张量b的形状是(4,6),而它成功使用了view方法,张量c的形状是(2, 3, 2, 2),这实际上就是张量c的四个维度满足了第二个条件,此时四个新的维度,都是张量b两个维度的子空间,即原来的第0个维度大小4被分解为张量c的第0个维度大小2和第1个维度大小3;原来的第1个维度大小6被分解为张量c的第2个维度大小2和第3个维度大小2。
甚至可以两种条件互补满足,即某些维度满足连续性条件,某些维度满足子空间条件,见下例子。
import torch
a=torch.rand(3,4,8,6)
b=a.permute(0,1,3,2)
print(list(b.shape))
print(b.stride())
print(b.is_contiguous())
c=b.view(2,6,3,2,4,2)
print(list(c.shape))
输出:
[3, 4, 6, 8]
(192, 48, 1, 6)
False
[2, 6, 3, 2, 4, 2]
其中,c张量的第2维大小3和第3维大小2来自于b张量的第3维的分解,c张量的第4维大小4和第5维大小2来自于b张量的第4维的分解。而张量的前两维来自b张量的第0维和第1维,因此b张量的前两维需要满足张量连续性条件,可以看出192=4*48即满足stride[0]=stride[1]×size[1],因此可以成功使用View方法。
在子空间分解的时候,原张量的各个维度的分解,是有顺序的,比如对于b张量的最后一维大小维8,c张量的第3维的2和第4维的4并不是它分解的子空间,因为此时c张量前三维的乘积结果是2*6*3不等于b张量前三维乘积的结果3*4*6,而c张量前四维的乘积结果是2*6*3*2等于b张量前三维乘积的结果3*4*6。从前向后分析和从后向前分析都是一样的,因为一旦一个维度之前的维度乘积确认相等了,该维度之后的维度也相等(因为数据个数不变)。
在某种特殊情况下,view方法的原张量确实必须是连续性张量,比如将张量展平,此时由于不可能满足子空间分解的条件,要求原张量的所有维满足连续性条件,因此原张量只能是一个连续性张量。
import torch
a=torch.rand(3,4,8,6)
b=a.permute(0,1,3,2)
print(list(b.shape))
print(b.stride())
print(b.is_contiguous())
c=b.view(-1)
输出:
[3, 4, 6, 8]
(192, 48, 1, 6)
False
Traceback (most recent call last):
File "/home/zhangchen/Vit(quant)/test.py", line 7, in <module>
c=b.view(-1) #
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.