use crate::error::{Context, Error};
use crate::neural_network::layers::ParamCounts;
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, Tensor};
use ndarray::IxDyn;
#[derive(Debug, Default)]
pub struct Flatten {
built: Option<Shape>,
}
impl Flatten {
pub fn new() -> Self {
Self { built: None }
}
}
impl LayerBase for Flatten {
fn layer_type(&self) -> &str {
"Flatten"
}
built_layer_shape_functions!();
no_trainable_parameters_layer_functions!();
}
impl UnaryLayer for Flatten {
fn build(&mut self, input: &Shape) -> Result<(), Error> {
let Some(built) = start_build(&self.built, "Flatten", input)? else {
return Ok(());
};
if !(3..=5).contains(&built.rank()) {
return Err(Error::invalid_input(format!(
"Flatten layer expects 3D, 4D, or 5D input, got {}D tensor",
built.rank()
)));
}
built.split_batch("Flatten")?;
self.built = Some(built);
Ok(())
}
fn forward(&self, input: &Tensor, ctx: &mut Ctx) -> Result<Tensor, Error> {
validate_built_input(&self.built, "Flatten", input.shape())?;
let input_shape = input.shape();
if ctx.is_training() {
ctx.push_cache("Flatten", input_shape.to_vec());
}
let batch_size = input_shape[0];
let flattened_features: usize = input_shape[1..].iter().product();
Ok(input
.to_shape(IxDyn(&[batch_size, flattened_features]))
.unwrap()
.to_owned())
}
fn backward(&self, grad_output: &Tensor, ctx: &mut Ctx) -> Result<Tensor, Error> {
let input_shape: Vec<usize> = ctx.pop_cache("Flatten")?;
let expected_grad_shape = [input_shape[0], input_shape[1..].iter().product()];
if grad_output.shape() != expected_grad_shape {
return Err(Error::shape_mismatch(
expected_grad_shape,
grad_output.shape(),
));
}
let reshaped_grad = grad_output
.to_shape(IxDyn(input_shape.as_slice()))
.context("reshape gradient")?
.to_owned();
Ok(reshaped_grad)
}
fn compute_output_shape(&self, input: &Shape) -> Result<Shape, Error> {
input.check_min_rank("Flatten", 2)?;
let (batch, tail) = input.split_batch("Flatten")?;
Ok(Shape::from_batch(batch, &[tail.iter().product()]))
}
}