use crate::error::{Context, Error};
use crate::neural_network::Tensor;
use crate::neural_network::layers::TrainingParameters;
use crate::neural_network::layers::layer_weight::LayerWeight;
use crate::neural_network::layers::no_trainable_parameters_layer_functions;
use crate::neural_network::traits::Layer;
use ndarray::IxDyn;
#[derive(Debug)]
pub struct Reshape {
target_shape: Vec<isize>,
input_shape: Option<Vec<usize>>,
}
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,
input_shape: 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 Layer for Reshape {
fn forward(&mut self, input: &Tensor) -> Result<Tensor, Error> {
if input.is_empty() {
return Err(Error::empty_input("input tensor"));
}
let output_shape = self.resolve(input.shape())?;
self.input_shape = Some(input.shape().to_vec());
Ok(input
.to_shape(IxDyn(&output_shape))
.context("reshape input")?
.to_owned())
}
fn predict(&self, input: &Tensor) -> Result<Tensor, Error> {
if input.is_empty() {
return Err(Error::empty_input("input tensor"));
}
let output_shape = self.resolve(input.shape())?;
Ok(input
.to_shape(IxDyn(&output_shape))
.context("reshape input")?
.to_owned())
}
fn backward(&mut self, grad_output: &Tensor) -> Result<Tensor, Error> {
let Some(input_shape) = &self.input_shape else {
return Err(Error::forward_pass_not_run("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 layer_type(&self) -> &str {
"Reshape"
}
fn output_shape(&self) -> String {
let resolved = match &self.input_shape {
Some(shape) => self.resolve(shape).ok(),
None if self.target_shape.contains(&-1) => None,
None => Some(
std::iter::once(0)
.chain(self.target_shape.iter().map(|&e| e as usize))
.collect(),
),
};
match resolved {
Some(shape) => {
let axes: Vec<String> = shape[1..].iter().map(|d| d.to_string()).collect();
if axes.is_empty() {
"(None,)".to_string()
} else {
format!("(None, {})", axes.join(", "))
}
}
None => "Unknown".to_string(),
}
}
no_trainable_parameters_layer_functions!();
}