Tensor:张量,可以是标量(一个数)、向量(一维数组)、矩阵(二维数组)或者更高维的数组。
它与numpy和ndarrays相似,但是PyTorch的tensor支持GPU加速。
一、tensor分类
- 从接口的角度分类
torch.function | 如torch.save() |
tensor.function | 如tensor.view() |
- 从存储的角度分类
不会修改自身的数据 | 如a.add(b),加法的结果会返回一个新的tensor |
会修改自身的数据 | 如a.add_(b),加法的结果仍然存储在a中,a被修改了 |
二、创建Tensor
在PyTorch中新建tensor的方法有很多,具体如下:
函数 | 功能 |
Tensor(*sizes) | 基础构造函数 |
ones(*sizes) | 全1Tensor |
zeros(*sizes) | 全0Tensor |
eye(*sizes) | 对角线为1,其他为0 |
arange(s,e,step) | 从s到e,步长为step |
linspace(s,e,steps) | 从s到e,均匀切分成steps份 |
rand/randn(*sizes) | 均匀/标准分布 |
normal(mean,std)/uniform(from,to) | 正态分布/均匀分布 |
randperm(m) | 随机排列 |
三、常用Tensor操作
通过tensor.view方法调整tensor的形状,但必须保证调整前后元素总数一致。view不会修改自身的数据,
返回的新tensor与源tensor共享内存,即更改其中一个,另一个也随之改变。
主要用在经常需要添加或者减少某一维度,这时就需要squeeze和unsqueeze这两个函数。
resize是另一种可以用来调整size的方法,但与view不同,它可以修改tensor的尺寸。如果新尺寸超过了原尺寸,会自动分配新的内存空间,但如果新尺寸小于原尺寸,则之前的数据依旧会被保存。
Tensor支持numpy.ndarray类似的索引操作,语法也类似。如无特殊说明,索引出来的结果与原tensor共享内存。
from __future__ import print_function
import torch as t
a = t.randn(3, 4)
print(a)
print(a[0]) # 第0行(下标从0开始)
print(a[:, 0]) # 第0列
结果如下:
四、常用的选择函数
函数 | 功能 |
index_select(input,dim,index) | 在指定维度dim上选取,例如选取某些行或列 |
masked_select(input,mask) | 例a[a>0],使用ByteTensor进行选取 |
non_zero(input) | 非0元素的下标 |
gather(input,dim,index) | 根据index,在dim维度上选取数据,输出的size与index一样 |
五、常见的逐元素操作
函数 | 功能 |
abs//sqrt/div/exp/fmod/log/pow | 绝对值/平方根/除法/指数/求余/求幂 |
cos/sin/asin/atan2/cosh | 三角函数 |
clamp(input,min,max) | 超过min和max部分截断 |
ceil/round/floor/trunc | 上取整/四舍五入/下取整/只保留整数部分 |
sigmod/tanh | 激活函数 |
clamp常用在某些需要比较大小的地方,如取一个tensor的每个元素与另一个数的较大值。
a = t.arange(0, 6).view(2, 3)
print(a)
# 不转换为float,则会报错:RuntimeError: cos_vml_cpu not implemented for 'Long'
print(t.cos(a.float()))
a = t.arange(0, 6).view(2, 3)
print(a)
print(a % 3) # 等价于t.fmod(a, 3)
print(a ** 2) # 等价于t.pow(a, 2)
a = t.arange(0, 6).view(2, 3)
print(a)
# a中每个元素都与3相比,取较大的一个
print(t.clamp(a, min=3))
六、归并操作
归并操作会是输出形状小于输入形状,并可以沿着某一维度进行指定操作。
常用的归并操作:
函数 | 功能 |
mean/sum/median/mode | 均值/和/中位数/众数 |
norm/dist | 范数/距离 |
std/var | 标准差/方差 |
cumsum/cumprod | 累加/累乘 |
以上大多数函数都有一个参数dim,用来指定这些操作是在哪个维度上执行的。
假设输入的形状是(m, n, k)——m行n列:
输入dim | 输出的形状 |
0 | (1, n, k)或者(n, k)——沿着列,变成一行 |
1 | (m, 1, k)或者(m, k)——沿着行,变成一列 |
2 | (m, n, 1)或者(m, n) |
注意: size中是否有“1”,取决于参数keepdim,keepdim=True会保留维度1。默认为false
七、常用的比较函数
函数 | 功能 |
gt/lt/ge/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相比较的元素 |
八、常用的线性代数函数
函数 | 功能 |
trace | 对角线元素之和(矩阵的迹) |
diag | 对角线元素 |
triu/tril | 矩阵的上三角/下三角,可指定偏移量 |
mm/bmm | 矩阵乘法/batch的矩阵乘法 |
addmm/addbmm/addmv | 矩阵运算 |
t | 转置 |
dot/cross | 内积/外积 |
inverse | 求逆矩阵 |
svd | 奇异值分解 |
注意:矩阵的转置会导致存储空间不连续,需调用它的.contiguous方法将其转为连续
a = t.ones(2, 3)
print(a)
# 求a的转置
b = a.t()
# 查看a的转置矩阵b是否连续
print(b.is_contiguous())
# 将其转为连续
print(b.contiguous())
结果:
九、对于Tensor和Numpy之间遇到的问题
需要注意的是:Numpy和Tensor共享内存。PyTorch已经支持了自动广播法则。
当遇到Tensor不支持的操作时,可以先转成Numpy数组,处理后再转为tensor,其转换开销很小。
- 当输入数组的某个维度的长度为1时,计算时沿此维度复制扩充成一样的形状。
- unsqueeze或view:为数据某一维的形状补1,实现法则1.
- expand或expand_as,重复数组,实现法则3;该操作不会复制数组,所以不会占用额外的空间
9.1、持久化
tensor的保存:t.save
tensor的加载:t.load
在load时还可以将GPU tensor映射到CPU或其他GPU上。
if t.cuda.is_available():
a = a.cuda(1) # 把a转为GPU1上的tensor
t.save(a, 'a.path')
# 加载为b,存储于GPU1上(因为保存时tensor就在GPU1上)
b = t.load('a.path')
# 加载为c,存储于CPU
c = t.load('a.path', map_location=lambda storage, loc: storage)
# 加载为d,存储于GPU0上
d = t.load('a.path', map_location={'cuda:1':'cuda: 0'})
9.2、向量化
向量化计算是一种特殊的并行计算方式,一般程序在同一时间只执行一个操作的方式,它可以在同一时间执行多个操作,通常是对不同的数据执行同样的一个或一批指令,或者说把指令应用于一个数组/向量上。向量化可极大地提高科学运算的效率。
在科学计算程序中应当极力避免使用Python原生的for循环,尽量使用向量化的数值计算。
还需要注意:
- 大多数t.function都有一个参数out,这时产生的结果将保存在out指定的tensor之中
a = t.arange(0, 20000000)
b = t.LongTensor()
t.arange(0, 20000000, out=b) # 64bit的LongTensor不会溢出
- t.set_num_threads可以设置PyTorch进行CPU多线程并行计算时所占用的线程数,用来限制PyTorch所占用的CPU数目。
- t.set_printoptions可以用来设置打印tensor时的数值精度和格式。
十、View的用法
a=torch.Tensor([[[1,2,3],[4,5,6]]])
b=torch.Tensor([1,2,3,4,5,6])
print(a.view(1,6))
print(b.view(1,6))
输出结果:tensor([[1., 2., 3., 4., 5., 6.]])
tensor([[1., 2., 3., 4., 5., 6.]])
# -------------------------------------------------------
a=torch.Tensor([[[1,2,3],[4,5,6]]])
print(a.view(3,2))
输出结果:
tensor([[1., 2.],
[3., 4.],
[5., 6.]])
相当于就是从1,2,3,4,5,6顺序的拿数组来填充需要的形状
# --------------------------------------------------------
但是如果你想得到:
tensor([[1., 4.],
[2., 5.],
[3., 6.]])
就需要用到permute()
# -------------------------------------------------------
参数中的-1就代表这个位置由其他位置的数字来推断,只要在不致歧义的情况的下,view参数就可以推断出来,也就是人可以推断出形状的情况下,view函数也可以推断出来。
比如a tensor的数据个数是6个,如果view(1,-1),我们就可以根据tensor的元素个数推断出-1代表6。而如果是view(-1,-1,2),人不知道怎么推断,机器也不知道。
还有一种情况是人可以推断出来,但是机器推断不出来的:view(-1,-1,6),人可以知道-1都代表1,但是机器不允许同时有两个负1。
如果没有-1,那么所有参数的乘积就要和tensor中元素的总个数一致了,否则就会出现错误。
十一、sequeeze和unsqueeze
简介和用法
squeeze和unsqueeze的作用与其翻译基本一致,被作用维度压缩和解压缩.用法相对简单,具体如下:
tensor_unsqueeze = tensor.unsqueeze(dim)
若tensor存在n个维度,则dim的取值为[-n+1,n]区间的整数,且dim的取值不能为空.
tensor_squeeze = tensor.squeeze(dim)
若tensor存在n个维度,则dim的取值为[-n,n-1]区间的整数,但dim的值可以为空
具体示例:
首先看unsqueeze,其中的参数dim不能为空。
f = torch.arange(0, 6).view(3, 2)
print(f, f.size())
# 参数表示在哪一维的前面增加一个维度
print(f.unsqueeze(0), f.unsqueeze(0).size()) # 1×3×2
print(f.unsqueeze(1), f.unsqueeze(1).size()) # 3×1×2
print(f.unsqueeze(2), f.unsqueeze(2).size()) # 3×2×1
# --------------------------------------------------
tensor([[0, 1],
[2, 3],
[4, 5]]) torch.Size([3, 2])
tensor([[[0, 1],
[2, 3],
[4, 5]]]) torch.Size([1, 3, 2])
tensor([[[0, 1]],
[[2, 3]],
[[4, 5]]]) torch.Size([3, 1, 2])
tensor([[[0],
[1]],
[[2],
[3]],
[[4],
[5]]]) torch.Size([3, 2, 1])
再看squeeze,它是将被操作目标中维度为1的部分去除。
其中dim表示需要在哪一维去掉一个维度,如果不指定则自动寻找,如果指定则当指定的维度为1时去掉,如果不为1则不改变。
f = torch.arange(0, 6).view(3, 2, 1)
print(f, f.size()) # 3*2*1
print(f.squeeze(0), f.squeeze(0).size()) # 3*2*1,维度为3,不能去掉,所以不变
print(f.squeeze(1), f.squeeze(1).size()) # 3*2*1,维度为2,不能去掉,所有不变
print(f.squeeze(2), f.squeeze(2).size()) # 3*2 ,维度为1,可以去掉,所有改变
print(f.squeeze(), f.squeeze().size()) # 如果无参数,则自动寻找,找到则删除。
# -------------------------------------------------------------------------------
tensor([[[0],
[1]],
[[2],
[3]],
[[4],
[5]]]) torch.Size([3, 2, 1])
tensor([[[0],
[1]],
[[2],
[3]],
[[4],
[5]]]) torch.Size([3, 2, 1])
tensor([[[0],
[1]],
[[2],
[3]],
[[4],
[5]]]) torch.Size([3, 2, 1])
tensor([[0, 1],
[2, 3],
[4, 5]]) torch.Size([3, 2])
tensor([[0, 1],
[2, 3],
[4, 5]]) torch.Size([3, 2])
十二、cat的用法
cat是用于将两个矩阵进行拼接,0表示按行拼接,1表示按列拼接
a = torch.ones(2, 3)
b = 2 * torch.ones(4, 3)
c = 4 * torch.ones(2, 4)
print(a)
print(b)
print(c)
d = torch.cat((a, b), 0) # 0表示按行拼接
e = torch.cat((a, c), 1) # 1表示按列拼接
print(d)
print(e)
# -----------------------------------------------
tensor([[1., 1., 1.],
[1., 1., 1.]])
tensor([[2., 2., 2.],
[2., 2., 2.],
[2., 2., 2.],
[2., 2., 2.]])
tensor([[4., 4., 4., 4.],
[4., 4., 4., 4.]])
tensor([[1., 1., 1.],
[1., 1., 1.],
[2., 2., 2.],
[2., 2., 2.],
[2., 2., 2.],
[2., 2., 2.]])
tensor([[1., 1., 1., 4., 4., 4., 4.],
[1., 1., 1., 4., 4., 4., 4.]])