thrust_rl/utils/
normalize.rs1#[derive(Debug, Clone)]
11pub struct RunningMeanStd {
12 mean: Vec<f32>,
13 var: Vec<f32>,
14 count: f64,
15 epsilon: f32,
16}
17
18impl RunningMeanStd {
19 pub fn new(size: usize, epsilon: f32) -> Self {
25 Self { mean: vec![0.0; size], var: vec![1.0; size], count: 1e-4, epsilon }
26 }
27
28 #[allow(clippy::needless_range_loop)]
36 pub fn update(&mut self, observations: &[Vec<f32>]) {
37 if observations.is_empty() {
38 return;
39 }
40
41 let batch_size = observations.len() as f64;
42 let obs_dim = self.mean.len();
43
44 let mut batch_mean = vec![0.0; obs_dim];
46 for obs in observations {
47 for (i, &val) in obs.iter().enumerate() {
48 batch_mean[i] += val as f64;
49 }
50 }
51 for val in &mut batch_mean {
52 *val /= batch_size;
53 }
54
55 let mut batch_var = vec![0.0; obs_dim];
56 for obs in observations {
57 for (i, &val) in obs.iter().enumerate() {
58 let diff = val as f64 - batch_mean[i];
59 batch_var[i] += diff * diff;
60 }
61 }
62 for val in &mut batch_var {
63 *val /= batch_size;
64 }
65
66 let delta = batch_mean
68 .iter()
69 .zip(&self.mean)
70 .map(|(b, m)| b - *m as f64)
71 .collect::<Vec<_>>();
72
73 let total_count = self.count + batch_size;
74
75 for i in 0..obs_dim {
77 self.mean[i] = (self.mean[i] as f64 + delta[i] * batch_size / total_count) as f32;
78 }
79
80 for i in 0..obs_dim {
82 let m_a = self.var[i] as f64 * self.count;
83 let m_b = batch_var[i] * batch_size;
84 let m2 = m_a + m_b + delta[i].powi(2) * self.count * batch_size / total_count;
85 self.var[i] = (m2 / total_count) as f32;
86 }
87
88 self.count = total_count;
89 }
90
91 pub fn normalize(&self, observations: &[f32]) -> Vec<f32> {
99 observations
100 .iter()
101 .zip(&self.mean)
102 .zip(&self.var)
103 .map(|((&obs, &mean), &var)| (obs - mean) / (var.sqrt() + self.epsilon))
104 .collect()
105 }
106
107 pub fn normalize_batch(&self, observations: &mut [Vec<f32>]) {
112 for obs in observations {
113 *obs = self.normalize(obs);
114 }
115 }
116
117 pub fn mean(&self) -> &[f32] {
119 &self.mean
120 }
121
122 pub fn std(&self) -> Vec<f32> {
124 self.var.iter().map(|v| (v + self.epsilon).sqrt()).collect()
125 }
126
127 pub fn count(&self) -> f64 {
129 self.count
130 }
131}
132
133#[cfg(test)]
134mod tests {
135 use super::*;
136
137 #[test]
138 fn test_normalize_basic() {
139 let mut normalizer = RunningMeanStd::new(2, 1e-8);
140
141 let data = vec![vec![1.0, 2.0], vec![2.0, 4.0], vec![3.0, 6.0]];
143 normalizer.update(&data);
144
145 assert!((normalizer.mean()[0] - 2.0).abs() < 0.1);
147 assert!((normalizer.mean()[1] - 4.0).abs() < 0.1);
148
149 let obs = vec![2.0, 4.0];
151 let normalized = normalizer.normalize(&obs);
152
153 assert!(normalized[0].abs() < 0.5);
155 assert!(normalized[1].abs() < 0.5);
156 }
157
158 #[test]
159 fn test_normalize_batch() {
160 let mut normalizer = RunningMeanStd::new(2, 1e-8);
161
162 let mut batch = vec![vec![0.0, 0.0], vec![1.0, 1.0]];
163
164 normalizer.update(&batch);
165 normalizer.normalize_batch(&mut batch);
166
167 let sum: f32 = batch.iter().flatten().sum();
169 assert!(sum.abs() < 0.5);
170 }
171
172 #[test]
173 fn test_incremental_update() {
174 let mut normalizer = RunningMeanStd::new(1, 1e-8);
175
176 normalizer.update(&[vec![1.0]]);
178 normalizer.update(&[vec![2.0]]);
179 normalizer.update(&[vec![3.0]]);
180
181 assert!((normalizer.mean()[0] - 2.0).abs() < 0.1);
183 }
184}