创建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.])
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的广播法则定义如下:
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)和存储区(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维度等