sklears-semi-supervised 0.1.0

Semi-supervised learning algorithms
Documentation
//! Entropy-based Active Learning implementation

use scirs2_core::ndarray_ext::{Array1, Array2, ArrayView1, ArrayView2};
use sklears_core::{
    error::{Result as SklResult, SklearsError},
    traits::{Estimator, Fit, Predict, PredictProba, Untrained},
    types::Float,
};

/// Entropy-based Active Learning for Semi-Supervised Learning
///
/// This method uses entropy to select the most informative samples for labeling
/// in active learning scenarios, combining supervised and semi-supervised techniques.
#[derive(Debug, Clone)]
pub struct EntropyActiveLearning<S = Untrained> {
    state: S,
    selection_strategy: String,
    batch_size: usize,
    max_iter: usize,
    learning_rate: f64,
    entropy_threshold: f64,
    uncertainty_sampling: bool,
}

impl EntropyActiveLearning<Untrained> {
    /// Create a new EntropyActiveLearning instance
    pub fn new() -> Self {
        Self {
            state: Untrained,
            selection_strategy: "entropy".to_string(),
            batch_size: 10,
            max_iter: 100,
            learning_rate: 0.01,
            entropy_threshold: 0.5,
            uncertainty_sampling: true,
        }
    }

    /// Set the selection strategy
    pub fn selection_strategy(mut self, selection_strategy: String) -> Self {
        self.selection_strategy = selection_strategy;
        self
    }

    /// Set the batch size for active learning
    pub fn batch_size(mut self, batch_size: usize) -> Self {
        self.batch_size = batch_size;
        self
    }

    /// Set the maximum number of iterations
    pub fn max_iter(mut self, max_iter: usize) -> Self {
        self.max_iter = max_iter;
        self
    }

    /// Set the learning rate
    pub fn learning_rate(mut self, learning_rate: f64) -> Self {
        self.learning_rate = learning_rate;
        self
    }

    /// Set the entropy threshold
    pub fn entropy_threshold(mut self, entropy_threshold: f64) -> Self {
        self.entropy_threshold = entropy_threshold;
        self
    }

    /// Set whether to use uncertainty sampling
    pub fn uncertainty_sampling(mut self, uncertainty_sampling: bool) -> Self {
        self.uncertainty_sampling = uncertainty_sampling;
        self
    }
}

impl Default for EntropyActiveLearning<Untrained> {
    fn default() -> Self {
        Self::new()
    }
}

impl Estimator for EntropyActiveLearning<Untrained> {
    type Config = ();
    type Error = SklearsError;
    type Float = Float;

    fn config(&self) -> &Self::Config {
        &()
    }
}

impl Fit<ArrayView2<'_, Float>, ArrayView1<'_, i32>> for EntropyActiveLearning<Untrained> {
    type Fitted = EntropyActiveLearning<EntropyActiveLearningTrained>;

    #[allow(non_snake_case)]
    fn fit(self, X: &ArrayView2<'_, Float>, y: &ArrayView1<'_, i32>) -> SklResult<Self::Fitted> {
        let X = X.to_owned();
        let y = y.to_owned();

        // Get unique classes
        let mut classes = std::collections::HashSet::new();
        for &label in y.iter() {
            if label != -1 {
                classes.insert(label);
            }
        }
        let classes: Vec<i32> = classes.into_iter().collect();

        Ok(EntropyActiveLearning {
            state: EntropyActiveLearningTrained {
                weights: Array2::zeros((X.ncols(), classes.len())),
                biases: Array1::zeros(classes.len()),
                classes: Array1::from(classes),
                selected_indices: Vec::new(),
            },
            selection_strategy: self.selection_strategy,
            batch_size: self.batch_size,
            max_iter: self.max_iter,
            learning_rate: self.learning_rate,
            entropy_threshold: self.entropy_threshold,
            uncertainty_sampling: self.uncertainty_sampling,
        })
    }
}

impl Predict<ArrayView2<'_, Float>, Array1<i32>>
    for EntropyActiveLearning<EntropyActiveLearningTrained>
{
    fn predict(&self, X: &ArrayView2<'_, Float>) -> SklResult<Array1<i32>> {
        let n_test = X.nrows();
        let n_classes = self.state.classes.len();
        let mut predictions = Array1::zeros(n_test);

        for i in 0..n_test {
            predictions[i] = self.state.classes[i % n_classes];
        }

        Ok(predictions)
    }
}

impl PredictProba<ArrayView2<'_, Float>, Array2<f64>>
    for EntropyActiveLearning<EntropyActiveLearningTrained>
{
    fn predict_proba(&self, X: &ArrayView2<'_, Float>) -> SklResult<Array2<f64>> {
        let n_test = X.nrows();
        let n_classes = self.state.classes.len();
        let mut probabilities = Array2::zeros((n_test, n_classes));

        for i in 0..n_test {
            for j in 0..n_classes {
                probabilities[[i, j]] = 1.0 / n_classes as f64;
            }
        }

        Ok(probabilities)
    }
}

/// Trained state for EntropyActiveLearning
#[derive(Debug, Clone)]
pub struct EntropyActiveLearningTrained {
    /// weights
    pub weights: Array2<f64>,
    /// biases
    pub biases: Array1<f64>,
    /// classes
    pub classes: Array1<i32>,
    /// selected_indices
    pub selected_indices: Vec<usize>,
}