use linalg::Vector;
use linalg::{Matrix, BaseMatrix};
use learning::{LearningResult, SupModel};
use learning::error::{Error, ErrorKind};
#[derive(Debug)]
pub struct GenLinearModel<C: Criterion> {
parameters: Option<Vector<f64>>,
criterion: C,
}
impl<C: Criterion> GenLinearModel<C> {
pub fn new(criterion: C) -> GenLinearModel<C> {
GenLinearModel {
parameters: None,
criterion: criterion,
}
}
}
impl<C: Criterion> SupModel<Matrix<f64>, Vector<f64>> for GenLinearModel<C> {
fn predict(&self, inputs: &Matrix<f64>) -> LearningResult<Vector<f64>> {
if let Some(ref v) = self.parameters {
let ones = Matrix::<f64>::ones(inputs.rows(), 1);
let full_inputs = ones.hcat(inputs);
Ok(self.criterion.apply_link_inv(full_inputs * v))
} else {
Err(Error::new_untrained())
}
}
fn train(&mut self, inputs: &Matrix<f64>, targets: &Vector<f64>) -> LearningResult<()> {
let n = inputs.rows();
if n != targets.size() {
return Err(Error::new(ErrorKind::InvalidData,
"Training data do not have the same dimensions"));
}
let mut mu = Vector::new(self.criterion.initialize_mu(targets.data()));
let mut z = mu.clone();
let mut beta: Vector<f64> = Vector::new(vec![0f64; inputs.cols() + 1]);
let ones = Matrix::<f64>::ones(inputs.rows(), 1);
let full_inputs = ones.hcat(inputs);
let x_t = full_inputs.transpose();
for _ in 0..8 {
let w_diag = self.criterion.compute_working_weight(mu.data());
let y_bar_data = self.criterion.compute_y_bar(targets.data(), mu.data());
let w = Matrix::from_diag(&w_diag);
let y_bar = Vector::new(y_bar_data);
let x_t_w = &x_t * w;
let new_beta = (&x_t_w * &full_inputs)
.inverse()
.expect("Could not compute input data inverse.") *
x_t_w * z;
let diff = (beta - &new_beta).apply(&|x| x.abs()).sum();
beta = new_beta;
if diff < 1e-10 {
break;
}
let fitted = &full_inputs * β
z = y_bar + &fitted;
mu = self.criterion.apply_link_inv(fitted);
}
self.parameters = Some(beta);
Ok(())
}
}
pub trait Criterion {
type Link: LinkFunc;
fn model_variance(&self, mu: f64) -> f64;
fn initialize_mu(&self, y: &[f64]) -> Vec<f64> {
y.to_vec()
}
fn compute_working_weight(&self, mu: &[f64]) -> Vec<f64> {
let mut working_weights_vec = Vec::with_capacity(mu.len());
for m in mu {
let grad = self.link_grad(*m);
working_weights_vec.push(1f64 / (self.model_variance(*m) * grad * grad));
}
working_weights_vec
}
fn compute_y_bar(&self, y: &[f64], mu: &[f64]) -> Vec<f64> {
let mut y_bar_vec = Vec::with_capacity(mu.len());
for (idx, m) in mu.iter().enumerate() {
y_bar_vec.push(self.link_grad(*m) * (y[idx] - m));
}
y_bar_vec
}
fn apply_link_func(&self, vec: Vector<f64>) -> Vector<f64> {
vec.apply(&Self::Link::func)
}
fn apply_link_inv(&self, vec: Vector<f64>) -> Vector<f64> {
vec.apply(&Self::Link::func_inv)
}
fn link_grad(&self, mu: f64) -> f64 {
Self::Link::func_grad(mu)
}
}
pub trait LinkFunc {
fn func(x: f64) -> f64;
fn func_grad(x: f64) -> f64;
fn func_inv(x: f64) -> f64;
}
#[derive(Clone, Copy, Debug)]
pub struct Logit;
impl LinkFunc for Logit {
fn func(x: f64) -> f64 {
(x / (1f64 - x)).ln()
}
fn func_grad(x: f64) -> f64 {
1f64 / (x * (1f64 - x))
}
fn func_inv(x: f64) -> f64 {
1.0 / (1.0 + (-x).exp())
}
}
#[derive(Clone, Copy, Debug)]
pub struct Log;
impl LinkFunc for Log {
fn func(x: f64) -> f64 {
x.ln()
}
fn func_grad(x: f64) -> f64 {
1f64 / x
}
fn func_inv(x: f64) -> f64 {
x.exp()
}
}
#[derive(Clone, Copy, Debug)]
pub struct Identity;
impl LinkFunc for Identity {
fn func(x: f64) -> f64 {
x
}
fn func_grad(_: f64) -> f64 {
1f64
}
fn func_inv(x: f64) -> f64 {
x
}
}
#[derive(Clone, Copy, Debug)]
pub struct Bernoulli;
impl Criterion for Bernoulli {
type Link = Logit;
fn model_variance(&self, mu: f64) -> f64 {
let var = mu * (1f64 - mu);
if var.abs() < 1e-10 {
1e-10
} else {
var
}
}
fn initialize_mu(&self, y: &[f64]) -> Vec<f64> {
let mut mu_data = Vec::with_capacity(y.len());
for y_val in y {
mu_data.push(if *y_val < 1e-10 {
1e-10
} else if *y_val > 1f64 - 1e-10 {
1f64 - 1e-10
} else {
*y_val
});
}
mu_data
}
fn compute_working_weight(&self, mu: &[f64]) -> Vec<f64> {
let mut working_weights_vec = Vec::with_capacity(mu.len());
for m in mu {
let var = self.model_variance(*m);
working_weights_vec.push(if var.abs() < 1e-5 {
1e-5
} else {
var
});
}
working_weights_vec
}
fn compute_y_bar(&self, y: &[f64], mu: &[f64]) -> Vec<f64> {
let mut y_bar_vec = Vec::with_capacity(y.len());
for (idx, m) in mu.iter().enumerate() {
let target_diff = y[idx] - m;
y_bar_vec.push(if target_diff.abs() < 1e-15 {
0f64
} else {
self.link_grad(*m) * target_diff
});
}
y_bar_vec
}
}
#[derive(Debug)]
pub struct Binomial {
weights: Vec<f64>,
}
impl Criterion for Binomial {
type Link = Logit;
fn model_variance(&self, mu: f64) -> f64 {
let var = mu * (1f64 - mu);
if var.abs() < 1e-10 {
1e-10
} else {
var
}
}
fn initialize_mu(&self, y: &[f64]) -> Vec<f64> {
let mut mu_data = Vec::with_capacity(y.len());
for y_val in y {
mu_data.push(if *y_val < 1e-10 {
1e-10
} else if *y_val > 1f64 - 1e-10 {
1f64 - 1e-10
} else {
*y_val
});
}
mu_data
}
fn compute_working_weight(&self, mu: &[f64]) -> Vec<f64> {
let mut working_weights_vec = Vec::with_capacity(mu.len());
for (idx, m) in mu.iter().enumerate() {
let var = self.model_variance(*m) / self.weights[idx];
working_weights_vec.push(if var.abs() < 1e-5 {
1e-5
} else {
var
});
}
working_weights_vec
}
fn compute_y_bar(&self, y: &[f64], mu: &[f64]) -> Vec<f64> {
let mut y_bar_vec = Vec::with_capacity(y.len());
for (idx, m) in mu.iter().enumerate() {
let target_diff = y[idx] - m;
y_bar_vec.push(if target_diff.abs() < 1e-15 {
0f64
} else {
self.link_grad(*m) * target_diff
});
}
y_bar_vec
}
}
#[derive(Clone, Copy, Debug)]
pub struct Normal;
impl Criterion for Normal {
type Link = Identity;
fn model_variance(&self, _: f64) -> f64 {
1f64
}
}
#[derive(Clone, Copy, Debug)]
pub struct Poisson;
impl Criterion for Poisson {
type Link = Log;
fn model_variance(&self, mu: f64) -> f64 {
mu
}
fn initialize_mu(&self, y: &[f64]) -> Vec<f64> {
let mut mu_data = Vec::with_capacity(y.len());
for y_val in y {
mu_data.push(if *y_val < 1e-10 {
1e-10
} else {
*y_val
});
}
mu_data
}
fn compute_working_weight(&self, mu: &[f64]) -> Vec<f64> {
mu.to_vec()
}
}