PyTorch 3.1 Tensor笔记

Tensor

创建Tensor

a = t.Tensor(2,3) # 指定形状
a.tolist() # 转为list
a.size() # 返回size,与shape等价,torch.Size([2, 3])
a.numel() # 元素总个数
b = t.tensor([2,3]) # tensor([1,2]), torch.Size([2])

常用tensor操作

函数名以_结尾的都是inplace方式, 即会修改调用者自己的数据。

a = t.arange(0, 6)
a.view(2,3) # 调整tensor形状,须保证前后元素总数一致,不修改自身数据
b = a.view(-1, 3) # 当某一维为-1的时候,会自动计算它的大小
b.unsqueeze(1) # 增加维度,在第1维(下标从0开始)上增加"1",torch.Size([2,1,3])
c = b.view(1,1,1,2,3) 
c.squeeze(0) # 减少维度,压缩第0维的"1",torch.Size([1,1,2,3])
b.resize_(1,3) # 修改tensor大小,超过原大小则自动分配新内存,小于原大小则保存之前的数据

索引操作

与numpy.ndarray类似的索引操作

a = t.randn(3, 4)
a[None].shape # 为a新增一个轴,等价于a.view(1, a.shape[0], a.shape[1],torch.Size([1, 3, 4])
a[:,None,:,None,None].shape # torch.Size([3, 1, 4, 1, 1])
a[a>1] # 等价于a.masked_select(a>1), 选择结果与原tensor不共享内存空间

gather用法

a = t.arange(0, 16).view(4, 4)
# tensor([[ 0,  1,  2,  3],
#         [ 4,  5,  6,  7],
#         [ 8,  9, 10, 11],
#         [12, 13, 14, 15]])
index = t.LongTensor([[2,2,0,1]])
# index与[[2,2,0,1]]的维度相同,即(1,4).当dim=0的时候,即按列查看,
# 第一个2处于该张量的第0列 ,所以第一个元素取a中第0列的索引为2的元素8
print('dim=0:',a.gather(0, index)) # dim=0: tensor([[8, 9, 2, 7]])
# 当dim=1的时候,即按行查看,第一个2处于第0行,所以第一个元素取a中第0行的索引为2的元素,
# 由于index只有第0行,所以对应的返回为a中第0行的索引为2,2,0,1的元素,即[[2,2,0,1]]
print('dim=1:',a.gather(1, index)) # dim=1: tensor([[2, 2, 0, 1]])
index = t.LongTensor([[0,2],
                      [1,2],
                      [1,3]])
# dim=0,数字i在第x列就选取原张量的第x列索引为i的元素
# 0表示第0列索引为0(0),3表示第1列索引为3(13)
print('dim=0:\n',a.gather(0, index))
# dim=0:
#  tensor([[ 0,  9],
#         [ 4,  9],
#         [ 4, 13]])
# dim=1,数字在第x行就选取原张量的第x行索引为i的元素
# 表示第0行索引为0的数(0),3表示第2行索引为3的元素(11)
print('dim=1\n',a.gather(1,index))
# dim=1
# tensor([[ 0,  2],
#        [ 5,  6],
#        [ 9, 11]])

scatter_用法,gather的逆操作

c = t.zeros(4,4)
index = t.tensor([[0,3],
                  [1,2],
                  [2,1],
                  [3,0]])
b = t.tensor([[0,3],
              [5,6],
              [10,9],
              [15,12]])
c.scatter_(1, index, b.float())
# tensor([[ 0.,  0.,  0.,  3.],
#         [ 0.,  5.,  6.,  0.],
#         [ 0.,  9., 10.,  0.],
#         [12.,  0.,  0., 15.]])

高级索引

高级索引操作的结果一般不和原始的Tensor共享内存

x = t.arange(0,27).view(3,3,3)
x[[1,2],
  [1,2],
  [2,0]] # x[1,1,2]和x[2,2,0]
x[[2, 1, 0], 
  [0], 
  [1]] # x[2,0,1],x[1,0,1],x[0,0,1]
x[[0, 2], ...] # x[0]和x[2]

Tensor类型

默认: FloatTensor

t.set_default_tensor_type('torch.DoubleTensor') # 设置默认tensor类型
a = t.Tensor(2,3) # DoubleTensor
a.dtype # float64
b = a.type(t.FloatTensor) # 把a转为FloatTensor
t.zeros_like(a) # 等价于t.zeros(a.shape,dtype=a.dtype,device=a.device)

逐元素操作

对tensor的每一个元素(point-wise,又名element-wise)进行操作,输入与输出形状一致

a % 3 # 求余数,等价于t.fmod(a,3)
a ** 2 # 求幂,等价于t.pow(a,2)
t.clamp(a, min=3) # 取a中的每一个元素与3相比较大的一个 (小于3的截断成3)
b = a.sin() # 求正弦

归并

使输出形状小于输入形状,并可以沿着某一维度进行指定操作。

b = t.ones(2,3) # 输入形状[2,3]
b.sum(dim=0, keepdim=True) # 输出形状[1,3],若keepdim=False则输出形状为[3]
b.sum(dim=1, keepdim=True) # 输出形状[2,1],若keepdim=False则输出形状为[2]
a = t.arrange(0,6).view(2,3) # tensor([0.,1.,2.],[3.,4.,5.])
a.cumsum(dim=1) # 沿行累加,输出形状[2,3],tensor([[0.,1.,3.],[3.,7.,12.]])

比较

a = t.linspace(0, 15, 6).view(2, 3) # tensor([[0.,3.,6.],[9.,12.,15.]])
b = t.linspace(15, 0, 6).view(2, 3) # tensro([[15.,12.,9.],[6.,3.,0.]])
a > b 	# tensor([[0,0,0],[1,1,1]],dtype=torch.uint8)
a[a>b]  # 取a中大于b的元素
t.max(a) # 取a中最大的元素
t.max(a,b) # 取a和b中大的元素构成新的tensor
t.clamp(a,min=10) # 比较a和10较大的元素,tensor([10.,10.,10.].[10.,12.,15.])

线性代数

矩阵的转置会导致存储空间不连续,需调用它的.contiguous方法将其转为连续。

a = t.ones(3,3)
t.trace(a) # 对角线元素之和 tensor(3.)
t.diag(a) # 对角线元素 tensor([1.,1.,1.])
Tensor和Numpy
a = np.ones([2, 3],dtype=np.float32)
b = t.from_numpy(a) # 与b=t.Tensor(a)等价,数据类型一样时内存共享
a[0,1] = 100 # 修改索引为(0,1)的值
tensor = t.tensor(a)  # t.tensor只进行数据拷贝,不会共享内存

广播法则(broadcast)是科学运算中经常使用的一个技巧,它在快速执行向量化的同时不会占用额外的内存/显存。
Numpy的广播法则定义如下:

  • 让所有输入数组都向其中shape最长的数组看齐,shape中不足的部分通过在前面加1补齐
  • 两个数组要么在某一个维度的长度一致,要么其中一个为1,否则不能计算
  • 当输入数组的某个维度的长度为1时,计算时沿此维度复制扩充成一样的形状
a = t.ones(3, 2)
b = t.zeros(2, 3, 1)
# 自动广播法则
# 第一步:a是2维,b是3维,所以先在较小的a前面补1 ,
#               即:a.unsqueeze(0),a的形状变成(1,3,2),b的形状是(2,3,1),
# 第二步:   a和b在第一维和第三维形状不一样,其中一个为1 ,
#               可以利用广播法则扩展,两个形状都变成了(2,3,2)
a+b
# tensor([[[1., 1.],
#          [1., 1.],
#          [1., 1.]],
# 
#         [[1., 1.],
#          [1., 1.],
#          [1., 1.]]])
Tensor的内部结构

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

绝大多数操作并不修改tensor的数据,只修改了tensor的头信息,更节省内存,同时提升了处理速度。

普通索引可以通过只修改tensor的offset,stride和size,而不修改storage来实现.

a = t.arange(0, 6)
a.storage()
b = a.view(2, 3)
id(b.storage()) == id(a.storage()) # True, 一个对象的id值可以看作它在内存中的地址 
# storage的内存地址一样,即是同一个storage
c = a[2:] 
c.data_ptr(), a.data_ptr() # data_ptr返回tensor首元素的内存地址
# (61277776, 61277760),可以看出相差8,因为2*4=8--相差两个元素,每个元素占4个字节(float)
c[0] = -100 # a-> tensor([0,1,-100,3,4,5]), c[0]的内存地址对应a[2]的内存地址
d = t.LongTensor(c.storage()) # 共享内存地址
a.storage_offset(), c.storage_offset(), d.storage_offset() # (0,3,0) 偏离量
e = b[::2, ::2] # 每2行每2列取一个元素 tensor([[0,2,4],[12,14,16]])
b.stride(), e.stride() # ((3, 1), (6, 2)) 步长
e.is_contiguous() # False,需调用tensor.contiguous方法变成连续的数据
其他

GPU/CPU

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

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

持久化

使用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, map_location为重定向参数
    c = t.load('a.pth', map_location=lambda storage, loc: storage)
# 加载为d, 存储于GPU0上
    d = t.load('a.pth', map_location={'cuda:1':'cuda:0'})

向量化

a = t.arange(0, 20000000)
print(a[-1], a[-2]) # 32bit的IntTensor精度有限导致溢出
b = t.LongTensor()
t.arange(0, 20000000, out=b) # 产生的结果将保存在out指定tensor中, 64bit的LongTensor不会溢出
t.set_printoptions(precision=10) # 设置打印的precision数值精度、threshold阈值、edgeitem维度等
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值