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




