use mathru::algebra::linear::Matrix;
use mathru::algebra::linear::Vector;
use mathru::optimization::Gradient;
use crate::model::SupervisedLearn;
use mathru::algebra::abstr::Real;
use rand::Rng;
use mathru::optimization::{Optim};
use mathru::algebra::linear::matrix::Transpose;
pub struct LogisticRegression<T>
{
beta: Option<Vector<T>>,
optim: Gradient<T>,
}
impl<T> LogisticRegression<T>
where T: Real
{
pub fn new(optim: Gradient<T>) -> LogisticRegression<T>
{
LogisticRegression
{
beta: None,
optim: optim
}
}
pub fn parameters(self: &Self) -> Option<Vector<T>>
{
self.beta.clone()
}
}
impl<T> SupervisedLearn<Matrix<T>, Vector<T>> for LogisticRegression<T>
where T: Real
{
fn predict(self: &Self, x: &Matrix<T>) -> Result<Vector<T>, ()>
{
let (m, n) : (usize, usize) = x.dim();
let (_input_m, _input_n): (usize, usize) = x.dim();
let ones: Matrix<T> = Matrix::<T>::ones(m, n + 1);
let full_input: Matrix<T> = ones.set_slice( x, 0, 1);
return match self.beta.clone()
{
Some(beta) => Ok(LogisticRegressionBase::h_x(&beta, &full_input)),
None => Err(())
}
}
fn train(self: &mut Self, x: &Matrix<T>, y: &Vector<T>)
{
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<T> = Matrix::<T>::ones(x_m, x_n + 1);
let full_input: Matrix<T> = ones.set_slice( x, 0, 1);
let mut rng = rand::thread_rng();
let beta_0: Vector<T> = Vector::new_column(x_n + 1, vec![T::from_f64(rng.gen_range(-1.0, 1.0)); x_n + 1]);
let base: LogisticRegressionBase<T> = LogisticRegressionBase::new(y, &full_input);
let beta: Vector<T> = self.optim.minimize(&base, &beta_0).arg();
self.beta = Some(beta);
}
}
pub struct LogisticRegressionBase<'a, T>
{
pub x: &'a Matrix<T>,
pub y: &'a Vector<T>,
}
impl<'a, T> LogisticRegressionBase<'a, T>
where T: Real
{
fn new(y: &'a Vector<T>, x: &'a Matrix<T>) -> LogisticRegressionBase<'a, T>
{
LogisticRegressionBase
{
y: y,
x: x
}
}
}
impl<'a, T> Optim<T> for LogisticRegressionBase<'a, T>
where T: Real
{
fn eval(&self, beta: &Vector<T>) -> Vector<T>
{
let (m, _n) : (usize, usize) = self.x.dim();
let mut cost: T = T::zero();
for i in 0..m
{
let x_i: Vector<T> = self.x.get_row(i);
let y_hat_i: T = self.h_xi(beta, &x_i);
let y_i: T = *self.y.get(i);
let cost_i: T = y_i * (y_hat_i.ln()) + (T::one() - y_i) * (T::one() - y_hat_i).ln();
cost += cost_i;
}
return Vector::new_column(1, vec![-cost]);
}
fn jacobian(&self, beta: &Vector<T>) -> Matrix<T>
{
let y_hat: Vector<T> = LogisticRegressionBase::h_x(beta, self.x);
let diff = &y_hat - self.y;
let grad: Vector<T> = &(self.x.clone().transpose()) * &diff;
return Matrix::from( grad.transpose());
}
fn hessian(&self, _x: &Vector<T>) -> Matrix<T>
{
unimplemented!();
}
}
impl<'a, T> LogisticRegressionBase<'a, T>
where T: Real
{
fn h_xi(self: &'a Self, beta: &'a Vector<T>, x: &'a Vector<T>) -> T
{
let (_m, n): (usize, usize) = x.dim();
let mut y_hat: T = T::zero();
for k in 0..n
{
let x_k: T = x.get(k).clone();
let beta_k: T = beta.get(k).clone();
y_hat += beta_k * x_k;
}
let v: T = LogisticRegressionBase::sigmoid(&y_hat);
return v;
}
fn sigmoid(z: &T) -> T
{
T::one()/(T::one() + T::exp(-*z))
}
fn h_x(beta: &Vector<T>, x: &Matrix<T>) -> Vector<T>
{
(x * beta).apply(&LogisticRegressionBase::sigmoid)
}
}