PyTorch学习之路(五)Tensor操作及其常用函数

这篇文章是个速查表,需要使用的功能直接点击目录到相应的用法。

目录

  • 创建tensor
    • 新建tensor的方法表格
    • t.tensor和t.Tensor的区别
  • 基本操作
    • 查看tensor 大小: t.size(), t.shape()
    • tensor转list: t.tolist()
    • 计算tensor中元素总个数: t.numel()
    • 调整形状:
    • 增减维度 t.squeeze(), t.unsqueeze()?
    • 索引操作
    • Tensor类型
  • 数学运算
    • in place操作(是否修改自身数据)
    • 逐元素操作
    • 归并操作
    • 比较
    • 线性代数
  • GPU/CPU

创建tensor

import torch as t

新建tensor的方法表格

函数 功能 例子
Tensor(*sizes) 制定size构造函数 a = t.Tensor(2, 3)
Tensor(*list) 从list构造 a = t.Tensor([[1,2,3],[4,5,6]])
tensor(data,) 类似np.array的构造函数(需要数据) scalar = t.tensor(3.14159) ;vector = t.tensor([1, 2])
ones(*sizes) 全1Tensor
zeros(*sizes) 全0Tensor
eye(*sizes) 对角线为1,其他为0 t.eye(2, 3, dtype=t.int) # 对角线为1, 不要求行列数一致
arange(s,e,step 从s到e,步长为step t.arange(1, 6, 2)
linspace(s,e,steps) 从s到e,均匀切分成steps份 t.linspace(1, 10, 3)
rand/randn(*sizes) 均匀/标准分布 t.randn(2, 3, device=t.device(‘cpu’))
normal(mean,std)/uniform(from,to) 正态分布/均匀分布
randperm(m) 随机排列 t.randperm(5) # 长度为5的随机排列

这些创建方法都可以在创建的时候指定数据类型dtype和存放device(cpu/gpu).

t.tensor和t.Tensor的区别

一句话来讲,torch.Tensor是由torch.tensortorch.empty组成的。torch.Tensor可以新建一个空tensor而torch.tensor不可以。

tensor_without_data = torch.Tensor() #对

tensor_without_data = torch.tensor()  错

基本操作

查看tensor 大小: t.size(), t.shape()

tensor转list: t.tolist()

计算tensor中元素总个数: t.numel()

调整形状:

t.view(), t.resize()

a = t.arange(0, 6)
a.view(2, 3)
a.shape()

$ tensor([[ 0., 1., 2.],
[ 3., 4., 5.]])
torch.Size([2, 3])

resize与view不同的是它可以修改tensor的大小。

b.resize_(1, 3)
b.resize_(3, 3)

tensor([[ 0., 100., 2.]])
tensor([[ 0.0000, 100.0000, 2.0000],
[ 3.0000, 4.0000, 5.0000],
[ -0.0000, 0.0000, 0.0000]])

增减维度 t.squeeze(), t.unsqueeze()?

b.unsqueeze(1) # 注意形状,在第1维(下标从0开始)上增加“1” 
#等价于 b[:,None]
b[:, None].shape

b从torch.Size([2, 3])变成torch.Size([2, 1, 3])

索引操作

如无特殊说明,索引出来的结果与原tensor共享内存,也即修改一个,另一个会跟着修改。

a[0] # 第0行(下标从0开始)
a[:, 0] # 第0列
a[0][2] # 第0行第2个元素,等价于a[0, 2]
a[0, -1] # 第0行最后一个元素
a[:2] # 前两行
a[:2, 0:2] # 前两行,第0,1列
a[0:1, :2] # 第0行,前两列 

# None类似于np.newaxis, 为a新增了一个轴
# 等价于a.view(1, a.shape[0], a.shape[1])
a[None].shape # 等价于a[None,:,:]
a[:,None,:].shape

a > 1 # 返回一个ByteTensor
a[a>1] # 等价于a.masked_select(a>1)	# 选择结果与原tensor不共享内存空间
a[t.LongTensor([0,1])] # 第0行和第1行

常用的选择函数

函数 功能
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一样

gather是一个比较复杂的操作,对一个2维tensor,输出的每个元素如下:

out[i][j] = input[index[i][j]][j]  # dim=0
out[i][j] = input[i][index[i][j]]  # dim=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([[0,1,2,3]])
a.gather(0, index)
tensor([[  0.,   5.,  10.,  15.]])

# 选取反对角线上的元素
index = t.LongTensor([[3,2,1,0]]).t()
a.gather(1, index)
tensor([[  3.],
        [  6.],
        [  9.],
        [ 12.]])
 # 选取反对角线上的元素,注意与上面的不同
index = t.LongTensor([[3,2,1,0]])
a.gather(0, index)
# 选取反对角线上的元素,注意与上面的不同

index = t.LongTensor([[3,2,1,0]])

a.gather(0, index)

tensor([[ 12.,   9.,   6.,   3.]])      

gather相对应的逆操作是scatter_gather把数据从input中按index取出,而scatter_是把取出的数据再放回去。注意scatter_函数是inplace操作。

out = input.gather(dim, index)
-->近似逆操作
out = Tensor()
out.scatter_(dim, index)

把两个对角线元素放回去到指定位置
c = t.zeros(4,4)
c.scatter_(1, index, b)
tensor([[ 0., 0., 0., 3.],
[ 0., 5., 6., 0.],
[ 0., 9., 10., 0.],
[ 12., 0., 0., 15.]])

对tensor的任何索引操作仍是一个tensor,想要获取标准的python对象数值,需要调用tensor.item(), 这个方法只对包含一个元素的tensor适用
a[0,0] #依旧是tensor)
a[0,0].item() # python float

Tensor类型

Tensor有不同的数据类型,如表3-3所示,每种类型分别对应有CPU和GPU版本(HalfTensor除外)。默认的tensor是FloatTensor,可通过t.set_default_tensor_type 来修改默认tensor类型(如果默认类型为GPU tensor,则所有操作都将在GPU上进行)。Tensor的类型对分析内存占用很有帮助。例如对于一个size为(1000, 1000, 1000)的FloatTensor,它有1000*1000*1000=10^9个元素,每个元素占32bit/8 = 4Byte内存,所以共占大约4GB内存/显存。HalfTensor是专门为GPU版本设计的,同样的元素个数,显存占用只有FloatTensor的一半,所以可以极大缓解GPU显存不足的问题,但由于HalfTensor所能表示的数值大小和精度有限1,所以可能出现溢出等问题。

tensor数据类型

Data type dtype CPU tensor GPU tensor
32-bit floating point torch.float32 or torch.float torch.FloatTensor torch.cuda.FloatTensor
64-bit floating point torch.float64 or torch.double torch.DoubleTensor torch.cuda.DoubleTensor
16-bit floating point torch.float16 or torch.half torch.HalfTensor torch.cuda.HalfTensor
8-bit integer (unsigned) torch.uint8 torch.ByteTensor torch.cuda.ByteTensor
8-bit integer (signed) torch.int8 torch.CharTensor torch.cuda.CharTensor
16-bit integer (signed) torch.int16 or torch.short torch.ShortTensor torch.cuda.ShortTensor
32-bit integer (signed) torch.int32 or torch.int torch.IntTensor torch.cuda.IntTensor
64-bit integer (signed) torch.int64 or torch.long torch.LongTensor torch.cuda.LongTensor

各数据类型之间可以互相转换,type(new_type)是通用的做法,同时还有floatlonghalf等快捷方法。CPU tensor与GPU tensor之间的互相转换通过tensor.cudatensor.cpu方法实现,此外还可以使用tensor.to(device)。Tensor还有一个new方法,用法与t.Tensor一样,会调用该tensor对应类型的构造函数,生成与当前tensor类型一致的tensor。torch.*_like(tensora) 可以生成和tensora拥有同样属性(类型,形状,cpu/gpu)的新tensor。 tensor.new_*(new_shape) 新建一个不同形状的tensor。

数学运算

in place操作(是否修改自身数据)

对tensor的操作又可分为两类:

  1. 不会修改自身的数据,如 a.add(b), 加法的结果会返回一个新的tensor。
  2. 会修改自身的数据,如 a.add_(b), 加法的结果仍存储在a中,a被修改了。

逐元素操作

这部分操作会对tensor的每一个元素(point-wise,又名element-wise)进行操作,此类操作的输入与输出形状一致。常用的操作如表3-4所示。

表3-4: 常见的逐元素操作

函数 功能
abs/sqrt/div/exp/fmod/log/pow… 绝对值/平方根/除法/指数/求余/求幂…
cos/sin/asin/atan2/cosh… 相关三角函数
ceil/round/floor/trunc 上取整/四舍五入/下取整/只保留整数部分
clamp(input, min, max) 超过min和max部分截断
sigmod/tanh… 激活函数

对于很多操作,例如div、mul、pow、fmod等,PyTorch都实现了运算符重载,所以可以直接使用运算符。如a ** 2 等价于torch.pow(a,2), a * 2等价于torch.mul(a,2)

其中clamp(x, min, max)的输出满足以下公式:
y i = { m i n , if  x i < m i n x i , if  m i n ≤ x i ≤ m a x m a x , if  x i > m a x y_i = \begin{cases} min, & \text{if } x_i \lt min \\ x_i, & \text{if } min \le x_i \le max \\ max, & \text{if } x_i \gt max\\ \end{cases} yi=min,xi,max,if xi<minif minximaxif xi>max
clamp常用在某些需要比较大小的地方,如取一个tensor的每个元素与另一个数的较大值。

归并操作

此类操作会使输出形状小于输入形状,并可以沿着某一维度进行指定操作。如加法sum,既可以计算整个tensor的和,也可以计算tensor中每一行或每一列的和。常用的归并操作如表3-5所示。

表3-5: 常用归并操作

函数 功能
mean/sum/median/mode 均值/和/中位数/众数
norm/dist 范数/距离
std/var 标准差/方差
cumsum/cumprod 累加/累乘

以上大多数函数都有一个参数**dim**,用来指定这些操作是在哪个维度上执行的。关于dim(对应于Numpy中的axis)的解释众说纷纭,这里提供一个简单的记忆方式:

假设输入的形状是(m, n, k)

  • 如果指定dim=0,输出的形状就是(1, n, k)或者(n, k)
  • 如果指定dim=1,输出的形状就是(m, 1, k)或者(m, k)
  • 如果指定dim=2,输出的形状就是(m, n, 1)或者(m, n)

size中是否有"1",取决于参数keepdimkeepdim=True会保留维度1。注意,以上只是经验总结,并非所有函数都符合这种形状变化方式,如cumsum

比较

比较函数中有一些是逐元素比较,操作类似于逐元素操作,还有一些则类似于归并操作。常用比较函数如表3-6所示。

表3-6: 常用比较函数

函数 功能
gt/lt/ge/le/eq/ne 大于/小于/大于等于/小于等于/等于/不等
topk 最大的k个数
sort 排序
max/min 比较两个tensor最大最小值

表中第一行的比较操作已经实现了运算符重载,因此可以使用a>=ba>ba!=ba==b,其返回结果是一个ByteTensor,可用来选取元素。max/min这两个操作比较特殊,以max来说,它有以下三种使用情况:

  • t.max(tensor):返回tensor中最大的一个数
  • t.max(tensor,dim):指定维上最大的数,返回tensor和下标
  • t.max(tensor1, tensor2): 比较两个tensor相比较大的元素

至于比较一个tensor和一个数,可以使用clamp函数。

线性代数

PyTorch的线性函数主要封装了Blas和Lapack,其用法和接口都与之类似。常用的线性代数函数如表3-7所示。

表3-7: 常用的线性代数函数

函数 功能
trace 对角线元素之和(矩阵的迹)
diag 对角线元素
triu/tril 矩阵的上三角/下三角,可指定偏移量
mm/bmm 矩阵乘法,batch的矩阵乘法
addmm/addbmm/addmv/addr/badbmm… 矩阵运算
t 转置
dot/cross 内积/外积
inverse 求逆矩阵
svd 奇异值分解

具体使用说明请参见官方文档2,需要注意的是,矩阵的转置会导致存储空间不连续,需调用它的.contiguous方法将其转为连续。

GPU/CPU

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

  • 尽量使用tensor.to(device), 将device设为一个可配置的参数,这样可以很轻松的使程序同时兼容GPU和CPU
  • 数据在GPU之中传输的速度要远快于内存(CPU)到显存(GPU), 所以尽量避免频繁的在内存和显存中传输数据。
a = t.randn(3, 4)
a.device
device(type='cpu')
if t.cuda.is_available():
    a = t.randn(3,4, device=t.device('cuda:1'))
    # 等价于
    # a.t.randn(3,4).cuda(1)
    # 但是前者更快
    a.device
device = t.device('cpu')
a.to(device)
    

  1. https://stackoverflow.com/questions/872544/what-range-of-numbers-can-be-represented-in-a-16-32-and-64-bit-ieee-754-syste ↩︎

  2. http://pytorch.org/docs/torch.html#blas-and-lapack-operations ↩︎

你可能感兴趣的:(Pytorch神经网络)