Source code for snn.optimizers.radam
import numpy as np
from .base import Optimizer
[docs]
class RAdam(Optimizer):
"""
RAdam — Rectified Adam.
Fixes the variance issue in Adam's early training phase by computing
the effective sample size of the second moment estimate. When the
approximation is not reliable (small t), it falls back to an SGD-with-
momentum step; otherwise it applies the full variance-corrected Adam
update. This removes the need for a warmup schedule.
Reference: Liu et al. (2019) "On the Variance of the Adaptive Learning
Rate and Beyond". https://arxiv.org/abs/1908.03265
Parameters
----------
learning_rate : float
Step size (default 1e-3).
beta_1 : float
First-moment decay (default 0.9).
beta_2 : float
Second-moment decay (default 0.999).
epsilon : float
Numerical stability constant (default 1e-8).
weight_decay : float
L2 regularisation coefficient (default 0.0).
"""
def __init__(self, learning_rate=1e-3, beta_1=0.9, beta_2=0.999,
epsilon=1e-8, weight_decay=0.0):
super().__init__(learning_rate)
self.beta_1 = beta_1
self.beta_2 = beta_2
self.epsilon = epsilon
self.weight_decay = weight_decay
self._m = {}
self._v = {}
# Maximum ρ (SMA length upper bound)
self._rho_max = 2.0 / (1.0 - beta_2) - 1.0
[docs]
def apply_gradients(self, params, grads):
self._iterations += 1
t = self._iterations
updates = {}
b1t = self.beta_1 ** t
b2t = self.beta_2 ** t
# SMA approximation at step t
rho_t = self._rho_max - 2.0 * t * b2t / (1.0 - b2t)
for key, grad in grads.items():
param = params[key]
g = grad + self.weight_decay * param if self.weight_decay else grad
m = self._m.get(key, np.zeros_like(param))
v = self._v.get(key, np.zeros_like(param))
m_new = self.beta_1 * m + (1.0 - self.beta_1) * g
v_new = self.beta_2 * v + (1.0 - self.beta_2) * g ** 2
self._m[key] = m_new
self._v[key] = v_new
m_hat = m_new / (1.0 - b1t)
if rho_t > 4:
# Variance is tractable — use full Adam + rectification
r = np.sqrt(
(rho_t - 4) * (rho_t - 2) * self._rho_max
/ ((self._rho_max - 4) * (self._rho_max - 2) * rho_t)
)
v_hat = np.sqrt(v_new / (1.0 - b2t))
updates[key] = param - self.learning_rate * r * m_hat / (v_hat + self.epsilon)
else:
# Variance is intractable — SGD + momentum step
updates[key] = param - self.learning_rate * m_hat
return updates
[docs]
def get_config(self):
cfg = super().get_config()
cfg.update({
"beta_1": self.beta_1,
"beta_2": self.beta_2,
"epsilon": self.epsilon,
"weight_decay": self.weight_decay,
})
return cfg