pub mod data_loader;
pub mod layers;
pub mod linear_regression;
pub mod logistic_regression;
pub mod model;
pub mod multilayer_perceptron;
pub mod support_vector_machine;
pub mod k_means;
#[cfg(test)]
mod tests {
use crate::layers::layer::LinearLayer;
use super::*;
use data_loader::*;
use layers::layer::Layer;
use linear_regression::*;
use logistic_regression::*;
use model::activation_functions::{ActivationFunction, ActivationFunctionType};
use model::kernel_functions::{KernelFunction, KernelFunctionType};
use model::loss_functions::{LossFunction, LossFunctionType};
use model::model::SupervisedModeller;
use multilayer_perceptron::*;
use polars::prelude::*;
use rand::random;
use std::path::Path;
use support_vector_machine::*;
use model::model::ClusterModeller;
use k_means::*;
#[test]
fn test_load_csv() {
let path: &Path = Path::new("test/loadTest.csv");
let result: Result<DataFrame, PolarsError> = data_loader::data_loader_util::load_csv(path);
println!("{:?}", result);
assert!(result.is_ok());
let df: DataFrame = result.expect("Failed to load CSV file");
assert!(!df.is_empty());
}
#[test]
fn test_transform_by_col_custom_funct() {
let mut df: DataFrame =
DataFrame::new(vec![Series::new("col1", &[1.0, 2.0, 3.0])]).unwrap();
df = df.transform_cols(&["col1"], |s: &Series| s * 2).unwrap();
let expected_result: DataFrame =
DataFrame::new(vec![Series::new("col1", &[2.0, 4.0, 6.0])]).unwrap();
assert_eq!(df, expected_result);
}
#[test]
fn test_transform_by_col_identity_funct() {
let mut df: DataFrame =
DataFrame::new(vec![Series::new("col1", &[1.0, 2.0, 3.0])]).unwrap();
df = df
.transform_cols(&["col1"], transformer_functions::identity())
.unwrap();
let expected_result: DataFrame =
DataFrame::new(vec![Series::new("col1", &[1.0, 2.0, 3.0])]).unwrap();
assert_eq!(df, expected_result);
}
#[test]
fn test_transform_by_col_power_funct() {
let mut df: DataFrame =
DataFrame::new(vec![Series::new("col1", &[1.0, 2.0, 3.0])]).unwrap();
df = df
.transform_cols(&["col1"], transformer_functions::power(2.0))
.unwrap();
let expected_result: DataFrame =
DataFrame::new(vec![Series::new("col1", &[1.0, 4.0, 9.0])]).unwrap();
assert_eq!(df, expected_result);
}
#[test]
fn test_transform_by_col_log_funct() {
let mut df: DataFrame =
DataFrame::new(vec![Series::new("col1", &[1.0, 2.0, 4.0])]).unwrap();
df = df
.transform_cols(&["col1"], transformer_functions::log(2.0))
.unwrap();
let expected_result: DataFrame =
DataFrame::new(vec![Series::new("col1", &[0.0, 1.0, 2.0])]).unwrap();
assert_eq!(df, expected_result);
}
#[test]
fn test_split() {
let df: DataFrame =
DataFrame::new(vec![Series::new("col1", &[1.0, 2.0, 3.0, 4.0, 5.0])]).unwrap();
let (train, test) = df.split(0.8).unwrap();
assert_eq!(train.height(), 4);
assert_eq!(test.height(), 1);
}
#[test]
fn test_z_norm_cols() {
let mut df: DataFrame =
DataFrame::new(vec![Series::new("col1", &[1.0, 2.0, 3.0, 4.0, 5.0])]).unwrap();
df = df.z_norm_cols(&["col1"]).unwrap();
let std: f64 = f64::sqrt(2.0);
let mean: f64 = 3.0;
let expected_result: DataFrame = DataFrame::new(vec![Series::new(
"col1",
&[
(1.0 - mean) / std,
(2.0 - mean) / std,
(3.0 - mean) / std,
(4.0 - mean) / std,
(5.0 - mean) / std,
],
)])
.unwrap();
assert_eq!(df, expected_result);
}
#[test]
fn test_chained_transformations() {
let mut df = DataFrame::new(vec![Series::new("col1", &[1.0, 2.0, 4.0])]).unwrap();
df = df
.transform_cols(&["col1"], transformer_functions::power(2.0))
.transform_cols(&["col1"], transformer_functions::log(2.0))
.transform_cols(&["col1"], transformer_functions::identity())
.z_norm_cols(&["col1"])
.unwrap();
let std: f64 = f64::sqrt(8.0 / 3.0);
let mean: f64 = 2.0;
let expected_result = DataFrame::new(vec![Series::new(
"col1",
&[(0.0 - mean) / std, (2.0 - mean) / std, (4.0 - mean) / std],
)])
.unwrap();
assert_eq!(df, expected_result);
}
#[test]
fn test_min_max_norm_cols() {
let mut df: DataFrame =
DataFrame::new(vec![Series::new("col1", &[1.0, 2.0, 3.0, 4.0, 5.0])]).unwrap();
df = df.min_max_norm_cols(&["col1"]).unwrap();
let expected_result: DataFrame =
DataFrame::new(vec![Series::new("col1", &[0.0, 0.25, 0.5, 0.75, 1.0])]).unwrap();
assert_eq!(df, expected_result);
}
#[test]
fn test_select_rows_all_existing() {
let df: DataFrame = DataFrame::new(vec![
Series::new("col1", &[1.0, 2.0, 3.0, 4.0, 5.0]),
Series::new("col2", &[1.0, 2.0, 3.0, 4.0, 5.0]),
])
.unwrap();
let selected_rows: DataFrame = DataFrame::select_rows(&df, vec![0, 2, 4]).unwrap();
let expected_result: DataFrame = DataFrame::new(vec![
Series::new("col1", &[1.0, 3.0, 5.0]),
Series::new("col2", &[1.0, 3.0, 5.0]),
])
.unwrap();
assert_eq!(selected_rows, expected_result);
}
#[test]
#[should_panic]
fn test_select_rows_idx_out_of_bounds() {
let df: DataFrame = DataFrame::new(vec![
Series::new("col1", &[1.0, 2.0, 3.0, 4.0, 5.0]),
Series::new("col2", &[1.0, 2.0, 3.0, 4.0, 5.0]),
])
.unwrap();
let _selected_rows: Result<DataFrame, PolarsError> = DataFrame::select_rows(&df, vec![5]);
}
#[test]
fn test_mean_squared_error_loss_zero() {
let x: Series = Series::new("x", &[1.0, 2.0, 3.0]);
let mse: f64 = LossFunctionType::MeanSquaredError.loss(&x, &x);
assert_eq!(mse, 0.0);
}
#[test]
fn test_mean_squared_error_loss_non_zero() {
let x: Series = Series::new("x", &[1.0, 2.0, 3.0]);
let y: Series = Series::new("y", &[4.0, 5.0, 6.0]);
let mse = LossFunctionType::MeanSquaredError.loss(&x, &y);
assert_eq!(mse, 9.0);
}
#[test]
fn test_mean_squared_error_gradient_zeros() {
let x: DataFrame = DataFrame::new(vec![
Series::new("x1", &[1.0, 2.0, 3.0]),
Series::new("x2", &[1.0, 2.0, 3.0]),
])
.unwrap();
let y: Series = Series::new("y", &[1.0, 2.0, 3.0]);
let gradient: Series = LossFunctionType::MeanSquaredError.gradient(&x, &y, &y);
assert_eq!(gradient, Series::new("gradients", &[0.0, 0.0]));
}
#[test]
fn test_mean_squared_error_gradient_non_zeros() {
let x: DataFrame = DataFrame::new(vec![
Series::new("x1", &[1.0, 2.0, 3.0]),
Series::new("x2", &[4.0, 5.0, 6.0]),
])
.unwrap();
let y: Series = Series::new("y", &[1.0, 2.0, 3.0]);
let y_pred: Series = Series::new("y_pred", &[4.0, 5.0, 6.0]);
let gradient: Series = LossFunctionType::MeanSquaredError.gradient(&x, &y, &y_pred);
assert_eq!(gradient, Series::new("gradients", &[12.0, 30.0]));
}
#[test]
fn test_linear_model_fit_predict_single_feature() {
let x: DataFrame = DataFrame::new(vec![Series::new("feature1", vec![1, 2, 3])]).unwrap();
let y: Series = Series::new("target", vec![10.0, 20.0, 30.0]);
let mut model: Linear = Linear::new();
assert!(model.fit(&x, &y, 1000, 0.1).is_ok());
let predictions: Series = model.predict(&x).unwrap();
println!("Predictions: {:?}", predictions);
println!("Actual values: {:?}", y);
assert_eq!(predictions.len(), y.len());
println!("Sum of predictions: {:?}", predictions.sum::<f64>());
println!("Sum of actual values: {:?}", y.sum::<f64>());
assert!(
(predictions.sum::<f64>().unwrap() - y.sum::<f64>().unwrap()).abs() < 1e-6,
"Sums do not match within epsilon"
);
assert_eq!(predictions.len(), y.len());
assert_eq!(predictions.sum::<f64>(), y.sum()); }
#[test]
fn test_linear_model_fit_predict_multiple_features() {
let x: DataFrame = DataFrame::new(vec![
Series::new("feature1", vec![1, 2, 3]),
Series::new("feature2", vec![1, 2, 3]),
])
.unwrap();
let y: Series = Series::new("target", vec![10.0, 20.0, 30.0]);
let mut model: Linear = Linear::new();
assert!(model.fit(&x, &y, 10000, 0.01).is_ok());
let predictions: Series = model.predict(&x).unwrap();
println!("Predictions: {:?}", predictions);
println!("Actual values: {:?}", y);
assert_eq!(predictions.len(), y.len());
println!("Sum of predictions: {:?}", predictions.sum::<f64>());
println!("Sum of actual values: {:?}", y.sum::<f64>());
assert!(
(predictions.sum::<f64>().unwrap() - y.sum::<f64>().unwrap()).abs() < 1e-6,
"Sums do not match within epsilon"
);
assert_eq!(predictions.len(), y.len());
assert_eq!(predictions.sum::<f64>(), y.sum()); }
#[test]
fn test_linear_model_accuracy_perfect_single_feature() {
let x: DataFrame = DataFrame::new(vec![Series::new("feature1", vec![1, 2, 3])]).unwrap();
let y: Series = Series::new("target", vec![10.0, 20.0, 30.0]);
let mut model: Linear = Linear::new();
assert!(model.fit(&x, &y, 1000, 0.1).is_ok());
let accuracy: f64 = model.accuracy(&x, &y).unwrap();
println!("Accuracy: {:?}", accuracy);
assert!(
(accuracy - 1.0).abs() < 1e-6,
"Accuracy does not match within epsilon"
);
}
#[test]
fn test_linear_model_accuracy_non_perfect_single_feature() {
let x: DataFrame =
DataFrame::new(vec![Series::new("feature1", vec![1, 2, 3, 4, 5])]).unwrap();
let y: Series = Series::new("target", vec![10.0, 25.0, 50.0, 56.0, 50.0]);
let mut model: Linear = Linear::new();
assert!(model.fit(&x, &y, 10000, 0.01).is_ok());
let accuracy: f64 = model.accuracy(&x, &y).unwrap();
println!("Accuracy: {:?}", accuracy);
assert!(
(accuracy - 0.758).abs() < 1e-3,
"Accuracy does not match within epsilon"
);
}
#[test]
fn test_linear_model_accuracy_perfect_multiple_features() {
let x: DataFrame = DataFrame::new(vec![
Series::new("feature1", vec![1, 2, 3]),
Series::new("feature2", vec![1, 2, 3]),
])
.unwrap();
let y: Series = Series::new("target", vec![10.0, 20.0, 30.0]);
let mut model: Linear = Linear::new();
assert!(model.fit(&x, &y, 10000, 0.01).is_ok());
let accuracy = model.accuracy(&x, &y).unwrap();
println!("Accuracy: {:?}", accuracy);
assert!(
(accuracy - 1.0).abs() < 1e-6,
"Accuracy does not match within epsilon"
);
}
#[test]
fn test_linear_model_accuracy_non_perfect_multiple_features() {
let x: DataFrame = DataFrame::new(vec![
Series::new("feature1", vec![1, 2, 3, 4, 5]),
Series::new("feature2", vec![1, 2, 3, 4, 5]),
])
.unwrap();
let y: Series = Series::new("target", vec![10.0, 25.0, 50.0, 56.0, 50.0]);
let mut model: Linear = Linear::new();
assert!(model.fit(&x, &y, 10000, 0.01).is_ok());
let accuracy = model.accuracy(&x, &y).unwrap();
println!("Accuracy: {:?}", accuracy);
assert!(
(accuracy - 0.758).abs() < 1e-3,
"Accuracy does not match within epsilon"
);
}
#[test]
fn test_logistic_model_fit_predict_single_feature() {
let x: DataFrame = DataFrame::new(vec![Series::new("feature1", vec![1, 2, 3])]).unwrap();
let y: Series = Series::new("target", vec![0.0, 1.0, 1.0]);
let mut model: Logistic = Logistic::new();
assert!(model.fit(&x, &y, 1000, 0.1).is_ok());
let predictions: Series = model.predict(&x).unwrap();
let predictions: Series = predictions
.f64()
.unwrap()
.apply(|value: Option<f64>| {
if value.is_nan() {
None
} else {
Some(value.unwrap().round())
}
})
.into_series();
println!("Predictions: {:?}", predictions);
println!("Actual values: {:?}", y);
assert_eq!(predictions.len(), y.len());
println!("Sum of predictions: {:?}", predictions.sum::<f64>());
println!("Sum of actual values: {:?}", y.sum::<f64>());
assert!(
(predictions.sum::<f64>().unwrap() - y.sum::<f64>().unwrap()).abs() < 1e-6,
"Sums do not match within epsilon"
);
assert_eq!(predictions.len(), y.len());
assert_eq!(predictions.sum::<f64>(), y.sum()); }
#[test]
fn test_logistic_model_fit_predict_multiple_features() {
let x: DataFrame = DataFrame::new(vec![
Series::new("feature1", vec![1, 3, 2]),
Series::new("feature2", vec![1, 3, 2]),
])
.unwrap();
let y: Series = Series::new("target", vec![1.0, 0.0, 1.0]);
let mut model: Logistic = Logistic::new();
assert!(model.fit(&x, &y, 10000, 0.01).is_ok());
let predictions: Series = model.predict(&x).unwrap();
let predictions: Series = Logistic::classify(&predictions, 0.5);
println!("Predictions: {:?}", predictions);
println!("Actual values: {:?}", y);
assert_eq!(predictions.len(), y.len());
println!("Sum of predictions: {:?}", predictions.sum::<f64>());
println!("Sum of actual values: {:?}", y.sum::<f64>());
assert!(
(predictions.sum::<f64>().unwrap() - y.sum::<f64>().unwrap()).abs() < 1e-6,
"Sums do not match within epsilon"
);
assert_eq!(predictions.len(), y.len());
assert_eq!(predictions.sum::<f64>(), y.sum()); }
#[test]
fn test_logistic_model_accuracy_perfect_single_feature() {
let x: DataFrame = DataFrame::new(vec![Series::new("feature1", vec![1, 2, 3])]).unwrap();
let y: Series = Series::new("target", vec![0.0, 1.0, 1.0]);
let mut model: Logistic = Logistic::new();
assert!(model.fit(&x, &y, 1000, 0.1).is_ok());
let accuracy: f64 = model.accuracy(&x, &y).unwrap();
println!("Accuracy: {:?}", accuracy);
assert!(
(accuracy - 1.0).abs() < 1e-6,
"Accuracy does not match within epsilon"
);
}
#[test]
fn test_logistic_model_accuracy_non_perfect_single_feature() {
let x: DataFrame =
DataFrame::new(vec![Series::new("feature1", vec![1, 2, 3, 4, 5])]).unwrap();
let y: Series = Series::new("target", vec![0.0, 1.0, 1.0, 0.0, 1.0]);
let mut model: Logistic = Logistic::new();
assert!(model.fit(&x, &y, 10000, 0.01).is_ok());
let accuracy: f64 = model.accuracy(&x, &y).unwrap();
println!("Accuracy: {:?}", accuracy);
assert!(
(accuracy - 0.8).abs() < 1e-6,
"Accuracy does not match within epsilon"
);
}
#[test]
fn test_logistic_model_accuracy_perfect_multiple_features() {
let x: DataFrame = DataFrame::new(vec![
Series::new("feature1", vec![1, 3, 2]),
Series::new("feature2", vec![1, 3, 2]),
])
.unwrap();
let y: Series = Series::new("target", vec![1.0, 0.0, 1.0]);
let mut model: Logistic = Logistic::new();
assert!(model.fit(&x, &y, 10000, 0.01).is_ok());
let accuracy: f64 = model.accuracy(&x, &y).unwrap();
println!("Accuracy: {:?}", accuracy);
assert!(
(accuracy - 1.0).abs() < 1e-6,
"Accuracy does not match within epsilon"
);
}
#[test]
fn test_logistic_model_accuracy_non_perfect_multiple_features() {
let x: DataFrame = DataFrame::new(vec![
Series::new("feature1", vec![1, 3, 2, 4, 5]),
Series::new("feature2", vec![1, 3, 2, 4, 5]),
])
.unwrap();
let y: Series = Series::new("target", vec![1.0, 0.0, 1.0, 0.0, 1.0]);
let mut model: Logistic = Logistic::new();
assert!(model.fit(&x, &y, 10000, 0.01).is_ok());
let accuracy: f64 = model.accuracy(&x, &y).unwrap();
println!("Accuracy: {:?}", accuracy);
assert!(
(accuracy - 0.6).abs() < 1e-6,
"Accuracy does not match within epsilon"
);
}
#[test]
fn test_activation_function_sigmoid() {
let x: Series = Series::new("x", &[1.0, 2.0, 3.0]);
let sigmoid: Series = ActivationFunctionType::Sigmoid.activate(&x);
assert_eq!(
sigmoid,
Series::new(
"activated_values",
&[
1.0 / (1.0 + f64::exp(-1.0)),
1.0 / (1.0 + f64::exp(-2.0)),
1.0 / (1.0 + f64::exp(-3.0))
]
)
);
}
#[test]
fn test_activation_function_identity() {
let x: Series = Series::new("x", &[1.0, 2.0, 3.0]);
let identity: Series = ActivationFunctionType::Identity.activate(&x);
assert_eq!(identity, Series::new("activated_values", &[1.0, 2.0, 3.0]));
}
#[test]
fn test_kernel_function_linear() {
let x: Series = Series::new("x", &[1.0, 2.0, 3.0]);
let linear_kernel: Series = KernelFunctionType::Linear.kernel(&x, &x);
assert_eq!(linear_kernel, Series::new("kernel", &[1.0, 4.0, 9.0]));
}
#[test]
fn test_kernel_function_polynomial() {
let x: Series = Series::new("x", &[1.0, 2.0, 3.0]);
let polynomial_kernel: Series = KernelFunctionType::Polynomial(2.0, 2.0).kernel(&x, &x);
assert_eq!(
polynomial_kernel,
Series::new("kernel", &[9.0, 36.0, 121.0])
);
}
#[test]
fn test_kernel_function_rbf() {
let x: Series = Series::new("x", &[1.0, 2.0, 3.0]);
let y: Series = Series::new("y", &[3.0, 2.0, 1.0]);
let rbf_kernel: Series = KernelFunctionType::RadialBasisFunction(2.0).kernel(&x, &y);
assert_eq!(
rbf_kernel,
Series::new("kernel", &[f64::exp(-8.0), f64::exp(0.0), f64::exp(-8.0)])
);
}
#[test]
fn test_hinge_loss_zero() {
let x: Series = Series::new("x", &[1.0, 2.0, 3.0]);
let hinge_loss: f64 = LossFunctionType::Hinge.loss(&x, &x);
assert_eq!(hinge_loss, 0.0);
}
#[test]
fn test_hinge_loss_non_zero() {
let y: Series = Series::new("y", &[1.0, 1.0, 1.0]);
let y_pred: Series = Series::new("y_pred", &[1.0, 1.0, 0.0]);
let hinge_loss: f64 = LossFunctionType::Hinge.loss(&y, &y_pred);
assert_eq!(hinge_loss, 1.0);
}
#[test]
fn test_svm_model_fit_predict_single_feature() {
let x: DataFrame =
DataFrame::new(vec![Series::new("feature1", vec![1.0, 2.0, 3.0])]).unwrap();
let y: Series = Series::new("target", vec![0.0, 1.0, 1.0]);
let mut model: SVM = SVM::new();
assert!(model.fit(&x, &y, 1000, 0.1).is_ok());
let predictions: Series = model.predict(&x).unwrap();
let predictions: Series = Logistic::classify(&predictions, 0.5);
println!("Predictions: {:?}", predictions);
println!("Actual values: {:?}", y);
assert_eq!(predictions.len(), y.len());
println!("Sum of predictions: {:?}", predictions.sum::<f64>());
println!("Sum of actual values: {:?}", y.sum::<f64>());
assert!(
(predictions.sum::<f64>().unwrap() - y.sum::<f64>().unwrap()).abs() < 1e-6,
"Sums do not match within epsilon"
);
assert_eq!(predictions.len(), y.len());
assert_eq!(predictions.sum::<f64>(), y.sum()); }
#[test]
fn test_svm_model_fit_predict_multiple_features() {
let x: DataFrame = DataFrame::new(vec![
Series::new("feature1", vec![1.0, 2.0, 3.0]),
Series::new("feature2", vec![1.0, 2.0, 3.0]),
])
.unwrap();
let y: Series = Series::new("target", vec![0.0, 1.0, 1.0]);
let mut model: SVM = SVM::new();
assert!(model.fit(&x, &y, 10000, 0.01).is_ok());
let predictions: Series = model.predict(&x).unwrap();
let predictions: Series = Logistic::classify(&predictions, 0.5);
println!("Predictions: {:?}", predictions);
println!("Actual values: {:?}", y);
assert_eq!(predictions.len(), y.len());
println!("Sum of predictions: {:?}", predictions.sum::<f64>());
println!("Sum of actual values: {:?}", y.sum::<f64>());
assert!(
(predictions.sum::<f64>().unwrap() - y.sum::<f64>().unwrap()).abs() < 1e-6,
"Sums do not match within epsilon"
);
assert_eq!(predictions.len(), y.len());
assert_eq!(predictions.sum::<f64>(), y.sum()); }
#[test]
fn test_svm_model_accuracy_perfect_single_feature() {
let x: DataFrame =
DataFrame::new(vec![Series::new("feature1", vec![1.0, 2.0, 3.0])]).unwrap();
let y: Series = Series::new("target", vec![0.0, 1.0, 1.0]);
let mut model: SVM = SVM::new();
assert!(model.fit(&x, &y, 1000, 0.1).is_ok());
let accuracy: f64 = model.accuracy(&x, &y).unwrap();
println!("Actual values: {:?}", y);
println!("Predicted values: {:?}", model.predict(&x).unwrap());
println!("Accuracy: {:?}", accuracy);
assert!(
(accuracy - 1.0).abs() < 1e-6,
"Accuracy does not match within epsilon"
);
}
#[test]
fn test_svm_model_accuracy_non_perfect_single_feature() {
let x: DataFrame = DataFrame::new(vec![Series::new(
"feature1",
vec![1.0, -2.0, 3.0, 4.0, 5.0],
)])
.unwrap();
let y: Series = Series::new("target", vec![0.0, 0.0, 1.0, 0.0, 1.0]);
let mut model: SVM = SVM::new();
assert!(model.fit(&x, &y, 10000, 0.01).is_ok());
let accuracy: f64 = model.accuracy(&x, &y).unwrap();
println!("Accuracy: {:?}", accuracy);
assert!(
(accuracy - 0.8).abs() < 1e-6,
"Accuracy does not match within epsilon"
);
}
#[test]
fn test_svm_model_accuracy_perfect_multiple_features() {
let x: DataFrame = DataFrame::new(vec![
Series::new("feature1", vec![1.0, 2.0, 3.0]),
Series::new("feature2", vec![1.0, 2.0, 3.0]),
])
.unwrap();
let y: Series = Series::new("target", vec![1.0, 1.0, 0.0]);
let mut model: SVM = SVM::new();
assert!(model.fit(&x, &y, 10000, 0.01).is_ok());
let accuracy: f64 = model.accuracy(&x, &y).unwrap();
println!("Accuracy: {:?}", accuracy);
assert!(
(accuracy - 1.0).abs() < 1e-6,
"Accuracy does not match within epsilon"
);
}
#[test]
fn test_svm_model_accuracy_non_perfect_multiple_features() {
let x: DataFrame = DataFrame::new(vec![
Series::new("feature1", vec![1.0, -2.0, 3.0, 4.0, 5.0]),
Series::new("feature2", vec![1.0, 2.0, 3.0, -4.0, 5.0]),
])
.unwrap();
let y: Series = Series::new("target", vec![1.0, 1.0, 0.0, 0.0, 1.0]);
let mut model: SVM = SVM::new();
assert!(model.fit(&x, &y, 10000, 0.01).is_ok());
let accuracy: f64 = model.accuracy(&x, &y).unwrap();
println!("Accuracy: {:?}", accuracy);
assert!(
(accuracy - 0.4).abs() < 1e-6,
"Accuracy does not match within epsilon"
);
}
#[test]
fn test_activation_function_relu() {
let x: Series = Series::new("x", &[-1.0, 2.0, -3.0]);
let relu: Series = ActivationFunctionType::ReLU.activate(&x);
assert_eq!(relu, Series::new("activated_values", &[0.0, 2.0, 0.0]));
}
#[test]
fn test_activation_function_identity_gradient() {
let x: Series = Series::new("x", &[1.0, 2.0, 3.0]);
let identity_gradient: Series = ActivationFunctionType::Identity.gradient(&x);
assert_eq!(
identity_gradient,
Series::new("gradients", &[1.0, 1.0, 1.0])
);
}
#[test]
fn test_activation_function_sigmoid_gradient() {
fn sigmoid(x: f64) -> f64 {
1.0 / (1.0 + f64::exp(-x))
}
let x: Series = Series::new("x", &[1.0, 2.0, 3.0]);
let sigmoid_gradient: Series = ActivationFunctionType::Sigmoid.gradient(&x);
assert_eq!(
sigmoid_gradient,
Series::new(
"gradients",
&[
sigmoid(1.0) * (1.0 - sigmoid(1.0)),
sigmoid(2.0) * (1.0 - sigmoid(2.0)),
sigmoid(3.0) * (1.0 - sigmoid(3.0))
]
)
);
}
#[test]
fn test_activation_function_relu_gradient() {
let x: Series = Series::new("x", &[-1.0, 2.0, -3.0]);
let relu_gradient: Series = ActivationFunctionType::ReLU.gradient(&x);
assert_eq!(relu_gradient, Series::new("gradients", &[0.0, 1.0, 0.0]));
}
#[test]
fn test_activation_function_softmax() {
let x: Series = Series::new("x", vec![1.0, 2.0, 3.0]);
let softmax = |x: f64| -> f64 { return f64::exp(x) / (f64::exp(1.0) + f64::exp(2.0) + f64::exp(3.0)) };
let softmax_values: Series = Series::new("activated_values", vec![softmax(1.0), softmax(2.0), softmax(3.0)]);
assert_eq!(ActivationFunctionType::Softmax.activate(&x), softmax_values);
}
#[test]
fn test_activation_function_softmax_gradient() {
let x: Series = Series::new("x", vec![1.0, 2.0, 3.0]);
let true_softmax: Series = Series::new("gradients", vec![0.081925, -0.022033, -0.059892, -0.022033, 0.184836, -0.162803, -0.059892, -0.162803, 0.222695]);
let residuals: Series = ActivationFunctionType::Softmax.gradient(&x) - true_softmax;
assert!(residuals.f64().unwrap().max().unwrap().abs() < 0.001);
}
#[test]
fn test_activation_function_lrelu() {
let x: Series = Series::new("x", vec![-1.0, 2.0, -3.0]);
let alpha: f64 = 0.1;
let lrelu: Series = ActivationFunctionType::LReLU(alpha).activate(&x);
let true_lrelu: Series = Series::new("activated_values", vec![-1.0 * alpha, 2.0, -3.0 * alpha]);
assert_eq!(lrelu, true_lrelu);
}
#[test]
fn test_activation_function_lrelu_gradient() {
let x: Series = Series::new("x", vec![-1.0, 2.0, -3.0]);
let lrelu_gradient: Series = ActivationFunctionType::LReLU(0.1).gradient(&x);
assert_eq!(lrelu_gradient, Series::new("gradients", vec![0.1, 1.0, 0.1]));
}
#[ignore = "Random test, may fail"]
#[test]
fn test_kmeans_fit_predict_single_feature_random_init_tol_end_two_clusters() {
let x: DataFrame = DataFrame::new(vec![Series::new("feature1", vec![1.0, 2.0, 3.0, 101.0, 102.0, 103.0])]).unwrap();
let mut model: KMeans = KMeans::new_random(2, EndCondition::Tol(0.0));
assert!(model.fit(&x).is_ok());
let predictions: Series = model.predict(&x).unwrap();
println!("Predictions: {:?}", predictions);
assert_eq!(predictions.len(), x.height());
let possible_solutions: Vec<Series> = vec![
Series::new("clusters", vec![0.0, 0.0, 0.0, 1.0, 1.0, 1.0]),
Series::new("clusters", vec![1.0, 1.0, 1.0, 0.0, 0.0, 0.0]),
];
assert!(possible_solutions.contains(&predictions));
}
#[ignore = "Random test, may fail"]
#[test]
fn test_kmeans_fit_predict_multi_features_random_init_tol_end_two_clusters() {
let x: DataFrame = DataFrame::new(vec![
Series::new("feature1", vec![1.0, 2.0, 3.0, 101.0, 102.0, 103.0, 201.0, 202.0, 203.0]) + random::<f64>(),
Series::new("feature2", vec![1.0, 2.0, 3.0, 101.0, 102.0, 103.0, 201.0, 202.0, 203.0]) + random::<f64>(),
Series::new("feature3", vec![1.0, 2.0, 3.0, 101.0, 102.0, 103.0, 201.0, 202.0, 203.0]) + random::<f64>(),
]).unwrap();
let mut model: KMeans = KMeans::new_random(2, EndCondition::Tol(0.5));
assert!(model.fit(&x).is_ok());
let predictions: Series = model.predict(&x).unwrap();
println!("Predictions: {:?}", predictions);
assert_eq!(predictions.len(), x.height());
let possible_solutions: Vec<Series> = vec![
Series::new("clusters", vec![0.0, 0.0, 0.0, 1.0, 1.0, 1.0]),
Series::new("clusters", vec![1.0, 1.0, 1.0, 0.0, 0.0, 0.0]),
];
assert!(possible_solutions.contains(&predictions));
}
#[ignore = "Random test, may fail"]
#[test]
fn test_kmeans_fit_predict_multi_features_random_init_tol_end_three_clusters() {
let x: DataFrame = DataFrame::new(vec![
Series::new("feature1", vec![1.0, 2.0, 3.0, 101.0, 102.0, 103.0, 201.0, 202.0, 203.0]) + random::<f64>(),
Series::new("feature2", vec![1.0, 2.0, 3.0, 101.0, 102.0, 103.0, 201.0, 202.0, 203.0]) + random::<f64>(),
Series::new("feature3", vec![1.0, 2.0, 3.0, 101.0, 102.0, 103.0, 201.0, 202.0, 203.0]) + random::<f64>(),
]).unwrap();
let mut model: KMeans = KMeans::new_random(3, EndCondition::Tol(0.5));
assert!(model.fit(&x).is_ok());
let predictions: Series = model.predict(&x).unwrap();
println!("Predictions: {:?}", predictions);
assert_eq!(predictions.len(), x.height());
let possible_solutions: Vec<Series> = vec![
Series::new("clusters", vec![0.0, 0.0, 0.0, 1.0, 1.0, 1.0, 2.0, 2.0, 2.0]),
Series::new("clusters", vec![1.0, 1.0, 1.0, 2.0, 2.0, 2.0, 0.0, 0.0, 0.0]),
Series::new("clusters", vec![2.0, 2.0, 2.0, 0.0, 0.0, 0.0, 1.0, 1.0, 1.0]),
Series::new("clusters", vec![0.0, 0.0, 0.0, 2.0, 2.0, 2.0, 1.0, 1.0, 1.0]),
Series::new("clusters", vec![1.0, 1.0, 1.0, 0.0, 0.0, 0.0, 2.0, 2.0, 2.0]),
Series::new("clusters", vec![2.0, 2.0, 2.0, 1.0, 1.0, 1.0, 0.0, 0.0, 0.0]),
];
assert!(possible_solutions.contains(&predictions));
}
#[ignore = "Random test, may fail"]
#[test]
fn test_kmeans_fit_predict_multi_features_random_init_max_iter_end_three_clusters() {
let x: DataFrame = DataFrame::new(vec![
Series::new("feature1", vec![1.0, 2.0, 3.0, 101.0, 102.0, 103.0, 201.0, 202.0, 203.0]) + random::<f64>(),
Series::new("feature2", vec![1.0, 2.0, 3.0, 101.0, 102.0, 103.0, 201.0, 202.0, 203.0]) + random::<f64>(),
Series::new("feature3", vec![1.0, 2.0, 3.0, 101.0, 102.0, 103.0, 201.0, 202.0, 203.0]) + random::<f64>(),
]).unwrap();
let mut model: KMeans = KMeans::new_random(3, EndCondition::MaxIter(100));
assert!(model.fit(&x).is_ok());
let predictions: Series = model.predict(&x).unwrap();
println!("Predictions: {:?}", predictions);
assert_eq!(predictions.len(), x.height());
let possible_solutions: Vec<Series> = vec![
Series::new("clusters", vec![0.0, 0.0, 0.0, 1.0, 1.0, 1.0, 2.0, 2.0, 2.0]),
Series::new("clusters", vec![1.0, 1.0, 1.0, 2.0, 2.0, 2.0, 0.0, 0.0, 0.0]),
Series::new("clusters", vec![2.0, 2.0, 2.0, 0.0, 0.0, 0.0, 1.0, 1.0, 1.0]),
Series::new("clusters", vec![0.0, 0.0, 0.0, 2.0, 2.0, 2.0, 1.0, 1.0, 1.0]),
Series::new("clusters", vec![1.0, 1.0, 1.0, 0.0, 0.0, 0.0, 2.0, 2.0, 2.0]),
Series::new("clusters", vec![2.0, 2.0, 2.0, 1.0, 1.0, 1.0, 0.0, 0.0, 0.0]),
];
assert!(possible_solutions.contains(&predictions));
}
#[test]
fn test_kmeans_fit_predict_single_feature_userdefined_init_tol_end_two_clusters() {
let x: DataFrame = DataFrame::new(vec![Series::new("feature1", vec![1.0, 2.0, 3.0, 101.0, 102.0, 103.0])]).unwrap();
let initial_centroids: DataFrame = DataFrame::new(vec![Series::new("0", vec![1.0]), Series::new("1", vec![101.0])]).unwrap();
let mut model: KMeans = KMeans::new_user_defined(2, initial_centroids, EndCondition::Tol(0.0));
assert!(model.fit(&x).is_ok());
let predictions: Series = model.predict(&x).unwrap();
println!("Predictions: {:?}", predictions);
assert_eq!(predictions.len(), x.height());
let possible_solutions: Vec<Series> = vec![
Series::new("clusters", vec![0.0, 0.0, 0.0, 1.0, 1.0, 1.0]),
Series::new("clusters", vec![1.0, 1.0, 1.0, 0.0, 0.0, 0.0]),
];
assert!(possible_solutions.contains(&predictions));
}
#[test]
fn test_kmeans_fit_predict_multi_features_userdefined_init_tol_end_three_clusters() {
let x: DataFrame = DataFrame::new(vec![
Series::new("feature1", vec![1.0, 2.0, 3.0, 101.0, 102.0, 103.0, 201.0, 202.0, 203.0]) + random::<f64>(),
Series::new("feature2", vec![1.0, 2.0, 3.0, 101.0, 102.0, 103.0, 201.0, 202.0, 203.0]) + random::<f64>(),
Series::new("feature3", vec![1.0, 2.0, 3.0, 101.0, 102.0, 103.0, 201.0, 202.0, 203.0]) + random::<f64>(),
]).unwrap();
let initial_centroids: DataFrame = DataFrame::new(vec![
Series::new("0", vec![1.0, 1.1, 1.2]),
Series::new("1", vec![101.0, 101.1, 101.2]),
Series::new("2", vec![201.0, 201.1, 201.2]),
]).unwrap();
let mut model: KMeans = KMeans::new_user_defined(3, initial_centroids, EndCondition::Tol(0.0));
assert!(model.fit(&x).is_ok());
let predictions: Series = model.predict(&x).unwrap();
println!("Predictions: {:?}", predictions);
assert_eq!(predictions.len(), x.height());
let possible_solutions: Vec<Series> = vec![
Series::new("clusters", vec![0.0, 0.0, 0.0, 1.0, 1.0, 1.0, 2.0, 2.0, 2.0]),
Series::new("clusters", vec![1.0, 1.0, 1.0, 2.0, 2.0, 2.0, 0.0, 0.0, 0.0]),
Series::new("clusters", vec![2.0, 2.0, 2.0, 0.0, 0.0, 0.0, 1.0, 1.0, 1.0]),
Series::new("clusters", vec![0.0, 0.0, 0.0, 2.0, 2.0, 2.0, 1.0, 1.0, 1.0]),
Series::new("clusters", vec![1.0, 1.0, 1.0, 0.0, 0.0, 0.0, 2.0, 2.0, 2.0]),
Series::new("clusters", vec![2.0, 2.0, 2.0, 1.0, 1.0, 1.0, 0.0, 0.0, 0.0]),
];
assert!(possible_solutions.contains(&predictions));
}
#[test]
fn test_kmeans_fit_predict_multi_features_userdefined_init_max_iter_end_three_clusters() {
let x: DataFrame = DataFrame::new(vec![
Series::new("feature1", vec![1.0, 2.0, 3.0, 101.0, 102.0, 103.0, 201.0, 202.0, 203.0]) + random::<f64>(),
Series::new("feature2", vec![1.0, 2.0, 3.0, 101.0, 102.0, 103.0, 201.0, 202.0, 203.0]) + random::<f64>(),
Series::new("feature3", vec![1.0, 2.0, 3.0, 101.0, 102.0, 103.0, 201.0, 202.0, 203.0]) + random::<f64>(),
]).unwrap();
let initial_centroids: DataFrame = DataFrame::new(vec![
Series::new("0", vec![1.0, 1.1, 1.2]),
Series::new("1", vec![101.0, 101.1, 101.2]),
Series::new("2", vec![201.0, 201.1, 201.2]),
]).unwrap();
let mut model: KMeans = KMeans::new_user_defined(3, initial_centroids, EndCondition::MaxIter(100));
assert!(model.fit(&x).is_ok());
let predictions: Series = model.predict(&x).unwrap();
println!("Predictions: {:?}", predictions);
assert_eq!(predictions.len(), x.height());
let possible_solutions: Vec<Series> = vec![
Series::new("clusters", vec![0.0, 0.0, 0.0, 1.0, 1.0, 1.0, 2.0, 2.0, 2.0]),
Series::new("clusters", vec![1.0, 1.0, 1.0, 2.0, 2.0, 2.0, 0.0, 0.0, 0.0]),
Series::new("clusters", vec![2.0, 2.0, 2.0, 0.0, 0.0, 0.0, 1.0, 1.0, 1.0]),
Series::new("clusters", vec![0.0, 0.0, 0.0, 2.0, 2.0, 2.0, 1.0, 1.0, 1.0]),
Series::new("clusters", vec![1.0, 1.0, 1.0, 0.0, 0.0, 0.0, 2.0, 2.0, 2.0]),
Series::new("clusters", vec![2.0, 2.0, 2.0, 1.0, 1.0, 1.0, 0.0, 0.0, 0.0]),
];
assert!(possible_solutions.contains(&predictions));
}
#[test]
fn test_kmeans_fit_predict_single_feature_equidist_init_tol_end_two_clusters() {
let x: DataFrame = DataFrame::new(vec![Series::new("feature1", vec![1.0, 2.0, 3.0, 101.0, 102.0, 103.0])]).unwrap();
let mut model: KMeans = KMeans::new_equidistant(2, EndCondition::Tol(0.0));
assert!(model.fit(&x).is_ok());
let predictions: Series = model.predict(&x).unwrap();
println!("Predictions: {:?}", predictions);
assert_eq!(predictions.len(), x.height());
let possible_solutions: Vec<Series> = vec![
Series::new("clusters", vec![0.0, 0.0, 0.0, 1.0, 1.0, 1.0]),
Series::new("clusters", vec![1.0, 1.0, 1.0, 0.0, 0.0, 0.0]),
];
assert!(possible_solutions.contains(&predictions));
}
#[test]
fn test_kmeans_fit_predict_multi_features_equidist_init_tol_end_three_clusters() {
let x: DataFrame = DataFrame::new(vec![
Series::new("feature1", vec![1.0, 2.0, 3.0, 101.0, 102.0, 103.0, 201.0, 202.0, 203.0]) + random::<f64>(),
Series::new("feature2", vec![1.0, 2.0, 3.0, 101.0, 102.0, 103.0, 201.0, 202.0, 203.0]) + random::<f64>(),
Series::new("feature3", vec![1.0, 2.0, 3.0, 101.0, 102.0, 103.0, 201.0, 202.0, 203.0]) + random::<f64>(),
]).unwrap();
let mut model: KMeans = KMeans::new_equidistant(3, EndCondition::Tol(0.0));
assert!(model.fit(&x).is_ok());
let predictions: Series = model.predict(&x).unwrap();
println!("Predictions: {:?}", predictions);
assert_eq!(predictions.len(), x.height());
let possible_solutions: Vec<Series> = vec![
Series::new("clusters", vec![0.0, 0.0, 0.0, 1.0, 1.0, 1.0, 2.0, 2.0, 2.0]),
Series::new("clusters", vec![1.0, 1.0, 1.0, 2.0, 2.0, 2.0, 0.0, 0.0, 0.0]),
Series::new("clusters", vec![2.0, 2.0, 2.0, 0.0, 0.0, 0.0, 1.0, 1.0, 1.0]),
Series::new("clusters", vec![0.0, 0.0, 0.0, 2.0, 2.0, 2.0, 1.0, 1.0, 1.0]),
Series::new("clusters", vec![1.0, 1.0, 1.0, 0.0, 0.0, 0.0, 2.0, 2.0, 2.0]),
Series::new("clusters", vec![2.0, 2.0, 2.0, 1.0, 1.0, 1.0, 0.0, 0.0, 0.0]),
];
assert!(possible_solutions.contains(&predictions));
}
#[test]
fn test_kmeans_multi_features_equidist_init_max_iter_end_three_clusters() {
let x: DataFrame = DataFrame::new(vec![
Series::new("feature1", vec![1.0, 2.0, 3.0, 101.0, 102.0, 103.0, 201.0, 202.0, 203.0]) + random::<f64>(),
Series::new("feature2", vec![1.0, 2.0, 3.0, 101.0, 102.0, 103.0, 201.0, 202.0, 203.0]) + random::<f64>(),
Series::new("feature3", vec![1.0, 2.0, 3.0, 101.0, 102.0, 103.0, 201.0, 202.0, 203.0]) + random::<f64>(),
]).unwrap();
let mut model: KMeans = KMeans::new_equidistant(3, EndCondition::MaxIter(100));
assert!(model.fit(&x).is_ok());
let predictions: Series = model.predict(&x).unwrap();
println!("Predictions: {:?}", predictions);
assert_eq!(predictions.len(), x.height());
let possible_solutions: Vec<Series> = vec![
Series::new("clusters", vec![0.0, 0.0, 0.0, 1.0, 1.0, 1.0, 2.0, 2.0, 2.0]),
Series::new("clusters", vec![1.0, 1.0, 1.0, 2.0, 2.0, 2.0, 0.0, 0.0, 0.0]),
Series::new("clusters", vec![2.0, 2.0, 2.0, 0.0, 0.0, 0.0, 1.0, 1.0, 1.0]),
Series::new("clusters", vec![0.0, 0.0, 0.0, 2.0, 2.0, 2.0, 1.0, 1.0, 1.0]),
Series::new("clusters", vec![1.0, 1.0, 1.0, 0.0, 0.0, 0.0, 2.0, 2.0, 2.0]),
Series::new("clusters", vec![2.0, 2.0, 2.0, 1.0, 1.0, 1.0, 0.0, 0.0, 0.0]),
];
assert!(possible_solutions.contains(&predictions));
}
#[test]
fn test_kmeans_compactness_single_feature() {
let x: DataFrame = DataFrame::new(vec![Series::new("feature1", vec![1.0, 2.0, 3.0, 101.0, 102.0, 103.0])]).unwrap();
let mut model: KMeans = KMeans::new_equidistant(2, EndCondition::Tol(0.0));
assert!(model.fit(&x).is_ok());
let compactness: f64 = model.compactness(&x).unwrap();
println!("Compactness: {:?}", compactness);
assert!(compactness == 4.0);
}
#[test]
fn test_kmeans_compactness_multi_features() {
let x: DataFrame = DataFrame::new(vec![
Series::new("feature1", vec![1.0, 2.0, 3.0, 101.0, 102.0, 103.0, 201.0, 202.0, 203.0]) + random::<f64>(),
Series::new("feature2", vec![1.0, 2.0, 3.0, 101.0, 102.0, 103.0, 201.0, 202.0, 203.0]) + random::<f64>(),
Series::new("feature3", vec![1.0, 2.0, 3.0, 101.0, 102.0, 103.0, 201.0, 202.0, 203.0]) + random::<f64>(),
]).unwrap();
let mut model: KMeans = KMeans::new_equidistant(3, EndCondition::Tol(0.0));
assert!(model.fit(&x).is_ok());
let compactness: f64 = model.compactness(&x).unwrap();
println!("Compactness: {:?}", compactness);
assert!((compactness - 18.0).abs() < 0.1);
}
#[test]
fn test_linear_layer_new() {
let linear_layer: LinearLayer = LinearLayer::new(
Series::new("weights", [1.0, 2.0, 3.0]),
Series::new("biases", [1.0, 2.0, 3.0]),
);
assert_eq!(
linear_layer.weights,
Series::new("weights", vec![1.0, 2.0, 3.0])
);
assert_eq!(
linear_layer.biases,
Series::new("biases", vec![1.0, 2.0, 3.0])
);
}
#[test]
fn test_linear_layer_zeroes() {
let linear_layer: LinearLayer = LinearLayer::zeroes(3);
assert_eq!(
linear_layer.weights,
Series::new("weights", vec![0.0, 0.0, 0.0])
);
assert_eq!(
linear_layer.biases,
Series::new("biases", vec![0.0, 0.0, 0.0])
);
}
#[test]
fn test_linear_layer_new_random() {
let linear_layer: LinearLayer = LinearLayer::new_random(5, [-1.0, 1.0]);
assert_eq!(linear_layer.weights.len(), 5);
assert_eq!(linear_layer.biases.len(), 5);
assert!(linear_layer.weights.f64().unwrap().min().unwrap() >= -1.0);
assert!(linear_layer.weights.f64().unwrap().max().unwrap() <= 1.0);
}
#[test]
fn test_linear_layer_forward() {
let weights: Series = Series::new("weights", vec![1.0, 2.0, 3.0]);
let biases: Series = Series::new("biases", vec![4.0, 5.0, 6.0]);
let activation_function: ActivationFunctionType = ActivationFunctionType::Sigmoid;
let input: Series = Series::new("input", vec![7.0, 8.0, 9.0]);
let sigmoid = |x: f64| -> f64 { return 1.0 / (1.0 + f64::exp(-x)) };
let linear_layer: LinearLayer = LinearLayer::new(weights.clone(), biases.clone());
let output: Series = linear_layer.forward(input.clone(), activation_function.clone());
assert_eq!(
output,
Series::new(
"activated_values",
&[
sigmoid(
(&input * &weights).sum::<f64>().unwrap()
+ &biases.f64().unwrap().get(0).unwrap()
),
sigmoid(
(&input * &weights).sum::<f64>().unwrap()
+ &biases.f64().unwrap().get(1).unwrap()
),
sigmoid(
(&input * &weights).sum::<f64>().unwrap()
+ &biases.f64().unwrap().get(2).unwrap()
)
]
)
);
}
#[test]
fn test_linear_layer_backward() {
let weights: Series = Series::new("weights", vec![1.0, 2.0, 3.0]);
let biases: Series = Series::new("biases", vec![4.0, 5.0, 6.0]);
let input: Series = Series::new("input", vec![7.0, 8.0, 9.0]);
let lr: f64 = 0.01;
let test_layer: LinearLayer = LinearLayer::new(weights.clone(), biases.clone());
let grad_output: Series = Series::new("grad_output", vec![2.0, 2.0, 2.0]);
let (updated_weights, updated_biases) = test_layer.backward(input.clone(), grad_output.clone(), lr);
assert_eq!(
updated_weights,
Series::new(
"weights",
&[
1.0 - lr * 2.0 * 7.0,
2.0 - lr * 2.0 * 8.0,
3.0 - lr * 2.0 * 9.0
]
)
);
assert_eq!(
updated_biases,
Series::new(
"biases",
&[
4.0 - lr * 6.0,
5.0 - lr * 6.0,
6.0 - lr * 6.0
]
)
);
}
#[test]
fn test_mlp_new() {
let mlp: MLP = MLP::new(LossFunctionType::MeanSquaredError);
assert_eq!(mlp.layers.len(), 0);
assert!(mlp.loss_function == LossFunctionType::MeanSquaredError);
}
#[test]
fn test_mlp_add_layer() {
let mut mlp: MLP = MLP::new(LossFunctionType::MeanSquaredError);
mlp = mlp.add_layer(LinearLayer::zeroes(3), ActivationFunctionType::Sigmoid);
assert_eq!(mlp.layers.len(), 1);
}
#[test]
fn test_mlp_set_layer() {
let mut mlp: MLP = MLP::new(LossFunctionType::MeanSquaredError);
mlp = mlp.add_layer(LinearLayer::zeroes(3), ActivationFunctionType::Sigmoid);
mlp = mlp.set_layer(0, LinearLayer::zeroes(4), ActivationFunctionType::ReLU);
assert_eq!(mlp.layers[0].weights.len(), 4);
assert_eq!(mlp.activation_functions[0], ActivationFunctionType::ReLU);
}
#[test]
fn test_mlp_new_with_layers() {
let mlp: MLP = MLP::new_with_layers(
LossFunctionType::MeanSquaredError,
vec![
LinearLayer::zeroes(3),
LinearLayer::zeroes(3),
LinearLayer::zeroes(3),
],
vec![
ActivationFunctionType::Sigmoid,
ActivationFunctionType::Sigmoid,
ActivationFunctionType::Sigmoid,
]
);
assert_eq!(mlp.layers.len(), 3);
assert_eq!(mlp.activation_functions.len(), 3);
assert!(mlp.loss_function == LossFunctionType::MeanSquaredError);
}
#[test]
fn test_mlp_forward() {
let input: Series = Series::new("input", vec![1.0, 2.0, 3.0]);
let weights1: Series = Series::new("weights1", vec![1.0, 2.0, 3.0]);
let biases1: Series = Series::new("biases1", vec![4.0, 5.0, 6.0]);
let weights2: Series = Series::new("weights2", vec![1.0, 2.0, 3.0]);
let biases2: Series = Series::new("biases2", vec![4.0, 5.0, 6.0]);
let weights3: Series = Series::new("weights3", vec![1.0, 2.0, 3.0]);
let biases3: Series = Series::new("biases3", vec![4.0, 5.0, 6.0]);
let mlp: MLP = MLP::new_with_layers(
LossFunctionType::MeanSquaredError,
vec![
LinearLayer::new(weights1.clone(), biases1.clone()),
LinearLayer::new(weights2.clone(), biases2.clone()),
LinearLayer::new(weights3.clone(), biases3.clone()),
],
vec![
ActivationFunctionType::Sigmoid,
ActivationFunctionType::Sigmoid,
ActivationFunctionType::Sigmoid,
]
);
let output: Series = mlp.forward(input.clone());
let sigmoid = |x: f64| -> f64 { return 1.0 / (1.0 + f64::exp(-x)) };
let is_close = |a: f64, b: f64| -> bool { return (a - b).abs() < 1e-3 };
let expected_output: &[f64; 3] = &[
sigmoid(
(&input * &weights1).sum::<f64>().unwrap()
+ &biases1.f64().unwrap().get(0).unwrap()
),
sigmoid(
(&input * &weights1).sum::<f64>().unwrap()
+ &biases1.f64().unwrap().get(1).unwrap()
),
sigmoid(
(&input * &weights1).sum::<f64>().unwrap()
+ &biases1.f64().unwrap().get(2).unwrap()
)
];
for i in 0..3 {
assert!(is_close(output.f64().unwrap().get(i).unwrap(), expected_output[i]));
}
}
#[test]
fn test_mlp_forward_2_layers() {
let weights_1: Series = Series::new("weights", vec![1.0, 2.0, 3.0]);
let biases_1: Series = Series::new("biases", vec![4.0, 5.0, 6.0]);
let weights_2: Series = Series::new("weights", vec![7.0, 8.0, 9.0]);
let biases_2: Series = Series::new("biases", vec![10.0, 11.0, 12.0]);
let activation_function: ActivationFunctionType = ActivationFunctionType::Sigmoid;
let input: Series = Series::new("input", vec![13.0, 14.0, 15.0]);
let sigmoid = |x: f64| -> f64 { return 1.0 / (1.0 + f64::exp(-x)) };
let mut mlp: MLP = MLP::new(LossFunctionType::BinaryCrossEntropy);
mlp = mlp.add_layer(LinearLayer::new(weights_1.clone(), biases_1.clone()), activation_function.clone());
mlp = mlp.add_layer(LinearLayer::new(weights_2.clone(), biases_2.clone()), activation_function.clone());
let output: Series = mlp.forward(input.clone());
let true_values: Vec<f64> = vec![
sigmoid(
(&input * &weights_1).sum::<f64>().unwrap() + &biases_1.f64().unwrap().get(0).unwrap(),
),
sigmoid(
(&input * &weights_1).sum::<f64>().unwrap() + &biases_1.f64().unwrap().get(1).unwrap(),
),
sigmoid(
(&input * &weights_1).sum::<f64>().unwrap() + &biases_1.f64().unwrap().get(2).unwrap(),
),
];
let true_values: Vec<f64> = vec![
sigmoid(
(&Series::new("input", true_values.clone()) * &weights_2)
.sum::<f64>()
.unwrap()
+ &biases_2.f64().unwrap().get(0).unwrap(),
),
sigmoid(
(&Series::new("input", true_values.clone()) * &weights_2)
.sum::<f64>()
.unwrap()
+ &biases_2.f64().unwrap().get(1).unwrap(),
),
sigmoid(
(&Series::new("input", true_values.clone()) * &weights_2)
.sum::<f64>()
.unwrap()
+ &biases_2.f64().unwrap().get(2).unwrap(),
),
];
assert_eq!(
output,
Series::new("activated_values", &true_values)
);
}
#[test]
fn test_mlp_backward() {
let input: Series = Series::new("input", vec![1.0, 2.0, 3.0]);
let weights1: Series = Series::new("weights1", vec![1.0, 2.0, 3.0]);
let biases1: Series = Series::new("biases1", vec![4.0, 5.0, 6.0]);
let weights2: Series = Series::new("weights2", vec![1.0, 2.0, 3.0]);
let biases2: Series = Series::new("biases2", vec![4.0, 5.0, 6.0]);
let weights3: Series = Series::new("weights3", vec![1.0, 2.0, 3.0]);
let biases3: Series = Series::new("biases3", vec![4.0, 5.0, 6.0]);
let lr: f64 = 0.0001;
let mut mlp: MLP = MLP::new_with_layers(
LossFunctionType::MeanSquaredError,
vec![
LinearLayer::new(weights1.clone(), biases1.clone()),
LinearLayer::new(weights2.clone(), biases2.clone()),
LinearLayer::new(weights3.clone(), biases3.clone()),
],
vec![
ActivationFunctionType::Sigmoid,
ActivationFunctionType::Sigmoid,
ActivationFunctionType::Sigmoid,
]
);
let actual_output: Series = Series::new("output", vec![1.0, 2.0, 3.0]);
let _ = mlp.fit(&DataFrame::new(vec![input]).unwrap(), &actual_output, 1, lr);
let is_close = |a: f64, b: f64| -> bool { return (a - b).abs() < 1e-3 };
let expected_layer_0_weights = Series::new("weights1", vec![
1.0 - lr * 2.0 * 1.0,
2.0 - lr * 2.0 * 2.0,
3.0 - lr * 2.0 * 3.0
]);
let expected_layer_0_biases = Series::new("biases1", vec![
4.0 - lr * 2.0,
5.0 - lr * 2.0,
6.0 - lr * 2.0
]);
let expected_layer_1_weights = Series::new("weights2", vec![
1.0 - lr * 2.0 * 1.0,
2.0 - lr * 2.0 * 2.0,
3.0 - lr * 2.0 * 3.0
]);
let expected_layer_1_biases = Series::new("biases2", vec![
4.0 - lr * 2.0,
5.0 - lr * 2.0,
6.0 - lr * 2.0
]);
for i in 0..3 {
assert!(is_close(mlp.layers[0].weights.f64().unwrap().get(i).unwrap(), expected_layer_0_weights.f64().unwrap().get(i).unwrap()));
assert!(is_close(mlp.layers[0].biases.f64().unwrap().get(i).unwrap(), expected_layer_0_biases.f64().unwrap().get(i).unwrap()));
assert!(is_close(mlp.layers[1].weights.f64().unwrap().get(i).unwrap(), expected_layer_1_weights.f64().unwrap().get(i).unwrap()));
assert!(is_close(mlp.layers[1].biases.f64().unwrap().get(i).unwrap(), expected_layer_1_biases.f64().unwrap().get(i).unwrap()));
}
}
}