深度学习存储和操作数据的主要接口是张量(维数组)。
它提供了各种功能,包括基本数学运算、广播、索引、切片、内存节省和转换其它Python对象。
首先,我们导入torch。请注意,虽然它被称为PyTorch,但是代码中使用torch而不是pytorch。
import torch #导入pytorch数据包
张量表示由一个数值组成的数组,这个数组可能有多个维度。
具有两个轴以上的张量没有特殊的数学名称。
我们可以使用 arange 创建一个行向量 x。这个行向量包含以0开始的前12个整数,它们默认创建为整数。也可指定创建类型为浮点数。张量中的每个值都称为张量的 元素(element)。例如,张量 x 中有 12 个元素。除非额外指定,新的张量将存储在内存中,并采用基于CPU的计算。
x = torch.arange(12)
x
tensor([ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11])
可以通过张量的shape属性来访问张量(沿每个轴的长度)的形状 。
x.shape
torch.Size([12])
顺便输出一下x的类型
type(x)
torch.Tensor
可见,此Tensor即为张量。
如果只想知道张量中元素的总数,即形状的所有元素乘积,可以检查它的大小(size)。 因为这里在处理的是一个向量,所以它的shape与它的size相同。
这里使用**numel()**方法取得张量元素的总数。
x.numel()
12
要想改变一个张量的形状而不改变元素数量和元素值,可以调用reshape函数。 例如,可以把张量x从形状为(12,)的行向量转换为形状为(3,4)的矩阵。 这个新的张量包含与转换前相同的值,但是它被看成一个3行4列的矩阵。 要重点说明一下,虽然张量的形状发生了改变,但其元素值并没有变。 注意,通过改变张量的形状,张量的大小不会改变。
X = x.reshape(3,4) #注意这里是一个返回值
X
tensor([[ 0, 1, 2, 3],
[ 4, 5, 6, 7],
[ 8, 9, 10, 11]])
我们不需要通过手动指定每个维度来改变形状。 也就是说,如果我们的目标形状是(高度,宽度), 那么在知道宽度后,高度会被自动计算得出,不必我们自己做除法。 在上面的例子中,为了获得一个3行的矩阵,我们手动指定了它有3行和4列。幸运的是,我们可以通过-1来调用此自动计算出维度的功能。 即我们可以用x.reshape(-1,4)或x.reshape(3,-1)来取代x.reshape(3,4)。
如下
X = x.reshape(3,-1) #转化张量为3行,使用(-1)指定,其中会自动转换为4列
X
tensor([[ 0, 1, 2, 3],
[ 4, 5, 6, 7],
[ 8, 9, 10, 11]])
X = x.reshape(-1, 6) #转化张量为6列,行使用(-1)指定,其中会自动将行转换为2行
X
tensor([[ 0, 1, 2, 3, 4, 5],
[ 6, 7, 8, 9, 10, 11]])
有时,我们希望使用全0、全1、其他常量,或者从特定分布中随机采样的数字来初始化矩阵。 我们可以创建一个形状为(2,3,4)的张量,其中所有元素都设置为0。代码如下:
torch.zeros((2,3,4)) #生成3维值为0的张量
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)) #生成3维值为1的张量
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.]]])
有时我们想通过从某个特定的概率分布中随机采样来得到张量中每个元素的值。
例如,当我们构造数组来作为神经网络中的参数时,我们通常会随机初始化参数的值。 以下代码创建一个形状为(3,4)的张量。 其中的每个元素都从均值为0、标准差为1的标准高斯分布(正态分布)中随机采样。
torch.randn(3,4) #生成2维服从正态分布的数据
tensor([[-1.0417, -0.3881, 0.2431, 0.1366],
[ 0.9548, -1.3539, -0.1882, 1.0729],
[ 2.2897, 1.1516, 2.3107, -0.9823]])
我们还可以通过提供包含数值的Python列表(或嵌套列表),来为所需张量中的每个元素赋予确定值。 在这里,最外层的列表对应于轴0,内层的列表对应于轴1。
torch.tensor([[1,2,3,4], [1,2,3,4], [1,2,3,4]]) #使用列表的方式生成tensor张量,其为3行4列
tensor([[1, 2, 3, 4],
[1, 2, 3, 4],
[1, 2, 3, 4]])
我们的兴趣不仅限于读取数据和写入数据。 我们想在这些数据上执行数学运算,其中最简单且最有用的操作是按元素(elementwise)运算。它们将标准标量运算符应用于数组的每个元素。 对于将两个数组作为输入的函数,按元素运算将二元运算符应用于两个数组中的每对位置对应的元素。 我们可以基于任何从标量到标量的函数来创建按元素函数。
对于任意具有相同形状的张量,常见的标准算术运算符(+、-、*、/和**)都可以被升级为按元素运算。 我们可以在同一形状的任意两个张量上调用按元素操作。
在下面的例子中,我们使用逗号来表示一个具有5个元素的元组,其中每个元素都是按元素操作的结果(即加减乘除平方运算)。
a = torch.tensor([1, 3, 5, 7, 9]) #定义一维张量(1,5) a
b = torch.tensor([2, 4, 6, 8, 10]) #定义一维张量(1,5) b
a-b, a+b, a*b, a/b, a**b #张量之间的加减乘除平方运算
(tensor([-1, -1, -1, -1, -1]),
tensor([ 3, 7, 11, 15, 19]),
tensor([ 2, 12, 30, 56, 90]),
tensor([0.5000, 0.7500, 0.8333, 0.8750, 0.9000]),
tensor([ 1, 81, 15625, 5764801, 3486784401]))
“按元素”方式可以应用更多的计算,包括像**求幂(底数为e)**这样的一元运算符。
torch.exp(a)
tensor([2.7183e+00, 2.0086e+01, 1.4841e+02, 1.0966e+03, 8.1031e+03])
我们也可以把多个 张量连结(concatenate) 在一起, 把它们端对端地叠起来形成一个更大的张量。 我们只需要提供张量列表,并给出沿哪个轴连结。 下面的例子分别演示了当我们沿行(轴-0,形状的第一个元素) 和按列(轴-1,形状的第二个元素)连结两个矩阵时,会发生什么情况。
X1 = torch.arange(12).reshape(3,4)
X2 = torch.arange(12,24).reshape(3,4)
X1, X2, torch.cat((X1,X2), dim=0), torch.cat((X1,X2), dim=1) #dim为0时按行连接、1时按列连接
(tensor([[ 0, 1, 2, 3],
[ 4, 5, 6, 7],
[ 8, 9, 10, 11]]),
tensor([[12, 13, 14, 15],
[16, 17, 18, 19],
[20, 21, 22, 23]]),
tensor([[ 0, 1, 2, 3],
[ 4, 5, 6, 7],
[ 8, 9, 10, 11],
[12, 13, 14, 15],
[16, 17, 18, 19],
[20, 21, 22, 23]]),
tensor([[ 0, 1, 2, 3, 12, 13, 14, 15],
[ 4, 5, 6, 7, 16, 17, 18, 19],
[ 8, 9, 10, 11, 20, 21, 22, 23]]))
有时,我们想通过逻辑运算符构建二元张量。
以X == Y为例:
X1 == X2
tensor([[False, False, False, False],
[False, False, False, False],
[False, False, False, False]])
X2 = torch.tensor([[ 4, 5, 6, 7],[ 4, 5, 6, 7],[ 4, 5, 6, 7]])
X2, X1==X2
(tensor([[4, 5, 6, 7],
[4, 5, 6, 7],
[4, 5, 6, 7]]),
tensor([[False, False, False, False],
[ True, True, True, True],
[False, False, False, False]]))
对张量中的所有元素进行求和,会产生一个单元素张量。
X1.sum()
tensor(66)
在上面的部分中,我们看到了如何在相同形状的两个张量上执行按元素操作。 在某些情况下,即使形状不同,我们仍然可以通过调用 广播机制(broadcasting mechanism) 来执行按元素操作。 这种机制的工作方式如下:
1、通过适当复制元素来扩展一个或两个数组, 以便在转换之后,两个张量具有相同的形状。
2、对生成的数组执行按元素操作。
在大多数情况下,我们将沿着数组中长度为1的轴进行广播,如下例子:
X1 = torch.arange(3).reshape(1,3)
X2 = torch.arange(2).reshape(2,1)
X1, X2
(tensor([[0, 1, 2]]),
tensor([[0],
[1]]))
X1+X2
tensor([[0, 1, 2],
[1, 2, 3]])
如上, 相加的两个张量形状分别为(1,3)、(2,1),现在通过广播机制将两张量均转化为(2,3)形状(矩阵X1复制1行,矩阵X2,复制2列),然后相加。
Pytorch中的张量,就像在任何其他Python数组中一样,张量中的元素可以通过索引访问。
与任何Python数组一样:第一个元素的索引是0,最后一个元素索引是-1, 可以指定范围以包含第一个元素和最后一个之前的元素。
X, X[1], X[-1], X[1,1]
(tensor([[ 0, 1, 2, 3],
[ 4, 5, 6, 7],
[ 8, 9, 10, 11]]),
tensor([4, 5, 6, 7]),
tensor([ 8, 9, 10, 11]),
tensor(5))
如上是索引,以下为切片(注意以 ‘,’ 号隔开维度数, 如X[1,1]即索引2行2列的数据)
X = X.reshape(3,4)
X, X[1:2], X[:,:3]
(tensor([[ 0, 1, 2, 3],
[ 4, 5, 6, 7],
[ 8, 9, 10, 11]]),
tensor([[4, 5, 6, 7]]),
tensor([[ 0, 1, 2],
[ 4, 5, 6],
[ 8, 9, 10]]))
如果我们想为多个元素赋值相同的值,我们只需要索引所有元素,然后为它们赋值。
例如,[0:2, :]访问第1行和第2行,其中“:”代表沿轴1(列)的所有元素。
X[0:2, :] = 12
X
tensor([[12, 12, 12, 12],
[12, 12, 12, 12],
[ 8, 9, 10, 11]])
运行一些操作可能会导致为新结果分配内存。例如,如果我们用X1=X1+X2,我们将取消引用Y指向的张量,而是指向新分配的内存处的张量。
在下面的例子中,我们用Python的id()函数演示了这一点,它给我们提供了内存中引用对象的确切地址。运行X1=X1+X2后,我们会发现id(X1)指向另一个位置。
这是因为Python首先计算X1+X2,为结果分配新的内存,然后使X1指向内存中的这个新位置。
before = id(X1)
X1 = X1 + X2
id(X1) == before
False
这可能是不可取的,原因有两个:
1、首先,我们不想总是不必要地分配内存。 在机器学习中,我们可能有数百兆的参数,并且在一秒内多次更新所有参数。 通常情况下,我们希望原地执行这些更新。
2、其次,如果我们不原地更新,其他引用仍然会指向旧的内存位置, 这样我们的某些代码可能会无意中引用旧的参数。
幸运的是,执行原地操作非常简单。 我们可以使用切片表示法将操作的结果分配给先前分配的数组,例如 X1[:] = X1 + X2
before = id(X1)
X1[:] = X1 + X2
id(X1) == before
True
我们也可以创建一个新的矩阵X2,其形状与X1相同, 使用zeros_like()来分配一个全的块。
X2 = torch.zeros_like(X1)
X1,X2
(tensor([[0, 1, 2],
[5, 6, 7]]),
tensor([[0, 0, 0],
[0, 0, 0]]))
将深度学习框架定义的张量转换为NumPy张量(ndarray)很容易,反之也同样容易。
torch张量和numpy数组将共享它们的底层内存,就地操作更改一个张量也会同时更改另一个张量。
import numpy as np
Y = X.numpy() #X为tensor张量,Y为numpy数组
Z = torch.tensor(Y) #Z为tensor张量,Y为numpy数组
X, Y, Z, type(X), type(Y), type(Z) #分别输出他们的值及类型
(tensor([[12, 12, 12, 12],
[12, 12, 12, 12],
[ 8, 9, 10, 11]]),
array([[12, 12, 12, 12],
[12, 12, 12, 12],
[ 8, 9, 10, 11]], dtype=int64),
tensor([[12, 12, 12, 12],
[12, 12, 12, 12],
[ 8, 9, 10, 11]]),
torch.Tensor,
numpy.ndarray,
torch.Tensor)
要将大小为1的张量转换为Python标量,我们可以调用item函数或Python的内置函数。
X2 = X[1,1]
X2, X2.item(), float(X2), int(X2)
(tensor(12), 12, 12.0, 12)