#![allow(non_snake_case)]
use crate::utils::*;
use scirs2_core::ndarray::{s, Array1, Array2, ArrayView2, Axis};
use sklears_core::{
error::{Result as SklResult, SklearsError},
traits::{Estimator, Fit, Predict, Untrained},
types::Float,
};
#[derive(Debug, Clone)]
pub struct ClassifierChain<S = Untrained> {
state: S,
order: Option<Vec<usize>>,
cv: Option<usize>,
random_state: Option<u64>,
}
impl ClassifierChain<Untrained> {
pub fn new() -> Self {
Self {
state: Untrained,
order: None,
cv: None,
random_state: None,
}
}
pub fn order(mut self, order: Vec<usize>) -> Self {
self.order = Some(order);
self
}
pub fn cv(mut self, cv: usize) -> Self {
self.cv = Some(cv);
self
}
pub fn random_state(mut self, random_state: u64) -> Self {
self.random_state = Some(random_state);
self
}
}
impl Default for ClassifierChain<Untrained> {
fn default() -> Self {
Self::new()
}
}
impl Estimator for ClassifierChain<Untrained> {
type Config = ();
type Error = SklearsError;
type Float = Float;
fn config(&self) -> &Self::Config {
&()
}
}
impl ClassifierChain<Untrained> {
pub fn fit_simple(
self,
X: &ArrayView2<'_, Float>,
y: &Array2<i32>,
) -> SklResult<ClassifierChain<ClassifierChainTrained>> {
let (n_samples, n_features) = X.dim();
let n_labels = y.ncols();
if n_samples != y.nrows() {
return Err(SklearsError::InvalidInput(
"X and y must have the same number of samples".to_string(),
));
}
let order = self
.order
.clone()
.unwrap_or_else(|| (0..n_labels).collect());
if order.len() != n_labels {
return Err(SklearsError::InvalidInput(
"Chain order must contain all label indices".to_string(),
));
}
let mut models = Vec::new();
let mut current_features = X.to_owned();
for (i, &label_idx) in order.iter().enumerate() {
let y_binary = y.column(label_idx).to_owned();
let model = train_binary_classifier(¤t_features.view(), &y_binary)?;
models.push(model);
if i < order.len() - 1 {
let predictions = predict_binary_classifier(¤t_features.view(), &models[i]);
let n_current_features = current_features.ncols();
let mut new_features = Array2::<Float>::zeros((n_samples, n_current_features + 1));
new_features
.slice_mut(s![.., ..n_current_features])
.assign(¤t_features);
for j in 0..n_samples {
new_features[[j, n_current_features]] = predictions[j] as Float;
}
current_features = new_features;
}
}
let trained_state = ClassifierChainTrained {
models,
order,
n_features,
n_labels,
};
Ok(ClassifierChain {
state: trained_state,
order: self.order,
cv: self.cv,
random_state: self.random_state,
})
}
}
impl Fit<ArrayView2<'_, Float>, Array2<i32>, ClassifierChainTrained>
for ClassifierChain<Untrained>
{
type Fitted = ClassifierChain<ClassifierChainTrained>;
fn fit(self, X: &ArrayView2<'_, Float>, y: &Array2<i32>) -> SklResult<Self::Fitted> {
self.fit_simple(X, y)
}
}
#[derive(Debug, Clone)]
pub struct ClassifierChainTrained {
models: Vec<SimpleBinaryModel>,
order: Vec<usize>,
n_features: usize,
n_labels: usize,
}
impl Predict<ArrayView2<'_, Float>, Array2<i32>> for ClassifierChain<ClassifierChainTrained> {
fn predict(&self, X: &ArrayView2<'_, Float>) -> SklResult<Array2<i32>> {
let (n_samples, n_features) = X.dim();
if n_features != self.state.n_features {
return Err(SklearsError::InvalidInput(
"X has different number of features than training data".to_string(),
));
}
let mut predictions = Array2::<i32>::zeros((n_samples, self.state.n_labels));
let mut current_features = X.to_owned();
for (i, &label_idx) in self.state.order.iter().enumerate() {
let model = &self.state.models[i];
let label_predictions = predict_binary_classifier(¤t_features.view(), model);
for j in 0..n_samples {
predictions[[j, label_idx]] = label_predictions[j];
}
if i < self.state.order.len() - 1 {
let n_current_features = current_features.ncols();
let mut new_features = Array2::<Float>::zeros((n_samples, n_current_features + 1));
new_features
.slice_mut(s![.., ..n_current_features])
.assign(¤t_features);
for j in 0..n_samples {
new_features[[j, n_current_features]] = label_predictions[j] as Float;
}
current_features = new_features;
}
}
Ok(predictions)
}
}
impl ClassifierChain<ClassifierChainTrained> {
pub fn predict_proba(&self, X: &ArrayView2<'_, Float>) -> SklResult<Array2<Float>> {
let (n_samples, n_features) = X.dim();
if n_features != self.state.n_features {
return Err(SklearsError::InvalidInput(
"X has different number of features than training data".to_string(),
));
}
let mut probabilities = Array2::<Float>::zeros((n_samples, self.state.n_labels));
let mut current_features = X.to_owned();
for (i, &label_idx) in self.state.order.iter().enumerate() {
let model = &self.state.models[i];
let label_probabilities = predict_binary_probabilities(¤t_features.view(), model);
for j in 0..n_samples {
probabilities[[j, label_idx]] = label_probabilities[j];
}
if i < self.state.order.len() - 1 {
let label_predictions =
label_probabilities.mapv(|p| if p > 0.5 { 1.0 } else { 0.0 });
let n_current_features = current_features.ncols();
let mut new_features = Array2::<Float>::zeros((n_samples, n_current_features + 1));
new_features
.slice_mut(s![.., ..n_current_features])
.assign(¤t_features);
for j in 0..n_samples {
new_features[[j, n_current_features]] = label_predictions[j];
}
current_features = new_features;
}
}
Ok(probabilities)
}
pub fn chain_order(&self) -> &[usize] {
&self.state.order
}
pub fn n_models(&self) -> usize {
self.state.models.len()
}
pub fn n_targets(&self) -> usize {
self.state.n_labels
}
pub fn predict_simple(&self, X: &ArrayView2<'_, Float>) -> SklResult<Array2<i32>> {
self.predict(X)
}
pub fn predict_monte_carlo(
&self,
X: &ArrayView2<'_, Float>,
n_samples: usize,
_random_state: Option<u64>,
) -> SklResult<Array2<Float>> {
if n_samples == 0 {
return Err(SklearsError::InvalidInput(
"n_samples must be greater than 0".to_string(),
));
}
self.predict_proba(X)
}
pub fn predict_monte_carlo_labels(
&self,
X: &ArrayView2<'_, Float>,
n_samples: usize,
_random_state: Option<u64>,
) -> SklResult<Array2<i32>> {
if n_samples == 0 {
return Err(SklearsError::InvalidInput(
"n_samples must be greater than 0".to_string(),
));
}
self.predict(X)
}
}
#[derive(Debug, Clone)]
pub struct RegressorChain<S = Untrained> {
state: S,
order: Option<Vec<usize>>,
cv: Option<usize>,
random_state: Option<u64>,
}
impl RegressorChain<Untrained> {
pub fn new() -> Self {
Self {
state: Untrained,
order: None,
cv: None,
random_state: None,
}
}
pub fn order(mut self, order: Vec<usize>) -> Self {
self.order = Some(order);
self
}
pub fn cv(mut self, cv: usize) -> Self {
self.cv = Some(cv);
self
}
pub fn random_state(mut self, random_state: u64) -> Self {
self.random_state = Some(random_state);
self
}
}
impl Default for RegressorChain<Untrained> {
fn default() -> Self {
Self::new()
}
}
impl Estimator for RegressorChain<Untrained> {
type Config = ();
type Error = SklearsError;
type Float = Float;
fn config(&self) -> &Self::Config {
&()
}
}
impl RegressorChain<Untrained> {
pub fn fit_simple(
self,
X: &ArrayView2<'_, Float>,
y: &Array2<Float>,
) -> SklResult<RegressorChain<RegressorChainTrained>> {
let (n_samples, n_features) = X.dim();
let n_targets = y.ncols();
if n_samples != y.nrows() {
return Err(SklearsError::InvalidInput(
"X and y must have the same number of samples".to_string(),
));
}
let order = self
.order
.clone()
.unwrap_or_else(|| (0..n_targets).collect());
if order.len() != n_targets {
return Err(SklearsError::InvalidInput(
"Chain order must contain all target indices".to_string(),
));
}
let mut models = Vec::new();
let mut current_features = X.to_owned();
for (i, &target_idx) in order.iter().enumerate() {
let y_target = y.column(target_idx).to_owned();
let model = train_simple_linear_classifier(¤t_features.view(), &y_target)?;
models.push(model);
if i < order.len() - 1 {
let predictions = predict_simple_linear(¤t_features.view(), &models[i]);
let n_current_features = current_features.ncols();
let mut new_features = Array2::<Float>::zeros((n_samples, n_current_features + 1));
new_features
.slice_mut(s![.., ..n_current_features])
.assign(¤t_features);
for j in 0..n_samples {
new_features[[j, n_current_features]] = predictions[j];
}
current_features = new_features;
}
}
let trained_state = RegressorChainTrained {
models,
order,
n_features,
n_targets,
};
Ok(RegressorChain {
state: trained_state,
order: self.order,
cv: self.cv,
random_state: self.random_state,
})
}
}
impl Fit<ArrayView2<'_, Float>, Array2<Float>, RegressorChainTrained>
for RegressorChain<Untrained>
{
type Fitted = RegressorChain<RegressorChainTrained>;
fn fit(self, X: &ArrayView2<'_, Float>, y: &Array2<Float>) -> SklResult<Self::Fitted> {
self.fit_simple(X, y)
}
}
#[derive(Debug, Clone)]
pub struct RegressorChainTrained {
models: Vec<SimpleLinearClassifier>,
order: Vec<usize>,
n_features: usize,
n_targets: usize,
}
impl Predict<ArrayView2<'_, Float>, Array2<Float>> for RegressorChain<RegressorChainTrained> {
fn predict(&self, X: &ArrayView2<'_, Float>) -> SklResult<Array2<Float>> {
let (n_samples, n_features) = X.dim();
if n_features != self.state.n_features {
return Err(SklearsError::InvalidInput(
"X has different number of features than training data".to_string(),
));
}
let mut predictions = Array2::<Float>::zeros((n_samples, self.state.n_targets));
let mut current_features = X.to_owned();
for (i, &target_idx) in self.state.order.iter().enumerate() {
let model = &self.state.models[i];
let target_predictions = predict_simple_linear(¤t_features.view(), model);
for j in 0..n_samples {
predictions[[j, target_idx]] = target_predictions[j];
}
if i < self.state.order.len() - 1 {
let n_current_features = current_features.ncols();
let mut new_features = Array2::<Float>::zeros((n_samples, n_current_features + 1));
new_features
.slice_mut(s![.., ..n_current_features])
.assign(¤t_features);
for j in 0..n_samples {
new_features[[j, n_current_features]] = target_predictions[j];
}
current_features = new_features;
}
}
Ok(predictions)
}
}
impl RegressorChain<RegressorChainTrained> {
pub fn chain_order(&self) -> &[usize] {
&self.state.order
}
pub fn n_models(&self) -> usize {
self.state.models.len()
}
pub fn get_model(&self, index: usize) -> Option<&SimpleLinearClassifier> {
self.state.models.get(index)
}
pub fn n_targets(&self) -> usize {
self.state.n_targets
}
pub fn predict_simple(&self, X: &ArrayView2<'_, Float>) -> SklResult<Array2<Float>> {
self.predict(X)
}
}
#[derive(Debug, Clone)]
pub struct EnsembleOfChains<S = Untrained> {
state: S,
n_chains: usize,
chain_method: ChainMethod,
random_state: Option<u64>,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum ChainMethod {
Random,
Fixed,
Bootstrap,
}
impl EnsembleOfChains<Untrained> {
pub fn new() -> Self {
Self {
state: Untrained,
n_chains: 10,
chain_method: ChainMethod::Random,
random_state: None,
}
}
pub fn n_chains(mut self, n_chains: usize) -> Self {
self.n_chains = n_chains;
self
}
pub fn chain_method(mut self, method: ChainMethod) -> Self {
self.chain_method = method;
self
}
pub fn random_state(mut self, random_state: u64) -> Self {
self.random_state = Some(random_state);
self
}
}
impl Default for EnsembleOfChains<Untrained> {
fn default() -> Self {
Self::new()
}
}
impl Estimator for EnsembleOfChains<Untrained> {
type Config = ();
type Error = SklearsError;
type Float = Float;
fn config(&self) -> &Self::Config {
&()
}
}
impl EnsembleOfChains<Untrained> {
pub fn fit_simple(
self,
X: &ArrayView2<'_, Float>,
y: &Array2<i32>,
) -> SklResult<EnsembleOfChains<EnsembleOfChainsTrained>> {
let (n_samples, n_features) = X.dim();
let n_labels = y.ncols();
if n_samples != y.nrows() {
return Err(SklearsError::InvalidInput(
"X and y must have the same number of samples".to_string(),
));
}
let mut chains = Vec::new();
let mut rng_state = self.random_state.unwrap_or(42);
for i in 0..self.n_chains {
let chain_order = match self.chain_method {
ChainMethod::Random => {
let mut order: Vec<usize> = (0..n_labels).collect();
for j in (1..order.len()).rev() {
rng_state = rng_state.wrapping_mul(1664525).wrapping_add(1013904223);
let k = (rng_state as usize) % (j + 1);
order.swap(j, k);
}
order
}
ChainMethod::Fixed => {
let mut order: Vec<usize> = (0..n_labels).collect();
order.rotate_left(i % n_labels);
order
}
ChainMethod::Bootstrap => {
let mut order: Vec<usize> = (0..n_labels).collect();
for j in (1..order.len()).rev() {
rng_state = rng_state.wrapping_mul(1664525).wrapping_add(1013904223);
let k = (rng_state as usize) % (j + 1);
order.swap(j, k);
}
order
}
};
let chain = ClassifierChain::new()
.order(chain_order)
.random_state(rng_state);
let trained_chain = chain.fit_simple(X, y)?;
chains.push(trained_chain);
rng_state = rng_state.wrapping_add(1);
}
let trained_state = EnsembleOfChainsTrained {
chains,
n_features,
n_labels,
};
Ok(EnsembleOfChains {
state: trained_state,
n_chains: self.n_chains,
chain_method: self.chain_method,
random_state: self.random_state,
})
}
}
impl Fit<ArrayView2<'_, Float>, Array2<i32>, EnsembleOfChainsTrained>
for EnsembleOfChains<Untrained>
{
type Fitted = EnsembleOfChains<EnsembleOfChainsTrained>;
fn fit(self, X: &ArrayView2<'_, Float>, y: &Array2<i32>) -> SklResult<Self::Fitted> {
self.fit_simple(X, y)
}
}
#[derive(Debug, Clone)]
pub struct EnsembleOfChainsTrained {
chains: Vec<ClassifierChain<ClassifierChainTrained>>,
n_features: usize,
n_labels: usize,
}
impl Predict<ArrayView2<'_, Float>, Array2<i32>> for EnsembleOfChains<EnsembleOfChainsTrained> {
fn predict(&self, X: &ArrayView2<'_, Float>) -> SklResult<Array2<i32>> {
let (n_samples, n_features) = X.dim();
if n_features != self.state.n_features {
return Err(SklearsError::InvalidInput(
"X has different number of features than training data".to_string(),
));
}
let mut all_predictions = Vec::new();
for chain in &self.state.chains {
let predictions = chain.predict(X)?;
all_predictions.push(predictions);
}
let mut final_predictions = Array2::<i32>::zeros((n_samples, self.state.n_labels));
for i in 0..n_samples {
for j in 0..self.state.n_labels {
let mut votes = 0;
for predictions in &all_predictions {
votes += predictions[[i, j]];
}
final_predictions[[i, j]] = if votes > (self.state.chains.len() as i32) / 2 {
1
} else {
0
};
}
}
Ok(final_predictions)
}
}
impl EnsembleOfChains<EnsembleOfChainsTrained> {
pub fn predict_proba(&self, X: &ArrayView2<'_, Float>) -> SklResult<Array2<Float>> {
let (n_samples, n_features) = X.dim();
if n_features != self.state.n_features {
return Err(SklearsError::InvalidInput(
"X has different number of features than training data".to_string(),
));
}
let mut all_probabilities = Vec::new();
for chain in &self.state.chains {
let probabilities = chain.predict_proba(X)?;
all_probabilities.push(probabilities);
}
let mut final_probabilities = Array2::<Float>::zeros((n_samples, self.state.n_labels));
for i in 0..n_samples {
for j in 0..self.state.n_labels {
let mut prob_sum = 0.0;
for probabilities in &all_probabilities {
prob_sum += probabilities[[i, j]];
}
final_probabilities[[i, j]] = prob_sum / self.state.chains.len() as Float;
}
}
Ok(final_probabilities)
}
pub fn n_chains(&self) -> usize {
self.state.chains.len()
}
pub fn get_chain(&self, index: usize) -> Option<&ClassifierChain<ClassifierChainTrained>> {
self.state.chains.get(index)
}
pub fn chain_diversity(&self) -> Float {
if self.state.chains.len() < 2 {
return 0.0;
}
let mut diversity_sum = 0.0;
let mut count = 0;
for i in 0..self.state.chains.len() {
for j in (i + 1)..self.state.chains.len() {
let order1 = self.state.chains[i].chain_order();
let order2 = self.state.chains[j].chain_order();
let mut agreements = 0;
for k in 0..order1.len() {
if order1[k] == order2[k] {
agreements += 1;
}
}
let similarity = agreements as Float / order1.len() as Float;
diversity_sum += 1.0 - similarity;
count += 1;
}
}
if count > 0 {
diversity_sum / count as Float
} else {
0.0
}
}
pub fn n_targets(&self) -> usize {
self.state.n_labels
}
pub fn predict_simple(&self, X: &ArrayView2<'_, Float>) -> SklResult<Array2<i32>> {
self.predict(X)
}
pub fn predict_proba_simple(&self, X: &ArrayView2<'_, Float>) -> SklResult<Array2<Float>> {
self.predict_proba(X)
}
}
#[derive(Debug, Clone)]
pub struct BayesianClassifierChain<S = Untrained> {
state: S,
pub order: Option<Vec<usize>>,
pub n_samples: usize,
pub prior_strength: Float,
pub random_state: Option<u64>,
}
impl BayesianClassifierChain<Untrained> {
pub fn new() -> Self {
Self {
state: Untrained,
order: None,
n_samples: 100,
prior_strength: 1.0,
random_state: None,
}
}
pub fn order(mut self, order: Vec<usize>) -> Self {
self.order = Some(order);
self
}
pub fn n_samples(mut self, n_samples: usize) -> Self {
self.n_samples = n_samples;
self
}
pub fn prior_strength(mut self, prior_strength: Float) -> Self {
self.prior_strength = prior_strength;
self
}
pub fn random_state(mut self, random_state: u64) -> Self {
self.random_state = Some(random_state);
self
}
}
impl Default for BayesianClassifierChain<Untrained> {
fn default() -> Self {
Self::new()
}
}
impl Estimator for BayesianClassifierChain<Untrained> {
type Config = ();
type Error = SklearsError;
type Float = Float;
fn config(&self) -> &Self::Config {
&()
}
}
impl BayesianClassifierChain<Untrained> {
#[allow(non_snake_case)]
pub fn fit_simple(
self,
X: &ArrayView2<'_, Float>,
y: &Array2<i32>,
) -> SklResult<BayesianClassifierChain<BayesianClassifierChainTrained>> {
let (n_samples, n_features) = X.dim();
let n_labels = y.ncols();
if n_samples != y.nrows() {
return Err(SklearsError::InvalidInput(
"X and y must have the same number of samples".to_string(),
));
}
for &val in y.iter() {
if val != 0 && val != 1 {
return Err(SklearsError::InvalidInput(
"y must contain only binary values (0 or 1)".to_string(),
));
}
}
let order = self
.order
.clone()
.unwrap_or_else(|| (0..n_labels).collect());
if order.len() != n_labels {
return Err(SklearsError::InvalidInput(
"Chain order must contain all label indices".to_string(),
));
}
let feature_means = X
.mean_axis(Axis(0))
.expect("array should have elements for mean computation");
let feature_stds = X.std_axis(Axis(0), 0.0);
let X_standardized = standardize_features_simple(X, &feature_means, &feature_stds);
let mut bayesian_models = Vec::new();
let mut current_features = X_standardized;
for (i, &label_idx) in order.iter().enumerate() {
let y_binary = y.column(label_idx).to_owned();
let model = train_bayesian_binary_classifier(
¤t_features,
&y_binary,
self.prior_strength,
)?;
bayesian_models.push(model);
if i < order.len() - 1 {
let predictions =
predict_bayesian_mean(¤t_features.view(), &bayesian_models[i]);
let n_current_features = current_features.ncols();
let mut new_features = Array2::<Float>::zeros((n_samples, n_current_features + 1));
new_features
.slice_mut(s![.., ..n_current_features])
.assign(¤t_features);
for j in 0..n_samples {
new_features[[j, n_current_features]] = predictions[j];
}
current_features = new_features;
}
}
let trained_state = BayesianClassifierChainTrained {
bayesian_models,
order,
n_features,
n_labels,
feature_means,
feature_stds,
};
Ok(BayesianClassifierChain {
state: trained_state,
order: None,
n_samples: self.n_samples,
prior_strength: self.prior_strength,
random_state: self.random_state,
})
}
}
impl Fit<ArrayView2<'_, Float>, Array2<i32>, BayesianClassifierChainTrained>
for BayesianClassifierChain<Untrained>
{
type Fitted = BayesianClassifierChain<BayesianClassifierChainTrained>;
fn fit(self, X: &ArrayView2<'_, Float>, y: &Array2<i32>) -> SklResult<Self::Fitted> {
self.fit_simple(X, y)
}
}
#[derive(Debug, Clone)]
pub struct BayesianClassifierChainTrained {
bayesian_models: Vec<BayesianBinaryModel>,
order: Vec<usize>,
#[allow(dead_code)]
n_features: usize,
n_labels: usize,
feature_means: Array1<Float>,
feature_stds: Array1<Float>,
}
impl Predict<ArrayView2<'_, Float>, Array2<i32>>
for BayesianClassifierChain<BayesianClassifierChainTrained>
{
#[allow(non_snake_case)]
fn predict(&self, X: &ArrayView2<'_, Float>) -> SklResult<Array2<i32>> {
let (n_samples, n_features) = X.dim();
if n_features != self.state.feature_means.len() {
return Err(SklearsError::InvalidInput(
"X has different number of features than training data".to_string(),
));
}
let X_standardized =
standardize_features_simple(X, &self.state.feature_means, &self.state.feature_stds);
let mut predictions = Array2::<i32>::zeros((n_samples, self.state.n_labels));
let mut current_features = X_standardized;
for (chain_pos, &label_idx) in self.state.order.iter().enumerate() {
let model = &self.state.bayesian_models[chain_pos];
let label_predictions = predict_bayesian_binary(¤t_features.view(), model);
for i in 0..n_samples {
predictions[[i, label_idx]] = if label_predictions[i] > 0.5 { 1 } else { 0 };
}
if chain_pos < self.state.order.len() - 1 {
let mut new_features =
Array2::<Float>::zeros((n_samples, current_features.ncols() + 1));
new_features
.slice_mut(s![.., ..current_features.ncols()])
.assign(¤t_features);
for i in 0..n_samples {
new_features[[i, current_features.ncols()]] =
predictions[[i, label_idx]] as Float;
}
current_features = new_features;
}
}
Ok(predictions)
}
}
impl BayesianClassifierChain<BayesianClassifierChainTrained> {
#[allow(non_snake_case)]
pub fn predict_uncertainty(&self, X: &ArrayView2<'_, Float>) -> SklResult<Array2<Float>> {
let (n_samples, n_features) = X.dim();
if n_features != self.state.feature_means.len() {
return Err(SklearsError::InvalidInput(
"X has different number of features than training data".to_string(),
));
}
let X_standardized =
standardize_features_simple(X, &self.state.feature_means, &self.state.feature_stds);
let mut uncertainties = Array2::<Float>::zeros((n_samples, self.state.n_labels));
let mut current_features = X_standardized;
for (chain_pos, &label_idx) in self.state.order.iter().enumerate() {
let model = &self.state.bayesian_models[chain_pos];
let (means, variances) = predict_bayesian_uncertainty(¤t_features.view(), model)?;
for i in 0..n_samples {
uncertainties[[i, label_idx]] = variances[i];
}
if chain_pos < self.state.order.len() - 1 {
let mut new_features =
Array2::<Float>::zeros((n_samples, current_features.ncols() + 1));
new_features
.slice_mut(s![.., ..current_features.ncols()])
.assign(¤t_features);
for i in 0..n_samples {
new_features[[i, current_features.ncols()]] = means[i];
}
current_features = new_features;
}
}
Ok(uncertainties)
}
#[allow(non_snake_case)]
pub fn predict_proba(&self, X: &ArrayView2<'_, Float>) -> SklResult<Array2<Float>> {
let (n_samples, n_features) = X.dim();
if n_features != self.state.feature_means.len() {
return Err(SklearsError::InvalidInput(
"X has different number of features than training data".to_string(),
));
}
let X_standardized =
standardize_features_simple(X, &self.state.feature_means, &self.state.feature_stds);
let mut probabilities = Array2::<Float>::zeros((n_samples, self.state.n_labels));
let mut current_features = X_standardized;
for (chain_pos, &label_idx) in self.state.order.iter().enumerate() {
let model = &self.state.bayesian_models[chain_pos];
let label_probabilities = predict_bayesian_binary(¤t_features.view(), model);
for i in 0..n_samples {
probabilities[[i, label_idx]] = label_probabilities[i];
}
if chain_pos < self.state.order.len() - 1 {
let mut new_features =
Array2::<Float>::zeros((n_samples, current_features.ncols() + 1));
new_features
.slice_mut(s![.., ..current_features.ncols()])
.assign(¤t_features);
for i in 0..n_samples {
new_features[[i, current_features.ncols()]] = label_probabilities[i];
}
current_features = new_features;
}
}
Ok(probabilities)
}
pub fn chain_order(&self) -> &[usize] {
&self.state.order
}
pub fn n_models(&self) -> usize {
self.state.bayesian_models.len()
}
pub fn model_posterior_stats(
&self,
model_idx: usize,
) -> Option<(&Array1<Float>, &Array2<Float>)> {
self.state
.bayesian_models
.get(model_idx)
.map(|model| (&model.weight_mean, &model.weight_cov))
}
pub fn order(&self) -> &[usize] {
&self.state.order
}
}
fn predict_binary_classifier(X: &ArrayView2<Float>, model: &SimpleBinaryModel) -> Array1<i32> {
let raw_scores = X.dot(&model.weights) + model.bias;
raw_scores.mapv(|x| if x > 0.0 { 1 } else { 0 })
}