1、数据操作
在深度学习的模型训练中,为了更好的完成各种数据操作,我们需要采取某种方法来存储和操作数据。
通常,我们需要做两件重要的事:
(1)获取数据,通常为数据挖掘过程;
(2)将数据读入计算机后对其进行处理。 如果没有某种方法来存储数据,那么获取数据是没有意义的。
在本文中我们介绍n维数组,也称为张量(tensor)。 使用过Python中Numpy包的读者会对本部分很熟悉。 无论使用哪个深度学习框架,它的张量类( 在PyTorch和TensorFlow中为Tensor
)都与Numpy的ndarray
类似。 但在深度学习框架(pytorch)中又比Numpy的ndarray
多一些重要功能:
首先,GPU很好地支持加速计算,而Numpy仅支持CPU计算; 其次,张量类支持自动微分。 这些更加实的用功能使得张量类更适合深度学习。 在本文中如果没有特殊说明,本文中所提到的的张量均指的是张量类的实例。
1.1入门
首先,我们导入torch
。请注意,虽然它被称为PyTorch,但是代码中使用torch
而不是pytorch
。
import torch
张量表示一个由数值组成的数组,这个数组可能有多个维度。 具有一个轴的张量对应数学上的向量(vector); 具有两个轴的张量对应数学上的矩阵(matrix); 具有两个轴以上的张量没有特殊的数学名称。
首先,我们可以使用 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])
如果只想知道张量中元素的总数,即形状的所有元素乘积,可以检查它的大小(size)。 因为这里在处理的是一个向量,所以它的shape
与它的size
相同。
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)
。
有时,我们希望使用全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.]]])
1.2运算符
我们的工作不仅限于读取数据和写入数据。 我们想在这些数据上执行数学运算,其中最简单且最有用的操作是按元素(elementwise)运算。 它们将标准标量运算符应用于数组的每个元素。 对于将两个数组作为输入的函数,按元素运算将二元运算符应用于两个数组中的每对位置对应的元素。 我们可以基于任何从标量到标量的函数来创建按元素函数。
对于任意具有相同形状的张量, 常见的标准算术运算符(+
、-
、*
、/
和**
)都可以被升级为按元素运算。 我们可以在同一形状的任意两个张量上调用按元素操作。 在下面的例子中,我们使用逗号来表示一个具有5个元素的元组,其中每个元素都是按元素操作的结果。
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([[ 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.]]))
有时,我们想通过逻辑运算符构建二元张量。 以X == Y
为例: 对于每个位置,如果X
和Y
在该位置相等,则新张量中相应项的值为1。 这意味着逻辑语句X == Y
在该位置处为真,否则该位置为0。
X == Y
tensor([[False, True, False, True],
[False, False, False, False],
[False, False, False, False]])
对张量中的所有元素进行求和,会产生一个单元素张量。
X.sum()
tensor(66.)
1.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[1, 2] = 9
X
tensor([[ 0., 1., 2., 3.],
[ 4., 5., 9., 7.],
[ 8., 9., 10., 11.]])
如果我们想为多个元素赋值相同的值,我们只需要索引所有元素,然后为它们赋值。 例如,[0:2, :]
访问第1行和第2行,其中“:”代表沿轴1(列)的所有元素。 虽然我们讨论的是矩阵的索引,但这也适用于向量和超过2个维度的张量。
X[0:2, :] = 12
X
tensor([[12., 12., 12., 12.],
[12., 12., 12., 12.],
[ 8., 9., 10., 11.]])
1.4广播机制
在某些情况下,即使形状不同,我们仍然可以通过调用 广播机制(broadcasting mechanism)来执行按元素操作。 这种机制的工作方式如下:
-
通过适当复制元素来扩展一个或两个数组,以便在转换之后,两个张量具有相同的形状;
-
对生成的数组执行按元素操作。
在大多数情况下,我们将沿着数组中长度为1的轴进行广播,如下例子:
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
分别是3乘1和1乘2矩阵,如果让它们相加,它们的形状不匹配。 我们将两个矩阵广播为一个更大的矩阵,如下所示:矩阵a
将复制列, 矩阵b
将复制行,然后再按元素相加。
a + b
tensor([[0, 1],
[1, 2],
[2, 3]])
1.5 转换为其他Python对象
将深度学习框架定义的张量转换为Numpy张量(ndarray
)很容易,反之也同样容易。 torch张量和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)