d2l - 数据操作 Day_1

本文最后更新于:1 年前

跟李沐学AI的第一天

1.入门

首先,我们介绍n维数组,也称为张量(tensor)。⽆论使⽤哪个深度学习框架,它的张量类(在MXNet中为ndarray,在PyTorchTensorFlow中为Tensor)都与Numpyndarray类似。但深度学习框架⼜⽐Numpyndarray多⼀些重要功能:⾸先,GPU很好地⽀持加速计算,⽽NumPy仅⽀持CPU计算;其次,张量类⽀持⾃动微分。这些功能使得张量类更适合深度学习。如果没有特殊说明,本书中所说的张量均指的是张量类的实例。

⾸先,我们导⼊torch。请注意,虽然它被称为PyTorch,但是代码中使⽤torch⽽不是pytorch

1
import torch

张量表⽰⼀个由数值组成的数组,这个数组可能有多个维度。具有⼀个轴的张量对应数学上的向量(vector);

具有两个轴的张量对应数学上的矩阵(matrix);具有两个轴以上的张量没有特殊的数学名称。

⾸先,我们可以使⽤ arange 创建⼀个⾏向量 x。这个⾏向量包含以0开始的前12个整数,它们默认创建为整数。也可指定创建类型为浮点数。张量中的每个值都称为张量的 元素(element)。例如,张量 x 中有 12 个元素。除⾮额外指定,新的张量将存储在内存中,并采⽤基于CPU的计算。

1
x = torch.arange(12) # 创建一个有12个元素的行向量

我们可以通过张量的shape属性来访问张量(沿每个轴的长度)的形状。

1
2
x.shape  # 返回输入tensor张量的维度大小
x.size() # 跟torch.shape效果相同,也是返回输入tensor张量的维度大小。

如果只想知道张量中元素的总数,即形状的所有元素乘积,可以检查它的大小(size)。因为这里在处理的是一个向量,所以它的shape与它的size相同。

1
x.numel() # 返回输入张量中元素的总数

要想改变⼀个张量的形状⽽不改变元素数量和元素值,可以调⽤reshape函数。例如,可以把张量x从形状为(12,)的⾏向量转换为形状为(3,4)的矩阵。这个新的张量包含与转换前相同的值,但是它被看成⼀个34列的矩阵。要重点说明⼀下,虽然张量的形状发⽣了改变,但其元素值并没有变。注意,通过改变张量的形状,张量的⼤⼩不会改变。

1
X =x.reshape(3,4) # 返回将输入的形状转变为shape指定的形状大小,元素总数不变。

我们不需要通过⼿动指定每个维度来改变形状。也就是说,如果我们的⽬标形状是(⾼度,宽度),那么在知道宽度后,⾼度会被⾃动计算得出,不必我们⾃⼰做除法。在上⾯的例⼦中,为了获得⼀个3⾏的矩阵,我们⼿动指定了它有3⾏和4列。幸运的是,我们可以通过**-1来调⽤此⾃动计算出维度的功能。即我们可以⽤x.reshape(-1,4)x.reshape(3,-1)来取代x.reshape(3,4)**。

1
2
3
4
x.reshape(-1,4)
x.reshape(3.-1)
x.reshape(3,4)
#上面三个表达式效果相同

有时,我们希望使⽤全0、全1、其他常量,或者从特定分布中随机采样的数字来初始化矩阵。我们可以创建⼀个形状为(2,3,4)的张量,其中所有元素都设置为0。代码如下:

1
torch.zeros((2,3,4)) 

同样,我们可以创建⼀个形状为(2,3,4)的张量,其中所有元素都设置为1。代码如下:

1
torch.ones((2,3,4))

有时我们想通过从某个特定的概率分布中随机采样来得到张量中每个元素的值。例如,当我们构造数组来作为神经⽹络中的参数时,我们通常会随机初始化参数的值。以下代码创建⼀个形状为(3,4)的张量。其中的每个元素都从均值为0、标准差为1的标准⾼斯分布(正态分布)中随机采样。

1
torch.randn(3,4)

我们还可以通过提供包含数值的Python列表(或嵌套列表),来为所需张量中的每个元素赋予确定值。在这⾥,最外层的列表对应于轴0,内层的列表对应于轴1

1
torch.tensor([[2,1,3,4],[1,2,3,4],[4,3,2,1]])

2.运算符

1
2
3
4
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 # **运算符是求幂运算
torch.exp(x) #求幂

除了按元素计算外,我们还可以执⾏线性代数运算,包括向量点积和矩阵乘法。我们将在 2.3节中解释线性代数的重点内容。我们也可以把多个张量连结(concatenate)在⼀起,把它们端对端地叠起来形成⼀个更⼤的张量。我们只需要提供张量列表,并给出沿哪个轴连结。下⾯的例⼦分别演⽰了当我们沿⾏(轴-0形状的第⼀个元素)和按 列(轴-1形状的第⼆个元素)连结两个矩阵时,会发⽣什么情况。我们可以看到,第⼀个输出张量的轴-0⻓度(6)是两个输⼊张量轴-0⻓度的总和(3 + 3);第⼆个输出张量的轴-1⻓度(8)是两个输⼊张量轴-1⻓度的总和(4 + 4)。

1
2
3
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)

有时,我们想通过逻辑运算符构建⼆元张量。以X == Y为例:对于每个位置,如果XY在该位置相等,则新张量中相应项的值为1。这意味着逻辑语句X == Y在该位置处为真,否则该位置为0

1
2
3
4
5
X == Y
OUT:
tensor([[False, True, False, True],
[False, False, False, False],
[False, False, False, False]])

对张量中的所有元素进⾏求和,会产⽣⼀个单元素张量。

1
2
3
X.sum()
OUT:
tensor(66.)

3.广播机制

在上⾯的部分中,我们看到了如何在相同形状的两个张量上执⾏按元素操作。在某些情况下,即使形状不同,

我们仍然可以通过调⽤ ⼴播机制(broadcasting mechanism)来执⾏按元素操作。这种机制的⼯作⽅式如

下:

  • 通过适当复制元素来扩展⼀个或两个数组,以便在转换之后,两个张量具有相同的形状;

  • 对⽣成的数组执⾏按元素操作。

在⼤多数情况下,我们将沿着数组中⻓度为1的轴进⾏⼴播,如下例⼦:

1
2
3
4
5
6
7
8
a = torch.arange(3).reshape((3, 1))
b = torch.arange(2).reshape((1, 2))
a, b
OUT:
(tensor([[0],
[1],
[2]]),
tensor([[0, 1]]))

由于a和b分别是3 × 1和1 × 2矩阵,如果让它们相加,它们的形状不匹配。我们将两个矩阵⼴播为⼀个更⼤的3 × 2矩阵,如下所⽰:矩阵a将复制列,矩阵b将复制⾏,然后再按元素相加。 注意:自己复制自己

4.索引和切片

就像在任何其他Python数组中⼀样,张量中的元素可以通过索引访问。与任何Python数组⼀样:第⼀个元素的索引是0,最后⼀个元素索引是**-1**;可以指定范围以包含第⼀个元素和最后⼀个之前的元素。

如下所⽰,我们可以⽤**[-1]选择最后⼀个元素,可以⽤[1:3]**选择第⼆个和第三个元素:

1
X[-1],X[1:3]  #X[1:3]  默认

除读取外,我们还可以通过指定索引来将元素写⼊矩阵

1
X[1,2] = 9 #第二行第三个修改成9

如果我们想为多个元素赋值相同的值,我们只需要索引所有元素,然后为它们赋值。例如,**[0:2, :]访问第1⾏和第2⾏,其中“:”代表沿轴1(列)的所有元素。虽然我们讨论的是矩阵的索引,但这也适⽤于向量和超过2个维度的张量**。

1
X[0:2, :] = 12

5.节省内存

运⾏⼀些操作可能会导致为新结果分配内存。例如,如果我们⽤Y = X + Y,我们将取消引⽤Y指向的张量,

⽽是指向新分配的内存处的张量。

在下⾯的例⼦中,我们⽤Python的id()函数演⽰了这⼀点,它给我们提供了内存中引⽤对象的确切地址。运

⾏Y = Y + X后,我们会发现id(Y)指向另⼀个位置。这是因为Python⾸先计算Y + X,为结果分配新的内存,

然后使Y指向内存中的这个新位置。

1
2
3
before = id(Y)
Y = Y + X
id(Y) == before

这可能是不可取的,原因有两个:

  • ⾸先,我们不想总是不必要地分配内存。在机器学习中,我们可能有数百兆的参数,并且在⼀秒内多次

更新所有参数。通常情况下,我们希望原地执⾏这些更新

  • 如果我们不原地更新,其他引⽤仍然会指向旧的内存位置,这样我们的某些代码可能会⽆意中引⽤旧

的参数。

幸运的是,执⾏原地操作⾮常简单。我们可以使⽤切⽚表⽰法将操作的结果分配给先前分配的数组,例如Y[:]

= 。为了说明这⼀点,我们⾸先创建⼀个新的矩阵Z,其形状与另⼀个Y相同,使⽤zeros_like

分配⼀个全0的块。

1
2
3
4
5
6
7
Z = torch.zeros_like(Y)
print('id(Z):', id(Z))
Z[:] = X + Y
print('id(Z):', id(Z))
OUT:
id(Z): 139931132035296
id(Z): 139931132035296

如果在后续计算中没有重复使⽤X,我们也可以使⽤X[:] = X + YX += Y来减少操作的内存开销。

1
2
3
before = id(X)
X += Y
id(X) == before

6.转换为Python其他对象

将深度学习框架定义的张量转换为NumPy张量(ndarray)很容易,反之也同样容易。torch张量和numpy

组将共享它们的底层内存,就地操作更改⼀个张量也会同时更改另⼀个张量。

1
2
3
A = X.numpy()
B = torch.tensor(A)
type(A), type(B)

要将⼤⼩为1的张量转换为Python标量,我们可以调⽤item函数Python的内置函数

1
2
3
4
a = torch.tensor([3.5])
a, a.item(), float(a), int(a)
OUT:
(tensor([3.5000]), 3.5, 3.5, 3)

⼩结

深度学习存储和操作数据的主要接⼝是张量(n维数组)。它提供了各种功能,包括基本数学运算、⼴播、索引、切⽚、内存节省和转换其他Python对象。

练习

  • 运⾏本节中的代码。将本节中的条件语句X == Y更改为X < Y或X > Y,然后看看你可以得到什么样的

张量。

​ 答:可以当同一位置相等时,输出为False,其他按照比较运算符规则来输出。

  • ⽤其他形状(例如三维张量)替换⼴播机制中按元素操作的两个张量。结果是否与预期相同?

    答:输出结果如下:

    1
    2
    3
    4
    5
    6
    7
    8
    tensor([[[0],
    [1]],

    [[1],
    [2]],

    [[2],
    [3]]])

d2l - 数据操作 Day_1
https://wlpswmt.github.io/2023/03/14/d2l-数据操作/
作者
Sivan Zhang
发布于
2023年3月14日
许可协议