use optirs_core::optimizers::SGD;
use optirs_core::schedulers::{DecayStrategy, LearningRateScheduler, LinearWarmupDecay};
use optirs_core::Optimizer;
use scirs2_core::ndarray::{Array1, Array2};
use scirs2_core::numeric::Float;
#[allow(dead_code)]
fn generate_data<A: Float>(n_samples: usize, nfeatures: usize) -> (Array2<A>, Array1<A>) {
let mut rng = scirs2_core::random::thread_rng();
let mut x = Array2::<A>::zeros((n_samples, nfeatures));
let mut y = Array1::<A>::zeros(n_samples);
let true_weights: Vec<A> = (0..nfeatures)
.map(|_| A::from(rng.gen_range(-1.0..1.0)).expect("unwrap failed"))
.collect();
for i in 0..n_samples {
for j in 0..nfeatures {
let x_val = A::from(rng.gen_range(-5.0..5.0)).expect("unwrap failed");
x[[i, j]] = x_val;
}
let mut target = A::zero();
for j in 0..nfeatures {
target = target + x[[i, j]] * true_weights[j];
}
target = target + A::from(rng.gen_range(-0.1..0.1)).expect("unwrap failed");
y[i] = target;
}
(x, y)
}
#[allow(dead_code)]
fn mean_squared_error<A: Float>(y_true: &Array1<A>, ypred: &Array1<A>) -> A {
let diff = ypred - y_true;
let squared = diff.mapv(|x| x * x);
let sum = squared.sum();
sum / A::from(y_true.len()).expect("unwrap failed")
}
#[allow(dead_code)]
fn predict<A: Float + 'static>(x: &Array2<A>, weights: &Array1<A>) -> Array1<A> {
x.dot(weights)
}
#[allow(dead_code)]
fn main() -> Result<(), Box<dyn std::error::Error>> {
let n_samples = 100;
let nfeatures = 5;
let (x, y) = generate_data::<f64>(n_samples, nfeatures);
println!(
"Generated data with {} samples and {} features",
n_samples, nfeatures
);
let n_epochs = 50;
let batch_size = 10;
let n_batches = n_samples / batch_size;
let warmup_epochs = 5;
let total_steps = n_epochs * n_batches;
let warmup_steps = warmup_epochs * n_batches;
let decay_steps = total_steps - warmup_steps;
let mut weights = Array1::<f64>::zeros(nfeatures);
let mut sgd_constant = SGD::new(0.1);
let mut linear_warmup_linear_decay = LinearWarmupDecay::new(
0.1, 0.01, warmup_steps, decay_steps, DecayStrategy::Linear { final_lr: 0.001 }, );
let mut linear_warmup_cosine_decay = LinearWarmupDecay::new(
0.1, 0.01, warmup_steps, decay_steps, DecayStrategy::Cosine { min_lr: 0.001 }, );
let mut linear_warmup_linear_decay_lrs = Vec::with_capacity(total_steps);
let mut linear_warmup_cosine_decay_lrs = Vec::with_capacity(total_steps);
println!(
"Training for {} epochs with {} batches per epoch",
n_epochs, n_batches
);
println!(
"Warmup for first {} epochs, then decay for {} epochs",
warmup_epochs,
n_epochs - warmup_epochs
);
let mut weights_linear_decay = weights.clone();
let mut weights_cosine_decay = weights.clone();
let mut losses_constant = Vec::with_capacity(n_epochs);
let mut losses_linear_decay = Vec::with_capacity(n_epochs);
let mut losses_cosine_decay = Vec::with_capacity(n_epochs);
for epoch in 0..n_epochs {
let mut indices: Vec<usize> = (0..n_samples).collect();
let mut rng = scirs2_core::random::thread_rng();
for i in (1..indices.len()).rev() {
let j = rng.gen_range(0..i);
indices.swap(i, j);
}
let mut epoch_loss_constant = 0.0;
let mut epoch_loss_linear = 0.0;
let mut epoch_loss_cosine = 0.0;
for batch in 0..n_batches {
let batch_indices = &indices[batch * batch_size..(batch + 1) * batch_size];
let x_batch =
Array2::from_shape_fn((batch_size, nfeatures), |(i, j)| x[[batch_indices[i], j]]);
let y_batch = Array1::from_shape_fn(batch_size, |i| y[batch_indices[i]]);
let y_pred_constant = predict(&x_batch, &weights);
let loss_constant = mean_squared_error(&y_batch, &y_pred_constant);
epoch_loss_constant += loss_constant;
let error_constant = &y_pred_constant - &y_batch;
let gradient_constant = x_batch.t().dot(&error_constant) / f64::from(batch_size as u32);
weights = sgd_constant.step(&weights, &gradient_constant)?;
let lr_linear = linear_warmup_linear_decay.step();
linear_warmup_linear_decay_lrs.push(lr_linear);
let y_pred_linear = predict(&x_batch, &weights_linear_decay);
let loss_linear = mean_squared_error(&y_batch, &y_pred_linear);
epoch_loss_linear += loss_linear;
let error_linear = &y_pred_linear - &y_batch;
let gradient_linear = x_batch.t().dot(&error_linear) / f64::from(batch_size as u32);
weights_linear_decay = &weights_linear_decay - &(&gradient_linear * lr_linear);
let lr_cosine = linear_warmup_cosine_decay.step();
linear_warmup_cosine_decay_lrs.push(lr_cosine);
let y_pred_cosine = predict(&x_batch, &weights_cosine_decay);
let loss_cosine = mean_squared_error(&y_batch, &y_pred_cosine);
epoch_loss_cosine += loss_cosine;
let error_cosine = &y_pred_cosine - &y_batch;
let gradient_cosine = x_batch.t().dot(&error_cosine) / f64::from(batch_size as u32);
weights_cosine_decay = &weights_cosine_decay - &(&gradient_cosine * lr_cosine);
}
losses_constant.push(epoch_loss_constant / n_batches as f64);
losses_linear_decay.push(epoch_loss_linear / n_batches as f64);
losses_cosine_decay.push(epoch_loss_cosine / n_batches as f64);
if epoch % 5 == 0 || epoch == n_epochs - 1 {
println!(
"Epoch {}/{} - Loss (Constant): {:.6}, Loss (Linear Warmup+Decay): {:.6}, Loss (Linear Warmup+Cosine Decay): {:.6}",
epoch + 1,
n_epochs,
losses_constant[epoch],
losses_linear_decay[epoch],
losses_cosine_decay[epoch]
);
}
}
println!("\nLearning Rate Trajectories:");
println!("Constant LR: 0.1 (all steps)");
println!("\nScheduler Performance Comparison:");
println!(
"Final loss (Constant LR): {:.6}",
losses_constant.last().expect("unwrap failed")
);
println!(
"Final loss (Linear Warmup+Linear Decay): {:.6}",
losses_linear_decay.last().expect("unwrap failed")
);
println!(
"Final loss (Linear Warmup+Cosine Decay): {:.6}",
losses_cosine_decay.last().expect("unwrap failed")
);
println!("\nLearning Rates at Key Points:");
let warmup_midpoint = warmup_steps / 2;
let decay_midpoint = warmup_steps + decay_steps / 2;
println!(
"Start: Linear Warmup+Linear Decay={:.4}, Linear Warmup+Cosine Decay={:.4}",
linear_warmup_linear_decay_lrs[0], linear_warmup_cosine_decay_lrs[0]
);
println!(
"Mid-warmup: Linear Warmup+Linear Decay={:.4}, Linear Warmup+Cosine Decay={:.4}",
linear_warmup_linear_decay_lrs[warmup_midpoint],
linear_warmup_cosine_decay_lrs[warmup_midpoint]
);
println!(
"End of warmup: Linear Warmup+Linear Decay={:.4}, Linear Warmup+Cosine Decay={:.4}",
linear_warmup_linear_decay_lrs[warmup_steps - 1],
linear_warmup_cosine_decay_lrs[warmup_steps - 1]
);
println!(
"Mid-decay: Linear Warmup+Linear Decay={:.4}, Linear Warmup+Cosine Decay={:.4}",
linear_warmup_linear_decay_lrs[decay_midpoint],
linear_warmup_cosine_decay_lrs[decay_midpoint]
);
println!(
"End: Linear Warmup+Linear Decay={:.4}, Linear Warmup+Cosine Decay={:.4}",
linear_warmup_linear_decay_lrs
.last()
.expect("unwrap failed"),
linear_warmup_cosine_decay_lrs
.last()
.expect("unwrap failed")
);
println!("\nTraining complete!");
Ok(())
}