优化算法 (机器学习)(TensorFlow)
优化算法对于深度学习非常重要。一方面,训练复杂的深度学习模型可能需要数小时、几天甚至数周。优化算法的性能直接影响模型的训练效率。另一方面,了解不同优化算法的原则及其超参数的作用将使我们能够以有针对性的方式调整超参数,以提高深度学习模型的性能。
优化
对于深度学习问题,我们通常会先定义损失函数。一旦我们有了损失函数,我们就可以使用优化算法来尝试最小化损失。在优化中,损失函数通常被称为优化问题的目标函数。按照传统惯例,大多数优化算法都关注的是最小化。如果我们需要最大化目标,那么有一个简单的解决方案:在目标函数前加负号即可。
优化目标
尽管优化提供了一种最大限度地减少深度学习损失函数的方法,但本质上,优化和深度学习的目标是根本不同的。前者主要关注的是最小化目标,后者则关注在给定有限数据量的情况下寻找合适的模型。由于优化算法的目标函数通常是基于训练数据集的损失函数,因此优化的目标是减少训练误差。但是,深度学习(或更广义地说,统计推断)的目标是减少泛化误差。为了实现后者,除了使用优化算法来减少训练误差之外,我们还需要注意过拟合。
优化面临的挑战
深度学习优化存在许多挑战。其中最令人烦恼的是局部最小值、鞍点和梯度消失。
局部最小值
对于任何目标函数
我们可以近似该函数的局部最小值和全局最小值。
深度学习模型的目标函数通常有许多局部最优解。当优化问题的数值解接近局部最优值时,随着目标函数解的梯度接近或变为零,通过最终迭代获得的数值解可能仅使目标函数局部最优,而不是全局最优。只有一定程度的噪声可能会使参数跳出局部最小值。事实上,这是小批量随机梯度下降的有利特性之一。在这种情况下,小批量上梯度的自然变化能够将参数从局部极小值中跳出。
鞍点
除了局部最小值外,鞍点是 梯度消失的另一个原因鞍点(saddle point
)是指函数的所有梯度都消失但既不是全局最小值也不是局部最小值的任何位置。考虑这个函数Hessian
矩阵(也称黑塞矩阵)将有
- 当函数在零梯度位置处的
Hessian
矩阵的特征值全部为正值时,我们有该函数的局部最小值; - 当函数在零梯度位置处的
Hessian
矩阵的特征值全部为负值时,我们有该函数的局部最大值; - 当函数在零梯度位置处的
Hessian
矩阵的特征值为负值和正值时,我们有该函数的一个鞍点。
对于高维度问题,至少部分特征值为负的可能性相当高。这使得鞍点比局部最小值更有可能出现。简而言之,凸函数是Hessian
函数的特征值永远不为负值的函数。不幸的是,大多数深度学习问题并不属于这一类。尽管如此,它还是研究优化算法的一个很好的工具。
梯度消失
可能遇到的最隐蔽问题是梯度消失。回想一下常用的激活函数及其衍生函数。例如,假设我们想最小化函数ReLU
激活函数之前训练深度学习模型相当棘手的原因之一。
正如我们所看到的那样,深度学习的优化充满挑战。幸运的是,有一系列强大的算法表现良好,即使对于初学者也很容易使用。此外,没有必要找到最优解。局部最优解或其近似解仍然非常有用。
总结
最小化训练误差并不能保证我们找到最佳的参数集来最小化泛化误差。优化问题可能有许多局部最小值。一个问题可能有很多的鞍点,因为问题通常不是凸的。梯度消失可能会导致优化停滞,重参数化通常会有所帮助。对参数进行良好的初始化也可能是有益的。
凸性
凸性(convexity
)在优化算法的设计中起到至关重要的作用,这主要是由于在这种情况下对算法进行分析和测试要容易。换言之,如果算法在凸性条件设定下的效果很差,那通常我们很难在其他条件下看到好的结果。此外,即使深度学习中的优化问题通常是非凸的,它们也经常在局部极小值附近表现出一些凸性。这可能会产生一些像这样比较有意思的新优化变体。
定义
在进行凸分析之前,我们需要定义凸集(convex sets
)和凸函数(convex functions
)。
凸集
凸集(convex set
)是凸性的基础。简单地说,如果对于任何convex
)的。在数学术语上,这意味着对于所有
这听起来有点抽象,那我们来看下图里的例子。第一组存在不包含在集合内部的线段,所以该集合是非凸的,而另外两组则没有这样的问题。
接下来看一下交集,如下图所示。假设
我们可以毫不费力进一步得到这样的结果:给定凸集nonconvex
)的。
通常,深度学习的问题通常是在凸集上定义的。例如,
凸函数
现在我们有了凸集,我们可以引入凸函数(convex function
)
1 | f = lambda x: 0.5 * x**2 # 凸函数 |
不出所料,余弦函数为非凸的,而抛物线函数和指数函数为凸的。请注意,为使该条件有意义,
詹森不等式
给定一个凸函数Jensen’s inequality
)。它是凸性定义的一种推广:
其中
这里,
性质
局部极小值是全局极小值
首先凸函数的局部极小值也是全局极小值。下面我们用反证法给出证明。假设
这与
凸函数的局部最小值也是全局最小值这一性质很方便。这意味着如果我们最小化函数,我们就不会“卡住”。但是请注意,这并不能意味着不能有多个全局最小值,或者可能不存在一个全局最小值。例如,函数
凸函数的下水平集是凸的
我们可以方便地通过凸函数的下水平集(below sets
)定义凸集。具体来说,给定一个定义在凸集
是凸的。让我们快速证明一下。对于任何
凸性和二阶导数
当一个函数的二阶导数
因为二阶导数是由有限差分的极限给出的,所以遵循:
为了证明
通过单调性
由于
从而证明了凸性。第二,我们需要一个引理证明多维情况:
是凸的,为了证明
为了证明这一点,我们可以证明对
最后,利用上面的引理和一维情况的结果,我们可以证明多维情况:多维函数
约束
凸优化的一个很好的特性是能够让我们有效地处理约束(constraints
)。即它使我们能够解决以下形式的约束优化(constrained optimization
)问题:
这里
拉格朗日函数
通常,求解一个有约束的优化问题是困难的,解决这个问题的一种方法来自物理中相当简单的直觉。想象一个球在一个盒子里,球会滚到最低的地方,重力将与盒子两侧对球施加的力平衡。简而言之,目标函数(即重力)的梯度将被约束函数的梯度所抵消(由于墙壁的“推回”作用,需要保持在盒子内)。请注意,任何不起作用的约束(即球不接触壁)都将无法对球施加任何力。这里我们简略拉格朗日函数
这里的变量Lagrange multipliers
),它确保约束被正确的执行。选择它们的大小足以确保所有saddlepoint
)优化问题。在这个问题中我们想要使maximize
),同时使它相对于
惩罚
一种至少近似地满足约束优化问题的方法是采用拉格朗日函数
投影
满足约束条件的另一种策略是投影(projections
)。同样,我们之前也遇到过,我们通过:
确保梯度的长度以projection
)。更泛化地说,在凸集
图中有两个凸集,一个圆和一个菱形。两个集合内的点(黄色)在投影期间保持不变。两个集合(黑色)之外的点投影到集合中接近原始点(黑色)的点(红色)。虽然对
总结
在深度学习的背景下,凸函数的主要目的是帮助我们详细了解优化算法。我们由此得出梯度下降法和随机梯度下降法是如何相应推导出来的。凸集的交点是凸的,并集不是。根据詹森不等式,“一个多变量凸函数的总期望值”大于或等于“用每个变量的期望值计算这个函数的总值“。一个二次可微函数是凸函数,当且仅当其Hessian
(二阶导数矩阵)是半正定的。凸约束可以通过拉格朗日函数来添加。在实践中,只需在目标函数中加上一个惩罚就可以了。投影映射到凸集中最接近原始点的点。
梯度下降
尽管梯度下降(gradient descent
)很少直接用于深度学习,但了解它是理解随机梯度下降算法的关键。例如,由于学习率过大,优化问题可能会发散,这种现象早已在梯度下降中出现。同样地,预处理(preconditioning
)是梯度下降中的一种常用技术,还被沿用到更高级的算法中。让我们从简单的一维梯度下降开始。
一维梯度下降
为什么梯度下降算法可以优化目标函数?一维中的梯度下降给我们很好的启发。考虑一类连续可微实值函数
即在一阶近似中,
如果其导数
这意味着,如果我们使用:
来迭代
下面我们来展示如何实现梯度下降。为了简单起见,我们选用目标函数
1 | import numpy as np |
学习率
学习率(learning rate
)决定目标函数能否收敛到局部最小值,以及何时收敛到最小值。学习率10
个步骤,我们仍然离最优解很远。
1 | show_trace(gd(0.05, f_grad), f) |
相反,如果我们使用过高的学习率,
1 | show_trace(gd(1.1, f_grad), f) |
局部最小值
为了演示非凸函数的梯度下降,考虑函数
1 | c = tf.constant(0.15 * np.pi) |
多元梯度下降
现在我们对单变量的情况有了更好的理解,让我们考虑一下
梯度中的每个偏导数元素
换句话说,在
这个算法在实践中的表现如何呢?我们构造一个目标函数20
步之后,
自适应方法
选择“恰到好处”的学习率
牛顿法
回顾一下函数
为了避免繁琐的符号,我们将Hessian
,是
也就是说,作为优化问题的一部分,我们需要将Hessian
矩阵
收敛性分析
在此,我们以部分目标凸函数
这对某些
回想之前的方程
因此,每当我们处于有界区域
另一方面,优化研究人员称之为“线性”收敛,而将
预处理
计算和存储完整的Hessian
非常昂贵,而改善这个问题的一种方法是“预处理”。 它回避了计算整个Hessian
,而只计算“对角线”项,即如下的算法更新:
虽然这不如完整的牛顿法精确,但它仍然比不使用要好得多。为什么预处理有效呢?假设一个变量以毫米表示高度,另一个变量以公里表示高度的情况。假设这两种自然尺度都以米为单位,那么我们的参数化就出现了严重的不匹配。幸运的是,使用预处理可以消除这种情况。梯度下降的有效预处理相当于为每个变量选择不同的学习率(矢量
梯度下降和线搜索
梯度下降的一个关键问题是我们可能会超过目标或进展不足,解决这一问题的简单方法是结合使用线搜索和梯度下降。也就是说,我们使用
总结
学习率的大小很重要:学习率太大会使模型发散,学习率太小会没有进展。梯度下降会可能陷入局部极小值,而得不到全局最小值。在高维模型中,调整学习率是很复杂的。预处理有助于调节比例。牛顿法在凸问题中一旦开始正常工作,速度就会快得多。对于非凸问题,不要不作任何调整就使用牛顿法。
随机梯度下降
随机梯度更新
在深度学习中,目标函数通常是训练数据集中每个样本的损失函数的平均值。给定
如果使用梯度下降法,则每个自变量迭代的计算代价为SGD
)可降低每次迭代时的计算代价。在随机梯度下降的每次迭代中,我们对数据样本随机均匀采样一个索引
其中
这意味着,平均而言,随机梯度是对梯度的良好估计。现在,我们将把它与梯度下降进行比较,方法是向梯度添加均值为0、方差为1的随机噪声,以模拟随机梯度下降。
1 | def f(x1, x2): # 目标函数 |
正如我们所看到的,随机梯度下降中变量的轨迹比我们在之前观察到的梯度下降中观察到的轨迹嘈杂得多。这是由于梯度的随机性质。也就是说,即使我们接近最小值,我们仍然受到通过50
次迭代,质量仍然不那么好。更糟糕的是,经过额外的步骤,它不会得到改善。这给我们留下了唯一的选择:改变学习率sgd
步长函数中添加学习率函数lr
的原因。
动态学习率
用与时间相关的学习率
在第一个分段常数(piecewise constant
)场景中,我们会降低学习率,例如,每当优化进度停顿时。这是训练深度网络的常见策略。或者,我们可以通过指数衰减(exponential decay
)来更积极地减低它。不幸的是,这往往会导致算法收敛之前过早停止。一个受欢迎的选择是polynomial decay
)。在凸优化的情况下,有许多证据表明这种速率表现良好。让我们看看指数衰减在实践中是什么样子。
1 | def exponential_lr(): |
正如预期的那样,参数的方差大大减少。但是,这是以未能收敛到最优解1000
个迭代步骤,我们仍然离最优解很远。事实上,该算法根本无法收敛。另一方面,如果我们使用多项式衰减,其中学习率随迭代次数的平方根倒数衰减,那么仅在50
次迭代之后,收敛就会更好。
关于如何设置学习率,还有更多的选择。例如,我们可以从较小的学习率开始,然后使其迅速上涨,再让它降低,尽管这会更慢。我们甚至可以在较小和较大的学习率之间切换。现在,让我们专注于可以进行全面理论分析的学习率计划,即凸环境下的学习率。对一般的非凸问题,很难获得有意义的收敛保证,因为总的来说,最大限度地减少非线性非凸问题是NP
困难的。
凸目标的收敛性分析
随机梯度和有限样本
我们假设从分布
但是,这不是我们真正做的。在本节的简单示例中,我们只是将噪声添加到其他非随机梯度上,也就是说,我们假装有成对的
类似的推理表明,挑选一些样本(即训练示例)恰好一次的概率是:
这导致与无替换采样相比,方差增加并且数据效率降低。因此,在实践中我们执行后者。最后一点注意,重复采用训练数据集的时候,会以不同的随机顺序遍历它。
总结
对于凸问题,我们可以证明,对于广泛的学习率选择,随机梯度下降将收敛到最优解。对于深度学习而言,情况通常并非如此。但是,对凸问题的分析使我们能够深入了解如何进行优化,即逐步降低学习率,尽管不是太快。如果学习率太小或太大,就会出现问题。实际上,通常只有经过多次实验后才能找到合适的学习率。当训练数据集中有更多样本时,计算梯度下降的每次迭代的代价更高,因此在这些情况下,首选随机梯度下降。随机梯度下降的最优性保证在非凸情况下一般不可用,因为需要检查的局部最小值的数量可能是指数级的。
小批量随机梯度下降
我们在基于梯度的学习方法中遇到了两个极端情况:使用完整数据集来计算梯度并更新参数,一次处理一个训练样本来取得进展。二者各有利弊:每当数据非常相似时,梯度下降并不是“数据高效”。而由于CPU
和GPU
无法充分利用向量化,随机梯度下降并不“计算高效”。这暗示了两者之间可能有折中方案,这便涉及到小批量随机梯度下降(minibatch gradient descent
)。
向量化和缓存
使用小批量的决策的核心是计算效率。 当考虑与多个GPU
和多台服务器并行处理时,这一点最容易被理解。在这种情况下,我们需要向每个GPU
发送至少一张图像。有了每台服务器8
个GPU
和16
台服务器,我们就能得到大小为128
的小批量。当涉及到单个GPU
甚至CPU
时,事情会更微妙一些:这些设备有多种类型的内存、通常情况下多种类型的计算单元以及在它们之间不同的带宽限制。例如,一个CPU
有少量寄存器(register
),L1
和L2
缓存,以及L3
缓存(在不同的处理器内核之间共享)。随着缓存的大小的增加,它们的延迟也在增加,同时带宽在减少。可以说,处理器能够执行的操作远比主内存接口所能提供的多得多。
减轻这些限制的方法是使用足够快的CPU
缓存层次结构来为处理器提供数据。这是深度学习中批量处理背后的推动力。举一个简单的例子:矩阵-矩阵乘法。比如
- 我们可以计算
,也就是说,我们可以通过点积进行逐元素计算。 - 我们可以计算
,也就是说,我们可以一次计算一列。同样,我们可以一次计算 一行 。 - 我们可以简单地计算
。 - 我们可以将
和 分成较小的区块矩阵,然后一次计算 的一个区块。
如果我们使用第一个选择,每次我们计算一个元素CPU
中。更糟糕的是,由于矩阵元素是按顺序对齐的,因此当从内存中读取它们时,我们需要访问两个向量中许多不相交的位置。第二种选择相对更有利:我们能够在遍历CPU
缓存中。它将内存带宽需求减半,相应地提高了访问速度。第三种选择表面上是最可取的,然而大多数矩阵可能不能完全放入缓存中。第四种选择提供了一个实践上很有用的方案:我们可以将矩阵的区块移到缓存中然后在本地将它们相乘。
小批量
之前我们会理所当然地读取数据的小批量,而不是观测单个数据来更新参数,现在简要解释一下原因。处理单个观测值需要我们执行许多单一矩阵-矢量(甚至矢量-矢量)乘法,这耗费相当大,而且对应深度学习框架也要巨大的开销。这既适用于计算梯度以更新参数时,也适用于用神经网络预测。也就是说,每当我们执行
我们可以通过将其应用于一个小批量观测值来提高此操作的计算效率。也就是说,我们将梯度
让我们看看这对GPU
的内存。
总结
由于减少了深度学习框架的额外开销,使用更好的内存定位以及CPU
和GPU
上的缓存,向量化使代码更加高效。随机梯度下降的“统计效率”与大批量一次处理数据的“计算效率”之间存在权衡。小批量随机梯度下降提供了两全其美的答案:计算和统计效率。在小批量随机梯度下降中,我们处理通过训练数据的随机排列获得的批量数据(即每个观测值只处理一次,但按随机顺序)。在训练期间降低学习率有助于训练。一般来说,小批量随机梯度下降比随机梯度下降和梯度下降的速度快,收敛风险较小。
动量法
我们在选择学习率需要格外谨慎。如果衰减速度太快,收敛就会停滞。相反,如果太宽松,我们可能无法收敛到最优解。
泄漏平均值
我们讨论了小批量随机梯度下降作为加速计算的手段。它也有很好的副作用,即平均梯度减小了方差。小批量随机梯度下降可以通过以下方式计算:
为了保持记法简单,在这里我们使用leaky average
)取代梯度计算:
其中momentum
),它累加了过去的梯度。为了更详细地解释,让我们递归地将
其中,较大的
动量法
动量法(momentum
)使我们能够解决上面描述的梯度下降问题。观察上面的优化轨迹,我们可能会直觉到计算过去的平均梯度效果会很好。毕竟,在
请注意,对于
总结
动量法用过去梯度的平均值来替换梯度,这大大加快了收敛速度。对于无噪声梯度下降和嘈杂随机梯度下降,动量法都是可取的。动量法可以防止在随机梯度下降的优化过程停滞的问题。由于对过去的数据进行了指数降权,有效梯度数为
AdaGrad算法
稀疏特征和学习率
假设我们正在训练一个语言模型。为了获得良好的准确性,我们大多希望在训练的过程中降低学习率,速度通常为AdaGrad
算法通过将粗略的计数器AdaGrad
固有的一些额外优势,这些优势在预处理环境中很容易被理解。
预处理
凸优化问题有助于分析算法的特点。毕竟对大多数非凸问题来说,获得有意义的理论保证很难,但是直觉和洞察往往会延续。让我们来看看最小化
这里我们使用了
如果稍微扰动condition number
)。
如果条件编号
在这种情况下,我们得到了AdaGrad
算法巧妙的思路是,使用一个代理来表示黑塞矩阵(Hessian
)的对角线,既相对易于计算又高效。为了了解它是如何生效的,让我们来看看
其中AdaGrad
算法是一种随机梯度下降算法,所以即使是在最佳值中,我们也会看到具有非零方差的梯度。因此,我们可以放心地使用梯度的方差作为黑塞矩阵比例的廉价替代。
算法
让我们接着上面正式开始讨论。我们使用变量
在这里,操作是按照坐标顺序应用。也就是说,0
最后,我们初始化
就像在动量法中我们需要跟踪一个辅助变量一样,在AdaGrad
算法中,我们允许每个坐标有单独的学习率。与SGD
算法相比,这并没有明显增加AdaGrad
的计算代价,因为主要计算用在AdaGrad
算法的变体。眼下让我们先看看它在二次凸问题中的表现如何。我们仍然以同一函数为例:
我们将使用与之前相同的学习率来实现AdaGrad
算法,即
总结
AdaGrad
算法会在单个坐标层面动态降低学习率。AdaGrad
算法利用梯度的大小作为调整进度速率的手段:用较小的学习率来补偿带有较大梯度的坐标。在深度学习问题中,由于内存和计算限制,计算准确的二阶导数通常是不可行的。梯度可以作为一个有效的代理。如果优化问题的结构相当不均匀,AdaGrad
算法可以帮助缓解扭曲。AdaGrad
算法对于稀疏特征特别有效,在此情况下由于不常出现的问题,学习率需要更慢地降低。在深度学习问题上,AdaGrad
算法有时在降低学习率方面可能过于剧烈。