use super::{
broadcast_input, elementwise_merge_layer_functions, merge_layer_base_functions, merged_dims,
reduce_to,
};
use crate::error::Error;
use crate::neural_network::layers::ParamCounts;
use crate::neural_network::traits::{Arity, Layer, LayerBase};
use crate::neural_network::{Ctx, Shape, Tensor};
#[derive(Debug, Default)]
pub struct Average {
built: Option<Vec<Shape>>,
}
impl Average {
pub fn new() -> Self {
Self { built: None }
}
}
struct AverageCache {
inputs: Vec<Vec<usize>>,
output: Vec<usize>,
}
impl LayerBase for Average {
fn layer_type(&self) -> &str {
"Average"
}
merge_layer_base_functions!();
}
impl Layer for Average {
elementwise_merge_layer_functions!("Average", Arity::AtLeast(1));
fn forward_many(&self, inputs: &[&Tensor], ctx: &mut Ctx) -> Result<Tensor, Error> {
Arity::AtLeast(1).check("Average", inputs.len())?;
let output = merged_dims("Average", inputs)?;
let mut total = broadcast_input("Average", inputs[0], &output)?;
for input in &inputs[1..] {
total += &broadcast_input("Average", input, &output)?;
}
total /= inputs.len() as f32;
if ctx.is_training() {
ctx.push_cache(
"Average",
AverageCache {
inputs: inputs.iter().map(|input| input.shape().to_vec()).collect(),
output,
},
);
}
Ok(total)
}
fn backward_many(&self, grad_output: &Tensor, ctx: &mut Ctx) -> Result<Vec<Tensor>, Error> {
let AverageCache { inputs, output } = ctx.pop_cache::<AverageCache>("Average")?;
if grad_output.shape() != output.as_slice() {
return Err(Error::shape_mismatch(output, grad_output.shape()));
}
let scaled = grad_output / inputs.len() as f32;
Ok(inputs
.iter()
.map(|shape| reduce_to(&scaled, shape))
.collect())
}
}
#[cfg(test)]
mod tests {
use super::*;
use ndarray::IxDyn;
fn tensor(dims: &[usize], values: &[f32]) -> Tensor {
Tensor::from_shape_vec(IxDyn(dims), values.to_vec()).unwrap()
}
#[test]
fn the_forward_pass_divides_by_the_input_count() {
let a = tensor(&[1, 3], &[1.0, 2.0, 3.0]);
let b = tensor(&[1, 3], &[3.0, 4.0, 5.0]);
let c = tensor(&[1, 3], &[8.0, 9.0, 10.0]);
let mut layer = Average::new();
let mut ctx = Ctx::training();
let output = layer.forward_many_mut(&[&a, &b, &c], &mut ctx).unwrap();
assert_eq!(output, tensor(&[1, 3], &[4.0, 5.0, 6.0]));
assert!(output.is_standard_layout(), "a layer emits the C order");
}
#[test]
fn a_single_input_comes_back_unchanged() {
let a = tensor(&[2, 2], &[1.0, 2.0, 3.0, 4.0]);
let mut layer = Average::new();
let mut ctx = Ctx::inference();
let output = layer.forward_many_mut(&[&a], &mut ctx).unwrap();
assert_eq!(output, a);
assert_eq!(ctx.pending_caches(), 0, "an inference pass parks nothing");
}
#[test]
fn the_backward_pass_splits_the_gradient_by_the_input_count() {
let a = tensor(&[1, 2], &[1.0, 2.0]);
let b = tensor(&[1, 2], &[3.0, 4.0]);
let c = tensor(&[1, 2], &[5.0, 6.0]);
let mut layer = Average::new();
let mut ctx = Ctx::training();
layer.forward_many_mut(&[&a, &b, &c], &mut ctx).unwrap();
let grads = layer
.backward_many(&tensor(&[1, 2], &[3.0, 6.0]), &mut ctx)
.unwrap();
assert_eq!(grads.len(), 3);
for grad in &grads {
assert_eq!(grad, &tensor(&[1, 2], &[1.0, 2.0]));
assert!(grad.is_standard_layout(), "a layer emits the C order");
}
assert_eq!(ctx.pending_caches(), 0, "the backward pass takes the cache");
}
#[test]
fn a_broadcast_input_takes_the_summed_gradient() {
let wide = tensor(&[2, 3], &[1.0, 2.0, 3.0, 4.0, 5.0, 6.0]);
let narrow = tensor(&[2, 1], &[1.0, 3.0]);
let mut layer = Average::new();
let mut ctx = Ctx::training();
let output = layer.forward_many_mut(&[&wide, &narrow], &mut ctx).unwrap();
assert_eq!(output, tensor(&[2, 3], &[1.0, 1.5, 2.0, 3.5, 4.0, 4.5]));
let grads = layer
.backward_many(&Tensor::ones([2, 3].as_slice()), &mut ctx)
.unwrap();
assert_eq!(grads[0], tensor(&[2, 3], &[0.5; 6]));
assert_eq!(grads[1], tensor(&[2, 1], &[1.5, 1.5]));
}
#[test]
fn rank_alignment_inserts_after_the_batch_axis() {
let deep = tensor(&[1, 2, 2], &[1.0, 2.0, 3.0, 4.0]);
let flat = tensor(&[1, 2], &[1.0, 1.0]);
let mut layer = Average::new();
let mut ctx = Ctx::training();
let output = layer.forward_many_mut(&[&deep, &flat], &mut ctx).unwrap();
assert_eq!(output, tensor(&[1, 2, 2], &[1.0, 1.5, 2.0, 2.5]));
let grads = layer
.backward_many(&Tensor::ones([1, 2, 2].as_slice()), &mut ctx)
.unwrap();
assert_eq!(grads[0], tensor(&[1, 2, 2], &[0.5; 4]));
assert_eq!(grads[1], tensor(&[1, 2], &[1.0, 1.0]));
}
#[test]
fn the_batch_axis_does_not_broadcast() {
let mut layer = Average::new();
let message = layer
.build_many(&[Shape::known(&[2, 3]), Shape::known(&[1, 3])])
.unwrap_err()
.to_string();
assert!(message.contains("Average"), "{message}");
assert!(message.contains("batch axis"), "{message}");
let two = Tensor::zeros([2, 3].as_slice());
let one = Tensor::zeros([1, 3].as_slice());
let mut layer = Average::new();
let mut ctx = Ctx::training();
assert!(layer.forward_many_mut(&[&two, &one], &mut ctx).is_err());
}
#[test]
fn an_axis_that_cannot_broadcast_is_refused() {
let mut layer = Average::new();
let message = layer
.build_many(&[Shape::known(&[2, 3]), Shape::known(&[2, 5])])
.unwrap_err()
.to_string();
assert!(message.contains("Average"), "{message}");
assert!(message.contains("Axis 1"), "{message}");
assert!(
layer
.compute_output_shape_many(&[Shape::known(&[2, 3]), Shape::known(&[2, 5])])
.is_err()
);
}
#[test]
fn a_second_build_for_other_shapes_is_refused() {
let mut layer = Average::new();
layer
.build_many(&[Shape::known(&[2, 3]), Shape::known(&[2, 3])])
.unwrap();
layer
.build_many(&[Shape::known(&[5, 3]), Shape::known(&[5, 3])])
.unwrap();
let message = layer
.build_many(&[Shape::known(&[2, 4]), Shape::known(&[2, 4])])
.unwrap_err()
.to_string();
assert!(message.contains("already built"), "{message}");
assert!(layer.build_many(&[Shape::known(&[2, 3])]).is_err());
}
#[test]
fn no_input_at_all_is_refused() {
let mut layer = Average::new();
let message = layer.build_many(&[]).unwrap_err().to_string();
assert!(message.contains("Average"), "{message}");
assert!(message.contains("1 or more"), "{message}");
let mut ctx = Ctx::training();
let message = layer.forward_many(&[], &mut ctx).unwrap_err().to_string();
assert!(message.contains("1 or more"), "{message}");
assert!(layer.compute_output_shape_many(&[]).is_err());
assert_eq!(layer.arity(), Arity::AtLeast(1));
}
#[test]
fn the_layer_reports_its_build() {
let mut layer = Average::new();
assert_eq!(layer.layer_type(), "Average");
assert!(!layer.is_built());
assert_eq!(layer.output_shape(), "Unknown");
assert!(layer.build_config().is_none());
layer
.build_many(&[Shape::known(&[2, 1, 4]), Shape::known(&[2, 3, 1])])
.unwrap();
assert!(layer.is_built());
let reported = vec![
Shape::new(vec![None, Some(1), Some(4)]),
Shape::new(vec![None, Some(3), Some(1)]),
];
assert_eq!(layer.known_input_shapes().unwrap(), reported);
assert_eq!(layer.build_config().unwrap().input_shapes, reported);
assert_eq!(layer.output_shape(), "(None, 3, 4)");
}
#[test]
fn the_layer_holds_no_array() {
let mut layer = Average::new();
assert_eq!(layer.param_count(), ParamCounts::none());
assert!(layer.weights().is_empty());
assert!(layer.weights_mut().is_empty());
assert!(layer.parameters_mut().is_empty());
}
#[test]
fn a_backward_pass_without_a_forward_pass_is_refused() {
let layer = Average::new();
let mut ctx = Ctx::training();
let message = layer
.backward_many(&Tensor::ones([2, 3].as_slice()), &mut ctx)
.unwrap_err()
.to_string();
assert!(
message.contains("forward pass has not been run"),
"{message}"
);
}
#[test]
fn a_gradient_of_another_shape_is_refused() {
let a = tensor(&[1, 2], &[1.0, 2.0]);
let b = tensor(&[1, 2], &[3.0, 4.0]);
let mut layer = Average::new();
let mut ctx = Ctx::training();
layer.forward_many_mut(&[&a, &b], &mut ctx).unwrap();
assert!(
layer
.backward_many(&Tensor::ones([1, 3].as_slice()), &mut ctx)
.is_err()
);
}
}