首先,我们介绍n维数组,也称为张量(tensor)。在python中数组通过调用Numpy计算包实现,但在pyTorch中为Tensor。虽然这两者相似,但Tensor比Numpy多一些重要的功能:①GPU算力比CPU高,而Numpy仅支持CPU计算;②Tensor类支持自动微分,更适合深度学习。
torch
这节主要介绍一个基本数值计算工具torch的一些操作,首先我们导入torch,并使用arange创建一个行向量。这个⾏向量包含以0开始的前12个整数,它们默认创建为整数。张量中的每个值都称为张量的 元素(element)。例如,张量 x 中有 12 个元素。除⾮额外指定,新的张量将存储在内存中,并采⽤基于CPU的计算。
import torch
x = torch.arange(12)
print(x)
tensor([ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11])
可以通过张量的shape属性来访问张量的形状。
x.shape
torch.Size([12])
下面通过numel()函数获取张量中元素的数量
x.numel()
12
在一些场景中,我们需要改变一个张量的形状而不改变元素数量和元素值,这可以通过reshape()函数实现。例如,可以把张量x从形状为(12,)的行向量转换为形状为(3,4)的矩阵。
X = x.reshape(3, 4)
X
tensor([[ 0, 1, 2, 3],
[ 4, 5, 6, 7],
[ 8, 9, 10, 11]])
如果在改变张量形状前,我们知道目标矩阵的行数或列数,那么可以通过-1来调用reshape()函数自动计算出维度的功能。即我们可以⽤x.reshape(-1,4)或x.reshape(3,-1)来取x.reshape(3,4)。
有时,我们希望使⽤全0、全1、其他常量,或者从特定分布中随机采样的数字来初始化矩阵。我们可以创建⼀个形状为(2,3,4)的张量,其中所有元素都设置为0。代码如下:
torch.zeros((2, 3, 4))
tensor([[[0., 0., 0., 0.],
[0., 0., 0., 0.],
[0., 0., 0., 0.]],
[[0., 0., 0., 0.],
[0., 0., 0., 0.],
[0., 0., 0., 0.]]])
同样,我们可以创建⼀个形状为(2,3,4)的张量,其中所有元素都设置为1。代码如下:
torch.ones((2, 3, 4))
tensor([[[1., 1., 1., 1.],
[1., 1., 1., 1.],
[1., 1., 1., 1.]],
[[1., 1., 1., 1.],
[1., 1., 1., 1.],
[1., 1., 1., 1.]]])
有时我们想通过从某个特定的概率分布中随机采样来得到张量中每个元素的值,可以通过randn()函数实现。如以下代码创建⼀个形状为(3,4)的张量,其中的每个元素都从均值为0、标准差为1的标准正态分布中随机采样。
torch.randn(3, 4)
tensor([[ 1.1445, 0.5344, 1.7990, 0.1128],
[-0.5328, 0.4657, 0.7276, 0.2435],
[ 0.0553, 0.2340, 0.8917, -0.5017]])
我们还可以通过提供包含数值的Python列表(或嵌套列表),来为所需张量中的每个元素赋予确定值。
torch.tensor([[2, 1, 4, 3], [1, 2, 3, 4], [4, 3, 2, 1]])
tensor([[2, 1, 4, 3],
[1, 2, 3, 4],
[4, 3, 2, 1]])
运算符
常⻅的标准算术运算符(+、 -、 *、 /和**),该运算符用于任意具有相同形状的张量间的计算。
x = torch.tensor([1.0, 2, 4, 8])
y = torch.tensor([2, 2, 2, 2])
x + y, x - y, x * y, x / y, x ** y # **运算符是求幂运算
(tensor([ 3., 4., 6., 10.]),
tensor([-1., 0., 2., 6.]),
tensor([ 2., 4., 8., 16.]),
tensor([0.5000, 1.0000, 2.0000, 4.0000]),
tensor([ 1., 4., 16., 64.]))
“按元素”⽅式可以应⽤更多的计算,包括像求幂这样的⼀元运算符torch.exp(x),求正弦值torch.sin(x)等等。
除按元素计算外,还可以执行线性代数运算,包括向量点积和矩阵乘法。我们也可以把多个张量连接(concatenate)在一起形成一个更大的张量。我们只需要提供张量列表,并给出沿哪个轴连接(沿行轴用dim=0,沿列轴用dim=1),这里需要注意张量的形状。下面我们用代码实现张量连接操作:
X = torch.arange(12, dtype=torch.float32).reshape((3,4))
Y = torch.tensor([[2.0, 1, 4, 3], [1, 2, 3, 4], [4, 3, 2, 1]])
torch.cat((X, Y), dim=0), torch.cat((X, Y), dim=1)
(tensor([[ 0., 1., 2., 3.],
[ 4., 5., 6., 7.],
[ 8., 9., 10., 11.],
[ 2., 1., 4., 3.],
[ 1., 2., 3., 4.],
[ 4., 3., 2., 1.]]),
tensor([[ 0., 1., 2., 3., 2., 1., 4., 3.],
[ 4., 5., 6., 7., 1., 2., 3., 4.],
[ 8., 9., 10., 11., 4., 3., 2., 1.]]))
通过sum()函数对张量中的所有元素进⾏求和,会产⽣⼀个单元素张量。
X.sum()
tensor(66.)
广播机制
在上⾯的部分中,我们看到了如何在相同形状的两个张量上执⾏按元素操作。在某些情况下,即使形状不同,我们仍然可以通过调⽤ ⼴播机制(broadcasting mechanism)来执⾏按元素操作。这种机制的⼯作⽅式如下:⾸先,通过适当复制元素来扩展⼀个或两个数组,以便在转换之后,两个张量具有相同的形状。其次,对⽣成的数组执⾏按元素操作。如下:a和b分别是3 × 1和1 × 2矩阵,如果让它们相加,它们的形状不匹配。我们将两个矩阵⼴播为⼀个更⼤的3 × 2矩阵,矩阵a将复制列,矩阵b将复制⾏,然后再按元素相加。
a = torch.arange(3).reshape((3, 1))
b = torch.arange(2).reshape((1, 2))
a, b
(tensor([[0],
[1],
[2]]),
tensor([[0, 1]]))
a + b
tensor([[0, 1],
[1, 2],
[2, 3]])
索引和切片
就像python数组一样,张量中的元素可以通过索引访问。与python数组一样,张量中第一个元素的索引是0,最后一个元素的索引是-1。
如下所⽰,我们可以⽤[-1]选择最后⼀个元素,可以⽤[1:3]选择第⼆个和第三个元素(对于矩阵是默认对行进行操作)
X[-1], X[1:3]
(tensor([ 8., 9., 10., 11.]),
tensor([[ 4., 5., 6., 7.],
[ 8., 9., 10., 11.]]))
如果你想实现矩阵中列的索引,可以通过X[:, a:b]实现,同样对行索引可以通过X[a:b, :]实现
X[:, 1:3], X[1:3, :]
(tensor([[ 1., 2.],
[ 5., 6.],
[ 9., 10.]]),
tensor([[ 4., 5., 6., 7.],
[ 8., 9., 10., 11.]]))
除读取外,我们还可以通过指定索引来将元素写入矩阵
X[1, 2] = 9
X
tensor([[ 0., 1., 2., 3.],
[ 4., 5., 9., 7.],
[ 8., 9., 10., 11.]])
转换为其他python对象
将深度学习框架定义的张量转换为Numpy很容易,反之也同样容易。
A = X.numpy()
B = torch.tensor(A)
type(A), type(B)
(numpy.ndarray, torch.Tensor)
要将⼤小为1的张量转换为Python标量,我们可以调⽤item函数或Python的内置函数。
a = torch.tensor([3.5])
a, a.item(), float(a), int(a)
(tensor([3.5000]), 3.5, 3.5, 3)