tensor分为头信息区(Tensor)和存储区(Storage)。信息区主要保存着tensor的形状(size)、步长(stride)、数据类型(type)等信息,而真正的数据则保存成连续数组,存储在存储区。
通过id(tensor.storage)来获取“存储区”,注意:id(tensor)与id(tensor.storage())不同,id(tensor)是整个tensor所占的内存地址(包括信息区和存储区),而 id(tensor.storage())只是该tensor存储区的内存地址,也就是说某几个tensor的存储区的内存地址可以相同(即他们共享数据)。
tensor的底层存储与c语言很接近,多维tensor也是在storage中以一维的形式存储的,而并非像python的多维list存的是引用。
有一个问题,似乎即使两个张量不共享内存,他们的id(tensor.storage())也可以相同?这里其实涉及到对id()的理解:
一个对象在其生命周期中其id是唯一的,但是不同生命周期的对象的id是有可能相同的,两个对象可能先后使用堆上的同一块地址。也就是说,同一块内存上先后住过2个对象a和b,他们的id就会相同。参考https://www.zhihu.com/question/275830564
而is比较不会出现上面的问题,虽然is是依赖id()的,但是is判断时应该是把两个对象都接收,然后再判断id。两个对象都接收存起来就保证了两个进行is比较的对象必须在同一时刻都存在,即生命周期有重叠部分,就保证了必须是同一个对象id才会相同,is判断就不会失误了。参考【Python】Python中的id()和is
import torch
a=torch.tensor([[1,2],[2,3],[3,4]])
c=torch.tensor([[1,2],[2,3],[3,4]])
b=torch.cat((a,c))
print('a_id_storage:',id(a.storage()))
print('c_id_storage:',id(c.storage()))
print('b_id_storage:',id(b.storage()))
print(a.storage() is c.storage())
print('a_storage_offset:',a.storage_offset())
print('c_storage_offset:',c.storage_offset())
print('b_storage_offset:',b.storage_offset())
print('a_is_contiguous:',a.is_contiguous())
print('c_is_contiguous:',c.is_contiguous())
print('b_is_contiguous:',b.is_contiguous())
print('a第一个元素内存地址:',a.data_ptr())
print('c第一个元素内存地址:',c.data_ptr())
print('b第一个元素内存地址:',b.data_ptr())
print('修改a[0][0]=999')
a[0][0]=999
print('修改a后:')
print('a:',a)
print('c:',c)
print('b:',b)
输出:
# a c b 的id(tensor.storage())竟然是相同的?
a_id_storage: 140462411739968
c_id_storage: 140462411739968
b_id_storage: 140462411739968
# a和c并不是共享内存的
False
a_storage_offset: 0
c_storage_offset: 0
b_storage_offset: 0
a_is_contiguous: True
c_is_contiguous: True
b_is_contiguous: True
# 可以看出a c b的数据存储应该是不共享内存的
a第一个元素内存地址: 94432251447232
c第一个元素内存地址: 94432251464512
b第一个元素内存地址: 94432251479552
修改a[0][0]=999
# 可以看出a c b并不共享内存
修改a后:
a: tensor([[999, 2],
[ 2, 3],
[ 3, 4]])
c: tensor([[1, 2],
[2, 3],
[3, 4]])
b: tensor([[1, 2],
[2, 3],
[3, 4],
[1, 2],
[2, 3],
[3, 4]])
常用方法:
.is_contiguous()判断张量存储是否连续;
.data_ptr()返回张量第一个数据的内存地址;
id(tensor.storage())返回存储区地址;
.storage_offset()返回张量第一个元素在存储区的位置;
.stride()是在指定维度dim中从一个元素跳到下一个元素所必需的步长。当没有参数传入时,返回所有步长的元组。否则,将返回一个整数值作为特定维度dim中的步长。 实际是tensor对storage区的索引方式。
>>> b
tensor([[0, 1, 2],
[3, 9, 5]])
>>> b.stride()
(3, 1)
>>> b.stride(0)
3
>>> b.stride(1)
1
1 切片操作:
索引出来的结果与原数据共享内存(存储区内存地址相同,总内存地址不同)。
n]: x=torch.zeros(2,2) # x: [ [ 0,0]
[0,0] ]
y=x[0,:]
y+=1
print(y)
print(x)
print(id(y)==id(x))
print(id(y.storage)==id(x.storage))
[Out]:
y:[[1,1]]
x:[[1,1]
[0,0]]
False
True
2 detach,data方法:
与tensor全切片时相同。
3 numpy,from_numpy方法:
该方法将tensor与numpy相互转换,这两个函数所产生的tensor与numpy的数组共享内存(即存储区内存相同)。
4 张量维度操作(拼接、维度扩展、压缩、转置、重复......):
大部分操作都是共享内存的。
.view()和.reshape()区别:
.view()方法只能改变连续的(contiguous)张量,否则需要先调用.contiguous()方法;而.reshape()方法不受此限制;如果对 tensor 调用过 transpose
, permute
等操作的话会使该 tensor 在内存中变得不再连续。.view()方法返回的张量与原张量共享内存。
.reshape()方法返回的可能是原张量的copy,也可能不是。使用.reshape()时,如果张量是连续的,则此时执行和.view()相同的操作,是共享内存;如果不连续,则先调用.contiguous(),然后.view(),由于调用.contiguous()时会开辟新内存,故此时不共享内存。
torch.cat()和torch.stack()区别:
torch.cat()是在指定既有的维度上进行拼接,torch.stack()是在新维度上叠加。这两个是不共享内存的。
torch.transpose()和.T是共享内存的。
torch.unsqueeze()是在指定位置上增加新维度,torch.squeeze()是删除为1的维度。二者都共享内存。
.expand()和.repeat()的区别 :
.expand()只能扩张为1的维度,共享内存;.repeat()将原张量作为一个整体,按照给定的size进行重复,不共享内存。
.permute()执行维度换位,共享内存。