优化提供了一种最大程度减少深度学习损失函数的方法,但本质上,优化和深度学习的目标不同。
优化关注的是最小化目标;深度学习是在给定有限数据量的情况下寻找合适的模型。
考虑一类连续可微实值函数f:R→Rf:\mathbb{R} \rightarrow \mathbb{R}f:R→R,利用泰勒展开,可以得到:
f(x+ϵ)=f(x)+ϵf′(x)+O(ϵ2)f(x + \epsilon ) = f(x) + \epsilon f'(x) + O(\epsilon ^2) f(x+ϵ)=f(x)+ϵf′(x)+O(ϵ2)
在一阶近似中,f(x+ϵ)f(x + \epsilon )f(x+ϵ) 可通过xxx处的函数值f(x)f(x)f(x)和一阶导数f′(x)f'(x)f′(x)得出。假设在负梯度方向上移动的ϵ\epsilonϵ会减少fff。为了简化问题,选择固定步长η>0\eta > 0η>0,然后取ϵ=−ηf′(x)\epsilon = - \eta f'(x)ϵ=−ηf′(x),将其带入泰勒展开式后,得到
f(x−ηf′(x))=f(x)−ηf′2(x)+O(η2f′2(x))f(x - \eta f'(x)) = f(x) - \eta f'^2(x) + O(\eta^2f'^2(x)) f(x−ηf′(x))=f(x)−ηf′2(x)+O(η2f′2(x))
若f′(x)≠0f'(x) \ne 0f′(x)=0,则ηf′2(x)>0\eta f'^2(x) > 0ηf′2(x)>0。另外,总是可以找到令η\etaη足够小使得高阶项变的不相关,因此
f(x−ηf′(x))
x←x−ηf′(x)x \leftarrow x - \eta f'(x) x←x−ηf′(x)
来迭代x,函数f(x)f(x)f(x)的值可能会下降。
因此,在梯度下降中,首先选用初始值xxx和η>0\eta > 0η>0,然后使用它们连续迭代xxx,直到停止条件达成。例如:当梯度∣f′(x)∣|f'(x)|∣f′(x)∣的幅度足够小或迭代次数达到某个值。
其中,步长η\etaη叫做学习率,是超参数,其决定目标函数能否收敛到局部最小值,以及何时收敛到最小值。
深度学习中,目标函数通常是训练数据集中每个样本的损失函数的平均值。
给定nnn个样本,假设fi(x)f_i(x)fi(x)是关于索引iii的训练样本的损失函数,其中XXX是参数向量,得到目标函数:
f(X)=1n∑i=1nfi(X)f(X) = \frac{1}{n} \sum_{i=1}^{n} f_i(X) f(X)=n1i=1∑nfi(X)
XXX的目标函数的梯度为:
∇f(X)=1n∑i=1n∇fi(X)\nabla f(X) = \frac{1}{n} \sum_{i=1}^{n} \nabla f_i(X) ∇f(X)=n1i=1∑n∇fi(X)
如果采用梯度下降法,每个自变量迭代的计算代价为O(n)O(n)O(n),其随nnn线性增长,因此,当训练数据集规模较大时,每次迭代的梯度下降的计算代价将更高。
SGD可降低每次迭代时的计算代价。在随机梯度下降的每次迭代中,对数据样本随机均匀采样一个索引iii,其中i∈{1,...,n}i \in \{ 1,...,n \}i∈{1,...,n},并计算梯度∇fi(X)\nabla f_i(X)∇fi(X)来更新XXX:
x←x−η∇fi(X)x \leftarrow x - \eta \nabla f_i(X) x←x−η∇fi(X)
其中,η\etaη是学习率。
可以发现,每次迭代的计算代价从梯度下降的O(n)O(n)O(n)下降到了常数O(1)O(1)O(1)。
此外,随机梯度∇fi(x)\nabla f_i(x)∇fi(x)是对完整梯度∇f(x)\nabla f(x)∇f(x)的无偏估计。
E∇fi(X)=1n∑i=1n∇fi(X)=∇f(X)\mathbb{E} \nabla f_i(X) = \frac{1}{n} \sum_{i=1}^{n} \nabla f_i(X) = \nabla f(X) E∇fi(X)=n1i=1∑n∇fi(X)=∇f(X)
这意味着,平均而言,随机梯度是对梯度的良好估计。
处理单个观测值需要我们执行许多单一矩阵-矢量(甚至矢量-矢量)乘法,这耗费很大,而且对应深度学习框架也要巨大的开销。这既适用于计算梯度以更新参数时,也适用于用神经网络预测。既,当执行w←w−ηtgt\mathbf{w} \leftarrow \mathbf{w} - \eta_t \mathbf{g}_tw←w−ηtgt时,消耗巨大,其中:
gt=∂wf(xt,w).\mathbf{g}_t = \partial_{\mathbf{w}} f(\mathbf{x}_{t}, \mathbf{w}). gt=∂wf(xt,w).
可以通过将其应用于一个小批量观测值来提高次操作的计算效率。也就是说,将梯度gtg_tgt替换为一个小批量而不是单个观测值。
gt=∂w1∣Bt∣∑i∈Btf(xi,w).\mathbf{g}_t = \partial_{\mathbf{w}} \frac{1}{|\mathcal{B}_t|} \sum_{i \in \mathcal{B}_t} f(\mathbf{x}_{i}, \mathbf{w}). gt=∂w∣Bt∣1i∈Bt∑f(xi,w).
分析gt\mathbf{g}_tgt的统计属性的影响:
实践中我们选择一个足够大的小批量,它可以提供良好的计算效率同时仍适合GPU的内存。
在小批量随机梯度下降中,定义某次时间ttt的梯度下降计算公式:
gt,t−1=∂w1∣Bt∣∑i∈Btf(xi,wt−1)=1∣Bt∣∑i∈Bthi,t−1.\mathbf{g}_{t, t-1} = \partial_{\mathbf{w}} \frac{1}{|\mathcal{B}_t|} \sum_{i \in \mathcal{B}_t} f(\mathbf{x}_{i}, \mathbf{w}_{t-1}) = \frac{1}{|\mathcal{B}_t|} \sum_{i \in \mathcal{B}_t} \mathbf{h}_{i, t-1}. gt,t−1=∂w∣Bt∣1i∈Bt∑f(xi,wt−1)=∣Bt∣1i∈Bt∑hi,t−1.
小批量随机梯度下降可以作为加速计算的手段,同时其也有很好的副作用,即平均梯度减小了方差。
vt=βvt−1+gt,t−1\mathbf{v}_t = \beta \mathbf{v}_{t-1} + \mathbf{g}_{t, t-1} vt=βvt−1+gt,t−1
其中,β∈(0,1)\beta \in (0, 1)β∈(0,1)。这将有效的将瞬间梯度替换为“过去“多个梯度的平均值。v\mathbf{v}v被称为动量,其累加了过去的梯度。其中,可以vt\mathbf{v}_tvt扩展到
vt=β2vt−2+βgt−1,t−2+gt,t−1=…,=∑τ=0t−1βτgt−τ,t−τ−1.\begin{aligned} \mathbf{v}_t = \beta^2 \mathbf{v}_{t-2} + \beta \mathbf{g}_{t-1, t-2} + \mathbf{g}_{t, t-1} = \ldots, = \sum_{\tau = 0}^{t-1} \beta^{\tau} \mathbf{g}_{t-\tau, t-\tau-1}. \end{aligned} vt=β2vt−2+βgt−1,t−2+gt,t−1=…,=τ=0∑t−1βτgt−τ,t−τ−1.
其中,较大的β\betaβ相当于长期平均值,较小的β\betaβ相当于梯度只是略有修正。新的梯度替换不在指向模型中下降最陡的方向,而是指向过去梯度的加权平均值的方向。
对于minibatch-SGD,使用vt\mathbf{v}_tvt而不是梯度gt\mathbf{g}_tgt可以生成以下更新等式:
vt←βvt−1+gt,t−1,xt←xt−1−ηtvt.\begin{aligned} \mathbf{v}_t &\leftarrow \beta \mathbf{v}_{t-1} + \mathbf{g}_{t, t-1}, \\ \mathbf{x}_t &\leftarrow \mathbf{x}_{t-1} - \eta_t \mathbf{v}_t. \end{aligned} vtxt←βvt−1+gt,t−1,←xt−1−ηtvt.
注意,对于β=0\beta = 0β=0,我们恢复常规的梯度下降。
vt=∑τ=0t−1βτgt−τ,t−τ−1\mathbf{v}_t = \sum_{\tau = 0}^{t-1} \beta^{\tau} \mathbf{g}_{t-\tau, t-\tau-1}vt=∑τ=0t−1βτgt−τ,t−τ−1。极限条件下,∑τ=0∞βτ=11−β\sum_{\tau=0}^\infty \beta^\tau = \frac{1}{1-\beta}∑τ=0∞βτ=1−β1。
即,不同于在梯度下降或者随机梯度下降中取步长η\etaη,我们选取步长η1−β\frac{\eta}{1-\beta}1−βη,同时处理潜在表现可能会更好的下降方向。这是集两种好处于一身的做法。
动量法用过去梯度的平均值来替换梯度,这大大加快了收敛速度。
对于无噪声梯度下降和嘈杂随机梯度下降,动量法都是可取的。
动量法可以防止在随机梯度下降的优化过程停滞的问题。
由于对过去的数据进行了指数降权,有效梯度数为11−β\frac{1}{1-\beta}1−β1。
动量法的实现非常简单,但它需要我们存储额外的状态向量(动量v\mathbf{v}v)。
需要明确:为了使训练模型获得良好的准确性,我们大多希望在训练的过程中降低学习率,速度通常为O(t−12)\mathcal{O}(t^{-\frac{1}{2}})O(t−21)或更低。 对于稀疏特征(即只在偶尔出现的特征)的模型训练,只有在不常见的特征出现时,与其相关的参数才会得到有意义的更新。 鉴于学习率下降,我们可能最终会面临这样的情况:常见特征的参数相当迅速地收敛到最佳值,而对于不常见的特征,我们仍缺乏足够的观测以确定其最佳值。 换句话说,学习率要么对于常见特征而言降低太慢,要么对于不常见特征而言降低太快。
解决此问题的一个方法是记录我们看到特定特征的次数,然后将其用作调整学习率。 即我们可以使用大小为ηi=η0s(i,t)+c\eta_i = \frac{\eta_0}{\sqrt{s(i, t) + c}}ηi=s(i,t)+cη0的学习率,而不是η=η0t+c\eta = \frac{\eta_0}{\sqrt{t + c}}η=t+cη0。在这里s(i,t)s(i, t)s(i,t)计下了我们截至ttt时观察到功能iii的次数。
AdaGrad算法 通过将粗略的计数器s(i,t)s(i, t)s(i,t)替换为先前观察所得梯度的平方之和来解决这个问题。它使用s(i,t+1)=s(i,t)+(∂if(x))2s(i, t+1) = s(i, t) + \left(\partial_i f(\mathbf{x})\right)^2s(i,t+1)=s(i,t)+(∂if(x))2来调整学习率。
这有两个好处:
使用变量st\mathbf{s}_tst来累加过去的梯度方差:
gt=∂wl(yt,f(xt,w)),st=st−1+gt2,wt=wt−1−ηst+ϵ⋅gt.\begin{aligned} \mathbf{g}_t & = \partial_{\mathbf{w}} l(y_t, f(\mathbf{x}_t, \mathbf{w})), \\ \mathbf{s}_t & = \mathbf{s}_{t-1} + \mathbf{g}_t^2, \\ \mathbf{w}_t & = \mathbf{w}_{t-1} - \frac{\eta}{\sqrt{\mathbf{s}_t + \epsilon}} \cdot \mathbf{g}_t. \end{aligned} gtstwt=∂wl(yt,f(xt,w)),=st−1+gt2,=wt−1−st+ϵη⋅gt.
η\etaη是学习率,ϵ\epsilonϵ是一个为维持数值稳定性而添加的常数,用来确保不会除以000。最后,初始化s0=0\mathbf{s}_0 = \mathbf{0}s0=0。
就像在动量法中需要跟踪一个辅助变量一样,在AdaGrad算法中,我们允许每个坐标有单独的学习率。与SGD算法相比,这并没有明显增加AdaGrad的计算代价,因为主要计算用在l(yt,f(xt,w))l(y_t, f(\mathbf{x}_t, \mathbf{w}))l(yt,f(xt,w))及其导数。
注意,在st\mathbf{s}_tst中累加平方梯度意味着st\mathbf{s}_tst基本上以线性速率增长(由于梯度从最初开始衰减,实际上比线性慢一些)。这产生了一个学习率O(t−12)\mathcal{O}(t^{-\frac{1}{2}})O(t−21),但是在单个坐标的层面上进行了调整。对于凸问题,这完全足够了。然而,在深度学习中,我们可能希望更慢地降低学习率。这引出了许多AdaGrad算法的变体。
AdaGrad算法会在单个坐标层面动态降低学习率。
AdaGrad算法利用梯度的大小作为调整进度速率的手段:用较小的学习率来补偿带有较大梯度的坐标。
在深度学习问题中,由于内存和计算限制,计算准确的二阶导数通常是不可行的。梯度可以作为一个有效的代理。
如果优化问题的结构相当不均匀,AdaGrad算法可以帮助缓解扭曲。
AdaGrad算法对于稀疏特征特别有效,在此情况下由于不常出现的问题,学习率需要更慢地降低。
在深度学习问题上,AdaGrad算法有时在降低学习率方面可能过于剧烈。
Adagrad算法的关键问题之一就是学习率按预定时间表O(t−12)\mathcal{O}(t^{-\frac{1}{2}})O(t−21)显著降低。 而且,Adagrad算法将梯度gt\mathbf{g}_tgt的平方累加成状态矢量st=st−1+gt2\mathbf{s}_t = \mathbf{s}_{t-1} + \mathbf{g}_t^2st=st−1+gt2。因此,由于缺乏规范化,没有约束力,st\mathbf{s}_tst持续增长,几乎上是在算法收敛时呈线性递增。
解决此问题的一种方法是使用st/t\mathbf{s}_t / tst/t。对gt\mathbf{g}_tgt的合理分布来说,它将收敛。遗憾的是,限制行为生效可能需要很长时间,因为该流程记住了值的完整轨迹。
另一种方法是按动量法中的方式使用泄漏平均值,即st←γst−1+(1−γ)gt2\mathbf{s}_t \leftarrow \gamma \mathbf{s}_{t-1} + (1-\gamma) \mathbf{g}_t^2st←γst−1+(1−γ)gt2,其中参数γ>0\gamma > 0γ>0。保持所有其它部分不变就产生了RMSProp算法。公式:
st←γst−1+(1−γ)gt2,xt←xt−1−ηst+ϵ⊙gt.\begin{aligned} \mathbf{s}_t & \leftarrow \gamma \mathbf{s}_{t-1} + (1 - \gamma) \mathbf{g}_t^2, \\ \mathbf{x}_t & \leftarrow \mathbf{x}_{t-1} - \frac{\eta}{\sqrt{\mathbf{s}_t + \epsilon}} \odot \mathbf{g}_t. \end{aligned} stxt←γst−1+(1−γ)gt2,←xt−1−st+ϵη⊙gt.
常数ϵ>0\epsilon > 0ϵ>0通常设置为10−610^{-6}10−6,以确保不会因除以零或步长过大而受到影响。鉴于这种扩展,现在可以自由控制学习率η\etaη,而不考虑基于每个坐标应用的缩放。就泄漏平均值而言,可以采用与之前在动量法中适用的相同推理。
扩展st\mathbf{s}_tst定义可获得:
st=(1−γ)gt2+γst−1=(1−γ)(gt2+γgt−12+γ2gt−2+…,).\begin{aligned} \mathbf{s}_t & = (1 - \gamma) \mathbf{g}_t^2 + \gamma \mathbf{s}_{t-1} \\ & = (1 - \gamma) \left(\mathbf{g}_t^2 + \gamma \mathbf{g}_{t-1}^2 + \gamma^2 \mathbf{g}_{t-2} + \ldots, \right). \end{aligned} st=(1−γ)gt2+γst−1=(1−γ)(gt2+γgt−12+γ2gt−2+…,).
同之前一样,使用1+γ+γ2+…,=11−γ1 + \gamma + \gamma^2 + \ldots, = \frac{1}{1-\gamma}1+γ+γ2+…,=1−γ1。因此,权重总和标准化为111且观测值的半衰期为γ−1\gamma^{-1}γ−1。
Adadelta是AdaGrad的另一种变体,主要区别在于其减少了学习率适应坐标的数量。
此外,广义上Adadelta被称为没有学习率,因为它使用变化量本身作为未来变化的校准。简而言之,Adadelta使用两个状态变量,st\mathbf{s}_tst用于存储梯度二阶导数的泄露平均值,Δxt\Delta\mathbf{x}_tΔxt用于存储模型本身中参数变化二阶导数的泄露平均值。
以下是Adadelta的技术细节。
首先获得泄露更新:
st=ρst−1+(1−ρ)gt2.\begin{aligned} \mathbf{s}_t & = \rho \mathbf{s}_{t-1} + (1 - \rho) \mathbf{g}_t^2. \end{aligned} st=ρst−1+(1−ρ)gt2.
与 RMSProp的区别在于,本算法使用重新缩放的梯度gt′\mathbf{g}_t'gt′执行更新,即
xt=xt−1−gt′.\begin{aligned} \mathbf{x}_t & = \mathbf{x}_{t-1} - \mathbf{g}_t'. \\ \end{aligned} xt=xt−1−gt′.
对于调整后的梯度gt′\mathbf{g}_t'gt′:
gt′=Δxt−1+ϵst+ϵ⊙gt,\begin{aligned} \mathbf{g}_t' & = \frac{\sqrt{\Delta\mathbf{x}_{t-1} + \epsilon}}{\sqrt{{\mathbf{s}_t + \epsilon}}} \odot \mathbf{g}_t, \\ \end{aligned} gt′=st+ϵΔxt−1+ϵ⊙gt,
其中Δxt−1\Delta \mathbf{x}_{t-1}Δxt−1是重新缩放梯度的平方gt′\mathbf{g}_t'gt′的泄漏平均值。首先将Δx0\Delta \mathbf{x}_{0}Δx0初始化为000,然后在每个步骤中使用gt′\mathbf{g}_t'gt′更新它,即
Δxt=ρΔxt−1+(1−ρ)gt′2,\begin{aligned} \Delta \mathbf{x}_t & = \rho \Delta\mathbf{x}_{t-1} + (1 - \rho) {\mathbf{g}_t'}^2, \end{aligned} Δxt=ρΔxt−1+(1−ρ)gt′2,
ϵ\epsilonϵ(例如10−510^{-5}10−5这样的小值)是为了保持数字稳定性而加入的。
Adadelta没有学习率参数。相反,它使用参数本身的变化率来调整学习率。
Adadelta需要两个状态变量来存储梯度的二阶导数和参数的变化。
Adadelta使用泄漏的平均值来保持对适当统计数据的运行估计。
Adam算法的关键组成部分之一是:使用指数加权移动平均值来估算梯度的动量和二次矩,即它使用状态变量:
vt←β1vt−1+(1−β1)gt,st←β2st−1+(1−β2)gt2.\begin{aligned} \mathbf{v}_t & \leftarrow \beta_1 \mathbf{v}_{t-1} + (1 - \beta_1) \mathbf{g}_t, \\ \mathbf{s}_t & \leftarrow \beta_2 \mathbf{s}_{t-1} + (1 - \beta_2) \mathbf{g}_t^2. \end{aligned} vtst←β1vt−1+(1−β1)gt,←β2st−1+(1−β2)gt2.
这里β1\beta_1β1和β2\beta_2β2是非负加权参数。常将它们设置为β1=0.9\beta_1 = 0.9β1=0.9和β2=0.999\beta_2 = 0.999β2=0.999。也就是说,方差估计的移动远远慢于动量估计的移动。注意,如果我们初始化v0=s0=0\mathbf{v}_0 = \mathbf{s}_0 = 0v0=s0=0,就会获得一个相当大的初始偏差。可以通过使用∑i=0tβi=1−βt1−β\sum_{i=0}^t \beta^i = \frac{1 - \beta^t}{1 - \beta}∑i=0tβi=1−β1−βt来解决这个问题。
相应地,标准化状态变量由下式获得
v^t=vt1−β1tand s^t=st1−β2t.\hat{\mathbf{v}}_t = \frac{\mathbf{v}_t}{1 - \beta_1^t} \text{ and } \hat{\mathbf{s}}_t = \frac{\mathbf{s}_t}{1 - \beta_2^t}. v^t=1−β1tvt and s^t=1−β2tst.
有了正确的估计,我们现在可以写出更新方程。首先,我们以非常类似于RMSProp算法的方式重新缩放梯度以获得
gt′=ηv^ts^t+ϵ.\mathbf{g}_t' = \frac{\eta \hat{\mathbf{v}}_t}{\sqrt{\hat{\mathbf{s}}_t} + \epsilon}. gt′=s^t+ϵηv^t.
与RMSProp不同,我们的更新使用动量v^t\hat{\mathbf{v}}_tv^t而不是梯度本身。此外,由于使用1s^t+ϵ\frac{1}{\sqrt{\hat{\mathbf{s}}_t} + \epsilon}s^t+ϵ1而不是1s^t+ϵ\frac{1}{\sqrt{\hat{\mathbf{s}}_t + \epsilon}}s^t+ϵ1进行缩放,两者会略有差异。前者在实践中效果略好一些,因此与RMSProp算法有所区分。通常,选择ϵ=10−6\epsilon = 10^{-6}ϵ=10−6,这是为了在数值稳定性和逼真度之间取得良好的平衡。最后,简单更新:
xt←xt−1−gt′.\mathbf{x}_t \leftarrow \mathbf{x}_{t-1} - \mathbf{g}_t'. xt←xt−1−gt′.
回顾Adam算法,其设计灵感很清楚:
首先,动量和规模在状态变量中清晰可见,其相当独特的定义使我们移除偏项(这可以通过稍微不同的初始化和更新条件来修正)。其次,RMSProp算法中两项的组合都非常简单。最后,明确的学习率η\etaη使我们能够控制步长来解决收敛问题。
Adam算法也存在一些问题:即使在凸环境下,当st\mathbf{s}_tst的二次矩估计值爆炸时,它可能无法收敛。为此,有人为st\mathbf{s}_tst提出了改进的更新和参数初始化。论文中建议重写Adam算法更新如下:
st←st−1+(1−β2)(gt2−st−1).\mathbf{s}_t \leftarrow \mathbf{s}_{t-1} + (1 - \beta_2) \left(\mathbf{g}_t^2 - \mathbf{s}_{t-1}\right). st←st−1+(1−β2)(gt2−st−1).
每当gt2\mathbf{g}_t^2gt2具有值很大的变量或更新很稀疏时,st\mathbf{s}_tst可能会太快地“忘记”过去的值。一个有效的解决方法是将gt2−st−1\mathbf{g}_t^2 - \mathbf{s}_{t-1}gt2−st−1替换为gt2⊙sgn(gt2−st−1)\mathbf{g}_t^2 \odot \mathop{\mathrm{sgn}}(\mathbf{g}_t^2 - \mathbf{s}_{t-1})gt2⊙sgn(gt2−st−1)。这就是Yogi更新,现在更新的规模不再取决于偏差的量。
st←st−1+(1−β2)gt2⊙sgn(gt2−st−1).\mathbf{s}_t \leftarrow \mathbf{s}_{t-1} + (1 - \beta_2) \mathbf{g}_t^2 \odot \mathop{\mathrm{sgn}}(\mathbf{g}_t^2 - \mathbf{s}_{t-1}). st←st−1+(1−β2)gt2⊙sgn(gt2−st−1).
论文中,作者还进一步建议用更大的初始批量来初始化动量,而不仅仅是初始的逐点估计。
Adam算法将许多优化算法的功能结合到了相当强大的更新规则中。
Adam算法在RMSProp算法基础上创建的,还在小批量的随机梯度上使用EWMA。
在估计动量和二次矩时,Adam算法使用偏差校正来调整缓慢的启动速度。
对于具有显著差异的梯度,我们可能会遇到收敛性问题。我们可以通过使用更大的小批量或者切换到改进的估计值st\mathbf{s}_tst来修正它们。Yogi提供了这样的替代方案。