use mathru::algebra::linear::Matrix;
use mathru::algebra::linear::Vector;
use mathru::optim::{OptimAlgorithm, Optimizable};
use crate::model::SupervisedLearn;
use rand::Rng;
pub struct LogisticRegression<O>
where O: OptimAlgorithm<LogisticRegressionBase>
{
base: LogisticRegressionBase,
optim: O
}
impl<O> LogisticRegression<O>
where O: OptimAlgorithm<LogisticRegressionBase>
{
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
{
type Input = Matrix<f64>;
type Target = Vector<f64>;
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)
}
}