pub mod activation_functions;
pub mod io;
pub mod loss;
use nalgebra::DMatrix;
use rand::{
distributions::{Distribution, Uniform},
rngs::ThreadRng,
};
use crate::activation_functions::ActivationFunction;
#[derive(Clone)]
pub struct ELM {
input_size: usize,
hidden_size: usize,
output_size: usize,
activation_function_fn: fn(&mut f64),
activation_function: ActivationFunction,
weights: DMatrix<f64>,
biases: DMatrix<f64>,
beta: DMatrix<f64>,
epsilon: f64,
}
impl ELM {
pub fn new(
input_size: usize,
hidden_size: usize,
output_size: usize,
activation_function: ActivationFunction,
epsilon: Epsilon,
) -> Self {
epsilon.verify();
let mut rng: ThreadRng = rand::thread_rng();
let weights_distribution: Uniform<f64> = Uniform::from(-0.5..=0.5);
let biases_distribution: Uniform<f64> = Uniform::from(0.0..=1.0);
Self {
input_size,
hidden_size,
output_size,
activation_function_fn: activation_functions::map(&activation_function),
activation_function,
weights: DMatrix::from_fn(hidden_size, input_size, |_, _| {
weights_distribution.sample(&mut rng)
}),
biases: DMatrix::from_fn(1, hidden_size, |_, _| biases_distribution.sample(&mut rng)),
beta: DMatrix::from_element(hidden_size, output_size, 1.0),
epsilon: epsilon.get(),
}
}
fn pass_to_hidden<T: ToMatrix>(&self, inputs: &T) -> DMatrix<f64> {
let inputs = inputs.to_matrix();
let mut hidden = inputs * self.weights.transpose();
hidden
.row_iter_mut()
.for_each(|mut row| row += &self.biases);
hidden.apply(|x| (self.activation_function_fn)(x));
hidden
}
pub fn train<T: ToMatrix, I: ToMatrix + FromMatrix>(&mut self, inputs: &I, targets: &T) {
let hidden = self.pass_to_hidden(inputs);
let moore_penrose = (hidden.transpose() * &hidden)
.pseudo_inverse(self.epsilon)
.unwrap();
self.beta = (moore_penrose * hidden.transpose()) * &targets.to_matrix();
}
pub fn predict<T: ToMatrix + FromMatrix>(&self, inputs: &T) -> <T as FromMatrix>::Output {
let hidden = self.pass_to_hidden(inputs);
let res = hidden * &self.beta;
<T as FromMatrix>::from_matrix(res)
}
pub fn input_size(&self) -> usize {
self.input_size
}
pub fn hidden_size(&self) -> usize {
self.hidden_size
}
pub fn output_size(&self) -> usize {
self.output_size
}
pub fn activation_function(&self) -> ActivationFunction {
self.activation_function.clone()
}
}
pub trait ToMatrix {
fn to_matrix(&self) -> DMatrix<f64>;
}
impl ToMatrix for Vec<Vec<f64>> {
fn to_matrix(&self) -> DMatrix<f64> {
let num_columns = self.first().map_or(0, |first_row| first_row.len());
DMatrix::from_rows(
&self
.iter()
.filter(|row| row.len() == num_columns)
.map(|row| row.clone().into())
.collect::<Vec<_>>(),
)
}
}
impl ToMatrix for DMatrix<f64> {
fn to_matrix(&self) -> DMatrix<f64> {
self.clone()
}
}
pub trait FromMatrix {
type Output;
fn from_matrix(matrix: DMatrix<f64>) -> Self::Output;
}
impl FromMatrix for DMatrix<f64> {
type Output = DMatrix<f64>;
fn from_matrix(matrix: DMatrix<f64>) -> Self::Output {
matrix
}
}
impl FromMatrix for Vec<Vec<f64>> {
type Output = Vec<Vec<f64>>;
fn from_matrix(matrix: DMatrix<f64>) -> Self::Output {
matrix
.row_iter()
.map(|row| row.iter().cloned().collect())
.collect()
}
}
pub fn flatten_matrix(matrix: &DMatrix<f64>) -> Vec<f64> {
matrix.iter().cloned().collect()
}
const EPSILON: f64 = 0.0001;
pub enum Epsilon {
Default,
Custom(f64),
}
impl Epsilon {
fn verify(&self) {
if let Epsilon::Custom(eps) = self {
if *eps < 0.0 {
panic!("Epsilon must be non-negative.");
}
};
}
fn get(&self) -> f64 {
match self {
Epsilon::Default => EPSILON,
Epsilon::Custom(eps) => *eps,
}
}
}
#[cfg(test)]
mod tests {
use super::activation_functions::ActivationFunction;
use super::{Epsilon, ELM};
#[test]
#[should_panic]
fn test_epsilon() {
let _ = ELM::new(
2,
4,
2,
ActivationFunction::LeakyReLU,
Epsilon::Custom(-0.01),
);
}
}