0401-Tensor的基础操作
0401-Tensor的基础操作
pytorch完整教程目录:https://www.cnblogs.com/nickchen121/p/14662511.html
一、tensor引入
tensor,也可以叫做张量,学过线性代数的你,其实早就接触了张量,只不过我们一直把它叫做向量和矩阵,而向量就是一维张量、矩阵是二维张量,只不过张量还可以是三维的、四维的,只是维数高了之后,我们难以理解,因此统一把它都叫做张量。
在torch中,张量是一个数据类型,也就是tensor,它和numpy中的ndarray这个数据类型很像,以及和它的操作方法也很类似,其实你可以发现,ndarray不就是一维和二维张量吗?
如果你看过我的Python博客,可以发现我把python的所有的基础类型和其对应的操作方法都讲到了,那是因为任何框架的基础都是python,python的所有操作方法都学习全面了,你自己也可以造框架。而对于框架的各种操作方法,底层无非就是一堆python代码的堆叠,也就是有些操作方法你不学,你也可以自己造出来,所以对于torch的很多不常用的内容我们可能会一笔概之或者直接不讲。
而对于tensor的基础操作,我们可以从两个方面来讲。
如果从接口的角度,对tensor的操作可以分为两类:
torch.function
,如torch.save
tensor.function
,如tensor.view
注:对于这两种接口方法,大多数时候都是等价的,如torch.sum(a,b)
和a.sum(b)
如果从存储的角度讲,对tensor的操作也可以分为两类:
a.add(b)
,不会修改a自身的数据,加法的结果会返回一个新的tensora.add_(b)
,会修改a自身的数据,也就是说加法的结果存在a中
注:函数名以_结尾的都是修改调用者自身的数据。
二、创建tensor
此处我只列出表格,不给出详细介绍和代码打印结果,只给出一些细节上需要注意的东西,因为它除了支持多维,其他和numpy简直一模一样
函数 | 功能 |
---|---|
Tensor(*size) |
基础构造函数 |
ones(*sizes) |
全1Tensor |
zeros(*sizes) |
全0Tensor |
eye(*sizes) |
对角矩阵(对角线为1,其他为0,不要求行列一致) |
arrange(s,e,step) |
从s到e,步长为step |
linspace(s,e,steps) |
从s到e,均匀分成steps份 |
rand/randn(*sizes) |
均匀/标准分布 |
normal(mean,std)/uniform(from,tor) |
正态分布/均匀分布 |
randperm(m) |
随机排列 |
import torch as t
如果*size
为列表,则按照列表的形状生成张量,否则传入的参数看作是张量的形状
a = t.Tensor(2, 3) # 指定形状构建2*3维的张量
a
tensor([[ 0.0000e+00, -2.5244e-29, 0.0000e+00],
[-2.5244e-29, 6.7294e+22, 1.8037e+28]])
b = t.tensor([[1, 2, 3], [2, 3, 4]]) # 通过传入列表构建2*3维的张量
b
tensor([[1, 2, 3],
[2, 3, 4]])
b.tolist() # 把b转化为列表,但是b的实际数据类型仍是tensor
[[1, 2, 3], [2, 3, 4]]
print(f'type(b): {type(b)}')
type(b): <class 'torch.Tensor'>
b.size() # 返回b的大小,等价于b.shape()
torch.Size([2, 3])
b.numel() # 计算b中的元素个数,等价于b.nelement()
6
c = t.Tensor(b.size()) # 创建一个和b一样形状的张量
c
tensor([[0.0000e+00, 3.6013e-43, 1.8754e+28],
[2.0592e+23, 1.3003e+22, 1.0072e-11]])
注:t.Tensor(*size)创建tensor时,系统不会马上分配空间,只有使用到tensor时才会分配内存,而其他操作都是在创建tensor后马上进行空间分配
三、常用tensor操作
3.1 调整tensor的形状
view()
方法调整tensor的形状,但是必须得保证调整前后元素个数一致,但是view方法不会修改原tensor的形状和数据
a = t.arange(0, 6)
a
tensor([0, 1, 2, 3, 4, 5])
b = a.view(2, 3)
print(f'a: {a}\n\n b:{b}')
a: tensor([0, 1, 2, 3, 4, 5])
b:tensor([[0, 1, 2],
[3, 4, 5]])
c = a.view(-1, 3) # -1会自动计算大小。注:我已经知道你在想什么了,两个-1你就上天吧,鬼知道你想改成什么形状的
print(f'a: {a}\n\n b:{c}')
a: tensor([0, 1, 2, 3, 4, 5])
b:tensor([[0, 1, 2],
[3, 4, 5]])
a[1] = 0 # view方法返回的tensor和原tensor共享内存,修改一个,另外一个也会修改
print(f'a: {a}\n\n b:{b}')
a: tensor([0, 0, 2, 3, 4, 5])
b:tensor([[0, 0, 2],
[3, 4, 5]])
resize()
是另一种用来调整size的方法,但是它相比较view,可以修改tensor的尺寸,如果尺寸超过了原尺寸,则会自动分配新的内存,反之,则会保留老数据
b.resize_(1, 3)
tensor([[0, 0, 2]])
b.resize_(3, 3)
tensor([[0, 0, 2],
[3, 4, 5],
[0, 0, 0]])
b.resize_(2, 3)
tensor([[0, 0, 2],
[3, 4, 5]])
3.2 添加或压缩tensor维度
unsqueeze()
可以增加tensor的维度;squeeze()
可以压缩tensor的维度
# 过于抽象,无法理解就跳过。
d = b.unsqueeze(
1) # 在第1维上增加“1”,也就是2*3的形状变成2*1*3。如果是b.unsqueeze(0)就是在第0维上增加1,形状变成1*2*3。
d, d.size()
(tensor([[[0, 0, 2]],
[[3, 4, 5]]]), torch.Size([2, 1, 3]))
b.unsqueeze(-1) # 在倒数第1维上增加“1”,也就是2*3的形状变成2*3*1。
tensor([[[0],
[0],
[2]],
[[3],
[4],
[5]]])
e = b.view(1, 1, 2, 1, 3)
f = e.squeeze(0) # 压缩第0维的“1”,某一维度为“1”才能压缩,如果第0维的维度是“2”如(2,1,1,1,3)则无法亚索第0维
f, f.size()
(tensor([[[[0, 0, 2]],
[[3, 4, 5]]]]), torch.Size([1, 2, 1, 3]))
e.squeeze() # 把所有维度为“1”的压缩。
tensor([[0, 0, 2],
[3, 4, 5]])
四、索引操作
tensor的索引操作和ndarray的索引操作类似,并且索引出来的结果与原tensor共享内存。因此在这里普通的切片操作我们就不多介绍,我们只讲解tensor一些特有的选择函数。
函数 | 功能 |
---|---|
index_select(input,dim,index) |
在指定维度dim上选取,例如选取某些行、某些列 |
masked_select(inpu,mask) |
a[a>1] 等价于a.masked_select(a>1) |
non_zero(input) |
获取非0元素的下标 |
gather(input,dim,index) |
根据index,在dim维度上选取数据,输出的size与index一样 |
import torch as t
对于上述选择函数,我们讲解一下比较难的gather函数,对于一个二维tensor,gather的输出如下所示:
out[i][j] = input[index[i][j]][j] # dim=0
out[i][j] = input[i][index[i][j]] # dim=1
a = t.arange(0, 16).view(4, 4)
a
tensor([[ 0, 1, 2, 3],
[ 4, 5, 6, 7],
[ 8, 9, 10, 11],
[12, 13, 14, 15]])
# 选取对角线的元素
index = t.LongTensor([[0, 1, 2, 3]])
print(f'index: {index}')
a.gather(0, index) # dim=0
index: tensor([[0, 1, 2, 3]])
tensor([[ 0, 5, 10, 15]])
对于上述实例,可以做出如下解释:
i=0,j=0 -> index[0,0]=0 -> input[index[0,0]][0]=input[0][0] = 0
i=0,j=1 -> index[0,1]=1 -> input[index[0,1]][1]=input[1][1] = 5
i=0,j=2 -> index[0,2]=2 -> input[index[0,2]][2]=input[2][2] = 10
i=0,j=3 -> index[0,3]=3 -> input[index[0,3]][3]=input[3][3] = 15
下述实例,自行判断。
# 选取反对角线上的元素
index = t.LongTensor([[3, 2, 1, 0]]).t() # .t()是转置
print(f'index: {index}')
a.gather(1, index)
index: tensor([[3],
[2],
[1],
[0]])
tensor([[ 3],
[ 6],
[ 9],
[12]])
# 选取反对角线上的元素
index = t.LongTensor([[3, 2, 1, 0]]) # .t()是转置
a.gather(0, index)
tensor([[12, 9, 6, 3]])
# 选取两个对角线上的元素
index = t.LongTensor([[0, 1, 2, 3], [3, 2, 1, 0]]).t() # .t()是转置
print(f'index: {index}')
b = a.gather(1, index)
b
index: tensor([[0, 3],
[1, 2],
[2, 1],
[3, 0]])
tensor([[ 0, 3],
[ 5, 6],
[10, 9],
[15, 12]])
与gather函数相应的逆操作则是scatter_,scatter_可以把gather取出的元素放回去。
out = input.gather(dim, index)
out = Tensor()
out.scatter_(dim, index)
# 把两个对角线元素放回到指定位置里
c = t.zeros(4, 4, dtype=t.int64)
c.scatter_(1, index, b)
tensor([[ 0, 0, 0, 3],
[ 0, 5, 6, 0],
[ 0, 9, 10, 0],
[12, 0, 0, 15]])
五、高级索引
torch的高级索引和numpy的高级索引也很类似,因此照例,只讲一些复杂的高级索引方法。
注:高级索引操作的结果和原tensor不共享内存
x = t.arange(0, 27).view(3, 3, 3)
x
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]]])
x[[1, 2], [1, 2], [2, 0]] # x[1,1,2] 和 x[2,2,0]
tensor([14, 24])
x[[2, 1, 0], [0], [1]] # x[2,0,1],x[1,0,1],x[0,0,1]
tensor([19, 10, 1])
x[[0, 2], ...] # x[0] 和 x[2]
tensor([[[ 0, 1, 2],
[ 3, 4, 5],
[ 6, 7, 8]],
[[18, 19, 20],
[21, 22, 23],
[24, 25, 26]]])
可以从上述三个例子看出高级索引的本质就是先循环第一个列表中的元素,然后与后面列表的元素配对,配对满足维数要求则停止,否则继续往后搜索。
对于第一个例子:
- 先从
[1,2]
中取出1 - 1和第二个列表
[1,2]
配对,满足三维要求,即[1,1,2]
,停止配对,循环步骤一取出2 - 2和第三个列表
[2,0]
配对,满足三维要求,即[2,2,0]
,停止配对
对于第二例子:
- 先从
[2,1,0]
中取出2 - 2和第二个列表
[0]
配对,不满足三维要求,继续往后搜索,和第三个列表[1]
配对,满足三维要求,即[2,0,1]
- ……
六、Tensor类型
6.1 Tensor数据类型
数据类型 | CPU tensor | GPU tensor |
---|---|---|
32bit浮点 | torch.FloatTensor | torch.cuda.FloatTensor |
64bit浮点 | torch.DoubleTensor | torch.cuda.DoubleTensor |
16bit半精度浮点 | torch.HalfTensor | torch.cuda.HalfTensor |
8bit无符号整型(0~255) | torch.ByteTensor | torch.cuda.ByteTensor |
8bit有符号整型(-128~127) | torch.CharTensor | torch.cuda.CharTensor |
16bit有符号整型 | torch.ShortTensor | torch.cuda.ShortTensor |
32bit有符号整型 | torch.IntTensor | torch.cuda.IntTensor |
64bit有符号整型 | torch.LongTensor | torch.cuda.LongTensor |
上表中只有HalfTensor值得一提,它是gpu独有的数据类型,使用该数据类型,gpu在存储该类型数据时,内存占用会减少一半,可以解决gpu显存不足的问题,但是由于它所能表示的数值大小和精度有限,所以可能存在溢出问题。
6.2 数据类型转换
# 设置默认tensor,系统默认tensor是FloatTensor,也仅支持浮点数类型为默认数据类型,设置成IntTensor会报错
t.set_default_tensor_type('torch.DoubleTensor')
a = t.Tensor(2, 3)
a, a.type() # a现在是DoubleTensor
(tensor([[0., 0., 0.],
[0., 0., 0.]]), 'torch.DoubleTensor')
b = a.int() # 可通过`float(), int(), double(), char(), long(), int()`更换数据类型
b.type()
'torch.IntTensor'
c = a.type_as(b) # 对a进行数据类型转换
c, c.type()
(tensor([[0, 0, 0],
[0, 0, 0]], dtype=torch.int32), 'torch.IntTensor')
d = a.new(2, 3) # 生成与a数据类型一致的tensor
d, d.type()
(tensor([[ 2.0000e+00, 2.0000e+00, 3.9525e-323],
[ 0.0000e+00, 0.0000e+00, 0.0000e+00]]), 'torch.DoubleTensor')
a.new?? # 查看new的源码
t.set_default_tensor_type('torch.FloatTensor') # 恢复之前的默认设置
6.3 cpu和gpu间数据类型转换
cpu和gpu的数据类型通常有tensor.cpu()
和tensor.gpu()
互相转换,由于我的电脑没有gpu,从网上摘抄一段供大家参考:
In [115]: a = t.ones(2,3)
In [116]: a.type()
Out[116]: 'torch.FloatTensor'
In [117]: a
Out[117]:
tensor([[1., 1., 1.],
[1., 1., 1.]])
In [118]: a.cuda()
Out[118]:
tensor([[1., 1., 1.],
[1., 1., 1.]], device='cuda:0')
In [119]: b = a.cuda()
In [120]: b
Out[120]:
tensor([[1., 1., 1.],
[1., 1., 1.]], device='cuda:0')
In [121]: b.type()
Out[121]: 'torch.cuda.FloatTensor'
In [122]: b.cpu()
Out[122]:
tensor([[1., 1., 1.],
[1., 1., 1.]])
In [123]: b.cpu().type()
Out[123]: 'torch.FloatTensor'
七、逐元素操作
通俗点讲,就是对tensor进行数学操作,只不过是对tensor的每个元素都进行相对应的操作,因此叫做逐元素操作,也因此该类操作的输出形状与原tensor形状一致。常见的逐元素操作如下表:
函数 | 功能 |
---|---|
mul/abs/sqrt/exp/fmod/log/pow…… | 乘法(*)/绝对值/平方根/除法(/)/指数/求余(%)/求幂(**) |
cos/sin/asin/atan2/cosh | 三角函数 |
ceil/round/floor/trunc | 上取整/四舍五入/下去整/只保留整数部分 |
clamp(input,min,max) |
超过min和max部分截断 |
sigmod/tanh/... | 激活函数 |
针对上述一些运算符,torch实现了运算符重载,例如a**2
等价于torch.pow(a,2)
。
针对clamp函数,它的输出满足下述公式:
a = t.arange(0, 6).view(2, 3)
a
tensor([[0, 1, 2],
[3, 4, 5]])
a.clamp(min=3)
tensor([[3, 3, 3],
[3, 4, 5]])
八、归并操作
该类操作可以沿着某一维度进行指定操作,因此它们的输出形状一般小于元tensor形状。如加法sum,可以计算正整个tensor的和,也可以计算某一行或某一列的和。常用的归并操作如下表所示:
函数 | 功能 |
---|---|
mean/sum/median/mode | 均值/和/中位数/众数 |
norm/dist | 范数/距离 |
std/var | 标准差/方差 |
cunsum/cumprod | 累加/累乘 |
cat(tensor1,tensor2,dim) |
让tensor1按照第dim个维度与tensor2进行连接 |
以上函数大多都有一个dim参数(对应numpy中的axis参数),它的使用如下所示(假设输入的形状是(a,b,c)):
- 如果指定dim=0,输出形状是(1,b,c)或(b,c)
- 如果指定dim=1,输出形状是(a,1,c)或(a,c)
- 如果指定dim=2,输出形状是(a,b,1)或(a,b)
对于上述操作是否保留输出形状中的“1”,取决于参数keepdim,如果keepdim=True
则保留,反之不保留。但是从torch0.2.0版本开始,统一不保留。虽然以上总结适用于大多数函数,但是对于cumsum函数,则不适用该规则。
b = t.ones(2, 3)
b.sum(dim=0), b.sum(dim=0, keepdim=True) # 前者输出形状是(3),后者输出形状是(1,3)
(tensor([2., 2., 2.]), tensor([[2., 2., 2.]]))
a = t.arange(0, 6).view(2, 3)
a
tensor([[0, 1, 2],
[3, 4, 5]])
a.cumsum(dim=1) # 对第二个维度行的元素按照索引顺序进行累加
tensor([[ 0, 1, 3],
[ 3, 7, 12]])
九、比较
对于比较函数中,有些函数逐元素操作,有些函数则类似于归并不逐元素操作。常用的比较函数有:
函数 | 功能 |
---|---|
gt/lt/le/eq/ne | 大于(>)/小于(<)/大于等于(>=)/小于等于(<=)/等于)(=)/不等(!=) |
topk | 最大的k个数 |
sort | 排序 |
max/min | 比较两个tensor的最大值和最小值 |
其中max和min两个函数有点特殊,它们有以下三种情况:
t.max(tensor)
:返回tensor中最大的一个数t.max(tensor,dim)
:指定维上最带的数,返回tensor和下标t.max(tensor1,tensor2)
:比较两个tensor相比较大的元素
a = t.linspace(0, 15, 6).view(2, 3)
a
tensor([[ 0., 3., 6.],
[ 9., 12., 15.]])
b = t.linspace(15, 0, 6).view(2, 3)
b
tensor([[15., 12., 9.],
[ 6., 3., 0.]])
t.max(a)
tensor(15.)
t.max(a, 1) # 返回第0行和第1行的最大的元素
torch.return_types.max(
values=tensor([ 6., 15.]),
indices=tensor([2, 2]))
t.max(a, b)
tensor([[15., 12., 9.],
[ 9., 12., 15.]])
十、线性代数运算
常用的线性代数函数如下表所示:
函数 | 功能 |
---|---|
trace | 对角线元素之和(矩阵的迹) |
diag | 对角线元素 |
triu/tril | 矩阵的上三角/下三角,可指定偏移量 |
mv/mm/bmm | 矩阵和向量的乘法/矩阵的乘法/两个batch内的矩阵进行批矩阵乘法 |
addmm | 两个矩阵进行乘法操作的结果与另一向量相加 |
addmv | 将矩阵和向量的结果与另一向量相加 |
addbmm | 将两个batch内的矩阵进行批矩阵乘法操作并累加,其结果与另一矩阵相加 |
baddbmm | 将两个batch内的矩阵进行批矩阵乘法操作,其结果与另一batch内的矩阵相加 |
eig | 计算方阵的特征值和特征向量 |
t | 转置 |
dot/cross | 内积/外积 |
inverse | 求逆矩阵 |
svd | 奇异值分解 |
其中矩阵的转置会导致存储空间不连续,需调用它的.contiguous方法让它连续
b = a.t()
b, b.is_contiguous()
(tensor([[ 0., 9.],
[ 3., 12.],
[ 6., 15.]]), False)
b = b.contiguous()
b, b.is_contiguous()
(tensor([[ 0., 9.],
[ 3., 12.],
[ 6., 15.]]), True)