use crate::error::Error;
use crate::neural_network::optimizers::kernels;
use crate::neural_network::optimizers::validation::{
validate_clip_norm, validate_learning_rate, validate_non_negative_finite,
};
use crate::neural_network::traits::{Layer, Optimizer};
#[derive(Debug)]
pub struct SGD {
learning_rate: f32,
momentum: f32,
nesterov: bool,
weight_decay: f32,
clip_norm: Option<f32>,
velocities: Vec<Vec<f32>>,
cursor: usize,
}
impl SGD {
pub fn new(
learning_rate: f32,
momentum: f32,
nesterov: bool,
weight_decay: f32,
) -> Result<Self, Error> {
validate_learning_rate(learning_rate)?;
validate_non_negative_finite(momentum, "momentum")?;
validate_non_negative_finite(weight_decay, "weight_decay")?;
Ok(Self {
learning_rate,
momentum,
nesterov,
weight_decay,
clip_norm: None,
velocities: Vec::new(),
cursor: 0,
})
}
pub 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)
}
}
impl Optimizer for SGD {
fn step(&mut self) {
self.cursor = 0;
}
fn clip_norm(&self) -> Option<f32> {
self.clip_norm
}
fn set_learning_rate(&mut self, learning_rate: f32) {
self.learning_rate = learning_rate;
}
fn update(&mut self, layer: &mut dyn Layer, grad_scale: f32) {
for pg in layer.parameters() {
let grad = kernels::scaled_grad(pg.grad, grad_scale);
if pg.decays {
kernels::apply_weight_decay(pg.value, self.learning_rate, self.weight_decay);
}
if self.momentum == 0.0 {
kernels::sgd_step(pg.value, &grad, self.learning_rate);
} else {
if self.cursor >= self.velocities.len() {
self.velocities.push(vec![0.0; pg.value.len()]);
} else if self.velocities[self.cursor].len() != pg.value.len() {
self.velocities[self.cursor] = vec![0.0; pg.value.len()];
}
kernels::sgd_momentum_step(
pg.value,
&grad,
&mut self.velocities[self.cursor],
self.learning_rate,
self.momentum,
self.nesterov,
);
self.cursor += 1;
}
}
}
}