Skip to content

#d2l update chapter2 #894

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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[:] = <expression>`。为了说明这一点,我们首先创建一个新的矩阵 `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)

Original file line number Diff line number Diff line change
@@ -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)

Large diffs are not rendered by default.

242 changes: 242 additions & 0 deletions Dive-into-DL-paddlepaddle/docs/2_Preparatory-knowledge/2.4微分.md
Original file line number Diff line number Diff line change
@@ -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)

Original file line number Diff line number Diff line change
@@ -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)

384 changes: 384 additions & 0 deletions Dive-into-DL-paddlepaddle/docs/2_Preparatory-knowledge/2.6概率.md

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -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)