揉扁搓圆transformer架构:NAG优化器算法详解

上一节我们研究了sgd with momentum算法,在其基础上非常自然的就能过度到下一个优化算法NAG,全名叫Nestetrov accelerated gradient.它是对前者非常自然的过度。前面我们看到sgd with momentum算法的核心

思想就是,我们把前面前进的过程积累成一个惯性,如果当前我们落入到一个坑底(局部最优解),那么利用惯性就能冲出当前的坑底,也就是它有助于我们逃脱局部最优点,从而有机会找到全局最优点。

NAG算法在momentum的基础上就做了一点改进。如果说momentun算法是利用惯性无脑向前冲,NAG就是睁开眼往前看,如果前面是一个向上的陡坡,那么它就会及时踩刹车。我们可以感受到,由于我们没有上帝视觉,因此我们

无法得知当前处于的低点是不是全局最低点,momemtun虽然能帮我冲出当前最低点,但如果此刻的最低点已经是全局最低,那么继续往前冲不就错过了全局最优点吗。所以NAG就能避免这一点,如果它发现前面向上的陡坡足够高,

它就能及时降低速度,从而留在当前低点。这时你可能就会问了,如果全局最低点在当前低点的后面,那么NAG冲不出当前低点,不就错过了全局最低吗,确实是这样,在解决这个问题之前我们先看看NAG算法的具体逻辑。

在前面momemtun算法中,我们会积累前面的惯性,然后在当前点我们会利用以前的惯性结合现在的速度前进。如果现在是下坡,那么惯性会帮助我们加快速度往下冲,如果是上坡,惯性会帮助我们向上冲,有可能让我们冲出

当前最低点,于是有机会逃出当前局部最低点,转换为代码如下:

py 复制代码
g = grad_f(x)              # 1. 在当前位置计算梯度
v = beta * v + lr * g      # 2. 累积速度
x = x - v                  # 3. 用速度更新

NAG算法就在上面的基础上做了一点点改进,它的思想是在当前点决定是否利用惯性加速前进前,先预先看一步,如果看到前面多走一步就遇到往上的陡坡,那就在当前点适当减速,具体做法如下:

py 复制代码
g = grad_f(x - beta * v)   # 1. 在"预判位置"计算梯度 ← 唯一改动!
v = beta * v + lr * g      # 2. 累积速度
x = x - v                  # 3. 用速度更新

在上面代码中,x-betav表示"向前多走一步",grad_f(x-beta v)用于计算多走一步后所在位置的梯度,如果计算的梯度是向下的(g>0),说明前面遇到一个向上的陡坡,如果向前一步是个向上的陡坡(g<0),那么它的梯度g就是一个正值,

说明"向右走"就是上坡方向,注意到v对应的是向下方向产生的能力积累,也就是惯性。如果前一步的梯度g<0,说明前面是上坡,那么lrg就是负数,由此算出的v=beta v+lr*g的数值就会减少,也就是继续向前冲的惯性降低,那么

x=x-v就使得当前点向前冲的距离变小,这就产生了前面是"墙壁",我们现在就有刹车的作用。

如果g>0,说明向前一步所在的位置是继续向下的坡度,于是g<0,v=betav+lr g 就会使得向下的惯性得到一个"能量补充",也就是betav使得向下的惯性衰减,但是lr g又补充了向下的惯性,于是v的数值衰减的小,如果g足够大,v甚至会增加,

那么x=x-v就表示我们向下冲的距离增大,也就是如果前一步还处于下坡位置,那么我们就加大向下冲的力量。

回到前面的问题,NAG的算法思想是,如果前面是上坡,那么我们要在当前点降低向前冲的力量,使得在当前点往前冲的距离变小。问题是如果全局最优点需要冲出当前最低点才能达到,那么NAG算法不就阻止我们前往最优点吗。事实上NAG算法

的作用并不是前往全局最优点,而是在抵达最优点时,减少在最优点的左右震荡。是想momemtun算法是不顾前面情况一味蒙着眼睛向前冲,那么当我们位于最优点的左边时,我们就会一下子冲到最优点的右边。位于最优点右边,

那么就会一下子冲到最优点左边,于是我们一直在最优点的左右两边晃动,一直无法停留在最低点。但是NAG算法由于预判到前面如果是上坡,那么我就在当前点立刻降低前进的速度,于是就更加容易防止一下子冲过最低点,然后在

最低点停留下来,我们看下面代码示例就能更容易明白:

py 复制代码
import numpy as np
import matplotlib.pyplot as plt

# ---------- 简单二次函数 ----------
def f(x):
    return 0.5 * x**2

def grad_f(x):
    return x

# ---------- 三种优化器 ----------
def sgd(start, lr=0.9, epochs=20):
    x = start
    path = [x]
    for _ in range(epochs):
        x = x - lr * grad_f(x)
        path.append(x)
    return np.array(path)

def momentum(start, lr=0.9, beta=0.9, epochs=20):
    x = start
    v = 0.0
    path = [x]
    for _ in range(epochs):
        g = grad_f(x)
        v = beta * v + lr * g
        x = x - v
        path.append(x)
    return np.array(path)

def nag(start, lr=0.9, beta=0.9, epochs=20):
    x = start
    v = 0.0
    path = [x]
    for _ in range(epochs):
        pos_ahead = x - beta * v      # 预判位置
        g = grad_f(pos_ahead)          # 在预判位置算梯度
        v = beta * v + lr * g
        x = x - v
        path.append(x)
    return np.array(path)

# ---------- 运行 ----------
start = 5.0
epochs = 20

path_sgd = sgd(start, lr=0.9, epochs=epochs)
path_mom = momentum(start, lr=0.9, beta=0.9, epochs=epochs)
path_nag = nag(start, lr=0.9, beta=0.9, epochs=epochs)

# 打印每步的 x 值
print("Step |    SGD    |  Momentum |    NAG")
print("-" * 45)
for i in range(epochs + 1):
    print(f"{i:4d} | {path_sgd[i]:+9.4f} | {path_mom[i]:+9.4f} | {path_nag[i]:+9.4f}")

print("\n最终位置:")
print(f"SGD:       x={path_sgd[-1]:+.6f}, f={f(path_sgd[-1]):.6f}")
print(f"Momentum:  x={path_mom[-1]:+.6f}, f={f(path_mom[-1]):.6f}")
print(f"NAG:       x={path_nag[-1]:+.6f}, f={f(path_nag[-1]):.6f}")
print("全局最小: x=0, f=0")

# ---------- 绘制损失曲线 ----------
plt.figure(figsize=(12, 5))

plt.subplot(1, 2, 1)
plt.plot([f(x) for x in path_sgd], 'r.-', label='SGD')
plt.plot([f(x) for x in path_mom], 'g.-', label='Momentum')
plt.plot([f(x) for x in path_nag], 'b.-', label='NAG')
plt.xlabel('Iteration')
plt.ylabel('Loss f(x)')
plt.title('损失下降曲线')
plt.legend()
plt.grid(True)

plt.subplot(1, 2, 2)
plt.plot(path_sgd, 'r.-', label='SGD')
plt.plot(path_mom, 'g.-', label='Momentum')
plt.plot(path_nag, 'b.-', label='NAG')
plt.axhline(0, color='k', linestyle='--', alpha=0.3)
plt.xlabel('Iteration')
plt.ylabel('x 值')
plt.title('参数轨迹')
plt.legend()
plt.grid(True)

plt.tight_layout()
plt.show()

上面代码运行后结果如下:

py 复制代码
Step |    SGD    |  Momentum |    NAG
---------------------------------------------
   0 |   +5.0000 |   +5.0000 |   +5.0000
   1 |   +0.5000 |   +0.5000 |   +0.5000
   2 |   +0.0500 |   -4.0000 |   -0.3550
   3 |   +0.0050 |   -4.4500 |   -0.1125
   4 |   +0.0005 |   -0.8500 |   +0.0106
   5 |   +0.0000 |   +3.1550 |   +0.0121
   6 |   +0.0000 |   +3.9200 |   +0.0014
   7 |   +0.0000 |   +1.0805 |   -0.0008
   8 |   +0.0000 |   -2.4475 |   -0.0003
   9 |   +0.0000 |   -3.4200 |   +0.0000
  10 |   +0.0000 |   -1.2172 |   +0.0000
  11 |   +0.0000 |   +1.8608 |   +0.0000
  12 |   +0.0000 |   +2.9562 |   -0.0000
  13 |   +0.0000 |   +1.2816 |   -0.0000
  14 |   +0.0000 |   -1.3791 |   +0.0000
  15 |   +0.0000 |   -2.5325 |   +0.0000
  16 |   +0.0000 |   -1.2913 |   +0.0000
  17 |   +0.0000 |   +0.9879 |   -0.0000
  18 |   +0.0000 |   +2.1501 |   -0.0000
  19 |   +0.0000 |   +1.2610 |   +0.0000
  20 |   +0.0000 |   -0.6741 |   +0.0000

最终位置:
SGD:       x=+0.000000, f=0.000000
Momentum:  x=-0.674106, f=0.227210
NAG:       x=+0.000000, f=0.000000
全局最小: x=0, f=0

绘制的图像如下:

从运行结果看sgd的效果更好,基于sgd改进的两个算法momentu和nag反而要差一些,原因在于不同算法在不同目标函数面前产生的效果不一样,有些在特定函数效果好,在另一些函数,其他算法会更好。但从图像看NAG算法

对应的紫色线条其抖动是最小的,也就是它走的更平稳,绿色对应的momemtun算法抖动非常厉害,抖动厉害的算法有点是容易冲出当前最低点,缺点就是在最低点处容易左右震荡而难以收敛。

相关推荐
小雉系统7 小时前
BcoreOS安全通信架构:FsSession 抗算法后门安全设计
算法·安全·架构
用户0807759078511 小时前
从输入到输出:大模型推理全流程复盘(含纠错清单)
深度学习
唐墨12311 小时前
linux kernel 比较数据大小的宏定义
linux·算法
jay神11 小时前
基于 YOLOv8 的手势识别系统(Web 版)
前端·人工智能·python·深度学习·yolo·计算机视觉·计算机毕业设计
拂拉氏12 小时前
【知识讲解-题目讲解】 统计满足K约束的子字符串数量2 (滑动窗口+前缀和+二分查找+分析)
算法·前缀和·二分查找·滑动窗口
武子康15 小时前
H3-World 写了 CPU offload,为什么还不能保证塞进 48GB?
大数据·人工智能·深度学习
通信小呆呆16 小时前
电子战抗干扰技术:当“不被干扰”成为一种系统
算法·通信·mimo·isac
笑容掩饰我的失落16 小时前
区块链从小白到精通(八):从零实现一个标准 ERC-20 代币(完整可运行代码)
算法·区块链·php·智能合约·哈希算法
Severus_black17 小时前
【C++初阶】Stack、Queue算法题大练兵!
c语言·c++·算法·stl