use spintronics::autodiff::Activation;
use spintronics::prelude::*;
fn target_function(x: f64) -> f64 {
(5.0 * x).sin() * (-x * x).exp()
}
fn evaluate_mse(learner: &ActiveLearner, test_x: &[f64], test_y: &[f64]) -> f64 {
let mut acc = 0.0_f64;
for (x, &y) in test_x.iter().zip(test_y.iter()) {
let (mean, _std) = learner.predict_with_uncertainty(&[*x]).unwrap();
let err = mean[0] - y;
acc += err * err;
}
acc / test_x.len() as f64
}
fn run_strategy(
strategy: QueryStrategy,
label: &str,
) -> std::result::Result<(usize, f64), Box<dyn std::error::Error>> {
println!("\n--- {label} ---\n");
let candidate_pool: Vec<Vec<f64>> = (0..200)
.map(|i| vec![-2.0 + 4.0 * (i as f64) / 199.0])
.collect();
let config = ActiveLearningConfig {
n_committee: 5,
n_initial_samples: 5,
n_query_iterations: 15,
n_inner_train: 80,
lr: 1e-2,
query_strategy: strategy,
};
let layer_sizes = [1, 16, 16, 1];
let activations = [Activation::Tanh, Activation::Tanh, Activation::Linear];
let mut learner = ActiveLearner::new(&layer_sizes, &activations, config, 42)?;
let oracle = |x: &[f64]| -> Vec<f64> { vec![target_function(x[0])] };
let result = learner.fit(oracle, &candidate_pool)?;
let test_x: Vec<f64> = (0..101).map(|i| -2.0 + 4.0 * (i as f64) / 100.0).collect();
let test_y: Vec<f64> = test_x.iter().copied().map(target_function).collect();
let test_x_slices: Vec<Vec<f64>> = test_x.iter().map(|&x| vec![x]).collect();
let mse = evaluate_mse(
&learner,
&test_x_slices.iter().map(|v| v[0]).collect::<Vec<f64>>(),
&test_y,
);
println!(" Queries used: {}", result.n_queries_used);
println!(" Final training loss: {:.4e}", result.final_loss);
println!(" Independent-test MSE: {mse:.4e}");
println!(
" Queried indices: {:?}",
&result.queried_indices[..result.queried_indices.len().min(8)]
);
Ok((result.n_queries_used, mse))
}
fn main() -> std::result::Result<(), Box<dyn std::error::Error>> {
println!("=============================================================");
println!(" Active Learning: Uncertainty Sampling vs Random Baseline");
println!("=============================================================");
println!();
println!(" Target: f(x) = sin(5x) · exp(-x²) on x ∈ [-2, 2]");
println!(" Pool: 200 candidate points uniformly spaced");
println!(" Query budget: 15 oracle calls + 5 initial random samples");
println!(" Committee: 5 MLPs of [1 → 16 → 16 → 1]");
let (n_rand, mse_rand) = run_strategy(QueryStrategy::RandomBaseline, "Random Baseline")?;
let (n_qbc, mse_qbc) = run_strategy(QueryStrategy::QueryByCommittee, "Query By Committee")?;
let (n_unc, mse_unc) =
run_strategy(QueryStrategy::UncertaintySampling, "Uncertainty Sampling")?;
println!("\n--- Comparison Summary ---\n");
println!(
" {:>22} {:>10} {:>14}",
"Strategy", "queries", "test MSE"
);
println!(" {}", "-".repeat(50));
println!(
" {:>22} {:>10} {:>14.4e}",
"RandomBaseline", n_rand, mse_rand
);
println!(
" {:>22} {:>10} {:>14.4e}",
"QueryByCommittee", n_qbc, mse_qbc
);
println!(
" {:>22} {:>10} {:>14.4e}",
"UncertaintySampling", n_unc, mse_unc
);
println!("\n=============================================================");
println!(" Done. Active strategies typically achieve lower test MSE for");
println!(" the same query budget by focusing on high-uncertainty regions.");
println!("=============================================================\n");
Ok(())
}