一、Tensor数据结构
tensor的结构分为头信息区(Tensor)和存储区(Storage),信息区主要保存着tensor的形状(size)、步长(stride)、数据类型(type)等信息,而真正的数据则保存成连续数组。由于数据规模庞大,因此信息区的元素占用内存较少,主要内存占用取决于tensor中元素的数目,也就是存储区的大小。
一般而言,一个tensor有着与之相对应的storage,storage是在data之上封装的接口,便于使用,。不同tensor的头信息一般不同,但却可能使用相同的storage,也就是共享内存。下面拿几个共享内存的tensor举个例子
创建tensor:
In [17]: import torch as t
In [18]: a=t.arange(8)
In [19]: a
Out[19]: tensor([0, 1, 2, 3, 4, 5, 6, 7])
In [20]: a.storage()
Out[20]:
0
1
2
3
4
5
6
7
[torch.LongStorage of size 8]
以view方法改变tensor的形状
In [21]: b=a.view(2,4)
In [22]: b.storage()
Out[22]:
0
1
2
3
4
5
6
7
[torch.LongStorage of size 8]
下面我们通过判断两个tensor的storage的id是否相同来判断它们是否共享内存(一个对象的id值可以看作它在内存中的地址)
In [23]: id(a.storage)==id(b.storage)
Out[23]: True
从结果True可以看出,两个tensor的storage地址是一致的,也就是说它们共享内存。
从另一个方面讲,假如a,b共享内存,那么当我们对a修改后,b的数据也会随之改变,下面让我们来验证一下:
In [24]: a[0]=100
In [25]: b
Out[25]:
tensor([[100, 1, 2, 3],
[ 4, 5, 6, 7]])
从这几行代码我们看到,当把a的第一个元素修改为100后,b的第一个元素也变成了100,显然a,b共享内存。
接下来我们对a进行切片,并把它赋值给新的张量c
In [28]: c=a[2:]
In [29]: c
Out[29]: tensor([2, 3, 4, 5, 6, 7])
In [30]: c.storage()
Out[30]:
100
1
2
3
4
5
6
7
[torch.LongStorage of size 8]
我们可以看到,尽管在切片后新的张量c的元素仅仅为a的后六个,然而当我们查看c的storage时,我们仍可以惊奇的发现c的storage与a的一致,这意味着切片得到的张量c仍与原张量a共享内存。
data_ptr()函数用以查看tensor的首个元素的内存地址,我么可以查看一下a与c的首个元素的地址
In [31]: c.data_ptr(),a.data_ptr()
Out[31]: (2478227845200, 2478227845184)
从结果来看,c的首个元素地址地址比a的首个元素地址多了16,这是因为a比c多两个元素(每个元素8个字节(float)),而c是a从第三个元素开始切片得到的,由此更加印证了a与c共享内存。
可见,事实上绝大多数操作并不修改tensor的数据,只是修改他们的头信息,如形状,类型等等。通过这种机制可以节省内存,提升处理速度。一般而言,高级索引不共享内存,而普通索引共享内存。
二、有关tensor的其他一些话题
持久化:使用t.save和t.load可以实现tensor的保存和加载,在使用时可以指定使用的pickle模块(用以序列化对象并存储在磁盘上),在load时还可以将GPU tensor映射到CPU或者其他的GPU上。
向量化:向量化计算是一种特殊的并行计算方式,它可在同一时间内执行多个操作,通常是对不同的数据执行同一个或者同一批命令。向量化可极大地提高科学运算的效率。
下面我们一起了解一下向量化计算究竟如何提高运算效率
In [33]: def for_loop_add(x,y):
...: result=[]
...: for i,j in zip(x,y):
...: result.append(i+j)
...: return t.Tensor(result)
...:
In [34]: x=t.zeros(1000)
In [35]: y=t.ones(1000)
In [36]: %timeit -n 10 for_loop_add(x,y)
9.84 ms ± 1.83 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
In [37]: %timeit -n 10 x+y
10.7 µs ± 5.57 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)
可见,对于两个有1000个元素的tensor,向量化运算的时间仅为for循环的1/1000左右。