tensor内部结构

ensor的数据结构如下图所示。tensor分为头信息区(Tensor)和存储区(Storage),信息区主要保存着tensor的形状(size)、步长(stride)、数据类型(type)等信息,而真正的数据则保存成连续数组。由于数据动辄成千上万,因此信息区元素占用内存较少,主要内存占用则取决于tensor中元素的数目,也即存储区的大小。

在这里插入图片描述
一般来说一个tensor有着与之相对应的storage, storage是在data之上封装的接口,便于使用,而不同tensor的头信息一般不同,但却可能使用相同的数据。下面看两个例子。

a = t.arange(0, 6) #a为一维tensor
print(a.storage())
"""
 0
 1
 2
 3
 4
 5
[torch.LongStorage of size 6]
"""
b = a.view(2, 3)
print(b.storage())
"""
 0
 1
 2
 3
 4
 5
[torch.LongStorage of size 6]
"""
# 一个对象的id值可以看作它在内存中的地址
# storage的内存地址一样,即是同一个storage
print(id(b.storage()) == id(a.storage()))
"""
True
"""
# a改变,b也随之改变,因为他们共享storage
a[1] = 100
print(b)
"""
tensor([[  0, 100,   2],
        [  3,   4,   5]])

"""
c = a[2:]
print(c.storage())
"""
 0
 100
 2
 3
 4
 5
[torch.LongStorage of size 6]
"""
print(c.data_ptr(), a.data_ptr()) # data_ptr返回tensor首元素的内存地址
# 可以看出相差16,这是因为4*4=16--相差四个元素,每个元素占4个字节(float)
"""
1736685874768 1736685874752
"""
c[0] = -100 # c[0]的内存地址对应a[2]的内存地址
print(a) #a为一维tensor
"""
tensor([   0,  100, -100,    3,    4,    5])
"""

d = t.LongTensor(c.storage())
d[0] = 6666
print(b)
"""
tensor([[6666,  100, -100],
        [   3,    4,    5]])
"""
# 下面4个tensor共享storage
print(id(a.storage()) == id(b.storage()) == id(c.storage()) == id(d.storage()))
"""
True
"""
#storage_offset():偏移量 以储存元素的个数的形式返回tensor在原内存中的偏移量。
print(a.storage_offset(), c.storage_offset(), d.storage_offset())
"""
0 2 0
"""
e = b[::2, ::2] # 隔2行/列取一个元素
id(e.storage()) == id(a.storage())
"""
tensor([[6666, -100]])
True
"""
#stride():步长
print(a)
print(b)
print(e)
print(b.stride(), e.stride())
"""
(3, 1) (6, 2)
"""
print(e.is_contiguous())#判断内存是否连续
"""
False
"""

可见绝大多数操作并不修改tensor的数据,而只是修改了tensor的头信息。这种做法更节省内存,同时提升了处理速度。在使用中需要注意。 此外有些操作会导致tensor不连续,这时需调用tensor.contiguous方法将它们变成连续的数据,该方法会使数据复制一份,不再与原来的数据共享storage。

其它关于Tensor的话题

GPU/CPU
tensor可以很随意的在gpu/cpu上传输。使用tensor.cuda(device_id)或者tensor.cpu()。另外一个更通用的方法是tensor.to(device)。

a = t.randn(3, 4)
print(a.device)
"""
cpu
"""
if t.cuda.is_available():
    a = t.randn(3,4, device=t.device('cuda:1'))
    # 等价于
    # a.t.randn(3,4).cuda(1)
    # 但是前者更快
    print(a.device)


device = t.device('cpu')
print(a.to(device))
"""
tensor([[ 0.9213, -1.1403, -0.9568,  2.2102],
        [ 1.1778, -0.8786,  0.2658,  1.8155],
        [ 0.3244,  0.7009, -1.0741, -0.7994]])
"""

注意

  • 尽量使用tensor.to(device), 将device设为一个可配置的参数,这样可以很轻松的使程序同时兼容GPU和CPU
  • 数据在GPU之中传输的速度要远快于内存(CPU)到显存(GPU), 所以尽量避免频繁的在内存和显存中传输数据。

持久化
Tensor的保存和加载十分的简单,使用t.save和t.load即可完成相应的功能。在save/load时可指定使用的pickle模块,在load时还可将GPU tensor映射到CPU或其它GPU上。

if t.cuda.is_available():
    a = a.cuda(1) # 把a转为GPU1上的tensor,
    t.save(a,'a.pth')

    # 加载为b, 存储于GPU1上(因为保存时tensor就在GPU1上)
    b = t.load('a.pth')
    # 加载为c, 存储于CPU
    c = t.load('a.pth', map_location=lambda storage, loc: storage)
    # 加载为d, 存储于GPU0上
    d = t.load('a.pth', map_location={'cuda:1':'cuda:0'})

向量化
向量化计算是一种特殊的并行计算方式,相对于一般程序在同一时间只执行一个操作的方式,它可在同一时间执行多个操作,通常是对不同的数据执行同样的一个或一批指令,或者说把指令应用于一个数组/向量上。向量化可极大提高科学运算的效率,Python本身是一门高级语言,使用很方便,但这也意味着很多操作很低效,尤其是for循环。在科学计算程序中应当极力避免使用Python原生的for循环。

在实际使用中应尽量调用内建函数(buildin-function),这些函数底层由C/C++实现,能通过执行底层优化实现高效计算。因此在平时写代码时,就应养成向量化的思维习惯,千万避免对较大的tensor进行逐元素遍历。
注意:

  • 大多数t.function都有一个参数out,这时候产生的结果将保存在out指定tensor之中。
  • t.set_num_threads可以设置PyTorch进行CPU多线程并行计算时候所占用的线程数,这个可以用来限制PyTorch所占用的CPU数目。
  • t.set_printoptions可以用来设置打印tensor时的数值精度和格式。
a = t.arange(0, 20000000)
print(a[-1], a[-2]) # 32bit的IntTensor精度有限导致溢出
b = t.LongTensor()
t.arange(0, 20000000, out=b) # 64bit的LongTensor不会溢出
print(b[-1])
print(b[-2])
"""
tensor(19999999) tensor(19999998)
tensor(19999999)
tensor(19999998)
"""
a = t.randn(2,3)
print(a)
"""
tensor([[-0.3922,  0.6426,  1.3085],
        [ 0.8493, -2.3117,  0.7676]])
"""
t.set_printoptions(precision=10)
print(a)
"""
tensor([[-1.2462767363,  0.2002745867,  0.5890192389],
        [ 0.3281148374, -1.7425490618,  1.7160825729]])
"""
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值