use crate::misc::Decay;
use crate::{Dtype, Float, Gradients, Unit};
use num_traits::FromPrimitive;
use serde::{Deserialize, Serialize};
#[derive(Clone, Serialize, Deserialize, Debug)]
pub struct TrainInfo {
pub lr: f32,
pub l1_reg: f32,
pub l2_reg: f32,
pub grad_clip: Option<f32>,
pub step: usize,
}
pub trait GradAdjuster<G: Gradients> {
fn adjust(&mut self, gradient_updates: G, loss: f32) -> G;
}
pub trait GradApplyer {
fn apply<G: Gradients>(
&mut self,
gradient_updates: G,
weights: &mut G,
) -> Result<(), crate::Error>;
fn advance_step(&mut self);
}
#[derive(Clone, Serialize, Deserialize, Debug)]
pub struct TrainParams {
pub lr: Decay,
pub l1_reg: Option<Decay>,
pub l2_reg: Option<Decay>,
pub soft_start_epochs: Option<usize>,
pub grad_clip: Option<f32>,
step: usize,
}
impl Default for TrainParams {
fn default() -> Self {
Self {
lr: Decay::None(1.0e-6),
l1_reg: None,
l2_reg: None,
soft_start_epochs: None,
grad_clip: None,
step: 0,
}
}
}
impl Into<TrainInfo> for &TrainParams {
fn into(self) -> TrainInfo {
TrainInfo {
lr: self.current_lr(),
l1_reg: self
.l1_reg
.as_ref()
.map(|d| d.at_timestep(self.step))
.unwrap_or(0.0),
l2_reg: self
.l2_reg
.as_ref()
.map(|d| d.at_timestep(self.step))
.unwrap_or(0.0),
grad_clip: self.grad_clip,
step: self.step,
}
}
}
impl TrainParams {
pub fn with_lr(lr: f32) -> Self {
Self {
lr: Decay::None(lr),
..Default::default()
}
}
pub fn and_l1(mut self, l1: f32) -> Self {
self.l1_reg = Some(Decay::None(l1));
self
}
pub fn and_l2(mut self, l2: f32) -> Self {
self.l2_reg = Some(Decay::None(l2));
self
}
pub fn and_lr_decay(mut self, lr_decay: f32) -> Self {
self.lr = Decay::Linear {
start: self.lr.start_value(),
decay: lr_decay,
};
self
}
pub fn and_lr_cosine_decay(mut self, final_lr: f32, over_steps: usize) -> Self {
self.lr = Decay::Cosine {
start: self.lr.start_value(),
end: final_lr,
num_steps: over_steps,
};
self
}
pub fn and_soft_start(mut self, epochs: usize) -> Self {
self.soft_start_epochs = Some(epochs);
self
}
pub fn and_gradient_clip(mut self, clip: f32) -> Self {
self.grad_clip = Some(clip);
self
}
pub fn current_lr(&self) -> f32 {
let lr = self.lr.at_timestep(self.step);
match self.soft_start_epochs {
Some(soft_start_epochs) => {
if self.step < soft_start_epochs {
lr / soft_start_epochs as f32 * (1 + self.step) as f32
} else {
lr
}
}
None => lr,
}
}
fn clip_grad<G: Dtype>(&self, grad: G) -> G {
if let Some(clip) = self.grad_clip {
let clip = G::from_f32(clip).unwrap();
let neg_clip = G::default() - clip;
if grad > clip {
clip
} else if grad < neg_clip {
neg_clip
} else {
grad
}
} else {
grad
}
}
pub fn train_params(&self) -> &Self {
self
}
}
impl<G: Gradients> GradAdjuster<G> for TrainParams {
fn adjust(&mut self, mut gradient_updates: G, loss: f32) -> G {
let l = G::Concrete::from_f32(-loss * self.current_lr()).unwrap();
gradient_updates
.grad_iter_mut()
.for_each(|g| *g = self.clip_grad(*g) * l);
gradient_updates
}
}
impl GradApplyer for TrainParams {
fn apply<G: Gradients>(
&mut self,
gradient_updates: G,
weights: &mut G,
) -> Result<(), crate::Error> {
let l1 = self.l1_reg.as_ref().map(|d| d.at_timestep(self.step));
let l2 = self.l2_reg.as_ref().map(|d| d.at_timestep(self.step));
weights
.grad_iter_mut_with_class()
.zip(gradient_updates.into_grads())
.for_each(|((w, c), u)| {
let reg_penalty = if c.should_regularize() {
G::Concrete::from_f32(if let Some(l1) = l1 {
if *w > G::Concrete::default() {
l1
} else if *w < G::Concrete::default() {
-l1
} else {
0.0
}
} else {
0.0
})
.unwrap()
+ if let Some(l2) = l2 {
(*w) * (G::Concrete::ONE + G::Concrete::ONE)
* G::Concrete::from_f32(l2).unwrap()
} else {
G::Concrete::default()
}
} else {
G::Concrete::default()
};
*w += u - reg_penalty;
});
Ok(())
}
fn advance_step(&mut self) {
self.step += 1;
}
}
pub struct Momentum<G: Gradients> {
params: TrainParams,
velocity: G,
momentum_coeff: f32,
similarity_term: Option<f32>,
}
impl<G: Gradients> Momentum<G> {
pub fn new(params: TrainParams, momentum_coeff: f32) -> Momentum<G> {
Self {
params,
momentum_coeff,
velocity: G::empty(),
similarity_term: None, }
}
pub fn and_similarity_penalty(mut self, similarity_term: f32) -> Self {
self.similarity_term = Some(similarity_term);
self
}
fn update(&mut self, gradient_updates: G, loss: f32) -> G {
let mc = G::Concrete::from_f32(self.momentum_coeff).unwrap();
let loss = G::Concrete::from_f32(-loss * self.params.current_lr()).unwrap();
let sim_mul = G::Concrete::from_f32(if let Some(term) = self.similarity_term {
self.velocity
.cosine_similarity(&gradient_updates)
.map(|x| (2.0 * (x + 1.0) + term).log2() + 0.25)
.unwrap_or(1.0)
} else {
1.0
})
.unwrap();
self.velocity
.grad_iter_mut()
.zip(gradient_updates.into_grads())
.for_each(|(v, g)| {
*v = (*v * mc) + (self.params.clip_grad(g) * loss * sim_mul);
});
self.velocity.clone()
}
pub fn train_params(&self) -> &TrainParams {
&self.params
}
}
impl<G: Gradients> GradAdjuster<G> for Momentum<G> {
fn adjust(&mut self, gradient_updates: G, loss: f32) -> G {
self.update(gradient_updates, loss)
}
}
impl<G: Gradients> GradApplyer for Momentum<G> {
fn apply<G2: Gradients>(
&mut self,
gradient_updates: G2,
weights: &mut G2,
) -> Result<(), crate::Error> {
self.params.apply(gradient_updates, weights)
}
fn advance_step(&mut self) {
self.params.advance_step();
}
}
enum RMSPropBase<G: Gradients> {
NoMomentum(TrainParams),
Momentum(Momentum<G>),
}
impl<G: Gradients> RMSPropBase<G> {
pub fn train_params(&self) -> &TrainParams {
match self {
RMSPropBase::NoMomentum(p) => p,
RMSPropBase::Momentum(m) => m.train_params(),
}
}
}
impl<G: Gradients> GradAdjuster<G> for RMSPropBase<G> {
fn adjust(&mut self, gradient_updates: G, loss: f32) -> G {
use RMSPropBase::*;
match self {
NoMomentum(params) => params.adjust(gradient_updates, loss),
Momentum(m) => m.adjust(gradient_updates, loss),
}
}
}
impl<G: Gradients> GradApplyer for RMSPropBase<G> {
fn apply<G2: Gradients>(
&mut self,
gradient_updates: G2,
weights: &mut G2,
) -> Result<(), crate::Error> {
use RMSPropBase::*;
match self {
NoMomentum(params) => params.apply(gradient_updates, weights),
Momentum(m) => m.apply(gradient_updates, weights),
}
}
fn advance_step(&mut self) {
match self {
RMSPropBase::NoMomentum(p) => p.advance_step(),
RMSPropBase::Momentum(m) => m.advance_step(),
}
}
}
pub struct RMSProp<G: Gradients>
where
G::Concrete: Float,
{
base: RMSPropBase<G>,
accumulator: G,
beta: f32,
}
impl<G: Gradients> RMSProp<G>
where
G::Concrete: Float,
{
pub fn new(params: TrainParams, beta: f32) -> RMSProp<G> {
Self {
beta,
base: RMSPropBase::NoMomentum(params),
accumulator: G::empty(),
}
}
pub fn new_with_momentum(params: TrainParams, momentum_coeff: f32, beta: f32) -> RMSProp<G> {
Self {
beta,
base: RMSPropBase::Momentum(Momentum::new(params, momentum_coeff)),
accumulator: G::empty(),
}
}
pub fn and_similarity_penalty(mut self, similarity_term: f32) -> Self {
if let RMSPropBase::Momentum(m) = &mut self.base {
m.similarity_term = Some(similarity_term);
}
self
}
pub fn train_params(&self) -> &TrainParams {
self.base.train_params()
}
}
impl<G: Gradients> GradAdjuster<G> for RMSProp<G>
where
G::Concrete: Float,
{
fn adjust(&mut self, mut gradient_updates: G, loss: f32) -> G {
let b = G::Concrete::from_f32(self.beta).unwrap();
self.accumulator
.grad_iter_mut()
.zip(gradient_updates.grad_iter_mut())
.for_each(|(a, u)| {
let new_a = (*a * b) + (G::Concrete::ONE - b) * (*u) * (*u);
*a = new_a;
*u /= (new_a + G::Concrete::SMOL).sqrt();
});
self.base.adjust(gradient_updates, loss)
}
}
impl<G: Gradients> GradApplyer for RMSProp<G>
where
G::Concrete: Float,
{
fn apply<G2: Gradients>(
&mut self,
gradient_updates: G2,
weights: &mut G2,
) -> Result<(), crate::Error> {
self.base.apply(gradient_updates, weights)
}
fn advance_step(&mut self) {
self.base.advance_step();
}
}