use crate::error::{Context, Error};
use crate::neural_network::layers::ParamCounts;
use crate::neural_network::layers::validation::start_build;
use crate::neural_network::layers::{
build_config_function, no_trainable_parameters_layer_functions,
};
use crate::neural_network::traits::{LayerBase, UnaryLayer};
use crate::neural_network::{Ctx, Shape, Tensor};
use ndarray::IxDyn;
#[derive(Debug)]
pub struct Reshape {
target_shape: Vec<isize>,
built: Option<Shape>,
}
impl Reshape {
pub fn new(target_shape: Vec<isize>) -> Result<Self, Error> {
let mut inferred_axes = 0;
for (axis, &extent) in target_shape.iter().enumerate() {
match extent {
-1 => inferred_axes += 1,
e if e < -1 => {
return Err(Error::invalid_parameter(
"target_shape",
format!("axis {axis} is {e}, which must be -1 or greater than 0"),
));
}
0 => {
return Err(Error::invalid_parameter(
"target_shape",
format!("axis {axis} is 0, which no non-empty input can match"),
));
}
_ => {}
}
}
if inferred_axes > 1 {
return Err(Error::invalid_parameter(
"target_shape",
format!("holds {inferred_axes} entries of -1, and at most 1 may be inferred"),
));
}
Ok(Reshape {
target_shape,
built: None,
})
}
fn resolve(&self, input_shape: &[usize]) -> Result<Vec<usize>, Error> {
if input_shape.is_empty() {
return Err(Error::invalid_input(
"Reshape layer expects an input with a batch axis, got a 0D tensor",
));
}
let batch = input_shape[0];
let elements: usize = input_shape[1..].iter().product();
let named: usize = self
.target_shape
.iter()
.filter(|&&e| e != -1)
.map(|&e| e as usize)
.product();
let has_inferred = self.target_shape.contains(&-1);
let mut output_shape = Vec::with_capacity(self.target_shape.len() + 1);
output_shape.push(batch);
if has_inferred {
if !elements.is_multiple_of(named) {
return Err(Error::shape_mismatch(
vec![batch, named],
input_shape.to_vec(),
));
}
let inferred = elements / named;
output_shape.extend(
self.target_shape
.iter()
.map(|&e| if e == -1 { inferred } else { e as usize }),
);
} else {
if named != elements {
let mut expected = vec![batch];
expected.extend(self.target_shape.iter().map(|&e| e as usize));
return Err(Error::shape_mismatch(expected, input_shape.to_vec()));
}
output_shape.extend(self.target_shape.iter().map(|&e| e as usize));
}
Ok(output_shape)
}
}
impl LayerBase for Reshape {
fn layer_type(&self) -> &str {
"Reshape"
}
fn known_input_shapes(&self) -> Option<Vec<Shape>> {
match &self.built {
Some(shape) => Some(vec![shape.free_batch()]),
None if self.target_shape.contains(&-1) => None,
None => Some(vec![Shape::new(vec![
None,
Some(self.target_shape.iter().map(|&e| e as usize).product()),
])]),
}
}
build_config_function!();
no_trainable_parameters_layer_functions!();
}
impl UnaryLayer for Reshape {
fn build(&mut self, input: &Shape) -> Result<(), Error> {
let Some(built) = start_build(&self.built, "Reshape", input)? else {
return Ok(());
};
self.compute_output_shape(&built)?;
self.built = Some(built);
Ok(())
}
fn forward(&self, input: &Tensor, ctx: &mut Ctx) -> Result<Tensor, Error> {
if input.is_empty() {
return Err(Error::empty_input("input tensor"));
}
let output_shape = self.resolve(input.shape())?;
if ctx.is_training() {
ctx.push_cache("Reshape", input.shape().to_vec());
}
Ok(input
.to_shape(IxDyn(&output_shape))
.context("reshape input")?
.to_owned())
}
fn backward(&self, grad_output: &Tensor, ctx: &mut Ctx) -> Result<Tensor, Error> {
let input_shape: Vec<usize> = ctx.pop_cache("Reshape")?;
let expected = self.resolve(&input_shape)?;
if grad_output.shape() != expected.as_slice() {
return Err(Error::shape_mismatch(expected, grad_output.shape()));
}
Ok(grad_output
.to_shape(IxDyn(input_shape.as_slice()))
.context("reshape gradient")?
.to_owned())
}
fn compute_output_shape(&self, input: &Shape) -> Result<Shape, Error> {
let (batch, tail) = input.split_batch("Reshape")?;
let mut dims = vec![batch.unwrap_or(1)];
dims.extend(tail);
Ok(Shape::from_batch(batch, &self.resolve(&dims)?[1..]))
}
}