use crate::error::Error;
use crate::neural_network::layers::ParamCounts;
use crate::neural_network::layers::regularization::dropout::{
spatial_dropout_backward, spatial_dropout_scale,
};
use crate::neural_network::layers::regularization::validation::{
validate_input_ndim, validate_rate,
};
use crate::neural_network::layers::validation::{start_build, validate_built_input};
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::spatial_dropout_scale_parallel_min_elems;
use ndarray::IxDyn;
use ndarray_rand::rand::rngs::StdRng;
use ndarray_rand::{RandomExt, rand_distr::Uniform};
#[derive(Debug)]
pub struct SpatialDropout3D {
rate: f32,
built: Option<Shape>,
rng: StdRng,
}
impl SpatialDropout3D {
pub fn new(rate: f32) -> Result<Self, Error> {
validate_rate(rate, "Dropout rate")?;
Ok(SpatialDropout3D {
rate,
built: None,
rng: crate::random::make_rng(None),
})
}
pub fn with_random_state(mut self, random_state: u64) -> Self {
self.rng = crate::random::make_rng(Some(random_state));
self
}
}
impl LayerBase for SpatialDropout3D {
fn layer_type(&self) -> &str {
"SpatialDropout3D"
}
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 SpatialDropout3D {
fn build(&mut self, input: &Shape) -> Result<(), Error> {
let Some(built) = start_build(&self.built, "SpatialDropout3D", input)? else {
return Ok(());
};
input.check_rank("SpatialDropout3D", 5)?;
self.built = Some(built);
Ok(())
}
fn forward(&self, input: &Tensor, ctx: &mut Ctx) -> Result<Tensor, Error> {
validate_built_input(&self.built, "SpatialDropout3D", input.shape())?;
validate_input_ndim(
input.ndim(),
5,
"SpatialDropout3D (batch_size, depth, height, width, channels)",
)?;
if !ctx.is_training() {
return Ok(input.clone());
}
if self.rate == 0.0 {
return Ok(input.clone());
}
if self.rate == 1.0 {
return Ok(Tensor::zeros(input.raw_dim()));
}
let shape = input.shape();
let batch_size = shape[0];
let channels = shape[shape.len() - 1];
let mut rng = ctx
.take_state::<StdRng>("rng")
.unwrap_or_else(|| self.rng.clone());
let mut mask_2d = Tensor::random_using(
IxDyn(&[batch_size, channels]),
Uniform::new(0.0, 1.0).unwrap(),
&mut rng,
);
ctx.set_state("rng", rng);
let rate = self.rate;
mask_2d.mapv_inplace(|x| if x >= rate { 1.0 } else { 0.0 });
let channel_mask = mask_2d.as_slice().expect("per-channel mask is contiguous");
let output = spatial_dropout_scale(
input,
channel_mask,
self.rate,
spatial_dropout_scale_parallel_min_elems(),
);
ctx.push_cache("SpatialDropout3D", mask_2d);
Ok(output)
}
fn backward(&self, grad_output: &Tensor, ctx: &mut Ctx) -> Result<Tensor, Error> {
let mask = ctx.pop_cache::<Tensor>("SpatialDropout3D").ok();
spatial_dropout_backward(
grad_output,
&mask,
ctx.is_training(),
self.rate,
"SpatialDropout3D",
spatial_dropout_scale_parallel_min_elems(),
)
}
fn compute_output_shape(&self, input: &Shape) -> Result<Shape, Error> {
input.check_rank("SpatialDropout3D", 5)?;
Ok(input.clone())
}
}