use mininn_derive::Layer;
use ndarray::{Array1, ArrayD, ArrayViewD};
use ndarray_rand::{rand, rand::distributions::Uniform, RandomExt};
use serde::{Deserialize, Serialize};
use crate::{
core::{NNMode, NNResult},
layers::{Layer, TrainLayer},
utils::{MSGPackFormatting, Optimizer},
};
pub const DEFAULT_DROPOUT_P: f32 = 0.5;
#[derive(Layer, Debug, Clone, Serialize, Deserialize)]
pub struct Dropout {
input: Array1<f32>,
p: f32,
seed: u64,
mask: Array1<f32>,
}
impl Dropout {
#[inline]
pub fn new(p: f32) -> Self {
Self {
input: Array1::zeros(0),
p,
seed: rand::random(),
mask: Array1::zeros(0),
}
}
pub fn with_seed(mut self, seed: u64) -> Self {
self.seed = seed;
self
}
#[inline]
pub fn p(&self) -> f32 {
self.p
}
#[inline]
pub fn seed(&self) -> u64 {
self.seed
}
#[inline]
pub fn set_p(&mut self, p: f32) {
self.p = p;
}
#[inline]
pub fn set_seed(&mut self, seed: u64) {
self.seed = seed;
}
}
impl Default for Dropout {
#[inline]
fn default() -> Self {
Self::new(DEFAULT_DROPOUT_P)
}
}
impl TrainLayer for Dropout {
fn forward(&mut self, input: ArrayViewD<f32>, mode: &NNMode) -> NNResult<ArrayD<f32>> {
self.input = input.to_owned().into_dimensionality()?;
match mode {
NNMode::Train => {
self.mask = Array1::random(input.len(), Uniform::new(0.0, 1.0)).mapv(|v| {
if v < self.p {
1.
} else {
0.
}
}) / self.p;
Ok((&self.input * &self.mask).into_dyn())
}
NNMode::Test => Ok(self.input.to_owned().into_dyn()),
}
}
#[inline]
fn backward(
&mut self,
output_gradient: ArrayViewD<f32>,
_learning_rate: f32,
_optimizer: &Optimizer,
mode: &NNMode,
) -> NNResult<ArrayD<f32>> {
match mode {
NNMode::Train => Ok(output_gradient.to_owned() * &self.mask),
NNMode::Test => Ok(output_gradient.to_owned()),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::utils::Optimizer;
use ndarray::array;
#[test]
fn test_new_dropout() {
let dropout = Dropout::new(DEFAULT_DROPOUT_P).with_seed(42);
assert_eq!(dropout.p(), DEFAULT_DROPOUT_P);
assert_eq!(dropout.seed(), 42);
assert_eq!(dropout.input.len(), 0);
assert_eq!(dropout.mask.len(), 0);
}
#[test]
fn test_new_dropout_default() {
let dropout = Dropout::default();
assert_eq!(dropout.p(), DEFAULT_DROPOUT_P);
}
#[test]
fn test_dropout_forward_pass_train() {
let mut dropout = Dropout::new(0.5).with_seed(42);
let input = array![1.0, 2.0, 3.0, 4.0].into_dyn();
let output = dropout.forward(input.view(), &NNMode::Train).unwrap();
assert_eq!(dropout.mask.len(), input.len());
for (o, (i, m)) in output.iter().zip(input.iter().zip(dropout.mask.iter())) {
assert_eq!(*o, *i * *m);
}
}
#[test]
fn test_dropout_backward_pass_train() {
let mut dropout = Dropout::new(0.5).with_seed(42);
let input = array![1.0, 2.0, 3.0, 4.0].into_dyn();
let output_gradient = array![0.1, 0.2, 0.3, 0.4].into_dyn();
dropout.forward(input.view(), &NNMode::Train).unwrap();
let backprop_output = dropout
.backward(
output_gradient.view(),
0.01,
&Optimizer::default(),
&NNMode::Train,
)
.unwrap();
for (bp, (og, m)) in backprop_output
.iter()
.zip(output_gradient.iter().zip(dropout.mask.iter()))
{
assert_eq!(*bp, *og * *m);
}
}
#[test]
fn test_dropout_forward_pass_test() {
let mut dropout = Dropout::new(0.5).with_seed(42);
let input = array![1.0, 2.0, 3.0, 4.0].into_dyn();
let output = dropout.forward(input.view(), &NNMode::Test).unwrap();
assert_eq!(output, input);
}
#[test]
fn test_dropout_backward_pass_test() {
let mut dropout = Dropout::new(0.5).with_seed(42);
let input = array![1.0, 2.0, 3.0, 4.0].into_dyn();
let output_gradient = array![0.1, 0.2, 0.3, 0.4].into_dyn();
dropout.forward(input.view(), &NNMode::Test).unwrap();
let backprop_output = dropout
.backward(
output_gradient.view(),
0.01,
&Optimizer::default(),
&NNMode::Test,
)
.unwrap();
assert_eq!(backprop_output, output_gradient.into_dyn());
}
}