本文针深度学习中不同的优化器进行了汇总,包括公式实现、代码示例、演变过程和优缺点做了较为详细的分析。
随机梯度下降(SGD)
随机梯度下降(SGD)是一种简单但极其有效的优化算法,经常用于训练各种类型的机器学习模型,特别是大规模数据集上的深度学习模型。SGD 的核心在于每次更新参数时只使用数据集中的一个样本或一小批样本来计算梯度,这与传统的批量梯度下降(BGD)算法相比,可以大大加快计算速度并降低内存需求。
提出的契机
在 SGD 被普遍采纳之前,批量梯度下降是最常见的优化方法,它在每一步都使用整个数据集来计算梯度并更新参数。虽然这种方法理论上可以保证在凸优化问题上收敛到全局最优解,但在面对大规模数据集时,每次迭代都需要大量的计算资源和时间。SGD 的提出正是为了解决这个问题,通过每次只使用一个样本或一小批样本来估计真实梯度,从而加速训练过程,并使得模型能够在线更新和处理大数据。
理论与公式
SGD 的基本更新公式非常简单:
- 选择初始参数 θ 0 \theta_0 θ0 和学习率 η \eta η。
- 在每一次迭代 k k k 中,随机选择一个样本 i i i(或一小批样本),计算该样本上的梯度:
g = ∇ θ k − 1 L i ( θ ) g = \nabla_{\theta_{k-1}} L_i(\theta) g=∇θk−1Li(θ)
其中 L i ( θ ) L_i(\theta) Li(θ) 是在第 i i i 个样本上的损失函数。 - 更新参数:
θ k = θ k − 1 − η g \theta_{k} = \theta_{k-1} - \eta g θk=θk−1−ηg
这里没有 r k r_k rk 的概念,因为 SGD 不涉及梯度累积或调整学习率的复杂机制。每次迭代直接使用当前梯度进行参数更新。
代码示例
下面是 SGD 算法的一个简单 Python 代码示例:
python
def sgd_update(parameters, gradients, lr=0.01):
for param, grad in zip(parameters, gradients):
param -= lr * grad
这个函数接受 parameters(模型参数列表),gradients(对应梯度列表),以及 lr(学习率)作为输入,然后使用 SGD 公式更新每个参数。
优缺点
优点
- 效率高: 由于每次只处理一个样本或一小批样本,SGD 可以快速完成迭代,特别适合大规模数据集。
- 在线学习: SGD 可以用于在线学习场景,即模型可以随着新数据的到来实时更新。
- 跳出局部最优: SGD 因为其随机性,有可能跳出局部最优,找到更好的解。
缺点
- 收敛问题: 由于每次更新只使用一个样本,SGD 的收敛过程可能会非常嘈杂,导致训练不稳定。
- 超参数调整: 学习率等超参数的选择对 SGD 性能有很大影响,而且不容易调整。
- 可能不是最优解: 在非凸优化问题上,SGD 可能只能找到局部最优解而非全局最优解。
总的来说,SGD 是一种高效且广泛应用的优化算法,尤其适用于大规模数据集。然而,其随机性也带来了一些挑战,比如可能的训练不稳定
梯度下降 + Momentum
动量梯度下降(Momentum Gradient Descent)是一种优化算法,用于训练机器学习模型,特别是神经网络模型。它是在标准梯度下降算法的基础上引入了动量概念,以解决梯度下降中的一些问题,例如局部最小值、鞍点等。
提出背景:
动量梯度下降算法的提出是为了解决传统梯度下降算法中的一些问题。
- 在高度曲折的损失函数表面上的震荡
- 收敛速度缓慢等。
通过引入动量的概念,可以在一定程度上加速收敛,并且有助于跳出局部最小值。
理论:
动量梯度下降的核心思想是引入动量因子,使得更新方向不仅取决于当前梯度,还考虑了之前更新方向的影响。这样可以在一定程度上平滑更新路径,加速收敛。
公式
- While 条件:
- g ← ∇ θ k − 1 L ( θ ) g \leftarrow \nabla_{\theta_{k-1}} L(\theta) g←∇θk−1L(θ) # 计算梯度
- w k = α w k − 1 + ( 1 − α ) g w_k = \alpha w_{k-1} + (1 - \alpha) g wk=αwk−1+(1−α)g # 计算动量
- θ k = θ k − 1 − η w k \theta_{k} = \theta_{k-1} - \eta w_k θk=θk−1−ηwk # 更新参数
其中:
- θ k − 1 \theta_{k-1} θk−1是第k-1步的模型参数向量。
- ∇ L ( θ k − 1 ) \nabla L(\theta_{k-1}) ∇L(θk−1) 是损失函数 L L L 对参数 θ k − 1 \theta_{k-1} θk−1 的梯度。
- η \eta η是学习率。
- α \alpha α是动量参数,通常取值在 0 到 1 之间,决定了历史梯度对当前更新的影响程度。
- w k w_k wk是动量,表示历史梯度的加权累积。
代码示例
下面是一个简单的 Python 代码示例,演示了如何使用动量梯度下降算法来训练一个简单的线性回归模型:
python
import numpy as np
def momentum_gradient_descent(params, grads, velocities, lr=0.01, momentum=0.9):
for param, grad, velocity in zip(params, grads, velocities):
velocity[:] = momentum * velocity + lr * grad
param[:] -= velocity
优缺点
优点:
- 加速收敛:动量可以帮助加速SGD在相关方向上的收敛,并抑制振荡,使得训练过程更快。
- 降低震荡:通过平滑梯度,动量方法可以减少训练过程中的震荡,使得更新过程更稳定。
- 逃离局部最小:动量的累积可以帮助算法跳出局部最小值。
缺点:
- 超参数敏感:动量系数的选择对算法的性能影响很大,需要仔细调整。
- 可能错过最小值:过大的动量可能会导致算法在最小值附近"冲过头",从而错过最优解。
Adagrad
AdaGrad(Adaptive Gradient Algorithm)是一种自适应学习率的梯度下降算法,于2011年由Duchi等人提出。这个算法主要是为了解决标准的梯度下降算法中学习率一成不变的问题。在标准的梯度下降算法中,如果学习率过大,可能会导致算法在最小值附近震荡而不收敛;如果学习率过小,又会导致收敛速度过慢。AdaGrad算法通过自适应调整每个参数的学习率,尝试解决这个问题。
理论和公式
AdaGrad算法的核心思想是对每个参数根据其历史梯度的平方和进行自适应地调整学习率。这意味着对于出现频率高的特征,其学习率会较低;而对于出现频率低的特征,其学习率会较高。这种方式使得模型在稀疏数据上的表现更好。
AdaGrad的参数更新公式如下:
-
while 条件:
- g = ∇ θ k − 1 L ( θ ) g = \nabla_{\theta_{k-1}} L(\theta) g=∇θk−1L(θ)
- r k = r k − 1 + g ⊙ g r_{k} = r_{k-1} + g \odot g rk=rk−1+g⊙g
- η = η r k + ϵ \eta = \frac{\eta}{\sqrt{r_{k} + \epsilon}} η=rk+ϵ η
- θ k = θ − η g \theta_{k} = \theta - \eta g θk=θ−ηg
-
g = ∇ θ k − 1 L ( θ ) g = \nabla_{\theta_{k-1}} L(\theta) g=∇θk−1L(θ): g g g代表损失函数 L ( θ ) L(\theta) L(θ)关于参数 θ \theta θ在 θ k − 1 \theta_{k-1} θk−1点的梯度,其中 ∇ θ \nabla_{\theta} ∇θ表示梯度运算符, θ k − 1 \theta_{k-1} θk−1表示上一步的参数值。
-
r k = r k − 1 + g ⊙ g r_{k} = r_{k-1} + g \odot g rk=rk−1+g⊙g: r k r_{k} rk代表到当前迭代为止所有梯度平方的累积和, ⊙ \odot ⊙表示元素乘法(即Hadamard乘积)。这里 r k r_{k} rk用于调整学习率,以适应不同参数的不同梯度值。
-
η = η r k + ϵ \eta = \frac{\eta}{\sqrt{r_{k} + \epsilon}} η=rk+ϵ η: 这里通过累积梯度平方和 r k r_{k} rk来调整学习率 η \eta η。 ϵ \epsilon ϵ是一个很小的常数,用于防止分母为零。这样的调整使得学习率对于出现频繁的特征会更小,而对于稀疏特征会更大,有助于提高模型在稀疏数据上的性能。
-
θ k = θ − η g \theta_{k} = \theta - \eta g θk=θ−ηg: 这是参数更新的步骤,新的参数 θ k \theta_{k} θk通过从当前参数 θ \theta θ减去学习率 η \eta η乘以梯度 g g g来计算。这一步是基于梯度下降算法的,目的是减少损失函数 L ( θ ) L(\theta) L(θ)的值。
代码示例
一个简单的AdaGrad算法的Python代码示例如下:
python
import numpy as np
# AdaGrad optimizer function
def adagrad_optimizer(grad, params, sqr_grads, learning_rate=0.01, epsilon=1e-8):
sqr_grads += grad ** 2
adjusted_grad = grad / (np.sqrt(sqr_grads) + epsilon)
params -= learning_rate * adjusted_grad
# Example usage
params = np.array([1.0, 2.0]) # Initial parameters
grads = np.array([0.2, -0.3]) # Example gradients
sqr_grads = np.zeros_like(params) # Initialize square gradients sum
adagrad_optimizer(grads, params, sqr_grads)
print(params) # Updated parameters
优缺点
优点
- 自适应学习率:对于每个参数,AdaGrad根据其历史梯度的平方和自适应调整学习率,减少了手动调节学习率的需要。
- 适用于稀疏数据:对于稀疏特征,AdaGrad能够自动提高其学习率,使得模型更快地学习到这些特征的重要性。
缺点
- 学习率持续衰减:由于累积的平方梯度持续增加,学习率会持续衰减,最终导致学习率过小,从而使得训练后期模型难以收敛。
- 存储梯度平方和:需要为每个参数存储一个累积的梯度平方和,这在参数很多时会增加额外的内存开销。
AdaGrad算法在处理稀疏数据和不同频率特征的调整上具有优势,但在长期训练中可能会遇到学习率过小的问题。为了克服这个问题,后续研究者提出了AdaGrad的改进版本,如RMSProp和Adam,这些算法在各种机器学习任务中得到了广泛的应用。
RMSProp
RMSProp(Root Mean Square Propagation)是一种自适应学习率的优化算法,主要用于深度学习中的参数更新。旨在解决 Adagrad 算法在深度学习训练过程中学习率逐渐减小直至无法进一步学习的问题。
提出的契机
Adagrad 算法通过累积历史梯度的平方来调整每个参数的学习率,从而实现对频繁更新参数的惩罚和对不频繁更新参数的鼓励。然而,Adagrad 也存在一个问题:随着参数更新的累积,学习率会越来越小,最终导致学习过程提前结束。RMSProp 通过引入一个衰减系数来解决这个问题,使得历史信息能够指数级衰减,从而避免了学习率持续下降的问题。
理论与公式
RMSProp 的核心思想是对每个参数使用不同的学习率,这些学习率是根据参数的最近梯度大小自适应调整的。具体来说,RMSProp 使用平方梯度的指数加权移动平均来调整学习率,从而使得学习率的调整更加平滑。
修改后的公式如下:
- While 条件:
- g = ∇ θ k − 1 L ( θ ) g = \nabla_{\theta_{k-1}} L(\theta) g=∇θk−1L(θ)
- r k = β r k − 1 + ( 1 − β ) g ⊙ g r_{k} = \beta r_{k-1} + (1 - \beta) g \odot g rk=βrk−1+(1−β)g⊙g
- η = η r k + ϵ \eta = \frac{\eta}{\sqrt{r_{k} + \epsilon}} η=rk+ϵ η
- θ k = θ k − 1 − η g \theta_{k} = \theta_{k-1} - \eta g θk=θk−1−ηg
其中, ⊙ \odot ⊙ 表示元素乘积, β \beta β 是衰减系数(通常设置为 0.9),用于控制历史信息的衰减速度, ϵ \epsilon ϵ 是为了避免除以 0 的小常数(通常设置为 1 e − 8 1e-8 1e−8)。
代码示例
下面是一个简单的 RMSProp 更新规则的 Python 代码示例:
python
def rmsprop_update(parameters, gradients, sq_grads, lr=0.01, beta=0.9, epsilon=1e-8):
for param, grad in zip(parameters, gradients):
sq_grads[param] = beta * sq_grads[param] + (1 - beta) * (grad ** 2)
param_update = lr / (np.sqrt(sq_grads[param]) + epsilon) * grad
param -= param_update
在这个函数中,parameters 是模型参数列表,gradients 是对应的梯度列表,sq_grads 是历史梯度平方的累积(需要初始化),lr 是学习率,beta 和 epsilon 是 RMSProp 算法的超参数。
优缺点
优点
- 自适应学习率调整: RMSProp 通过考虑最近的梯度大小来自适应地调整每个参数的学习率,避免了全局学习率带来的一些问题。
- 解决了 Adagrad 的缺陷: 通过引入衰减因子,解决了 Adagrad 学习率持续下降直至消失的问题。
缺点
- 超参数依赖: RMSProp 的效果在很大程度上依赖于衰减因子等超参数的选择。
- 可能不适合所有问题: 尽管 RMSProp 在许多深度学习问题中表现良好,但它并不保证在所有问题上都是最优的。
RMSProp 是一种强大的优化算法,特别适合处理非凸优化和深度学习中的大规模问题。然而,正如任何算法一样,理解其工作原理和局限性对于有效地应用它至关重要。
Adam
Adam(Adaptive Moment Estimation)是一种广泛使用的深度学习优化算法,由 Diederik P. Kingma 和 Jimmy Ba 在 2014 年提出。它结合了动量法(Momentum)和 RMSProp 的思想,旨在通过计算梯度的一阶矩估计和二阶矩估计来调整每个参数的学习率,从而实现更高效的网络训练。
提出的契机
在 Adam 提出之前,研究者们已经在使用像 Momentum 和 RMSProp 这样的优化算法来加速深度神经网络的训练过程。然而,这些算法各有优势和局限。Momentum 善于处理梯度的方向和大小,而 RMSProp 善于调整学习率以应对数据的稀疏性。Adam 的提出是为了结合这两种算法的优点,同时减少它们的缺点,提供一种更加鲁棒的优化解决方案。
理论与公式
Adam 算法的关键在于同时计算梯度的一阶矩(均值)和二阶矩(未中心的方差)的指数移动平均,并对它们进行偏差校正,以确保在训练初期时梯度估计不会偏向于 0。
算法的更新规则如下:
- While 条件:
- g = ∇ θ k − 1 L ( θ ) g = \nabla_{\theta_{k-1}} L(\theta) g=∇θk−1L(θ)
- m k = β 1 m k − 1 + ( 1 − β 1 ) g m_{k} = \beta_1 m_{k-1} + (1 - \beta_1) g mk=β1mk−1+(1−β1)g
- v k = β 2 v k − 1 + ( 1 − β 2 ) g ⊙ g v_{k} = \beta_2 v_{k-1} + (1 - \beta_2) g \odot g vk=β2vk−1+(1−β2)g⊙g
- m ^ k = m k 1 − β 1 k \hat{m}{k} = \frac{m{k}}{1 - \beta_1^k} m^k=1−β1kmk
- v ^ k = v k 1 − β 2 k \hat{v}{k} = \frac{v{k}}{1 - \beta_2^k} v^k=1−β2kvk
- θ k = θ k − 1 − η v ^ k + ϵ m ^ k \theta_{k} = \theta_{k-1} - \frac{\eta}{\sqrt{\hat{v}{k}} + \epsilon} \hat{m}{k} θk=θk−1−v^k +ϵηm^k
其中, m k m_{k} mk 和 v k v_{k} vk 分别是梯度的一阶矩和二阶矩的估计, β 1 \beta_1 β1 和 β 2 \beta_2 β2 是控制这两个矩估计的指数衰减率,通常设置为 0.9 和 0.999。 ϵ \epsilon ϵ是一个非常小的数(例如1e-8),防止除以零。 k是当前迭代次数,用于做偏差校正。
- β 1 k = β 1 × β 1 × ... × β 1 \beta_1^k = \beta_1 \times \beta_1 \times \ldots \times \beta_1 β1k=β1×β1×...×β1 (共乘以 k k k 次)
- β 2 k = β 2 × β 2 × ... × β 2 \beta_2^k = \beta_2 \times \beta_2 \times \ldots \times \beta_2 β2k=β2×β2×...×β2 (共乘以 k k k 次)
在 Adam 优化算法中, β 1 k \beta_1^k β1k 和 β 2 k \beta_2^k β2k 用于进行偏差校正(bias correction)。这是因为在算法的初期,由于 m k m_k mk 和 v k v_k vk(分别是梯度的一阶矩和二阶矩的估计)是从 0 开始初始化的,会导致它们在初始阶段被低估。特别是当 β 1 \beta_1 β1 和 β 2 \beta_2 β2 接近 1 时,这个偏差会更加显著。为了补偿这种估计的偏差,Adam 算法引入了偏差校正步骤。
偏差校正的作用
-
对 m k m_k mk的偏差校正: 初始时刻,一阶矩 m k m_k mk 的值偏小,因为它是梯度值的加权平均,起始所有梯度都被初始化为 0。通过除以 1 − β 1 k 1 - \beta_1^k 1−β1k,可以将 m k m_k mk 的值放大,使其更快地接近实际的梯度均值。随着迭代次数 k k k的增加, β 1 k \beta_1^k β1k 会趋向于 0,偏差校正因子 1 − β 1 k 1 - \beta_1^k 1−β1k 就会趋向于 1,偏差校正的影响会逐渐减小。
-
对 v k v_k vk 的偏差校正: 类似地,二阶矩 v k v_k vk(梯度平方的加权平均)也会在初始阶段被低估。通过除以 1 − β 2 k 1 - \beta_2^k 1−β2k,可以增加 v k v_k vk 的值,使其更接近实际的梯度平方的均值。随着 k k k 的增加,偏差校正因子 1 − β 2 k 1 - \beta_2^k 1−β2k 也会趋向于 1。
偏差校正的重要性
偏差校正对于 Adam 算法的性能至关重要,特别是在训练的初期阶段。没有偏差校正,算法可能会因为初始的低估而导致学习步长太小,进而影响训练的速度和效果。通过偏差校正,Adam 算法可以更快地调整其参数更新的大小,加速初期的学习过程,并提高整体的优化效率。随着训练的进行,这种校正变得不那么重要,因为 m k m_k mk 和 v k v_k vk 会逐渐积累足够的信息来准确估计梯度的一阶和二阶矩。
代码示例
以下是 Adam 优化算法的 Python 代码示例:
python
def adam_update(parameters, gradients, m, v, t, lr=0.001, beta1=0.9, beta2=0.999, epsilon=1e-8):
for param, grad in zip(parameters, gradients):
m[param] = beta1 * m[param] + (1 - beta1) * grad
v[param] = beta2 * v[param] + (1 - beta2) * (grad ** 2)
m_corrected = m[param] / (1 - beta1 ** t)
v_corrected = v[param] / (1 - beta2 ** t)
param_update = lr * m_corrected / (np.sqrt(v_corrected) + epsilon)
param -= param_update
这个函数中,parameters 和 gradients 分别是模型参数和梯度的列表,m 和 v 是梯度一阶和二阶矩的估计,t 是当前迭代次数,lr, beta1, beta2, epsilon 是 Adam 算法的超参数。
优缺点
优点
- 自适应学习率: Adam 通过计算一阶和二阶矩估计来为每个参数自适应地调整学习率。
- 偏差校正: 初始阶段梯度估计可能偏低,通过偏差校正可以加速初期的学习速率。
- 适应性强: Adam 在很多不同的模型和数据集上都表现出了良好的性能。
缺点
- 超参数调整: 尽管默认的超参数在很多情况下都表现良好,但某些问题可能需要仔细的超参数调整。
- 内存需求: 相对于一些简单的优化算法,Adam 需要存储更多的变量(例如一阶和二阶矩估计),这可能会增加计算资源的消耗。
Adam 由于其优秀的性能和适应性,已经成为深度学习领域中最受欢迎的优化算法之一。理解其工作原理对于有效利用这一工具至关重要。