You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

Adam优化器直观理解与代码实现正确性的技术咨询

问题分析与解答

首先,先明确你遇到的核心问题:第一次实现的代码之所以得到反常结果,是因为你错误地修改了Adam算法中一阶矩(m)和二阶矩(v)的更新逻辑

原始Adam算法的核心步骤

Adam的偏差修正,是为了解决初始时m和v从0开始、被低估的问题,但要注意:修正操作是针对当前迭代的m和v计算临时的修正值(m_hat、v_hat),而不是修改m和v本身。原始伪代码的关键步骤是:

t = t + 1
m_t = beta1 * m_{t-1} + (1 - beta1) * g_t
v_t = beta2 * v_{t-1} + (1 - beta2) * g_t^2
m_hat_t = m_t / (1 - beta1^t)  # 仅用于本次步长计算,不更新m_t
v_hat_t = v_t / (1 - beta2^t)  # 仅用于本次步长计算,不更新v_t
theta_t = theta_{t-1} - alpha * m_hat_t / (sqrt(v_hat_t) + epsilon)

你第一次代码的错误

你在每次迭代中,把修正后的m_hat直接赋值回了m:

m = beta_m*m + (1 - beta_m)*g
m = m/(1-beta_m**i)  # 错误:用m_hat覆盖了原始m

这导致下一次迭代的m更新,是基于修正后的m_hat而非原始的一阶矩m,完全打乱了动量累积的逻辑。当梯度恒定为10时,这种错误的迭代会让m和v的数值逐渐偏离真实的累积值,最终导致mv趋近于0,和预期不符。

你修改后的代码是否正确?

很遗憾,修改后的代码也不符合原始Adam的规范,只是碰巧在梯度恒定的场景下得到了符合直觉的结果:

  • 你只在第一次迭代做修正,之后直接用修正后的m和v进行累积,相当于把m和v当成了m_hat和v_hat来使用。
  • 在梯度恒定的场景下,当迭代次数足够多,beta^t趋近于0,此时修正的影响可以忽略,所以你的错误实现碰巧收敛到了接近预期的数值,但这是特殊场景下的巧合,不是正确的Adam实现。

正确的实现方式

你需要保留原始的m和v,每次迭代计算临时的m_hat和v_hat来计算步长系数,而不修改m和v本身。修正后的代码应该是这样:

from matplotlib import pyplot as plt
import numpy as np

num = 100
x = np.arange(num).tolist()
g_list = [10 for _ in range(num)]  # 梯度恒定为正

m = 0
v = 0
beta_m = 0.9
beta_v = 0.999
epsilon = 0.001
mv_list = []

for i in range(1, num+1):
    g = g_list[i-1]
    # 更新原始一阶矩和二阶矩
    m = beta_m * m + (1 - beta_m) * g
    v = beta_v * v + (1 - beta_v) * (g ** 2)
    # 计算修正后的矩(仅用于本次步长计算)
    m_hat = m / (1 - beta_m ** i)
    v_hat = v / (1 - beta_v ** i)
    # 计算步长系数
    mv = m_hat / (np.sqrt(v_hat) + epsilon)
    
    mv_list.append(mv)

plt.plot(x, g_list, label='Original Gradient')
plt.plot(x, mv_list, label='Step Coefficient (mv)')
plt.legend()
plt.show()

运行这段代码后,你会看到mv逐渐趋近于10/(sqrt(100)+0.001)≈0.9999,完全符合你的直觉。

总结

  • 第一次代码的错误在于用修正后的矩覆盖了原始累积矩,破坏了Adam的动量逻辑;
  • 修改后的代码是不符合规范的,只是特殊场景下的巧合结果;
  • 正确的实现需要区分原始累积矩(m、v)和修正后的矩(m_hat、v_hat),前者用于迭代累积,后者仅用于当前步长计算。

内容的提问来源于stack exchange,提问作者Wade Wang

火山引擎 最新活动