1. tensor的数据结构
pytorch中一个tensor对象分为头信息区(Tensor)和存储区(Storage)两部分。
头信息区主要保存tensor的形状(size)、步长(stride)、数据类型(type)等信息;而真正的data(数据)则以连续一维数组的形式放在存储区,由torch.Storage实例管理着。
注意:storage永远是一维数组,任何维度的tensor的实际数据都存储在一维的storage中。
大部分情况下一个tensor有独立的头信息区和storage,但pytorch中也可以多个不同的tensor共享一个storage,这么做是为了节省内存。
也正因如此,从一个已有的tensor创建一个新的tensor时速度很快,因为并不会真正为新tensor开辟新内存。
如下图所示:
2. storage()
2.1 获取tensor的storage
>>>a = torch.tensor([[1.0, 4.0],[2.0, 1.0],[3.0, 5.0]])
>>>a.storage()
1.0
4.0
2.0
1.0
3.0
5.0
[torch.FloatStorage of size 6]
2.2 对storage进行索引取值
>>>a_storage = a.storage()
>>>a_storage[2] #获取第三个值
2.0
2.3 更改storage的值
注意:tensor的值存储在storage中,若改变了storage的值,势必会改变tensor的值。
>>>a_storage[0]=100 #改变storage的第1个值
>>>print(a.storage())
>>>print(a)
100.0
4.0
2.0
1.0
3.0
5.0
[torch.FloatStorage of size 6]
tensor([[100., 4.],
[ 2., 1.],
[ 3., 5.]])
2.4 tensor的id()和tensor.storage的id()
注意:当一个tensor创建后,id(tensor)固定不变(这没毛病);但id(tensor.storage)每次执行都会变化(这就有点诡异)★★★★,原因还不清楚。
#只运行一次
>>>a = torch.tensor([1,2])
#多次运行
>>>print(id(a))
>>>print(id(a.storage()))
2638892189312
2638989250912
2638892189312
2638989251056
2638892189312
2638989153376
2.5 多个tensor共用storage(判断多个tensor是否共用一个storage)
2.5.1 创建三个tensor(注意,只运行一次)
新建三个tensor a、b、c,其中b和c是在a的基础上创建的,事实上这三者共用了一个storage。
>>>a = torch.tensor([[1,2,3],[4,5,6]])
>>>b = a.view(3,2)
>>>c = a[:,1]
>>>print(a)
tensor([[1, 2, 3],
[4, 5, 6]])
>>>print(b)
tensor([[1, 2],
[3, 4],
[5, 6]])
>>>print(c)
tensor([2, 5])
2.5.2 利用id(tensor)获取tensor头信息区地址(不能判断是否共用storage)
id(tensor)得到的是tensor“头信息区”的地址,任何不同的tensor,其头信息区是不一样的,因此这里得到的三个不同的结果。
当tensor创建后,id(tensor)固定不变。
>>>print(id(a))
>>>print(id(b))
>>>print(id(c))
2638851940160
2638874315168
2638982342992
2.5.3 查看三个tensor的storage(不能判断是否共用storage)
虽然结果是一样的(但这还不能说是共用同一个storage)。
这里也可以看出,张量c虽然只有2个元素,但其storage依然是完整的1、2、3、4、5、6。这说明并没有给张量c独立创建storage,而是共享了a的storage。
可见,tensor的元素和他的storage的元素不一定相同。
>>>print(a.storage())
>>>print(b.storage())
>>>print(c.storage())
1
2
3
4
5
6
[torch.LongStorage of size 6]
1
2
3
4
5
6
[torch.LongStorage of size 6]
1
2
3
4
5
6
[torch.LongStorage of size 6]
2.5.4 利用tensor.data_ptr()查看tensor的首元素的地址(不能判断是否共用storage)
从前面可知a和b的首元素都是1,因此其首元素地址相同;而c的首元素是2,显然首元素地址与a和b是不一样。
多次运行代码,结果不变。
>>>print(a.data_ptr())
>>>print(b.data_ptr())
>>>print(c.data_ptr())
2638924337600
2638924337600
2638924337608
2.5.5 ★★利用tensor.storage.data_ptr()查看storage的首元素的地址(可以判断是否共用storage)
tensor.storage().data_ptr()返回的是storage的首元素地址,如果他们相同,则肯定是同一个storage。
>>>print(a.storage().data_ptr())
>>>print(b.storage().data_ptr())
>>>print(c.storage().data_ptr())
2638924337600
2638924337600
2638924337600
2.5.6 利用id(tensor.storage())获取storage地址(不能判断是否共用storage)
这条最奇怪!
根据前面我们已经知道,a、b、c共用storage,因此理论上id(tensor.storage())的结果是一样的,但实际上三者结果不一样,更奇怪的是每次运行都变化(网上很多例子都说这里三者是相同的,不知道是pytorch后面的版本发生了变化,还是啥原因)。
>>>print(id(a.storage()))
>>>print(id(b.storage()))
>>>print(id(c.storage()))
第一次运行结果
2638986051440
2638986049856
2638986050432
第二次运行结果
2638986050912
2638986047696
2638986048944
2.5.7 共用storage时,一个变,全部变
>>>c[0]=100 #从tensor改变某个元素
>>>print(a)
>>>print(b)
>>>print(c)
tensor([[ 1, 100, 3],
[ 4, 5, 6]])
tensor([[ 1, 100],
[ 3, 4],
[ 5, 6]])
tensor([100, 5])
>>>b.storage()[3]=-100 #从storage改变某个元素
>>>print(a)
>>>print(b)
>>>print(c)
tensor([[ 1, 100, 3],
[-100, 5, 6]])
tensor([[ 1, 100],
[ 3, -100],
[ 5, 6]])
tensor([100, 5])
3. stride()
stride是在指定维度(dim)中从一个元素跳到紧邻下一个元素所必需的步长。当没有参数传入时,stride()返回由每个维度步长组成的一个元组。如果有整数参数传入,则返回该整数指定的维度的步长。
注意:前面讲过,pytorch中tensor的实际数据在内存中是按照行优先连续存储的storage。stride的计算也是按照storage存储的位置进行计算。
tensor 连续情况:
如下例中,tensor a总共有0和1两个dim。
沿着dim0(即纵向),从一个元素跳到下一个元素(如从4到7)要经过2、5、7,三个元素;
沿着dim1(即横向),从一个元素跳到下一个元素(如从4到2)只经过2,一个元素。
>>>a = torch.tensor([[4,2,5],[7,6,9]])
>>>print(a)
tensor([[4, 2, 5],
[7, 6, 9]])
>>>print(a.storage())
4
2
5
7
6
9
[torch.LongStorage of size 6]
>>>print(a.stride())
(3, 1)
>>>print(a.stride(0))
3
>>>print(a.stride(1))
1
tensor 不连续情况:
如下例中,b是a的转置,则b是不连续的。
沿着dim0(即纵向),从一个元素跳到下一个元素(如从4到2)要经过2,一个元素;
沿着dim1(即横向),从一个元素跳到下一个元素(如从4到7)只经过2、5、7,三个元素。
>>>a = torch.tensor([[4,2,5],[7,6,9]])
>>>b = a.t()
>>>print(b)
>>>print(b.storage())
>>>print(b.stride())
tensor([[4, 7],
[2, 6],
[5, 9]])
4
2
5
7
6
9
[torch.LongStorage of size 6]
(1, 3)
4.storage_offset()
返回tensor的第一个元素与其storage的第一个元素的偏移量。
前面已经说过一个tensor的元素和他的storage的元素不一定完全相同(共享别人的storage)。
下例中a的第一个元素是3,a的storage的第一个元素也是3,因此其storage_offset是0;
b的第一个元素是5,而b的storage的第一个元素还是3,从3到5的storage_offset是2。
>>>a = torch.tensor([[3,2,5],[7,6,9]])
>>>print(a)
tensor([[3, 2, 5],
[7, 6, 9]])
>>>print(a.storage())
3
2
5
7
6
9
[torch.LongStorage of size 6]
>>>print(a.storage_offset())
0
>>>b =a[:,2] #在a的基础上生成
>>>print(b)
tensor([5, 9])
>>>print(b.storage())
3
2
5
7
6
9
[torch.LongStorage of size 6]
>>>print(b.storage_offset())
2