use ghostflow_core::Tensor;
use crate::optimizer::Optimizer;
pub struct Adam {
params: Vec<Tensor>,
lr: f32,
betas: (f32, f32),
eps: f32,
weight_decay: f32,
m: Vec<Vec<f32>>, v: Vec<Vec<f32>>, t: usize, }
impl Adam {
pub fn new(params: Vec<Tensor>, lr: f32) -> Self {
let m = params.iter().map(|p| vec![0.0f32; p.numel()]).collect();
let v = params.iter().map(|p| vec![0.0f32; p.numel()]).collect();
Adam {
params,
lr,
betas: (0.9, 0.999),
eps: 1e-8,
weight_decay: 0.0,
m,
v,
t: 0,
}
}
pub fn betas(mut self, beta1: f32, beta2: f32) -> Self {
self.betas = (beta1, beta2);
self
}
pub fn eps(mut self, eps: f32) -> Self {
self.eps = eps;
self
}
pub fn weight_decay(mut self, weight_decay: f32) -> Self {
self.weight_decay = weight_decay;
self
}
}
impl Optimizer for Adam {
fn step(&mut self) {
self.t += 1;
let (beta1, beta2) = self.betas;
let bias_correction1 = 1.0 - beta1.powi(self.t as i32);
let bias_correction2 = 1.0 - beta2.powi(self.t as i32);
for (i, param) in self.params.iter_mut().enumerate() {
if let Some(grad) = param.grad() {
let mut grad_data = grad.data_f32();
let param_data = param.data_f32();
if self.weight_decay != 0.0 {
for (g, &p) in grad_data.iter_mut().zip(param_data.iter()) {
*g += self.weight_decay * p;
}
}
for (j, &g) in grad_data.iter().enumerate() {
self.m[i][j] = beta1 * self.m[i][j] + (1.0 - beta1) * g;
}
for (j, &g) in grad_data.iter().enumerate() {
self.v[i][j] = beta2 * self.v[i][j] + (1.0 - beta2) * g * g;
}
let new_data: Vec<f32> = param_data.iter()
.enumerate()
.map(|(j, &p)| {
let m_hat = self.m[i][j] / bias_correction1;
let v_hat = self.v[i][j] / bias_correction2;
p - self.lr * m_hat / (v_hat.sqrt() + self.eps)
})
.collect();
*param = Tensor::from_slice(&new_data, param.dims()).unwrap();
}
}
}
fn zero_grad(&mut self) {
for param in &mut self.params {
param.zero_grad();
}
}
fn get_lr(&self) -> f32 {
self.lr
}
fn set_lr(&mut self, lr: f32) {
self.lr = lr;
}
fn parameters(&self) -> &[Tensor] {
&self.params
}
}
pub struct AdamW {
params: Vec<Tensor>,
lr: f32,
betas: (f32, f32),
eps: f32,
weight_decay: f32,
m: Vec<Vec<f32>>,
v: Vec<Vec<f32>>,
t: usize,
}
impl AdamW {
pub fn new(params: Vec<Tensor>, lr: f32) -> Self {
let m = params.iter().map(|p| vec![0.0f32; p.numel()]).collect();
let v = params.iter().map(|p| vec![0.0f32; p.numel()]).collect();
AdamW {
params,
lr,
betas: (0.9, 0.999),
eps: 1e-8,
weight_decay: 0.01, m,
v,
t: 0,
}
}
pub fn betas(mut self, beta1: f32, beta2: f32) -> Self {
self.betas = (beta1, beta2);
self
}
pub fn eps(mut self, eps: f32) -> Self {
self.eps = eps;
self
}
pub fn weight_decay(mut self, weight_decay: f32) -> Self {
self.weight_decay = weight_decay;
self
}
}
impl Optimizer for AdamW {
fn step(&mut self) {
self.t += 1;
let (beta1, beta2) = self.betas;
let bias_correction1 = 1.0 - beta1.powi(self.t as i32);
let bias_correction2 = 1.0 - beta2.powi(self.t as i32);
for (i, param) in self.params.iter_mut().enumerate() {
if let Some(grad) = param.grad() {
let grad_data = grad.data_f32();
let param_data = param.data_f32();
for (j, &g) in grad_data.iter().enumerate() {
self.m[i][j] = beta1 * self.m[i][j] + (1.0 - beta1) * g;
self.v[i][j] = beta2 * self.v[i][j] + (1.0 - beta2) * g * g;
}
let new_data: Vec<f32> = param_data.iter()
.enumerate()
.map(|(j, &p)| {
let m_hat = self.m[i][j] / bias_correction1;
let v_hat = self.v[i][j] / bias_correction2;
let p_decayed = p * (1.0 - self.lr * self.weight_decay);
p_decayed - self.lr * m_hat / (v_hat.sqrt() + self.eps)
})
.collect();
*param = Tensor::from_slice(&new_data, param.dims()).unwrap();
}
}
}
fn zero_grad(&mut self) {
for param in &mut self.params {
param.zero_grad();
}
}
fn get_lr(&self) -> f32 {
self.lr
}
fn set_lr(&mut self, lr: f32) {
self.lr = lr;
}
fn parameters(&self) -> &[Tensor] {
&self.params
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_adam_step() {
let mut param = Tensor::ones(&[3]);
param.set_requires_grad(true);
param.set_grad(Tensor::full(&[3], 0.1f32));
let mut adam = Adam::new(vec![param], 0.001);
for _ in 0..10 {
adam.step();
}
let updated = &adam.params[0];
assert!(updated.data_f32()[0] < 1.0);
}
}