use std::marker::PhantomData;
use static_assertions::assert_impl_all;
use crate::{Element, Symbol, Tape, Tensor, Value};
assert_impl_all!(BatchNorm<f64>: Send, Sync);
#[derive(Debug, Clone)]
pub struct BatchNorm<E> {
scale: Symbol,
shift: Symbol,
epsilon: Symbol,
_marker: PhantomData<E>,
}
impl<E: Element> BatchNorm<E> {
pub fn new(tape: &Tape<E>, scale: Tensor<E>, shift: Tensor<E>, epsilon: Tensor<E>) -> Self {
let scale_shape = scale.shape();
let shift_shape = shift.shape();
let epsilon_shape = epsilon.shape();
assert_eq!(
scale_shape.rank(),
1,
"batch-norm scale must be rank 1, got {scale_shape}"
);
assert_eq!(
shift_shape, scale_shape,
"batch-norm shift {shift_shape} must be shaped like the scale {scale_shape}"
);
assert_eq!(
epsilon_shape.volume(),
1,
"batch-norm epsilon must hold a single value, got {epsilon_shape}"
);
Self {
scale: tape.parameter(scale).symbol(),
shift: tape.parameter(shift).symbol(),
epsilon: tape.leaf(epsilon).symbol(),
_marker: PhantomData,
}
}
pub fn parameters(&self) -> impl Iterator<Item = Symbol> + '_ {
[self.scale, self.shift].into_iter()
}
}
impl<E: Element> BatchNorm<E> {
pub fn express<'tape>(&self, input: Value<'tape, E>) -> Normalization<'tape, E> {
let mean = input.mean_along(0);
let centered = input - mean.broadcast_along_like(0, input);
let variance = (centered * centered).mean_along(0);
let output = self.normalize(centered, variance);
Normalization {
output,
mean,
variance,
}
}
pub fn express_with<'tape>(
&self,
input: Value<'tape, E>,
mean: Value<'tape, E>,
variance: Value<'tape, E>,
) -> Value<'tape, E> {
let centered = input - mean.broadcast_along_like(0, input);
self.normalize(centered, variance)
}
fn normalize<'tape>(
&self,
centered: Value<'tape, E>,
variance: Value<'tape, E>,
) -> Value<'tape, E> {
let tape = centered.tape();
let scale = tape.resolve(self.scale);
let shift = tape.resolve(self.shift);
let epsilon = tape.resolve(self.epsilon);
let centered_shape = centered.shape();
let scale_shape = scale.shape();
assert_eq!(
centered_shape.rank(),
2,
"batch-norm input must be rank 2 [batch, features], got {centered_shape}"
);
assert_eq!(
centered_shape.axes()[1],
scale_shape.axes()[0],
"batch-norm input {centered_shape} and scale {scale_shape} disagree on features"
);
let deviation = (variance + epsilon.broadcast_like(variance)).sqrt();
let normalized = centered / deviation.broadcast_along_like(0, centered);
normalized * scale.broadcast_along_like(0, centered)
+ shift.broadcast_along_like(0, centered)
}
}
#[derive(Debug)]
pub struct Normalization<'tape, E> {
pub output: Value<'tape, E>,
pub mean: Value<'tape, E>,
pub variance: Value<'tape, E>,
}
impl<E> Clone for Normalization<'_, E> {
fn clone(&self) -> Self {
*self
}
}
impl<E> Copy for Normalization<'_, E> {}
#[cfg(test)]
#[path = "tests/batch_norm_tests.rs"]
mod tests;