tinguely 0.1.0

Machine learning library
Documentation
use mathru::algebra::linear::Matrix;
use mathru::algebra::linear::Vector;
use mathru::optim::{OptimAlgorithm, Optimizable};
use crate::model::SupervisedLearn;
use rand::Rng;

/// Logistic regression
///
/// Fore more information: <br>
/// <a href="https://en.wikipedia.org/wiki/Logistic_regression">https://en.wikipedia.org/wiki/Logistic_regression</a>
///
/// # Examples
///
///
pub struct LogisticRegression<O>
	where O: OptimAlgorithm<LogisticRegressionBase>
{
	base: LogisticRegressionBase,
	optim: O
}

impl<O> LogisticRegression<O>
	where O: OptimAlgorithm<LogisticRegressionBase>
{
	/// Creates an instance
	///
	/// # Arguments
	/// * 'optim': Optimization algorithm, which is used to optimize  the model
	///
	/// # Returns
	/// LogisticRegression instance
	pub fn new(optim: O) -> LogisticRegression<O>
	{
		LogisticRegression
		{
			base: LogisticRegressionBase::new(),
			optim: optim
		}
	}

  	pub fn parameter(self: &Self) -> Vector<f64>
  	{
        self.base.beta.clone()
	}
}

impl<O> SupervisedLearn<Matrix<f64>, Vector<f64>> for LogisticRegression<O>
	where O: OptimAlgorithm<LogisticRegressionBase>
{
	///
	///
    fn train<'a, 'b>(self: &'a mut Self, x: &'b Matrix<f64>, y: &'b Vector<f64>)
	{
		let (x_m, x_n): (usize, usize) = x.dim();
		let (y_m, _y_n): (usize, usize) = x.dim();

		if x_m != y_m
		{
			panic!("Dimension mismatch")
		}

        let ones: Matrix<f64> = Matrix::<f64>::ones(x_m, x_n + 1);
        let full_input: Matrix<f64> = ones.set_slice( x, 0, 1);
		let mut rng = rand::thread_rng();
		let beta_0: Vector<f64> = Vector::new_column(x_n + 1, vec![rng.gen_range(0.0, 1.0); x_n + 1]);

		let beta: Vector<f64> = self.optim.minimize(&self.base, &beta_0, &full_input, y);
		self.base.beta = beta;
	}

    fn predict<'a, 'b>(self: &'a Self, x: &'b Matrix<f64>) -> Vector<f64>
	{

		let (m, n) : (usize, usize) = x.dim();
    	let (_input_m, _input_n): (usize, usize) = x.dim();

      	let ones: Matrix<f64> = Matrix::<f64>::ones(m, n  + 1);

      	let full_input: Matrix<f64> = ones.set_slice( x, 0, 1);

       	let y_hat: Vector<f64> = self.base.h_x(&self.base.beta, &full_input);

		return y_hat;

	}
}


pub struct LogisticRegressionBase
{
	pub beta: Vector<f64>,
}

impl LogisticRegressionBase
{
	fn new() -> LogisticRegressionBase
	{
		LogisticRegressionBase
		{
			beta: Vector::zero(2)
		}
	}
}


impl Optimizable for LogisticRegressionBase
{
	/// The input data type to the model.
    type Input = Matrix<f64>;

    /// The target data type to the model.
    type Target = Vector<f64>;

    /// Compute the gradient for the model.
    fn value(&self, param: &Vector<f64>, input: &Self::Input, target: &Self::Target) -> f64
	{
		let (m, _n) : (usize, usize) = input.dim();

		let mut cost: f64 = 0.0;

		for i in 0..m
		{
			let x_i: Vector<f64> = input.get_row(&i);
			let y_hat_i: f64 = self.h_xi(param, &x_i);

			let y_i: f64 = target.get(&i).clone();
			let cost_i: f64 = y_i * (y_hat_i.ln()) + (1.0 - y_i) * (1.0 - y_hat_i).ln();
			cost += cost_i;
		}

		cost
	}

	fn gradient(&self, param: &Vector<f64>, input: &Self::Input, target: &Self::Target) -> Vector<f64>
	{
		let y_hat: Vector<f64> = self.h_x(param, input);

        let diff = &y_hat - target;

        let grad: Vector<f64> = &(input.transpose()) * &diff;

		return grad;
	}
}

impl LogisticRegressionBase
{
	fn h_xi(self: &Self, beta: &Vector<f64>, x: &Vector<f64>) -> f64
	{
		let (_m, n): (usize, usize) = x.dim();

		let mut y_hat: f64 = 0.0;

		for k in 0..n
		{
			let x_k: f64 = *(x.get(&k));
			let beta_k: f64 = *(beta.get(&(k)));
			y_hat += beta_k * x_k;
		}

		let v: f64 = LogisticRegressionBase::sigmoid(&y_hat);

		return v;
	}

	fn sigmoid(z: &f64) -> f64
	{
		1.0/(1.0 +  f64::exp(-*z))
	}

	fn h_x(self: &Self, beta: &Vector<f64>, x: &Matrix<f64>) -> Vector<f64>
	{
		(x * beta).apply(&LogisticRegressionBase::sigmoid)
	}
}