use optirs_core::optimizers::{Adam, Optimizer, SparseAdam, SparseGradient};
use scirs2_core::ndarray::Array1;
use std::time::Instant;
#[allow(dead_code)]
fn main() {
println!("SparseAdam Optimizer Example");
println!("===========================\n");
let n_features = 10000; let n_samples = 100; let n_active_features = 20; let lr = 0.01; let n_iterations = 50;
println!("Sparse Linear Regression Task:");
println!(" - {} features total", n_features);
println!(" - {} training samples", n_samples);
println!(
" - Only {}/{} features are non-zero in each sample ({:.2}% sparsity)",
n_active_features,
n_features,
100.0 * (1.0 - n_active_features as f64 / n_features as f64)
);
println!();
let (x_train, y_train, true_weights) =
generate_sparse_data(n_samples, n_features, n_active_features);
println!("Training with standard Adam optimizer:");
let mut adam = Adam::new(lr);
let start_time = Instant::now();
let adam_result = train_with_adam(&x_train, &y_train, &true_weights, n_iterations, &mut adam);
let adam_time = start_time.elapsed();
println!("\nTraining with SparseAdam optimizer:");
let mut sparse_adam = SparseAdam::new(lr);
let start_time = Instant::now();
let sparse_adam_result = train_with_sparse_adam(
&x_train,
&y_train,
&true_weights,
n_iterations,
&mut sparse_adam,
);
let sparse_adam_time = start_time.elapsed();
println!("\nResults Comparison:");
println!(" Standard Adam:");
println!(" - Time: {:?}", adam_time);
println!(" - Final Loss: {:.6}", adam_result.0);
println!(" - Parameter Error: {:.6}", adam_result.1);
println!(" SparseAdam:");
println!(" - Time: {:?}", sparse_adam_time);
println!(" - Final Loss: {:.6}", sparse_adam_result.0);
println!(" - Parameter Error: {:.6}", sparse_adam_result.1);
let speedup = adam_time.as_secs_f64() / sparse_adam_time.as_secs_f64();
println!("\nPerformance Comparison:");
println!(
" - SparseAdam is {:.2}x faster than standard Adam",
speedup
);
println!(
" - Loss difference: {:.6}",
(adam_result.0 - sparse_adam_result.0).abs()
);
println!("\nConclusion:");
println!(" SparseAdam provides similar optimization quality to standard Adam,");
println!(" but can be significantly faster when dealing with sparse gradients.");
println!(" The speedup is especially noticeable with very large, sparse feature spaces.");
}
#[allow(dead_code)]
fn generate_sparse_data(
n_samples: usize,
n_features: usize,
n_active_features: usize,
) -> (Vec<SparseInput>, Array1<f64>, Array1<f64>) {
let mut true_weights = Array1::zeros(n_features);
let active_indices: Vec<usize> = (0..n_features)
.step_by(n_features / n_active_features) .take(n_active_features)
.collect();
for &idx in &active_indices {
true_weights[idx] = rand_value(-1.0, 1.0);
}
let mut x_train = Vec::with_capacity(n_samples);
let mut y_train = Array1::zeros(n_samples);
for i in 0..n_samples {
let mut indices = Vec::with_capacity(n_active_features);
let mut values = Vec::with_capacity(n_active_features);
let mut target_sum = 0.0;
let n_true_active = n_active_features / 2;
for j in 0..n_true_active {
let idx = active_indices[j % active_indices.len()];
indices.push(idx);
let value = rand_value(-1.0, 1.0);
values.push(value);
target_sum += value * true_weights[idx];
}
for _ in n_true_active..n_active_features {
let idx = (rand_value(0.0, 1.0) * n_features as f64) as usize % n_features;
if !indices.contains(&idx) {
indices.push(idx);
let value = rand_value(-1.0, 1.0);
values.push(value);
target_sum += value * true_weights[idx];
}
}
y_train[i] = target_sum + rand_value(-0.1, 0.1);
x_train.push(SparseInput {
indices,
values,
dim: n_features,
});
}
(x_train, y_train, true_weights)
}
#[allow(dead_code)]
struct SparseInput {
indices: Vec<usize>,
values: Vec<f64>,
#[allow(dead_code)]
dim: usize,
}
impl SparseInput {
fn dot(&self, weights: &Array1<f64>) -> f64 {
self.indices
.iter()
.zip(&self.values)
.map(|(&idx, &val)| val * weights[idx])
.sum()
}
fn accumulate_gradient(&self, error: f64, gradients: &mut Array1<f64>) {
for (&idx, &val) in self.indices.iter().zip(&self.values) {
gradients[idx] += error * val;
}
}
#[allow(dead_code)]
fn compute_sparse_gradient(&self, error: f64) -> SparseGradient<f64> {
let values: Vec<f64> = self.values.iter().map(|&val| error * val).collect();
SparseGradient::new(self.indices.clone(), values, self.dim)
}
}
#[allow(dead_code)]
fn train_with_adam(
x_train: &[SparseInput],
y_train: &Array1<f64>,
true_weights: &Array1<f64>,
n_iterations: usize,
optimizer: &mut Adam<f64>,
) -> (f64, f64) {
let n_samples = x_train.len();
let n_features = true_weights.len();
let mut _weights = Array1::zeros(n_features);
for iter in 0..n_iterations {
let mut loss = 0.0;
for (i, x) in x_train.iter().enumerate() {
let pred = x.dot(&_weights);
let error = pred - y_train[i];
loss += error * error;
}
loss /= n_samples as f64;
let mut gradients = Array1::zeros(n_features);
for (i, x) in x_train.iter().enumerate() {
let pred = x.dot(&_weights);
let error = 2.0 * (pred - y_train[i]) / n_samples as f64;
x.accumulate_gradient(error, &mut gradients);
}
_weights = optimizer
.step(&_weights, &gradients)
.expect("unwrap failed");
let param_error = (&_weights - true_weights).map(|x| x * x).sum().sqrt();
if iter == 0 || iter == n_iterations - 1 || (iter + 1) % 10 == 0 {
println!(
" Iteration {}/{}: loss = {:.6}, param error = {:.6}",
iter + 1,
n_iterations,
loss,
param_error
);
}
}
let mut final_loss = 0.0;
for (i, x) in x_train.iter().enumerate() {
let pred = x.dot(&_weights);
let error = pred - y_train[i];
final_loss += error * error;
}
final_loss /= n_samples as f64;
let param_error = (&_weights - true_weights).map(|x| x * x).sum().sqrt();
(final_loss, param_error)
}
#[allow(dead_code)]
fn train_with_sparse_adam(
x_train: &[SparseInput],
y_train: &Array1<f64>,
true_weights: &Array1<f64>,
n_iterations: usize,
optimizer: &mut SparseAdam<f64>,
) -> (f64, f64) {
let n_samples = x_train.len();
let n_features = true_weights.len();
let mut _weights = Array1::zeros(n_features);
for iter in 0..n_iterations {
let mut loss = 0.0;
for (i, x) in x_train.iter().enumerate() {
let pred = x.dot(&_weights);
let error = pred - y_train[i];
loss += error * error;
}
loss /= n_samples as f64;
let mut all_indices = Vec::new();
let mut all_values = Vec::new();
let mut index_map = std::collections::HashMap::new();
for (i, x) in x_train.iter().enumerate() {
let pred = x.dot(&_weights);
let error = 2.0 * (pred - y_train[i]) / n_samples as f64;
for (&idx, &val) in x.indices.iter().zip(&x.values) {
let grad_val = error * val;
if let Some(pos) = index_map.get(&idx) {
all_values[*pos] += grad_val;
} else {
index_map.insert(idx, all_values.len());
all_indices.push(idx);
all_values.push(grad_val);
}
}
}
let sparse_grad = SparseGradient::new(all_indices, all_values, n_features);
_weights = optimizer
.step_sparse(&_weights, &sparse_grad)
.expect("unwrap failed");
let param_error = (&_weights - true_weights).map(|x| x * x).sum().sqrt();
if iter == 0 || iter == n_iterations - 1 || (iter + 1) % 10 == 0 {
println!(
" Iteration {}/{}: loss = {:.6}, param error = {:.6}",
iter + 1,
n_iterations,
loss,
param_error
);
}
}
let mut final_loss = 0.0;
for (i, x) in x_train.iter().enumerate() {
let pred = x.dot(&_weights);
let error = pred - y_train[i];
final_loss += error * error;
}
final_loss /= n_samples as f64;
let param_error = (&_weights - true_weights).map(|x| x * x).sum().sqrt();
(final_loss, param_error)
}
#[allow(dead_code)]
fn rand_value(min: f64, max: f64) -> f64 {
min + (max - min) * scirs2_core::random::random::<f64>()
}