Adam Optimizer Step
Implement a single step of the Adam optimizer, widely used in deep learning.
Adam (Adaptive Moment Estimation) combines momentum and RMSprop, adapting learning rates per-parameter:
Algorithm (single step at time t):
- Compute gradient gtβ at current parameters
- Update biased first moment: mtβ=Ξ²1βmtβ1β+(1βΞ²1β)gtβ
- Update biased second moment: vtβ=Ξ²2βvtβ1β+(1βΞ²2β)gt2β
- Bias correction: m^tβ=1βΞ²1tβmtββ, v^tβ=1βΞ²2tβvtββ
- Update parameters: ΞΈtβ=ΞΈtβ1ββΞ±v^tββ+Ο΅m^tββ
Typical hyperparameters: Ξ±=0.001, Ξ²1β=0.9, Ξ²2β=0.999, Ο΅=10β8
Example:
params = [1.0, 2.0] grads = [0.1, 0.2] m = [0.0, 0.0] v = [0.0, 0.0] t = 1 lr = 0.001 beta1 = 0.9 beta2 = 0.999
{'params': [0.999, 1.999], 'm': [0.01, 0.02], 'v': [0.00001, 0.00004]}Step 1: Update first moment (m) m1β=0.9Γ0+0.1Γ[0.1,0.2]=[0.01,0.02]
Step 2: Update second moment (v) v1β=0.999Γ0+0.001Γ[0.01,0.04]=[0.00001,0.00004]
Step 3: Bias correction m^1β=1β0.91[0.01,0.02]β=[0.1,0.2] v^1β=1β0.9991[0.00001,0.00004]β=[0.01,0.04]
Step 4: Parameter update ΞΈ1β=[1,2]β0.001Γ[0.01,0.04]β+Ο΅[0.1,0.2]β ΞΈ1β=[1,2]β0.001Γ[0.1,0.2][0.1,0.2]β ΞΈ1β=[1,2]β0.001Γ[1,1]=[0.999,1.999]
Constraints:
- params: Current parameter values (list)
- grads: Gradients at current params (list)
- m, v: First and second moment estimates (lists)
- t: Current timestep (int >= 1)
- Return: Dict with new 'params', 'm', 'v'
- Round to 6 decimal places
Adam is a first-order stochastic optimization algorithm that improves plain gradient descent by keeping exponentially decaying moving averages of both the gradient (a kind of momentum) and the squared gradient (a kind of adaptive per-parameter learning rate, like RMSprop). Conceptually, at each step it uses past gradients to estimate the mean and variance of the gradient for each parameter, then rescales the update so that noisy / high-variance directions get smaller effective steps and consistent directions get larger ones.
Because these moving averages start at zero, their early values are biased toward zero; Adam corrects this with bias correction factors depending on the timestep t. After correction, it updates parameters by subtracting a scaled, normalized first moment: the estimated mean gradient divided by the square root of the estimated second moment (plus a small Ο΅ to avoid division by zero). This yields stable, per-parameter step sizes that work well in deep learning.
1. Background Knowledge
Key concepts you should understand:
- Gradient descent: For parameters ΞΈ, learning rate Ξ±, and gradient gtβ=\nabla_\thetaL(\thetatβ1β), vanilla SGD does
- Momentum: Keeps an exponential moving average of gradients:
then updates ΞΈ using mtβ instead of gtβ. This smooths noisy gradients and accelerates in consistent directions.
- Adaptive learning rates (RMSprop-style): Keeps an exponential moving average of squared gradients:
and scales the step by 1/(\sqrt{v_t}+\epsilon) so each parameter gets its own effective step size.
Adam combines both: it keeps first moment mtβ and second moment vtβ, applies bias correction, then does a scaled update.
2. Algorithm / Approach Pattern
For this coding task, you are not training a network; you are asked to implement one optimizer step given all necessary inputs.
The general pattern:
- Inputs:
- Current parameters ΞΈtβ1β
- Previous first moment mtβ1β
- Previous second moment vtβ1β
- Current gradient gtβ
- Hyperparameters Ξ±,\beta1β,\beta2β,Ο΅
- Current timestep t (1-based)
- Compute:
- New first moment mtβ
- New second moment vtβ
- Bias-corrected moments m^tβ,\hat{v}tβ
- New parameters ΞΈtβ
- Return updated ΞΈtβ,mtβ,vtβ.
All operations are element-wise over the parameter tensors.
3. Step-by-Step Strategy
Assuming everything is stored as arrays/tensors of the same shape:
- Update first moment (biased):
- Update second moment (biased):
- Square the gradient element-wise: gt2β
- Then:
- Compute bias-correction denominators:
- You will be given t (or you increment it); compute:
- Bias-correct the moments:
- Compute the normalized step:
- Denominator: \sqrt{\hat{v}_t} + \epsilon (element-wise sqrt).
- Fraction: m^tβ/(\sqrt{v^_t}+\epsilon).
- Update parameters:
- Return:
- New parameters ΞΈtβ
- New moments mtβ,vtβ
- (Optionally timestep t+1, depending on interface).
Skeleton-style example in pseudocode:
def adam_step(theta, grad, m, v, t, alpha, beta1, beta2, eps):
m = beta1 * m + (1 - beta1) * grad
v = beta2 * v + (1 - beta2) * (grad ** 2)
m_hat = m / (1 - (beta1 ** t))
v_hat = v / (1 - (beta2 ** t))
theta = theta - alpha * m_hat / (v_hat**0.5 + eps)
return theta, m, v
4. Common Pitfalls
-
Forgetting bias correction: Omitting m^tβ and v^tβ (using mtβ,vtβ directly) makes early steps too small, diverging from the true Adam algorithm.
-
Wrong timestep t:
-
Make sure t starts at 1 for the first update.
-
Do not recompute Ξ²1tβ,\beta2tβ incorrectly (e.g., using tβ1).
-
Element-wise vs scalar ops:
-
gt2β, \sqrt{\hat{v}_t}, division, and addition of Ο΅ are per-parameter operations.
-
Do not accidentally take a global norm or sum.
-
Numeric stability:
-
Ensure Ο΅ is added inside the denominator: sqrt(v_hat) + eps, not sqrt(v_hat + eps) (most Adam definitions use the former).
-
Use floating-point types; integer division will break things.
-
Shape mismatch:
-
theta, grad, m, v must all have the same shape; verify broadcasting doesnβt hide a bug.
5. Time & Space Complexity
Let n be the number of parameters:
-
Time complexity:
-
Each step does a constant number of element-wise operations over n elements: updates for m, v, bias correction, sqrt, division, and parameter update.
-
Overall: O(n) per step.
-
Space complexity:
-
You store m, v, ΞΈ, and usually gtβ.
-
The optimizer-specific extra memory is from m and v: O(n) auxiliary space.