diff --git "a/Dive-into-DL-paddlepaddle/docs/2_Preparatory-knowledge/2.1\346\225\260\346\215\256\346\223\215\344\275\234.md" "b/Dive-into-DL-paddlepaddle/docs/2_Preparatory-knowledge/2.1\346\225\260\346\215\256\346\223\215\344\275\234.md" new file mode 100644 index 000000000..85eb15972 --- /dev/null +++ "b/Dive-into-DL-paddlepaddle/docs/2_Preparatory-knowledge/2.1\346\225\260\346\215\256\346\223\215\344\275\234.md" @@ -0,0 +1,275 @@ +# 数据操作 +:label:`sec_ndarray` + +为了能够完成各种操作,我们需要某种方法来存储和操作数据。一般来说,我们需要做两件重要的事情:(1)获取数据;(2)在数据读入计算机后对其进行处理。如果没有某种方法来存储数据,那么获取数据是没有意义的。我们先尝试一个合成数据。首先,我们介绍$n$维数组,也称为 *张量*(tensor)。 + +如果你使用过 Python 中最广泛使用的科学计算包 NumPy,那么你会感觉对本部分很熟悉。无论你使用哪个框架,它的 *张量类*(在 MXNet 中为 `ndarray`,在 Paddle中为 `Tensor`)与 Numpy 的 `ndarray` 类似,但都比Numpy 的 `ndarray`多一些重要功能。首先,GPU 很好地支持加速计算,而 NumPy 仅支持 CPU 计算。其次,张量类支持自动微分。这些功能使得张量类更适合深度学习。除非另有说明,在整本书中所说的张量指的是张量类的实例。 + + +## 入门 + +在本节中,我们的目标是帮助你开始了解并运行一些基本数值计算工具。在你阅读本书的过程中,将用到这些工具。如果你很难理解一些数学概念或库函数,请不要担心。在后面的章节将通过一些实际的例子来回顾这些内容。如果你已经有了一些背景知识,想要深入学习数学内容,可以就跳过这一节。 + + +(**首先,我们导入 `paddle`。**) + + + +```python +import paddle +``` + +[**张量表示一个数值组成的数组,这个数组可能有多个维度**]。具有一个轴的张量对应于数学上的 *向量*(vector)。具有两个轴的张量对应于数学上的 *矩阵*(matrix)。具有两个轴以上的张量没有特殊的数学名称。 + +首先,我们可以使用 `arange` 创建一个行向量 `x`。这个行向量包含以0开始的前12个整数,它们默认创建为浮点数。张量中的每个值都称为张量的 *元素*(element)。例如,张量 `x` 中有 12 个元素。除非额外指定,新的张量将存储在内存中,并采用基于CPU的计算。 + + + +```python +x = paddle.arange(12) +x +``` + +[**我们可以通过张量的 `shape` 属性来访问张量的 *形状***] (~~和张量中元素的总数~~)(沿每个轴的长度)。 + + + +```python +x.shape +``` + +如果我们只想知道张量中元素的总数,即形状的所有元素乘积,我们可以检查它的大小(size)。 +因为这里在处理的是一个向量,所以它的 `shape` 与它的 `size` 相同。 + + + +```python +x.numel() +``` + +[**要改变一个张量的形状而不改变元素数量和元素值,我们可以调用 `paddle.reshape` 函数。**] +例如,我们可以把张量 `x` 从形状为 (12, ) 的行向量转换为形状 (3, 4) 的矩阵。这个新的张量包含与转换前相同的值,但是把它们看成一个三行四列的矩阵。要重点说明一下,虽然形状发生了改变,但元素值没有变。注意,通过改变张量的形状,张量的大小不会改变。 + + + +```python +X = paddle.reshape(x, (3, 4)) +X +``` + +不需要通过手动指定每个维度来改变形状。 +如果我们的目标形状是 (高度, 宽度) ,那么在知道宽度后,高度应当会隐式得出,我们不必自己做除法。在上面的例子中,要获得一个有3行的矩阵,我们手动指定了它有3行和4列。幸运的是,张量在给出其他部分后可以自动计算出一个维度。我们可以通过将希望张量自动推断的维度放置 `-1` 来调用此功能。在上面的例子中,我们可以用 `paddle.reshape(x, (-1, 4))` 。 + + +有时,我们希望[**使用全0、全1、其他常量或者从特定分布中随机采样的数字**],来初始化矩阵。我们可以创建一个形状为 (2, 3, 4) 的张量,其中所有元素都设置为0。代码如下: + + + +```python +paddle.zeros((2, 3, 4)) +``` + +同样的,我们可以创建一个张量,其中所有元素都设置为1。代码如下: + + + +```python +paddle.ones((2, 3, 4)) +``` + +有时我们想从某个概率分布中随机采样来得到张量中每个元素的值。例如,当我们构造数组来作为神经网络中的参数时,我们通常会随机初始化参数的值。以下代码创建一个形状为 (3, 4) 的张量。其中的每个元素都从均值为0、标准差为1的标准高斯(正态)分布中随机采样。 + + + +```python +paddle.randn((3, 4),'float32') +``` + +我们还可以[**通过提供包含数值的 Python 列表(或嵌套列表)来为所需张量中的每个元素赋予确定值**]。在这里,最外层的列表对应于轴 0,内层的列表对应于轴 1。 + + + +```python +paddle.to_tensor([[2, 1, 4, 3], [1, 2, 3, 4], [4, 3, 2, 1]]) +``` + +## 运算 + +这本书不是关于软件工程的。我们的兴趣不仅仅限于从数组读取和写入数据。我们想在这些数组上执行数学运算。一些最简单且最有用的操作是 *按元素*(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个元素的元组,其中每个元素都是按元素操作的结果。 + + + +```python +x = paddle.to_tensor([1.0, 2, 4, 8]) +y = paddle.to_tensor([2, 2, 2, 2]) +x + y, x - y, x * y, x / y, x**y # **运算符是求幂运算 +``` + +可以(**按按元素方式应用更多的计算**),包括像求幂这样的一元运算符。 + + + +```python +paddle.exp(x) +``` + +除了按元素计算外,我们还可以执行线性代数运算,包括向量点积和矩阵乘法。我们将在 :numref:`sec_linear-algebra` 中解释线性代数的重点内容(不需要先修知识)。 + +[**我们也可以把多个张量 *连结*(concatenate) 在一起**],把它们端对端地叠起来形成一个更大的张量。 +我们只需要提供张量列表,并给出沿哪个轴连结。下面的例子分别演示了当我们沿行(轴-0,形状的第一个元素)和按列(轴-1,形状的第二个元素)连结两个矩阵时会发生什么情况。我们可以看到,第一个输出张量的轴-0长度 ($6$) 是两个输入张量轴-0长度的总和 ($3 + 3$);第二个输出张量的轴-1长度 ($8$) 是两个输入张量轴-1长度的总和 ($4 + 4$)。 + + + +```python +X = paddle.arange(12, dtype='float32').reshape((3, 4)) +Y = paddle.to_tensor([[2.0, 1, 4, 3], [1, 2, 3, 4], [4, 3, 2, 1]]) +paddle.concat((X, Y), axis=0), paddle.concat((X, Y), axis=1) +``` + +有时,我们想[**通过 *逻辑运算符* 构建二元张量**]。以 `X == Y` 为例子。 +对于每个位置,如果 `X` 和 `Y` 在该位置相等,则新张量中相应项的值为1,这意味着逻辑语句 `X == Y` 在该位置处为真,否则该位置为 0。 + + + +```python +X == Y +``` + +[**对张量中的所有元素进行求和会产生一个只有一个元素的张量。**] + + + +```python +X.sum() +``` + +## 广播机制 +:label:`subsec_broadcasting` + +在上面的部分中,我们看到了如何在相同形状的两个张量上执行按元素操作。在某些情况下,[**即使形状不同,我们仍然可以通过调用 *广播机制* (broadcasting mechanism) 来执行按元素操作**]。这种机制的工作方式如下:首先,通过适当复制元素来扩展一个或两个数组,以便在转换之后,两个张量具有相同的形状。其次,对生成的数组执行按元素操作。 + +在大多数情况下,我们将沿着数组中长度为1的轴进行广播,如下例子: + + + +```python +a = paddle.reshape(paddle.arange(3), (3, 1)) +b = paddle.reshape(paddle.arange(2), (1, 2)) +a, b +``` + +由于 `a` 和 `b` 分别是 $3\times1$ 和 $1\times2$ 矩阵,如果我们让它们相加,它们的形状不匹配。我们将两个矩阵*广播*为一个更大的 $3\times2$ 矩阵,如下所示:矩阵 `a`将复制列,矩阵 `b`将复制行,然后再按元素相加。 + + + +```python +a + b +``` + +## 索引和切片 + +就像在任何其他 Python 数组中一样,张量中的元素可以通过索引访问。与任何 Python 数组一样:第一个元素的索引是 0;可以指定范围以包含第一个元素和最后一个之前的元素。与标准 Python 列表一样,我们可以通过使用负索引根据元素到列表尾部的相对位置访问元素。 + +因此,我们[**可以用 `[-1]` 选择最后一个元素,可以用 `[1:3]` 选择第二个和第三个元素**],如下所示: + + + +```python +X[-1], X[1:3] +``` + +[**除读取外,我们还可以通过指定索引来将元素写入矩阵。**] + + + +```python +X[1, 2] = 9 +X +``` + +如果我们想[**为多个元素赋值相同的值,我们只需要索引所有元素,然后为它们赋值。**] +例如,`[0:2, :]` 访问第1行和第2行,其中 “:” 代表沿轴 1(列)的所有元素。虽然我们讨论的是矩阵的索引,但这也适用于向量和超过2个维度的张量。 + + + +```python +X[0:2, :] = 12 +X +``` + +## 节省内存 + +[**运行一些操作可能会导致为新结果分配内存**]。例如,如果我们用 `Y = X + Y`,我们将取消引用 `Y` 指向的张量,而是指向新分配的内存处的张量。 + +在下面的例子中,我们用 Python 的 `id()` 函数演示了这一点,它给我们提供了内存中引用对象的确切地址。运行 `Y = Y + X` 后,我们会发现 `id(Y)` 指向另一个位置。这是因为 Python 首先计算 `Y + X`,为结果分配新的内存,然后使 `Y` 指向内存中的这个新位置。 + + + +```python +before = id(Y) +Y = Y + X +id(Y) == before +``` + +这可能是不可取的,原因有两个:首先,我们不想总是不必要地分配内存。在机器学习中,我们可能有数百兆的参数,并且在一秒内多次更新所有参数。通常情况下,我们希望原地执行这些更新。其次,我们可能通过多个变量指向相同参数。如果我们不原地更新,其他引用仍然会指向旧的内存位置,这样我们的某些代码可能会无意中引用旧的参数。 + + +幸运的是,(**执行原地操作**)非常简单。我们可以使用切片表示法将操作的结果分配给先前分配的数组,例如 `Y[:] = `。为了说明这一点,我们首先创建一个新的矩阵 `Z`,其形状与另一个 `Y` 相同,使用 `zeros_like` 来分配一个全$0$的块。 + + + +```python +Z = paddle.zeros_like(Y) +print('id(Z):', id(Z)) +Z = X + Y +print('id(Z):', id(Z)) +``` + +[**如果在后续计算中没有重复使用 `X`,我们也可以使用 `X[:] = X + Y` 或 `X += Y` 来减少操作的内存开销。**] + + + +```python +before = id(X) +X += Y +id(X) == before +``` + +## 转换为其他 Python 对象 + +[**转换为 NumPy 张量**]很容易,反之也很容易。转换后的结果不共享内存。 +这个小的不便实际上是非常重要的:当你在 CPU 或 GPU 上执行操作的时候,如果 Python 的 NumPy 包也希望使用相同的内存块执行其他操作,你不希望停下计算来等它。 + + + +```python +A = X.numpy() +B = paddle.to_tensor(A) +type(A), type(B) +``` + +要(**将大小为1的张量转换为 Python 标量**),我们可以调用 `item` 函数或 Python 的内置函数。 + + + +```python +a = paddle.to_tensor([3.5]) +a, a.item(), float(a), int(a) +``` + +## 小结 + +* 深度学习存储和操作数据的主要接口是张量($n$维数组)。它提供了各种功能,包括基本数学运算、广播、索引、切片、内存节省和转换其他 Python 对象。 + +## 练习 + +1. 运行本节中的代码。将本节中的条件语句 `X == Y` 更改为 `X < Y` 或 `X > Y`,然后看看你可以得到什么样的张量。 +1. 用其他形状(例如三维张量)替换广播机制中按元素操作的两个张量。结果是否与预期相同? + + +[Discussions](https://discuss.d2l.ai/t/1747) + diff --git "a/Dive-into-DL-paddlepaddle/docs/2_Preparatory-knowledge/2.2\346\225\260\346\215\256\351\242\204\345\244\204\347\220\206.md" "b/Dive-into-DL-paddlepaddle/docs/2_Preparatory-knowledge/2.2\346\225\260\346\215\256\351\242\204\345\244\204\347\220\206.md" new file mode 100644 index 000000000..c28d39df0 --- /dev/null +++ "b/Dive-into-DL-paddlepaddle/docs/2_Preparatory-knowledge/2.2\346\225\260\346\215\256\351\242\204\345\244\204\347\220\206.md" @@ -0,0 +1,90 @@ +# 数据预处理 +:label:`sec_pandas` + +到目前为止,我们已经介绍了处理存储在张量中数据的各种技术。为了能用深度学习来解决现实世界的问题,我们经常从预处理原始数据开始,而不是从那些准备好的张量格式数据开始。在Python中常用的数据分析工具中,通常使用 `pandas` 软件包。像庞大的 Python 生态系统中的许多其他扩展包一样,`pandas` 可以与张量兼容。因此,我们将简要介绍使用 `pandas` 预处理原始数据并将原始数据转换为张量格式的步骤。我们将在后面的章节中介绍更多的数据预处理技术。 + +## 读取数据集 + +举一个例子,我们首先(**创建一个人工数据集,并存储在csv(逗号分隔值)文件**) `../data/house_tiny.csv` 中。以其他格式存储的数据也可以通过类似的方式进行处理。下面的`mkdir_if_not_exist` 函数可确保目录 `../data` 存在。注意,注释 `#@save`是一个特殊的标记,该标记下方的函数、类或语句将保存在 `d2l` 软件包中,以便以后可以直接调用它们(例如 `d2l.mkdir_if_not_exist(path)`)而无需重新定义。 + +下面我们将数据集按行写入 csv 文件中。 + + + +```python +import os + +os.makedirs(os.path.join('.', 'data'), exist_ok=True) +data_file = os.path.join('.', 'data', 'house_tiny.csv') +with open(data_file, 'w') as f: + f.write('NumRooms,Alley,Price\n') # 列名 + f.write('NA,Pave,127500\n') # 每行表示一个数据样本 + f.write('2,NA,106000\n') + f.write('4,NA,178100\n') + f.write('NA,NA,140000\n') +``` + +要[**从创建的 csv 文件中加载原始数据集**],我们导入 `pandas` 包并调用 `read_csv` 函数。该数据集有四行三列。其中每行描述了房间数量(“NumRooms”)、巷子类型(“Alley”)和房屋价格(“Price”)。 + + + +```python +# 如果没有安装pandas,只需取消对以下行的注释: +# !pip install pandas +import pandas as pd + +data = pd.read_csv(data_file) +print(data) +``` + +## 处理缺失值 + +注意,“NaN” 项代表缺失值。[**为了处理缺失的数据,典型的方法包括 *插值* 和 *删除*,**]其中插值用替代值代替缺失值。而删除则忽略缺失值。在(**这里,我们将考虑插值**)。 + +通过位置索引`iloc`,我们将 `data` 分成 `inputs` 和 `outputs`,其中前者为 `data`的前两列,而后者为 `data`的最后一列。对于 `inputs` 中缺少的数值,我们用同一列的均值替换 “NaN” 项。 + + + +```python +inputs, outputs = data.iloc[:, 0:2], data.iloc[:, 2] +inputs = inputs.fillna(inputs.mean()) +print(inputs) +``` + +[**对于 `inputs` 中的类别值或离散值,我们将 “NaN” 视为一个类别。**]由于 “巷子”(“Alley”)列只接受两种类型的类别值 “Pave” 和 “NaN”,`pandas` 可以自动将此列转换为两列 “Alley_Pave” 和 “Alley_nan”。巷子类型为 “Pave” 的行会将“Alley_Pave”的值设置为1,“Alley_nan”的值设置为0。缺少巷子类型的行会将“Alley_Pave”和“Alley_nan”分别设置为0和1。 + + + +```python +inputs = pd.get_dummies(inputs, dummy_na=True) +print(inputs) +``` + +## 转换为张量格式 + +[**现在 `inputs` 和 `outputs` 中的所有条目都是数值类型,它们可以转换为张量格式。**]当数据采用张量格式后,可以通过在 :numref:`sec_ndarray` 中引入的那些张量函数来进一步操作。 + + + +```python +import paddle + +X, y = paddle.to_tensor(inputs.values), paddle.to_tensor(outputs.values) +X, y +``` + +## 小结 + +* 像庞大的 Python 生态系统中的许多其他扩展包一样,`pandas` 可以与张量兼容。 +* 插值和删除可用于处理缺失的数据。 + +## 练习 + +创建包含更多行和列的原始数据集。 + +1. 删除缺失值最多的列。 +2. 将预处理后的数据集转换为张量格式。 + + +[Discussions](https://discuss.d2l.ai/t/1750) + diff --git "a/Dive-into-DL-paddlepaddle/docs/2_Preparatory-knowledge/2.3\347\272\277\346\200\247\344\273\243\346\225\260.md" "b/Dive-into-DL-paddlepaddle/docs/2_Preparatory-knowledge/2.3\347\272\277\346\200\247\344\273\243\346\225\260.md" new file mode 100644 index 000000000..9a6eaaabc --- /dev/null +++ "b/Dive-into-DL-paddlepaddle/docs/2_Preparatory-knowledge/2.3\347\272\277\346\200\247\344\273\243\346\225\260.md" @@ -0,0 +1,514 @@ +# 线性代数 +:label:`sec_linear-algebra` + + +在你已经可以存储和操作数据后,让我们简要地回顾一下基本线性代数的部分内容。这些内容能够帮助你了解和实现本书中介绍的大多数模型。下面我们将介绍线性代数中的基本数学对象、算术和运算,并用数学符号和相应的代码实现来表示它们。 + +## 标量 + +如果你从来没有学过线性代数或机器学习,那么你过去的数学经历可能是一次只想一个数字。如果你曾经报销过发票,或者在餐厅支付餐费,那么你已经知道如何做一些基本的事情,比如在数字间相加或相乘。例如,北京的温度为 $52$ 华氏度(除了摄氏度外,另一种温度刻度)。严格来说,我们称仅包含一个数值的叫 *标量* (scalar)。如果要将此华氏度值转换为更常用的摄氏度,则可以计算表达式 $c = \frac{5}{9}(f - 32)$,并将 $f$ 赋为 $52$。在此等式中,每一项($5$、$9$ 和 $32$)都是标量值。符号 $c$ 和 $f$ 称为 *变量*(variables),它们表示未知的标量值。 + +在本书中,我们采用了数学表示法,其中标量变量由普通小写字母表示(例如,$x$、$y$ 和 $z$)。我们用 $\mathbb{R}$ 表示所有(连续)*实数* 标量的空间。为了方便,我们之后将严格定义 *空间*(space)是什么,但现在只要记住,表达式 $x \in \mathbb{R}$ 是表示$x$是一个实值标量的正式形式。符号 $\in$ 称为 “属于”,它表示“是集合中的成员”。我们可以用 $x, y \in \{0, 1\}$ 来表明 $x$ 和 $y$ 是值只能为 $0$ 或 $1$的数字。 + +(**标量由只有一个元素的张量表示**)。在下面的代码中,我们实例化两个标量,并使用它们执行一些熟悉的算术运算,即加法,乘法,除法和指数。 + + + +```python +import paddle + +x = paddle.to_tensor([3.0]) +y = paddle.to_tensor([2.0]) + +x + y, x * y, x / y, x**y +``` + +## 向量 + +[**你可以将向量视为标量值组成的列表**]。我们将这些标量值称为向量的 *元素*(elements)或*分量*(components)。当我们的向量表示数据集中的样本时,它们的值具有一定的现实意义。例如,如果我们正在训练一个模型来预测贷款违约风险,我们可能会将每个申请人与一个向量相关联,其分量与其收入、工作年限、过往违约次数和其他因素相对应。如果我们正在研究医院患者可能面临的心脏病发作风险,我们可能会用一个向量来表示每个患者,其分量为最近的生命体征、胆固醇水平、每天运动时间等。在数学表示法中,我们通常将向量记为粗体、小写的符号(例如,$\mathbf{x}$、$\mathbf{y}$和$\mathbf{z})$)。 + +我们通过一维张量处理向量。一般来说,张量可以具有任意长度,取决于机器的内存限制。 + + + +```python +x = paddle.arange(4) +x +``` + +我们可以使用下标来引用向量的任一元素。例如,我们可以通过 $x_i$ 来引用第 $i$ 个元素。注意,元素 $x_i$ 是一个标量,所以我们在引用它时不会加粗。大量文献认为列向量是向量的默认方向,在本书中也是如此。在数学中,向量 $\mathbf{x}$ 可以写为: + +$$\mathbf{x} =\begin{bmatrix}x_{1} \\x_{2} \\ \vdots \\x_{n}\end{bmatrix},$$ +:eqlabel:`eq_vec_def` + +其中 $x_1, \ldots, x_n$ 是向量的元素。在代码中,我们(**通过张量的索引来访问任一元素**)。 + + + +```python +x[3] +``` + +### 长度、维度和形状 + +让我们回顾一下 :numref:`sec_ndarray` 中的一些概念。向量只是一个数字数组。就像每个数组都有一个长度一样,每个向量也是如此。在数学表示法中,如果我们想说一个向量 $\mathbf{x}$ 由 $n$ 个实值标量组成,我们可以将其表示为 $\mathbf{x} \in \mathbb{R}^n$。向量的长度通常称为向量的 *维度*(dimension)。 + +与普通的 Python 数组一样,我们可以通过调用 Python 的内置 `len()` 函数来[**访问张量的长度**]。 + + + +```python +len(x) +``` + +当用张量表示一个向量(只有一个轴)时,我们也可以通过 `.shape` 属性访问向量的长度。形状(shape)是一个元组,列出了张量沿每个轴的长度(维数)。对于(**只有一个轴的张量,形状只有一个元素。**) + + + +```python +x.shape +``` + +请注意,*维度*(dimension)这个词在不同上下文时往往会有不同的含义,这经常会使人感到困惑。为了清楚起见,我们在此明确一下。*向量*或*轴*的维度被用来表示*向量*或*轴*的长度,即向量或轴的元素数量。然而,张量的维度用来表示张量具有的轴数。在这个意义上,张量的某个轴的维数就是这个轴的长度。 + +## 矩阵 + +正如向量将标量从零阶推广到一阶,矩阵将向量从一阶推广到二阶。矩阵,我们通常用粗体、大写字母来表示(例如,$\mathbf{X}$、$\mathbf{Y}$ 和 $\mathbf{Z}$),在代码中表示为具有两个轴的张量。 + +在数学表示法中,我们使用 $\mathbf{A} \in \mathbb{R}^{m \times n}$ 来表示矩阵 $\mathbf{A}$ ,其由$m$ 行和 $n$ 列的实值标量组成。直观地,我们可以将任意矩阵 $\mathbf{A} \in \mathbb{R}^{m \times n}$ 视为一个表格,其中每个元素 $a_{ij}$ 属于第 $i$ 行第$j$ 列: + +$$\mathbf{A}=\begin{bmatrix} a_{11} & a_{12} & \cdots & a_{1n} \\ a_{21} & a_{22} & \cdots & a_{2n} \\ \vdots & \vdots & \ddots & \vdots \\ a_{m1} & a_{m2} & \cdots & a_{mn} \\ \end{bmatrix}.$$ +:eqlabel:`eq_matrix_def` + +对于任意$\mathbf{A} \in \mathbb{R}^{m \times n}$,$\mathbf{A}$的形状是($m$, $n$)或$m \times n$。当矩阵具有相同数量的行和列时,其形状将变为正方形;因此,它被称为 *方矩阵*(square matrix)。 + +当调用函数来实例化张量时,我们可以[**通过指定两个分量$m$ 和 $n$来创建一个形状为$m \times n$ 的矩阵**]。 + + + +```python +A = paddle.reshape(paddle.arange(20), (5, 4)) +A +``` + +我们可以通过行索引($i$)和列索引($j$)来访问矩阵中的标量元素 $a_{ij}$,例如 $[\mathbf{A}]_{ij}$。如果没有给出矩阵 $\mathbf{A}$ 的标量元素,如在 :eqref:`eq_matrix_def`那样,我们可以简单地使用矩阵 $\mathbf{A}$ 的小写字母索引下标 $a_{ij}$来引用$[\mathbf{A}]_{ij}$。为了表示起来简单,只有在必要时才会将逗号插入到单独的索引中,例如 $a_{2, 3j}$ 和 $[\mathbf{A}]_{2i-1, 3}$。 + +有时候,我们想翻转轴。当我们交换矩阵的行和列时,结果称为矩阵的 *转置*(transpose)。我们用$\mathbf{a}^\top$来表示矩阵的转置,如果$\mathbf{B} = \mathbf{A}^\top$,则对于任意$i$和$j$,都有$b_{ij} = a_{ji}$。因此,在 :eqref:`eq_matrix_def` 中的转置是一个形状为$n \times m$的矩阵: + +$$ +\mathbf{A}^\top = +\begin{bmatrix} + a_{11} & a_{21} & \dots & a_{m1} \\ + a_{12} & a_{22} & \dots & a_{m2} \\ + \vdots & \vdots & \ddots & \vdots \\ + a_{1n} & a_{2n} & \dots & a_{mn} +\end{bmatrix}. +$$ + +现在我们在代码中访问(**矩阵的转置**)。 + + + +```python +paddle.transpose(A, perm=[1, 0]) +``` + +作为方矩阵的一种特殊类型,[***对称矩阵*(symmetric matrix) $\mathbf{A}$ 等于其转置:$\mathbf{A} = \mathbf{A}^\top$**]。这里我们定义一个对称矩阵 `B`: + + + +```python +B = paddle.to_tensor([[1, 2, 3], [2, 0, 4], [3, 4, 5]]) +B +``` + +现在我们将 `B` 与它的转置进行比较。 + + + +```python +B == paddle.transpose(B, perm=[1, 0]) +``` + +矩阵是有用的数据结构:它们允许我们组织具有不同变化模式的数据。例如,我们矩阵中的行可能对应于不同的房屋(数据样本),而列可能对应于不同的属性。如果你曾经使用过电子表格软件或已阅读过 :numref:`sec_pandas`,这应该听起来很熟悉。因此,尽管单个向量的默认方向是列向量,但在表示表格数据集的矩阵中,将每个数据样本作为矩阵中的行向量更为常见。我们将在后面的章节中讲到这点。这种约定将支持常见的深度学习实践。例如,沿着张量的最外轴,我们可以访问或遍历小批量的数据样本。如果不存在小批量,我们也可以只访问数据样本。 + +## 张量 + +[**就像向量是标量的推广,矩阵是向量的推广一样,我们可以构建具有更多轴的数据结构**]。张量(本小节中的 “张量” 指代数对象)为我们提供了描述具有任意数量轴的$n$维数组的通用方法。例如,向量是一阶张量,矩阵是二阶张量。张量用特殊字体的大写字母(例如,$\mathsf{X}$、$\mathsf{Y}$ 和 $\mathsf{Z}$)表示,它们的索引机制(例如 $x_{ijk}$ 和 $[\mathsf{X}]_{1, 2i-1, 3}$)与矩阵类似。 + +当我们开始处理图像时,张量将变得更加重要,图像以$n$维数组形式出现,其中3个轴对应于高度、宽度,以及一个*通道*(channel)轴,用于堆叠颜色通道(红色、绿色和蓝色)。现在,我们将跳过高阶张量,集中在基础知识上。 + + + +```python +X = paddle.reshape(paddle.arange(24), (2, 3, 4)) +X +``` + +## 张量算法的基本性质 + +标量、向量、矩阵和任意数量轴的张量(本小节中的 “张量” 指代数对象)有一些很好的属性,通常会派上用场。例如,你可能已经从按元素操作的定义中注意到,任何按元素的一元运算都不会改变其操作数的形状。同样,[**给定具有相同形状的任意两个张量,任何按元素二元运算的结果都将是相同形状的张量**]。例如,将两个相同形状的矩阵相加会在这两个矩阵上执行元素加法。 + + + +```python +A = paddle.reshape(paddle.arange(20, dtype=paddle.float32), (5, 4)) +B = A.clone() # 通过分配新内存,将A的一个副本分配给B +A, A + B +``` + +具体而言,[**两个矩阵的按元素乘法称为 *哈达玛积*(Hadamard product)(数学符号 $\odot$)**]。对于矩阵 $\mathbf{B} \in \mathbb{R}^{m \times n}$,其中第 $i$ 行和第 $j$ 列的元素是 $b_{ij}$。矩阵$\mathbf{A}$(在 :eqref:`eq_matrix_def` 中定义)和 $\mathbf{B}$的哈达玛积为: + +$$ +\mathbf{A} \odot \mathbf{B} = +\begin{bmatrix} + a_{11} b_{11} & a_{12} b_{12} & \dots & a_{1n} b_{1n} \\ + a_{21} b_{21} & a_{22} b_{22} & \dots & a_{2n} b_{2n} \\ + \vdots & \vdots & \ddots & \vdots \\ + a_{m1} b_{m1} & a_{m2} b_{m2} & \dots & a_{mn} b_{mn} +\end{bmatrix}. +$$ + + + +```python +A * B +``` + +将张量乘以或加上一个标量不会改变张量的形状,其中张量的每个元素都将与标量相加或相乘。 + + + +```python +a = 2 +X = paddle.reshape(paddle.arange(24), (2, 3, 4)) +a + X, (a * X).shape +``` + +## 降维 +:label:`subseq_lin-alg-reduction` + +我们可以对任意张量进行的一个有用的操作是[**计算其元素的和**]。在数学表示法中,我们使用 $\sum$ 符号表示求和。为了表示长度为$d$的向量中元素的总和,可以记为 $\sum_{i=1}^d x_i$。在代码中,我们可以调用计算求和的函数: + + + +```python +x = paddle.arange(4, dtype=paddle.float32) +x, x.sum() +``` + +我们可以(**表示任意形状张量的元素和**)。例如,矩阵 $\mathbf{A}$ 中元素的和可以记为$\sum_{i=1}^{m} \sum_{j=1}^{n} a_{ij}$。 + + + +```python +A.shape, A.sum() +``` + +默认情况下,调用求和函数会沿所有的轴降低张量的维度,使它变为一个标量。 +我们还可以[**指定张量沿哪一个轴来通过求和降低维度**]。以矩阵为例,为了通过求和所有行的元素来降维(轴0),我们可以在调用函数时指定`axis=0`。 +由于输入矩阵沿0轴降维以生成输出向量,因此输入的轴0的维数在输出形状中丢失。 + + + +```python +A_sum_axis0 = A.sum(axis=0) +A_sum_axis0, A_sum_axis0.shape +``` + +指定 `axis=1` 将通过汇总所有列的元素降维(轴1)。因此,输入的轴1的维数在输出形状中消失。 + + + +```python +A_sum_axis1 = A.sum(axis=1) +A_sum_axis1, A_sum_axis1.shape +``` + +沿着行和列对矩阵求和,等价于对矩阵的所有元素进行求和。 + + + +```python +A.sum(axis=[0, 1]) # Same as `A.sum()` +``` + +[**一个与求和相关的量是 *平均值*(mean或average)**]。我们通过将总和除以元素总数来计算平均值。在代码中,我们可以调用函数来计算任意形状张量的平均值。 + + + +```python +A.mean(), A.sum() / A.numel() +``` + +同样,计算平均值的函数也可以沿指定轴降低张量的维度。 + + + +```python +A.mean(axis=0), A.sum(axis=0) / A.shape[0] +``` + +### 非降维求和 +:label:`subseq_lin-alg-non-reduction` + +但是,有时在调用函数来[**计算总和或均值时保持轴数不变**]会很有用。 + + + +```python +sum_A = paddle.sum(A, axis=1, keepdim=True) +sum_A +``` + +例如,由于 `sum_A` 在对每行进行求和后仍保持两个轴,我们可以(**通过广播将 `A` 除以 `sum_A`**) 。 + + + +```python +A / sum_A +``` + +如果我们想沿[**某个轴计算 `A` 元素的累积总和**],比如 `axis=0`(按行计算),我们可以调用 `cumsum` 函数。此函数不会沿任何轴降低输入张量的维度。 + + + +```python +A.cumsum(axis=0) +``` + +## 点积(Dot Product) + +到目前为止,我们只执行了按元素操作、求和及平均值。如果这就是我们所能做的,那么线性代数可能就不需要单独一节了。 +但是,最基本的操作之一是点积。给定两个向量 $\mathbf{x}, \mathbf{y} \in \mathbb{R}^d$,它们的 *点积*(dot product) $\mathbf{x}^\top \mathbf{y}$(或 $\langle \mathbf{x}, \mathbf{y} \rangle$)是相同位置的按元素乘积的和:$\mathbf{x}^\top \mathbf{y} = \sum_{i=1}^{d} x_i y_i$。 + +[~~点积是相同位置的按元素乘积的和~~] + + + +```python +y = paddle.ones(shape=[4], dtype='float32') +x, y, paddle.dot(x, y) +``` + +注意,(**我们可以通过执行按元素乘法,然后进行求和来表示两个向量的点积**): + + + +```python +paddle.sum(x * y) +``` + +点积在很多场合都很有用。例如,给定一组由向量$\mathbf{x} \in \mathbb{R}^d$ 表示的值,和一组由 $\mathbf{w} \in \mathbb{R}^d$ 表示的权重。$\mathbf{x}$ 中的值根据权重 $\mathbf{w}$ 的加权和可以表示为点积 $\mathbf{x}^\top \mathbf{w}$。当权重为非负数且和为1(即 $\left(\sum_{i=1}^{d} {w_i} = 1\right)$)时,点积表示 *加权平均*(weighted average)。将两个向量归一化得到单位长度后,点积表示它们夹角的余弦。我们将在本节的后面正式介绍*长度*(length)的概念。 + +## 矩阵-向量积 + +现在我们知道如何计算点积,我们可以开始理解 *矩阵-向量积*(matrix-vector products)。回顾分别在 :eqref:`eq_matrix_def` 和 :eqref:`eq_vec_def` 中定义并画出的矩阵 $\mathbf{A} \in \mathbb{R}^{m \times n}$ 和向量 $\mathbf{x} \in \mathbb{R}^n$。让我们将矩阵$\mathbf{A}$用它的行向量表示 + +$$\mathbf{A}= +\begin{bmatrix} +\mathbf{a}^\top_{1} \\ +\mathbf{a}^\top_{2} \\ +\vdots \\ +\mathbf{a}^\top_m \\ +\end{bmatrix},$$ + +其中每个$\mathbf{a}^\top_{i} \in \mathbb{R}^n$ 都是行向量,表示矩阵的第 $i$ 行。[**矩阵向量积 $\mathbf{A}\mathbf{x}$ 是一个长度为 $m$ 的列向量,其第 $i$ 个元素是点积 $\mathbf{a}^\top_i \mathbf{x}$**]: + +$$ +\mathbf{A}\mathbf{x} += \begin{bmatrix} +\mathbf{a}^\top_{1} \\ +\mathbf{a}^\top_{2} \\ +\vdots \\ +\mathbf{a}^\top_m \\ +\end{bmatrix}\mathbf{x} += \begin{bmatrix} + \mathbf{a}^\top_{1} \mathbf{x} \\ + \mathbf{a}^\top_{2} \mathbf{x} \\ +\vdots\\ + \mathbf{a}^\top_{m} \mathbf{x}\\ +\end{bmatrix}. +$$ + +我们可以把一个矩阵 $\mathbf{A}\in \mathbb{R}^{m \times n}$ 乘法看作是一个从 $\mathbb{R}^{n}$ 到 $\mathbb{R}^{m}$ 向量的转换。这些转换证明是非常有用的。例如,我们可以用方阵的乘法来表示旋转。 +我们将在后续章节中讲到,我们也可以使用矩阵-向量积来描述在给定前一层的值时,求解神经网络每一层所需的复杂计算。 + +在代码中使用张量表示矩阵-向量积,我们使用与点积相同的 `dot` 函数。当我们为矩阵 `A` 和向量 `x` 调用 `np.dot(A, x)`时,会执行矩阵-向量积。注意,`A` 的列维数(沿轴1的长度)必须与 `x` 的维数(其长度)相同。 + + + +```python +A.shape, x.shape, paddle.mv(A, x) +``` + +## 矩阵-矩阵乘法 + +如果你已经掌握了点积和矩阵-向量积的知识,那么 **矩阵-矩阵乘法**(matrix-matrix multiplication) 应该很简单。 + +假设我们有两个矩阵 $\mathbf{A} \in \mathbb{R}^{n \times k}$ 和 $\mathbf{B} \in \mathbb{R}^{k \times m}$: + +$$\mathbf{A}=\begin{bmatrix} + a_{11} & a_{12} & \cdots & a_{1k} \\ + a_{21} & a_{22} & \cdots & a_{2k} \\ +\vdots & \vdots & \ddots & \vdots \\ + a_{n1} & a_{n2} & \cdots & a_{nk} \\ +\end{bmatrix},\quad +\mathbf{B}=\begin{bmatrix} + b_{11} & b_{12} & \cdots & b_{1m} \\ + b_{21} & b_{22} & \cdots & b_{2m} \\ +\vdots & \vdots & \ddots & \vdots \\ + b_{k1} & b_{k2} & \cdots & b_{km} \\ +\end{bmatrix}.$$ + +用行向量$\mathbf{a}^\top_{i} \in \mathbb{R}^k$ 表示矩阵$\mathbf{A}$的第 $i$ 行,并让列向量$\mathbf{b}_{j} \in \mathbb{R}^k$ 作为矩阵$\mathbf{B}$的第 $j$ 列。要生成矩阵积 $\mathbf{C} = \mathbf{A}\mathbf{B}$,最简单的方法是考虑$\mathbf{A}$的行向量和$\mathbf{B}$的列向量: + +$$\mathbf{A}= +\begin{bmatrix} +\mathbf{a}^\top_{1} \\ +\mathbf{a}^\top_{2} \\ +\vdots \\ +\mathbf{a}^\top_n \\ +\end{bmatrix}, +\quad \mathbf{B}=\begin{bmatrix} + \mathbf{b}_{1} & \mathbf{b}_{2} & \cdots & \mathbf{b}_{m} \\ +\end{bmatrix}. +$$ + +当我们简单地将每个元素$c_{ij}$计算为点积$\mathbf{a}^\top_i \mathbf{b}_j$: + +$$\mathbf{C} = \mathbf{AB} = \begin{bmatrix} +\mathbf{a}^\top_{1} \\ +\mathbf{a}^\top_{2} \\ +\vdots \\ +\mathbf{a}^\top_n \\ +\end{bmatrix} +\begin{bmatrix} + \mathbf{b}_{1} & \mathbf{b}_{2} & \cdots & \mathbf{b}_{m} \\ +\end{bmatrix} += \begin{bmatrix} +\mathbf{a}^\top_{1} \mathbf{b}_1 & \mathbf{a}^\top_{1}\mathbf{b}_2& \cdots & \mathbf{a}^\top_{1} \mathbf{b}_m \\ + \mathbf{a}^\top_{2}\mathbf{b}_1 & \mathbf{a}^\top_{2} \mathbf{b}_2 & \cdots & \mathbf{a}^\top_{2} \mathbf{b}_m \\ + \vdots & \vdots & \ddots &\vdots\\ +\mathbf{a}^\top_{n} \mathbf{b}_1 & \mathbf{a}^\top_{n}\mathbf{b}_2& \cdots& \mathbf{a}^\top_{n} \mathbf{b}_m +\end{bmatrix}. +$$ + +[**我们可以将矩阵-矩阵乘法 $\mathbf{AB}$ 看作是简单地执行 $m$次矩阵-向量积,并将结果拼接在一起,形成一个 $n \times m$ 矩阵**]。在下面的代码中,我们在 `A` 和 `B` 上执行矩阵乘法。这里的`A` 是一个5行4列的矩阵,`B`是一个4行3列的矩阵。相乘后,我们得到了一个5行3列的矩阵。 + + + +```python +B = paddle.ones(shape=[4, 3], dtype='float32') +paddle.mm(A, B) +``` + +矩阵-矩阵乘法可以简单地称为 **矩阵乘法**,不应与 哈达玛积 混淆。 + +## 范数 +:label:`subsec_lin-algebra-norms` + +线性代数中最有用的一些运算符是 *范数*(norms)。非正式地说,一个向量的*范数*告诉我们一个向量有多大。 +这里考虑的 *大小*(size) 概念不涉及维度,而是分量的大小。 + +在线性代数中,向量范数是将向量映射到标量的函数 $f$。向量范数要满足一些属性。 +给定任意向量 $\mathbf{x}$,第一个性质说,如果我们按常数因子 $\alpha$ 缩放向量的所有元素,其范数也会按相同常数因子的 *绝对值* 缩放: + +$$f(\alpha \mathbf{x}) = |\alpha| f(\mathbf{x}).$$ + +第二个性质是我们熟悉的三角不等式: + +$$f(\mathbf{x} + \mathbf{y}) \leq f(\mathbf{x}) + f(\mathbf{y}).$$ + +第三个性质简单地说范数必须是非负的: + +$$f(\mathbf{x}) \geq 0.$$ + +这是有道理的,因为在大多数情况下,任何东西的最小的*大小*是0。最后一个性质要求范数最小为0,当且仅当向量全由0组成。 + +$$\forall i, [\mathbf{x}]_i = 0 \Leftrightarrow f(\mathbf{x})=0.$$ + +你可能会注意到,范数听起来很像距离的度量。如果你还记得小学时的欧几里得距离(想想毕达哥拉斯定理),那么非负性的概念和三角不等式可能会给你一些启发。 +事实上,欧几里得距离是一个范数:具体而言,它是 $L_2$ 范数。假设$n$维向量 $\mathbf{x}$ 中的元素是$x_1, \ldots, x_n$,其 [**$L_2$ *范数* 是向量元素平方和的平方根:**] + +(**$$\|\mathbf{x}\|_2 = \sqrt{\sum_{i=1}^n x_i^2},$$**) + +其中,在 $L_2$ 范数中常常省略下标 $2$,也就是说,$\|\mathbf{x}\|$ 等同于 $\|\mathbf{x}\|_2$。在代码中,我们可以按如下方式计算向量的 $L_2$ 范数。 + + + +```python +u = paddle.to_tensor([3.0, -4.0]) +paddle.norm(u) +``` + +在深度学习中,我们更经常地使用 $L_2$ 范数的平方。你还会经常遇到 [**$L_1$ 范数,它表示为向量元素的绝对值之和:**] + +(**$$\|\mathbf{x}\|_1 = \sum_{i=1}^n \left|x_i \right|.$$**) + +与 $L_2$ 范数相比,$L_1$ 范数受异常值的影响较小。为了计算 $L_1$ 范数,我们将绝对值函数和按元素求和组合起来。 + + + +```python +paddle.abs(u).sum() +``` + +$L_2$ 范数和 $L_1$ 范数都是更一般的$L_p$范数的特例: + +$$\|\mathbf{x}\|_p = \left(\sum_{i=1}^n \left|x_i \right|^p \right)^{1/p}.$$ + +类似于向量的$L_2$ 范数,[**矩阵**] $\mathbf{X} \in \mathbb{R}^{m \times n}$ (**的 *弗罗贝尼乌斯范数*(Frobenius norm) 是矩阵元素平方和的平方根:**) + +(**$$\|\mathbf{X}\|_F = \sqrt{\sum_{i=1}^m \sum_{j=1}^n x_{ij}^2}.$$**) + +弗罗贝尼乌斯范数满足向量范数的所有性质。它就像是矩阵形向量的 $L_2$ 范数。调用以下函数将计算矩阵的弗罗贝尼乌斯范数。 + + + +```python +paddle.norm(paddle.ones(shape=[4, 9], dtype='float32')) +``` + +### 范数和目标 +:label:`subsec_norms_and_objectives` + +虽然我们不想走得太远,但我们可以对这些概念为什么有用有一些直觉。在深度学习中,我们经常试图解决优化问题: +*最大化* 分配给观测数据的概率; +*最小化* 预测和真实观测之间的距离。 +用向量表示物品(如单词、产品或新闻文章),以便最小化相似项目之间的距离,最大化不同项目之间的距离。 +通常,目标,或许是深度学习算法最重要的组成部分(除了数据),被表达为范数。 + + +## 关于线性代数的更多信息 + +仅用一节,我们就教会了你所需的,用以理解大量的现代深度学习的全部线性代数。 +线性代数还有很多,其中很多数学对于机器学习非常有用。例如,矩阵可以分解为因子,这些分解可以显示真实世界数据集中的低维结构。机器学习的整个子领域都侧重于使用矩阵分解及其向高阶张量的泛化来发现数据集中的结构并解决预测问题。但这本书的重点是深度学习。我们相信,一旦你开始动手尝试并在真实数据集上应用了有效的机器学习模型,你会更倾向于学习更多数学。因此,虽然我们保留在后面介绍更多数学知识的权利,但我们这一节到此结束。 + +如果你渴望了解有关线性代数的更多信息,你可以参考 [线性代数运算的在线附录](https://d2l.ai/chapter_appendix-mathematics-for-deep-learning/geometry-linear-algebraic-ops.html) 或其他优秀资源 :cite:`Strang.1993,Kolter.2008,Petersen.Pedersen.ea.2008`。 + +## 小结 + +* 标量、向量、矩阵和张量是线性代数中的基本数学对象。 +* 向量泛化自标量,矩阵泛化自向量。 +* 标量、向量、矩阵和张量分别具有零、一、二和任意数量的轴。 +* 一个张量可以通过`sum` 和 `mean`沿指定的轴降低维度。 +* 两个矩阵的按元素乘法被称为他们的哈达玛积。它与矩阵乘法不同。 +* 在深度学习中,我们经常使用范数,如 $L_1$范数、$L_2$范数和弗罗贝尼乌斯范数。 +* 我们可以对标量、向量、矩阵和张量执行各种操作。 + +## 练习 + +1. 证明一个矩阵 $\mathbf{A}$ 的转置的转置是 $\mathbf{A}$:$(\mathbf{A}^\top)^\top = \mathbf{A}$。 +1. 给出两个矩阵 $\mathbf{A}$ 和 $\mathbf{B}$, 显示转置的和等于和的转置:$\mathbf{A}^\top + \mathbf{B}^\top = (\mathbf{A} + \mathbf{B})^\top$. +1. 给定任意方矩阵$\mathbf{A}$, $\mathbf{A} + \mathbf{A}^\top$总是对称的吗?为什么? +1. 我们在本节中定义了形状(2, 3, 4)的张量 `X`。`len(X)`的输出结果是什么? +1. 对于任意形状的张量`X`, `len(X)`是否总是对应于`X`特定轴的长度?这个轴是什么? +1. 运行 `A / A.sum(axis=1)`,看看会发生什么。你能分析原因吗? +1. 当你在曼哈顿的两点之间旅行时,你需要在坐标上走多远,也就是说,就大街和街道而言?你能斜着走吗? +1. 考虑一个具有形状(2, 3, 4)的张量,在轴 0,1,2 上的求和输出是什么形状? +1. 向 `linalg.norm` 函数提供 3 个或更多轴的张量,并观察其输出。对于任意形状的张量这个函数计算得到什么? + + +[Discussions](https://discuss.d2l.ai/t/1751) + diff --git "a/Dive-into-DL-paddlepaddle/docs/2_Preparatory-knowledge/2.4\345\276\256\345\210\206.md" "b/Dive-into-DL-paddlepaddle/docs/2_Preparatory-knowledge/2.4\345\276\256\345\210\206.md" new file mode 100644 index 000000000..bcaf619a7 --- /dev/null +++ "b/Dive-into-DL-paddlepaddle/docs/2_Preparatory-knowledge/2.4\345\276\256\345\210\206.md" @@ -0,0 +1,242 @@ +# 微分 +:label:`sec_calculus` + +在2500年前,古希腊人把一个多边形分成三角形,并把它们的面积相加,才找到计算多边形面积的方法。 +为了求出曲线形状(比如圆)的面积,古希腊人在这样的形状上刻内接多边形。如 :numref:`fig_circle_area` 所示,内接多边形的等长边越多,就越接近圆。这个过程也被称为 *逼近法*(method of exhaustion)。 + +![用逼近法求圆的面积。](../img/polygon-circle.svg) + +:label:`fig_circle_area` + +事实上,逼近法就是 *积分*(integral calculus)的起源,我们将在 :numref:`sec_integral_calculus` 中详细描述。2000 多年后,微积分的另一支,*微分*(differential calculus),被发明出来。在微分学最重要的应用是优化问题,即考虑如何把事情做到最好。正如在 :numref:`subsec_norms_and_objectives` 中讨论的那样,这种问题在深度学习中是无处不在的。 + +在深度学习中,我们“训练”模型,不断更新它们,使它们在看到越来越多的数据时变得越来越好。通常情况下,变得更好意味着最小化一个 *损失函数*(loss function),即一个衡量“我们的模型有多糟糕”这个问题的分数。这个问题比看上去要微妙得多。最终,我们真正关心的是生成一个能够在我们从未见过的数据上表现良好的模型。但我们只能将模型与我们实际能看到的数据相拟合。因此,我们可以将拟合模型的任务分解为两个关键问题:(1)*优化*(optimization):用模型拟合观测数据的过程;(2)*泛化*(generalization):数学原理和实践者的智慧,能够指导我们生成出有效性超出用于训练的数据集本身的模型。 + +为了帮助你在后面的章节中更好地理解优化问题和方法,这里我们对深度学习中常用的微分知识提供了一个非常简短的入门教程。 + +## 导数和微分 + +我们首先讨论导数的计算,这是几乎所有深度学习优化算法的关键步骤。在深度学习中,我们通常选择对于模型参数可微的损失函数。简而言之,这意味着,对于每个参数, +如果我们把这个参数*增加*或*减少*一个无穷小的量,我们可以知道损失会以多快的速度增加或减少, + +假设我们有一个函数 $f: \mathbb{R} \rightarrow \mathbb{R}$,其输入和输出都是标量。(**$f$ 的 *导数* 被定义为**) + +(**$$f'(x) = \lim_{h \rightarrow 0} \frac{f(x+h) - f(x)}{h},$$**) +:eqlabel:`eq_derivative` + +如果这个极限存在。如果$f'(a)$存在,则称$f$在$a$处是*可微*(differentiable)的。如果 $f$ 在一个区间内的每个数上都是可微的,则此函数在此区间中是可微的。我们可以将 :eqref:`eq_derivative` 中的导数 $f'(x)$ 解释为$f(x)$相对于 $x$ 的 *瞬时*(instantaneous) 变化率。所谓的瞬时变化率是基于$x$中的变化$h$,且$h$接近$0$。 + +为了更好地解释导数,让我们用一个例子来做实验。 +(**定义$u = f(x) = 3x^2-4x$.**) + + + +```python +%matplotlib inline +import numpy as np +from IPython import display +from matplotlib import pyplot as plt + + +def f(x): + return 3 * x ** 2 - 4 * x +``` + +[**通过令 $x=1$ 并让 $h$ 接近 $0$,**] :eqref:`eq_derivative` 中(**$\frac{f(x+h) - f(x)}{h}$ 的数值结果接近 $2$**)。虽然这个实验不是一个数学证明,但我们稍后会看到,当 $x=1$时,导数 $u'$是 $2$ 。 + + + +```python +def numerical_lim(f, x, h): + return (f(x + h) - f(x)) / h + +h = 0.1 +for i in range(5): + print(f'h={h:.5f}, numerical limit={numerical_lim(f, 1, h):.5f}') + h *= 0.1 +``` + +让我们熟悉一下导数的几个等价符号。 +给定 $y = f(x)$,其中 $x$ 和 $y$ 分别是函数 $f$ 的自变量和因变量。以下表达式是等价的: + +$$f'(x) = y' = \frac{dy}{dx} = \frac{df}{dx} = \frac{d}{dx} f(x) = Df(x) = D_x f(x),$$ + +其中符号 $\frac{d}{dx}$ 和 $D$ 是*微分运算符*,表示*微分*操作。我们可以使用以下规则来对常见函数求微分: + +* $DC = 0$ ($C$ 是一个常数) +* $Dx^n = nx^{n-1}$ (*幂律*(power rule), $n$是任意实数) +* $De^x = e^x$ +* $D\ln(x) = 1/x$ + +为了微分一个由一些简单函数(如上面的常见函数)组成的函数,下面的法则使用起来很方便。 +假设函数$f$和$g$都是可微的,$C$是一个常数,我们有: + +*常数相乘法则* +$$\frac{d}{dx} [Cf(x)] = C \frac{d}{dx} f(x),$$ + +*加法法则* + +$$\frac{d}{dx} [f(x) + g(x)] = \frac{d}{dx} f(x) + \frac{d}{dx} g(x),$$ + +*乘法法则* + +$$\frac{d}{dx} [f(x)g(x)] = f(x) \frac{d}{dx} [g(x)] + g(x) \frac{d}{dx} [f(x)],$$ + +*除法法则* + +$$\frac{d}{dx} \left[\frac{f(x)}{g(x)}\right] = \frac{g(x) \frac{d}{dx} [f(x)] - f(x) \frac{d}{dx} [g(x)]}{[g(x)]^2}.$$ + +现在我们可以应用上述几个法则来计算 $u' = f'(x) = 3 \frac{d}{dx} x^2-4\frac{d}{dx}x = 6x-4$。因此,通过令 $x = 1$ ,我们有 $u' = 2$ :这一点得到了我们在本节前面的实验的支持,在这个实验中,数值结果接近$2$。当 $x=1$ 时,此导数也是曲线 $u = f(x)$ 切线的斜率。 + +[**为了对导数的这种解释进行可视化,**]我们将使用 `matplotlib`,这是一个Python中流行的绘图库。要配置`matplotlib`生成图形的属性,我们需要(**定义几个函数**)。 +在下面,`use_svg_display` 函数指定 `matplotlib` 软件包输出svg图表以获得更清晰的图像。 + +注意,注释`#@save`是一个特殊的标记,会将对应的函数、类或语句保存在`d2l`包中 +因此,以后无需重新定义就可以直接调用它们(例如,`d2l.use_svg_display()`)。 + + + +```python +def use_svg_display(): #@save + """使用svg格式在Jupyter中显示绘图。""" + display.set_matplotlib_formats('svg') +``` + +我们定义 `set_figsize` 函数来设置图表大小。注意,这里我们直接使用 `d2l.plt`,因为导入语句 `from matplotlib import pyplot as plt` 已在前言中标记为保存到`d2l` 包中。 + + + +```python +def set_figsize(figsize=(3.5, 2.5)): #@save + """设置matplotlib的图表大小。""" + use_svg_display() + plt.rcParams['figure.figsize'] = figsize +``` + +下面的`set_axes`函数用于设置由`matplotlib`生成图表的轴的属性。 + + + +```python +#@save +def set_axes(axes, xlabel, ylabel, xlim, ylim, xscale, yscale, legend): + """设置matplotlib的轴。""" + axes.set_xlabel(xlabel) + axes.set_ylabel(ylabel) + axes.set_xscale(xscale) + axes.set_yscale(yscale) + axes.set_xlim(xlim) + axes.set_ylim(ylim) + if legend: + axes.legend(legend) + axes.grid() +``` + +通过这三个用于图形配置的函数,我们定义了 `plot` 函数来简洁地绘制多条曲线,因为我们需要在整个书中可视化许多曲线。 + + + +```python +#@save +def plot(X, Y=None, xlabel=None, ylabel=None, legend=None, xlim=None, + ylim=None, xscale='linear', yscale='linear', + fmts=('-', 'm--', 'g-.', 'r:'), figsize=(3.5, 2.5), axes=None): + """绘制数据点。""" + if legend is None: + legend = [] + + set_figsize(figsize) + axes = axes if axes else plt.gca() + + # 如果 `X` 有一个轴,输出True + def has_one_axis(X): + return (hasattr(X, "ndim") and X.ndim == 1 or + isinstance(X, list) and not hasattr(X[0], "__len__")) + + if has_one_axis(X): + X = [X] + if Y is None: + X, Y = [[]] * len(X), X + elif has_one_axis(Y): + Y = [Y] + if len(X) != len(Y): + X = X * len(Y) + axes.cla() + for x, y, fmt in zip(X, Y, fmts): + if len(x): + axes.plot(x, y, fmt) + else: + axes.plot(y, fmt) + set_axes(axes, xlabel, ylabel, xlim, ylim, xscale, yscale, legend) +``` + +现在我们可以[**绘制函数 $u = f(x)$ 及其在 $x=1$ 处的切线 $y = 2x - 3$**],其中系数$2$是切线的斜率。 + + + +```python +x = np.arange(0, 3, 0.1) +plot(x, [f(x), 2 * x - 3], 'x', 'f(x)', legend=['f(x)', 'Tangent line (x=1)']) +``` + +## 偏导数 + +到目前为止,我们只讨论了仅含一个变量的函数的微分。在深度学习中,函数通常依赖于许多变量。因此,我们需要将微分的思想推广到这些 *多元函数* (multivariate function)上。 + +设 $y = f(x_1, x_2, \ldots, x_n)$ 是一个具有 $n$ 个变量的函数。$y$ 关于第$i$ 个参数$x_i$的*偏导数*(partial derivative)为: + +$$ \frac{\partial y}{\partial x_i} = \lim_{h \rightarrow 0} \frac{f(x_1, \ldots, x_{i-1}, x_i+h, x_{i+1}, \ldots, x_n) - f(x_1, \ldots, x_i, \ldots, x_n)}{h}.$$ + +为了计算 $\frac{\partial y}{\partial x_i}$,我们可以简单地将 $x_1, \ldots, x_{i-1}, x_{i+1}, \ldots, x_n$ 看作常数,并计算 $y$关于$x_i$ 的导数。对于偏导数的表示,以下是等价的: + +$$\frac{\partial y}{\partial x_i} = \frac{\partial f}{\partial x_i} = f_{x_i} = f_i = D_i f = D_{x_i} f.$$ + +## 梯度 + +我们可以连结一个多元函数对其所有变量的偏导数,以得到该函数的*梯度*(gradient)向量。设函数 $f: \mathbb{R}^n \rightarrow \mathbb{R}$ 的输入是一个 $n$ 维向量 $\mathbf{x} = [x_1, x_2, \ldots, x_n]^\top$,并且输出是一个标量。 +函数$f(\mathbf{x})$相对于$\mathbf{x}$的梯度是一个包含$n$个偏导数的向量: + +$$\nabla_{\mathbf{x}} f(\mathbf{x}) = \bigg[\frac{\partial f(\mathbf{x})}{\partial x_1}, \frac{\partial f(\mathbf{x})}{\partial x_2}, \ldots, \frac{\partial f(\mathbf{x})}{\partial x_n}\bigg]^\top,$$ + +其中 $\nabla_{\mathbf{x}} f(\mathbf{x})$ 通常在没有歧义时被 $\nabla f(\mathbf{x})$ 取代。 + +假设$\mathbf{x}$为$n$维向量,在微分多元函数时经常使用以下规则: + +* 对于所有$\mathbf{A} \in \mathbb{R}^{m \times n}$,都有 $\nabla_{\mathbf{x}} \mathbf{A} \mathbf{x} = \mathbf{A}^\top$ +* 对于所有$\mathbf{A} \in \mathbb{R}^{n \times m}$,都有 $\nabla_{\mathbf{x}} \mathbf{x}^\top \mathbf{A} = \mathbf{A}$ +* 对于所有$\mathbf{A} \in \mathbb{R}^{n \times n}$,都有 $\nabla_{\mathbf{x}} \mathbf{x}^\top \mathbf{A} \mathbf{x} = (\mathbf{A} + \mathbf{A}^\top)\mathbf{x}$ +* $\nabla_{\mathbf{x}} \|\mathbf{x} \|^2 = \nabla_{\mathbf{x}} \mathbf{x}^\top \mathbf{x} = 2\mathbf{x}$ + +同样,对于任何矩阵 $\mathbf{X}$,我们都有 $\nabla_{\mathbf{X}} \|\mathbf{X} \|_F^2 = 2\mathbf{X}$。正如我们之后将看到的,梯度对于设计深度学习中的优化算法有很大用处。 + +## 链式法则 + +然而,上面方法可能很难找到梯度。 +这是因为在深度学习中,多元函数通常是 *复合*(composite)的,所以我们可能没法应用上述任何规则来微分这些函数。 +幸运的是,链式法则使我们能够微分复合函数。 + +让我们先考虑单变量函数。假设函数 $y=f(u)$ 和 $u=g(x)$ 都是可微的,根据链式法则: + +$$\frac{dy}{dx} = \frac{dy}{du} \frac{du}{dx}.$$ + +现在让我们把注意力转向一个更一般的场景,即函数具有任意数量的变量的情况。假设可微分函数 $y$ 有变量 $u_1, u_2, \ldots, u_m$,其中每个可微分函数 $u_i$ 都有变量 $x_1, x_2, \ldots, x_n$。注意,$y$是 $x_1, x_2, \ldots, x_n$ 的函数。对于任意 $i = 1, 2, \ldots, n$,链式法则给出: + +$$\frac{dy}{dx_i} = \frac{dy}{du_1} \frac{du_1}{dx_i} + \frac{dy}{du_2} \frac{du_2}{dx_i} + \cdots + \frac{dy}{du_m} \frac{du_m}{dx_i}$$ + +## 小结 + +* 微分和积分是微积分的两个分支,其中前者可以应用于深度学习中无处不在的优化问题。 +* 导数可以被解释为函数相对于其变量的瞬时变化率。它也是函数曲线的切线的斜率。 +* 梯度是一个向量,其分量是多变量函数相对于其所有变量的偏导数。 +* 链式法则使我们能够微分复合函数。 + +## 练习 + +1. 绘制函数$y = f(x) = x^3 - \frac{1}{x}$和其在$x = 1$处切线的图像。 +1. 求函数$f(\mathbf{x}) = 3x_1^2 + 5e^{x_2}$的梯度。 +1. 函数$f(\mathbf{x}) = \|\mathbf{x}\|_2$的梯度是什么? +1. 你可以写出函数$u = f(x, y, z)$,其中$x = x(a, b)$,$y = y(a, b)$,$z = z(a, b)$的链式法则吗? + + +[Discussions](https://discuss.d2l.ai/t/1756) + diff --git "a/Dive-into-DL-paddlepaddle/docs/2_Preparatory-knowledge/2.5\350\207\252\345\212\250\346\261\202\345\257\274.md" "b/Dive-into-DL-paddlepaddle/docs/2_Preparatory-knowledge/2.5\350\207\252\345\212\250\346\261\202\345\257\274.md" new file mode 100644 index 000000000..d3e68f479 --- /dev/null +++ "b/Dive-into-DL-paddlepaddle/docs/2_Preparatory-knowledge/2.5\350\207\252\345\212\250\346\261\202\345\257\274.md" @@ -0,0 +1,165 @@ +# 自动求导 +:label:`sec_autograd` + +正如我们在 :numref:`sec_calculus` 中所说的那样,求导是几乎所有深度学习优化算法的关键步骤。虽然求导的计算很简单,只需要一些基本的微积分,但对于复杂的模型,手工进行更新是一件很痛苦的事情(而且经常容易出错)。 + +深度学习框架通过自动计算导数,即 *自动求导* (automatic differentiation),来加快这项工作。实际中,根据我们设计的模型,系统会构建一个 *计算图* (computational graph),来跟踪计算是哪些数据通过哪些操作组合起来产生输出。自动求导使系统能够随后反向传播梯度。 +这里,*反向传播*(backpropagate)只是意味着跟踪整个计算图,填充关于每个参数的偏导数。 + + +## 一个简单的例子 + +作为一个演示例子,(**假设我们想对函数 $y = 2\mathbf{x}^{\top}\mathbf{x}$关于列向量 $\mathbf{x}$求导**)。首先,我们创建变量 `x` 并为其分配一个初始值。 + + + +```python +import paddle + +x = paddle.arange(4, dtype='float32') +x +``` + +[**在我们计算$y$关于$\mathbf{x}$的梯度之前,我们需要一个地方来存储梯度。**] +重要的是,我们不会在每次对一个参数求导时都分配新的内存。因为我们经常会成千上万次地更新相同的参数,每次都分配新的内存可能很快就会将内存耗尽。注意,标量函数关于向量$\mathbf{x}$的梯度是向量,并且与$\mathbf{x}$具有相同的形状。 + + + +```python +x = paddle.to_tensor(x, stop_gradient=False) +x.grad # 默认值是None +``` + +(**现在让我们计算 $y$。**) + + + +```python +y = 2 * paddle.dot(x, x) +y +``` + +`x` 是一个长度为 4 的向量,计算 `x` 和 `x` 的内积,得到了我们赋值给 `y` 的标量输出。接下来,我们可以[**通过调用反向传播函数来自动计算`y`关于`x` 每个分量的梯度**],并打印这些梯度。 + + + +```python +y.backward() +x.grad +``` + +函数 $y = 2\mathbf{x}^{\top}\mathbf{x}$ 关于$\mathbf{x}$ 的梯度应为$4\mathbf{x}$。让我们快速验证我们想要的梯度是否正确计算。 + + + +```python +x.grad == 4 * x +``` + +[**现在让我们计算 `x` 的另一个函数。**] + + + +```python +# 在默认情况下,PaddlePaddle会累积梯度,我们需要清除之前的值 +x.clear_gradient() +y = paddle.sum(x) +y.backward() +x.grad +``` + +## 非标量变量的反向传播 + +当 `y` 不是标量时,向量`y`关于向量`x`的导数的最自然解释是一个矩阵。对于高阶和高维的 `y` 和 `x`,求导的结果可以是一个高阶张量。 + +然而,虽然这些更奇特的对象确实出现在高级机器学习中(包括[**深度学习中**]),但当我们调用向量的反向计算时,我们通常会试图计算一批训练样本中每个组成部分的损失函数的导数。这里(**,我们的目的不是计算微分矩阵,而是批量中每个样本单独计算的偏导数之和。**) + + + +```python +x.clear_gradient() +y = x * x +paddle.sum(y).backward() +x.grad +``` + +## 分离计算 + +有时,我们希望[**将某些计算移动到记录的计算图之外**]。 +例如,假设`y`是作为`x`的函数计算的,而`z`则是作为`y`和`x`的函数计算的。 +现在,想象一下,我们想计算 `z` 关于 `x` 的梯度,但由于某种原因,我们希望将 `y` 视为一个常数,并且只考虑到 `x` 在`y`被计算后发挥的作用。 + +在这里,我们可以分离 `y` 来返回一个新变量 `u`,该变量与 `y` 具有相同的值,但丢弃计算图中如何计算 `y` 的任何信息。换句话说,梯度不会向后流经 `u` 到 `x`。因此,下面的反向传播函数计算 `z = u * x` 关于 `x` 的偏导数,同时将 `u` 作为常数处理,而不是`z = x * x * x`关于 `x` 的偏导数。 + + + +```python +x.clear_gradient() +y = x * x +u = y.detach() +z = u * x + +paddle.sum(z).backward() +x.grad == u +``` + +由于记录了 `y` 的计算结果,我们可以随后在 `y` 上调用反向传播,得到 `y = x * x` 关于的`x`的导数,这里是 `2 * x`。 + + + +```python +x.clear_gradient() +paddle.sum(y).backward() +x.grad == 2 * x +``` + +## Python控制流的梯度计算 + +使用自动求导的一个好处是,[**即使构建函数的计算图需要通过 Python控制流(例如,条件、循环或任意函数调用),我们仍然可以计算得到的变量的梯度**]。在下面的代码中,`while` 循环的迭代次数和 `if` 语句的结果都取决于输入 `a` 的值。 + + + +```python +def f(a): + b = a * 2 + while paddle.norm(b) < 1000: + b = b * 2 + if paddle.sum(b) > 0: + c = b + else: + c = 100 * b + return c +``` + +让我们计算梯度。 + + +```python +a = paddle.to_tensor(paddle.randn(shape=[1]), stop_gradient=False) +d = f(a) +d.backward() +``` + +我们现在可以分析上面定义的 `f` 函数。请注意,它在其输入 `a` 中是分段线性的。换言之,对于任何 `a`,存在某个常量标量 `k`,使得 `f(a) = k * a`,其中 `k` 的值取决于输入 `a`。因此,`d / a` 允许我们验证梯度是否正确。 + + + +```python +a.grad == d / a +``` + +## 小结 + +* 深度学习框架可以自动计算导数。为了使用它,我们首先将梯度附加到想要对其计算偏导数的变量上。然后我们记录目标值的计算,执行它的反向传播函数,并访问得到的梯度。 + +## 练习 + +1. 为什么计算二阶导数比一阶导数的开销要更大? +1. 在运行反向传播函数之后,立即再次运行它,看看会发生什么。 +1. 在控制流的例子中,我们计算 `d` 关于 `a` 的导数,如果我们将变量 `a` 更改为随机向量或矩阵,会发生什么?此时,计算结果 `f(a)` 不再是标量。结果会发生什么?我们如何分析这个结果? +1. 重新设计一个求控制流梯度的例子。运行并分析结果。 +1. 使$f(x) = \sin(x)$,绘制$f(x)$ 和$\frac{df(x)}{dx}$的图像,其中后者不使用$f'(x) = \cos(x)$。 + + +[Discussions](https://discuss.d2l.ai/t/1759) + diff --git "a/Dive-into-DL-paddlepaddle/docs/2_Preparatory-knowledge/2.6\346\246\202\347\216\207.md" "b/Dive-into-DL-paddlepaddle/docs/2_Preparatory-knowledge/2.6\346\246\202\347\216\207.md" new file mode 100644 index 000000000..ccc0e308f --- /dev/null +++ "b/Dive-into-DL-paddlepaddle/docs/2_Preparatory-knowledge/2.6\346\246\202\347\216\207.md" @@ -0,0 +1,384 @@ +# 概率 +:label:`sec_prob` + +简单地说,机器学习就是做出预测。 + +根据病人的临床病史,我们可能想预测他们在下一年心脏病发作的*概率*。 +在飞机喷气发动机的异常检测中,我们想要评估一组发动机读数为正常运行情况的概率有多大。 +在强化学习中,我们希望智能体(agent)能在一个环境中智能地行动。 +这意味着我们需要考虑在每种可行的行为下获得高奖励的概率。 +当我们建立推荐系统时,我们也需要考虑概率。 +例如,假设我们为一家大型在线书店工作,我们可能希望估计某些用户购买特定图书的概率。 +为此,我们需要使用概率学。 +有完整的课程、专业、论文、职业、甚至院系,都致力于概率学的工作。 +所以很自然地,我们在这部分的目标不是教授你整个科目。 +相反,我们希望教给你在基础的概率知识,使你能够开始构建你的第一个深度学习模型, +以便你可以开始自己探索它。 + +现在让我们更认真地考虑第一个例子:根据照片区分猫和狗。 +这听起来可能很简单,但对于机器却可能是一个艰巨的挑战。 +首先,问题的难度可能取决于图像的分辨率。 + +![不同分辨率的图像 ($10 \times 10$, $20 \times 20$, $40 \times 40$, $80 \times 80$, 和 $160 \times 160$ pixels)](../img/cat-dog-pixels.png) +:width:`300px` +:label:`fig_cat_dog` + +如 :numref:`fig_cat_dog`所示,虽然人类很容易以$160 \times 160$像素的分辨率识别猫和狗, +但它在$40\times40$像素上变得具有挑战性,而且在$10 \times 10$像素下几乎是不可能的。 +换句话说,我们在很远的距离(从而降低分辨率)区分猫和狗的能力可能会变为猜测。 +概率给了我们一种正式的途径来说明我们的确定性水平。 +如果我们完全肯定图像是一只猫,我们说标签$y$是"猫"的*概率*,表示为$P(y=$"猫"$)$等于$1$。 +如果我们没有证据表明$y=$“猫”或$y=$“狗”,那么我们可以说这两种可能性是相等的, +即$P(y=$"猫"$)=P(y=$"狗"$)=0.5$。 +如果我们不十分确定图像描绘的是一只猫,我们可以将概率赋值为$0.5 1$时的$\mathcal{A}_i = \emptyset$, +那么我们可以证明$P(\emptyset) = 0$,即不可能发生事件的概率是$0$。 + +### 随机变量 + +在我们掷骰子的随机实验中,我们引入了*随机变量*(random variable)的概念。 +随机变量几乎可以是任何数量,并且它可以在随机实验的一组可能性中取一个值。 +考虑一个随机变量$X$,其值在掷骰子的样本空间$\mathcal{S}=\{1,2,3,4,5,6\}$中。 +我们可以将事件“看到一个$5$”表示为$\{X=5\}$或$X=5$, +其概率表示为$P(\{X=5\})$或$P(X=5)$。 +通过$P(X=a)$,我们区分了随机变量$X$和$X$可以采取的值(例如$a$)。 +然而,这可能会导致繁琐的表示。 +为了简化符号,一方面,我们可以将$P(X)$表示为随机变量$X$上的*分布*(distribution): +分布告诉我们$X$获得某一值的概率。 +另一方面,我们可以简单用$P(a)$表示随机变量取值$a$的概率。 +由于概率论中的事件是来自样本空间的一组结果,因此我们可以为随机变量指定值的可取范围。 +例如,$P(1 \leq X \leq 3)$表示事件$\{1 \leq X \leq 3\}$, +即$\{X = 1, 2, \text{or}, 3\}$的概率。 +等价地,$P(1 \leq X \leq 3)$表示随机变量$X$从$\{1, 2, 3\}$中取值的概率。 + +请注意,*离散*(discrete)随机变量(如骰子的每一面) +和*连续*(continuous)随机变量(如人的体重和身高)之间存在微妙的区别。 +现实生活中,测量两个人是否具有完全相同的身高没有太大意义。 +如果我们进行足够精确的测量,你会发现这个星球上没有两个人具有完全相同的身高。 +在这种情况下,询问某人的身高是否落入给定的区间,比如是否在1.79米和1.81米之间更有意义。 +在这些情况下,我们将这个看到某个数值的可能性量化为*密度*(density)。 +高度恰好为1.80米的概率为0,但密度不是0。 +在任何两个不同高度之间的区间,我们都有非零的概率。 +在本节的其余部分中,我们将考虑离散空间中的概率。 +对于连续随机变量的概率,你可以参考深度学习数学附录中[随机变量](https://d2l.ai/chapter_appendix-mathematics-for-deep-learning/random-variables.html) +的一节。 + +## 处理多个随机变量 + +很多时候,我们会考虑多个随机变量。 +比如,我们可能需要对疾病和症状之间的关系进行建模。 +给定一个疾病和一个症状,比如“流感”和“咳嗽”,以某个概率存在或不存在于某个患者身上。 +我们需要估计这些概率以及概率之间的关系,以便我们可以运用我们的推断来实现更好的医疗服务。 + +再举一个更复杂的例子:图像包含数百万像素,因此有数百万个随机变量。 +在许多情况下,图像会附带一个*标签*(label),标识图像中的对象。 +我们也可以将标签视为一个随机变量。 +我们甚至可以将所有元数据视为随机变量,例如位置、时间、光圈、焦距、ISO、对焦距离和相机类型。 +所有这些都是联合发生的随机变量。 +当我们处理多个随机变量时,会有若干个变量是我们感兴趣的。 + +### 联合概率 + +第一个被称为*联合概率*(joint probability)$P(A=a,B=b)$。 +给定任意值$a$和$b$,联合概率可以回答:$A=a$和$B=b$同时满足的概率是多少? +请注意,对于任何$a$和$b$的取值,$P(A = a, B=b) \leq P(A=a)$。 +这点是确定的,因为要同时发生$A=a$和$B=b$,$A=a$就必须发生,$B=b$也必须发生(反之亦然)。因此,$A=a$和$B=b$同时发生的可能性不大于$A=a$或是$B=b$单独发生的可能性。 + +### 条件概率 + +联合概率的不等式带给我们一个有趣的比率: +$0 \leq \frac{P(A=a, B=b)}{P(A=a)} \leq 1$。 +我们称这个比率为*条件概率*(conditional probability), +并用$P(B=b \mid A=a)$表示它:它是$B=b$的概率,前提是$A=a$已发生。 + +### 贝叶斯定理 + +使用条件概率的定义,我们可以得出统计学中最有用的方程之一: +*Bayes定理*(Bayes' theorem)。 +根据*乘法法则*(multiplication rule )可得到$P(A, B) = P(B \mid A) P(A)$。 +根据对称性,可得到$P(A, B) = P(A \mid B) P(B)$。 +假设$P(B)>0$,求解其中一个条件变量,我们得到 + +$$P(A \mid B) = \frac{P(B \mid A) P(A)}{P(B)}.$$ + +请注意,这里我们使用紧凑的表示法: +其中$P(A, B)$是一个*联合分布*(joint distribution), +$P(A \mid B)$是一个*条件分布*(conditional distribution)。 +这种分布可以在给定值$A = a, B=b$上进行求值。 + +### 边际化 + +为了能进行事件概率求和,我们需要*求和法则*(sum rule), +即$B$的概率相当于计算$A$的所有可能选择,并将所有选择的联合概率聚合在一起: + +$$P(B) = \sum_{A} P(A, B),$$ + +这也称为*边际化*(marginalization)。 +边际化结果的概率或分布称为*边际概率*(marginal probability) +或*边际分布*(marginal distribution)。 + +### 独立性 + +另一个有用属性是*依赖*(dependence)与*独立*(independence)。 +如果两个随机变量$A$和$B$是独立的,意味着事件$A$的发生跟$B$事件的发生无关。 +在这种情况下,统计学家通常将这一点表述为$A \perp B$。 +根据贝叶斯定理,马上就能同样得到$P(A \mid B) = P(A)$。 +在所有其他情况下,我们称$A$和$B$依赖。 +比如,两次连续抛出一个骰子的事件是相互独立的。 +相比之下,灯开关的位置和房间的亮度并不是(因为可能存在灯泡坏掉、电源故障,或者开关故障)。 + +由于$P(A \mid B) = \frac{P(A, B)}{P(B)} = P(A)$等价于$P(A, B) = P(A)P(B)$, +因此两个随机变量是独立的,当且仅当两个随机变量的联合分布是其各自分布的乘积。 +同样地,给定另一个随机变量$C$时,两个随机变量$A$和$B$是*条件独立的*(conditionally independent), +当且仅当$P(A, B \mid C) = P(A \mid C)P(B \mid C)$。 +这个情况表示为$A \perp B \mid C$。 + +### 应用 +:label:`subsec_probability_hiv_app` + +我们实战演练一下! +假设一个医生对患者进行艾滋病病毒(HIV)测试。 +这个测试是相当准确的,如果患者健康但测试显示他患病,这个概率只有1%; +如果患者真正感染HIV,它永远不会检测不出。 +我们使用$D_1$来表示诊断结果(如果阳性,则为$1$,如果阴性,则为$0$), +$H$来表示感染艾滋病病毒的状态(如果阳性,则为$1$,如果阴性,则为$0$)。 +在 :numref:`conditional_prob_D1`中列出了这样的条件概率。 + +:条件概率为$P(D_1 \mid H)$ + +| 条件概率 | $H=1$ | $H=0$ | +|---|---|---| +|$P(D_1 = 1 \mid H)$| 1 | 0.01 | +|$P(D_1 = 0 \mid H)$| 0 | 0.99 | +:label:`conditional_prob_D1` + +请注意,每列的加和都是1(但每行的加和不是),因为条件概率需要总和为1,就像概率一样。 +让我们计算如果测试出来呈阳性,患者感染HIV的概率,即$P(H = 1 \mid D_1 = 1)$。 +显然,这将取决于疾病有多常见,因为它会影响错误警报的数量。 +假设人口总体是相当健康的,例如,$P(H=1) = 0.0015$。 +为了应用贝叶斯定理,我们需要运用边际化和乘法法则来确定 + +$$\begin{aligned} +&P(D_1 = 1) \\ +=& P(D_1=1, H=0) + P(D_1=1, H=1) \\ +=& P(D_1=1 \mid H=0) P(H=0) + P(D_1=1 \mid H=1) P(H=1) \\ +=& 0.011485. +\end{aligned} +$$ + +因此,我们得到 + +$$\begin{aligned} +&P(H = 1 \mid D_1 = 1)\\ =& \frac{P(D_1=1 \mid H=1) P(H=1)}{P(D_1=1)} \\ =& 0.1306 \end{aligned}.$$ + +换句话说,尽管使用了非常准确的测试,患者实际上患有艾滋病的几率只有13.06%。 +正如我们所看到的,概率可能是违反直觉的。 + +患者在收到这样可怕的消息后应该怎么办? +很可能,患者会要求医生进行另一次测试来确定病情。 +第二个测试具有不同的特性,它不如第一个测试那么精确, +如 :numref:`conditional_prob_D2`所示。 + +:条件概率为$P(D_2 \mid H)$ + +| 条件概率 | $H=1$ | $H=0$ | +|---|---|---| +|$P(D_2 = 1 \mid H)$| 0.98 | 0.03 | +|$P(D_2 = 0 \mid H)$| 0.02 | 0.97 | +:label:`conditional_prob_D2` + +不幸的是,第二次测试也显示阳性。让我们通过假设条件独立性来计算出应用Bayes定理的必要概率: + +$$\begin{aligned} +&P(D_1 = 1, D_2 = 1 \mid H = 0) \\ +=& P(D_1 = 1 \mid H = 0) P(D_2 = 1 \mid H = 0) \\ +=& 0.0003, +\end{aligned} +$$ + +$$\begin{aligned} +&P(D_1 = 1, D_2 = 1 \mid H = 1) \\ +=& P(D_1 = 1 \mid H = 1) P(D_2 = 1 \mid H = 1) \\ +=& 0.98. +\end{aligned} +$$ + +现在我们可以应用边际化和乘法规则: + +$$\begin{aligned} +&P(D_1 = 1, D_2 = 1) \\ +=& P(D_1 = 1, D_2 = 1, H = 0) + P(D_1 = 1, D_2 = 1, H = 1) \\ +=& P(D_1 = 1, D_2 = 1 \mid H = 0)P(H=0) + P(D_1 = 1, D_2 = 1 \mid H = 1)P(H=1)\\ +=& 0.00176955. +\end{aligned} +$$ + +最后,鉴于存在两次阳性检测,患者患有艾滋病的概率为 + +$$\begin{aligned} +&P(H = 1 \mid D_1 = 1, D_2 = 1)\\ +=& \frac{P(D_1 = 1, D_2 = 1 \mid H=1) P(H=1)}{P(D_1 = 1, D_2 = 1)} \\ +=& 0.8307. +\end{aligned} +$$ + +也就是说,第二次测试使我们能够对患病的情况获得更高的信心。 +尽管第二次检验比第一次检验的准确性要低得多,但它仍然显著提高我们的预测概率。 + +## 期望和方差 + +为了概括概率分布的关键特征,我们需要一些测量方法。 +一个随机变量$X$的*期望*(expectation,或平均值(average))表示为 + +$$E[X] = \sum_{x} x P(X = x).$$ + +当函数$f(x)$的输入是从分布$P$中抽取的随机变量时,$f(x)$的期望值为 + +$$E_{x \sim P}[f(x)] = \sum_x f(x) P(x).$$ + +在许多情况下,我们希望衡量随机变量$X$与其期望值的偏置。这可以通过方差来量化 + +$$\mathrm{Var}[X] = E\left[(X - E[X])^2\right] = +E[X^2] - E[X]^2.$$ + +方差的平方根被称为*标准差*(standared deviation)。 +随机变量函数的方差衡量的是:当从该随机变量分布中采样不同值$x$时, +函数值偏离该函数的期望的程度: + +$$\mathrm{Var}[f(x)] = E\left[\left(f(x) - E[f(x)]\right)^2\right].$$ + +## 小结 + +* 我们可以从概率分布中采样。 +* 我们可以使用联合分布、条件分布、Bayes定理、边缘化和独立性假设来分析多个随机变量。 +* 期望和方差为概率分布的关键特征的概括提供了实用的度量形式。 + +## 练习 + +1. 进行$m=500$组实验,每组抽取$n=10$个样本。改变$m$和$n$,观察和分析实验结果。 +2. 给定两个概率为$P(\mathcal{A})$和$P(\mathcal{B})$的事件,计算$P(\mathcal{A} \cup \mathcal{B})$和$P(\mathcal{A} \cap \mathcal{B})$的上限和下限。(提示:使用[友元图](https://en.wikipedia.org/wiki/Venn_diagram)来展示这些情况。) +3. 假设我们有一系列随机变量,例如$A$、$B$和$C$,其中$B$只依赖于$A$,而$C$只依赖于$B$,你能简化联合概率$P(A, B, C)$吗?(提示:这是一个[马尔可夫链](https://en.wikipedia.org/wiki/Markov_chain)。) +4. 在 :numref:`subsec_probability_hiv_app`中,第一个测试更准确。为什么不运行第一个测试两次,而是同时运行第一个和第二个测试? + + +[Discussions](https://discuss.d2l.ai/t/1762) + diff --git "a/Dive-into-DL-paddlepaddle/docs/2_Preparatory-knowledge/2.7\346\237\245\351\230\205\346\226\207\346\241\243.md" "b/Dive-into-DL-paddlepaddle/docs/2_Preparatory-knowledge/2.7\346\237\245\351\230\205\346\226\207\346\241\243.md" new file mode 100644 index 000000000..230ae582c --- /dev/null +++ "b/Dive-into-DL-paddlepaddle/docs/2_Preparatory-knowledge/2.7\346\237\245\351\230\205\346\226\207\346\241\243.md" @@ -0,0 +1,52 @@ +# 查阅文档 + + +由于本书篇幅的限制,我们不可能介绍每一个PaddlePaddle函数和类(你可能也不希望我们这样做)。API文档、其他教程和示例提供了本书之外的大量文档。在本节中,我们为你提供了一些查看PaddlePaddle API的指导。 + + +## 查找模块中的所有函数和类 + +为了知道模块中可以调用哪些函数和类,我们调用 `dir` 函数。例如,我们可以(**查询随机数生成模块中的所有属性:**) + + + +```python +import paddle + +print(dir(paddle.distribution)) +``` + +通常,我们可以忽略以 `__` 开始和结束的函数(Python 中的特殊对象)或以单个 `_` 开始的函数(通常是内部函数)。根据剩余的函数名或属性名,我们可能会猜测这个模块提供了各种生成随机数的方法,包括从均匀分布(`uniform`)、正态分布 (`normal`)和多项分布(`multinomial`)中采样。 + +## 查找特定函数和类的用法 + +有关如何使用给定函数或类的更具体说明,我们可以调用 `help` 函数。例如,我们来[**查看张量 `ones` 函数的用法。**] + + + +```python +help(paddle.ones) +``` + +从文档中,我们可以看到 `ones` 函数创建一个具有指定形状的新张量,并将所有元素值设置为 1。让我们来[**运行一个快速测试**]来确认这一解释: + + + +```python +paddle.ones([4], dtype='float32') +``` + +在Jupyter记事本中,我们可以使用 `?` 在另一个窗口中显示文档。例如,`list?`将创建与`help(list)` 几乎相同的内容,并在新的浏览器窗口中显示它。此外,如果我们使用两个问号,如 `list??`,将显示实现该函数的 Python 代码。 + +## 小结 + +* 官方文档提供了本书之外的大量描述和示例。 +* 我们可以通过调用 `dir` 和 `help` 函数或在Jupyter记事本中使用`?` 和 `??`查看API的用法文档。 + +## 练习 + +1. 在深度学习框架中查找任何函数或类的文档。你能在这个框架的官方网站上找到文档吗? + + +[Discussions](https://discuss.d2l.ai/t/1765) +