use scirs2_core::ndarray::{s, Array1, Array2};
use scirs2_core::random::Normal;
use scirs2_core::random::RandomExt;
use optirs_core::optimizer_composition::{
ChainedOptimizer, ParallelOptimizer, ParameterGroup, SequentialOptimizer,
};
use optirs_core::optimizers::{Adam, Optimizer, RMSprop, SGD};
use std::time::Instant;
#[allow(dead_code)]
fn main() {
println!("Optimizer Composition Framework Example");
println!("======================================\n");
let n_samples = 100;
let nfeatures = 10;
println!(
"Generating synthetic linear regression data with {} samples and {} features",
n_samples, nfeatures
);
let (x_train, y_train, true_weights, true_bias) = generate_data(n_samples, nfeatures);
println!("\nRunning regression with different optimizer compositions...\n");
sequential_optimizer_example(&x_train, &y_train, true_weights.len());
parallel_optimizer_example(&x_train, &y_train, true_weights.len());
chained_optimizer_example(&x_train, &y_train, true_weights.len());
}
#[allow(dead_code)]
fn sequential_optimizer_example(x_train: &Array2<f64>, y_train: &Array1<f64>, nfeatures: usize) {
println!("Sequential Optimizer Example");
println!("----------------------------");
println!(
"Applying SGD followed by Adam - this can be useful for coarse-to-fine optimization\n"
);
let mut weights = Array1::<f64>::zeros(nfeatures);
let mut bias = 0.0;
let sgd = SGD::new_with_config(0.1, 0.0, 0.0); let adam = Adam::new(0.01);
let mut sequential_optimizer = SequentialOptimizer::new(vec![Box::new(sgd), Box::new(adam)]);
let n_iterations = 100;
let start_time = Instant::now();
for i in 0..n_iterations {
let predictions = compute_predictions(x_train, &weights, bias);
let (loss, weight_grad, bias_grad) =
compute_gradients(x_train, y_train, &predictions, &weights, bias);
weights = sequential_optimizer.step(&weights, &weight_grad).expect("unwrap failed");
bias -= 0.01 * bias_grad;
if i == 0 || i == n_iterations - 1 || (i + 1) % 25 == 0 {
println!(" Iteration {}: loss = {:.6}", i + 1, loss);
}
}
let elapsed = start_time.elapsed();
let predictions = compute_predictions(x_train, &weights, bias);
let (final_loss__, _, _) = compute_gradients(x_train, y_train, &predictions, &weights, bias);
println!("\nResults:");
println!(" Training time: {:?}", elapsed);
println!(" Final loss: {:.6}", final_loss__);
println!(" Weight norm: {:.6}", weights.mapv(|w| w * w).sum().sqrt());
println!();
}
#[allow(dead_code)]
fn parallel_optimizer_example(x_train: &Array2<f64>, y_train: &Array1<f64>, nfeatures: usize) {
println!("Parallel Optimizer Example");
println!("---------------------------");
println!("Using different optimizers for different parameter groups\n");
let split_point = nfeatures / 2;
let weights_group1 = Array1::<f64>::zeros(split_point);
let weights_group2 = Array1::<f64>::zeros(nfeatures - split_point);
let mut bias = 0.0;
let sgd = SGD::new(0.1);
let adam = Adam::new(0.01);
let group1 = ParameterGroup::new(weights_group1, 0); let group2 = ParameterGroup::new(weights_group2, 1);
let mut parallel_optimizer =
ParallelOptimizer::new(vec![Box::new(sgd), Box::new(adam)], vec![group1, group2]);
let n_iterations = 100;
let start_time = Instant::now();
for i in 0..n_iterations {
let current_weights = parallel_optimizer.get_all_parameters().expect("unwrap failed");
let mut combined_weights = Array1::<f64>::zeros(nfeatures);
for j in 0..split_point {
combined_weights[j] = current_weights[0][j];
}
for j in 0..(nfeatures - split_point) {
combined_weights[j + split_point] = current_weights[1][j];
}
let predictions = compute_predictions(x_train, &combined_weights, bias);
let (loss, weight_grad, bias_grad) =
compute_gradients(x_train, y_train, &predictions, &combined_weights, bias);
let weight_grad_group1 = weight_grad.slice(s![0..split_point]).to_owned();
let weight_grad_group2 = weight_grad.slice(s![split_point..]).to_owned();
parallel_optimizer
.update_all_parameters(&[weight_grad_group1, weight_grad_group2])
.expect("unwrap failed");
bias -= 0.01 * bias_grad;
if i == 0 || i == n_iterations - 1 || (i + 1) % 25 == 0 {
println!(" Iteration {}: loss = {:.6}", i + 1, loss);
}
}
let elapsed = start_time.elapsed();
let final_weights = parallel_optimizer.get_all_parameters().expect("unwrap failed");
let mut combined_weights = Array1::<f64>::zeros(nfeatures);
for j in 0..split_point {
combined_weights[j] = final_weights[0][j];
}
for j in 0..(nfeatures - split_point) {
combined_weights[j + split_point] = final_weights[1][j];
}
let predictions = compute_predictions(x_train, &combined_weights, bias);
let (final_loss, _, _) =
compute_gradients(x_train, y_train, &predictions, &combined_weights, bias);
println!("\nResults:");
println!(" Training time: {:?}", elapsed);
println!(" Final loss: {:.6}", final_loss);
println!(
" Weight norm (group 1): {:.6}",
final_weights[0].mapv(|w| w * w).sum().sqrt()
);
println!(
" Weight norm (group 2): {:.6}",
final_weights[1].mapv(|w| w * w).sum().sqrt()
);
println!();
}
#[allow(dead_code)]
fn chained_optimizer_example(x_train: &Array2<f64>, y_train: &Array1<f64>, nfeatures: usize) {
println!("Chained Optimizer Example");
println!("-------------------------");
println!("Using RMSprop wrapped with Adam\n");
let mut weights = Array1::<f64>::zeros(nfeatures);
let mut bias = 0.0;
let inner = RMSprop::new(0.01);
let outer = Adam::new(0.001);
let mut chained_optimizer = ChainedOptimizer::new(Box::new(inner), Box::new(outer));
let n_iterations = 100;
let start_time = Instant::now();
for i in 0..n_iterations {
let predictions = compute_predictions(x_train, &weights, bias);
let (loss, weight_grad, bias_grad) =
compute_gradients(x_train, y_train, &predictions, &weights, bias);
weights = chained_optimizer.step(&weights, &weight_grad).expect("unwrap failed");
bias -= 0.01 * bias_grad;
if i == 0 || i == n_iterations - 1 || (i + 1) % 25 == 0 {
println!(" Iteration {}: loss = {:.6}", i + 1, loss);
}
}
let elapsed = start_time.elapsed();
let predictions = compute_predictions(x_train, &weights, bias);
let (final_loss__, _, _) = compute_gradients(x_train, y_train, &predictions, &weights, bias);
println!("\nResults:");
println!(" Training time: {:?}", elapsed);
println!(" Final loss: {:.6}", final_loss__);
println!(" Weight norm: {:.6}", weights.mapv(|w| w * w).sum().sqrt());
println!();
}
#[allow(dead_code)]
fn generate_data(
n_samples: usize,
nfeatures: usize,
) -> (Array2<f64>, Array1<f64>, Array1<f64>, f64) {
let true_weights = Array1::random(nfeatures, Normal::new(0.0, 1.0).expect("unwrap failed"));
let true_bias = 1.0;
let x = Array2::random((n_samples, nfeatures), Normal::new(0.0, 1.0).expect("unwrap failed"));
let y_without_noise = x.dot(&true_weights) + true_bias;
let noise = Array1::random(n_samples, Normal::new(0.0, 0.1).expect("unwrap failed"));
let y = &y_without_noise + &noise;
(x, y, true_weights, true_bias)
}
#[allow(dead_code)]
fn compute_predictions(x: &Array2<f64>, weights: &Array1<f64>, bias: f64) -> Array1<f64> {
&x.dot(weights) + bias
}
#[allow(dead_code)]
fn compute_gradients(
x: &Array2<f64>,
y: &Array1<f64>,
predictions: &Array1<f64>,
_weights: &Array1<f64>,
_bias: f64,
) -> (f64, Array1<f64>, f64) {
let error = predictions - y;
let loss = (&error * &error).sum() / (2.0 * y.len() as f64);
let weight_grad = x.t().dot(&error) / (y.len() as f64);
let bias_grad = error.sum() / (y.len() as f64);
(loss, weight_grad, bias_grad)
}