rusty-machine 0.5.4

A machine learning library.
Documentation
//! Regularization Module
//!
//! This module contains some base utility methods for regularization
//! within machine learning algorithms.
//!
//! The module contains a `Regularization` enum which provides access to
//! `L1`, `L2` and `ElasticNet` regularization.
//!
//! # Examples
//!
//! ```
//! use rusty_machine::learning::toolkit::regularization::Regularization;
//!
//! let reg = Regularization::L1(0.5);
//! ```

use linalg::Metric;
use linalg::{Matrix, MatrixSlice, BaseMatrix};
use libnum::{FromPrimitive, Float};

/// Model Regularization
#[derive(Debug, Clone, Copy)]
pub enum Regularization<T: Float> {
    /// L1 Regularization
    L1(T),
    /// L2 Regularization
    L2(T),
    /// Elastic Net Regularization (L1 and L2)
    ElasticNet(T, T),
    /// No Regularization
    None,
}

impl<T: Float + FromPrimitive> Regularization<T> {
    /// Compute the regularization addition to the cost.
    pub fn reg_cost(&self, mat: MatrixSlice<T>) -> T {
        match *self {
            Regularization::L1(x) => Self::l1_reg_cost(&mat, x),
            Regularization::L2(x) => Self::l2_reg_cost(&mat, x),
            Regularization::ElasticNet(x, y) => {
                Self::l1_reg_cost(&mat, x) + Self::l2_reg_cost(&mat, y)
            }
            Regularization::None => T::zero(),
        }
    }

    /// Compute the regularization addition to the gradient.
    pub fn reg_grad(&self, mat: MatrixSlice<T>) -> Matrix<T> {
        match *self {
            Regularization::L1(x) => Self::l1_reg_grad(&mat, x),
            Regularization::L2(x) => Self::l2_reg_grad(&mat, x),
            Regularization::ElasticNet(x, y) => {
                Self::l1_reg_grad(&mat, x) + Self::l2_reg_grad(&mat, y)
            }
            Regularization::None => Matrix::zeros(mat.rows(), mat.cols()),
        }
    }

    fn l1_reg_cost(mat: &MatrixSlice<T>, x: T) -> T {
        // TODO: This won't be regularized. Need to unroll...
        let l1_norm = mat.iter()
            .fold(T::zero(), |acc, y| acc + y.abs());
        l1_norm * x / ((T::one() + T::one()) * FromPrimitive::from_usize(mat.rows()).unwrap())
    }

    fn l1_reg_grad(mat: &MatrixSlice<T>, x: T) -> Matrix<T> {
        let m_2 = (T::one() + T::one()) * FromPrimitive::from_usize(mat.rows()).unwrap();
        let out_mat_data = mat.iter()
            .map(|y| {
                if y.is_sign_negative() {
                    -x / m_2
                } else {
                    x / m_2
                }
            })
            .collect::<Vec<_>>();
        Matrix::new(mat.rows(), mat.cols(), out_mat_data)
    }

    fn l2_reg_cost(mat: &MatrixSlice<T>, x: T) -> T {
        mat.norm() * x / ((T::one() + T::one()) * FromPrimitive::from_usize(mat.rows()).unwrap())
    }

    fn l2_reg_grad(mat: &MatrixSlice<T>, x: T) -> Matrix<T> {
        mat * (x / FromPrimitive::from_usize(mat.rows()).unwrap())
    }
}

#[cfg(test)]
mod tests {
    use super::Regularization;
    use linalg::{Matrix, BaseMatrix};
    use linalg::Metric;

    #[test]
    fn test_no_reg() {
        let input_mat = Matrix::new(3, 4, (0..12).map(|x| x as f64).collect::<Vec<_>>());
        let mat_slice = input_mat.as_slice();

        let no_reg: Regularization<f64> = Regularization::None;

        let a = no_reg.reg_cost(mat_slice);
        let b = no_reg.reg_grad(mat_slice);

        assert_eq!(a, 0f64);
        assert_eq!(b, Matrix::zeros(3, 4));
    }

    #[test]
    fn test_l1_reg() {
        let input_mat = Matrix::new(3, 4, (0..12).map(|x| x as f64 - 3f64).collect::<Vec<_>>());
        let mat_slice = input_mat.as_slice();

        let no_reg: Regularization<f64> = Regularization::L1(0.5);

        let a = no_reg.reg_cost(mat_slice);
        let b = no_reg.reg_grad(mat_slice);

        assert!((a - (42f64 / 12f64)) < 1e-18);

        let true_grad = vec![-1., -1., -1., 1., 1., 1., 1., 1., 1., 1., 1., 1.]
            .into_iter()
            .map(|x| x / 12f64)
            .collect::<Vec<_>>();

        for eps in (b - Matrix::new(3, 4, true_grad)).into_vec() {
            assert!(eps < 1e-18);
        }
    }

    #[test]
    fn test_l2_reg() {
        let input_mat = Matrix::new(3, 4, (0..12).map(|x| x as f64 - 3f64).collect::<Vec<_>>());
        let mat_slice = input_mat.as_slice();

        let no_reg: Regularization<f64> = Regularization::L2(0.5);

        let a = no_reg.reg_cost(mat_slice);
        let b = no_reg.reg_grad(mat_slice);

        assert!((a - (input_mat.norm() / 12f64)) < 1e-18);

        let true_grad = &input_mat / 6f64;
        for eps in (b - true_grad).into_vec() {
            assert!(eps < 1e-18);
        }
    }

    #[test]
    fn test_elastic_net_reg() {
        let input_mat = Matrix::new(3, 4, (0..12).map(|x| x as f64 - 3f64).collect::<Vec<_>>());
        let mat_slice = input_mat.as_slice();

        let no_reg: Regularization<f64> = Regularization::ElasticNet(0.5, 0.25);

        let a = no_reg.reg_cost(mat_slice);
        let b = no_reg.reg_grad(mat_slice);

        assert!(a - ((input_mat.norm() / 24f64) + (42f64 / 12f64)) < 1e-18);

        let l1_true_grad = Matrix::new(3, 4,
            vec![-1., -1., -1., 1., 1., 1., 1., 1., 1., 1., 1., 1.]
            .into_iter()
            .map(|x| x / 12f64)
            .collect::<Vec<_>>());
        let l2_true_grad = &input_mat / 12f64;

        for eps in (b - l1_true_grad - l2_true_grad)
            .into_vec() {
            // Slightly lower boundary than others - more numerical error as more ops.
            assert!(eps < 1e-12);
        }
    }
}