深度学习框架PyTorch入门与实践:第三章 Tensor和autograd
几乎所有的深度学习框架背后的设计核心都是张量和计算图,PyTorch也不例外,本章我们将学习PyTorch中的张量系统(Tensor)和自动微分系统(autograd)。
3.1Tensor
Tensor,又名张量,读者可能对这个名词似曾相识,因为它不仅在PyTorch中出现过,也是Theano、TensorFlow、Torch和MXNet中重要的数据结构。关于张量的本质不乏深度剖析的文章,但从工程角度讲,可简单地认为它就是一个数组,且支持高效的科学计算。它可以是一个数(标量)、一维数组(向量)、二维数组(矩阵)或更高维的数组(高阶数据)。Tensor和numpy的ndarray类似,但PyTorch的tensor支持GPU加速。
本节将系统讲解Tensor的使用,力求面面俱到,但不会涉及每个函数。对于更多函数及其用法,读者可通过在IPython/Notebook中使用<function>?查看帮助文档,或查阅PyTorch的官方文档。
In : from __future__ import print_function
import torch as t
3.1.1 基础操作
学习过numpy的读者会对本节内容非常熟悉,因为tensor的接口设计得与numpy类似,以方便用户使用。若不熟悉numpy也没关系,本节内容并不要求读者先掌握numpy。
从接口角度讲,对tensor的操作可分为两类:
(1)torch.function,如torch.save等。
(2)tensor.function,如tensor.view等。
为方便使用,对tensor的大部分操作同时支持这两类接口,在本书中不做具体区分,如torch.sum(a,b)与a.sum(b)功能等价。
从存储角度讲,对tensor的操作又可以分为两类:
(1)不会修改自身的数据,如a.add(b),加法的结果会返回一个新的tensor。
(2)会修改自身的数据,如a.add_(b),加法的结果仍存储在a中,a被修改了。
函数名以下划线结尾的都是inplace方式,即会修改调用者自己的数据,在实际应用中需要加以区分。
(1)创建Tensor
在PyTorch中新建Tensor的方法有很多种,具体如下:
其中使用Tensor函数新建tensor是最复杂多变的方式,它既可以接收一个list,并根据list的数据新建tensor,也能根据指定的形状新建tensor,还能传入其他的tensor,下面举几个例子。
In : # 指定tensor的形状
a = t.Tensor(2,3)
a # 数值取决于内存空间的状态
Out : tensor([[1.8441e-35, 6.3619e-43, 1.8441e-35],
[6.3619e-43, 3.5071e-35, 6.3619e-43]])
In : # 用list的数据创建tensor
b = t.Tensor([[1,2,3],[4,5,6]])
b
Out : tensor([[1., 2., 3.],
[4., 5., 6.]])
In : b.tolist() # 把tensor转为list
Out : [[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]]
tensor.size()返回torch.Size对象,它是tuple的子类,但其使用方式与tuple略有区别。
In : b_size = b.size()
b_size
Out : torch.Size([2, 3])
In : b.numel() # b中元素个数, 2 * 3,等价于b.nelument()
Out : 6
In : # 创建一个和b形状一样的tensor
c = t.Tensor(b_size)
# 创建一个元素为2和3的tensor
d = t.Tensor((2,3))
c,d
Out : (tensor([[1.3003e+22, 1.0723e-08, 1.3602e-05],
[2.6942e+23, 5.3835e+22, 1.0547e-08]]), tensor([2., 3.]))
除了tensor.size(),还可以利用tensor.shape直接查看tensor的形状,tensor.shape等价于tensor.size()。
In : c.shape
Out : torch.Size([2, 3])
In : c.shape??
需要注意的是,t.Tensor(*sizes)创建tensor,系统不会马上分配空间,只会计算剩余的内存是否足够使用,使用到tensor时才会分配,而其他操作都是在创建完tensor后马上进行空间分配。其他常用的创建tensor方法举例如下。
In : t.ones(2,3)
Out : tensor([[1., 1., 1.],
[1., 1., 1.]])
In : t.zeros(2,3)
Out : tensor([[0., 0., 0.],
[0., 0., 0.]])
In : t.arange(1,6,2)
Out : tensor([1, 3, 5])
In : t.linspace(1,10,3)
Out : tensor([ 1.0000, 5.5000, 10.0000])
In : t.randn(2,3)
Out : tensor([[ 1.5961, -0.3247, 0.8576],
[ 1.4467, 0.5929, 1.1375]])
In : t.randperm(5) # 长度为5的随机排列
Out : tensor([3, 0, 1, 4, 2])
In : t.eye(2,3) # 对角线为1,其他位置为0,不要求行列数一致
Out : tensor([[1., 0., 0.],
[0., 1., 0.]])
(2)常用Tensor操作
通过tensor.view方法可以调整tensor的形状,但必须保证调整前后元素总数一致。view不会修改自身的数据,返回的新tensor与源tensor共享内存,即更改其中一个,另外一个也会跟着改变。在实际应用中可能经常需要添加或减少某一维度,这时squeeze和unsqueeze两个函数就派上了用场。
In : a = t.arange(0,6)
a.view(2,3)
Out : tensor([[0, 1, 2],
[3, 4, 5]])
In : b = a.view(-1,3) # 当某一维为-1的时候,会自动计算它的大小
b
Out : tensor([[0, 1, 2],
[3, 4, 5]])
In : b.unsqueeze(1) # 注意形状,在第1维(下标从0开始)上增加“1”
Out : tensor([[[0, 1, 2]],
[[3, 4, 5]]])
In : b.unsqueeze(-2) # -2表示倒数第二个维度
Out : tensor([[[0, 1, 2]],
[[3, 4, 5]]])
In : c = b.view(1,1,1,2,3)
c.squeeze(0) # 压缩第0维的“1”
Out : tensor([[[[0, 1, 2],
[3, 4, 5]]]])
In : c.squeeze() # 把所有维度为“1”的压缩
Out : tensor([[0, 1, 2],
[3, 4, 5]])
In : a[1] = 100
b # a和b共享内存,修改了a,b也变了
Out : tensor([[ 0, 100, 2],
[ 3, 4, 5]])
resize是另一种可用来调整size的方法,但与view不同,它可以修改tensor的尺寸。如果新尺寸超过了原尺寸,会自动分配新的内存空间,而如果新尺寸小于原尺寸,则之前的数据依旧会被保存,我们来看一个例子。
In : b.resize_(1,3)
b
Out : tensor([[ 0, 100, 2]])
In : b.resize_(3,3) # 旧的数据依旧保存着,多出的数据会分配新空间
b
Out : tensor([[ 0, 100, 2],
[ 3, 4, 5],
[ 0, 0, 0]])
(3)索引操作
Tensor支持与numpy.ndarray类似的索引操作,语法上也类似,下面通过一些例子,讲解常用的索引操作。如无特殊说明,索引出来的结果与原tensor共享内存,即修改一个,另一个会跟着修改。
In : a = t.randn(3,4)
a
Out : tensor([[-0.3973, 0.0700, -0.5916, -0.4848],
[ 0.5232, 0.8094, 2.4726, 1.4351],
[-0.0845, 2.0454, -0.0363, -0.8606]])
In : a[0] # 第0行(下标从0开始)
Out : tensor([-0.3973, 0.0700, -0.5916, -0.4848])
In : a[:,0] # 第0列
Out : tensor([-0.3973, 0.5232, -0.0845])
In : a[0][2] # 第0行第2个元素,等价于a[0,2]
Out : tensor(-0.5916)
In : a[0,-1] # 第0行最后一个元素
Out : tensor(-0.4848)
In : a[:2] # 前两行
Out : tensor([[-0.3973, 0.0700, -0.5916, -0.4848],
[ 0.5232, 0.8094, 2.4726, 1.4351]])
In : a[:2,0:2] # 前两行,前两列
Out : tensor([[-0.3973, 0.0700],
[ 0.5232, 0.8094]])
In : print(a[0:1,:2]) # 第0行,前两列
print(a[0,:2]) # 注意两者的区别,形状不同
Out : tensor([[-0.3973, 0.0700]])
tensor([-0.3973, 0.0700])
In : a > 1
Out : tensor([[False, False, False, False],
[False, False, True, True],
[False, True, False