Skip to content
This repository was archived by the owner on Jul 16, 2021. It is now read-only.
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,4 @@ datasets = []
[dependencies]
num = { version = "0.1.35", default-features = false }
rand = "0.3.14"
rulinalg = "0.3.7"
rulinalg = "0.4.0"
4 changes: 2 additions & 2 deletions examples/k-means_generating_cluster.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,10 @@ fn generate_data(centroids: &Matrix<f64>,

for _ in 0..points_per_centroid {
// Generate points from each centroid
for centroid in centroids.iter_rows() {
for centroid in centroids.row_iter() {
// Generate a point randomly around the centroid
let mut point = Vec::with_capacity(centroids.cols());
for feature in centroid {
for feature in centroid.iter() {
point.push(feature + normal_rv.ind_sample(&mut rng));
}

Expand Down
12 changes: 6 additions & 6 deletions examples/naive_bayes_dogs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -135,16 +135,16 @@ fn main() {
// Score how well we did.
let mut hits = 0;
let unprinted_total = test_set_size.saturating_sub(10) as usize;
for (dog, prediction) in test_dogs.iter().zip(predictions.iter_rows()).take(unprinted_total) {
evaluate_prediction(&mut hits, dog, prediction);
for (dog, prediction) in test_dogs.iter().zip(predictions.row_iter()).take(unprinted_total) {
evaluate_prediction(&mut hits, dog, prediction.raw_slice());
}

if unprinted_total > 0 {
println!("...");
}
for (dog, prediction) in test_dogs.iter().zip(predictions.iter_rows()).skip(unprinted_total) {
let (actual_color, accurate) = evaluate_prediction(&mut hits, dog, prediction);

for (dog, prediction) in test_dogs.iter().zip(predictions.row_iter()).skip(unprinted_total) {
let (actual_color, accurate) = evaluate_prediction(&mut hits, dog, prediction.raw_slice());
println!("Predicted: {:?}; Actual: {:?}; Accurate? {:?}",
dog.color, actual_color, accurate);
}
Expand Down
49 changes: 46 additions & 3 deletions src/analysis/score.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,24 @@ pub fn accuracy<I>(outputs: I, targets: I) -> f64
correct as f64 / len
}


/// Returns the fraction of outputs rows which match their target.
pub fn row_accuracy(outputs: &Matrix<f64>, targets: &Matrix<f64>) -> f64 {
accuracy(outputs.iter_rows(), targets.iter_rows())
pub fn row_accuracy<T: PartialEq>(outputs: &Matrix<T>, targets: &Matrix<T>) -> f64 {

assert!(outputs.rows() == targets.rows());
let len = outputs.rows() as f64;

let correct = outputs.row_iter()
.zip(targets.row_iter())
.filter(|&(ref x, ref y)| x.raw_slice()
.iter()
.zip(y.raw_slice())
.all(|(v1, v2)| v1 == v2))
.count();
correct as f64 / len

// Row doesn't impl PartialEq
// accuracy(outputs.row_iter(), targets.row_iter())
}

/// Returns the precision score for 2 class classification.
Expand Down Expand Up @@ -212,7 +227,7 @@ pub fn neg_mean_squared_error(outputs: &Matrix<f64>, targets: &Matrix<f64>) -> f
#[cfg(test)]
mod tests {
use linalg::Matrix;
use super::{accuracy, precision, recall, f1, neg_mean_squared_error};
use super::{accuracy, row_accuracy, precision, recall, f1, neg_mean_squared_error};

#[test]
fn test_accuracy() {
Expand Down Expand Up @@ -331,6 +346,34 @@ mod tests {
f1(outputs.iter(), targets.iter());
}

#[test]
fn test_row_accuracy() {
let outputs = matrix![1, 0;
0, 1;
1, 0];
let targets = matrix![1, 0;
0, 1;
1, 0];
assert_eq!(row_accuracy(&outputs, &targets), 1.0);

let outputs = matrix![1, 0;
0, 1;
1, 0];
let targets = matrix![0, 1;
0, 1;
1, 0];
assert_eq!(row_accuracy(&outputs, &targets), 2. / 3.);

let outputs = matrix![1., 0.;
0., 1.;
1., 0.];
let targets = matrix![0., 1.;
0., 1.;
1., 0.];
assert_eq!(row_accuracy(&outputs, &targets), 2. / 3.);
}


#[test]
fn test_neg_mean_squared_error_1d() {
let outputs = Matrix::new(3, 1, vec![1f64, 2f64, 3f64]);
Expand Down
12 changes: 6 additions & 6 deletions src/data/transforms/minmax.rs
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ impl<T: Float> Transformer<Matrix<T>> for MinMaxScaler<T> {
// https://github.com/AtheMathmo/rulinalg/pull/115
let mut input_min_max = vec![(T::max_value(), T::min_value()); features];

for row in inputs.iter_rows() {
for row in inputs.row_iter() {
for (idx, (feature, min_max)) in row.into_iter().zip(input_min_max.iter_mut()).enumerate() {
if !feature.is_finite() {
return Err(Error::new(ErrorKind::InvalidData,
Expand Down Expand Up @@ -129,6 +129,7 @@ impl<T: Float> Transformer<Matrix<T>> for MinMaxScaler<T> {
.map(|(&(_, x), &s)| self.scaled_max - x * s)
.collect::<Vec<_>>();

<<<<<<< HEAD
self.scale_factors = Some(Vector::new(scales));
self.const_factors = Some(Vector::new(consts));
Ok(())
Expand All @@ -145,15 +146,14 @@ impl<T: Float> Transformer<Matrix<T>> for MinMaxScaler<T> {
Err(Error::new(ErrorKind::InvalidData,
"Input data has different number of columns from fitted data."))
} else {
for row in inputs.iter_rows_mut() {
utils::in_place_vec_bin_op(row, scales.data(), |x, &y| {
for mut row in inputs.row_iter_mut() {
utils::in_place_vec_bin_op(&mut row.raw_slice_mut(), &scales, |x, &y| {
*x = *x * y;
});

utils::in_place_vec_bin_op(row, consts.data(), |x, &y| {
utils::in_place_vec_bin_op(&mut row.raw_slice_mut(), &consts, |x, &y| {
*x = *x + y;
});
}
Ok(inputs)
}
} else {
Expand All @@ -174,7 +174,7 @@ impl<T: Float> Invertible<Matrix<T>> for MinMaxScaler<T> {
"Inputs have different feature count than transformer."));
}

for row in inputs.iter_rows_mut() {
for mut row in inputs.row_iter_mut() {
for i in 0..features {
row[i] = (row[i] - consts[i]) / scales[i];
}
Expand Down
12 changes: 6 additions & 6 deletions src/data/transforms/standardize.rs
Original file line number Diff line number Diff line change
Expand Up @@ -118,10 +118,10 @@ impl<T: Float + FromPrimitive> Transformer<Matrix<T>> for Standardizer<T> {
Err(Error::new(ErrorKind::InvalidData,
"Input data has different number of columns from fitted data."))
} else {
for row in inputs.iter_rows_mut() {
for mut row in inputs.row_iter_mut() {
// Subtract the mean
utils::in_place_vec_bin_op(row, means.data(), |x, &y| *x = *x - y);
utils::in_place_vec_bin_op(row, variances.data(), |x, &y| {
utils::in_place_vec_bin_op(&mut row.raw_slice_mut(), &mean.data(), |x, &y| *x = *x - y);
utils::in_place_vec_bin_op(&mut row.raw_slice_mut(), &variance.data(), |x, &y| {
*x = (*x * self.scaled_stdev / y.sqrt()) + self.scaled_mean
});
}
Expand All @@ -143,13 +143,13 @@ impl<T: Float + FromPrimitive> Invertible<Matrix<T>> for Standardizer<T> {
"Inputs have different feature count than transformer."));
}

for row in inputs.iter_rows_mut() {
utils::in_place_vec_bin_op(row, &variances.data(), |x, &y| {
for mut row in inputs.row_iter_mut() {
utils::in_place_vec_bin_op(&mut row.raw_slice_mut(), &variances.data(), |x, &y| {
*x = (*x - self.scaled_mean) * y.sqrt() / self.scaled_stdev
});

// Add the mean
utils::in_place_vec_bin_op(row, &means.data(), |x, &y| *x = *x + y);
utils::in_place_vec_bin_op(&mut row.raw_slice_mut(), &means.data(), |x, &y| *x = *x + y);
}

Ok(inputs)
Expand Down
20 changes: 11 additions & 9 deletions src/learning/dbscan.rs
Original file line number Diff line number Diff line change
Expand Up @@ -80,13 +80,13 @@ impl UnSupModel<Matrix<f64>, Vector<Option<usize>>> for DBSCAN {
self.init_params(inputs.rows());
let mut cluster = 0;

for (idx, point) in inputs.iter_rows().enumerate() {
for (idx, point) in inputs.row_iter().enumerate() {
let visited = self._visited[idx];

if !visited {
self._visited[idx] = true;

let neighbours = self.region_query(point, inputs);
let neighbours = self.region_query(point.raw_slice(), inputs);

if neighbours.len() >= self.min_points {
self.expand_cluster(inputs, idx, neighbours, cluster);
Expand All @@ -108,12 +108,14 @@ impl UnSupModel<Matrix<f64>, Vector<Option<usize>>> for DBSCAN {
&self.clusters) {
let mut classes = Vec::with_capacity(inputs.rows());

for input_point in inputs.iter_rows() {
for input_point in inputs.row_iter() {
let mut distances = Vec::with_capacity(cluster_data.rows());

for cluster_point in cluster_data.iter_rows() {
for cluster_point in cluster_data.row_iter() {
let point_distance =
utils::vec_bin_op(input_point, cluster_point, |x, y| x - y);
utils::vec_bin_op(input_point.raw_slice(),
cluster_point.raw_slice(),
|x, y| x - y);
distances.push(utils::dot(&point_distance, &point_distance).sqrt());
}

Expand Down Expand Up @@ -182,8 +184,8 @@ impl DBSCAN {
let visited = self._visited[*data_point_idx];
if !visited {
self._visited[*data_point_idx] = true;
let data_point_row = unsafe { inputs.get_row_unchecked(*data_point_idx) };
let sub_neighbours = self.region_query(data_point_row, inputs);
let data_point_row = unsafe { inputs.row_unchecked(*data_point_idx) };
let sub_neighbours = self.region_query(data_point_row.raw_slice(), inputs);

if sub_neighbours.len() >= self.min_points {
self.expand_cluster(inputs, *data_point_idx, sub_neighbours, cluster);
Expand All @@ -198,8 +200,8 @@ impl DBSCAN {
"point must be of same dimension as inputs");

let mut in_neighbourhood = Vec::new();
for (idx, data_point) in inputs.iter_rows().enumerate() {
let point_distance = utils::vec_bin_op(data_point, point, |x, y| x - y);
for (idx, data_point) in inputs.row_iter().enumerate() {
let point_distance = utils::vec_bin_op(data_point.raw_slice(), point, |x, y| x - y);
let dist = utils::dot(&point_distance, &point_distance).sqrt();

if dist < self.eps {
Expand Down
14 changes: 7 additions & 7 deletions src/learning/gmm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@ impl GaussianMixtureModel {
if mixture_weights.size() != k {
Err(Error::new(ErrorKind::InvalidParameters, "Mixture weights must have length k."))
} else if mixture_weights.data().iter().any(|&x| x < 0f64) {
Err(Error::new(ErrorKind::InvalidParameters, "Mixture weights must have only non-negative entries."))
Err(Error::new(ErrorKind::InvalidParameters, "Mixture weights must have only non-negative entries."))
} else {
let sum = mixture_weights.sum();
let normalized_weights = mixture_weights / sum;
Expand Down Expand Up @@ -233,9 +233,9 @@ impl GaussianMixtureModel {
CovOption::Full | CovOption::Regularized(_) => {
let means = inputs.mean(Axes::Row);
let mut cov_mat = Matrix::zeros(inputs.cols(), inputs.cols());
for (j, row) in cov_mat.iter_rows_mut().enumerate() {
for (j, mut row) in cov_mat.row_iter_mut().enumerate() {
for (k, elem) in row.iter_mut().enumerate() {
*elem = inputs.iter_rows().map(|r| {
*elem = inputs.row_iter().map(|r| {
(r[j] - means[j]) * (r[k] - means[k])
}).sum::<f64>();
}
Expand All @@ -259,10 +259,10 @@ impl GaussianMixtureModel {
let mut cov_invs = Vec::with_capacity(self.comp_count);

if let Some(ref covars) = self.model_covars {
for cov in covars {
for cov in covars.iter() {
// TODO: combine these. We compute det to get the inverse.
let covar_det = cov.det();
let covar_inv = try!(cov.inverse().map_err(Error::from));
let covar_det = cov.clone().det();
let covar_inv = try!(cov.clone().inverse().map_err(Error::from));

cov_sqrt_dets.push(covar_det.sqrt());
cov_invs.push(covar_inv);
Expand Down Expand Up @@ -309,7 +309,7 @@ impl GaussianMixtureModel {

let mut new_means = membership_weights.transpose() * inputs;

for (mean, w) in new_means.iter_rows_mut().zip(sum_weights.data().iter()) {
for (mut mean, w) in new_means.row_iter_mut().zip(sum_weights.data().iter()) {
for m in mean.iter_mut() {
*m /= *w;
}
Expand Down
10 changes: 5 additions & 5 deletions src/learning/gp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -131,9 +131,9 @@ impl<T: Kernel, U: MeanFunc> GaussianProcess<T, U> {
let dim2 = m2.rows();

let mut ker_data = Vec::with_capacity(dim1 * dim2);
ker_data.extend(m1.iter_rows().flat_map(|row1| {
m2.iter_rows()
.map(move |row2| self.ker.kernel(row1, row2))
ker_data.extend(m1.row_iter().flat_map(|row1| {
m2.row_iter()
.map(move |row2| self.ker.kernel(row1.raw_slice(), row2.raw_slice()))
}));

Ok(Matrix::new(dim1, dim2, ker_data))
Expand Down Expand Up @@ -195,8 +195,8 @@ impl<T: Kernel, U: MeanFunc> GaussianProcess<T, U> {

let test_mat = try!(self.ker_mat(inputs, t_data));
let mut var_data = Vec::with_capacity(inputs.rows() * inputs.cols());
for row in test_mat.iter_rows() {
let test_point = Vector::new(row.to_vec());
for row in test_mat.row_iter() {
let test_point = Vector::new(row.raw_slice());
var_data.append(&mut t_mat.solve_l_triangular(test_point).unwrap().into_vec());
}

Expand Down
4 changes: 2 additions & 2 deletions src/learning/k_means.rs
Original file line number Diff line number Diff line change
Expand Up @@ -330,7 +330,7 @@ impl Initializer for KPlusPlus {
let first_cen = rng.gen_range(0usize, inputs.rows());

unsafe {
init_centroids.extend_from_slice(inputs.get_row_unchecked(first_cen));
init_centroids.extend_from_slice(inputs.row_unchecked(first_cen).raw_slice());
}

for i in 1..k {
Expand All @@ -350,7 +350,7 @@ impl Initializer for KPlusPlus {
}

let next_cen = sample_discretely(dist);
init_centroids.extend_from_slice(inputs.get_row_unchecked(next_cen));
init_centroids.extend_from_slice(inputs.row_unchecked(next_cen).raw_slice());
}
}

Expand Down
8 changes: 4 additions & 4 deletions src/learning/naive_bayes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -152,9 +152,9 @@ impl<T: Distribution> NaiveBayes<T> {
self.class_counts = vec![0; class_count];
let mut class_data = vec![Vec::new(); class_count];

for (idx, row) in targets.iter_rows().enumerate() {
for (idx, row) in targets.row_iter().enumerate() {
// Find the class of this input
let class = try!(NaiveBayes::<T>::find_class(row));
let class = try!(NaiveBayes::<T>::find_class(row.raw_slice()));

// Note the class of the input
class_data[class].push(idx);
Expand Down Expand Up @@ -199,9 +199,9 @@ impl<T: Distribution> NaiveBayes<T> {
fn get_classes(log_probs: Matrix<f64>) -> Vec<usize> {
let mut data_classes = Vec::with_capacity(log_probs.rows());

data_classes.extend(log_probs.iter_rows().map(|row| {
data_classes.extend(log_probs.row_iter().map(|row| {
// Argmax each class log-probability per input
let (class, _) = utils::argmax(row);
let (class, _) = utils::argmax(row.raw_slice());
class
}));

Expand Down
2 changes: 1 addition & 1 deletion src/learning/nnet.rs
Original file line number Diff line number Diff line change
Expand Up @@ -247,7 +247,7 @@ impl<'a, T: Criterion> BaseNeuralNet<'a, T> {
/// Gets the weights for a layer excluding the bias weights.
fn get_non_bias_weights(&self, weights: &[f64], idx: usize) -> MatrixSlice<f64> {
let layer_weights = self.get_layer_weights(weights, idx);
layer_weights.reslice([1, 0], layer_weights.rows() - 1, layer_weights.cols())
layer_weights.sub_slice([1, 0], layer_weights.rows() - 1, layer_weights.cols())
}

/// Compute the gradient using the back propagation algorithm.
Expand Down
10 changes: 5 additions & 5 deletions src/learning/svm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -111,9 +111,9 @@ impl<K: Kernel> SVM<K> {
let dim2 = m2.rows();

let mut ker_data = Vec::with_capacity(dim1 * dim2);
ker_data.extend(m1.iter_rows().flat_map(|row1| {
m2.iter_rows()
.map(move |row2| self.ker.kernel(row1, row2))
ker_data.extend(m1.row_iter().flat_map(|row1| {
m2.row_iter()
.map(move |row2| self.ker.kernel(row1.raw_slice(), row2.raw_slice()))
}));

Ok(Matrix::new(dim1, dim2, ker_data))
Expand Down Expand Up @@ -154,8 +154,8 @@ impl<K: Kernel> SupModel<Matrix<f64>, Vector<f64>> for SVM<K> {
for t in 0..self.optim_iters {
let i = rng.gen_range(0, n);
let row_i = full_inputs.select_rows(&[i]);
let sum = full_inputs.iter_rows()
.fold(0f64, |sum, row| sum + self.ker.kernel(row_i.data(), row)) *
let sum = full_inputs.row_iter()
.fold(0f64, |sum, row| sum + self.ker.kernel(row_i.data(), row.raw_slice())) *
targets[i] / (self.lambda * (t as f64));

if sum < 1f64 {
Expand Down
Loading