安装
我们需要配置一个环境来运行 Python、Jupyter Notebook、相关库以及运行本书所需的代码,以快速入门并获得动手学习经验。
安装 Miniconda
最简单的方法就是安装依赖 Python 3.x 的 Miniconda。如果已安装 conda,则可以跳过以下步骤。 从网站下载相应的 Miniconda sh 文件,然后使用 sh
对于 macOS 用户:
文件名可能会更改
sh Miniconda3-latest-MacOSX-x86_64.sh -b
Copy to clipboard
对于 Linux 用户:
文件名可能会更改
sh Miniconda3-latest-Linux-x86_64.sh -b
Copy to clipboard
接下来,初始化终端 Shell,以便我们可以直接运行 conda。
~/miniconda3/bin/conda init
Copy to clipboard
现在关闭并重新打开当前的 shell。你应该能用下面的命令创建一个新的环境:
conda create --name d2l python=3.8 -y
Copy to clipboard
下载 D2L Notebook
接下来,需要下载这本书的代码。你可以点击任何 HTML 页面顶部的 “Jupyter 记事本文件” 选项下载后解压代码。或者可以按照如下方式进行下载:
mkdir d2l-zh && cd d2l-zh
curl https://zh-v2.d2l.ai/d2l-zh.zip -o d2l-zh.zip
unzip d2l-zh.zip && rm d2l-zh.zip
Copy to clipboard
注意:如果没有安装 unzip,则可以通过运行 sudo apt install unzip 进行安装。
现在我们要激活 d2l 环境。
conda activate d2l
Copy to clipboard
安装框架和 d2l 软件包
在安装深度学习框架之前,请先检查你的计算机上是否有可用的 GPU(在笔记本电脑上为显示器提供输出的GPU不算)。如果要在 GPU 机器上安装,请继续在 GPU 支持 获取有关安装GPU支持版本的说明。
或者,你可以按照如下方法安装CPU版本。这将足够帮助你完成前几章,但你需要在运行更大模型之前获取GPU。
MXNET
PYTORCH
TENSORFLOW
pip install mxnet==1.7.0.post1
Copy to clipboard
你还需要安装 d2l 软件包,它封装了本书中常用的函数和类。
-U:将所有包升级到最新的可用版本
pip install -U d2l
Copy to clipboard
安装完成后,我们通过运行以下命令打开 Jupyter 笔记本:
jupyter notebook
Copy to clipboard
现在,你可以在 Web 浏览器中打开 http://localhost:8888(通常会自动打开)。然后我们可以运行这本书中每个部分的代码。在运行书籍代码、更新深度学习框架或 d2l 软件包之前,请始终执行 conda activate d2l 以激活运行时环境。要退出环境,请运行 conda deactivate。
GPU 支持
MXNET
PYTORCH
TENSORFLOW
默认情况下,安装的MXNet不支持GPU。这可以确保它在任何计算机(包括大多数笔记本电脑)上运行。本书的部分内容建议或要求使用 GPU 运行。如果你的计算机带有 NVIDIA 显卡并且已安装 CUDA,则应安装支持 GPU 的版本。如果你已经安装了仅支持 CPU 版本,则可能需要先通过运行以下命令将其删除:
pip uninstall mxnet
Copy to clipboard
然后,我们需要找到安装的 CUDA 版本。你可以通过 nvcc --version 或 cat /usr/local/cuda/version.txt 查看。假设你已安装 CUDA 10.1,则可以使用以下命令进行安装:
对于 Windows 用户:
pip install mxnet-cu101==1.7.0 -f https://dist.mxnet.io/python
对于 Linux 和 macOS 用户:
pip install mxnet-cu101==1.7.0
Copy to clipboard
你可以根据你的 CUDA 版本更改最后一位数字,例如:CUDA 10.0 是 cu100, CUDA 9.0 是 cu90。
2.1. 数据操作
COLAB [PYTORCH]Open the notebook in Colab
为了能够完成各种操作,我们需要某种方法来存储和操作数据。一般来说,我们需要做两件重要的事情:(1)获取数据;(2)在数据读入计算机后对其进行处理。如果没有某种方法来存储数据,那么获取数据是没有意义的。我们先尝试一个合成数据。首先,我们介绍(n)维数组,也称为 张量(tensor)。
如果你使用过 Python 中最广泛使用的科学计算包 NumPy,那么你会感觉怼本部分很熟悉。无论你使用哪个框架,它的 张量类(在 MXNet 中为 ndarray,在 PyTorch 和TensorFlow中为 Tensor)与 Numpy 的 ndarray 类似,但都比Numpy 的 ndarray多一些重要功能。首先,GPU 很好地支持加速计算,而 NumPy 仅支持 CPU 计算。其次,张量类支持自动微分。这些功能使得张量类更适合深度学习。除非另有说明,在整本书中所说的张量指的是张量类的实例。
2.1.1. 入门
在本节中,我们的目标是帮助你开始了解并运行一些基本数值计算工具。在你阅读本书的过程中,将用到这些工具。如果你很难理解一些数学概念或库函数,请不要担心。在后面的章节将通过一些实际的例子来回顾这些内容。如果你已经有了一些背景知识,想要深入学习数学内容,可以就跳过这一节。
MXNET
PYTORCH
TENSORFLOW
首先,我们导入 torch。请注意,虽然它被称为PyTorch,但我们应该导入 torch 而不是 pytorch。
import torch
Copy to clipboard
张量表示一个数值组成的数组,这个数组可能有多个维度。具有一个轴的张量对应于数学上的 向量(vector)。具有两个轴的张量对应于数学上的 矩阵(matrix)。具有两个轴以上的张量没有特殊的数学名称。
首先,我们可以使用 arange 创建一个行向量 x。这个行向量包含以0开始的前12个整数,它们默认创建为浮点数。张量中的每个值都称为张量的 元素(element)。例如,张量 x 中有 12 个元素。除非额外指定,新的张量将存储在内存中,并采用基于CPU的计算。
MXNET
PYTORCH
TENSORFLOW
x = torch.arange(12)
x
Copy to clipboard
tensor([ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11])
我们可以通过张量的 shape 属性来访问张量的 形状 (沿每个轴的长度)。
MXNET
PYTORCH
TENSORFLOW
x.shape
Copy to clipboard
torch.Size([12])
如果我们只想知道张量中元素的总数,即形状的所有元素乘积,我们可以检查它的大小(size)。 因为这里在处理的是一个向量,所以它的 shape 与它的 size 相同。
MXNET
PYTORCH
TENSORFLOW
x.numel()
Copy to clipboard
12
要改变一个张量的形状而不改变元素数量和元素值,我们可以调用 reshape 函数。 例如,我们可以把张量 x 从形状为 (12, ) 的行向量转换为形状 (3, 4) 的矩阵。这个新的张量包含与转换前相同的值,但是把它们看成一个三行四列的矩阵。要重点说明一下,虽然形状发生了改变,但元素值没有变。注意,通过改变张量的形状,张量的大小不会改变。
MXNET
PYTORCH
TENSORFLOW
X = x.reshape(3, 4)
X
Copy to clipboard
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。代码如下:
MXNET
PYTORCH
TENSORFLOW
torch.zeros((2, 3, 4))
Copy to clipboard
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.]]])
同样的,我们可以创建一个张量,其中所有元素都设置为1。代码如下:
MXNET
PYTORCH
TENSORFLOW
torch.ones((2, 3, 4))
Copy to clipboard
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的标准高斯(正态)分布中随机采样。
MXNET
PYTORCH
TENSORFLOW
torch.randn(3, 4)
Copy to clipboard
tensor([[ 0.6624, 0.6089, 2.1315, -0.4236],
[ 0.2953, -0.0027, -1.6279, -1.0125],
[-0.2773, 0.5857, 0.2215, 1.7032]])
我们还可以通过提供包含数值的 Python 列表(或嵌套列表)来为所需张量中的每个元素赋予确定值。在这里,最外层的列表对应于轴 0,内层的列表对应于轴 1。
MXNET
PYTORCH
TENSORFLOW
torch.tensor([[2, 1, 4, 3], [1, 2, 3, 4], [4, 3, 2, 1]])
Copy to clipboard
tensor([[2, 1, 4, 3],
[1, 2, 3, 4],
[4, 3, 2, 1]])
2.1.2. 运算
这本书不是关于软件工程的。我们的兴趣不仅仅限于从数组读取和写入数据。我们想在这些数组上执行数学运算。一些最简单且最有用的操作是 按元素(elementwise) 操作。它们将标准标量运算符应用于数组的每个元素。对于将两个数组作为输入的函数,按元素运算将二元运算符应用于两个数组中的每对位置对应的元素。我们可以基于任何从标量到标量的函数来创建按元素函数。
在数学表示法中,我们将通过符号 (f: \mathbb{R} \rightarrow \mathbb{R}) 来表示 一元 标量运算符(只接收一个输入)。这意味着该函数从任何实数((\mathbb{R}))映射到另一个实数。同样,我们通过符号 (f: \mathbb{R}, \mathbb{R} \rightarrow \mathbb{R}) 表示 二元 标量运算符,这意味着该函数接收两个输入,并产生一个输出。给定同一形状的任意两个向量(\mathbf{u})和(\mathbf{v}) 和二元运算符 (f),我们可以得到向量(\mathbf{c} = F(\mathbf{u},\mathbf{v}))。具体计算方法是(c_i \gets f(u_i, v_i)) ,其中 (c_i)、u_i$ 和 (v_i) 分别是向量(\mathbf{c})、(\mathbf{u}) 和 (\mathbf{v})中的元素。在这里,我们通过将标量函数升级为按元素向量运算来生成向量值 (F: \mathbb{R}^d, \mathbb{R}^d \rightarrow \mathbb{R}^d)。
对于任意具有相同形状的张量,常见的标准算术运算符(+、-、*、/ 和 **)都可以被升级为按元素运算。我们可以在同一形状的任意两个张量上调用按元素操作。在下面的例子中,我们使用逗号来表示一个具有5个元素的元组,其中每个元素都是按元素操作的结果。
MXNET
PYTORCH
TENSORFLOW
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 # **运算符是求幂运算
Copy to clipboard
(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.]))
可以按按元素方式应用更多的计算,包括像求幂这样的一元运算符。
MXNET
PYTORCH
TENSORFLOW
torch.exp(x)
Copy to clipboard
tensor([2.7183e+00, 7.3891e+00, 5.4598e+01, 2.9810e+03])
除了按元素计算外,我们还可以执行线性代数运算,包括向量点积和矩阵乘法。我们将在 2.3节 中解释线性代数的重点内容(不需要先修知识)。
我们也可以把多个张量连结在一起,把它们端对端地叠起来形成一个更大的张量。 我们也可以 连结(concatenate) 多个张量在一起,将它们端到端堆叠以形成更大的张量。我们只需要提供张量列表,并给出沿哪个轴连结。下面的例子分别演示了当我们沿行(轴-0,形状的第一个元素)和按列(轴-1,形状的第二个元素)连结两个矩阵时会发生什么情况。我们可以看到,第一个输出张量的轴-0长度 ((6)) 是两个输入张量轴-0长度的总和 ((3 + 3));第二个输出张量的轴-1长度 ((8)) 是两个输入张量轴-1长度的总和 ((4 + 4))。
MXNET
PYTORCH
TENSORFLOW
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)
Copy to clipboard
(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。
MXNET
PYTORCH
TENSORFLOW
X == Y
Copy to clipboard
tensor([[False, True, False, True],
[False, False, False, False],
[False, False, False, False]])
对张量中的所有元素进行求和会产生一个只有一个元素的张量。
MXNET
PYTORCH
TENSORFLOW
X.sum()
Copy to clipboard
tensor(66.)
2.1.3. 广播机制
在上面的部分中,我们看到了如何在相同形状的两个张量上执行按元素操作。在某些情况下,即使形状不同,我们仍然可以通过调用 广播机制 (broadcasting mechanism) 来执行按元素操作。这种机制的工作方式如下:首先,通过适当复制元素来扩展一个或两个数组,以便在转换之后,两个张量具有相同的形状。其次,对生成的数组执行按元素操作。
在大多数情况下,我们将沿着数组中长度为1的轴进行广播,如下例子:
MXNET
PYTORCH
TENSORFLOW
a = torch.arange(3).reshape((3, 1))
b = torch.arange(2).reshape((1, 2))
a, b
Copy to clipboard
(tensor([[0],
[1],
[2]]),
tensor([[0, 1]]))
由于 a 和 b 分别是 (3\times1) 和 (1\times2) 矩阵,如果我们让它们相加,它们的形状不匹配。我们将两个矩阵广播为一个更大的 (3\times2) 矩阵,如下所示:矩阵 a将复制列,矩阵 b将复制行,然后再按元素相加。
MXNET
PYTORCH
TENSORFLOW
a + b
Copy to clipboard
tensor([[0, 1],
[1, 2],
[2, 3]])
2.1.4. 索引和切片
就像在任何其他 Python 数组中一样,张量中的元素可以通过索引访问。与任何 Python 数组一样:第一个元素的索引是 0;可以指定范围以包含第一个元素和最后一个之前的元素。与标准 Python 列表一样,我们可以通过使用负索引根据元素到列表尾部的相对位置访问元素。
因此,我们可以用 [-1] 选择最后一个元素,可以用 [1:3] 选择第二个和第三个元素,如下所示:
MXNET
PYTORCH
TENSORFLOW
X[-1], X[1:3]
Copy to clipboard
(tensor([ 8., 9., 10., 11.]),
tensor([[ 4., 5., 6., 7.],
[ 8., 9., 10., 11.]]))
除读取外,我们还可以通过指定索引来将元素写入矩阵。
X[1, 2] = 9
X
Copy to clipboard
tensor([[ 0., 1., 2., 3.],
[ 4., 5., 9., 7.],
[ 8., 9., 10., 11.]])
如果我们想为多个元素赋值相同的值,我们只需要索引所有元素,然后为它们赋值。 例如,[0:2, :] 访问第1行和第2行,其中 “:” 代表沿轴 1(列)的所有元素。虽然我们讨论的是矩阵的索引,但这也适用于向量和超过2个维度的张量。
MXNET
PYTORCH
TENSORFLOW
X[0:2, :] = 12
X
Copy to clipboard
tensor([[12., 12., 12., 12.],
[12., 12., 12., 12.],
[ 8., 9., 10., 11.]])
2.1.5. 节省内存
运行一些操作可能会导致为新结果分配内存。例如,如果我们用 Y = X + Y,我们将取消引用 Y 指向的张量,而是指向新分配的内存处的张量。
在下面的例子中,我们用 Python 的 id() 函数演示了这一点,它给我们提供了内存中引用对象的确切地址。运行 Y = Y + X 后,我们会发现 id(Y) 指向另一个位置。这是因为 Python 首先计算 Y + X,为结果分配新的内存,然后使 Y 指向内存中的这个新位置。
MXNET
PYTORCH
TENSORFLOW
before = id(Y)
Y = Y + X
id(Y) == before
Copy to clipboard
False
这可能是不可取的,原因有两个:首先,我们不想总是不必要地分配内存。在机器学习中,我们可能有数百兆的参数,并且在一秒内多次更新所有参数。通常情况下,我们希望原地执行这些更新。其次,我们可能通过多个变量指向相同参数。如果我们不原地更新,其他引用仍然会指向旧的内存位置,这样我们的某些代码可能会无意中引用旧的参数。
MXNET
PYTORCH
TENSORFLOW
幸运的是,执行原地操作非常简单。我们可以使用切片表示法将操作的结果分配给先前分配的数组,例如 Y[:] =
Z = torch.zeros_like(Y)
print('id(Z):', id(Z))
Z[:] = X + Y
print('id(Z):', id(Z))
Copy to clipboard
id(Z): 139621644254592
id(Z): 139621644254592
如果在后续计算中没有重复使用 X,我们也可以使用 X[:] = X + Y 或 X += Y 来减少操作的内存开销。
before = id(X)
X += Y
id(X) == before
Copy to clipboard
True
2.1.6. 转换为其他 Python 对象
转换为 NumPy 张量很容易,反之也很容易。转换后的结果不共享内存。 这个小的不便实际上是非常重要的:当你在 CPU 或 GPU 上执行操作的时候,此时Python的NumPy包也希望使用相同的内存块执行其他操作时,你不希望停止计算。
MXNET
PYTORCH
TENSORFLOW
A = X.numpy()
B = torch.tensor(A)
type(A), type(B)
Copy to clipboard
(numpy.ndarray, torch.Tensor)
要将大小为1的张量转换为 Python 标量,我们可以调用 item 函数或 Python 的内置函数。
MXNET
PYTORCH
TENSORFLOW
a = torch.tensor([3.5])
a, a.item(), float(a), int(a)
Copy to clipboard
(tensor([3.5000]), 3.5, 3.5, 3)
2.1.7. 小结
深度学习存储和操作数据的主要接口是张量((n)维数组)。它提供了各种功能,包括基本数学运算、广播、索引、切片、内存节省和转换其他 Python 对象。
2.1.8. 练习
运行本节中的代码。将本节中的条件语句 X == Y 更改为 X < Y 或 X > Y,然后看看你可以得到什么样的张量。
用其他形状(例如三维张量)替换广播机制中按元素操作的两个张量。结果是否与预期相同?