2.4 微积分
直到至少2500年前,计算多边形的面积仍一直是个谜,直到古希腊人将一个多边形分割成多个三角形,并将这些三角形的面积相加,进而得出多边形的面积。为了找到弯曲形状的面积,比如一个圆,古希腊人刻了这种形状的多边形。如图2.4.1 所示,使用内接等边多边形近似圆,该方法也称为穷举法。
事实上,穷举法是微积分的起源【18.5 会介绍相关内容】。2000多年后,微积分的另一个分支,微分学被发明。微分学最为重要的应用是最优化问题。正如【2.3.10.1】所讨论的,这些问题在深度学习中是普遍存在的。
在深度学习的算法流程中,我们训练模型,不断更新模型,让模型随着看到的数据越来越多,变得越来越好。通常,变得更好意味着最小化损失函数,这个分数可以回答 “我们的模型有多糟糕?”, 这个问题比看上去要微妙得多。最终,我们真正关心的是生成一个模型,它能够在我们从未见过的数据上表现良好。但我们只能将模型与我们实际看到的数据相吻合。因此,我们可以将拟合模型的任务分解为两个关键问题:(i)最优化:用模型拟合现有的数据;(ii)泛化能力;数学原理和实践者的智慧,指导如何产生有效性超出训练模型所用的精确数据集的模型。
为了帮助您在后面的章节中理解优化问题和方法,这里我们简要介绍了在深度学习中常用的微分学。
2.4.1 导数与微分
我们首先讨论导数的计算,基本上在所有的深度学习优化算法中都是关键的一步。在深度学习中,我们通常选择对模型参数可导的损失函数。简单地说,这意味着对于每个参数,如果我们增加或减少一个无限小的数值,可以【确定】损失增加或减少的速度有多快。
假设存在函数
f
:
R
→
R
f:R\rightarrow R
f:R→R,输入和输出均为标量,那么导数的定义如下:
f
′
(
x
)
=
lim
h
→
0
f
(
x
+
h
)
−
f
(
x
)
h
(2.4.1)
f'(x)=\lim_{h\rightarrow 0}\frac{f(x+h)-f(x)}{h}\tag{2.4.1}
f′(x)=h→0limhf(x+h)−f(x)(2.4.1)
假设极限存在,那么
f
′
(
a
)
f'(a)
f′(a) 称之为
x
=
a
x=a
x=a 的导数。如果
f
f
f 在某个区间内每一点都可导,称为该区间可导。我们可以将导数
f
′
(
x
)
f'(x)
f′(x) 称为是
f
(
x
)
f(x)
f(x) 在 x 的瞬时速度。所谓瞬时速度的变化依赖于
h
(
h
→
0
)
h(h\rightarrow 0)
h(h→0) 在 x 处的变化。为了解释导数,我们举例如下,假设函数为:
u
=
f
(
x
)
=
3
x
2
−
4
x
u=f(x)=3x^2-4x
u=f(x)=3x2−4x,代码如下:
%matplotlib inline
import numpy as np
from IPython import display
from d2l import torch as d2l
def f(x):
return 3 * x ** 2 - 4 * x
令 x = 1 x=1 x=1,令 h → 0 h\rightarrow 0 h→0, f ( x + h ) − f ( x ) h \frac{f(x+h)-f(x)}{h} hf(x+h)−f(x) 的值为接近 2. 尽管没有数学上的证明(后面有补充证明),后面我们会看到,当 x = 1 x=1 x=1 时, u ′ u' u′ 的值为 2. 代码如下:
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
# 输出如下:
h=0.10000, numerical limit=2.30000
h=0.01000, numerical limit=2.03000
h=0.00100, numerical limit=2.00300
h=0.00010, numerical limit=2.00030
h=0.00001, numerical limit=2.00003
补充证明极限值求解,当 x = 1 x=1 x=1 时,
f ′ ( x ) = lim h → 0 f ( x + h ) − f ( x ) h = 2 f'(x)=\lim_{h\rightarrow 0}\frac{f(x+h)-f(x)}{h}=2 f′(x)=h→0limhf(x+h)−f(x)=2
证明如下:
lim h → 0 f ( x + h ) − f ( x ) h = lim h → 0 3 ⋅ ( x + h ) 2 − 4 ⋅ ( x + h ) − ( 3 x 2 − 4 x ) h = lim h → 0 3 ⋅ ( x + h ) 2 − 3 ⋅ x 2 − 4 h h = lim h → 0 3 ⋅ ( x + h − x ) ⋅ ( x + h + x ) − 4 h h = lim h → 0 3 ⋅ h ⋅ ( 2 x + h ) − 4 h h = lim h → 0 ( 6 x + 3 h − 4 ) = lim h → 0 ( 2 + 3 h ) = 2 \begin{aligned} \lim_{h\rightarrow 0}\frac{f(x+h)-f(x)}{h}&=\lim_{h\rightarrow 0}\frac{3\cdot(x+h)^2-4\cdot (x+h)-(3x^2-4x)}{h}\\ &=\lim_{h\rightarrow 0}\frac{3\cdot (x+h)^2-3\cdot x^2-4h}{h}\\ &=\lim_{h\rightarrow 0}\frac{3\cdot (x+h-x)\cdot (x+h+x)-4h}{h}\\ &=\lim_{h\rightarrow 0}\frac{3\cdot h\cdot (2x+h)-4h}{h}\\ &=\lim_{h\rightarrow 0}(6x+3h-4)\\ &=\lim_{h\rightarrow 0}(2+3h)\\ &=2 \end{aligned} h→0limhf(x+h)−f(x)=h→0limh3⋅(x+h)2−4⋅(x+h)−(3x2−4x)=h→0limh3⋅(x+h)2−3⋅x2−4h=h→0limh3⋅(x+h−x)⋅(x+h+x)−4h=h→0limh3⋅h⋅(2x+h)−4h=h→0lim(6x+3h−4)=h→0lim(2+3h)=2
让我们使用一些与导数等价的概念进一步熟悉导数。给定
y
=
f
(
x
)
y=f(x)
y=f(x),y 和 x 分别是函数
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
)
(2.4.2)
f'(x)=y'=\frac{dy}{dx}=\frac{df}{dx}=\frac{d}{dx}f(x)=Df(x)=D_xf(x)\tag{2.4.2}
f′(x)=y′=dxdy=dxdf=dxdf(x)=Df(x)=Dxf(x)(2.4.2)
其中,
d
d
x
,
D
\frac{d}{dx},D
dxd,D是微分运算符,我们可以借助这些符号,给常见的函数求导,具体如下:
- DC=0(C是常量)
- D x n = n x n − 1 Dx^n=nx^{n-1} Dxn=nxn−1
- D e x = e x De^x=e^x Dex=ex
- D l n ( x ) = 1 / x Dln(x)=1/x Dln(x)=1/x
为了将一个函数与一些简单的函数(如上述常见函数)区分开来,我们可以使用以下规则。假设函数
f
,
g
f,g
f,g均是可导的,C 是常量,可以得到以下的计算方法:
常量乘法准则:
d
d
x
[
C
f
(
x
)
]
=
C
d
d
x
f
(
x
)
(2.4.3)
\frac{d}{dx}[Cf(x)]=C\frac{d}{dx}f(x)\tag{2.4.3}
dxd[Cf(x)]=Cdxdf(x)(2.4.3)
求和准则:
d
d
x
[
f
(
x
)
+
g
(
x
)
]
=
d
d
x
f
(
x
)
+
d
d
x
g
(
x
)
(2.4.4)
\frac{d}{dx}[f(x)+g(x)]=\frac{d}{dx}f(x)+\frac{d}{dx}g(x)\tag{2.4.4}
dxd[f(x)+g(x)]=dxdf(x)+dxdg(x)(2.4.4)
乘法准则:
d
d
x
[
f
(
x
)
g
(
x
)
]
=
f
(
x
)
d
d
x
g
(
x
)
+
g
(
x
)
d
d
x
f
(
x
)
(2.4.5)
\frac{d}{dx}[f(x)g(x)]=f(x)\frac{d}{dx}g(x)+g(x)\frac{d}{dx}f(x)\tag{2.4.5}
dxd[f(x)g(x)]=f(x)dxdg(x)+g(x)dxdf(x)(2.4.5)
除法准则:
d
d
x
[
f
(
x
)
g
(
x
)
]
=
g
(
x
)
d
d
x
[
f
(
x
)
]
−
f
(
x
)
d
d
x
[
g
(
x
)
]
[
g
(
x
)
]
2
(2.4.6)
\frac{d}{dx}\Bigg[\frac{f(x)}{g(x)}\Bigg]=\frac{g(x)\frac{d}{dx}[f(x)]-f(x)\frac{d}{dx}[g(x)]}{[g(x)]^2}\tag{2.4.6}
dxd[g(x)f(x)]=[g(x)]2g(x)dxd[f(x)]−f(x)dxd[g(x)](2.4.6)
现在我们可以使用上面的公式计算
u
=
f
(
x
)
u=f(x)
u=f(x) 在
x
=
1
x=1
x=1 的导数,
u
′
=
f
′
(
x
)
=
3
d
d
x
x
2
−
4
d
d
x
x
=
6
x
−
4
u'=f'(x)=3\frac{d}{dx}x^2-4\frac{d}{dx}x=6x-4
u′=f′(x)=3dxdx2−4dxdx=6x−4,当
x
=
1
x=1
x=1 时,
u
′
=
2
u'=2
u′=2,这与上面的数值结果几乎一致。从曲线上看,导数是曲线
u
=
f
(
x
)
u=f(x)
u=f(x) 在
x
=
1
x=1
x=1 处切线的斜率。我们使用Python库 【matplotlib
】 将曲线和切线可视化。为了用【matplotlib
】配置图形的属性,我们需要定义一些函数。我们使用函数 【use_svg_display
】来画出更为清晰的图像。值得注意的是,【#@save
】是一个特殊的标记,表示下面的函数,类,声明都在 安装包 【d2l】中定义过,可以被直接使用(d2l.use_svg_display()
),不用重新定义。
def use_svg_display(): #@save
"""Use the svg format to display a plot in Jupyter."""
display.set_matplotlib_formats('svg')
我们定义函数【set_figsize
】来设置图像的大小。值得注意的是,我们直接使用【d2l.plt】,因为声明【from matplotlib import pyplot as plt
】已经在安装包【d2l】中导入。函数定义如下:
def set_figsize(figsize=(3.5, 2.5)): #@save
"""Set the figure size for matplotlib."""
use_svg_display()
d2l.plt.rcParams['figure.figsize'] = figsize
下面的函数【set_axes
】用于设置图像轴的一些特性,代码如下:
#@save
def set_axes(axes, xlabel, ylabel, xlim, ylim, xscale, yscale, legend):
"""Set the axes for 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
】函数来简洁地绘制多条曲线,因为在本书中我们需要将许多曲线可视化。具体代码如下:
#@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):
"""Plot data points."""
if legend is None:
legend = []
set_figsize(figsize)
axes = axes if axes else d2l.plt.gca()
# Return True if `X` (tensor or list) has 1 axis
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)
# 如果不是用的 jupyter notebook 运行此代码,则添加下面的代码,否则无法显示图形
d2l.plt.show()
现在我们可以画出函数 u = f ( x ) u=f(x) u=f(x) 以及它在 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. 偏导数
到目前,我们只处理了单变量函数的微分。 在深度学习中,通常函数依赖很多变量。因此,我们需要将微分推广到多变量函数。
令函数 y = f ( x 1 , x 2 , ⋯ , x n ) y=f(x_1,x_2,\cdots,x_n) y=f(x1,x2,⋯,xn) 具有 【n】个变量,那么函数 y y y 关于第 【i】个变量 x i x_i xi 的偏导数定义形式如下:
∂
y
∂
x
i
=
lim
h
→
0
f
(
x
1
,
.
.
.
,
x
i
−
1
,
x
i
+
h
,
x
i
+
1
,
.
.
.
,
x
n
)
−
f
(
x
1
,
.
.
.
,
x
i
,
.
.
.
,
x
n
)
h
(2.4.7)
\frac{\partial y}{\partial x_i}=\lim_{h\rightarrow 0}\frac{f(x_1,...,x_{i-1},x_i+h,x_{i+1},...,x_n)-f(x_1,...,x_i,...,x_n)}{h}\tag{2.4.7}
∂xi∂y=h→0limhf(x1,...,xi−1,xi+h,xi+1,...,xn)−f(x1,...,xi,...,xn)(2.4.7)
为了计算
∂
y
∂
x
i
\frac{\partial y}{\partial x_i}
∂xi∂y,我们需要将
x
1
,
.
.
.
,
x
i
−
1
,
x
i
+
1
,
.
.
.
,
x
n
x_{1},...,x_{i-1},x_{i+1},...,x_n
x1,...,xi−1,xi+1,...,xn 当作是常量,然后计算
y
y
y 相对于
x
i
x_i
xi 的导数。对于偏导的概念,下面的形式是等价的:
∂
y
∂
x
i
=
∂
f
∂
x
i
=
f
x
i
=
f
i
=
D
i
f
=
D
x
i
f
(2.4.8)
\frac{\partial y}{\partial x_i}=\frac{\partial f}{\partial x_i}=f_{x_i}=f_i=D_if=D_{x_i}f\tag{2.4.8}
∂xi∂y=∂xi∂f=fxi=fi=Dif=Dxif(2.4.8)
2.4.3. 梯度
我们可以串联多元函数相对于其所有变量的偏导数来得到函数的梯度向量。假设输入函数 f : R n → R f:R^n\rightarrow R f:Rn→R 的输入为 【n】维向量 x = [ x 1 , . . . , x 2 , . . . , x n ] T \pmb x=[x1,...,x2,...,x_n]^T xxx=[x1,...,x2,...,xn]T,输出为标量。那么,函数 f ( x ) f(\pmb x) f(xxx) 的梯度是关于向量 x \pmb x xxx 的偏导数,形式如下:
∇ x f ( x ) = [ ∂ f ( x ) ∂ x 1 , ∂ f ( x ) ∂ x 2 , . . . , ∂ f ( x ) ∂ x n ] (2.4.9) \nabla_xf(\pmb x)=\Bigg[\frac{\partial f(\pmb x)}{\partial x_1},\frac{\partial f(\pmb x)}{\partial x_2},...,\frac{\partial f(\pmb x)}{\partial x_n}\Bigg]\tag{2.4.9} ∇xf(xxx)=[∂x1∂f(xxx),∂x2∂f(xxx),...,∂xn∂f(xxx)](2.4.9)
这里 ∇ x f ( x ) \nabla_xf(\pmb x) ∇xf(xxx) 可以使用 ∇ f ( x ) \nabla f(\pmb x) ∇f(xxx) 代替。
令 x \pmb x xxx 是【n】维向量,下面的公式经常使用:
- 对于所有的 A ∈ R m × n , ∇ x A x = A T A\in R^{m\times n},\nabla_xA\pmb x=A^T A∈Rm×n,∇xAxxx=AT,
- 对于所有的 A ∈ R n × m , ∇ x x T A = A A\in R^{n\times m},\nabla_x\pmb x^TA=A A∈Rn×m,∇xxxxTA=A,
- 对于所有的 A ∈ R n × n , ∇ x x T A x = ( A + A T ) x A\in R^{n\times n},\nabla_xx^TAx=(A+A^T)x A∈Rn×n,∇xxTAx=(A+AT)x,
- ∇ x ∣ ∣ x ∣ ∣ 2 = ∇ x x T x = 2 x \nabla_x||x||^2=\nabla_xx^Tx=2x ∇x∣∣x∣∣2=∇xxTx=2x.
注意:上面公式的证明,只需将矩阵乘法展开,然后依据公式(2.4.9)计算就可得到。
类似地,对于任何矩阵
X
\pmb X
XXX,
∇
X
∣
∣
X
∣
∣
F
2
=
2
X
\nabla_{X}||X||^2_F=2X
∇X∣∣X∣∣F2=2X. 后面我们会看到,当梯度对于设计优化算法非常重要。
2.4.4. 链式规则
但是,这样的梯度还是很难遇到。在深度学习中,多变量函数通常是复合函数,我们通常很难用上述的规则来求偏导。幸运的是,链式规则可以使得我们求这些符合函数的偏导。
让我们首先考虑单变量函数。假设函数
y
=
f
(
u
)
y=f(u)
y=f(u) 和
u
=
g
(
x
)
u=g(x)
u=g(x) 均是可导的。那么对
x
x
x 求导的链式规则如下:
d
y
d
x
=
d
y
d
u
d
u
d
x
(2.4.10)
\frac{dy}{dx}=\frac{dy}{du}\frac{du}{dx}\tag{2.4.10}
dxdy=dudydxdu(2.4.10)
下面我们将其推广到具有任意数量变量的符合函数。假设可导函数
y
y
y 具有变量
u
1
,
u
2
,
.
.
.
,
u
m
u_{1},u_{2},...,u_{m}
u1,u2,...,um,而每一个可导函数
u
i
u_i
ui 具有变量
x
1
,
x
2
,
.
.
.
,
x
n
x_{1},x_{2},...,x_{n}
x1,x2,...,xn,值得注意的是,函数
y
y
y是
x
1
,
x
2
,
.
.
.
,
x
n
x_{1},x_{2},...,x_{n}
x1,x2,...,xn 的函数。链式规则如下:
d
y
d
x
i
=
d
y
d
u
1
d
u
1
d
x
i
+
d
y
d
u
2
d
u
2
d
x
i
+
⋯
+
d
y
d
u
m
d
u
m
d
x
i
(2.4.11)
\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}\tag{2.4.11}
dxidy=du1dydxidu1+du2dydxidu2+⋯+dumdydxidum(2.4.11)
对于任意
i
=
1
,
2
,
.
.
.
,
n
i=1,2,...,n
i=1,2,...,n.
2.4.5 总结
- 微分学和积分学是微积分的两个分支,前者常用于深度学习中的优化算法。
- 导数可以解释为函数在其变量处的瞬时速度,也是函数曲线切线的斜率。
- 梯度是多变量函数在所有变量的偏导数。
- 链式规则用求解复合函数的导数。