mod adaboost;
mod adaboost_hyperparams;
mod algorithm;
mod hyperparams;
pub use adaboost::*;
pub use adaboost_hyperparams::*;
pub use algorithm::*;
pub use hyperparams::*;
#[cfg(test)]
mod tests {
use super::*;
use linfa::prelude::{Fit, Predict, ToConfusionMatrix};
use linfa_trees::DecisionTree;
use ndarray_rand::rand::SeedableRng;
use rand::rngs::SmallRng;
#[test]
fn test_random_forest_accuracy_on_iris_dataset() {
let mut rng = SmallRng::seed_from_u64(42);
let (train, test) = linfa_datasets::iris()
.shuffle(&mut rng)
.split_with_ratio(0.8);
let model = RandomForestParams::new_fixed_rng(DecisionTree::params(), rng)
.ensemble_size(100)
.bootstrap_proportion(0.7)
.feature_proportion(0.3)
.fit(&train)
.unwrap();
let predictions = model.predict(&test);
let cm = predictions.confusion_matrix(&test).unwrap();
let acc = cm.accuracy();
assert!(acc >= 0.9, "Expected accuracy to be above 90%, got {}", acc);
}
#[test]
fn test_ensemble_learner_accuracy_on_iris_dataset() {
let mut rng = SmallRng::seed_from_u64(42);
let (train, test) = linfa_datasets::iris()
.shuffle(&mut rng)
.split_with_ratio(0.8);
let model = EnsembleLearnerParams::new_fixed_rng(DecisionTree::params(), rng)
.ensemble_size(100)
.bootstrap_proportion(0.7)
.fit(&train)
.unwrap();
let predictions = model.predict(&test);
let cm = predictions.confusion_matrix(&test).unwrap();
let acc = cm.accuracy();
assert!(acc >= 0.9, "Expected accuracy to be above 90%, got {}", acc);
}
#[test]
fn test_adaboost_accuracy_on_iris_dataset() {
let mut rng = SmallRng::seed_from_u64(42);
let (train, test) = linfa_datasets::iris()
.shuffle(&mut rng)
.split_with_ratio(0.8);
let model = AdaBoostParams::new_fixed_rng(DecisionTree::params().max_depth(Some(1)), rng)
.n_estimators(50)
.learning_rate(1.0)
.fit(&train)
.unwrap();
let predictions = model.predict(&test);
let cm = predictions.confusion_matrix(&test).unwrap();
let acc = cm.accuracy();
assert!(
acc >= 0.85,
"Expected accuracy to be above 85%, got {}",
acc
);
}
#[test]
fn test_adaboost_with_low_learning_rate() {
let mut rng = SmallRng::seed_from_u64(42);
let (train, test) = linfa_datasets::iris()
.shuffle(&mut rng)
.split_with_ratio(0.8);
let model = AdaBoostParams::new_fixed_rng(DecisionTree::params().max_depth(Some(2)), rng)
.n_estimators(100)
.learning_rate(0.5)
.fit(&train)
.unwrap();
let predictions = model.predict(&test);
let cm = predictions.confusion_matrix(&test).unwrap();
let acc = cm.accuracy();
assert!(
acc >= 0.85,
"Expected accuracy to be above 85%, got {}",
acc
);
}
#[test]
fn test_adaboost_model_weights() {
let mut rng = SmallRng::seed_from_u64(42);
let (train, _) = linfa_datasets::iris()
.shuffle(&mut rng)
.split_with_ratio(0.8);
let model = AdaBoostParams::new_fixed_rng(DecisionTree::params().max_depth(Some(1)), rng)
.n_estimators(10)
.fit(&train)
.unwrap();
for weight in model.weights() {
assert!(
*weight > 0.0,
"Model weight should be positive, got {}",
weight
);
}
assert_eq!(model.n_estimators(), 10);
}
#[test]
fn test_adaboost_different_learning_rates() {
let rng1 = SmallRng::seed_from_u64(42);
let rng2 = SmallRng::seed_from_u64(42);
let (train, _) = linfa_datasets::iris()
.shuffle(&mut SmallRng::seed_from_u64(42))
.split_with_ratio(0.8);
let model1 = AdaBoostParams::new_fixed_rng(DecisionTree::params().max_depth(Some(1)), rng1)
.n_estimators(10)
.learning_rate(1.0)
.fit(&train)
.unwrap();
let model2 = AdaBoostParams::new_fixed_rng(DecisionTree::params().max_depth(Some(1)), rng2)
.n_estimators(10)
.learning_rate(0.5)
.fit(&train)
.unwrap();
let weights1 = model1.weights();
let weights2 = model2.weights();
let mut has_difference = false;
for (w1, w2) in weights1.iter().zip(weights2.iter()) {
if (w1 - w2).abs() > 0.01 {
has_difference = true;
break;
}
}
assert!(
has_difference,
"Different learning rates should produce different model weights"
);
}
#[test]
fn test_adaboost_early_stopping_on_perfect_fit() {
use linfa::DatasetBase;
use ndarray::Array2;
let records = Array2::from_shape_vec(
(6, 2),
vec![
0.0, 0.0, 0.1, 0.1, 0.2, 0.2, 1.0, 1.0, 1.1, 1.1, 1.2, 1.2, ],
)
.unwrap();
let targets = ndarray::array![0, 0, 0, 1, 1, 1];
let dataset = DatasetBase::new(records, targets);
let rng = SmallRng::seed_from_u64(42);
let model = AdaBoostParams::new_fixed_rng(DecisionTree::params().max_depth(Some(3)), rng)
.n_estimators(50)
.fit(&dataset)
.unwrap();
assert!(
model.n_estimators() < 50,
"Expected early stopping, but got {} estimators",
model.n_estimators()
);
}
#[test]
fn test_adaboost_single_class_error() {
use linfa::DatasetBase;
use ndarray::Array2;
let records =
Array2::from_shape_vec((4, 2), vec![0.0, 0.0, 0.1, 0.1, 0.2, 0.2, 0.3, 0.3]).unwrap();
let targets = ndarray::array![0, 0, 0, 0]; let dataset = DatasetBase::new(records, targets);
let rng = SmallRng::seed_from_u64(42);
let result = AdaBoostParams::new_fixed_rng(DecisionTree::params(), rng)
.n_estimators(10)
.fit(&dataset);
assert!(result.is_err(), "Should fail with single class dataset");
}
#[test]
fn test_adaboost_classes_method() {
let mut rng = SmallRng::seed_from_u64(42);
let (train, _) = linfa_datasets::iris()
.shuffle(&mut rng)
.split_with_ratio(0.8);
let model = AdaBoostParams::new_fixed_rng(DecisionTree::params().max_depth(Some(1)), rng)
.n_estimators(10)
.fit(&train)
.unwrap();
let classes = &model.classes;
assert_eq!(classes.len(), 3, "Iris has 3 classes");
assert_eq!(classes, &vec![0, 1, 2], "Classes should be [0, 1, 2]");
}
}