梯度下降法

RPChe_

即 Gradient Descent 。我们不是来讲最优化方法的,所以这里将不会提供收敛性和收敛速度的严格分析。

问题背景

  • 我们希望求解如下问题: 其中 在定义域内可微。

算法描述

  • 假设我们已经得到了一个初始解 , 那么一个简单的思路是,我们每次向 处的负梯度方向移动。我们期待通过有限次的移动就可以抵达一个足够好的解,这就是最朴素的梯度下降算法。

    1
    2
    3
    4
    5
    # Vanilla gradient descent
    w = initialize_weights()
    for t in range(num_steps):
    dw = compute_gradient(loss_func, data, w)
    w -= learning_rate * dw

SGD

  • 即 Stochastic Gradient Descent 。在运行梯度下降法时,实践中的一个常见的问题是,我们的数据集往往很大,那么每一次计算损失函数的时间开销是不能接受的。因此我们往往会考虑使用蒙特卡洛方法,即选定一个 batch size(e.g. 32/64/128),每一次计算梯度时,我们都从数据集中随机取样出大小为 batch size 的样本集,然后再对样本集计算梯度。我们期待在梯度下降的过程中,样本集可以较好的反映出数据集的性质。这样的算法就被称作 SGD 。

    1
    2
    3
    4
    5
    6
    # Stochastic gradient descent
    w = initialize_weights()
    for t in range(num_steps):
    minibatch = sample_data(data, batch_size)
    dw = compute_gradient(loss_func, minibatch, w)
    w -= learning_rate * dw

SGD with Momentum

  • 为了避免梯度的剧烈震荡/解停滞在鞍点/较大的噪声,我们可以考虑引入“速度”的概念来替代目前的“加速度”(即负梯度)。

    1
    2
    3
    4
    5
    6
    7
    8
    # Stochastic gradient descent with momentum
    w = initialize_weights()
    v = 0
    for t in range(num_steps):
    minibatch = sample_data(data, batch_size)
    dw = compute_gradient(loss_func, minibatch, w)
    v = rho * v + dw
    w -= learning_rate * v

    其中的超参数 rho 是衰减系数。SGD with Momentum 也有另一种实现,称作 Nesterov Momentum ,这里就不详谈了。

AdaGrad

  • 即自适应梯度下降。类似的,我们希望避免梯度剧烈震荡的问题,因此考虑保存每一维上梯度的平方历史和,并在计算下一个解时为每一维除以对应的开根平方历史和。这样做的好处是,变化大的维度被限制,而变化小的维度则相对不被限制。

    1
    2
    3
    4
    5
    6
    7
    8
    # Adaptive stochastic gradient descent
    w = initialize_weights()
    grad_squared = 0
    for t in range(num_steps):
    minibatch = sample_data(data, batch_size)
    dw = compute_gradient(loss_func, minibatch, w)
    grad_squared += dw**2
    w -= learning_rate * dw / (grad_squared.sqrt() + 1e-7)

RMSProp

  • AdaGrad 存在一个明显的问题,就是随着 grad_squared 的积累,到最后解就很难移动了。一个好的改进是 RMSProp(Root Mean Square Propagation),可以将其视作带有衰减系数的 AdaGrad 。

    1
    2
    3
    4
    5
    6
    7
    8
    # RMSProp
    w = initialize_weights()
    grad_squared = 0
    for t in range(num_steps):
    minibatch = sample_data(data, batch_size)
    dw = compute_gradient(loss_func, minibatch, w)
    grad_squared = decay_rate * grad_squared + (1 - decay_rate) * dw**2
    w -= learning_rate * dw / (grad_squared.sqrt() + 1e-7)

Adam

  • 现在我们试着结合 Momentum 和 RMSProp 的优点,这样就得到了 Adam :

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    # Adaptive Moment Estimation
    moment1 = 0
    moment2 = 0
    for t in range(num_steps):
    minibatch = sample_data(data, batch_size)
    dw = compute_gradient(loss_func, minibatch, w)
    moment1 = beta1 * moment1 + (1 - beta1) * dw
    moment2 = beta2 * moment2 + (1 - beta2) * dw**2
    moment1_unbias = moment1 / (1 - beta1**t)
    moment2_unbias = moment2 / (1 - beta2**t)
    w -= learning_rate * moment1_unbias / (moment2_unbias.sqrt() + 1e-7)

    我们在上述的代码加入了计算 moment1_unbiasmoment2_unbias ,这是为了避免在算法最开始更新 w 时除以一个极小的数,导致解剧烈变化。1

    特别的,Adam 在很多时候都是一个好的方法,建议的初始参数 beta1 = 0.9beta2 = 0.999learning_rate = 1e-3, 5e-4, 1e-4

基于二阶微分的优化

  • 基于二阶微分的优化也是有研究的,但是并不常用,因为计算 Hessian 矩阵以及矩阵求逆的复杂度开销太高了。这里就不详谈了。

  1. 这样的写法应该有更严格的推导。↩︎

  • 标题: 梯度下降法
  • 作者: RPChe_
  • 创建于 : 2025-03-06 00:00:00
  • 更新于 : 2025-03-06 23:02:12
  • 链接: https://rpche-6626.github.io/2025/03/06/DL/GD/
  • 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。
评论