use crate::error::Error;
use crate::neural_network::optimizers::kernels;
use crate::neural_network::optimizers::validation::{
validate_clip_norm, validate_decay_rate, validate_epsilon, validate_learning_rate,
validate_non_negative_finite,
};
use crate::neural_network::traits::Layer;
#[derive(Debug, Clone, Default)]
struct AdamParamState {
m: Vec<f32>,
v: Vec<f32>,
}
#[derive(Debug)]
pub(super) struct AdamCore {
learning_rate: f32,
beta1: f32,
beta2: f32,
epsilon: f32,
t: u64,
states: Vec<AdamParamState>,
cursor: usize,
clip_norm: Option<f32>,
weight_decay: f32,
decoupled: bool,
}
impl AdamCore {
pub(super) fn new(
learning_rate: f32,
beta1: f32,
beta2: f32,
epsilon: f32,
weight_decay: f32,
decoupled: bool,
) -> Result<Self, Error> {
validate_learning_rate(learning_rate)?;
validate_decay_rate(beta1, "beta1")?;
validate_decay_rate(beta2, "beta2")?;
validate_epsilon(epsilon)?;
validate_non_negative_finite(weight_decay, "weight_decay")?;
Ok(Self {
learning_rate,
beta1,
beta2,
epsilon,
t: 0,
states: Vec::new(),
cursor: 0,
clip_norm: None,
weight_decay,
decoupled,
})
}
pub(super) fn with_clip_norm(mut self, clip_norm: f32) -> Result<Self, Error> {
validate_clip_norm(Some(clip_norm))?;
self.clip_norm = Some(clip_norm);
Ok(self)
}
pub(super) fn clip_norm(&self) -> Option<f32> {
self.clip_norm
}
pub(super) fn set_learning_rate(&mut self, learning_rate: f32) {
self.learning_rate = learning_rate;
}
pub(super) fn step(&mut self) {
self.t = self.t.saturating_add(1).min(i32::MAX as u64);
self.cursor = 0;
}
pub(super) fn update(&mut self, layer: &mut dyn Layer, grad_scale: f32) {
for pg in layer.parameters() {
if self.cursor >= self.states.len() {
self.states.push(AdamParamState {
m: vec![0.0; pg.value.len()],
v: vec![0.0; pg.value.len()],
});
} else if self.states[self.cursor].m.len() != pg.value.len() {
self.states[self.cursor] = AdamParamState {
m: vec![0.0; pg.value.len()],
v: vec![0.0; pg.value.len()],
};
}
let state = &mut self.states[self.cursor];
let grad = kernels::scaled_grad(pg.grad, grad_scale);
if pg.decays && self.weight_decay != 0.0 {
if self.decoupled {
kernels::apply_weight_decay(pg.value, self.learning_rate, self.weight_decay);
kernels::adam_step(
pg.value,
&grad,
&mut state.m,
&mut state.v,
self.learning_rate,
self.beta1,
self.beta2,
self.epsilon,
self.t,
);
} else {
let l2 = kernels::l2_regularized_grad(&grad, pg.value, self.weight_decay);
kernels::adam_step(
pg.value,
&l2,
&mut state.m,
&mut state.v,
self.learning_rate,
self.beta1,
self.beta2,
self.epsilon,
self.t,
);
}
} else {
kernels::adam_step(
pg.value,
&grad,
&mut state.m,
&mut state.v,
self.learning_rate,
self.beta1,
self.beta2,
self.epsilon,
self.t,
);
}
self.cursor += 1;
}
}
}