微积分
在2500年前,古希腊人把一个多边形分成三角形,并把它们的面积相加,才找到计算多边形面积的方法。为了求出曲线形状(比如圆)的面积,古希腊人在这样的形状上刻内接多边形。如下图所示,内接多边形的等长边越多,就越接近圆:
这个过程也被称为逼近法 (method of exhaustion)。
事实上,逼近法就是积分 (integral calculus)的起源。2000多年后,微积分的另一支,微分 (differential calculus)被发明出来。在微分学最重要的应用是优化问题,即考虑如何把事情做到最好。正如在 范数和目标 中讨论的那样,这种问题在深度学习中是无处不在的。
在深度学习中,我们“训练”模型,不断更新它们,使它们在看到越来越多的数据时变得越来越好。通常情况下,变得更好意味着最小化一个损失函数 (loss function),即一个衡量“模型有多糟糕”这个问题的分数。最终,我们真正关心的是生成一个模型,它能够在从未见过的数据上表现良好。但“训练”模型只能将模型与我们实际能看到的数据相拟合。因此,我们可以将拟合模型的任务分解为两个关键问题:
优化 (optimization):用模型拟合观测数据的过程;
泛化 (generalization):数学原理和实践者的智慧,能够指导我们生成出有效性超出用于训练的数据集本身的模型。
为了帮助读者在后面的章节中更好地理解优化问题和方法,本节提供了一个非常简短的入门教程,帮助读者快速掌握深度学习中常用的微分知识。
导数和微分
我们首先讨论导数的计算,这是几乎所有深度学习优化算法的关键步骤。
在深度学习中,我们通常选择对于模型参数可微的损失函数。
简而言之,对于每个参数,
如果我们把这个参数增加 或减少 一个无穷小的量,可以知道损失会以多快的速度增加或减少,
假设我们有一个函数 f : R → R ,其输入和输出都是标量。如果 f 的导数 存在,这个极限被定义为
f ′ ( x ) = h → 0 lim h f ( x + h ) − f ( x ) . ( 1.4.1 )
如果 f ′ ( a ) 存在,则称 f 在 a 处是可微 (differentiable)的。如果 f 在一个区间内的每个数上都是可微的,则此函数在此区间中是可微的。我们可以将 (1.4.1)
中的导数 f ′ ( x ) 解释为 f ( x ) 相对于 x 的瞬时 (instantaneous)变化率。所谓的瞬时变化率是基于 x 中的变化 h ,且 h 接近 0 。
为了更好地解释导数,让我们做一个实验。定义 u = f ( x ) = 3 x 2 − 4 x 如下:
% matplotlib inline
from d2l import torch as d2l
from matplotlib_inline import backend_inline
import numpy as np
def f ( x ):
return 3 * x ** 2 - 4 * x
通过令 x = 1 并让 h 接近 0 ,(1.4.1)
中 h f ( x + h ) − f ( x ) 的数值结果接近 2 。虽然这个实验不是一个数学证明,但稍后会看到,当 x = 1 时,导数 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
让我们熟悉一下导数的几个等价符号。给定 y = f ( x ) ,其中 x 和 y 分别是函数 f 的自变量和因变量。以下表达式是等价的:
f ′ ( x ) = y ′ = d x d y = d x df = d x d f ( x ) = D f ( x ) = D x f ( x ) , ( 1.4.2 )
其中符号d x d 和D 是微分运算符 ,表示微分 操作。我们可以使用以下规则来对常见函数求微分:
D C = 0 (C 是一个常数)
D x n = n x n − 1 (幂律 (power rule),n 是任意实数)
D e x = e x
D ln ( x ) = 1/ x
为了微分一个由一些常见函数组成的函数,下面的一些法则方便使用。假设函数 f 和 g 都是可微的,C 是一个常数,则:
常数相乘法则
d x d [ C f ( x )] = C d x d f ( x ) , ( 1.4.3 )
加法法则
d x d [ f ( x ) + g ( x )] = d x d f ( x ) + d x d g ( x ) , ( 1.4.4 )
乘法法则
d x d [ f ( x ) g ( x )] = f ( x ) d x d [ g ( x )] + g ( x ) d x d [ f ( x )] , ( 1.4.5 )
除法法则
d x d [ g ( x ) f ( x ) ] = [ g ( x ) ] 2 g ( x ) d x d [ f ( x )] − f ( x ) d x d [ g ( x )] . ( 1.4.6 )
现在我们可以应用上述几个法则来计算 u ′ = f ′ ( x ) = 3 d x d x 2 − 4 d x d x = 6 x − 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()
)。
def use_svg_display (): #@save
"""使用svg格式在Jupyter中显示绘图"""
backend_inline. set_matplotlib_formats ( 'svg' )
我们定义 set_figsize
函数来设置图表大小。注意,这里可以直接使用 d2l.plt
,因为导入语句 from matplotlib import pyplot as plt
已标记为保存到 d2l
包中。
def set_figsize ( figsize = ( 3.5 , 2.5 )): #@save
"""设置matplotlib的图表大小"""
use_svg_display ()
d2l.plt.rcParams[ 'figure.figsize' ] = figsize
下面的 set_axes
函数用于设置由 matplotlib
生成图表的轴的属性。
#@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
函数来简洁地绘制多条曲线,
因为我们需要在整个书中可视化许多曲线。
#@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 ) 及其在 x = 1 处的切线 y = 2 x − 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)' ])
偏导数
到目前为止,我们只讨论了仅含一个变量的函数的微分。在深度学习中,函数通常依赖于许多变量。因此,我们需要将微分的思想推广到多元函数 (multivariate function)上。
设 y = f ( x 1 , x 2 , … , x n ) 是一个具有 n 个变量的函数。y 关于第 i 个参数 x i 的偏导数 (partial derivative)为:
∂ x i ∂ y = h → 0 lim h f ( x 1 , … , x i − 1 , x i + h , x i + 1 , … , x n ) − f ( x 1 , … , x i , … , x n ) . ( 1.4.7 )
为了计算 ∂ x i ∂ y ,我们可以简单地将 x 1 , … , x i − 1 , x i + 1 , … , x n 看作常数,并计算 y 关于 x i 的导数。对于偏导数的表示,以下是等价的:
∂ x i ∂ y = ∂ x i ∂ f = f x i = f i = D i f = D x i f . ( 1.4.8 )
梯度
我们可以连结一个多元函数对其所有变量的偏导数,以得到该函数的梯度 (gradient)向量。具体而言,设函数 f : R n → R 的输入是一个 n 维向量 x = [ x 1 , x 2 , … , x n ] ⊤ ,并且输出是一个标量。函数 f ( x ) 相对于 x 的梯度是一个包含 n 个偏导数的向量:
∇ x f ( x ) = [ ∂ x 1 ∂ f ( x ) , ∂ x 2 ∂ f ( x ) , … , ∂ x n ∂ f ( x ) ] ⊤ , ( 1.4.9 )
其中 ∇ x f ( x ) 通常在没有歧义时被 ∇ f ( x ) 取代。
假设 x 为 n 维向量,在微分多元函数时经常使用以下规则:
对于所有 A ∈ R m × n ,都有 ∇ x Ax = A ⊤
对于所有 A ∈ R n × m ,都有 ∇ x x ⊤ A = A
对于所有 A ∈ R n × n ,都有 ∇ x x ⊤ Ax = ( A + A ⊤ ) x
∇ x ∥ x ∥ 2 = ∇ x x ⊤ x = 2 x
同样,对于任何矩阵 X ,都有 ∇ X ∥ X ∥ F 2 = 2 X 。正如我们之后将看到的,梯度对于设计深度学习中的优化算法有很大用处。
链式法则
然而,上面方法可能很难找到梯度。这是因为在深度学习中,多元函数通常是复合 (composite)的,所以难以应用上述任何规则来微分这些函数。幸运的是,可以用链式法则来对复合函数进行微分。
让我们先考虑单变量函数。假设函数 y = f ( u ) 和 u = g ( x ) 都是可微的,根据链式法则:
d x d y = d u d y d x d u . ( 1.4.10 )
现在考虑一个更一般的场景,即函数具有任意数量的变量的情况。假设可微分函数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 ,链式法则给出:
∂ x i ∂ y = ∂ u 1 ∂ y ∂ x i ∂ u 1 + ∂ u 2 ∂ y ∂ x i ∂ u 2 + ⋯ + ∂ u m ∂ y ∂ x i ∂ u m ( 1.4.11 )
小结
微分和积分是微积分的两个分支,前者可以应用于深度学习中的优化问题。
导数可以被解释为函数相对于其变量的瞬时变化率,它也是函数曲线的切线的斜率。
梯度是一个向量,其分量是多变量函数相对于其所有变量的偏导数。
链式法则可以用来微分复合函数。
练习
绘制函数y = f ( x ) = x 3 − x 1 和其在x = 1 处切线的图像。
求函数 f ( x ) = 3 x 1 2 + 5 e x 2 的梯度。
函数 f ( x ) = ∥ x ∥ 2 的梯度是什么?
尝试写出函数 u = f ( x , y , z ) ,其中 x = x ( a , b ) ,y = y ( a , b ) ,z = z ( a , b ) 的链式法则。
Discussions