use serde::{Deserialize, Serialize};
use crate::error::{Error, Result};
use crate::latent::FrameSeq;
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(try_from = "LatentNormRepr")]
pub struct LatentNorm {
mean: Vec<f32>,
std: Vec<f32>,
}
#[derive(Deserialize)]
struct LatentNormRepr {
mean: Vec<f32>,
std: Vec<f32>,
}
impl TryFrom<LatentNormRepr> for LatentNorm {
type Error = Error;
fn try_from(repr: LatentNormRepr) -> Result<Self> {
Self::from_stats(repr.mean, repr.std)
}
}
impl LatentNorm {
pub const DEFAULT_EPS: f32 = 1e-5;
pub fn validate_eps(eps: f32) -> Result<()> {
if !eps.is_finite() || eps <= 0.0 {
return Err(Error::validation(format!(
"eps must be finite and positive, got {eps}"
)));
}
Ok(())
}
pub fn new(mean: impl Into<Vec<f32>>, std: impl Into<Vec<f32>>, eps: f32) -> Result<Self> {
Self::validate_eps(eps)?;
let mean = mean.into();
let mut std = std.into();
if mean.len() != std.len() {
return Err(Error::validation(format!(
"latent normalization mean/std length mismatch: {} vs {}",
mean.len(),
std.len()
)));
}
if mean.is_empty() {
return Err(Error::validation(
"latent normalization needs at least one dimension",
));
}
if let Some(d) = mean.iter().position(|m| !m.is_finite()) {
return Err(Error::validation(format!(
"non-finite mean at dimension {d}"
)));
}
if let Some(d) = std.iter().position(|s| !s.is_finite()) {
return Err(Error::validation(format!(
"non-finite std at dimension {d}"
)));
}
for s in &mut std {
*s = s.max(eps);
}
Ok(Self { mean, std })
}
fn from_stats(mean: Vec<f32>, std: Vec<f32>) -> Result<Self> {
if mean.len() != std.len() {
return Err(Error::validation(format!(
"latent normalization mean/std length mismatch: {} vs {}",
mean.len(),
std.len()
)));
}
if mean.is_empty() {
return Err(Error::validation(
"latent normalization needs at least one dimension",
));
}
if let Some(d) = mean.iter().position(|m| !m.is_finite()) {
return Err(Error::validation(format!(
"non-finite mean at dimension {d}"
)));
}
if let Some(d) = std.iter().position(|s| !s.is_finite() || *s <= 0.0) {
return Err(Error::validation(format!(
"std at dimension {d} must be finite and positive, got {}",
std[d]
)));
}
Ok(Self { mean, std })
}
pub fn fit<'a>(seqs: impl IntoIterator<Item = &'a FrameSeq>, eps: f32) -> Result<Self> {
Self::validate_eps(eps)?;
let mut fit = NormFitter::new();
for seq in seqs {
fit.push(seq)?;
}
fit.finish_with_eps(eps)
}
pub fn dim(&self) -> usize {
self.mean.len()
}
pub fn mean(&self) -> &[f32] {
&self.mean
}
pub fn std(&self) -> &[f32] {
&self.std
}
pub fn standardize(&self, seq: &FrameSeq) -> Result<FrameSeq> {
self.apply(seq, |x, mean, std| (x - mean) / std)
}
pub fn unstandardize(&self, seq: &FrameSeq) -> Result<FrameSeq> {
self.apply(seq, |x, mean, std| x * std + mean)
}
fn apply(&self, seq: &FrameSeq, f: impl Fn(f32, f32, f32) -> f32) -> Result<FrameSeq> {
let dim = seq.dim();
if dim != self.dim() {
return Err(Error::shape(
vec![seq.frames(), self.dim()],
vec![seq.frames(), dim],
));
}
let mapped: Vec<f32> = seq
.values()
.iter()
.enumerate()
.map(|(i, &x)| {
let d = i % dim;
f(x, self.mean[d], self.std[d])
})
.collect();
FrameSeq::new(seq.frames(), dim, mapped)
}
}
#[derive(Clone, Copy, Debug, Default)]
struct DimAccumulator {
count: u64,
mean: f64,
m2: f64,
}
impl DimAccumulator {
fn push(&mut self, v: f32) {
self.count += 1;
let x = v as f64;
let delta = x - self.mean;
self.mean += delta / self.count as f64;
self.m2 += delta * (x - self.mean);
}
fn std(&self) -> f64 {
if self.count == 0 {
return 0.0;
}
(self.m2.max(0.0) / self.count as f64).sqrt()
}
}
#[derive(Clone, Debug, Default)]
pub struct NormFitter {
acc: Vec<DimAccumulator>,
}
impl NormFitter {
pub fn new() -> Self {
Self::default()
}
pub fn is_empty(&self) -> bool {
self.acc.is_empty()
}
pub fn dim(&self) -> Option<usize> {
(!self.acc.is_empty()).then_some(self.acc.len())
}
pub fn push(&mut self, seq: &FrameSeq) -> Result<()> {
let dim = seq.dim();
if self.acc.is_empty() {
let mut acc = vec![DimAccumulator::default(); dim];
accumulate(seq, dim, &mut acc);
self.acc = acc;
} else if self.acc.len() != dim {
return Err(Error::shape(
vec![seq.frames(), self.acc.len()],
vec![seq.frames(), dim],
));
} else {
accumulate(seq, dim, &mut self.acc);
}
Ok(())
}
pub fn finish(self) -> Result<LatentNorm> {
self.finish_with_eps(LatentNorm::DEFAULT_EPS)
}
fn finish_with_eps(self, eps: f32) -> Result<LatentNorm> {
LatentNorm::validate_eps(eps)?;
if self.acc.is_empty() {
return Err(Error::validation(
"cannot fit normalization over an empty set",
));
}
let count = self.acc[0].count;
if count == 0 {
return Err(Error::validation(
"cannot fit normalization: latents contributed no frames",
));
}
if self.acc.iter().any(|a| a.count != count) {
return Err(Error::validation(
"cannot fit normalization: inconsistent per-dimension frame counts",
));
}
let dim = self.acc.len();
let mut mean = Vec::with_capacity(dim);
let mut std = Vec::with_capacity(dim);
for a in &self.acc {
mean.push(a.mean as f32);
std.push((a.std().max(eps as f64)) as f32);
}
LatentNorm::from_stats(mean, std)
}
}
fn accumulate(seq: &FrameSeq, dim: usize, acc: &mut [DimAccumulator]) {
for (i, &x) in seq.values().iter().enumerate() {
acc[i % dim].push(x);
}
}
#[cfg(test)]
mod tests;