常规的梯度下降算法中,会遇到平缓区域,碰到鞍点,碰到局部最小值(截止当前无解),因此为了解决这个问题,我们需要优化传统的梯度下降算法。
动量算法(Momentum) 是梯度下降算法的一种优化方法,旨在解决传统梯度下降容易陷入局部最小值或在鞍点附近震荡的问题。动量算法通过引入一个“动量”的概念,能够加速梯度下降的收敛,并帮助模型更快地找到全局最优解。
1. 梯度下降的基本问题
在常规的梯度下降中,模型通过计算损失函数的梯度,并根据这个梯度的方向更新参数。公式如下:
其中:
-
是模型的参数。
-
是学习率。
-
是参数 方向的梯度。
在简单的梯度下降中,如果损失函数的形状复杂,比如有很多曲率不同的区域,模型在某些方向上的更新会过快,导致在目标最小值附近震荡,或者更新速度过慢,导致收敛时间变长。
2. 动量算法(Momentum)概述
动量算法的关键思想是:不仅仅考虑当前梯度对参数更新的影响,还考虑之前梯度的影响。通过引入“动量”这一概念,算法能够“加速”向最优解的方向前进。
动量算法通过以下公式更新参数:
其中:
-
是第 步时的速度(或动量),表示之前梯度的累积方向。
-
是动量项(通常取值为 0.9 左右),用于控制之前梯度对当前更新的影响,类似于惯性。
-
是学习率,用于控制每一步更新的幅度。
-
是当前梯度。
3. 动量的作用
-
平滑更新方向:通过将前几次更新方向上的“动量”引入当前更新,动量算法能够平滑梯度更新的方向,避免在陡峭区域产生剧烈的梯度更新,从而减少震荡。
-
加速收敛:在平坦的区域或相对低曲率的地方,动量会使得更新步伐逐渐加快,加速收敛到最优解。
-
帮助逃离局部最小值:由于动量带来的累积效果,模型可以更容易地摆脱局部最小值的困扰,帮助找到更好的全局最优解。
代码:
import torch
def momentum_optimization_example():
# Step 1: 初始化一个可训练的张量 w,设置 requires_grad=True 表示 w 的梯度需要计算
w = torch.tensor([1.0], requires_grad=True, dtype=torch.float32) # 权重初始化为 1.0,类型为 float32
# 定义一个简单的目标函数(损失函数): y = (w^2) / 2
y = ((w ** 2) / 2.0).sum()
# Step 2: 使用 SGD 优化器并带有动量(momentum=0.9)
# - 学习率 lr = 0.01
# - 动量系数 beta = 0.9
optimizer = torch.optim.SGD([w], lr=0.01, momentum=0.9)
# 梯度清零(非常重要,防止梯度累加)
optimizer.zero_grad()
# 计算目标函数 y 对 w 的梯度
y.backward() # 反向传播计算梯度
# 使用带动量的 SGD 进行参数更新
optimizer.step() # 使用 optimizer.step() 更新参数 w
# 打印更新后的梯度和 w 值
print('Step 1: w 的梯度: %f, 更新后的 w 值: %f' % (w.grad.numpy(), w.detach().numpy()))
# Step 3: 继续第二轮的梯度更新
# 重新计算损失函数,基于更新后的 w 值
y = ((w ** 2) / 2.0).sum()
# 在第二轮梯度计算前需要将之前的梯度清零
optimizer.zero_grad()
# 计算目标函数对新 w 的梯度
y.backward()
# 再次更新参数 w
optimizer.step()
# 打印第二次更新后的梯度和 w 值
print('Step 2: w 的梯度: %f, 更新后的 w 值: %f' % (w.grad.numpy(), w.detach().numpy()))
# 调用函数进行动量算法优化示例
momentum_optimization_example()
代码讲解:
1. 初始化权重 w
:
w = torch.tensor([1.0], requires_grad=True, dtype=torch.float32)
-
w
是一个可训练的权重,初始化为 1.0。requires_grad=True
表示需要计算其梯度,dtype=torch.float32
指定了数据类型。 -
我们将优化这个
w
,使其能够最小化损失函数 。 -
2.定义损失函数
y
: -
-
-
通过最小化这个损失函数,我们可以观察到参数更新过程中的动量算法。
-
-
3.优化器
optimizer
:optimizer = torch.optim.SGD([w], lr=0.01, momentum=0.9)
-
使用了随机梯度下降(SGD)作为优化器,并设置了动量项
momentum=0.9
。动量可以加速梯度下降过程,尤其是在复杂或有噪声的损失函数中。 -
学习率
lr=0.01
控制每次更新的步长,动量系数momentum=0.9
决定了累积历史梯度的影响。
-
-
4.梯度清零:
optimizer.zero_grad()
-
在每次计算新的梯度之前,我们需要将前一步的梯度清零。否则,PyTorch 会将梯度累加。
-
-
5. 反向传播计算梯度:
y.backward()
-
backward()
函数会根据损失函数计算出参数w
的梯度,即y
对w
的偏导数。
-
-
6. 更新参数
w
:optimizer.step()
-
调用
optimizer.step()
后,优化器会根据计算出的梯度和动量,更新权重w
的值。
-
-
7. 打印梯度和更新后的参数:
print('Step 1: w 的梯度: %f, 更新后的 w 值: %f' % (w.grad.numpy(), w.detach().numpy()))
-
w.grad
表示当前权重w
的梯度值。 -
w.detach()
返回的是更新后的权重值,并且从计算图中分离出来,不会再继续跟踪梯度。
-
动量算法原理简要说明:
动量算法在梯度下降的过程中,通过累积过去的梯度来加速优化。具体原理如下:
-
动量可以理解为模拟物理系统中物体的惯性,它在梯度下降过程中累积了之前的更新方向,使得参数更新更加平滑、稳定。
-
公式:
-
在动量算法中,每次更新时会引入历史梯度的影响:
-
其中 是当前的动量值,是动量系数,
-
通常接近 1(例如 0.9)。动量项 可以看作是梯度的指数加权平均。
-
接着,使用 来更新参数:
-
在代码中,momentum=0.9
就是使用了动量项,帮助梯度在下降的过程中减少震荡,加速收敛。
4. 动量算法的更新过程详解
每次迭代时,动量算法通过计算两个关键公式:
-
速度更新公式:
-
这一步表示计算当前的动量,它是前一次动量 和当前梯度的加权和。这样可以保证之前的更新方向对当前更新有影响。
-
-
参数更新公式:
-
这一步表示用更新后的动量 来调整参数,这样可以使得模型在朝向最优解的方向上有更稳定和加速的更新。
-
5. 动量算法的直观理解
可以把动量算法想象成一个球沿着曲面滚动的过程。传统的梯度下降类似于每一步都按照曲面的斜率(梯度)前进,而动量算法则模拟了一个带有惯性的球滚动的行为:
-
惯性作用:球滚动时不仅受到当前坡度的影响,还会受到前面滚动过程中积累的速度(动量)的影响,这样就能够滚得更远、更快。
-
减少震荡:在复杂的损失函数表面,动量算法可以避免在凹凸不平的表面上来回振荡,而是保持一个平滑的轨迹。
6. 动量算法的优势
-
减少震荡:动量算法能够减少梯度下降过程中在局部最优或鞍点附近的震荡。
-
加速收敛:通过累积梯度方向上的动量,动量算法能够加快模型的收敛速度,特别是在长梯度路径上。
-
对局部最优点的鲁棒性:动量算法能够有效避免模型陷入局部最优点,增加找到全局最优解的机会。
7. 动量算法的常见变种
-
Nesterov 动量(Nesterov Accelerated Gradient, NAG):Nesterov 动量是在标准动量算法的基础上做了改进,NAG 会先看一步未来的梯度,然后再进行更新,以达到更好的预见性。它的更新公式如下:这种方法往往比标准动量更快收敛,尤其是在复杂的损失函数中。
8. 动量算法的应用
动量算法被广泛应用于神经网络训练中,尤其是在深度学习任务中,它可以有效提升训练速度并且提高模型的性能。它可以和其他优化方法(如自适应优化算法 Adam)结合使用,进一步优化模型的学习效果。
总结
-
动量算法 是一种改进的梯度下降方法,通过引入“动量”来减少震荡、加速收敛,并避免陷入局部最优解。
-
通过结合当前和过去的梯度更新,动量算法能够有效改善传统梯度下降的缺陷,尤其在神经网络训练中表现优异。
-
Nesterov 动量 是动量算法的一种改进变种,能够进一步加速收敛,尤其在较为复杂的损失函数表面。