#[derive(Debug, Clone)]
pub struct RunningMeanStd {
mean: Vec<f32>,
var: Vec<f32>,
count: f64,
epsilon: f32,
}
impl RunningMeanStd {
pub fn new(size: usize, epsilon: f32) -> Self {
Self { mean: vec![0.0; size], var: vec![1.0; size], count: 1e-4, epsilon }
}
#[allow(clippy::needless_range_loop)]
pub fn update(&mut self, observations: &[Vec<f32>]) {
if observations.is_empty() {
return;
}
let batch_size = observations.len() as f64;
let obs_dim = self.mean.len();
let mut batch_mean = vec![0.0; obs_dim];
for obs in observations {
for (i, &val) in obs.iter().enumerate() {
batch_mean[i] += val as f64;
}
}
for val in &mut batch_mean {
*val /= batch_size;
}
let mut batch_var = vec![0.0; obs_dim];
for obs in observations {
for (i, &val) in obs.iter().enumerate() {
let diff = val as f64 - batch_mean[i];
batch_var[i] += diff * diff;
}
}
for val in &mut batch_var {
*val /= batch_size;
}
let delta = batch_mean
.iter()
.zip(&self.mean)
.map(|(b, m)| b - *m as f64)
.collect::<Vec<_>>();
let total_count = self.count + batch_size;
for i in 0..obs_dim {
self.mean[i] = (self.mean[i] as f64 + delta[i] * batch_size / total_count) as f32;
}
for i in 0..obs_dim {
let m_a = self.var[i] as f64 * self.count;
let m_b = batch_var[i] * batch_size;
let m2 = m_a + m_b + delta[i].powi(2) * self.count * batch_size / total_count;
self.var[i] = (m2 / total_count) as f32;
}
self.count = total_count;
}
pub fn normalize(&self, observations: &[f32]) -> Vec<f32> {
observations
.iter()
.zip(&self.mean)
.zip(&self.var)
.map(|((&obs, &mean), &var)| (obs - mean) / (var.sqrt() + self.epsilon))
.collect()
}
pub fn normalize_batch(&self, observations: &mut [Vec<f32>]) {
for obs in observations {
*obs = self.normalize(obs);
}
}
pub fn mean(&self) -> &[f32] {
&self.mean
}
pub fn std(&self) -> Vec<f32> {
self.var.iter().map(|v| (v + self.epsilon).sqrt()).collect()
}
pub fn count(&self) -> f64 {
self.count
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_normalize_basic() {
let mut normalizer = RunningMeanStd::new(2, 1e-8);
let data = vec![vec![1.0, 2.0], vec![2.0, 4.0], vec![3.0, 6.0]];
normalizer.update(&data);
assert!((normalizer.mean()[0] - 2.0).abs() < 0.1);
assert!((normalizer.mean()[1] - 4.0).abs() < 0.1);
let obs = vec![2.0, 4.0];
let normalized = normalizer.normalize(&obs);
assert!(normalized[0].abs() < 0.5);
assert!(normalized[1].abs() < 0.5);
}
#[test]
fn test_normalize_batch() {
let mut normalizer = RunningMeanStd::new(2, 1e-8);
let mut batch = vec![vec![0.0, 0.0], vec![1.0, 1.0]];
normalizer.update(&batch);
normalizer.normalize_batch(&mut batch);
let sum: f32 = batch.iter().flatten().sum();
assert!(sum.abs() < 0.5);
}
#[test]
fn test_incremental_update() {
let mut normalizer = RunningMeanStd::new(1, 1e-8);
normalizer.update(&[vec![1.0]]);
normalizer.update(&[vec![2.0]]);
normalizer.update(&[vec![3.0]]);
assert!((normalizer.mean()[0] - 2.0).abs() < 0.1);
}
}