use ndarray::{Array1, Array2, ArrayD, ArrayView1, ArrayView2, ArrayViewD};
use ndarray_rand::{rand::distributions::Uniform, RandomExt};
use serde::{Deserialize, Serialize};
use crate::{
core::{MininnError, NNMode, NNResult},
layers::{Layer, TrainLayer},
utils::{ActivationFunction, MSGPackFormatting, Optimizer, OptimizerType},
};
use mininn_derive::Layer;
#[derive(Layer, Clone, Debug, Serialize, Deserialize)]
pub struct Dense {
weights: Array2<f32>,
biases: Array1<f32>,
input: Array1<f32>,
activation: Option<Box<dyn ActivationFunction>>,
}
impl Dense {
#[inline]
pub fn new(ninputs: usize, noutputs: usize) -> Self {
Self {
weights: Array2::random((noutputs, ninputs), Uniform::new(-1.0, 1.0)),
biases: Array1::random(noutputs, Uniform::new(-1.0, 1.0)),
input: Array1::zeros(ninputs),
activation: None,
}
}
pub fn apply(mut self, activation: impl ActivationFunction + 'static) -> Self {
self.activation = Some(Box::new(activation));
self
}
#[inline]
pub fn ninputs(&self) -> usize {
self.weights.ncols()
}
#[inline]
pub fn noutputs(&self) -> usize {
self.weights.nrows()
}
#[inline]
pub fn weights(&self) -> ArrayView2<f32> {
self.weights.view()
}
#[inline]
pub fn biases(&self) -> ArrayView1<f32> {
self.biases.view()
}
#[inline]
pub fn activation(&self) -> Option<&str> {
self.activation.as_ref().map(|a| a.as_ref().name())
}
#[inline]
pub fn set_weights(&mut self, weights: &Array2<f32>) {
self.weights = weights.to_owned();
}
#[inline]
pub fn set_biases(&mut self, biases: &Array1<f32>) {
self.biases = biases.to_owned();
}
}
impl TrainLayer for Dense {
fn forward(&mut self, input: ArrayViewD<f32>, _mode: &NNMode) -> NNResult<ArrayD<f32>> {
self.input = input.to_owned().into_dimensionality()?;
if self.input.is_empty() {
return Err(MininnError::LayerError(
"Input is empty, cannot forward pass".to_string(),
));
}
if self.weights.is_empty() {
return Err(MininnError::LayerError(
"Weights are empty, cannot forward pass".to_string(),
));
}
let sum = self.weights.dot(&self.input) + &self.biases;
match &self.activation {
Some(act) => Ok(act.function(&sum.into_dimensionality()?.view())),
None => Ok(sum.into_dimensionality()?),
}
}
fn backward(
&mut self,
output_gradient: ArrayViewD<f32>,
learning_rate: f32,
optimizer: &Optimizer,
_mode: &NNMode,
) -> NNResult<ArrayD<f32>> {
if self.input.is_empty() {
return Err(MininnError::LayerError(
"Input is empty, cannot backward pass".to_string(),
));
}
if self.weights.is_empty() {
return Err(MininnError::LayerError(
"Weights are empty, cannot backward pass".to_string(),
));
}
let weights_gradient = output_gradient
.to_owned()
.to_shape((output_gradient.len(), 1))?
.dot(&self.input.view().to_shape((1, self.input.len()))?);
let dim_output: Array1<f32> = output_gradient.to_owned().into_dimensionality()?;
let dim_input: ArrayD<f32> = self.input.to_owned().into_dimensionality()?;
let input_gradient = self.weights.t().dot(&dim_output);
let mut optimizer_type = match optimizer {
Optimizer::GD => OptimizerType::GD,
Optimizer::Momentum(momentum) => {
OptimizerType::new_momentum(*momentum, self.weights.dim(), self.biases.len())
} };
optimizer_type.optimize(
&mut self.weights,
&mut self.biases,
&weights_gradient.view(),
&dim_output.view(),
learning_rate,
);
match &self.activation {
Some(act) => Ok(input_gradient * act.derivate(&dim_input.view())),
None => Ok(input_gradient.into_dyn()),
}
}
}
#[cfg(test)]
mod tests {
use crate::utils::Act;
use super::*;
use ndarray::array;
#[test]
fn test_dense_creation() {
let dense = Dense::new(3, 2).apply(Act::ReLU);
assert_eq!(dense.ninputs(), 3);
assert_eq!(dense.noutputs(), 2);
assert!(dense.activation().is_some());
assert_eq!(dense.activation().unwrap(), "ReLU");
}
#[test]
fn test_forward_pass_without_name() {
let mut dense = Dense::new(3, 2);
let input = array![0.5, -0.3, 0.8].into_dyn();
let output = dense.forward(input.view(), &NNMode::Train).unwrap();
assert_eq!(output.len(), 2);
}
#[test]
fn test_forward_pass_with_name() {
let mut dense = Dense::new(3, 2).apply(Act::ReLU);
let input = array![0.5, -0.3, 0.8].into_dyn();
let output = dense.forward(input.view(), &NNMode::Train).unwrap();
assert_eq!(output.len(), 2);
}
#[test]
fn test_forward_pass_empty_input() {
let mut dense = Dense::new(3, 2).apply(Act::ReLU);
let input: Array1<f32> = array![];
let result = dense.forward(input.into_dyn().view(), &NNMode::Train);
assert!(result.is_err());
assert_eq!(
result.unwrap_err().to_string(),
"Layer Error: Input is empty, cannot forward pass."
);
}
#[test]
fn test_forward_pass_empty_weights() {
let mut dense = Dense::new(3, 2).apply(Act::ReLU);
dense.weights = array![[]];
let input = array![0.5, -0.3, 0.8].into_dyn();
let result = dense.forward(input.view(), &NNMode::Train);
assert!(result.is_err());
assert_eq!(
result.unwrap_err().to_string(),
"Layer Error: Weights are empty, cannot forward pass."
);
}
#[test]
fn test_backward_pass() {
let mut dense = Dense::new(3, 2).apply(Act::ReLU);
let input = array![0.5, -0.3, 0.8].into_dyn();
dense.forward(input.view(), &NNMode::Train).unwrap();
let output_gradient = array![1.0, 1.0].into_dyn();
let learning_rate = 0.01;
let input_gradient = dense
.backward(
output_gradient.view(),
learning_rate,
&Optimizer::GD,
&NNMode::Train,
)
.unwrap();
assert_eq!(input_gradient.len(), 3);
}
#[test]
fn test_backward_pass_empty_input() {
let mut dense = Dense::new(3, 2).apply(Act::ReLU);
let input: Array1<f32> = array![];
dense.input = input.clone();
let result = dense.backward(input.into_dyn().view(), 0.1, &Optimizer::GD, &NNMode::Train);
assert!(result.is_err());
assert_eq!(
result.unwrap_err().to_string(),
"Layer Error: Input is empty, cannot backward pass."
);
}
#[test]
fn test_backward_pass_empty_weights() {
let mut dense = Dense::new(3, 2).apply(Act::ReLU);
dense.weights = array![[]];
let output_gradient = array![1.0, 1.0].into_dyn();
let learning_rate = 0.01;
let result = dense.backward(
output_gradient.view(),
learning_rate,
&Optimizer::GD,
&NNMode::Train,
);
assert!(result.is_err());
assert_eq!(
result.unwrap_err().to_string(),
"Layer Error: Weights are empty, cannot backward pass."
);
}
#[test]
fn test_layer_type() {
let dense = Dense::new(3, 2).apply(Act::ReLU);
assert_eq!(dense.layer_type(), "Dense");
}
}