2.4 微积分
可以将拟合模型的任务分解为两个关键问题:
- 优化(optimization):用模型拟合观测数据的过程;
- 泛化(generalization):数学原理和实践者的智慧,能够指导我们生成出有效性超出用于训练的数据集本身的模型。
2.4.1 标量导数
导数实际上就是极限,其几何意义就是切线的斜率,可以看作在某点的瞬时变化率。
给定!
y
=
f
(
x
)
y=f(x)
y=f(x),其中
x
x
x ,
y
y
y分别是函数
f
f
f的自变量和因变量。一下表达式是等价的:
f
′
(
x
)
=
y
′
=
d
y
d
x
=
d
f
d
x
=
d
d
x
f
(
x
)
=
D
f
(
x
)
=
D
x
f
(
x
)
,
f'(x)=y'=\dfrac{dy}{dx}=\dfrac{df}{dx}=\dfrac d{dx}f(x)=Df(x)=D_xf(x),\quad
f′(x)=y′=dxdy=dxdf=dxdf(x)=Df(x)=Dxf(x),
常用函数求导规则及求导法则:
y y y | a a a | x n x^n xn | e x e^x ex | l o g ( x ) log(x) log(x) | u + v u+v u+v | u v uv uv | y = f ( u ) , u = g ( x ) y=f(u),u=g(x) y=f(u),u=g(x) |
---|---|---|---|---|---|---|---|
d y d x \dfrac{dy}{dx} dxdy | 0 | n x n − 1 nx^{n-1} nxn−1 | e x e^x ex | 1 x \frac{1}{x} x1 | d u d x + d v d x \dfrac{du}{dx}+\dfrac{dv}{dx} dxdu+dxdv | d u d x v + d v d x u \dfrac{du}{dx}v+\dfrac{dv}{dx}u dxduv+dxdvu | d y d u d u d x \dfrac{dy}{du}\dfrac{du}{dx} dudydxdu(链式法则) |
其中 a a a为标量; u , v u,v u,v为关于 x x x的函数。
书中关于Python配置matplotlib生成图形的属性所定义的几个函数:
其中注释#@save是一个特殊的标记,会将对应的函数、类或语句保存在d2l包中
def use_svg_display(): #@save
"""使用svg格式在Jupyter中显示绘图"""
backend_inline.set_matplotlib_formats('svg')
def set_figsize(figsize=(3.5, 2.5)): #@save
"""设置matplotlib的图表大小"""
use_svg_display()
d2l.plt.rcParams['figure.figsize'] = figsize
#可以直接使用d2l.plt,是因为导入语句 from matplotlib import pyplot as plt已标记为保存到d2l包中。
#@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()
#@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 d2l.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 ) = 3 x 2 − 4 x u=f(x)=3x^2-4x u=f(x)=3x2−4x及其在 x = 1 x=1 x=1处的切线 y = 2 x − 3 y=2x-3 y=2x−3, 其中系数2是切线的斜率。
x = np.arange(0, 3, 0.1)
plot(x, [f(x), 2 * x - 3], 'x', 'f(x)', legend=['f(x)', 'Tangent line (x=1)'])
2.4.2 亚导数
-
将导数拓展到不可微的函数,例如
∂ ∣ x ∣ ∂ x = { 1 if x > 0 − 1 if x < 0 a if x = 0 , a ∈ [ − 1 , 1 ] \dfrac{\partial\left|x\right|}{\partial x}=\begin{cases}1&\text{if }x>0\\ -1&\text{if }{x<0}\\ a&\text{if }{x=0},\quad a\in[-1,1]\end{cases} ∂x∂∣x∣=⎩ ⎨ ⎧1−1aif x>0if x<0if x=0,a∈[−1,1]∂ ∂ x max ( x , 0 ) = { 1 if x > 0 0 if x < 0 a if x = 0 , a ∈ [ 0 , 1 ] \dfrac{\partial}{\partial x}\max(x,0)=\begin{cases}1&\text{if }x>0\\ 0&\text{if }x<0\\ a&\text{if }x=0,&a\in[0,1]\end{cases} ∂x∂max(x,0)=⎩ ⎨ ⎧10aif x>0if x<0if x=0,a∈[0,1]
2.4.3 梯度
-
将导数拓展到向量,广义定义是分三种情况:函数或自变量分别或同时为向量的情况;
-
通常数学中的定义是:一个多元函数对其所有变量的偏导数,
∇ x f ( x ) = [ ∂ f ( x ) ∂ x 1 , ∂ f ( x ) ∂ x 2 , … , ∂ f ( x ) ∂ x n ] ⊤ ; \nabla_{\mathbf{x}}f(\mathbf{x})=\left[\dfrac{\partial f(\mathbf{x})}{\partial x_1},\dfrac{\partial{f(\mathbf{x})}}{\partial{x_2}},\ldots,\dfrac{\partial{f({\mathbf{x}})}}{\partial{x_n}}\right]^{\top}; ∇xf(x)=[∂x1∂f(x),∂x2∂f(x),…,∂xn∂f(x)]⊤; -
梯度向量的方向指向函数值增加最快的方向,其大小表示函数值增加的速率;
梯度大小: ∣ ∇ f ( x , y ) ∣ = ( ∂ f ∂ x ) 2 + ( ∂ f ∂ y ) 2 , 梯度方向: θ ( x , y ) = tan − 1 ( ∂ f ∂ y ∂ f ∂ x ) . \text{梯度大小:}|\nabla f(x,y)| = \sqrt{\left(\frac{\partial f}{\partial x}\right)^2 + \left(\frac{\partial f}{\partial y}\right)^2},\quad \text{梯度方向:}\theta(x,y) = \tan^{-1}\left(\frac{\frac{\partial f}{\partial y}}{\frac{\partial f}{\partial x}}\right). 梯度大小:∣∇f(x,y)∣=(∂x∂f)2+(∂y∂f)2,梯度方向:θ(x,y)=tan−1(∂x∂f∂y∂f).
y y y | a a a | a u au au | ∑ ( x ) \sum (x) ∑(x) | ∣ x ∣ 2 |x|^2 ∣x∣2 | u + v u+v u+v | u v uv uv | < u , v > <u,v> <u,v> |
---|---|---|---|---|---|---|---|
d y d x \dfrac{dy}{d\mathbf{x}} dxdy | 0 T \mathbf{0}^T 0T | a ∂ u ∂ x a\frac{\partial u}{\partial \mathbf{x}} a∂x∂u | 1 T \mathbf{1}^T 1T | 2 x T 2\mathbf{x}^T 2xT | ∂ u ∂ x + ∂ v ∂ x \dfrac{\partial u}{\partial \mathbf{x}}+\dfrac{\partial v}{\partial \mathbf{x}} ∂x∂u+∂x∂v | ∂ u ∂ x v + ∂ v ∂ x u \dfrac{\partial u}{\partial \mathbf{x}}v+\dfrac{\partial v}{\partial \mathbf{x}}u ∂x∂uv+∂x∂vu | u T ∂ v ∂ x + v T ∂ u ∂ x \mathbf{u}^T\dfrac{\partial \mathbf{v}}{\partial \mathbf{x}}+\mathbf{v}^T\dfrac{\partial \mathbf{u}}{\partial \mathbf{x}} uT∂x∂v+vT∂x∂u |
注意: d y d x \dfrac{dy}{d\mathbf{x}} dxdy是行向量, d y d x \dfrac{d\mathbf{y}}{dx} dxdy是列向量。这个被称为分子布局符号,反过来的版本叫分母布局符号。
y \mathbf{y} y | a \mathbf{a} a | x \mathbf{x} x | A x A\mathbf{x} Ax | x T A \mathbf{x}^TA xTA | a u a\mathbf{u} au | A u A\mathbf{u} Au | u + v \mathbf{u}+\mathbf{v} u+v |
---|---|---|---|---|---|---|---|
d y d x \dfrac{d\mathbf{y}}{d\mathbf{x}} dxdy | 0 \mathbf{0} 0 | I \mathbf{I} I | A A A | A T A^T AT | a ∂ u ∂ x a\frac{\partial \mathbf{u}}{\partial \mathbf{x}} a∂x∂u | A ∂ u ∂ x A\dfrac{\partial \mathbf{u}}{\partial \mathbf{x}} A∂x∂u | ∂ u ∂ x + ∂ v ∂ x \dfrac{\partial u}{\partial \mathbf{x}}+\dfrac{\partial v}{\partial \mathbf{x}} ∂x∂u+∂x∂v |
其中, I \mathbf{I} I为Identical矩阵。
2.4.5 总结
个人记忆方法:因为这是分子布局符号,所以导数的前几维的大小要与对应分子
y
y
y的维度大小一致。而分母
x
x
x的维度大小要调换个位置。
2.5 自动微分
2.5.1 理论
- 深度学习框架中用于加快求导;自动求导计算一个函数在指定值上的导数。与数学符号求导以及数值方法求导不同。
- 实际中,根据设计好的模型,系统会构建一个计算图(computational graph), 来跟踪计算是哪些数据通过哪些操作组合起来产生输出。 自动微分使系统能够随后反向传播梯度。 这里,反向传播(backpropagate)意味着跟踪整个计算图,填充关于每个参数的偏导数。
- 计算图:
- 将代码分解成操作子
- 将计算表示成一个无环图
- 显示构造:Tensorflow/Theano/MXNet
- 隐式构造:Pytorch/MXNet
- 自动求导的两种模式:正向累积、反向传递
- 链式法则: ∂ y ∂ x = ∂ y ∂ u n ∂ u n ∂ u n − 1 . . . ∂ u 2 ∂ u 1 ∂ u 1 ∂ x \frac{\partial y}{\partial x}=\frac{\partial y}{\partial u_n}\frac{\partial u_n}{\partial u_{n-1}}...\frac{\partial u_2}{\partial u_1}\frac{\partial u_1}{\partial x} ∂x∂y=∂un∂y∂un−1∂un...∂u1∂u2∂x∂u1
- 正向累积:
∂
y
∂
x
=
∂
y
∂
u
n
(
∂
u
n
∂
u
n
−
1
(
⋯
(
∂
u
2
∂
u
1
∂
u
1
∂
x
)
)
)
\begin{aligned}\frac{\partial y}{\partial x}=\frac{\partial y}{\partial u_n}\left(\frac{\partial u_n}{\partial u_{n-1}}\left(\cdots\left(\frac{\partial u_2}{\partial u_1}\frac{\partial u_1}{\partial x}\right)\right)\right)\end{aligned}
∂x∂y=∂un∂y(∂un−1∂un(⋯(∂u1∂u2∂x∂u1)))
- (从右到左、从前到后)(计算图中是从下到上)需要存储中间结果
- 反向传递(反向累积):
∂
y
∂
x
=
∂
y
∂
u
n
(
∂
u
n
∂
u
n
−
1
(
⋯
(
∂
u
2
∂
u
1
∂
u
1
∂
x
)
)
)
\begin{aligned}\frac{\partial y}{\partial x}=\frac{\partial y}{\partial u_n}\left(\frac{\partial u_n}{\partial u_{n-1}}\left(\cdots\left(\frac{\partial u_2}{\partial u_1}\frac{\partial u_1}{\partial x}\right)\right)\right)\end{aligned}
∂x∂y=∂un∂y(∂un−1∂un(⋯(∂u1∂u2∂x∂u1)))
- (从左到右,从后到前)(计算图中是从上到下)
- 需要去除计算图中不必要的枝
- 一般对于输入数据会先正向计算结果,再反向计算梯度,正向计算时会保存所有的中间变量
- 复杂度:
- 计算复杂度: O ( n ) O(n) O(n), n n n是操作子个数;正反向代价类似
- 内存复杂度: O ( n ) O(n) O(n),因为需要存储正向的所有中间结果(深度学习耗GPU资源的根源)
- 与正向累积对比:
- 计算复杂度: O ( n ) O(n) O(n),计算一个变量的梯度
- 内存复杂度: O ( 1 ) O(1) O(1),因为需要存储正向的所有中间结果
2.5.2 一个例子
- 假设我们想对函数 y = 2 x T x y=2\mathbf{x}^T\mathbf{x} y=2xTx关于列向量 x \mathbf{x} x求导。 首先,我们创建变量x并为其分配一个初始值。
import torch
x = torch.arange(4.0)
x
x.requires_grad_(True) # 表示需要开放区域来存储梯度
#等价于x=torch.arange(4.0,requires_grad=True)
x.grad # 存储关于x的梯度,默认值是None
y = 2 * torch.dot(x, x) #y的表达式
y
注释:grad_fn=<MulBackward0>
表示该张量是由哪个计算图节点计算得来的,以便在反向传播时构建计算图。其中,MulBackward0
表示该张量是通过一个乘法操作计算得来的,并且是计算图中的第一个反向传播节点(即从后往前数的第0个节点)。在PyTorch中,每个张量都有一个grad_fn
属性,用于记录该张量是如何计算得来的。对于用户创建的张量,其grad_fn
属性为None。
- 接下来,通过调用反向传播函数来自动计算y关于x每个分量的梯度,并打印这些梯度。
y.backward()
x.grad
注释:backward()
用于计算张量的梯度。该梯度会被存储在张量的.grad
属性中。一般在深度学习中,在计算梯度之前,我们需要先定义一个损失函数,然后将该损失函数作为参数传递给backward()
函数。
- 现在计算x的另一个函数
# 在默认情况下,PyTorch会累积梯度,我们需要清除之前的值
x.grad.zero_()
y = x.sum()
y.backward()
x.grad
2.5.3 非标量变量的反向传播
由于在深度学习中,一般是对损失函数的进行反向传递,而损失函数一般是标量,因此不是计算微分矩阵,而是单独计算批量中每个样本的偏导数之和。
# 对非标量调用backward需要传入一个gradient参数,该参数指定微分函数关于self的梯度。
# 本例只想求偏导数的和,所以传递一个1的梯度是合适的
x.grad.zero_()
y = x * x # y为一个矩阵
# 等价于y.backward(torch.ones(len(x)))
y.sum().backward() # y.sum()后为标量
x.grad
2.5.4 分离计算
由于某种原因,希望将y视为一个常数, 并且只考虑到x在y被计算后发挥的作用
x.grad.zero_()
y = x * x
u = y.detach() # 分离y来返回一个新变量u,将u作为常数处理
z = u * x
z.sum().backward()
x.grad == u
由于记录了y的计算结果,我们可以随后在y上调用反向传播, 得到y=x*x关于的x的导数,即2*x。
x.grad.zero_()
y.sum().backward()
x.grad == 2 * x
2.5.5 Python控制流的梯度计算
复杂的Python控制流也能进行自动求导
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
a = torch.randn(size=(), requires_grad=True)
d = f(a)
d.backward()
2.6 概率
2.6.1 基本概率论
- 概率论公理
- **抽样(sampling):**从概率分布中抽取样本的过程。
- 在处理骰子掷出时,我们将集合 S = { 1 , 2 , 3 , 4 , 5 , 6 } \mathcal{S}=\{1,2,3,4,5,6\} S={1,2,3,4,5,6}称为样本空间(sample space)或结果空间(outcome space), 其中每个元素都是结果(outcome)。
- **事件(event)**是一组给定样本空间的随机结果。 例如,“看到5”( { 5 } \{5\} {5})是掷出骰子的有效事件。
- 概率(probability):将集合映射到真实值的函数。 在给定的样本空间
S
\mathcal{S}
S中,事件A的概率, 表示为
P
(
A
)
P(A)
P(A)满足以下属性:
- 非负性: P ( A ) ≥ 0 P(A)\geq 0 P(A)≥0;
- P ( S ) ≥ 0 P(S)\geq 0 P(S)≥0,整个样本空间的概率为1;
- P ( ⋃ i = 1 ∞ A i ) = ∑ i = 1 ∞ P ( A i ) P(\bigcup_{i=1}^\infty{A}_i)=\sum_{i=1}^{\infty}P({A}_i) P(⋃i=1∞Ai)=∑i=1∞P(Ai),其中 A i A_i Ai互斥。
- 随机变量
- 考虑一个随机变量(random variable) X \mathbf{X} X,其值在掷骰子的样本空间 S = { 1 , 2 , 3 , 4 , 5 , 6 } \mathcal{S}=\{1,2,3,4,5,6\} S={1,2,3,4,5,6}中。可将事件“看到一个5”表示为 { X = 5 } \{\mathbf{X}=5\} {X=5}或 X = 5 \mathbf{X}=5 X=5,其概率表示为 P ( { X = 5 } ) P(\{\mathbf{X}=5\}) P({X=5})或 P ( X = 5 ) P(\mathbf{X}=5) P(X=5);
- 为了简化符号,一方面,我们可以将 P ( X ) P(\mathbf{X}) P(X)表示为随机变量 X \mathbf{X} X上的分布(distribution): 分布告诉我们获得某一值的概率。 X \mathbf{X} X可以用具体数取代,也可以显示 X \mathbf{X} X的可取范围。
2.6.2 处理多个随机变量
- 联合概率(joint probability)
- P ( A = a , B = b ) P(A=a,B=b) P(A=a,B=b)或 P ( A , B ) P(A,B) P(A,B)(紧凑表示法);
- 含义:给定任意值a和b, P ( A = a ) P(A=a) P(A=a)和 P ( B = b ) P(B=b) P(B=b)同时满足的概率;
- P ( A = a , B = b ) ≤ P ( A = a ) P(A=a,B=b)\leq P(A=a) P(A=a,B=b)≤P(A=a)且 P ( A = a , B = b ) ≤ P ( A = a ) P(A=a,B=b)\leq P(A=a) P(A=a,B=b)≤P(A=a)。
- 条件概率(conditional probability)
- P ( B = b ∣ A = a ) = P ( A = a , B = b ) P ( A = a ) P(B=b\mid A=a)=\frac{P(A=a,B=b)}{P(A=a)} P(B=b∣A=a)=P(A=a)P(A=a,B=b);
- 含义: A = a A=a A=a已发生时, B = b B=b B=b的概率。
- 贝叶斯定理(Bayes’ theorem)
- 乘法法则(multiplication rule): P ( A , B ) = P ( B ∣ A ) P ( A ) P(A,B)=P(B\mid A)P(A) P(A,B)=P(B∣A)P(A)或 P ( A , B ) = P ( A ∣ B ) P ( B ) P(A,B)=P(A \mid B)P(B) P(A,B)=P(A∣B)P(B);
- 假设 P ( B = b ) > 0 P(B=b)>0 P(B=b)>0, P ( A ∣ B ) = P ( B ∣ A ) P ( A ) P ( B ) P(A\mid B)=\dfrac{P(B\mid A)P(A)}{P(B)} P(A∣B)=P(B)P(B∣A)P(A)。
- 边际化
- 求和法则(sum rule): P ( B ) = ∑ A P ( A , B ) P(B)=\sum_A P(A,B) P(B)=∑AP(A,B);
- 上述公式也被称为边际化,边际化结果的概率或分布称为边际概率(marginal probability) 或边际分布(marginal distribution)。 P ( A ∣ B ) = P ( A ) P(A\mid B)=P(A) P(A∣B)=P(A);
- 独立性
- 如果两个随机变量 A A A和 B B B是独立的,意味着事件 A A A的发生跟 B B B事件的发生无关。表示为 A ⊥ B A\perp B A⊥B;
- 两个随机变量是独立的,当且仅当两个随机变量的联合分布是其各自分布的乘积。[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-svkOtok4-1680445654846)(null#card=math&code=P(A,B)]=P(A)P(B)&id=IShhX);
- 反之则称为依赖;
- 两个随机变量 A A A和 B B B是条件独立的,当且仅当 P ( A , B ∣ C ) = P ( A ∣ C ) P ( B ∣ C ) P(A,B\mid C)=P(A\mid C)P(B\mid C) P(A,B∣C)=P(A∣C)P(B∣C)。表示 A ⊥ B ∣ C A\perp B\mid C A⊥B∣C。
2.6.3 期望和方差
-
期望(expectation,或平均值(average))
- E [ X ] = ∑ x x P ( X = x ) ; E[X]=\sum_x xP(X=x); E[X]=∑xxP(X=x);
- E x ∼ P [ f ( x ) ] = ∑ x f ( x ) P ( x ) . E_{x\sim P}[f(x)]=\sum_x f(x)P(x). Ex∼P[f(x)]=∑xf(x)P(x).
-
方差
- Var [ X ] = E [ ( X − E [ X ] ) 2 ] = E [ X 2 ] − E [ X ] 2 \operatorname{Var}[X]=E\left[(X-E[X])^2\right]=E[X^2]-E[X]^2 Var[X]=E[(X−E[X])2]=E[X2]−E[X]2;
- Var [ f ( x ) ] = E [ ( f ( x ) − E [ f ( x ) ] ) 2 ] \operatorname{Var}[f(x)]=E\Big[(f(x)-E[f(x)])^2\Big] Var[f(x)]=E[(f(x)−E[f(x)])2];
- 方差的平方根被称为标准差(standard deviation)。
2.7 查阅文档
- 查找模块中所有函数和类
dir()
:了解模块中可以调用哪些函数和类
import torch
print(dir(torch.distributions)) # 查询随机数生成模块中的所有属性
PS:通常可以忽略以__
(双下划线)开始和结束的函数——Python中的特殊对象, 或以单个_
(单下划线)开始的函数——通常是内部函数。
- 查找特定函数和类的用法
help()
:了解给定函数或类更具体的使用说明
help(torch.ones) # 查看张量ones函数的用法
- 在Jupyter记事本中,可用
?
指令在另一个浏览器窗口中显示文档。 例如,list?
指令将创建与help(list
)指令几乎相同的内容,并在新的浏览器窗口中显示它。 此外,如果我们使用两个问号,如list??
,将显示实现该函数的Python代码。