深度学习问题需要一个损失函数,我们的目标就是通过优化算法来最小化损失,即最小化目标(损失)函数。

需要注意的是,优化和深度学习的本质目标有差异:优化关注的是最小(最大)化目标函数,深度学习更关注模型的泛化能力。比如说,训练阶段的目标是最小化训练误差,但深度学习真正关心的是减小泛化误差,也就是推理阶段的误差。


最小化目标函数

最小化目标函数时,优化过程容易在梯度接近 0 的位置停滞。下述几种情况都可能导致这一问题。

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

  • 鞍点(saddle point):一个不是局部极值点的驻点称为鞍点。此时函数的梯度为 0,但该点既非局部最小值、也非局部最大值。这种情况下优化同样会停滞。多元函数 f(x,y)=x^2-y^2 在原点处就是典型的鞍点;一元函数 f(x)=x^3x=0 处则是驻点,同时也是拐点。

    • 驻点(stationary point):函数的一阶导数为零的点。
    • 拐点(inflection point):在一条连续曲线上,曲线的凹凸性发生改变的点。

凸 Convex

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

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

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

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

比如在训练中,我们的目标是最小化损失函数 f(x),同时要求 x 落在由 c_i(x) 定义的可行域内。

理论核心:拉格朗日函数

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

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

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

  • 若约束未激活,c_i(x)<0,参数在可行域内部,则 \alpha_i=0,该约束对最优解不起作用。
  • 若约束激活,c_i(x)=0,参数在可行域边界,则 \alpha_i>0,约束在最优解处被收紧。

无论哪种情况,都满足 \alpha_i c_i(x)=0,这被称作互补松弛条件。互补松弛条件帮助我们在优化过程中判断哪些约束是活跃的,哪些是松弛的。

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

  • x 最小化 L:等价于在约束条件下最小化目标函数 f(x)
  • \alpha_i 最大化 L:等价于找到最合适的约束力度

实践方法

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

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

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

权重衰减的约束函数为:

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

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

\text{Loss}(w) + \frac{\lambda}{2}\|w\|^2
  • 投影方法

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

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


梯度下降 Gradient Descent

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

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

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

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

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

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

特性 GD SGD MiniBatch GD
数据使用量 全部训练样本 单个随机样本 一批样本(如 32、64)
计算效率 低(计算量大) 高(计算量最小) 中等
收敛稳定性 高(路径平滑) 低(路径震荡大) 中等
内存需求 高(需加载全部数据) 低(加载一个样本) 中等(需加载小批量)

动量法 Momentum

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

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

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

v_t=\beta v_{t-1} + g_{t,t-1}

其中:

  • v_t:动量,梯度的累计
  • \beta:动量系数,表示对过去梯度的记忆程度
  • g_{t,t-1}t-1 时刻的瞬时梯度(SGD 梯度)

公式递归展开可以得到:

v_t = \sum_{\tau=0}^{t-1} \beta^\tau g_{t-\tau, t-\tau-1}

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


AdaGrad

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

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

梯度平方的累积量更新:

s_i=s_i+g_i^2

参数更新:

x_i=x_i-\cfrac{\eta}{\sqrt{s_i+\epsilon}}g_i

其中:

  • g_i:参数 x_i 的梯度
  • s_i:初值为 0,g_i 的累计平方和
  • \eta:学习率
  • \epsilon:极小常数,避免分母为 0

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


RMSProp

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

梯度平方的累积量更新:

\text{指数移动平均: } s_i = \gamma \cdot s_i + (1-\gamma) \cdot g_i^2

参数更新:

\text{参数更新: } x_i = x_i - \frac{\eta}{\sqrt{s_i+\epsilon}} \cdot g_i

加入了一个衰减系数 \gamma,取值范围 [0, 1]。衰减系数可以避免 s_i 无限增大,从而缓解 AdaGrad 学习率过早消失的问题。


AdaDelta

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

梯度平方的累计量更新:

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

修正后的梯度计算:

g'_i = \sqrt{\frac{\Delta x_i + \epsilon}{s_i + \epsilon}} \cdot g_i

参数值更新:

x_i = x_i - g'_i

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

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

这里的 \rho 为衰减系数。

太复杂的公式不用记,只需要知道 AdaDelta 用 \sqrt{\Delta x_i + \epsilon}\sqrt{s_i + \epsilon} 的比值来替代原本的学习率,让每一步更新幅度更加自适应。


Adam

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

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

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

m_i = \beta_1 \cdot m_i + (1-\beta_1) \cdot g_i

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

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

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

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

\hat{m}_i = \frac{m_i}{1-\beta_1^t}
\hat{v}_i = \frac{v_i}{1-\beta_2^t}

参数最终更新:

x_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 算法(含原理、公式、代码实现)-老饼讲解