torsh_nn/layers/normalization/
common.rs1use parking_lot::RwLock;
7use std::sync::Arc;
8use torsh_core::error::Result;
9use torsh_tensor::{creation::*, Tensor};
10
11#[cfg(not(feature = "std"))]
14use hashbrown::HashMap;
15
16#[derive(Debug, Clone)]
18pub struct NormalizationConfig {
19 pub eps: f32,
21 pub momentum: f32,
23 pub affine: bool,
25 pub track_running_stats: bool,
27}
28
29impl Default for NormalizationConfig {
30 fn default() -> Self {
31 Self {
32 eps: 1e-5,
33 momentum: 0.1,
34 affine: true,
35 track_running_stats: true,
36 }
37 }
38}
39
40impl NormalizationConfig {
41 pub fn training() -> Self {
43 Self::default()
44 }
45
46 pub fn inference() -> Self {
48 Self {
49 track_running_stats: false,
50 ..Self::default()
51 }
52 }
53
54 pub fn non_affine() -> Self {
56 Self {
57 affine: false,
58 ..Self::default()
59 }
60 }
61
62 pub fn with_eps(eps: f32) -> Self {
64 Self {
65 eps,
66 ..Self::default()
67 }
68 }
69
70 pub fn with_momentum(momentum: f32) -> Self {
72 Self {
73 momentum,
74 ..Self::default()
75 }
76 }
77}
78
79#[derive(Debug, Clone)]
81pub struct NormalizationStats {
82 pub mean: Tensor,
83 pub var: Tensor,
84 pub running_mean: Option<Tensor>,
85 pub running_var: Option<Tensor>,
86 pub num_batches_tracked: Option<Tensor>,
87}
88
89impl NormalizationStats {
90 pub fn new(num_features: usize, track_running: bool) -> Result<Self> {
92 let mean = zeros(&[num_features])?;
93 let var = ones(&[num_features])?;
94
95 let (running_mean, running_var, num_batches_tracked) = if track_running {
96 (
97 Some(zeros(&[num_features])?),
98 Some(ones(&[num_features])?),
99 Some(zeros(&[1])?),
100 )
101 } else {
102 (None, None, None)
103 };
104
105 Ok(Self {
106 mean,
107 var,
108 running_mean,
109 running_var,
110 num_batches_tracked,
111 })
112 }
113
114 pub fn update_running_stats(
116 &mut self,
117 batch_mean: &Tensor,
118 batch_var: &Tensor,
119 momentum: f32,
120 ) -> Result<()> {
121 if let (Some(ref mut running_mean), Some(ref mut running_var)) =
122 (&mut self.running_mean, &mut self.running_var)
123 {
124 let one_minus_momentum = 1.0 - momentum;
126 *running_mean = running_mean
127 .mul_scalar(one_minus_momentum)?
128 .add(&batch_mean.mul_scalar(momentum)?)?;
129
130 *running_var = running_var
132 .mul_scalar(one_minus_momentum)?
133 .add(&batch_var.mul_scalar(momentum)?)?;
134
135 if let Some(ref mut num_batches) = self.num_batches_tracked {
137 *num_batches = num_batches.add_scalar(1.0)?;
138 }
139 }
140 Ok(())
141 }
142}
143
144#[derive(Debug, Clone)]
151pub struct RunningStats {
152 running_mean: Arc<RwLock<Tensor>>,
153 running_var: Arc<RwLock<Tensor>>,
154 num_batches_tracked: Arc<RwLock<Tensor>>,
155}
156
157impl RunningStats {
158 pub fn new(num_features: usize) -> Result<Self> {
160 Ok(Self {
161 running_mean: Arc::new(RwLock::new(zeros(&[num_features])?)),
162 running_var: Arc::new(RwLock::new(ones(&[num_features])?)),
163 num_batches_tracked: Arc::new(RwLock::new(zeros(&[1])?)),
164 })
165 }
166
167 pub fn running_mean_handle(&self) -> Arc<RwLock<Tensor>> {
169 Arc::clone(&self.running_mean)
170 }
171
172 pub fn running_var_handle(&self) -> Arc<RwLock<Tensor>> {
174 Arc::clone(&self.running_var)
175 }
176
177 pub fn num_batches_tracked_handle(&self) -> Arc<RwLock<Tensor>> {
179 Arc::clone(&self.num_batches_tracked)
180 }
181
182 pub fn running_mean(&self) -> Tensor {
184 self.running_mean.read().clone()
185 }
186
187 pub fn running_var(&self) -> Tensor {
189 self.running_var.read().clone()
190 }
191
192 pub fn num_batches_tracked(&self) -> Result<f32> {
194 let counter = self.num_batches_tracked.read();
195 Ok(counter.to_vec()?.first().copied().unwrap_or(0.0))
196 }
197
198 pub fn update(&self, batch_mean: &Tensor, batch_var: &Tensor, momentum: f32) -> Result<()> {
203 let one_minus_momentum = 1.0 - momentum;
204
205 {
206 let mut running_mean = self.running_mean.write();
207 *running_mean = running_mean
208 .mul_scalar(one_minus_momentum)?
209 .add(&batch_mean.mul_scalar(momentum)?)?;
210 }
211 {
212 let mut running_var = self.running_var.write();
213 *running_var = running_var
214 .mul_scalar(one_minus_momentum)?
215 .add(&batch_var.mul_scalar(momentum)?)?;
216 }
217 {
218 let mut counter = self.num_batches_tracked.write();
219 *counter = counter.add_scalar(1.0)?;
220 }
221
222 Ok(())
223 }
224}
225
226pub fn unbiased_variance(biased: &Tensor, count: usize) -> Result<Tensor> {
232 if count > 1 {
233 biased.mul_scalar(count as f32 / (count - 1) as f32)
234 } else {
235 Ok(biased.clone())
236 }
237}
238
239pub mod utils {
241 use super::*;
242
243 fn channel_reduce_dims(rank: usize) -> Vec<usize> {
246 core::iter::once(0usize).chain(2..rank).collect()
247 }
248
249 fn validate_channel_rank(dims: &[usize]) -> Result<()> {
252 match dims.len() {
253 2 | 4 | 5 => Ok(()),
254 other => Err(torsh_core::error::TorshError::InvalidShape(format!(
255 "Unsupported input dimensions: {other}"
256 ))),
257 }
258 }
259
260 pub fn compute_channel_mean(input: &Tensor) -> Result<Tensor> {
269 let input_shape = input.shape();
270 let dims = input_shape.dims();
271 validate_channel_rank(dims)?;
272
273 input.mean(Some(&channel_reduce_dims(dims.len())), false)
274 }
275
276 pub fn compute_channel_variance(input: &Tensor, mean: &Tensor) -> Result<Tensor> {
283 let input_shape = input.shape();
284 let dims = input_shape.dims();
285 validate_channel_rank(dims)?;
286
287 let broadcast = channel_broadcast_shape(dims.len(), dims[1]);
288 let centered = input.sub(&mean.reshape(&broadcast)?)?;
289 centered
290 .pow_scalar(2.0)?
291 .mean(Some(&channel_reduce_dims(dims.len())), false)
292 }
293
294 pub fn channel_broadcast_shape(rank: usize, channels: usize) -> Vec<i32> {
300 let mut shape = vec![1i32; rank];
301 if rank >= 2 {
302 shape[1] = channels as i32;
303 }
304 shape
305 }
306
307 pub fn detached_statistic(stat: &Tensor) -> Result<Tensor> {
314 Tensor::from_data(stat.to_vec()?, stat.shape().dims().to_vec(), stat.device())
315 }
316
317 pub fn instance_normalize(
325 input: &Tensor,
326 weight: Option<&Tensor>,
327 bias: Option<&Tensor>,
328 eps: f32,
329 ) -> Result<Tensor> {
330 let input_shape = input.shape();
331 let dims = input_shape.dims();
332
333 if dims.len() < 2 {
334 return Err(torsh_core::error::TorshError::InvalidShape(format!(
335 "instance normalization expects an (N, C, ...) tensor, got {dims:?}"
336 )));
337 }
338
339 let instances = dims[0] * dims[1];
340 let spatial: usize = dims[2..].iter().product::<usize>().max(1);
341
342 let flat = input.reshape(&[instances as i32, spatial as i32])?;
343 let mean = flat.mean(Some(&[1]), true)?;
344 let centered = flat.sub(&mean)?;
345 let variance = centered.pow_scalar(2.0)?.mean(Some(&[1]), true)?;
346 let std = variance.add_scalar(eps)?.sqrt()?;
347
348 let original: Vec<i32> = dims.iter().map(|&d| d as i32).collect();
349 let mut normalized = centered.div(&std)?.reshape(&original)?;
350
351 let broadcast = channel_broadcast_shape(dims.len(), dims[1]);
352 if let Some(w) = weight {
353 normalized = normalized.mul(&w.reshape(&broadcast)?)?;
354 }
355 if let Some(b) = bias {
356 normalized = normalized.add(&b.reshape(&broadcast)?)?;
357 }
358
359 Ok(normalized)
360 }
361
362 pub fn apply_channel_normalization(
370 input: &Tensor,
371 mean: &Tensor,
372 var: &Tensor,
373 weight: Option<&Tensor>,
374 bias: Option<&Tensor>,
375 eps: f32,
376 ) -> Result<Tensor> {
377 let input_shape = input.shape();
378 let dims = input_shape.dims();
379
380 if dims.len() < 2 {
381 return Err(torsh_core::error::TorshError::InvalidShape(format!(
382 "channel normalization expects an (N, C, ...) tensor, got {dims:?}"
383 )));
384 }
385
386 let channels = dims[1];
387 let broadcast = channel_broadcast_shape(dims.len(), channels);
388
389 let inv_std = var.add_scalar(eps)?.sqrt()?;
390 let centered = input.sub(&mean.reshape(&broadcast)?)?;
391 let mut normalized = centered.div(&inv_std.reshape(&broadcast)?)?;
392
393 if let Some(w) = weight {
394 normalized = normalized.mul(&w.reshape(&broadcast)?)?;
395 }
396 if let Some(b) = bias {
397 normalized = normalized.add(&b.reshape(&broadcast)?)?;
398 }
399
400 Ok(normalized)
401 }
402
403 pub fn apply_channel_affine(input: &Tensor, scale: &[f32], shift: &[f32]) -> Result<Tensor> {
412 let input_shape = input.shape();
413 let dims = input_shape.dims();
414
415 if dims.len() < 2 {
416 return Err(torsh_core::error::TorshError::InvalidShape(format!(
417 "channel affine expects an (N, C, ...) tensor, got {dims:?}"
418 )));
419 }
420
421 let channels = dims[1];
422 if scale.len() != channels || shift.len() != channels {
423 return Err(torsh_core::error::TorshError::InvalidShape(format!(
424 "expected {channels} scale/shift values, got {} and {}",
425 scale.len(),
426 shift.len()
427 )));
428 }
429
430 let broadcast: Vec<usize> = channel_broadcast_shape(dims.len(), channels)
431 .into_iter()
432 .map(|d| d as usize)
433 .collect();
434 let scale_tensor = Tensor::from_data(scale.to_vec(), broadcast.clone(), input.device())?;
435 let shift_tensor = Tensor::from_data(shift.to_vec(), broadcast, input.device())?;
436
437 input.mul(&scale_tensor)?.add(&shift_tensor)
438 }
439
440 pub fn apply_normalization(
453 input: &Tensor,
454 mean: &Tensor,
455 var: &Tensor,
456 weight: Option<&Tensor>,
457 bias: Option<&Tensor>,
458 eps: f32,
459 ) -> Result<Tensor> {
460 match try_apply_normalization_simple(input, mean, var, weight, bias, eps) {
463 Ok(result) => Ok(result),
464 Err(_) => {
465 apply_normalization_with_broadcasting(input, mean, var, weight, bias, eps)
467 }
468 }
469 }
470
471 fn try_apply_normalization_simple(
473 input: &Tensor,
474 mean: &Tensor,
475 var: &Tensor,
476 weight: Option<&Tensor>,
477 bias: Option<&Tensor>,
478 eps: f32,
479 ) -> Result<Tensor> {
480 let centered = input.sub(mean)?;
482
483 let std = var.add_scalar(eps)?.sqrt()?;
485
486 let mut normalized = centered.div(&std)?;
488
489 if let Some(w) = weight {
491 normalized = normalized.mul(w)?;
492 }
493
494 if let Some(b) = bias {
495 normalized = normalized.add(b)?;
496 }
497
498 Ok(normalized)
499 }
500
501 fn apply_normalization_with_broadcasting(
503 input: &Tensor,
504 mean: &Tensor,
505 var: &Tensor,
506 weight: Option<&Tensor>,
507 bias: Option<&Tensor>,
508 eps: f32,
509 ) -> Result<Tensor> {
510 let input_shape = input.shape();
511 let input_dims = input_shape.dims();
512 let mean_shape = mean.shape();
513 let mean_dims = mean_shape.dims();
514
515 let (broadcast_mean, broadcast_var) = if input_dims.len() == 4 && mean_dims.len() == 1 {
518 let channels = mean_dims[0];
519 let mean_broadcast = mean.reshape(&[1i32, channels as i32, 1i32, 1i32])?;
520 let var_broadcast = var.reshape(&[1i32, channels as i32, 1i32, 1i32])?;
521 (mean_broadcast, var_broadcast)
522 } else if input_dims.len() == 2 && mean_dims.len() == 1 {
523 let channels = mean_dims[0];
524 let mean_broadcast = mean.reshape(&[1i32, channels as i32])?;
525 let var_broadcast = var.reshape(&[1i32, channels as i32])?;
526 (mean_broadcast, var_broadcast)
527 } else {
528 (mean.clone(), var.clone())
530 };
531
532 let centered = input.sub(&broadcast_mean)?;
534
535 let std = broadcast_var.add_scalar(eps)?.sqrt()?;
537
538 let mut normalized = centered.div(&std)?;
540
541 if let Some(w) = weight {
543 let weight_shape = w.shape();
544 let weight_dims = weight_shape.dims();
545 let broadcast_weight = if input_dims.len() == 4 && weight_dims.len() == 1 {
546 let channels = weight_dims[0];
547 w.reshape(&[1i32, channels as i32, 1i32, 1i32])?
548 } else if input_dims.len() == 2 && weight_dims.len() == 1 {
549 let channels = weight_dims[0];
550 w.reshape(&[1i32, channels as i32])?
551 } else {
552 w.clone()
553 };
554 normalized = normalized.mul(&broadcast_weight)?;
555 }
556
557 if let Some(b) = bias {
558 let bias_shape = b.shape();
559 let bias_dims = bias_shape.dims();
560 let broadcast_bias = if input_dims.len() == 4 && bias_dims.len() == 1 {
561 let channels = bias_dims[0];
562 b.reshape(&[1i32, channels as i32, 1i32, 1i32])?
563 } else if input_dims.len() == 2 && bias_dims.len() == 1 {
564 let channels = bias_dims[0];
565 b.reshape(&[1i32, channels as i32])?
566 } else {
567 b.clone()
568 };
569 normalized = normalized.add(&broadcast_bias)?;
570 }
571
572 Ok(normalized)
573 }
574}
575
576#[cfg(test)]
577mod tests {
578 use super::*;
579
580 #[test]
581 fn test_normalization_config() {
582 let config = NormalizationConfig::default();
583 assert_eq!(config.eps, 1e-5);
584 assert_eq!(config.momentum, 0.1);
585 assert!(config.affine);
586 assert!(config.track_running_stats);
587
588 let inference_config = NormalizationConfig::inference();
589 assert!(!inference_config.track_running_stats);
590
591 let non_affine_config = NormalizationConfig::non_affine();
592 assert!(!non_affine_config.affine);
593 }
594
595 #[test]
596 fn test_normalization_stats_creation() {
597 let stats = NormalizationStats::new(10, true).expect("Normalization Stats should succeed");
598 assert!(stats.running_mean.is_some());
599 assert!(stats.running_var.is_some());
600 assert!(stats.num_batches_tracked.is_some());
601
602 let stats_no_tracking =
603 NormalizationStats::new(10, false).expect("Normalization Stats should succeed");
604 assert!(stats_no_tracking.running_mean.is_none());
605 assert!(stats_no_tracking.running_var.is_none());
606 assert!(stats_no_tracking.num_batches_tracked.is_none());
607 }
608
609 #[test]
610 fn test_channel_mean_computation() {
611 let input = Tensor::from_data(
613 vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0],
614 vec![2, 3],
615 torsh_core::device::DeviceType::Cpu,
616 )
617 .expect("operation should succeed");
618 let mean = utils::compute_channel_mean(&input).expect("utils should succeed");
619 let expected_mean = vec![2.5, 3.5, 4.5]; let mean_data = mean
621 .to_vec()
622 .expect("tensor to vec conversion should succeed");
623
624 for (i, &expected) in expected_mean.iter().enumerate() {
625 assert!((mean_data[i] - expected).abs() < 1e-6);
626 }
627 }
628}