张量的操作主要包括张量的结构操作和张量的数学运算。
张量结构操作诸如:张量创建,索引切片,维度变换,合并分割。
张量数学运算主要有:标量运算,向量运算,矩阵运算。另外我们会介绍张量运算的广播机制。
本篇我们介绍张量的结构操作。
张量创建的许多方法和numpy中创建array的方法很像。
import numpy as np
import torch
a = torch.tensor([1,2,3],dtype = torch.float)
print(a)
tensor([1., 2., 3.])
b = torch.arange(1,10,step = 2)
print(b)
tensor([1, 3, 5, 7, 9])
c = torch.linspace(0.0,2*3.14,10)
print(c)
tensor([0.0000, 0.6978, 1.3956, 2.0933, 2.7911, 3.4889, 4.1867, 4.8844, 5.5822,
6.2800])
d = torch.zeros((3,3))
print(d)
tensor([[0., 0., 0.],
[0., 0., 0.],
[0., 0., 0.]])
a = torch.ones((3,3),dtype = torch.int)
b = torch.zeros_like(a,dtype = torch.float)
print(a)
print(b)
tensor([[1, 1, 1],
[1, 1, 1],
[1, 1, 1]], dtype=torch.int32)
tensor([[0., 0., 0.],
[0., 0., 0.],
[0., 0., 0.]])
torch.fill_(b,5)
print(b)
tensor([[5., 5., 5.],
[5., 5., 5.],
[5., 5., 5.]])
#均匀随机分布
torch.manual_seed(0)
minval,maxval = 0,10
a = minval + (maxval-minval)*torch.rand([5])
print(a)
tensor([4.9626, 7.6822, 0.8848, 1.3203, 3.0742])
#正态分布随机
b = torch.normal(mean = torch.zeros(3,3), std = torch.ones(3,3))
print(b)
tensor([[-1.3836, 0.2459, -0.1312],
[-0.1785, -0.5959, 0.2739],
[ 0.5679, -0.6731, -1.2095]])
#正态分布随机
mean,std = 2,5
c = std*torch.randn((3,3))+mean
print(c)
tensor([[ 8.7204, 13.9161, -0.8323],
[ -3.7681, -10.5115, 6.3778],
[-11.3628, 1.8433, 4.4939]])
#整数随机排列
d = torch.randperm(20)
print(d)
tensor([ 5, 15, 19, 10, 7, 17, 0, 4, 12, 16, 14, 13, 1, 3, 9, 6, 18, 2,
8, 11])
#特殊矩阵
I = torch.eye(3,3) #单位矩阵
print(I)
t = torch.diag(torch.tensor([1,2,3])) #对角矩阵
print(t)
tensor([[1., 0., 0.],
[0., 1., 0.],
[0., 0., 1.]])
tensor([[1, 0, 0],
[0, 2, 0],
[0, 0, 3]])
张量的索引切片方式和numpy几乎是一样的。切片时支持缺省参数和省略号。
可以通过索引和切片对部分元素进行修改。
此外,对于不规则的切片提取,可以使用torch.index_select, torch.masked_select, torch.take
如果要通过修改张量的某些元素得到新的张量,可以使用torch.where,torch.masked_fill,torch.index_fill
#均匀随机分布
torch.manual_seed(0)
minval,maxval = 0,10
t = torch.floor(minval + (maxval-minval)*torch.rand([5,5])).int()
print(t)
tensor([[4, 7, 0, 1, 3],
[6, 4, 8, 4, 6],
[3, 4, 0, 1, 2],
[5, 6, 8, 1, 2],
[6, 9, 3, 8, 4]], dtype=torch.int32)
#第0行
print(t[0])
tensor([4, 7, 0, 1, 3], dtype=torch.int32)
#倒数第一行
print(t[-1])
tensor([6, 9, 3, 8, 4], dtype=torch.int32)
#第1行第3列
print(t[1,3])
print(t[1][3])
tensor(4, dtype=torch.int32)
tensor(4, dtype=torch.int32)
#第1行至第3行
print(t[1:4,:])
tensor([[6, 4, 8, 4, 6],
[3, 4, 0, 1, 2],
[5, 6, 8, 1, 2]], dtype=torch.int32)
#第1行至最后一行,第0列到最后一列每隔两列取一列
print(t[1:4,:4:2])
tensor([[6, 8],
[3, 0],
[5, 8]], dtype=torch.int32)
#可以使用索引和切片修改部分元素
x = torch.tensor([[1,2],[3,4]],dtype = torch.float32,requires_grad=True)
x.data[1,:] = torch.tensor([0.0,0.0])
x
tensor([[1., 2.],
[0., 0.]], requires_grad=True)
a = torch.arange(27).view(3,3,3)
print(a)
tensor([[[ 0, 1, 2],
[ 3, 4, 5],
[ 6, 7, 8]],
[[ 9, 10, 11],
[12, 13, 14],
[15, 16, 17]],
[[18, 19, 20],
[21, 22, 23],
[24, 25, 26]]])
#省略号可以表示多个冒号
print(a[...,1])
tensor([[ 1, 4, 7],
[10, 13, 16],
[19, 22, 25]])
以上切片方式相对规则,对于不规则的切片提取,可以使用torch.index_select, torch.take, torch.gather, torch.masked_select.
考虑班级成绩册的例子,有4个班级,每个班级10个学生,每个学生7门科目成绩。可以用一个4×10×7的张量来表示。
minval=0
maxval=100
scores = torch.floor(minval + (maxval-minval)*torch.rand([4,10,7])).int()
print(scores)
tensor([[[55, 95, 3, 18, 37, 30, 93],
[17, 26, 15, 3, 20, 92, 72],
[74, 52, 24, 58, 3, 13, 24],
[81, 79, 27, 48, 81, 99, 69],
[56, 83, 20, 59, 11, 15, 24],
[72, 70, 20, 65, 77, 43, 51],
[61, 81, 98, 11, 31, 69, 91],
[93, 94, 59, 6, 54, 18, 3],
[94, 88, 0, 59, 41, 41, 27],
[69, 20, 68, 75, 85, 68, 0]],
[[17, 74, 60, 10, 21, 97, 83],
[28, 37, 2, 49, 12, 11, 47],
[57, 29, 79, 19, 95, 84, 7],
[37, 52, 57, 61, 69, 52, 25],
[73, 2, 20, 37, 25, 32, 9],
[39, 60, 17, 47, 85, 44, 51],
[45, 60, 81, 97, 81, 97, 46],
[ 5, 26, 84, 49, 25, 11, 3],
[ 7, 39, 77, 77, 1, 81, 10],
[39, 29, 40, 40, 5, 6, 42]],
[[50, 27, 68, 4, 46, 93, 29],
[95, 68, 4, 81, 44, 27, 89],
[ 9, 55, 39, 85, 63, 74, 67],
[37, 39, 8, 77, 89, 84, 14],
[52, 14, 22, 20, 67, 20, 48],
[52, 82, 12, 15, 20, 84, 32],
[92, 68, 56, 49, 40, 56, 38],
[49, 56, 10, 23, 90, 9, 46],
[99, 68, 51, 6, 74, 14, 35],
[33, 42, 50, 91, 56, 94, 80]],
[[18, 72, 14, 28, 64, 66, 87],
[33, 50, 75, 1, 86, 8, 50],
[41, 23, 56, 91, 35, 20, 31],
[ 0, 72, 25, 16, 21, 78, 76],
[88, 68, 33, 36, 64, 91, 63],
[26, 26, 2, 60, 21, 5, 93],
[17, 44, 64, 51, 16, 9, 89],
[58, 91, 33, 64, 38, 47, 19],
[66, 65, 48, 38, 19, 84, 12],
[70, 33, 25, 58, 24, 61, 59]]], dtype=torch.int32)
#抽取每个班级第0个学生,第5个学生,第9个学生的全部成绩
torch.index_select(scores,dim = 1,index = torch.tensor([0,5,9]))
tensor([[[55, 95, 3, 18, 37, 30, 93],
[72, 70, 20, 65, 77, 43, 51],
[69, 20, 68, 75, 85, 68, 0]],
[[17, 74, 60, 10, 21, 97, 83],
[39, 60, 17, 47, 85, 44, 51],
[39, 29, 40, 40, 5, 6, 42]],
[[50, 27, 68, 4, 46, 93, 29],
[52, 82, 12, 15, 20, 84, 32],
[33, 42, 50, 91, 56, 94, 80]],
[[18, 72, 14, 28, 64, 66, 87],
[26, 26, 2, 60, 21, 5, 93],
[70, 33, 25, 58, 24, 61, 59]]], dtype=torch.int32)
#抽取每个班级第0个学生,第5个学生,第9个学生的第1门课程,第3门课程,第6门课程成绩
q = torch.index_select(torch.index_select(scores,dim = 1,index = torch.tensor([0,5,9]))
,dim=2,index = torch.tensor([1,3,6]))
print(q)
tensor([[[95, 18, 93],
[70, 65, 51],
[20, 75, 0]],
[[74, 10, 83],
[60, 47, 51],
[29, 40, 42]],
[[27, 4, 29],
[82, 15, 32],
[42, 91, 80]],
[[72, 28, 87],
[26, 60, 93],
[33, 58, 59]]], dtype=torch.int32)
#抽取第0个班级第0个学生的第0门课程,第2个班级的第4个学生的第1门课程,第3个班级的第9个学生第6门课程成绩
#take将输入看成一维数组,输出和index同形状
s = torch.take(scores,torch.tensor([0*10*7+0,2*10*7+4*7+1,3*10*7+9*7+6]))
s
<tf.Tensor: shape=(3, 7), dtype=int32, numpy=
array([[52, 82, 66, 55, 17, 86, 14],
[99, 94, 46, 70, 1, 63, 41],
[46, 83, 70, 80, 90, 85, 17]], dtype=int32)>
#抽取分数大于等于80分的分数(布尔索引)
#结果是1维张量
g = torch.masked_select(scores,scores>=80)
print(g)
以上这些方法仅能提取张量的部分元素值,但不能更改张量的部分元素值得到新的张量。
如果要通过修改张量的部分元素值得到新的张量,可以使用torch.where,torch.index_fill 和 torch.masked_fill
torch.where可以理解为if的张量版本。
torch.index_fill的选取元素逻辑和torch.index_select相同。
torch.masked_fill的选取元素逻辑和torch.masked_select相同。
#如果分数大于60分,赋值成1,否则赋值成0
ifpass = torch.where(scores>60,torch.tensor(1),torch.tensor(0))
print(ifpass)
#将每个班级第0个学生,第5个学生,第9个学生的全部成绩赋值成满分
torch.index_fill(scores,dim = 1,index = torch.tensor([0,5,9]),value = 100)
#等价于 scores.index_fill(dim = 1,index = torch.tensor([0,5,9]),value = 100)
#将分数小于60分的分数赋值成60分
b = torch.masked_fill(scores,scores<60,60)
#等价于b = scores.masked_fill(scores<60,60)
b
维度变换相关函数主要有 torch.reshape(或者调用张量的view方法), torch.squeeze, torch.unsqueeze, torch.transpose
torch.reshape 可以改变张量的形状。
torch.squeeze 可以减少维度。
torch.unsqueeze 可以增加维度。
torch.transpose 可以交换维度。
# 张量的view方法有时候会调用失败,可以使用reshape方法。
torch.manual_seed(0)
minval,maxval = 0,255
a = (minval + (maxval-minval)*torch.rand([1,3,3,2])).int()
print(a.shape)
print(a)
torch.Size([1, 3, 3, 2])
tensor([[[[126, 195],
[ 22, 33],
[ 78, 161]],
[[124, 228],
[116, 161],
[ 88, 102]],
[[ 5, 43],
[ 74, 132],
[177, 204]]]], dtype=torch.int32)
# 改成 (3,6)形状的张量
b = a.view([3,6]) #torch.reshape(a,[3,6])
print(b.shape)
print(b)
torch.Size([3, 6])
tensor([[126, 195, 22, 33, 78, 161],
[124, 228, 116, 161, 88, 102],
[ 5, 43, 74, 132, 177, 204]], dtype=torch.int32)
# 改回成 [1,3,3,2] 形状的张量
c = torch.reshape(b,[1,3,3,2]) # b.view([1,3,3,2])
print(c)
tensor([[[[126, 195],
[ 22, 33],
[ 78, 161]],
[[124, 228],
[116, 161],
[ 88, 102]],
[[ 5, 43],
[ 74, 132],
[177, 204]]]], dtype=torch.int32)
如果张量在某个维度上只有一个元素,利用torch.squeeze可以消除这个维度。
torch.unsqueeze的作用和torch.squeeze的作用相反。
a = torch.tensor([[1.0,2.0]])
s = torch.squeeze(a)
print(a)
print(s)
print(a.shape)
print(s.shape)
tensor([[1., 2.]])
tensor([1., 2.])
torch.Size([1, 2])
torch.Size([2])
#在第0维插入长度为1的一个维度
d = torch.unsqueeze(s,axis=0)
print(s)
print(d)
print(s.shape)
print(d.shape)
tensor([1., 2.])
tensor([[1., 2.]])
torch.Size([2])
torch.Size([1, 2])
torch.transpose可以交换张量的维度,torch.transpose常用于图片存储格式的变换上。
如果是二维的矩阵,通常会调用矩阵的转置方法 matrix.t(),等价于 torch.transpose(matrix,0,1)。
minval=0
maxval=255
# Batch,Height,Width,Channel
data = torch.floor(minval + (maxval-minval)*torch.rand([100,256,256,4])).int()
print(data.shape)
# 转换成 Pytorch默认的图片格式 Batch,Channel,Height,Width
# 需要交换两次
data_t = torch.transpose(torch.transpose(data,1,2),1,3)
print(data_t.shape)
torch.Size([100, 256, 256, 4])
torch.Size([100, 4, 256, 256])
matrix = torch.tensor([[1,2,3],[4,5,6]])
print(matrix)
print(matrix.t()) #等价于torch.transpose(matrix,0,1)
tensor([[1, 2, 3],
[4, 5, 6]])
tensor([[1, 4],
[2, 5],
[3, 6]])
可以用torch.cat方法和torch.stack方法将多个张量合并,可以用torch.split方法把一个张量分割成多个张量。
torch.cat和torch.stack有略微的区别,torch.cat是连接,不会增加维度,而torch.stack是堆叠,会增加维度。
a = torch.tensor([[1.0,2.0],[3.0,4.0]])
b = torch.tensor([[5.0,6.0],[7.0,8.0]])
c = torch.tensor([[9.0,10.0],[11.0,12.0]])
abc_cat = torch.cat([a,b,c],dim = 0)
print(abc_cat.shape)
print(abc_cat)
torch.Size([6, 2])
tensor([[ 1., 2.],
[ 3., 4.],
[ 5., 6.],
[ 7., 8.],
[ 9., 10.],
[11., 12.]])
abc_stack = torch.stack([a,b,c],axis = 0) #torch中dim和axis参数名可以混用
print(abc_stack.shape)
print(abc_stack)
torch.Size([3, 2, 2])
tensor([[[ 1., 2.],
[ 3., 4.]],
[[ 5., 6.],
[ 7., 8.]],
[[ 9., 10.],
[11., 12.]]])
torch.cat([a,b,c],axis = 1)
tensor([[ 1., 2., 5., 6., 9., 10.],
[ 3., 4., 7., 8., 11., 12.]])
torch.stack([a,b,c],axis = 1)
tensor([[[ 1., 2.],
[ 5., 6.],
[ 9., 10.]],
[[ 3., 4.],
[ 7., 8.],
[11., 12.]]])
torch.split是torch.cat的逆运算,可以指定分割份数平均分割,也可以通过指定每份的记录数量进行分割。
print(abc_cat)
a,b,c = torch.split(abc_cat,split_size_or_sections = 2,dim = 0) #每份2个进行分割
print(a)
print(b)
print(c)
print(abc_cat)
p,q,r = torch.split(abc_cat,split_size_or_sections =[4,1,1],dim = 0) #每份分别为[4,1,1]
print(p)
print(q)
print(r)
tensor([[ 1., 2.],
[ 3., 4.],
[ 5., 6.],
[ 7., 8.],
[ 9., 10.],
[11., 12.]])
tensor([[1., 2.],
[3., 4.],
[5., 6.],
[7., 8.]])
tensor([[ 9., 10.]])
tensor([[11., 12.]])
如果对本书内容理解上有需要进一步和作者交流的地方,欢迎在公众号"Python与算法之美"下留言。作者时间和精力有限,会酌情予以回复。
也可以在公众号后台回复关键字:加群,加入读者交流群和大家讨论。