blob: af056cc92c80c9230054dba3701f2afe50a0e456 [file] [log] [blame]
//! 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 std::vec::*;
use linalg::norm::{Euclidean, Lp, MatrixNorm};
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 {
let l1_norm = Lp::Integer(1).norm(mat);
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 {
Euclidean.norm(mat) * 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::norm::{Euclidean, MatrixNorm};
#[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 - (Euclidean.norm(&input_mat) / 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 - ((Euclidean.norm(&input_mat) / 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);
}
}
}