Skip to content

Commit

Permalink
Merge branch 'development' into patch-0.3.2
Browse files Browse the repository at this point in the history
  • Loading branch information
Mec-iS committed Mar 4, 2024
2 parents af5033b + 80a93c1 commit ea74b32
Show file tree
Hide file tree
Showing 22 changed files with 287 additions and 159 deletions.
2 changes: 2 additions & 0 deletions .github/CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ $ rust-code-analysis-cli -p src/algorithm/neighbour/fastpair.rs --ls 22 --le 213
```
* find more information about what happens in your binary with [`twiggy`](https://rustwasm.github.io/twiggy/install.html). This need a compiled binary so create a brief `main {}` function using `smartcore` and then point `twiggy` to that file.

* Please take a look to the output of a profiler to spot most evident performance problems, see [this guide about using a profiler](http://www.codeofview.com/fix-rs/2017/01/24/how-to-optimize-rust-programs-on-linux/).

## Issue Report Process

1. Go to the project's issues.
Expand Down
4 changes: 2 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
name = "smartcore"
description = "Machine Learning in Rust."
homepage = "https://smartcorelib.org"
version = "0.4.0"
version = "0.3.3"
authors = ["smartcore Developers"]
edition = "2021"
license = "Apache-2.0"
Expand Down Expand Up @@ -48,7 +48,7 @@ getrandom = { version = "0.2.8", optional = true }
wasm-bindgen-test = "0.3"

[dev-dependencies]
itertools = "0.10.5"
itertools = "0.12.0"
serde_json = "1.0"
bincode = "1.3.1"

Expand Down
3 changes: 1 addition & 2 deletions src/cluster/dbscan.rs
Original file line number Diff line number Diff line change
Expand Up @@ -315,8 +315,7 @@ impl<TX: Number, TY: Number, X: Array2<TX>, Y: Array1<TY>, D: Distance<Vec<TX>>>
}
}

while !neighbors.is_empty() {
let neighbor = neighbors.pop().unwrap();
while let Some(neighbor) = neighbors.pop() {
let index = neighbor.0;

if y[index] == outlier {
Expand Down
2 changes: 1 addition & 1 deletion src/dataset/diabetes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ pub fn load_dataset() -> Dataset<f32, u32> {
target: y,
num_samples,
num_features,
feature_names: vec![
feature_names: [
"Age", "Sex", "BMI", "BP", "S1", "S2", "S3", "S4", "S5", "S6",
]
.iter()
Expand Down
8 changes: 3 additions & 5 deletions src/dataset/digits.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,16 +25,14 @@ pub fn load_dataset() -> Dataset<f32, f32> {
target: y,
num_samples,
num_features,
feature_names: vec![
"sepal length (cm)",
feature_names: ["sepal length (cm)",
"sepal width (cm)",
"petal length (cm)",
"petal width (cm)",
]
"petal width (cm)"]
.iter()
.map(|s| s.to_string())
.collect(),
target_names: vec!["setosa", "versicolor", "virginica"]
target_names: ["setosa", "versicolor", "virginica"]
.iter()
.map(|s| s.to_string())
.collect(),
Expand Down
4 changes: 2 additions & 2 deletions src/dataset/iris.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ pub fn load_dataset() -> Dataset<f32, u32> {
target: y,
num_samples,
num_features,
feature_names: vec![
feature_names: [
"sepal length (cm)",
"sepal width (cm)",
"petal length (cm)",
Expand All @@ -45,7 +45,7 @@ pub fn load_dataset() -> Dataset<f32, u32> {
.iter()
.map(|s| s.to_string())
.collect(),
target_names: vec!["setosa", "versicolor", "virginica"]
target_names: ["setosa", "versicolor", "virginica"]
.iter()
.map(|s| s.to_string())
.collect(),
Expand Down
6 changes: 2 additions & 4 deletions src/linalg/basic/arrays.rs
Original file line number Diff line number Diff line change
Expand Up @@ -188,8 +188,7 @@ pub trait ArrayView1<T: Debug + Display + Copy + Sized>: Array<T, usize> {
_ => max,
}
};
self.iterator(0)
.fold(T::min_value(), |max, x| max_f(max, x))
self.iterator(0).fold(T::min_value(), max_f)
}
/// return min value from the view
fn min(&self) -> T
Expand All @@ -202,8 +201,7 @@ pub trait ArrayView1<T: Debug + Display + Copy + Sized>: Array<T, usize> {
_ => min,
}
};
self.iterator(0)
.fold(T::max_value(), |max, x| min_f(max, x))
self.iterator(0).fold(T::max_value(), min_f)
}
/// return the position of the max value of the view
fn argmax(&self) -> usize
Expand Down
14 changes: 7 additions & 7 deletions src/linalg/basic/matrix.rs
Original file line number Diff line number Diff line change
Expand Up @@ -495,9 +495,9 @@ impl<T: Number + RealNumber> SVDDecomposable<T> for DenseMatrix<T> {}
impl<'a, T: Debug + Display + Copy + Sized> Array<T, (usize, usize)> for DenseMatrixView<'a, T> {
fn get(&self, pos: (usize, usize)) -> &T {
if self.column_major {
&self.values[(pos.0 + pos.1 * self.stride)]
&self.values[pos.0 + pos.1 * self.stride]
} else {
&self.values[(pos.0 * self.stride + pos.1)]
&self.values[pos.0 * self.stride + pos.1]
}
}

Expand Down Expand Up @@ -559,9 +559,9 @@ impl<'a, T: Debug + Display + Copy + Sized> ArrayView1<T> for DenseMatrixView<'a
impl<'a, T: Debug + Display + Copy + Sized> Array<T, (usize, usize)> for DenseMatrixMutView<'a, T> {
fn get(&self, pos: (usize, usize)) -> &T {
if self.column_major {
&self.values[(pos.0 + pos.1 * self.stride)]
&self.values[pos.0 + pos.1 * self.stride]
} else {
&self.values[(pos.0 * self.stride + pos.1)]
&self.values[pos.0 * self.stride + pos.1]
}
}

Expand All @@ -583,9 +583,9 @@ impl<'a, T: Debug + Display + Copy + Sized> MutArray<T, (usize, usize)>
{
fn set(&mut self, pos: (usize, usize), x: T) {
if self.column_major {
self.values[(pos.0 + pos.1 * self.stride)] = x;
self.values[pos.0 + pos.1 * self.stride] = x;
} else {
self.values[(pos.0 * self.stride + pos.1)] = x;
self.values[pos.0 * self.stride + pos.1] = x;
}
}

Expand Down Expand Up @@ -775,7 +775,7 @@ mod tests {

#[test]
fn test_from_iterator() {
let data = vec![1, 2, 3, 4, 5, 6];
let data = [1, 2, 3, 4, 5, 6];

let m = DenseMatrix::from_iterator(data.iter(), 2, 3, 0);

Expand Down
22 changes: 21 additions & 1 deletion src/linalg/basic/vector.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,25 @@ pub struct VecView<'a, T: Debug + Display + Copy + Sized> {
ptr: &'a [T],
}

impl<T: Debug + Display + Copy + Sized> Array<T, usize> for &[T] {
fn get(&self, i: usize) -> &T {
&self[i]
}

fn shape(&self) -> usize {
self.len()
}

fn is_empty(&self) -> bool {
self.len() > 0
}

fn iterator<'b>(&'b self, axis: u8) -> Box<dyn Iterator<Item = &'b T> + 'b> {
assert!(axis == 0, "For one dimensional array `axis` should == 0");
Box::new(self.iter())
}
}

impl<T: Debug + Display + Copy + Sized> Array<T, usize> for Vec<T> {
fn get(&self, i: usize) -> &T {
&self[i]
Expand Down Expand Up @@ -47,6 +66,7 @@ impl<T: Debug + Display + Copy + Sized> MutArray<T, usize> for Vec<T> {
}

impl<T: Debug + Display + Copy + Sized> ArrayView1<T> for Vec<T> {}
impl<T: Debug + Display + Copy + Sized> ArrayView1<T> for &[T] {}

impl<T: Debug + Display + Copy + Sized> MutArrayView1<T> for Vec<T> {}

Expand Down Expand Up @@ -192,7 +212,7 @@ mod tests {

#[test]
fn test_len() {
let x = vec![1, 2, 3];
let x = [1, 2, 3];
assert_eq!(3, x.len());
}

Expand Down
2 changes: 1 addition & 1 deletion src/linear/bg_solver.rs
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ mod tests {
let a = DenseMatrix::from_2d_array(&[&[25., 15., -5.], &[15., 18., 0.], &[-5., 0., 11.]])
.unwrap();
let b = vec![40., 51., 28.];
let expected = vec![1.0, 2.0, 3.0];
let expected = [1.0, 2.0, 3.0];

let mut x = Vec::zeros(3);

Expand Down
6 changes: 1 addition & 5 deletions src/linear/logistic_regression.rs
Original file line number Diff line number Diff line change
Expand Up @@ -898,11 +898,7 @@ mod tests {

let y_hat = lr.predict(&x).unwrap();

let error: i32 = y
.into_iter()
.zip(y_hat.into_iter())
.map(|(a, b)| (a - b).abs())
.sum();
let error: i32 = y.into_iter().zip(y_hat).map(|(a, b)| (a - b).abs()).sum();

assert!(error <= 1);

Expand Down
4 changes: 2 additions & 2 deletions src/model_selection/hyper_tuning/grid_search.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@
use crate::{
api::{Predictor, SupervisedEstimator},
error::{Failed, FailedError},
linalg::basic::arrays::{Array2, Array1},
numbers::realnum::RealNumber,
linalg::basic::arrays::{Array1, Array2},
numbers::basenum::Number,
numbers::realnum::RealNumber,
};

use crate::model_selection::{cross_validate, BaseKFold, CrossValidationResult};
Expand Down
8 changes: 2 additions & 6 deletions src/model_selection/kfold.rs
Original file line number Diff line number Diff line change
Expand Up @@ -283,9 +283,7 @@ mod tests {
(vec![0, 1, 2, 3, 7, 8, 9], vec![4, 5, 6]),
(vec![0, 1, 2, 3, 4, 5, 6], vec![7, 8, 9]),
];
for ((train, test), (expected_train, expected_test)) in
k.split(&x).into_iter().zip(expected)
{
for ((train, test), (expected_train, expected_test)) in k.split(&x).zip(expected) {
assert_eq!(test, expected_test);
assert_eq!(train, expected_train);
}
Expand All @@ -307,9 +305,7 @@ mod tests {
(vec![0, 1, 2, 3, 7, 8, 9], vec![4, 5, 6]),
(vec![0, 1, 2, 3, 4, 5, 6], vec![7, 8, 9]),
];
for ((train, test), (expected_train, expected_test)) in
k.split(&x).into_iter().zip(expected)
{
for ((train, test), (expected_train, expected_test)) in k.split(&x).zip(expected) {
assert_eq!(test.len(), expected_test.len());
assert_eq!(train.len(), expected_train.len());
}
Expand Down
94 changes: 84 additions & 10 deletions src/naive_bayes/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ use crate::linalg::basic::arrays::{Array1, Array2, ArrayView1};
use crate::numbers::basenum::Number;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
use std::marker::PhantomData;
use std::{cmp::Ordering, marker::PhantomData};

/// Distribution used in the Naive Bayes classifier.
pub(crate) trait NBDistribution<X: Number, Y: Number>: Clone {
Expand Down Expand Up @@ -92,11 +92,10 @@ impl<TX: Number, TY: Number, X: Array2<TX>, Y: Array1<TY>, D: NBDistribution<TX,
/// Returns a vector of size N with class estimates.
pub fn predict(&self, x: &X) -> Result<Y, Failed> {
let y_classes = self.distribution.classes();
let (rows, _) = x.shape();
let predictions = (0..rows)
.map(|row_index| {
let row = x.get_row(row_index);
let (prediction, _probability) = y_classes
let predictions = x
.row_iter()
.map(|row| {
y_classes
.iter()
.enumerate()
.map(|(class_index, class)| {
Expand All @@ -106,11 +105,26 @@ impl<TX: Number, TY: Number, X: Array2<TX>, Y: Array1<TY>, D: NBDistribution<TX,
+ self.distribution.prior(class_index).ln(),
)
})
.max_by(|(_, p1), (_, p2)| p1.partial_cmp(p2).unwrap())
.unwrap();
*prediction
// For some reason, the max_by method cannot use NaNs for finding the maximum value, it panics.
// NaN must be considered as minimum values,
// therefore it's like NaNs would not be considered for choosing the maximum value.
// So we need to handle this case for avoiding panicking by using `Option::unwrap`.
.max_by(|(_, p1), (_, p2)| match p1.partial_cmp(p2) {
Some(ordering) => ordering,
None => {
if p1.is_nan() {
Ordering::Less
} else if p2.is_nan() {
Ordering::Greater
} else {
Ordering::Equal
}
}
})
.map(|(prediction, _probability)| *prediction)
.ok_or_else(|| Failed::predict("Failed to predict, there is no result"))
})
.collect::<Vec<TY>>();
.collect::<Result<Vec<TY>, Failed>>()?;
let y_hat = Y::from_vec_slice(&predictions);
Ok(y_hat)
}
Expand All @@ -119,3 +133,63 @@ pub mod bernoulli;
pub mod categorical;
pub mod gaussian;
pub mod multinomial;

#[cfg(test)]
mod tests {
use super::*;
use crate::linalg::basic::arrays::Array;
use crate::linalg::basic::matrix::DenseMatrix;
use num_traits::float::Float;

type Model<'d> = BaseNaiveBayes<i32, i32, DenseMatrix<i32>, Vec<i32>, TestDistribution<'d>>;

#[derive(Debug, PartialEq, Clone)]
struct TestDistribution<'d>(&'d Vec<i32>);

impl<'d> NBDistribution<i32, i32> for TestDistribution<'d> {
fn prior(&self, _class_index: usize) -> f64 {
1.
}

fn log_likelihood<'a>(
&'a self,
class_index: usize,
_j: &'a Box<dyn ArrayView1<i32> + 'a>,
) -> f64 {
match self.0.get(class_index) {
&v @ 2 | &v @ 10 | &v @ 20 => v as f64,
_ => f64::nan(),
}
}

fn classes(&self) -> &Vec<i32> {
&self.0
}
}

#[test]
fn test_predict() {
let matrix = DenseMatrix::from_2d_array(&[&[1, 2, 3], &[4, 5, 6], &[7, 8, 9]]);

let val = vec![];
match Model::fit(TestDistribution(&val)).unwrap().predict(&matrix) {

Check failure on line 175 in src/naive_bayes/mod.rs

View workflow job for this annotation

GitHub Actions / tests (ubuntu, wasm32-unknown-unknown)

mismatched types

Check failure on line 175 in src/naive_bayes/mod.rs

View workflow job for this annotation

GitHub Actions / tests (ubuntu, wasm32-wasi)

mismatched types

Check failure on line 175 in src/naive_bayes/mod.rs

View workflow job for this annotation

GitHub Actions / coverage

mismatched types
Ok(_) => panic!("Should return error in case of empty classes"),
Err(err) => assert_eq!(
err.to_string(),
"Predict failed: Failed to predict, there is no result"
),
}

let val = vec![1, 2, 3];
match Model::fit(TestDistribution(&val)).unwrap().predict(&matrix) {

Check failure on line 184 in src/naive_bayes/mod.rs

View workflow job for this annotation

GitHub Actions / tests (ubuntu, wasm32-unknown-unknown)

mismatched types

Check failure on line 184 in src/naive_bayes/mod.rs

View workflow job for this annotation

GitHub Actions / tests (ubuntu, wasm32-wasi)

mismatched types
Ok(r) => assert_eq!(r, vec![2, 2, 2]),
Err(_) => panic!("Should success in normal case with NaNs"),
}

let val = vec![20, 2, 10];
match Model::fit(TestDistribution(&val)).unwrap().predict(&matrix) {

Check failure on line 190 in src/naive_bayes/mod.rs

View workflow job for this annotation

GitHub Actions / tests (ubuntu, wasm32-unknown-unknown)

mismatched types

Check failure on line 190 in src/naive_bayes/mod.rs

View workflow job for this annotation

GitHub Actions / tests (ubuntu, wasm32-wasi)

mismatched types
Ok(r) => assert_eq!(r, vec![20, 20, 20]),
Err(_) => panic!("Should success in normal case without NaNs"),
}
}
}
4 changes: 2 additions & 2 deletions src/neighbors/knn_regressor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -298,7 +298,7 @@ mod tests {
DenseMatrix::from_2d_array(&[&[1., 2.], &[3., 4.], &[5., 6.], &[7., 8.], &[9., 10.]])
.unwrap();
let y: Vec<f64> = vec![1., 2., 3., 4., 5.];
let y_exp = vec![1., 2., 3., 4., 5.];
let y_exp = [1., 2., 3., 4., 5.];
let knn = KNNRegressor::fit(
&x,
&y,
Expand Down Expand Up @@ -326,7 +326,7 @@ mod tests {
DenseMatrix::from_2d_array(&[&[1., 2.], &[3., 4.], &[5., 6.], &[7., 8.], &[9., 10.]])
.unwrap();
let y: Vec<f64> = vec![1., 2., 3., 4., 5.];
let y_exp = vec![2., 2., 3., 4., 4.];
let y_exp = [2., 2., 3., 4., 4.];
let knn = KNNRegressor::fit(&x, &y, Default::default()).unwrap();
let y_hat = knn.predict(&x).unwrap();
assert_eq!(5, Vec::len(&y_hat));
Expand Down
2 changes: 1 addition & 1 deletion src/preprocessing/categorical.rs
Original file line number Diff line number Diff line change
Expand Up @@ -281,7 +281,7 @@ mod tests {
)]
#[test]
fn hash_encode_f64_series() {
let series = vec![3.0, 1.0, 2.0, 1.0];
let series = [3.0, 1.0, 2.0, 1.0];
let hashable_series: Vec<CategoricalFloat> =
series.iter().map(|v| v.to_category()).collect();
let enc = CategoryMapper::from_positional_category_vec(hashable_series);
Expand Down
Loading

0 comments on commit ea74b32

Please sign in to comment.