1use std::f64::consts::PI;
15
16#[derive(Debug, Clone, PartialEq, Eq)]
22pub enum FanMode {
23 FanIn,
25 FanOut,
27 Average,
29}
30
31#[derive(Debug, Clone, PartialEq)]
33pub enum InitDistribution {
34 Uniform,
36 Normal,
38}
39
40#[derive(Debug, Clone, PartialEq)]
42pub enum InitStrategy {
43 Zeros,
45 Ones,
47 Constant(f64),
49 Xavier(InitDistribution),
51 He(FanMode, InitDistribution),
53 Kaiming(FanMode, InitDistribution),
55 LeCun(InitDistribution),
57 Orthogonal(f64),
59 Sparse(f64),
61 TruncatedNormal {
63 mean: f64,
65 std: f64,
67 a: f64,
69 b: f64,
71 },
72}
73
74#[derive(Debug, Clone)]
80pub struct WeightInitConfig {
81 pub strategy: InitStrategy,
83 pub seed: u64,
85 pub gain: f64,
87}
88
89impl Default for WeightInitConfig {
90 fn default() -> Self {
91 Self {
92 strategy: InitStrategy::Xavier(InitDistribution::Uniform),
93 seed: 42,
94 gain: 1.0,
95 }
96 }
97}
98
99#[derive(Debug, Clone)]
101pub struct TensorShape {
102 pub dims: Vec<usize>,
104}
105
106impl TensorShape {
107 pub fn new(dims: Vec<usize>) -> Self {
109 Self { dims }
110 }
111
112 pub fn numel(&self) -> usize {
114 self.dims.iter().product()
115 }
116}
117
118#[derive(Debug, Clone)]
120pub struct InitStats {
121 pub total_params: u64,
123 pub mean: f64,
125 pub variance: f64,
127 pub min: f64,
129 pub max: f64,
131}
132
133pub struct WeightInitializer {
139 config: WeightInitConfig,
140 rng_state: u64,
141 initialized_count: u64,
142}
143
144impl WeightInitializer {
145 pub fn new(config: WeightInitConfig) -> Self {
147 let seed = if config.seed == 0 { 1 } else { config.seed };
148 Self {
149 rng_state: seed,
150 config,
151 initialized_count: 0,
152 }
153 }
154
155 pub fn initialize(&mut self, shape: &TensorShape) -> Vec<f64> {
159 let n = shape.numel();
160 let (fan_in, fan_out) = Self::compute_fan(shape);
161 let gain = self.config.gain;
162
163 let strategy = self.config.strategy.clone();
164 let weights = match strategy {
165 InitStrategy::Zeros => vec![0.0; n],
166 InitStrategy::Ones => vec![1.0; n],
167 InitStrategy::Constant(v) => vec![v; n],
168 InitStrategy::Xavier(ref dist) => self.xavier_init(n, fan_in, fan_out, gain, dist),
169 InitStrategy::He(ref mode, ref dist) | InitStrategy::Kaiming(ref mode, ref dist) => {
170 self.he_init(n, fan_in, fan_out, gain, mode, dist)
171 }
172 InitStrategy::LeCun(ref dist) => self.lecun_init(n, fan_in, gain, dist),
173 InitStrategy::Orthogonal(g) => self.orthogonal_init(shape, g),
174 InitStrategy::Sparse(sparsity) => self.sparse_init(n, sparsity),
175 InitStrategy::TruncatedNormal { mean, std, a, b } => {
176 self.truncated_normal_init(n, mean, std, a, b)
177 }
178 };
179
180 self.initialized_count += n as u64;
181 weights
182 }
183
184 pub fn compute_fan(shape: &TensorShape) -> (usize, usize) {
191 match shape.dims.len() {
192 0 => (1, 1),
193 1 => (shape.dims[0], shape.dims[0]),
194 2 => (shape.dims[1], shape.dims[0]),
195 _ => {
196 let receptive: usize = shape.dims[2..].iter().product();
197 let fan_in = shape.dims[1] * receptive;
198 let fan_out = shape.dims[0] * receptive;
199 (fan_in, fan_out)
200 }
201 }
202 }
203
204 pub fn xavier_bound(fan_in: usize, fan_out: usize, gain: f64) -> f64 {
206 gain * (6.0 / (fan_in + fan_out) as f64).sqrt()
207 }
208
209 pub fn he_bound(fan: usize, gain: f64) -> f64 {
211 gain * (3.0 / fan as f64).sqrt()
212 }
213
214 pub fn next_uniform(&mut self, low: f64, high: f64) -> f64 {
216 let u = self.next_unit();
217 low + u * (high - low)
218 }
219
220 pub fn next_normal(&mut self, mean: f64, std: f64) -> f64 {
222 let u1 = self.next_unit().max(1e-300); let u2 = self.next_unit();
224 let z = (-2.0 * u1.ln()).sqrt() * (2.0 * PI * u2).cos();
225 mean + std * z
226 }
227
228 pub fn compute_stats(weights: &[f64]) -> InitStats {
230 if weights.is_empty() {
231 return InitStats {
232 total_params: 0,
233 mean: 0.0,
234 variance: 0.0,
235 min: 0.0,
236 max: 0.0,
237 };
238 }
239
240 let n = weights.len() as f64;
241 let sum: f64 = weights.iter().sum();
242 let mean = sum / n;
243
244 let var_sum: f64 = weights.iter().map(|w| (w - mean).powi(2)).sum();
245 let variance = var_sum / n;
246
247 let min = weights.iter().copied().fold(f64::INFINITY, f64::min);
248 let max = weights.iter().copied().fold(f64::NEG_INFINITY, f64::max);
249
250 InitStats {
251 total_params: weights.len() as u64,
252 mean,
253 variance,
254 min,
255 max,
256 }
257 }
258
259 pub fn reset_seed(&mut self, seed: u64) {
261 self.rng_state = if seed == 0 { 1 } else { seed };
262 }
263
264 pub fn initialized_count(&self) -> u64 {
266 self.initialized_count
267 }
268
269 fn next_unit(&mut self) -> f64 {
273 let mut x = self.rng_state;
274 x ^= x << 13;
275 x ^= x >> 7;
276 x ^= x << 17;
277 self.rng_state = x;
278 (x as f64) / (u64::MAX as f64)
280 }
281
282 fn xavier_init(
283 &mut self,
284 n: usize,
285 fan_in: usize,
286 fan_out: usize,
287 gain: f64,
288 dist: &InitDistribution,
289 ) -> Vec<f64> {
290 match dist {
291 InitDistribution::Uniform => {
292 let bound = Self::xavier_bound(fan_in, fan_out, gain);
293 (0..n).map(|_| self.next_uniform(-bound, bound)).collect()
294 }
295 InitDistribution::Normal => {
296 let std = gain * (2.0 / (fan_in + fan_out) as f64).sqrt();
297 (0..n).map(|_| self.next_normal(0.0, std)).collect()
298 }
299 }
300 }
301
302 fn he_init(
303 &mut self,
304 n: usize,
305 fan_in: usize,
306 fan_out: usize,
307 gain: f64,
308 mode: &FanMode,
309 dist: &InitDistribution,
310 ) -> Vec<f64> {
311 let fan = match mode {
312 FanMode::FanIn => fan_in,
313 FanMode::FanOut => fan_out,
314 FanMode::Average => (fan_in + fan_out) / 2,
315 };
316 match dist {
317 InitDistribution::Uniform => {
318 let bound = Self::he_bound(fan, gain);
319 (0..n).map(|_| self.next_uniform(-bound, bound)).collect()
320 }
321 InitDistribution::Normal => {
322 let std = gain * (2.0 / fan as f64).sqrt();
323 (0..n).map(|_| self.next_normal(0.0, std)).collect()
324 }
325 }
326 }
327
328 fn lecun_init(
329 &mut self,
330 n: usize,
331 fan_in: usize,
332 gain: f64,
333 dist: &InitDistribution,
334 ) -> Vec<f64> {
335 match dist {
336 InitDistribution::Uniform => {
337 let bound = gain * (3.0 / fan_in as f64).sqrt();
338 (0..n).map(|_| self.next_uniform(-bound, bound)).collect()
339 }
340 InitDistribution::Normal => {
341 let std = gain * (1.0 / fan_in as f64).sqrt();
342 (0..n).map(|_| self.next_normal(0.0, std)).collect()
343 }
344 }
345 }
346
347 fn orthogonal_init(&mut self, shape: &TensorShape, gain: f64) -> Vec<f64> {
354 let (rows, cols) = match shape.dims.len() {
355 0 => (1, 1),
356 1 => (shape.dims[0], 1),
357 2 => (shape.dims[0], shape.dims[1]),
358 _ => {
359 let rest: usize = shape.dims[1..].iter().product();
360 (shape.dims[0], rest)
361 }
362 };
363
364 let n = rows.max(cols);
365 let m = rows.min(cols);
366
367 let mut mat: Vec<Vec<f64>> = (0..n)
369 .map(|_| (0..m).map(|_| self.next_normal(0.0, 1.0)).collect())
370 .collect();
371
372 for j in 0..m {
374 let norm = col_norm(&mat, j, n);
376 if norm > 1e-15 {
377 for row in mat.iter_mut().take(n) {
378 row[j] /= norm;
379 }
380 }
381 for k in (j + 1)..m {
383 let dot = col_dot(&mat, j, k, n);
384 for row in mat.iter_mut().take(n) {
385 row[k] -= dot * row[j];
386 }
387 }
388 }
389
390 let mut result = Vec::with_capacity(rows * cols);
392 if rows <= cols {
393 for row in mat.iter().take(rows) {
395 for val in row.iter().take(cols.min(m)) {
396 result.push(val * gain);
397 }
398 if cols > m {
400 result.extend(std::iter::repeat_n(0.0, cols - m));
401 }
402 }
403 } else {
404 for row in mat.iter().take(rows) {
406 for val in row.iter().take(cols) {
407 result.push(val * gain);
408 }
409 }
410 }
411
412 result
413 }
414
415 fn sparse_init(&mut self, n: usize, sparsity: f64) -> Vec<f64> {
418 let sparsity = sparsity.clamp(0.0, 1.0);
419 (0..n)
420 .map(|_| {
421 let u = self.next_unit();
422 if u < sparsity {
423 0.0
424 } else {
425 self.next_normal(0.0, 1.0)
426 }
427 })
428 .collect()
429 }
430
431 fn truncated_normal_init(&mut self, n: usize, mean: f64, std: f64, a: f64, b: f64) -> Vec<f64> {
433 (0..n)
434 .map(|_| {
435 for _ in 0..1000 {
438 let v = self.next_normal(mean, std);
439 if v >= a && v <= b {
440 return v;
441 }
442 }
443 self.next_normal(mean, std).clamp(a, b)
445 })
446 .collect()
447 }
448}
449
450fn col_norm(mat: &[Vec<f64>], j: usize, rows: usize) -> f64 {
456 let s: f64 = mat.iter().take(rows).map(|row| row[j] * row[j]).sum();
457 s.sqrt()
458}
459
460fn col_dot(mat: &[Vec<f64>], j: usize, k: usize, rows: usize) -> f64 {
462 mat.iter().take(rows).map(|row| row[j] * row[k]).sum()
463}
464
465#[cfg(test)]
470mod tests {
471 use super::*;
472
473 fn make_init(strategy: InitStrategy) -> WeightInitializer {
476 WeightInitializer::new(WeightInitConfig {
477 strategy,
478 seed: 12345,
479 gain: 1.0,
480 })
481 }
482
483 fn shape2d(r: usize, c: usize) -> TensorShape {
484 TensorShape::new(vec![r, c])
485 }
486
487 #[test]
490 fn test_zeros() {
491 let mut init = make_init(InitStrategy::Zeros);
492 let w = init.initialize(&shape2d(3, 4));
493 assert_eq!(w.len(), 12);
494 assert!(w.iter().all(|&v| v == 0.0));
495 }
496
497 #[test]
498 fn test_ones() {
499 let mut init = make_init(InitStrategy::Ones);
500 let w = init.initialize(&shape2d(3, 4));
501 assert_eq!(w.len(), 12);
502 assert!(w.iter().all(|&v| v == 1.0));
503 }
504
505 #[test]
506 fn test_constant() {
507 let mut init = make_init(InitStrategy::Constant(std::f64::consts::PI));
508 let w = init.initialize(&shape2d(2, 5));
509 assert_eq!(w.len(), 10);
510 assert!(w.iter().all(|&v| (v - std::f64::consts::PI).abs() < 1e-12));
511 }
512
513 #[test]
516 fn test_fan_1d() {
517 let s = TensorShape::new(vec![128]);
518 let (fi, fo) = WeightInitializer::compute_fan(&s);
519 assert_eq!(fi, 128);
520 assert_eq!(fo, 128);
521 }
522
523 #[test]
524 fn test_fan_2d() {
525 let s = shape2d(64, 128);
526 let (fi, fo) = WeightInitializer::compute_fan(&s);
527 assert_eq!(fi, 128);
528 assert_eq!(fo, 64);
529 }
530
531 #[test]
532 fn test_fan_4d() {
533 let s = TensorShape::new(vec![32, 16, 3, 3]);
535 let (fi, fo) = WeightInitializer::compute_fan(&s);
536 assert_eq!(fi, 16 * 9);
537 assert_eq!(fo, 32 * 9);
538 }
539
540 #[test]
541 fn test_fan_0d() {
542 let s = TensorShape::new(vec![]);
543 let (fi, fo) = WeightInitializer::compute_fan(&s);
544 assert_eq!(fi, 1);
545 assert_eq!(fo, 1);
546 }
547
548 #[test]
549 fn test_fan_3d() {
550 let s = TensorShape::new(vec![64, 32, 5]);
552 let (fi, fo) = WeightInitializer::compute_fan(&s);
553 assert_eq!(fi, 32 * 5);
554 assert_eq!(fo, 64 * 5);
555 }
556
557 #[test]
560 fn test_xavier_uniform_variance() {
561 let mut init = WeightInitializer::new(WeightInitConfig {
562 strategy: InitStrategy::Xavier(InitDistribution::Uniform),
563 seed: 42,
564 gain: 1.0,
565 });
566 let shape = shape2d(512, 256);
567 let w = init.initialize(&shape);
568
569 let stats = WeightInitializer::compute_stats(&w);
570 let expected_var = 2.0 / (512.0 + 256.0);
573 let rel_err = (stats.variance - expected_var).abs() / expected_var;
574 assert!(
575 rel_err < 0.1,
576 "Xavier uniform var {:.6} vs expected {:.6}",
577 stats.variance,
578 expected_var
579 );
580 }
581
582 #[test]
583 fn test_xavier_normal_variance() {
584 let mut init = WeightInitializer::new(WeightInitConfig {
585 strategy: InitStrategy::Xavier(InitDistribution::Normal),
586 seed: 42,
587 gain: 1.0,
588 });
589 let shape = shape2d(512, 256);
590 let w = init.initialize(&shape);
591
592 let stats = WeightInitializer::compute_stats(&w);
593 let expected_var = 2.0 / (512.0 + 256.0);
594 let rel_err = (stats.variance - expected_var).abs() / expected_var;
595 assert!(
596 rel_err < 0.15,
597 "Xavier normal var {:.6} vs expected {:.6}",
598 stats.variance,
599 expected_var
600 );
601 }
602
603 #[test]
606 fn test_he_fan_in_uniform() {
607 let mut init = WeightInitializer::new(WeightInitConfig {
608 strategy: InitStrategy::He(FanMode::FanIn, InitDistribution::Uniform),
609 seed: 77,
610 gain: 1.0,
611 });
612 let shape = shape2d(256, 512);
613 let w = init.initialize(&shape);
614
615 let stats = WeightInitializer::compute_stats(&w);
616 let expected_var = 1.0 / 512.0;
631 let rel_err = (stats.variance - expected_var).abs() / expected_var;
632 assert!(
633 rel_err < 0.15,
634 "He fan_in uniform var {:.6} vs expected {:.6}",
635 stats.variance,
636 expected_var
637 );
638 }
639
640 #[test]
641 fn test_he_fan_in_normal() {
642 let mut init = WeightInitializer::new(WeightInitConfig {
643 strategy: InitStrategy::He(FanMode::FanIn, InitDistribution::Normal),
644 seed: 99,
645 gain: 1.0,
646 });
647 let shape = shape2d(256, 512);
648 let w = init.initialize(&shape);
649 let stats = WeightInitializer::compute_stats(&w);
650 let expected_var = 2.0 / 512.0;
652 let rel_err = (stats.variance - expected_var).abs() / expected_var;
653 assert!(
654 rel_err < 0.15,
655 "He fan_in normal var {:.6} vs expected {:.6}",
656 stats.variance,
657 expected_var
658 );
659 }
660
661 #[test]
662 fn test_kaiming_is_he_alias() {
663 let seed = 555;
664 let shape = shape2d(64, 128);
665
666 let mut init_he = WeightInitializer::new(WeightInitConfig {
667 strategy: InitStrategy::He(FanMode::FanIn, InitDistribution::Uniform),
668 seed,
669 gain: 1.0,
670 });
671 let mut init_kaiming = WeightInitializer::new(WeightInitConfig {
672 strategy: InitStrategy::Kaiming(FanMode::FanIn, InitDistribution::Uniform),
673 seed,
674 gain: 1.0,
675 });
676
677 let w1 = init_he.initialize(&shape);
678 let w2 = init_kaiming.initialize(&shape);
679 assert_eq!(w1, w2, "Kaiming must produce the same weights as He");
680 }
681
682 #[test]
683 fn test_he_fan_out() {
684 let mut init = WeightInitializer::new(WeightInitConfig {
685 strategy: InitStrategy::He(FanMode::FanOut, InitDistribution::Normal),
686 seed: 42,
687 gain: 1.0,
688 });
689 let shape = shape2d(256, 512);
690 let w = init.initialize(&shape);
691 let stats = WeightInitializer::compute_stats(&w);
692 let expected_var = 2.0 / 256.0;
694 let rel_err = (stats.variance - expected_var).abs() / expected_var;
695 assert!(
696 rel_err < 0.15,
697 "He fan_out normal var {:.6} vs expected {:.6}",
698 stats.variance,
699 expected_var
700 );
701 }
702
703 #[test]
704 fn test_he_average_mode() {
705 let mut init = WeightInitializer::new(WeightInitConfig {
706 strategy: InitStrategy::He(FanMode::Average, InitDistribution::Normal),
707 seed: 42,
708 gain: 1.0,
709 });
710 let shape = shape2d(200, 400);
711 let w = init.initialize(&shape);
712 let stats = WeightInitializer::compute_stats(&w);
713 let expected_var = 2.0 / 300.0;
715 let rel_err = (stats.variance - expected_var).abs() / expected_var;
716 assert!(
717 rel_err < 0.15,
718 "He average normal var {:.6} vs expected {:.6}",
719 stats.variance,
720 expected_var
721 );
722 }
723
724 #[test]
727 fn test_lecun_normal_variance() {
728 let mut init = WeightInitializer::new(WeightInitConfig {
729 strategy: InitStrategy::LeCun(InitDistribution::Normal),
730 seed: 42,
731 gain: 1.0,
732 });
733 let shape = shape2d(256, 512);
734 let w = init.initialize(&shape);
735 let stats = WeightInitializer::compute_stats(&w);
736 let expected_var = 1.0 / 512.0;
738 let rel_err = (stats.variance - expected_var).abs() / expected_var;
739 assert!(
740 rel_err < 0.15,
741 "LeCun normal var {:.6} vs expected {:.6}",
742 stats.variance,
743 expected_var
744 );
745 }
746
747 #[test]
748 fn test_lecun_uniform_variance() {
749 let mut init = WeightInitializer::new(WeightInitConfig {
750 strategy: InitStrategy::LeCun(InitDistribution::Uniform),
751 seed: 42,
752 gain: 1.0,
753 });
754 let shape = shape2d(256, 512);
755 let w = init.initialize(&shape);
756 let stats = WeightInitializer::compute_stats(&w);
757 let expected_var = 1.0 / 512.0;
759 let rel_err = (stats.variance - expected_var).abs() / expected_var;
760 assert!(
761 rel_err < 0.15,
762 "LeCun uniform var {:.6} vs expected {:.6}",
763 stats.variance,
764 expected_var
765 );
766 }
767
768 #[test]
771 fn test_truncated_normal_bounds() {
772 let mut init = make_init(InitStrategy::TruncatedNormal {
773 mean: 0.0,
774 std: 1.0,
775 a: -1.5,
776 b: 1.5,
777 });
778 let w = init.initialize(&TensorShape::new(vec![10000]));
779 assert!(w.iter().all(|&v| (-1.5..=1.5).contains(&v)));
780 }
781
782 #[test]
783 fn test_truncated_normal_mean_near_zero() {
784 let mut init = make_init(InitStrategy::TruncatedNormal {
785 mean: 0.0,
786 std: 1.0,
787 a: -2.0,
788 b: 2.0,
789 });
790 let w = init.initialize(&TensorShape::new(vec![50000]));
791 let stats = WeightInitializer::compute_stats(&w);
792 assert!(
793 stats.mean.abs() < 0.05,
794 "truncated normal mean should be near 0: {}",
795 stats.mean
796 );
797 }
798
799 #[test]
802 fn test_orthogonal_semi_orthogonal() {
803 let mut init = make_init(InitStrategy::Orthogonal(1.0));
804 let shape = shape2d(4, 4);
805 let w = init.initialize(&shape);
806 assert_eq!(w.len(), 16);
807
808 let rows = 4;
810 let cols = 4;
811 for i in 0..cols {
812 for j in 0..cols {
813 let dot: f64 = (0..rows).map(|r| w[r * cols + i] * w[r * cols + j]).sum();
814 if i == j {
815 assert!(
816 (dot - 1.0).abs() < 0.1,
817 "diagonal ({},{}) should be ~1.0, got {}",
818 i,
819 j,
820 dot,
821 );
822 } else {
823 assert!(
824 dot.abs() < 0.1,
825 "off-diagonal ({},{}) should be ~0.0, got {}",
826 i,
827 j,
828 dot,
829 );
830 }
831 }
832 }
833 }
834
835 #[test]
836 fn test_orthogonal_rectangular() {
837 let mut init = make_init(InitStrategy::Orthogonal(1.0));
838 let shape = shape2d(8, 4);
839 let w = init.initialize(&shape);
840 assert_eq!(w.len(), 32);
841
842 let cols = 4;
844 let rows = 8;
845 for i in 0..cols {
846 for j in 0..cols {
847 let dot: f64 = (0..rows).map(|r| w[r * cols + i] * w[r * cols + j]).sum();
848 if i == j {
849 assert!(
850 (dot - 1.0).abs() < 0.15,
851 "orthogonal rect diag ({},{}) = {}",
852 i,
853 j,
854 dot,
855 );
856 } else {
857 assert!(
858 dot.abs() < 0.15,
859 "orthogonal rect off-diag ({},{}) = {}",
860 i,
861 j,
862 dot,
863 );
864 }
865 }
866 }
867 }
868
869 #[test]
870 fn test_orthogonal_with_gain() {
871 let mut init = make_init(InitStrategy::Orthogonal(2.0));
872 let shape = shape2d(4, 4);
873 let w = init.initialize(&shape);
874
875 for j in 0..4 {
877 let norm: f64 = (0..4).map(|i| w[i * 4 + j].powi(2)).sum::<f64>().sqrt();
878 assert!(
879 (norm - 2.0).abs() < 0.3,
880 "column {} norm should be ~2.0, got {}",
881 j,
882 norm,
883 );
884 }
885 }
886
887 #[test]
890 fn test_sparse_ratio() {
891 let mut init = make_init(InitStrategy::Sparse(0.8));
892 let w = init.initialize(&TensorShape::new(vec![10000]));
893 let zeros = w.iter().filter(|&&v| v == 0.0).count();
894 let ratio = zeros as f64 / w.len() as f64;
895 assert!(
896 (ratio - 0.8).abs() < 0.05,
897 "sparse ratio {:.3} vs expected 0.8",
898 ratio,
899 );
900 }
901
902 #[test]
903 fn test_sparse_zero_sparsity() {
904 let mut init = make_init(InitStrategy::Sparse(0.0));
905 let w = init.initialize(&TensorShape::new(vec![1000]));
906 let zeros = w.iter().filter(|&&v| v == 0.0).count();
907 assert_eq!(zeros, 0, "sparsity=0 should produce no zeros");
908 }
909
910 #[test]
911 fn test_sparse_full_sparsity() {
912 let mut init = make_init(InitStrategy::Sparse(1.0));
913 let w = init.initialize(&TensorShape::new(vec![1000]));
914 assert!(
915 w.iter().all(|&v| v == 0.0),
916 "sparsity=1 should be all zeros"
917 );
918 }
919
920 #[test]
923 fn test_stats_basic() {
924 let data = vec![1.0, 2.0, 3.0, 4.0, 5.0];
925 let stats = WeightInitializer::compute_stats(&data);
926 assert_eq!(stats.total_params, 5);
927 assert!((stats.mean - 3.0).abs() < 1e-12);
928 assert!((stats.variance - 2.0).abs() < 1e-12);
929 assert!((stats.min - 1.0).abs() < 1e-12);
930 assert!((stats.max - 5.0).abs() < 1e-12);
931 }
932
933 #[test]
934 fn test_stats_empty() {
935 let stats = WeightInitializer::compute_stats(&[]);
936 assert_eq!(stats.total_params, 0);
937 assert_eq!(stats.mean, 0.0);
938 assert_eq!(stats.variance, 0.0);
939 }
940
941 #[test]
942 fn test_stats_single() {
943 let stats = WeightInitializer::compute_stats(&[7.0]);
944 assert_eq!(stats.total_params, 1);
945 assert!((stats.mean - 7.0).abs() < 1e-12);
946 assert!((stats.variance).abs() < 1e-12);
947 assert!((stats.min - 7.0).abs() < 1e-12);
948 assert!((stats.max - 7.0).abs() < 1e-12);
949 }
950
951 #[test]
954 fn test_same_seed_same_weights() {
955 let shape = shape2d(32, 64);
956 let mut a = WeightInitializer::new(WeightInitConfig {
957 strategy: InitStrategy::Xavier(InitDistribution::Normal),
958 seed: 999,
959 gain: 1.0,
960 });
961 let mut b = WeightInitializer::new(WeightInitConfig {
962 strategy: InitStrategy::Xavier(InitDistribution::Normal),
963 seed: 999,
964 gain: 1.0,
965 });
966 assert_eq!(a.initialize(&shape), b.initialize(&shape));
967 }
968
969 #[test]
970 fn test_different_seed_different_weights() {
971 let shape = shape2d(32, 64);
972 let mut a = WeightInitializer::new(WeightInitConfig {
973 strategy: InitStrategy::Xavier(InitDistribution::Normal),
974 seed: 111,
975 gain: 1.0,
976 });
977 let mut b = WeightInitializer::new(WeightInitConfig {
978 strategy: InitStrategy::Xavier(InitDistribution::Normal),
979 seed: 222,
980 gain: 1.0,
981 });
982 assert_ne!(a.initialize(&shape), b.initialize(&shape));
983 }
984
985 #[test]
986 fn test_reset_seed() {
987 let shape = shape2d(16, 16);
988 let mut init = WeightInitializer::new(WeightInitConfig {
989 strategy: InitStrategy::He(FanMode::FanIn, InitDistribution::Uniform),
990 seed: 42,
991 gain: 1.0,
992 });
993 let w1 = init.initialize(&shape);
994 init.reset_seed(42);
995 let w2 = init.initialize(&shape);
996 assert_eq!(w1, w2, "reset_seed should reproduce identical weights");
997 }
998
999 #[test]
1002 fn test_large_tensor() {
1003 let mut init = make_init(InitStrategy::Xavier(InitDistribution::Uniform));
1004 let shape = TensorShape::new(vec![100, 100]);
1005 let w = init.initialize(&shape);
1006 assert_eq!(w.len(), 10_000);
1007 let stats = WeightInitializer::compute_stats(&w);
1008 assert!(
1009 stats.mean.abs() < 0.05,
1010 "large Xavier mean should be near zero"
1011 );
1012 }
1013
1014 #[test]
1015 fn test_very_large_tensor() {
1016 let mut init = make_init(InitStrategy::He(FanMode::FanIn, InitDistribution::Normal));
1017 let shape = TensorShape::new(vec![50, 50]);
1018 let w = init.initialize(&shape);
1019 assert_eq!(w.len(), 2500);
1020 assert!(w.iter().all(|v| v.is_finite()));
1021 }
1022
1023 #[test]
1026 fn test_initialized_count() {
1027 let mut init = make_init(InitStrategy::Zeros);
1028 assert_eq!(init.initialized_count(), 0);
1029 init.initialize(&shape2d(3, 4));
1030 assert_eq!(init.initialized_count(), 12);
1031 init.initialize(&TensorShape::new(vec![10]));
1032 assert_eq!(init.initialized_count(), 22);
1033 }
1034
1035 #[test]
1038 fn test_xavier_bound_value() {
1039 let b = WeightInitializer::xavier_bound(256, 512, 1.0);
1040 let expected = (6.0 / 768.0_f64).sqrt();
1041 assert!((b - expected).abs() < 1e-12);
1042 }
1043
1044 #[test]
1045 fn test_he_bound_value() {
1046 let b = WeightInitializer::he_bound(512, 1.0);
1047 let expected = (3.0 / 512.0_f64).sqrt();
1048 assert!((b - expected).abs() < 1e-12);
1049 }
1050
1051 #[test]
1054 fn test_xavier_with_gain() {
1055 let mut init = WeightInitializer::new(WeightInitConfig {
1056 strategy: InitStrategy::Xavier(InitDistribution::Normal),
1057 seed: 42,
1058 gain: 2.0,
1059 });
1060 let shape = shape2d(256, 256);
1061 let w = init.initialize(&shape);
1062 let stats = WeightInitializer::compute_stats(&w);
1063 let expected_var = 4.0 * 2.0 / 512.0;
1065 let rel_err = (stats.variance - expected_var).abs() / expected_var;
1066 assert!(
1067 rel_err < 0.15,
1068 "Xavier gain=2 var {:.6} vs {:.6}",
1069 stats.variance,
1070 expected_var
1071 );
1072 }
1073
1074 #[test]
1077 fn test_numel() {
1078 assert_eq!(TensorShape::new(vec![3, 4, 5]).numel(), 60);
1079 assert_eq!(TensorShape::new(vec![]).numel(), 1); assert_eq!(TensorShape::new(vec![7]).numel(), 7);
1081 }
1082
1083 #[test]
1086 fn test_default_config() {
1087 let cfg = WeightInitConfig::default();
1088 assert_eq!(cfg.seed, 42);
1089 assert!((cfg.gain - 1.0).abs() < 1e-12);
1090 assert_eq!(
1091 cfg.strategy,
1092 InitStrategy::Xavier(InitDistribution::Uniform)
1093 );
1094 }
1095}