use std::fmt::Debug;
#[allow(dead_code)]
use scirs2_core::ndarray::{Array1, Array2};
use scirs2_core::numeric::Float;
use std::collections::VecDeque;
use crate::error::{OptimError, Result};
#[derive(Debug, Clone, Copy)]
pub enum LearningRateAdaptationStrategy {
Fixed,
ExponentialDecay,
PolynomialDecay,
CosineAnnealing,
WarmRestart,
LossAdaptive,
GradientAdaptive,
TransformerPredicted,
}
#[derive(Debug, Clone)]
pub struct LearningRateAdapter<T: Float + Debug + Send + Sync + 'static> {
strategy: LearningRateAdaptationStrategy,
base_lr: T,
current_lr: T,
adaptation_params: LRAdaptationParams<T>,
loss_history: VecDeque<T>,
gradient_history: VecDeque<T>,
step_count: usize,
epoch_count: usize,
best_loss: Option<T>,
patience_counter: usize,
}
#[derive(Debug, Clone)]
pub struct LRAdaptationParams<T: Float + Debug + Send + Sync + 'static> {
decay_rate: T,
decay_steps: usize,
power: T,
min_lr: T,
max_lr: T,
warmup_steps: usize,
restart_period: usize,
patience: usize,
reduction_factor: T,
improvement_threshold: T,
}
#[derive(Debug, Clone)]
pub struct ScheduleState<T: Float + Debug + Send + Sync + 'static> {
current_cycle: usize,
cycle_steps: usize,
in_warmup: bool,
last_update_step: usize,
state: T,
}
impl<T: Float + Debug + Default + Clone + Send + Sync + 'static> LearningRateAdapter<T> {
pub fn new(strategy: LearningRateAdaptationStrategy, base_lr: T) -> Self {
Self {
strategy,
base_lr,
current_lr: base_lr,
adaptation_params: LRAdaptationParams::default(),
loss_history: VecDeque::new(),
gradient_history: VecDeque::new(),
step_count: 0,
epoch_count: 0,
best_loss: None,
patience_counter: 0,
}
}
pub fn new_with_params(
strategy: LearningRateAdaptationStrategy,
base_lr: T,
params: LRAdaptationParams<T>,
) -> Self {
Self {
strategy,
base_lr,
current_lr: base_lr,
adaptation_params: params,
loss_history: VecDeque::new(),
gradient_history: VecDeque::new(),
step_count: 0,
epoch_count: 0,
best_loss: None,
patience_counter: 0,
}
}
pub fn update_learning_rate(
&mut self,
loss: Option<T>,
gradients: Option<&Array1<T>>,
) -> Result<T> {
self.step_count += 1;
if let Some(loss_val) = loss {
self.loss_history.push_back(loss_val);
if self.loss_history.len() > 100 {
self.loss_history.pop_front();
}
}
if let Some(grad) = gradients {
let grad_norm = grad
.iter()
.map(|&x| x * x)
.fold(T::zero(), |a, b| a + b)
.sqrt();
self.gradient_history.push_back(grad_norm);
if self.gradient_history.len() > 100 {
self.gradient_history.pop_front();
}
}
self.current_lr = match self.strategy {
LearningRateAdaptationStrategy::Fixed => self.base_lr,
LearningRateAdaptationStrategy::ExponentialDecay => self.exponential_decay(),
LearningRateAdaptationStrategy::PolynomialDecay => self.polynomial_decay(),
LearningRateAdaptationStrategy::CosineAnnealing => self.cosine_annealing(),
LearningRateAdaptationStrategy::WarmRestart => self.warm_restart(),
LearningRateAdaptationStrategy::LossAdaptive => self.loss_adaptive(loss)?,
LearningRateAdaptationStrategy::GradientAdaptive => {
self.gradient_adaptive(gradients)?
}
LearningRateAdaptationStrategy::TransformerPredicted => self.transformer_predicted()?,
};
if self.step_count < self.adaptation_params.warmup_steps {
let warmup_factor = scirs2_core::numeric::NumCast::from(self.step_count as f64)
.unwrap_or_else(|| T::zero())
/ scirs2_core::numeric::NumCast::from(self.adaptation_params.warmup_steps as f64)
.unwrap_or_else(|| T::zero());
self.current_lr = self.current_lr * warmup_factor;
}
self.current_lr = self
.current_lr
.max(self.adaptation_params.min_lr)
.min(self.adaptation_params.max_lr);
Ok(self.current_lr)
}
fn exponential_decay(&self) -> T {
let steps = scirs2_core::numeric::NumCast::from(self.step_count as f64)
.unwrap_or_else(|| T::zero());
let decay_steps =
scirs2_core::numeric::NumCast::from(self.adaptation_params.decay_steps as f64)
.unwrap_or_else(|| T::zero());
let decay_factor = (steps / decay_steps) * self.adaptation_params.decay_rate.ln();
self.base_lr * (-decay_factor).exp()
}
fn polynomial_decay(&self) -> T {
if self.step_count >= self.adaptation_params.decay_steps {
self.adaptation_params.min_lr
} else {
let progress = scirs2_core::numeric::NumCast::from(self.step_count as f64)
.unwrap_or_else(|| T::zero())
/ scirs2_core::numeric::NumCast::from(self.adaptation_params.decay_steps as f64)
.unwrap_or_else(|| T::zero());
let decay_factor = (T::one() - progress).powf(self.adaptation_params.power);
(self.base_lr - self.adaptation_params.min_lr) * decay_factor
+ self.adaptation_params.min_lr
}
}
fn cosine_annealing(&self) -> T {
let steps = scirs2_core::numeric::NumCast::from(self.step_count as f64)
.unwrap_or_else(|| T::zero());
let total_steps =
scirs2_core::numeric::NumCast::from(self.adaptation_params.decay_steps as f64)
.unwrap_or_else(|| T::zero());
let pi =
scirs2_core::numeric::NumCast::from(std::f64::consts::PI).unwrap_or_else(|| T::zero());
let cosine_factor = (T::one() + (pi * steps / total_steps).cos())
/ scirs2_core::numeric::NumCast::from(2.0).unwrap_or_else(|| T::zero());
self.adaptation_params.min_lr
+ (self.base_lr - self.adaptation_params.min_lr) * cosine_factor
}
fn warm_restart(&self) -> T {
let period = self.adaptation_params.restart_period;
let cycle_position = self.step_count % period;
let progress = scirs2_core::numeric::NumCast::from(cycle_position as f64)
.unwrap_or_else(|| T::zero())
/ scirs2_core::numeric::NumCast::from(period as f64).unwrap_or_else(|| T::zero());
let pi =
scirs2_core::numeric::NumCast::from(std::f64::consts::PI).unwrap_or_else(|| T::zero());
let cosine_factor = (T::one() + (pi * progress).cos())
/ scirs2_core::numeric::NumCast::from(2.0).unwrap_or_else(|| T::zero());
self.adaptation_params.min_lr
+ (self.base_lr - self.adaptation_params.min_lr) * cosine_factor
}
fn loss_adaptive(&mut self, loss: Option<T>) -> Result<T> {
if let Some(current_loss) = loss {
if let Some(best_loss) = self.best_loss {
let improvement = (best_loss - current_loss) / best_loss;
if improvement > self.adaptation_params.improvement_threshold {
self.best_loss = Some(current_loss);
self.patience_counter = 0;
Ok(self.current_lr
* scirs2_core::numeric::NumCast::from(1.01).unwrap_or_else(|| T::zero()))
} else {
self.patience_counter += 1;
if self.patience_counter >= self.adaptation_params.patience {
self.patience_counter = 0;
Ok(self.current_lr * self.adaptation_params.reduction_factor)
} else {
Ok(self.current_lr)
}
}
} else {
self.best_loss = Some(current_loss);
Ok(self.current_lr)
}
} else {
Ok(self.current_lr)
}
}
fn gradient_adaptive(&self, gradients: Option<&Array1<T>>) -> Result<T> {
if let Some(grad) = gradients {
let grad_norm = grad
.iter()
.map(|&x| x * x)
.fold(T::zero(), |a, b| a + b)
.sqrt();
let target_norm = scirs2_core::numeric::NumCast::from(1.0).unwrap_or_else(|| T::zero());
let scale_factor = target_norm
/ (grad_norm
+ scirs2_core::numeric::NumCast::from(1e-8).unwrap_or_else(|| T::zero()));
let alpha = scirs2_core::numeric::NumCast::from(0.1).unwrap_or_else(|| T::zero());
let adapted_lr =
self.current_lr * (T::one() - alpha) + (self.base_lr * scale_factor) * alpha;
Ok(adapted_lr)
} else {
Ok(self.current_lr)
}
}
fn transformer_predicted(&self) -> Result<T> {
let decay_factor = T::one()
/ (T::one()
+ scirs2_core::numeric::NumCast::from(self.step_count as f64)
.unwrap_or_else(|| T::zero())
* scirs2_core::numeric::NumCast::from(0.001).unwrap_or_else(|| T::zero()));
Ok(self.base_lr * decay_factor)
}
pub fn current_learning_rate(&self) -> T {
self.current_lr
}
pub fn base_learning_rate(&self) -> T {
self.base_lr
}
pub fn set_base_learning_rate(&mut self, lr: T) {
self.base_lr = lr;
}
pub fn step_count(&self) -> usize {
self.step_count
}
pub fn on_epoch_end(&mut self) {
self.epoch_count += 1;
}
pub fn reset(&mut self) {
self.step_count = 0;
self.epoch_count = 0;
self.current_lr = self.base_lr;
self.loss_history.clear();
self.gradient_history.clear();
self.best_loss = None;
self.patience_counter = 0;
}
pub fn loss_history(&self) -> &VecDeque<T> {
&self.loss_history
}
pub fn gradient_history(&self) -> &VecDeque<T> {
&self.gradient_history
}
pub fn set_strategy(&mut self, strategy: LearningRateAdaptationStrategy) {
self.strategy = strategy;
}
pub fn set_parameters(&mut self, params: LRAdaptationParams<T>) {
self.adaptation_params = params;
}
}
impl<T: Float + Debug + Default + Clone + Send + Sync + 'static> Default for LRAdaptationParams<T> {
fn default() -> Self {
Self {
decay_rate: scirs2_core::numeric::NumCast::from(0.96).unwrap_or_else(|| T::zero()),
decay_steps: 1000,
power: scirs2_core::numeric::NumCast::from(0.5).unwrap_or_else(|| T::zero()),
min_lr: scirs2_core::numeric::NumCast::from(1e-6).unwrap_or_else(|| T::zero()),
max_lr: scirs2_core::numeric::NumCast::from(1e-1).unwrap_or_else(|| T::zero()),
warmup_steps: 100,
restart_period: 1000,
patience: 10,
reduction_factor: scirs2_core::numeric::NumCast::from(0.5).unwrap_or_else(|| T::zero()),
improvement_threshold: scirs2_core::numeric::NumCast::from(0.01)
.unwrap_or_else(|| T::zero()),
}
}
}