Tensor
Tensor张量,可简单地认为它就是一个数组,且支持高效的科学计算。它可以是一个数(标量)、一维数组(向量)、二维数组(矩阵)和更高维的数组(高阶数据)。Tensor和Numpy的ndarrays类似,但PyTorch的tensor支持GPU加速。
# Let's begin
from __future__ import print_function
import torch as t
t.__version__
- 1
- 2
- 3
- 4
'1.0.1'
- 1
基础操作
tensor的接口成与Numpy类似
从接口的角度来讲,对tensor的操作可分为两类:
-
,如
等。
-
,如
等。
如 ((a, b))
与 ((b))
功能等价。
而从存储的角度来讲,对tensor的操作又可分为两类:
- 不会修改自身的数据,如
(b)
, 加法的结果会返回一个新的tensor。 - 会修改自身的数据,如
a.add_(b)
, 加法的结果仍存储在a中,a被修改了。
函数名以_
结尾的都是inplace方式, 即会修改调用者自己的数据,在实际应用中需加以区分。
创建Tensor
在PyTorch中新建tensor的方法有很多,具体如表3-1所示。
常见新建tensor的方法
函数 | 功能 |
---|---|
Tensor(*sizes) | 基础构造函数 |
tensor(data,) | 类似的构造函数 |
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) | 随机排列 |
这些创建方法都可以在创建的时候指定数据类型dtype和存放device(cpu/gpu).
其中使用Tensor
函数新建tensor是最复杂多变的方式,它既可以接收一个list,并根据list的数据新建tensor,也能根据指定的形状新建tensor,还能传入其他的tensor
# 指定tensor的形状
a = t.Tensor(2, 3)
a # 数值取决于内存空间的状态,print时候可能overflow
- 1
- 2
- 3
tensor([[5.3285e+01, 4.5908e-41, 1.4013e-45],
[0.0000e+00, 1.4013e-45, 0.0000e+00]])
- 1
- 2
# 用list的数据创建tensor
b = t.Tensor([[1,2,3],[4,5,6]])
b
- 1
- 2
- 3
tensor([[1., 2., 3.],
[4., 5., 6.]])
- 1
- 2
b.tolist() # 把tensor转为list
- 1
[[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]]
- 1
()
返回对象,它是tuple的子类,但其使用方式与tuple略有区别
b_size = b.size()
b_size
- 1
- 2
([2, 3])
- 1
b.numel() # b中元素总个数,2*3,等价于()
- 1
6
- 1
# 创建一个和b形状一样的tensor
c = t.Tensor(b_size)
# 创建一个元素为2和3的tensor
d = t.Tensor((2, 3))
c, d
- 1
- 2
- 3
- 4
- 5
(tensor([[5.3285e+01, 4.5908e-41, 0.0000e+00],
[0.0000e+00, 0.0000e+00, 0.0000e+00]]), tensor([2., 3.]))
- 1
- 2
除了()
,还可以利用直接查看tensor的形状,
等价于
()
c.shape
- 1
([2, 3])
- 1
需要注意的是,(*sizes)
创建tensor时,系统不会马上分配空间,只是会计算剩余的内存是否足够使用,使用到tensor时才会分配,而其它操作都是在创建完tensor之后马上进行空间分配。其它常用的创建tensor的方法举例如下。
t.ones(2, 3)
- 1
tensor([[1., 1., 1.],
[1., 1., 1.]])
- 1
- 2
t.zeros(2, 3)
- 1
tensor([[0., 0., 0.],
[0., 0., 0.]])
- 1
- 2
t.arange(1, 6, 2)
- 1
tensor([1, 3, 5])
- 1
# 不能以1为步长
t.linspace(1, 10, 3)
- 1
- 2
tensor([ 1.0000, 5.5000, 10.0000])
- 1
t.randn(2, 3)
- 1
tensor([[-1.4943, -0.7678, -1.3722],
[-0.9745, -1.3788, -0.0625]])
- 1
- 2
t.randperm(5) # 长度为5的随机排列
- 1
tensor([3, 2, 0, 1, 4])
- 1
t.eye(2, 3, dtype=t.int) # 对角线为1, 不要求行列数一致
- 1
tensor([[1, 0, 0],
[0, 1, 0]], dtype=torch.int32)
- 1
- 2
常用Tensor操作
通过方法可以调整tensor的形状,必须保证调整前后元素总数一致。应用中可能经常需要添加或减少某一维度,这时候
squeeze
和unsqueeze
两个函数就派上用场了。
# 就是numpy中的reshape
a = t.Tensor([[1,2,3],[4,5,6]])
a.view(2,3)
- 1
- 2
- 3
- 4
tensor([[1., 2., 3.],
[4., 5., 6.]])
- 1
- 2
b = a.view(-1, 3) # 当某一维为-1的时候,会自动计算它的大小
b.shape
b
- 1
- 2
- 3
tensor([[1., 2., 3.],
[4., 5., 6.]])
- 1
- 2
b.unsqueeze(1) # 注意形状,在第1维(下标从0开始)上增加“1”
#等价于 b[:,None]
b[:, None].shape # ([2, 1, 3])
print(b)
- 1
- 2
- 3
- 4
tensor([[1., 2., 3.],
[4., 5., 6.]])
- 1
- 2
b.unsqueeze(-2) # -2表示倒数第二个维度
- 1
tensor([[[1., 2., 3.]],
[[4., 5., 6.]]])
- 1
- 2
- 3
c = b.view(1, 1, 1, 2, 3)
c.squeeze(0) # 压缩第0维的“1”
- 1
- 2
tensor([[[[1., 2., 3.],
[4., 5., 6.]]]])
- 1
- 2
c.squeeze() # 把所有维度为“1”的压缩
- 1
tensor([[1., 2., 3.],
[4., 5., 6.]])
- 1
- 2
a[1] = 100
b # a修改,b作为view之后的,也会跟着修改
- 1
- 2
tensor([[ 1., 2., 3.],
[100., 100., 100.]])
- 1
- 2
resize
是另一种可用来调整size
的方法,但与view
不同,它可以修改tensor的大小。如果新大小超过了原大小,会自动分配新的内存空间,而如果新大小小于原大小,则之前的数据依旧会被保存,看一个例子。
b.resize_(1, 3)
b
- 1
- 2
tensor([[1., 2., 3.]])
- 1
b.resize_(3, 3) # 旧的数据依旧保存着,多出的大小会分配新空间
b
- 1
- 2
tensor([[1.0000e+00, 2.0000e+00, 3.0000e+00],
[1.0000e+02, 1.0000e+02, 1.0000e+02],
[1.6992e-07, 0.0000e+00, 0.0000e+00]])
- 1
- 2
- 3
索引操作
Tensor支持与类似的索引操作,语法上也类似,下面通过一些例子,讲解常用的索引操作。如无特殊说明,索引出来的结果与原tensor共享内存,也即修改一个,另一个会跟着修改。
a = t.randn(3, 4)
a
- 1
- 2
tensor([[-0.0766, -0.4981, -0.1263, -0.2356],
[ 0.1508, 1.0202, 0.4058, -0.9278],
[ 0.0286, -0.6448, -0.3914, 0.8246]])
- 1
- 2
- 3
a[0] # 第0行(下标从0开始)
- 1
tensor([-0.0766, -0.4981, -0.1263, -0.2356])
- 1
a[:, 0] # 第0列
- 1
tensor([-0.0766, 0.1508, 0.0286])
- 1
a[0][2] # 第0行第2个元素,等价于a[0, 2]
- 1
tensor(-0.1263)
- 1
a[0, -1] # 第0行最后一个元素
- 1
tensor(-0.2356)
- 1
a[:2] # 前两行
- 1
tensor([[-0.0766, -0.4981, -0.1263, -0.2356],
[ 0.1508, 1.0202, 0.4058, -0.9278]])
- 1
- 2
a[:2, 0:2] # 前两行,第0,1列
- 1
tensor([[-0.0766, -0.4981],
[ 0.1508, 1.0202]])
- 1
- 2
print(a[0:1, :2]) # 第0行,前两列
print(a[0, :2]) # 注意两者的区别:形状不同
- 1
- 2
tensor([[-0.0766, -0.4981]])
tensor([-0.0766, -0.4981])
- 1
- 2
# None类似于, 为a新增了一个轴
# 等价于(1, [0], [1])
a[None].shape
- 1
- 2
- 3
([1, 3, 4])
- 1
a[None].shape # 等价于a[None,:,:]
- 1
([1, 3, 4])
- 1
a[:,None,:].shape
- 1
([3, 1, 4])
- 1
a[:,None,:,None,None].shape
- 1
([3, 1, 4, 1, 1])
- 1
a > 1 # 返回一个ByteTensor
- 1
tensor([[0, 0, 0, 0],
[0, 1, 0, 0],
[0, 0, 0, 0]], dtype=torch.uint8)
- 1
- 2
- 3
a[a>1] # 等价于a.masked_select(a>1)
# 选择结果与原tensor不共享内存空间
- 1
- 2
tensor([1.0202])
- 1
a[t.LongTensor([0,1])] # 第0行和第1行
- 1
tensor([[-0.0766, -0.4981, -0.1263, -0.2356],
[ 0.1508, 1.0202, 0.4058, -0.9278]])
- 1
- 2
常用的选择函数
函数 | 功能 |
---|---|
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
- 1
- 2
三维tensor的gather
操作同理,下面举几个例子。
a = t.arange(0, 16).view(4, 4)
a
- 1
- 2
tensor([[ 0, 1, 2, 3],
[ 4, 5, 6, 7],
[ 8, 9, 10, 11],
[12, 13, 14, 15]])
- 1
- 2
- 3
- 4
# 选取对角线的元素
index = t.LongTensor([[0,1,2,3]])
a.gather(0, index)
- 1
- 2
- 3
tensor([[ 0, 5, 10, 15]])
- 1
# 选取反对角线上的元素
index = t.LongTensor([[3,2,1,0]]).t()
a.gather(1, index)
- 1
- 2
- 3
tensor([[ 3],
[ 6],
[ 9],
[12]])
- 1
- 2
- 3
- 4
# 选取反对角线上的元素,注意与上面的不同
index = t.LongTensor([[3,2,1,0]])
a.gather(0, index)
- 1
- 2
- 3
tensor([[12, 9, 6, 3]])
- 1
# 选取两个对角线上的元素
index = t.LongTensor([[0,1,2,3],[3,2,1,0]]).t()
b = a.gather(1, index)
b
- 1
- 2
- 3
- 4
tensor([[ 0, 3],
[ 5, 6],
[10, 9],
[15, 12]])
- 1
- 2
- 3
- 4