use crate::error::Error;
use crate::neural_network::layers::ParamCounts;
use crate::neural_network::layers::regularization::dropout::{
broadcast_dropout_scale, dropout_backward,
};
use crate::neural_network::layers::regularization::validation::validate_rate;
use crate::neural_network::layers::validation::start_build;
use crate::neural_network::layers::{
built_layer_shape_functions, no_trainable_parameters_layer_functions,
};
use crate::neural_network::traits::{LayerBase, UnaryLayer};
use crate::neural_network::{Ctx, Shape, StateSlot, Tensor};
use crate::parallel_gates::cheap_map_parallel_threshold;
use ndarray::IxDyn;
use ndarray_rand::rand::rngs::StdRng;
use ndarray_rand::{RandomExt, rand_distr::Uniform};
#[derive(Debug)]
pub struct Dropout {
rate: f32,
built: Option<Shape>,
noise_shape: Option<Vec<Option<usize>>>,
rng: StdRng,
}
impl Dropout {
pub fn new(rate: f32) -> Result<Self, Error> {
validate_rate(rate, "Dropout rate")?;
Ok(Dropout {
rate,
built: None,
noise_shape: None,
rng: crate::random::make_rng(None),
})
}
pub fn with_noise_shape(mut self, noise_shape: Vec<Option<usize>>) -> Result<Self, Error> {
if noise_shape.is_empty() {
return Err(Error::empty_input("noise_shape"));
}
if let Some(axis) = noise_shape.iter().position(|entry| *entry == Some(0)) {
return Err(Error::invalid_parameter(
"noise_shape",
format!(
"every entry must be positive or None, got 0 at axis {}",
axis
),
));
}
self.noise_shape = Some(noise_shape);
Ok(self)
}
pub fn with_random_state(mut self, random_state: u64) -> Self {
self.rng = crate::random::make_rng(Some(random_state));
self
}
fn resolve_noise_shape(&self, input_shape: &[usize]) -> Result<Vec<usize>, Error> {
let Some(noise_shape) = &self.noise_shape else {
return Ok(input_shape.to_vec());
};
if noise_shape.len() > input_shape.len() {
return Err(Error::invalid_parameter(
"noise_shape",
format!(
"has rank {}, which is more than the input rank {}",
noise_shape.len(),
input_shape.len()
),
));
}
let leading = input_shape.len() - noise_shape.len();
let mut resolved = Vec::with_capacity(noise_shape.len());
for (index, entry) in noise_shape.iter().enumerate() {
let extent = input_shape[leading + index];
match *entry {
None => resolved.push(extent),
Some(1) => resolved.push(1),
Some(size) if size == extent => resolved.push(size),
Some(size) => {
return Err(Error::invalid_parameter(
"noise_shape",
format!(
"entry {} at axis {} of the input must be 1, {}, or None",
size,
leading + index,
extent
),
));
}
}
}
Ok(resolved)
}
}
impl LayerBase for Dropout {
fn layer_type(&self) -> &str {
"Dropout"
}
built_layer_shape_functions!();
no_trainable_parameters_layer_functions!();
fn apply_state(&mut self, state: &mut StateSlot<'_>) {
if let Some(rng) = state.take::<StdRng>("rng") {
self.rng = rng;
}
}
}
impl UnaryLayer for Dropout {
fn build(&mut self, input: &Shape) -> Result<(), Error> {
let Some(built) = start_build(&self.built, "Dropout", input)? else {
return Ok(());
};
input.check_min_rank("Dropout", 1)?;
self.built = Some(built);
Ok(())
}
fn forward(&self, input: &Tensor, ctx: &mut Ctx) -> Result<Tensor, Error> {
if !self.is_built() {
return Err(Error::not_built("Dropout"));
}
if !ctx.is_training() {
return Ok(input.clone());
}
if self.rate == 0.0 {
return Ok(input.clone());
}
let noise_shape = self.resolve_noise_shape(input.shape())?;
if self.rate == 1.0 {
return Ok(Tensor::zeros(input.raw_dim()));
}
let mut rng = ctx
.take_state::<StdRng>("rng")
.unwrap_or_else(|| self.rng.clone());
let mut mask = Tensor::random_using(
IxDyn(&noise_shape),
Uniform::new(0.0, 1.0).unwrap(),
&mut rng,
);
ctx.set_state("rng", rng);
if mask.len() >= cheap_map_parallel_threshold() {
mask.par_mapv_inplace(|x| if x >= self.rate { 1.0 } else { 0.0 });
} else {
mask.mapv_inplace(|x| if x >= self.rate { 1.0 } else { 0.0 });
}
let output = broadcast_dropout_scale(input, &mask, self.rate)?;
ctx.push_cache("Dropout", mask);
Ok(output)
}
fn backward(&self, grad_output: &Tensor, ctx: &mut Ctx) -> Result<Tensor, Error> {
let mask = ctx.pop_cache::<Tensor>("Dropout").ok();
dropout_backward(grad_output, &mask, ctx.is_training(), self.rate, "Dropout")
}
}