《动手学深度学习》学习笔记 Ch.2 - 预备知识 (2.4-2.7)

2.4. 微积分

我们可以将拟合模型的任务分解为两个关键问题:

  • 优化(optimization):用模型拟合观测数据的过程;
  • 泛化(generalization):数学原理和实践者的智慧,能够指导我们生成出有效性超出用于训练的数据集本身的模型。

2.4.1. 导数和微分

假设我们有一个函数$f:R^n→R$,其输入和输出都是标量。 如果ff的导数存在,这个极限被定义为
$$
f’(x) = \lim_{h \to 0} \frac{f(x+h) - f(x)}{h}
$$
如果$f’(a)$存在,则称$f$在$a$处是可微(differentiable)的。如果$f$在一个区间内的每个数上都是可微的,则此函数在此区间中是可微的。 我们可以将 (2.4.1)中的导数$f′(x)$解释为$f(x)$相对于$x$的瞬时(instantaneous)变化率。 所谓的瞬时变化率是基于$x$中的变化$h$,且$h$接近$0$。

给定$y=f(x)$,其中$x$和$y$分别是函数$f$的自变量和因变量。以下表达式是等价的:
$$
f’(x) = y’ = \frac{dy}{dx} = \frac{df}{dx} = = \frac{d}{dx}f(x) = Df(x) = D_xf(x)
$$
为了微分一个由一些常见函数组成的函数,下面的一些法则方便使用。 假设函数$f$和$g$都是可微的,$C$是一个常数,则:

  1. 常数相乘法则

$$
\frac{d}{dx}|Cf(x)| = C\frac{d}{dx}f(x)
$$

  1. 加法法则

$$
\frac{d}{dx}|f(x) + g(x)| = \frac{d}{dx}f(x) + \frac{d}{dx}g(x)
$$

  1. 乘法法则

$$
\frac{d}{dx}|f(x)g(x)| = f(x)\frac{d}{dx}|g(x)| + g(x)\frac{d}{dx}|f(x)|
$$

  1. 除法法则

$$
\frac{d}{dx}[\frac{f(x)}{g(x)}] = \frac{g(x)\frac{d}{dx}|f(x)| - f(x) \frac{d}{dx}|g(x)| }{ |g(x)|^2 }
$$

2.4.2. 偏导数

到目前为止,我们只讨论了仅含一个变量的函数的微分。 在深度学习中,函数通常依赖于许多变量。 因此,我们需要将微分的思想推广到多元函数(multivariate function)上。

设$y=f(x_1,x_2,…,x_n)$是一个具有$n$个变量的函数。 $y$关于第$i$个参数$x_i$的偏导数(partial derivative)为:
$$
\frac{\partial y}{\partial x_i} = \lim_{h \to 0} \frac{f(x_1, …, x_{i-1}, x_i+h, x_{i+1},…,x_n) - f(x_1, …, x_i, …, x_n)}{h}
$$
为了计算$\frac{\partial y}{\partial x_i} $, 我们可以简单地将$x_1,…,x_{i−1},x_{i+1},…,x_n$看作常数, 并计算$y$关于$x_i$的导数。 对于偏导数的表示,以下是等价的:
$$
\frac{\partial y}{\partial x_i} = \frac{\partial f}{\partial x_i} = f_{x_i} = f_i = D_if = D_{x_i}f
$$

2.4.3. 梯度

我们可以连结一个多元函数对其所有变量的偏导数,以得到该函数的梯度(gradient)向量。 具体而言,设函数$f:R^n→R$的输入是 一个$n$维向量$x=[x_1,x_2,…,x_n]^T$,并且输出是一个标量。 函数$f(x)$相对于$x$的梯度是一个包含$n$个偏导数的向量:
$$
\nabla _xf(x) = [\frac{\partial f(x)}{\partial x_1},\frac{\partial f(x)}{\partial x_2}, …, \frac{\partial f(x)}{\partial x_n}]^T
$$
其中$∇_xf(x)$通常在没有歧义时被$∇f(x)$取代。

假设$x$为$n$维向量,在微分多元函数时经常使用以下规则:

  • 对于所有$A∈R^{m×n}$,都有$∇_xAx=A^T$
  • 对于所有$A∈R^{n×m}$,都有$∇_xx^TA=A$
  • 对于所有$A∈R^{n×n}$,都有$∇_xx^TAx=(A+A^T)x$
  • $∇_x||x||^2=∇_xx^Tx=2x$

同样,对于任何矩阵$X$,都有$∇_X∥X∥^2_F=2X$。 正如我们之后将看到的,梯度对于设计深度学习中的优化算法有很大用处。

2.4.4. 链式法则

在深度学习中,多元函数通常是复合(composite)的, 所以我们可能没法应用上述任何规则来微分这些函数。 幸运的是,链式法则使我们能够微分复合函数。

假设函数$y=f(u)$和$u=g(x)$都是可微的,根据链式法则:
$$
\frac{\mathrm{d} y}{\mathrm{d} x} = \frac{\mathrm{d} y}{\mathrm{d} u} \frac{\mathrm{d} u}{\mathrm{d} x}
$$
假设可微分函数$y$有变量$u_1,u_2,…,u_m$,其中每个可微分函数$u_i$都有变量$x_1,x_2,…,x_n$。 注意,$y$是$x_1,x_2,…,x_n$的函数。 对于任意$i=1,2,…,n$,链式法则给出:
$$
\frac{\mathrm{d} y}{\mathrm{d} x_i} = \frac{\mathrm{d} y}{\mathrm{d} u_1} \frac{\mathrm{d} u_1}{\mathrm{d} x_i}+ \frac{\mathrm{d} y}{\mathrm{d} u_2} \frac{\mathrm{d} u_2}{\mathrm{d} x_i} + … + \frac{\mathrm{d} y}{\mathrm{d} u_m} \frac{\mathrm{d} u_m}{\mathrm{d} x_i}
$$

2.4.5. 小结

  • 微分和积分是微积分的两个分支,前者可以应用于深度学习中的优化问题。
  • 导数可以被解释为函数相对于其变量的瞬时变化率,它也是函数曲线的切线的斜率。
  • 梯度是一个向量,其分量是多变量函数相对于其所有变量的偏导数。
  • 链式法则使我们能够微分复合函数。

2.5. 自动微分

深度学习框架通过自动计算导数,即自动微分(automatic differentiation)来加快求导。

实际中,根据我们设计的模型,系统会构建一个计算图(computational graph), 来跟踪计算是哪些数据通过哪些操作组合起来产生输出。 自动微分使系统能够随后反向传播梯度。 这里,反向传播(backpropagate)意味着跟踪整个计算图,填充关于每个参数的偏导数。

2.5.1. 一个简单的例子

作为一个演示例子,假设我们想对函数$y=2x^Tx$关于列向量$x$求导。

首先,我们创建变量x并为其分配一个初始值。

1
2
3
4
5
6
7
8
import torch

x = torch.arange(4.0)
x

'''
tensor([0., 1., 2., 3.])
'''

在我们计算$y$关于$x$的梯度之前,我们需要一个地方来存储梯度。

重要的是,我们不会在每次对一个参数求导时都分配新的内存。 因为我们经常会成千上万次地更新相同的参数,每次都分配新的内存可能很快就会将内存耗尽。

1
2
3
4
5
6
7
8
9
x.requires_grad_(True)  # 等价于x=torch.arange(4.0,requires_grad=True)
x.grad # 默认值是None

# 现在让我们计算y。
y = 2 * torch.dot(x, x)
y
'''
tensor(28., grad_fn=<MulBackward0>)
'''

x是一个长度为4的向量,计算xx的点积,得到了我们赋值给y的标量输出。 接下来,我们通过调用反向传播函数来自动计算y关于x每个分量的梯度,并打印这些梯度。

1
2
3
4
5
6
y.backward()
x.grad

'''
tensor([ 0., 4., 8., 12.])
'''

函数$y=2x^Tx$关于$x$的梯度应为$4x$。 让我们快速验证这个梯度是否计算正确。

1
2
3
4
5
x.grad == 4 * x

'''
tensor([True, True, True, True])
'''

现在让我们计算x的另一个函数。

1
2
3
4
5
6
7
8
9
# 在默认情况下,PyTorch会累积梯度,我们需要清除之前的值
x.grad.zero_()
y = x.sum()
y.backward()
x.grad

'''
tensor([1., 1., 1., 1.])
'''

2.5.2. 非标量变量的反向传播

y不是标量时,向量y关于向量x的导数的最自然解释是一个矩阵。 对于高阶和高维的yx,求导的结果可以是一个高阶张量。

1
2
3
4
5
6
7
8
9
10
11
# 对非标量调用backward需要传入一个gradient参数,该参数指定微分函数关于self的梯度。
# 在我们的例子中,我们只想求偏导数的和,所以传递一个1的梯度是合适的
x.grad.zero_()
y = x * x
# 等价于y.backward(torch.ones(len(x)))
y.sum().backward()
x.grad

'''
tensor([0., 2., 4., 6.])
'''

2.5.3. 分离计算

有时,我们希望将某些计算移动到记录的计算图之外。 例如,假设y是作为x的函数计算的,而z则是作为yx的函数计算的。 想象一下,我们想计算z关于x的梯度,但由于某种原因,我们希望将y视为一个常数, 并且只考虑到xy被计算后发挥的作用。

在这里,我们可以分离y来返回一个新变量u,该变量与y具有相同的值, 但丢弃计算图中如何计算y的任何信息。

1
2
3
4
5
6
7
8
9
10
11
x.grad.zero_()
y = x * x
u = y.detach()
z = u * x

z.sum().backward()
x.grad == u

'''
tensor([True, True, True, True])
'''

由于记录了y的计算结果,我们可以随后在y上调用反向传播, 得到y=x*x关于的x的导数,即2*x

1
2
3
4
5
6
7
x.grad.zero_()
y.sum().backward()
x.grad == 2 * x

'''
tensor([True, True, True, True])
'''

2.5.4. Python控制流的梯度计算

(个人理解是:Pytorch在自动微分的应用是,一个数学函数可以由Python的函数来定义)

使用自动微分的一个好处是: 即使构建函数的计算图需要通过Python控制流(例如,条件、循环或任意函数调用),我们仍然可以计算得到的变量的梯度。 在下面的代码中,while循环的迭代次数和if语句的结果都取决于输入a的值。

1
2
3
4
5
6
7
8
9
def f(a):
b = a * 2
while b.norm() < 1000:
b = b * 2
if b.sum() > 0:
c = b
else:
c = 100 * b
return c

让我们计算梯度。

1
2
3
a = torch.randn(size=(), requires_grad=True)
d = f(a)
d.backward()

我们现在可以分析上面定义的f函数。 请注意,它在其输入a中是分段线性的。 换言之,对于任何a,存在某个常量标量k,使得f(a)=k*a,其中k的值取决于输入a。 因此,我们可以用d/a验证梯度是否正确。

1
2
3
4
a.grad == d / a
'''
tensor(True)
'''

2.5.5. 小结

  • 深度学习框架可以自动计算导数:我们首先将梯度附加到想要对其计算偏导数的变量上。然后我们记录目标值的计算,执行它的反向传播函数,并访问得到的梯度。

2.6. 概率

2.6.1. 基本概率论

对于每个骰子,我们将观察到$\lbrace 1,…,6 \rbrace$中的一个值。对于每个值,一种自然的方法是将它出现的次数除以投掷的总次数, 即此事件(event)概率的估计值大数定律(law of large numbers)告诉我们: 随着投掷次数的增加,这个估计值会越来越接近真实的潜在概率。

在统计学中,我们把从概率分布中抽取样本的过程称为抽样(sampling)。 笼统来说,可以把分布(distribution)看作是对事件的概率分配, 稍后我们将给出的更正式定义。 将概率分配给一些离散选择的分布称为多项分布(multinomial distribution)。

2.6.1.1. 概率论公理

在处理骰子掷出时,我们将集合$S=\lbrace 1,2,3,4,5,6 \rbrace$称为样本空间(sample space)或结果空间(outcome space), 其中每个元素都是结果(outcome)。 事件(event)是一组给定样本空间的随机结果。

概率(probability)可以被认为是将集合映射到真实值的函数。 在给定的样本空间$S$中,事件$A$的概率, 表示为$P(A)$,满足以下属性:

  • 对于任意事件$A$,其概率从不会是负数,即$P(A)≥0$;
  • 整个样本空间的概率为$1$,即$P(S)=1$;
  • 对于互斥(mutually exclusive)事件(对于所有$i≠j$都有$A_i∩A_j=∅$)的任意一个可数序列$A1,A2,…$,序列中任意一个事件发生的概率等于它们各自发生的概率之和,即$P(⋃^∞_{i=1}A_i)=∑^∞_{i=1}P(A_i) $。

以上也是概率论的公理,由科尔莫戈罗夫于1933年提出。有了这个公理系统,我们可以避免任何关于随机性的哲学争论; 相反,我们可以用数学语言严格地推理。 例如,假设事件$A_1$为整个样本空间, 且当所有$i>1$时的$A_i=∅$, 那么我们可以证明$P(∅)=0$,即不可能发生事件的概率是0。

2.6.1.2. 随机变量

在我们掷骰子的随机实验中,我们引入了随机变量(random variable)的概念。随机变量几乎可以是任何数量,并且它可以在随机实验的一组可能性中取一个值。 考虑一个随机变量$X$,其值在掷骰子的样本空间$S=\left{ 1,2,3,4,5,6 \rbrace$中。 我们可以将事件“看到一个$5$”表示为$\left{X=5\right}$或$(X=5)$, 其概率表示为$P\left{X=5\right}$或$P(X=5)$。

2.6.2. 处理多个随机变量

2.6.2.1. 联合概率

联合概率(joint probability)$P(A=a,B=b)$。 给定任意值$a$和$b$,联合概率可以回答:$A=a$和$B=b$同时满足的概率是多少?

2.6.2.2. 条件概率

联合概率的不等式带给我们一个有趣的比率: $0≤\frac{P(A=a,B=b)}{P(A=a)}≤1$。 我们称这个比率为条件概率(conditional probability), 并用$P(B=b∣A=a)$表示它:它是$B=b$的概率,前提是$A=a$已发生。

2.6.2.3. 贝叶斯定理

使用条件概率的定义,我们可以得出统计学中最有用的方程之一: Bayes定理(Bayes’ theorem)。 根据乘法法则(multiplication rule )可得到$P(A,B)=P(B∣A)P(A)$。 根据对称性,可得到$P(A,B)=P(A∣B)P(B)$。 假设$P(B)>0$,求解其中一个条件变量,我们得到
$$
P(A|B) = \frac{P(B|A)P(A)}{P(B)}
$$
请注意,这里我们使用紧凑的表示法: 其中$P(A,B)$是一个联合分布(joint distribution), $P(A∣B)$是一个条件分布(conditional distribution)。 这种分布可以在给定值$A=a,B=b$上进行求值。

2.6.2.4. 边际化

为了能进行事件概率求和,我们需要求和法则(sum rule), 即$B$的概率相当于计算$A$的所有可能选择,并将所有选择的联合概率聚合在一起:
$$
P(B) = \sum_{A}P(A,B)
$$
这也称为边际化(marginalization)。 边际化结果的概率或分布称为边际概率(marginal probability) 或边际分布(marginal distribution)。

2.6.2.5. 独立性

另一个有用属性是依赖(dependence)与独立(independence)。 如果两个随机变量$A$和$B$是独立的,意味着事件AA的发生跟BB事件的发生无关。 在这种情况下,统计学家通常将这一点表述为$A⊥B$。 根据贝叶斯定理,马上就能同样得到$P(A∣B)=P(A)$。

2.6.2.6. 应用

是一个计算题的例子,略

2.6.3. 期望和方差

为了概括概率分布的关键特征,我们需要一些测量方法。 一个随机变量XX的期望(expectation,或平均值(average))表示为
$$
E|X| = \sum_{x}xP(X=x)
$$
当函数$f(x)$的输入是从分布$P$中抽取的随机变量时,$f(x)$的期望值为
$$
E_{x∼P}[f(x)]=\sum_{x}f(x)P(x).
$$
在许多情况下,我们希望衡量随机变量$X$与其期望值的偏置。这可以通过方差来量化
$$
Var[X]=E[(X−E[X])^2]=E[X^2]−E[X]^2.
$$
方差的平方根被称为标准差(standard deviation)。 随机变量函数的方差衡量的是:当从该随机变量分布中采样不同值$x$时, 函数值偏离该函数的期望的程度:
$$
Var[f(x)]=E[(f(x)−E[f(x)])^2].
$$

2.6.4. 小结

  • 我们可以从概率分布中采样。
  • 我们可以使用联合分布、条件分布、Bayes定理、边缘化和独立性假设来分析多个随机变量。
  • 期望和方差为概率分布的关键特征的概括提供了实用的度量形式。

2.7. 查阅文档

由于本书篇幅限制,我们不可能介绍每一个PyTorch函数和类(你可能也不希望我们这样做)。 API文档、其他教程和示例提供了本书之外的大量文档。 在本节中,我们为你提供了一些查看PyTorch API的指导。

2.7.1. 查找模块中的所有函数和类

为了知道模块中可以调用哪些函数和类,我们调用dir函数。 例如,我们可以查询随机数生成模块中的所有属性:

1
2
3
4
5
6
7
8
import torch

print(dir(torch.distributions))

'''
['AbsTransform', 'AffineTransform', 'Bernoulli', 'Beta', 'Binomial', 'CatTransform', 'Categorical', 'Cauchy', 'Chi2', 'ComposeTransform', 'ContinuousBernoulli', 'CorrCholeskyTransform', 'Dirichlet', 'Distribution', 'ExpTransform', 'Exponential', 'ExponentialFamily', 'FisherSnedecor', 'Gamma', 'Geometric', 'Gumbel', 'HalfCauchy', 'HalfNormal', 'Independent', 'IndependentTransform', 'Kumaraswamy', 'LKJCholesky', 'Laplace', 'LogNormal', 'LogisticNormal', 'LowRankMultivariateNormal', 'LowerCholeskyTransform', 'MixtureSameFamily', 'Multinomial', 'MultivariateNormal', 'NegativeBinomial', 'Normal', 'OneHotCategorical', 'OneHotCategoricalStraightThrough', 'Pareto', 'Poisson', 'PowerTransform', 'RelaxedBernoulli', 'RelaxedOneHotCategorical', 'ReshapeTransform', 'SigmoidTransform', 'SoftmaxTransform', 'StackTransform', 'StickBreakingTransform', 'StudentT', 'TanhTransform', 'Transform', 'TransformedDistribution', 'Uniform', 'VonMises', 'Weibull', '__all__', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__path__', '__spec__', 'bernoulli', 'beta', 'biject_to', 'binomial', 'categorical', 'cauchy', 'chi2', 'constraint_registry', 'constraints', 'continuous_bernoulli', 'dirichlet', 'distribution', 'exp_family', 'exponential', 'fishersnedecor', 'gamma', 'geometric', 'gumbel', 'half_cauchy', 'half_normal', 'identity_transform', 'independent', 'kl', 'kl_divergence', 'kumaraswamy', 'laplace', 'lkj_cholesky', 'log_normal', 'logistic_normal', 'lowrank_multivariate_normal', 'mixture_same_family', 'multinomial', 'multivariate_normal', 'negative_binomial', 'normal', 'one_hot_categorical', 'pareto', 'poisson', 'register_kl', 'relaxed_bernoulli', 'relaxed_categorical', 'studentT', 'transform_to', 'transformed_distribution', 'transforms', 'uniform', 'utils', 'von_mises', 'weibull']

'''

通常,我们可以忽略以“__”(双下划线)开始和结束的函数(它们是Python中的特殊对象), 或以单个“_”(单下划线)开始的函数(它们通常是内部函数)。 根据剩余的函数名或属性名,我们可能会猜测这个模块提供了各种生成随机数的方法, 包括从均匀分布(uniform)、正态分布(normal)和多项分布(multinomial)中采样。

2.7.2. 查找特定函数和类的用法

有关如何使用给定函数或类的更具体说明,我们可以调用help函数。 例如,我们来查看张量ones函数的用法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
help(torch.ones)

'''
Help on built-in function ones:

ones(...)
ones(*size, *, out=None, dtype=None, layout=torch.strided, device=None, requires_grad=False) -> Tensor

Returns a tensor filled with the scalar value 1, with the shape defined
by the variable argument size.

Args:
size (int...): a sequence of integers defining the shape of the output tensor.
Can be a variable number of arguments or a collection like a list or tuple.

Keyword arguments:
out (Tensor, optional): the output tensor.
dtype (torch.dtype, optional): the desired data type of returned tensor.
Default: if None, uses a global default (see torch.set_default_tensor_type()).
layout (torch.layout, optional): the desired layout of returned Tensor.
Default: torch.strided.
device (torch.device, optional): the desired device of returned tensor.
Default: if None, uses the current device for the default tensor type
(see torch.set_default_tensor_type()). device will be the CPU
for CPU tensor types and the current CUDA device for CUDA tensor types.
requires_grad (bool, optional): If autograd should record operations on the
returned tensor. Default: False.

Example::

>>> torch.ones(2, 3)
tensor([[ 1., 1., 1.],
[ 1., 1., 1.]])

>>> torch.ones(5)
tensor([ 1., 1., 1., 1., 1.])
'''

在Jupyter记事本中,我们可以使用?指令在另一个浏览器窗口中显示文档。 例如,list?指令将创建与help(list)指令几乎相同的内容,并在新的浏览器窗口中显示它。 此外,如果我们使用两个问号,如list??,将显示实现该函数的Python代码。

2.7.3. 小结

  • 官方文档提供了本书之外的大量描述和示例。
  • 我们可以通过调用dirhelp函数或在Jupyter记事本中使用???查看API的用法文档。

本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!
如果觉得文章内容不错,还请大力支持哦~