一 优化和深度学习
优化算法 使我们能够 继续更新模型参数, 并使损失函数的值最小化。这就像在训练集上评估一样。事实上,任何满足于将优化视为黑盒装置,以在简 单的设置中最小化目标函数的人,都可能会知道存在着一系列此类“咒语”(名称如“SGD”和“Adam”)。
但是,为了做得更好,还需要更深入的知识。优化算法对于深度学习非常重要。一方面,训练复杂的深度学 习模型可能需要数小时、几天甚至数周。优化算法的性能直接影响模型的训练效率。另一方面,了解 不同优化算法的原则及其超参数的作用将使我们能够以有针对性的方式调整超参数,以提高深度学习模型的性能。深度学习中出现的几乎所有优化问题都是非凸的。尽管如此,在凸问题背景下设计和分析算法是非常有启发性的。
对于深度学习问题,我们通常会先定义损失函数。一旦我们有了损失函数,我们就可以使用优化算法来尝试最小化损失。在优化中,损失函数通常被称为 优化问题的目标函数。按照传统惯例,大多数优化算法都关注的是最小化。如果我们需要最大化目标,那么有一个简单的解决方案:在目标函数前加负号即可。
1.1 优化的目标
尽管优化提供了一种 最大限度地减少深度学习损失函数的方法,但本质上,优化和深度学习的目标是根本不同的。前者主要关注的是最小化目标,后者则关注在给定有限数据量的情况下寻找合适的模型。例如,训练误差和泛化误差通常不同:由于优化算法的目标函数通常是基于训练数据集的损失函数,因此优化的目标是减少训练误差。但是,深度学习(或更广义地说,统计推断)的目标是减少泛化误差。为了实现后者,除了使用优化算法来减少训练误差之外,我们还需要注意过拟合。
%matplotlib inline
import numpy as np
import torch
from mpl_toolkits import mplot3d
from d2l import torch as d2l
为了说明上述不同的目标,引入两个概念 风险和经验风险。经验风险是训练数据集的平均损失,而风险则是整个数据群的预期损失。下面我们定义了两个函数:风险函数f和经验风险函数g。假设我们只有有限的训练数据。因此,这里的g不如f平滑。
def f(x):return x * torch.cos(np.pi * x)def g(x):return f(x) + 0.2 * torch.cos(5 * np.pi * x)
下图说明,训练数据集的最低经验风险可能与最低风险(泛化误差)不同。
def annotate(text, xy, xytext): #@saved2l.plt.gca().annotate(text, xy=xy, xytext=xytext,arrowprops=dict(arrowstyle='->'))
x = torch.arange(0.5, 1.5, 0.01)
d2l.set_figsize((4.5, 2.5))
d2l.plot(x, [f(x), g(x)], 'x', 'risk')
annotate('min of\nempirical risk', (1.0, -1.2), (0.5, -1.1))
annotate('min of risk', (1.1, -1.05), (0.95, -0.5))
1.2 深度学习中的优化挑战
在深度学习中,大多数目标函数都很复杂,没有解析解。相反,我们必须使用数值优化算法。 深度学习优化存在许多挑战。其中最令人烦恼的是 局部最小值、鞍点和梯度消失。
局部最小值:
x = torch.arange(-1.0, 2.0, 0.01)
d2l.plot(x, [f(x), ], 'x', 'f(x)')
annotate('local minimum', (-0.3, -0.25), (-0.77, -1.0))
annotate('global minimum', (1.1, -0.95), (0.6, 0.8))
深度学习模型的目标函数通常有许多局部最优解。当优化问题的数值解接近局部最优值时,随着目标函数解 的梯度接近或变为零,通过最终迭代获得的数值解可能仅使目标函数局部最优,而不是全局最优。只有一定程度的噪声可能会使参数跳出局部最小值。事实上,这是小批量随机梯度下降的有利特性之一。在这种情况 下,小批量上梯度的自然变化能够将参数从局部极小值中跳出。
鞍点:
除了局部最小值之外,鞍点是梯度消失的另一个原因。鞍点(saddle point)是指函数的所有梯度都消失但既 不是全局最小值也不是局部最小值的任何位置。
x = torch.arange(-2.0, 2.0, 0.01)
d2l.plot(x, [x**3], 'x', 'f(x)')
annotate('saddle point', (0, -0.2), (-0.52, -5.0))
梯度消失:
可能遇到的最隐蔽问题是梯度消失。回想一下我们常用的激活函数及其衍生函数。例如,假设我 们想最小化函数f(x) = tanh(x),然后我们恰好从x = 4开始。正如我们所看到的那样,f的梯度接近零。更 具体地说,f ′ (x) = 1 − tanh2 (x),因此是f ′ (4) = 0.0013。因此,在我们取得进展之前,优化将会停滞很长一 段时间。事实证明,这是 在引入 ReLU激活函数之前 训练深度学习模型相当棘手的原因之一。
x = torch.arange(-2.0, 5.0, 0.01)
d2l.plot(x, [torch.tanh(x)], 'x', 'f(x)')
annotate('vanishing gradient', (4, 1), (2, 0.0))
小结:
- 最小化训练误差并不能保证我们找到最佳的参数集来最小化泛化误差。
- 优化问题可能有许多局部最小值。
- 一个问题可能有很多的鞍点,因为问题通常不是凸的。
- 梯度消失可能会导致优化停滞,重参数化通常会有所帮助。对参数进行良好的初始化也可能是有益的。
1.3 凸性
凸性(convexity)在优化算法的设计中起到至关重要的作用,这主要是由于在这种情况下对算法进行分析和测试要容易。换言之,如果算法在凸性条件设定下的效果很差,那通常我们很难在其他条件下看到好的结果。 此外,即使深度学习中的优化问题通常是非凸的,它们也经常在局部极小值附近表现出一些凸性。这可能会 产生一些像 (Izmailov et al., 2018)这样比较有意思的新优化变体。
%matplotlib inline
import numpy as np
import torch
from mpl_toolkits import mplot3d
from d2l import torch as d2l
在进行凸分析之前,我们需要定义凸集(convex sets)和凸函数(convex functions)。
凸集(convex set)是凸性的基础。简单地说,如果对于任何a, b ∈ X,连接a和b的线段也位于X中,则向量 空间中的一个集合X是凸(convex)的。
现在我们有了凸集,我们可以引入凸函数(convex function)f。给定一个凸集X,如果对于所有x, x′ ∈ X和 所有λ ∈ [0, 1],函数f : X → R是凸的。
为了说明这一点,让我们绘制一些函数并检查哪些函数满足要求。下面我们定义一些函数,包括凸函数和非 凸函数。
f = lambda x: 0.5 * x**2 # 凸函数
g = lambda x: torch.cos(np.pi * x) # 非凸函数
h = lambda x: torch.exp(0.5 * x) # 凸函数
x, segment = torch.arange(-2, 2, 0.01), torch.tensor([-1.5, 1])
d2l.use_svg_display()
_, axes = d2l.plt.subplots(1, 3, figsize=(9, 3))
for ax, func in zip(axes, [f, g, h]):d2l.plot([x, segment], [func(x), func(segment)], axes=ax)
首先 凸函数的局部极小值也是全局极小值。下面我们用反证法给出证明。
例如,对于凸函数f(x) = (x − 1)2,有一个局部最小值x = 1,它也是全局最小值。
f = lambda x: (x - 1) ** 2
d2l.set_figsize()
d2l.plot([x, segment], [f(x), f(segment)], 'x', 'f(x)')
凸函数的局部极小值同时也是全局极小值这一性质是很方便的。这意味着如果我们最小化函数,我们就不会 “卡住”。
小结:
在深度学习的背景下,凸函数的主要目的是帮助我们详细了解优化算法。我们由此得出梯度下降法和随机梯 度下降法是如何相应推导出来的。
- 凸集的交点是凸的,并集不是。
- 根据詹森不等式,“一个多变量凸函数的总期望值”大于或等于“用每个变量的期望值计算这个函数的 总值“。
- 一个二次可微函数是凸函数,当且仅当其Hessian(二阶导数矩阵)是半正定的。
- 凸约束可以通过拉格朗日函数来添加。在实践中,只需在目标函数中加上一个惩罚就可以了。
- 投影映射到凸集中最接近原始点的点。