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 Multiply {
built: Option<Vec<Shape>>,
}
impl Multiply {
pub fn new() -> Self {
Multiply::default()
}
}
struct MultiplyCache {
lifted: Vec<Tensor>,
shapes: Vec<Vec<usize>>,
}
impl LayerBase for Multiply {
fn layer_type(&self) -> &str {
"Multiply"
}
merge_layer_base_functions!();
}
impl Layer for Multiply {
elementwise_merge_layer_functions!("Multiply", Arity::AtLeast(1));
fn forward_many(&self, inputs: &[&Tensor], ctx: &mut Ctx) -> Result<Tensor, Error> {
self.arity().check("Multiply", inputs.len())?;
let dims = merged_dims("Multiply", inputs)?;
let mut lifted = Vec::with_capacity(inputs.len());
for input in inputs {
lifted.push(broadcast_input("Multiply", input, &dims)?);
}
let mut output = lifted[0].clone();
for tensor in &lifted[1..] {
output *= tensor;
}
if ctx.is_training() {
ctx.push_cache(
"Multiply",
MultiplyCache {
shapes: inputs.iter().map(|input| input.shape().to_vec()).collect(),
lifted,
},
);
}
Ok(output)
}
fn backward_many(&self, grad_output: &Tensor, ctx: &mut Ctx) -> Result<Vec<Tensor>, Error> {
let cache: MultiplyCache = ctx.pop_cache("Multiply")?;
if let Some(first) = cache.lifted.first()
&& grad_output.shape() != first.shape()
{
return Err(Error::shape_mismatch(first.shape(), grad_output.shape()));
}
let mut grads = Vec::with_capacity(cache.shapes.len());
for (position, shape) in cache.shapes.iter().enumerate() {
let mut product = grad_output.as_standard_layout().into_owned();
for (other, tensor) in cache.lifted.iter().enumerate() {
if other != position {
product *= tensor;
}
}
grads.push(reduce_to(&product, shape));
}
Ok(grads)
}
}
#[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()
}
fn forward(inputs: &[&Tensor], ctx: &mut Ctx) -> Tensor {
Multiply::new().forward_many(inputs, ctx).unwrap()
}
#[test]
fn the_output_is_the_product_of_every_input() {
let first = tensor(&[2, 2], &[1.0, 2.0, 3.0, 4.0]);
let second = tensor(&[2, 2], &[5.0, 6.0, 7.0, 8.0]);
let third = tensor(&[2, 2], &[2.0, 2.0, 2.0, 2.0]);
let mut ctx = Ctx::training();
let output = forward(&[&first, &second, &third], &mut ctx);
assert_eq!(output, tensor(&[2, 2], &[10.0, 24.0, 42.0, 64.0]));
assert!(output.is_standard_layout(), "a layer emits the C order");
}
#[test]
fn a_single_input_passes_through() {
let only = tensor(&[2, 2], &[1.0, 2.0, 3.0, 4.0]);
let mut ctx = Ctx::training();
let layer = Multiply::new();
let output = layer.forward_many(&[&only], &mut ctx).unwrap();
assert_eq!(output, only);
let grad = tensor(&[2, 2], &[5.0, 6.0, 7.0, 8.0]);
let grads = layer.backward_many(&grad, &mut ctx).unwrap();
assert_eq!(grads.len(), 1);
assert_eq!(grads[0], grad);
}
#[test]
fn each_input_takes_the_product_of_the_others() {
let first = tensor(&[2, 2], &[1.0, 2.0, 3.0, 4.0]);
let second = tensor(&[2, 2], &[5.0, 6.0, 7.0, 8.0]);
let third = tensor(&[2, 2], &[2.0, 2.0, 2.0, 2.0]);
let mut ctx = Ctx::training();
let layer = Multiply::new();
layer
.forward_many(&[&first, &second, &third], &mut ctx)
.unwrap();
let grad = tensor(&[2, 2], &[1.0, 2.0, 3.0, 4.0]);
let grads = layer.backward_many(&grad, &mut ctx).unwrap();
assert_eq!(grads.len(), 3);
assert_eq!(grads[0], tensor(&[2, 2], &[10.0, 24.0, 42.0, 64.0]));
assert_eq!(grads[1], tensor(&[2, 2], &[2.0, 8.0, 18.0, 32.0]));
assert_eq!(grads[2], tensor(&[2, 2], &[5.0, 24.0, 63.0, 128.0]));
assert!(grads[0].is_standard_layout(), "a layer emits the C order");
}
#[test]
fn a_zero_in_an_input_gives_a_finite_gradient() {
let first = tensor(&[1, 2], &[0.0, 2.0]);
let second = tensor(&[1, 2], &[3.0, 4.0]);
let mut ctx = Ctx::training();
let layer = Multiply::new();
let output = layer.forward_many(&[&first, &second], &mut ctx).unwrap();
assert_eq!(output, tensor(&[1, 2], &[0.0, 8.0]));
let grads = layer
.backward_many(&tensor(&[1, 2], &[1.0, 1.0]), &mut ctx)
.unwrap();
assert_eq!(grads[0], second);
assert_eq!(grads[1], first);
}
#[test]
fn an_axis_of_one_position_broadcasts() {
let features = tensor(&[2, 3], &[1.0, 2.0, 3.0, 4.0, 5.0, 6.0]);
let scales = tensor(&[2, 1], &[10.0, 20.0]);
let mut ctx = Ctx::training();
let layer = Multiply::new();
let output = layer.forward_many(&[&features, &scales], &mut ctx).unwrap();
assert_eq!(
output,
tensor(&[2, 3], &[10.0, 20.0, 30.0, 80.0, 100.0, 120.0])
);
let grad = tensor(&[2, 3], &[1.0, 1.0, 1.0, 1.0, 1.0, 1.0]);
let grads = layer.backward_many(&grad, &mut ctx).unwrap();
assert_eq!(
grads[0],
tensor(&[2, 3], &[10.0, 10.0, 10.0, 20.0, 20.0, 20.0])
);
assert_eq!(grads[1], tensor(&[2, 1], &[6.0, 15.0]));
assert!(grads[1].is_standard_layout(), "a layer emits the C order");
}
#[test]
fn rank_alignment_inserts_after_the_batch_axis() {
let wide = tensor(&[2, 2, 2], &[1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0]);
let narrow = tensor(&[2, 2], &[1.0, 2.0, 3.0, 4.0]);
let mut ctx = Ctx::training();
let layer = Multiply::new();
let output = layer.forward_many(&[&wide, &narrow], &mut ctx).unwrap();
assert_eq!(
output,
tensor(&[2, 2, 2], &[1.0, 4.0, 3.0, 8.0, 15.0, 24.0, 21.0, 32.0])
);
let grad = Tensor::from_elem(IxDyn(&[2, 2, 2]), 1.0);
let grads = layer.backward_many(&grad, &mut ctx).unwrap();
assert_eq!(
grads[0],
tensor(&[2, 2, 2], &[1.0, 2.0, 1.0, 2.0, 3.0, 4.0, 3.0, 4.0])
);
assert_eq!(grads[1], tensor(&[2, 2], &[4.0, 6.0, 12.0, 14.0]));
}
#[test]
fn a_cache_reaches_the_context_in_training_alone() {
let first = tensor(&[2, 2], &[1.0, 2.0, 3.0, 4.0]);
let second = tensor(&[2, 2], &[5.0, 6.0, 7.0, 8.0]);
let mut ctx = Ctx::inference();
forward(&[&first, &second], &mut ctx);
assert_eq!(ctx.pending_caches(), 0);
let mut ctx = Ctx::training();
forward(&[&first, &second], &mut ctx);
assert_eq!(ctx.pending_caches(), 1);
}
#[test]
fn the_batch_axis_does_not_broadcast() {
let shapes = vec![Shape::known(&[2, 3]), Shape::known(&[1, 3])];
let message = Multiply::new().build_many(&shapes).unwrap_err().to_string();
assert!(message.contains("Multiply"), "{message}");
assert!(message.contains("batch axis"), "{message}");
assert!(message.contains("(2, 3)"), "{message}");
assert!(message.contains("(1, 3)"), "{message}");
}
#[test]
fn two_extents_that_cannot_broadcast_are_refused() {
let shapes = vec![Shape::known(&[2, 3]), Shape::known(&[2, 5])];
let message = Multiply::new().build_many(&shapes).unwrap_err().to_string();
assert!(message.contains("Multiply"), "{message}");
assert!(message.contains("Axis 1"), "{message}");
let first = Tensor::zeros(IxDyn(&[2, 3]));
let second = Tensor::zeros(IxDyn(&[2, 5]));
let mut ctx = Ctx::training();
assert!(
Multiply::new()
.forward_many(&[&first, &second], &mut ctx)
.is_err()
);
}
#[test]
fn a_second_build_for_other_shapes_is_refused() {
let mut layer = Multiply::new();
layer
.build_many(&[Shape::known(&[2, 3]), Shape::known(&[2, 3])])
.unwrap();
layer
.build_many(&[Shape::known(&[8, 3]), Shape::known(&[8, 3])])
.unwrap();
assert_eq!(
layer.known_input_shapes(),
Some(vec![
Shape::with_free_batch(&[2, 3]),
Shape::with_free_batch(&[2, 3])
])
);
assert_eq!(layer.output_shape(), "(None, 3)");
let message = layer
.build_many(&[Shape::known(&[2, 4]), Shape::known(&[2, 4])])
.unwrap_err()
.to_string();
assert!(message.contains("already built"), "{message}");
let message = layer
.build_many(&[Shape::known(&[2, 3])])
.unwrap_err()
.to_string();
assert!(message.contains("2 inputs"), "{message}");
}
#[test]
fn no_input_at_all_is_refused() {
let layer = Multiply::new();
assert_eq!(layer.arity(), Arity::AtLeast(1));
let message = Multiply::new().build_many(&[]).unwrap_err().to_string();
assert!(message.contains("Multiply"), "{message}");
assert!(message.contains("1 or more"), "{message}");
assert!(layer.compute_output_shape_many(&[]).is_err());
let mut ctx = Ctx::training();
assert!(layer.forward_many(&[], &mut ctx).is_err());
}
#[test]
fn the_layer_holds_no_array() {
let mut layer = Multiply::new();
layer
.build_many(&[Shape::known(&[2, 3]), Shape::known(&[2, 1])])
.unwrap();
assert_eq!(layer.param_count(), ParamCounts::none());
assert!(layer.weights().is_empty());
assert!(layer.weights_mut().is_empty());
assert!(layer.is_built());
let build = layer.build_config().unwrap();
assert_eq!(
build.input_shapes,
vec![
Shape::with_free_batch(&[2, 3]),
Shape::with_free_batch(&[2, 1])
]
);
}
}