深度学习 - 网络的优化 Optimisation for Training Deep Networks

深度学习问题需要一个损失函数,我们的目标就是通过优化算法来最小化损失,即最小化目标(损失)函数。需要注意的是,优化和深度学习的本质目标有差异,优化关注的是最小(最大)化目标,深度学习更关注模型。比如说,优化,也就是训练阶段,目标是最小化训练误差,但深度学习的目标是减小泛化误差,也就是推理阶段的误差。

最小化目标函数

最小化目标函数时,经常遇到的问题就是梯度消失,下述几种情况可能导致梯度消失。

  • 局部最小值:某点的函数值小于其附近所有点的函数值,为局部最小值;整个函数域内的最小值为全局最小值。当目标函数接近局部最优时,梯度会接近0。使用一定程度的噪声(SDG)可以让参数跳出局部最优。

  • 鞍点(saddle point):一个不是局部极值点的驻点称为鞍点。此时函数的梯度为 0,但该点既非局部最小值、也非局部最大值的位置。这种情况下优化也会停止。下面两种情况为函数f(x)=x3f(x)=x^3f(x,y)=x2y2f(x,y)=x^2-y^2的鞍点。

    • 驻点(stationary point):函数的一阶导数为零的点。

    • 拐点(inflection point):在一条连续曲线上,曲线的凹凸性发生改变的点。

凸 Convex

英文的逆天性在凹(concave)凸(convex)这两个单词上得到了极大体现。凹进去的函数是凸函数,凸出来的函数是凹函数。。。

凸函数最重要的一个特性就是:局部极小值就是全局极小值。也就是说,在最小化损失函数时,如果损失函数是凸函数,就不必担心陷入局部最小值的问题。

此外,凸优化的另一个特性是能有效处理约束。先明确啥是约束优化问题:

minimizexf(x)subject toci(x)0 (i{1,,N})\mathop{\mathrm{minimize}}_x f(x) \quad \text{subject to} \quad c_i(x) \leq 0 \ (\forall i \in \{1,\dots,N\})

比如在训练中,我们的目标是最小化损失函数f(x)f(x),不过给定了xx 一个 ci(x)c_i(x) 的可行域。

理论核心:拉格朗日函数。

直接求解带约束的优化问题较难,拉格朗日函数通过引入拉格朗日乘数,将约束条件融入目标函数,将约束优化问题转化为无约束的鞍点优化问题

L(x,α1,,αn)=f(x)+i=1nαici(x),αi0L(x,\alpha_1,\dots,\alpha_n) = f(x) + \sum_{i=1}^n \alpha_i c_i(x), \quad \alpha_i \geq 0

公式中,aia_i是拉格朗日乘数,非负实数

  • 若约束未激活,ci<0c_i<0,参数在可行域内,则 ai=0a_i=0 ,约束项在函数中消失。

  • 若约束激活,ci=0c_i=0 ,参数在可行域边界,则 ai>0a_i>0 ,约束项仍在函数中消失。

这被称作互补松弛条件,即乘积必须为零。互补松弛条件帮助我们在优化过程中判断哪些约束是活跃的,哪些是松弛的。

拉格朗日函数最优解是鞍点

  • xx 最小化 LL:等价于在约束条件下最小化目标函数 f(x)f(x)

  • aia_i 最大化 LL:等价于找到最合适的约束。

实践方法:

深度学习的损失函数并不总是凸函数,精确求解鞍点也不一定可行。

  • 惩罚方法:直接保留拉格朗日函数的$a_ic_i$项,加入到原损失函数中,获得新的优化目标。

    经典的惩罚方法例子包括权重衰减(Weight Decay, L2正则化)。

    权重衰减的约束函数为:

    c(w)=w2r20c(w) = \|w\|^2 - r^2 \leq 0

    权重衰减的惩罚项及最终优化目标为:

    Loss(w)+λ2w2\text{Loss}(w) + \frac{\lambda}{2}\|w\|^2

  • 投影方法

    惩罚方法属于软约束,投影方法属于硬约束。无论参数优化到哪里,都被强制投影回可行域内,确保约束被严格满足。

    典型方法包括梯度截断(Gradient Clipping)。

梯度下降 Gradient Decent

GD每次迭代使用全部训练样本计算梯度,然后统一更新参数,计算量巨大,因此有人提出了随机梯度下降(SGD),SGD是对GD的无偏估计,可以看出二者数学期望一致:

Eifi(x)=1ni=1nfi(x)=f(x)\mathbb{E}_i \nabla f_i(x) = \frac{1}{n} \sum_{i=1}^n \nabla f_i(x) = \nabla f(x)

SGD每次迭代随机选取一个样本计算梯度,然后更新参数。

不过SGD收敛不稳定,更新路径震荡比较大,对学习率也比较敏感,因此引入了动态学习率的概念:

η(t)=ηi, if titti+1(分段常数)η(t)=η0eλt(指数衰减)η(t)=η0(βt+1)α(多项式衰减)\begin{align*} \eta(t) &= \eta_i,\ \text{if}\ t_i \leq t \leq t_{i+1} \quad (\text{分段常数}) \\ \eta(t) &= \eta_0 \cdot e^{-\lambda t} \quad (\text{指数衰减}) \\ \eta(t) &= \eta_0 \cdot (\beta t + 1)^{-\alpha} \quad (\text{多项式衰减}) \end{align*}

为了平衡GD的稳定性和SGD高效性,有人提出了MiniBatch GD,即小批量梯度下降。这是目前DL的主流选择。GD和SGD在工程中使用极少,如果提到了,除非特别说明,否则使用的一般是MiniBatch GD。算法库中API的SGD实际上使用的也是MiniBatch GD。下文中SGD如不加说明均指MiniBatch GD

特性

GD

SGD

MiniBatch GD

数据使用量

全部训练样本

单个随机样本

一批样本(如32、64)

计算效率

低(计算量大)

高(计算量最小)

中等

收敛稳定性

高(路径平滑)

低(路径震荡大)

中等

内存需求

高(需加载全部数据)

低(加载一个样本)

中等(需加载小批量)

动量法 Momentum

核心是利用过去梯度的加权平均替代瞬时梯度

动态学习率解决的是训练过程中如何调整学习率的问题,动量法主要解决梯度噪声导致下降方向不稳定的问题。

SGD本身通过平均Batch内的梯度以减小方差,动量法的泄露平均值(Leaky Ave)是在时间维度上,对过去梯度做加权平均,以进一步降低方差。

vt=βvt1+gt,t1v_t=\beta v_{t-1} + g_{t,t-1}

其中:

  • vtv_t:动量,梯度的累计

  • β\beta:动量系数,对过去梯度的记忆程度

  • gt,t1 g_{t,t-1}:$t-1$时刻的瞬时梯度(SGD梯度)

公式递归展开可以得到:

vt=τ=0t1βτgtτ,tτ1v_t = \sum_{\tau=0}^{t-1} \beta^\tau g_{t-\tau, t-\tau-1}

大的 β\beta 说明对过去梯度记忆久,相当于长期平均;小的 β\beta说明仅关注近期梯度。

AdaGrad

全称 Adaptive Gradient,即自适应梯度算法,是梯度下降算法的一种改进。在一般的SGD中,学习率是统一的,AdaGrad认为不同的参数需要不同的学习率。

最终目的是在凸函数优化上能够快速收敛

梯度平方的累积量更新:

si=si+gi2s_i=s_i+g_i^2

参数更新:

xi=xiηsi+ϵgix_i=x_i-\cfrac{\eta}{\sqrt{s_i+\epsilon}}g_i

其中:

  • gig_i:参数xix_i 的梯度

  • sis_i:初值为0,gig_i 的累计平方和

  • η\eta:学习率

  • ϵ\epsilon:极小常数避免分母为0

自动调整的方式就是将学习率 η\eta 除以 si+ϵ\sqrt{s_i+\epsilon} 来实现的。

RMSProp

均方根传播,全称 Root Mean Square Propagation‌。是AdaGrad的改进:

梯度平方的累积量更新:

si=γsi+(1γ)gi2s_i = \gamma \cdot s_i + (1-\gamma) \cdot g_i^2

参数更新:

xi=xiηsi+ϵgix_i = x_i - \frac{\eta}{\sqrt{s_i+\epsilon}} \cdot g_i

加入了一个衰减系数 γ\gamma ,取值范围 [0,1]。衰减系数可以避免 sis_i 的无限增大和AdaGrad学习率消失的问题。

AdaDelta

改进的RMSProp,去掉了学习率,完全不需要设置学习率

梯度平方的累计量更新:

si=ρsi+(1ρ)gi2s_i = \rho \cdot s_i + (1-\rho) \cdot g_i^2

修正后的梯度计算:

gi=Δxi+ϵsi+ϵgig'_i = \sqrt{\frac{\Delta x_i + \epsilon}{s_i + \epsilon}} \cdot g_i

参数值更新:

xi=xigix_i = x_i - g'_i

参数变化量平方的累计量更新:

Δxi=ρΔxi+(1ρ)gi2\Delta x_i = \rho \cdot \Delta x_i + (1-\rho) \cdot {g'_i}^2

这里的 ρ\rho 为衰减系数。

太复杂的公式不用记,只需要知道AdaDelta使用 Δxi+ϵ\sqrt{{\Delta x_i + \epsilon}} 来替代了原本的学习率,让算法学习率的变化更加Adaptive。

Adam

全称Adaptive Moment Estimation,整合了RMSProp和Momentum方法。

Adam可以说是目前DL中使用频率最高的优化方法了。

融合Momentum的惯性特性,即梯度的指数移动平均:

mi=β1mi+(1β1)gim_i = \beta_1 \cdot m_i + (1-\beta_1) \cdot g_i

参考RMSProp自适应学习率,梯度平方的指数移动平均:

vi=β2vi+(1β2)gi2v_i = \beta_2 \cdot v_i + (1-\beta_2) \cdot g_i^2

一般将 β1\beta_1β2\beta_2 分别设为0.9和0.999。

为了避免初始偏差过大,对上述 mim_iviv_i 进行修正:

m^i=mi1β1t\hat{m}_i = \frac{m_i}{1-\beta_1^t}

v^i=vi1β2t\hat{v}_i = \frac{v_i}{1-\beta_2^t}

参数最终更新:

xi=xiηv^i+ϵm^ix_i = x_i - \frac{\eta}{\sqrt{\hat{v}_i} + \epsilon} \cdot \hat{m}_i

Pytorch中已经对上述优化方法提供了直接的参数实现:

# 1 带动量的SGD (Momentum)
model_momentum = LinearModel()
optimizer_momentum = optim.SGD(model_momentum.parameters(), lr=lr, momentum=0.9)  # momentum=0.9是常用值
loss_momentum = train_model(model_momentum, optimizer_momentum, epochs)
​
# 2 Adagrad
model_adagrad = LinearModel()
optimizer_adagrad = optim.Adagrad(model_adagrad.parameters(), lr=lr)  # 自适应学习率,无需手动调momentum
loss_adagrad = train_model(model_adagrad, optimizer_adagrad, epochs)
​
# 3 RMSprop
model_rmsprop = LinearModel()
optimizer_rmsprop = optim.RMSprop(model_rmsprop.parameters(), lr=lr, alpha=0.99)  # alpha是移动平均系数
loss_rmsprop = train_model(model_rmsprop, optimizer_rmsprop, epochs)
​
# 4 Adam 
model_adam = LinearModel()
optimizer_adam = optim.Adam(model_adam.parameters(), lr=lr, betas=(0.9, 0.999))  # betas是两个动量系数
loss_adam = train_model(model_adam, optimizer_adam, epochs)

参考文章

[1] 临界点、驻点、拐点、鞍点、顶点(曲线) - 知乎

[2] 11. 优化算法 — 动手学深度学习 2.0.0 documentation

[3] Anderson, S. (n.d.). Lecture 5 Optimisation for Training Deep Networks [Lecture]. In ACS61011 Deep Learning. University of Sheffield.

[4] 优化方法——AdaGrad、RMSProp、Adam - 知乎

[5] 一篇入门之-AdaGrad算法(含原理、公式、代码实现)-老饼讲解