1use scirs2_core::ndarray::{Array1, Array2};
7use scirs2_core::numeric::NumCast;
8use scirs2_core::random::essentials::{Normal, Uniform};
9use scirs2_core::random::{thread_rng, Distribution};
10use sklears_core::types::FloatBounds;
11
12#[derive(Debug, Clone, PartialEq)]
14pub enum RegularizationType {
15 L1,
17 L2,
19 ElasticNet,
21 None,
23}
24
25#[derive(Debug, Clone)]
27pub struct RegularizationConfig<T: FloatBounds> {
28 pub regularization_type: RegularizationType,
30 pub l1_lambda: T,
32 pub l2_lambda: T,
34 pub regularize_bias: bool,
36}
37
38impl<T: FloatBounds> Default for RegularizationConfig<T> {
39 fn default() -> Self {
40 Self {
41 regularization_type: RegularizationType::None,
42 l1_lambda: T::zero(),
43 l2_lambda: T::zero(),
44 regularize_bias: false,
45 }
46 }
47}
48
49impl<T: FloatBounds> RegularizationConfig<T> {
50 pub fn l1(lambda: T) -> Self {
52 Self {
53 regularization_type: RegularizationType::L1,
54 l1_lambda: lambda,
55 l2_lambda: T::zero(),
56 regularize_bias: false,
57 }
58 }
59
60 pub fn l2(lambda: T) -> Self {
62 Self {
63 regularization_type: RegularizationType::L2,
64 l1_lambda: T::zero(),
65 l2_lambda: lambda,
66 regularize_bias: false,
67 }
68 }
69
70 pub fn elastic_net(l1_lambda: T, l2_lambda: T) -> Self {
72 Self {
73 regularization_type: RegularizationType::ElasticNet,
74 l1_lambda,
75 l2_lambda,
76 regularize_bias: false,
77 }
78 }
79
80 pub fn regularize_bias(mut self, regularize: bool) -> Self {
82 self.regularize_bias = regularize;
83 self
84 }
85}
86
87pub struct Regularizer<T: FloatBounds> {
89 config: RegularizationConfig<T>,
90}
91
92impl<T: FloatBounds + scirs2_core::ndarray::ScalarOperand> Regularizer<T> {
93 pub fn new(config: RegularizationConfig<T>) -> Self {
95 Self { config }
96 }
97
98 pub fn compute_loss(&self, weights: &Array2<T>, bias: Option<&Array1<T>>) -> T {
100 let mut loss = T::zero();
101
102 match self.config.regularization_type {
104 RegularizationType::L1 => {
105 loss += self.config.l1_lambda * self.l1_norm_2d(weights);
106 }
107 RegularizationType::L2 => {
108 loss += self.config.l2_lambda * self.l2_norm_squared_2d(weights);
109 }
110 RegularizationType::ElasticNet => {
111 loss += self.config.l1_lambda * self.l1_norm_2d(weights);
112 loss += self.config.l2_lambda * self.l2_norm_squared_2d(weights);
113 }
114 RegularizationType::None => {}
115 }
116
117 if self.config.regularize_bias {
119 if let Some(bias_vec) = bias {
120 match self.config.regularization_type {
121 RegularizationType::L1 => {
122 loss += self.config.l1_lambda * self.l1_norm_1d(bias_vec);
123 }
124 RegularizationType::L2 => {
125 loss += self.config.l2_lambda * self.l2_norm_squared_1d(bias_vec);
126 }
127 RegularizationType::ElasticNet => {
128 loss += self.config.l1_lambda * self.l1_norm_1d(bias_vec);
129 loss += self.config.l2_lambda * self.l2_norm_squared_1d(bias_vec);
130 }
131 RegularizationType::None => {}
132 }
133 }
134 }
135
136 loss
137 }
138
139 pub fn compute_weight_gradients(&self, weights: &Array2<T>) -> Array2<T> {
141 let mut gradients = Array2::zeros(weights.dim());
142
143 match self.config.regularization_type {
144 RegularizationType::L1 => {
145 gradients = gradients + &self.l1_gradient_2d(weights) * self.config.l1_lambda;
146 }
147 RegularizationType::L2 => {
148 gradients = gradients + &self.l2_gradient_2d(weights) * self.config.l2_lambda;
149 }
150 RegularizationType::ElasticNet => {
151 gradients = gradients + &self.l1_gradient_2d(weights) * self.config.l1_lambda;
152 gradients = gradients + &self.l2_gradient_2d(weights) * self.config.l2_lambda;
153 }
154 RegularizationType::None => {}
155 }
156
157 gradients
158 }
159
160 pub fn compute_bias_gradients(&self, bias: &Array1<T>) -> Array1<T> {
162 if !self.config.regularize_bias {
163 return Array1::zeros(bias.len());
164 }
165
166 let mut gradients = Array1::zeros(bias.len());
167
168 match self.config.regularization_type {
169 RegularizationType::L1 => {
170 gradients = gradients + self.l1_gradient_1d(bias) * self.config.l1_lambda;
171 }
172 RegularizationType::L2 => {
173 gradients = gradients + self.l2_gradient_1d(bias) * self.config.l2_lambda;
174 }
175 RegularizationType::ElasticNet => {
176 gradients = gradients + self.l1_gradient_1d(bias) * self.config.l1_lambda;
177 gradients = gradients + self.l2_gradient_1d(bias) * self.config.l2_lambda;
178 }
179 RegularizationType::None => {}
180 }
181
182 gradients
183 }
184
185 fn l1_norm_2d(&self, array: &Array2<T>) -> T {
187 array.iter().fold(T::zero(), |acc, &x| acc + x.abs())
188 }
189
190 fn l1_norm_1d(&self, array: &Array1<T>) -> T {
192 array.iter().fold(T::zero(), |acc, &x| acc + x.abs())
193 }
194
195 fn l2_norm_squared_2d(&self, array: &Array2<T>) -> T {
197 let half = T::from(0.5).unwrap_or_else(|| T::one() / (T::one() + T::one()));
198 half * array.iter().fold(T::zero(), |acc, &x| acc + x * x)
199 }
200
201 fn l2_norm_squared_1d(&self, array: &Array1<T>) -> T {
203 let half = T::from(0.5).unwrap_or_else(|| T::one() / (T::one() + T::one()));
204 half * array.iter().fold(T::zero(), |acc, &x| acc + x * x)
205 }
206
207 fn l1_gradient_2d(&self, array: &Array2<T>) -> Array2<T> {
209 array.mapv(|x| {
210 if x > T::zero() {
211 T::one()
212 } else if x < T::zero() {
213 -T::one()
214 } else {
215 T::zero() }
217 })
218 }
219
220 fn l1_gradient_1d(&self, array: &Array1<T>) -> Array1<T> {
222 array.mapv(|x| {
223 if x > T::zero() {
224 T::one()
225 } else if x < T::zero() {
226 -T::one()
227 } else {
228 T::zero()
229 }
230 })
231 }
232
233 fn l2_gradient_2d(&self, array: &Array2<T>) -> Array2<T> {
235 array.clone()
236 }
237
238 fn l2_gradient_1d(&self, array: &Array1<T>) -> Array1<T> {
240 array.clone()
241 }
242}
243
244pub fn soft_threshold<T: FloatBounds>(x: T, lambda: T) -> T {
246 if x > lambda {
247 x - lambda
248 } else if x < -lambda {
249 x + lambda
250 } else {
251 T::zero()
252 }
253}
254
255pub fn apply_soft_threshold_2d<T: FloatBounds>(array: &Array2<T>, lambda: T) -> Array2<T> {
257 array.mapv(|x| soft_threshold(x, lambda))
258}
259
260pub fn apply_soft_threshold_1d<T: FloatBounds>(array: &Array1<T>, lambda: T) -> Array1<T> {
262 array.mapv(|x| soft_threshold(x, lambda))
263}
264
265#[derive(Debug, Clone)]
267pub struct EarlyStopping<T: FloatBounds> {
268 patience: usize,
270 min_delta: T,
272 wait: usize,
274 best_value: Option<T>,
276 minimize: bool,
278 stopped: bool,
280}
281
282impl<T: FloatBounds> EarlyStopping<T> {
283 pub fn new(patience: usize, min_delta: T, minimize: bool) -> Self {
290 Self {
291 patience,
292 min_delta,
293 wait: 0,
294 best_value: None,
295 minimize,
296 stopped: false,
297 }
298 }
299
300 pub fn update(&mut self, value: T) -> bool {
304 if self.stopped {
305 return true;
306 }
307
308 let is_improvement = match self.best_value {
309 None => true,
310 Some(best) => {
311 if self.minimize {
312 value < best - self.min_delta
313 } else {
314 value > best + self.min_delta
315 }
316 }
317 };
318
319 if is_improvement {
320 self.best_value = Some(value);
321 self.wait = 0;
322 } else {
323 self.wait += 1;
324 if self.wait >= self.patience {
325 self.stopped = true;
326 return true;
327 }
328 }
329
330 false
331 }
332
333 pub fn best_value(&self) -> Option<T> {
335 self.best_value
336 }
337
338 pub fn reset(&mut self) {
340 self.wait = 0;
341 self.best_value = None;
342 self.stopped = false;
343 }
344
345 pub fn is_stopped(&self) -> bool {
347 self.stopped
348 }
349}
350
351#[derive(Debug, Clone, PartialEq)]
353pub enum NoiseType {
354 Gaussian {
356 std_dev: f64,
358 },
359 Uniform {
361 magnitude: f64,
363 },
364 SaltPepper {
366 probability: f64,
368 min_value: f64,
370 max_value: f64,
372 },
373 Dropout {
375 probability: f64,
377 },
378}
379
380#[derive(Debug, Clone)]
382pub struct NoiseConfig {
383 pub noise_type: NoiseType,
385 pub training_only: bool,
387 pub seed: Option<u64>,
389}
390
391impl Default for NoiseConfig {
392 fn default() -> Self {
393 Self {
394 noise_type: NoiseType::Gaussian { std_dev: 0.01 },
395 training_only: true,
396 seed: None,
397 }
398 }
399}
400
401impl NoiseConfig {
402 pub fn gaussian(std_dev: f64) -> Self {
404 Self {
405 noise_type: NoiseType::Gaussian { std_dev },
406 training_only: true,
407 seed: None,
408 }
409 }
410
411 pub fn uniform(magnitude: f64) -> Self {
413 Self {
414 noise_type: NoiseType::Uniform { magnitude },
415 training_only: true,
416 seed: None,
417 }
418 }
419
420 pub fn salt_pepper(probability: f64, min_value: f64, max_value: f64) -> Self {
422 Self {
423 noise_type: NoiseType::SaltPepper {
424 probability,
425 min_value,
426 max_value,
427 },
428 training_only: true,
429 seed: None,
430 }
431 }
432
433 pub fn dropout(probability: f64) -> Self {
435 Self {
436 noise_type: NoiseType::Dropout { probability },
437 training_only: true,
438 seed: None,
439 }
440 }
441
442 pub fn training_only(mut self, training_only: bool) -> Self {
444 self.training_only = training_only;
445 self
446 }
447
448 pub fn with_seed(mut self, seed: u64) -> Self {
450 self.seed = Some(seed);
451 self
452 }
453}
454
455pub struct NoiseInjector {
457 config: NoiseConfig,
458}
459
460impl NoiseInjector {
461 pub fn new(config: NoiseConfig) -> Self {
463 Self { config }
464 }
465
466 pub fn apply_noise<T>(&self, input: &Array2<T>, is_training: bool) -> Array2<T>
468 where
469 T: FloatBounds + From<f64>,
470 {
471 if self.config.training_only && !is_training {
472 return input.clone();
473 }
474
475 match &self.config.noise_type {
476 NoiseType::Gaussian { std_dev } => self.apply_gaussian_noise(input, *std_dev),
477 NoiseType::Uniform { magnitude } => self.apply_uniform_noise(input, *magnitude),
478 NoiseType::SaltPepper {
479 probability,
480 min_value,
481 max_value,
482 } => self.apply_salt_pepper_noise(input, *probability, *min_value, *max_value),
483 NoiseType::Dropout { probability } => self.apply_dropout_noise(input, *probability),
484 }
485 }
486
487 pub fn apply_noise_1d<T>(&self, input: &Array1<T>, is_training: bool) -> Array1<T>
489 where
490 T: FloatBounds + From<f64>,
491 {
492 if self.config.training_only && !is_training {
493 return input.clone();
494 }
495
496 match &self.config.noise_type {
497 NoiseType::Gaussian { std_dev } => self.apply_gaussian_noise_1d(input, *std_dev),
498 NoiseType::Uniform { magnitude } => self.apply_uniform_noise_1d(input, *magnitude),
499 NoiseType::SaltPepper {
500 probability,
501 min_value,
502 max_value,
503 } => self.apply_salt_pepper_noise_1d(input, *probability, *min_value, *max_value),
504 NoiseType::Dropout { probability } => self.apply_dropout_noise_1d(input, *probability),
505 }
506 }
507
508 fn apply_gaussian_noise<T>(&self, input: &Array2<T>, std_dev: f64) -> Array2<T>
510 where
511 T: FloatBounds + From<f64>,
512 {
513 let mut rng = thread_rng();
514 let normal = Normal::new(0.0, std_dev).expect("valid distribution params");
515
516 input.mapv(|x| {
517 let noise = NumCast::from(normal.sample(&mut rng)).unwrap_or(T::zero());
518 x + noise
519 })
520 }
521
522 fn apply_gaussian_noise_1d<T>(&self, input: &Array1<T>, std_dev: f64) -> Array1<T>
524 where
525 T: FloatBounds + From<f64>,
526 {
527 let mut rng = thread_rng();
528 let normal = Normal::new(0.0, std_dev).expect("valid distribution params");
529
530 input.mapv(|x| {
531 let noise = NumCast::from(normal.sample(&mut rng)).unwrap_or(T::zero());
532 x + noise
533 })
534 }
535
536 fn apply_uniform_noise<T>(&self, input: &Array2<T>, magnitude: f64) -> Array2<T>
538 where
539 T: FloatBounds + From<f64>,
540 {
541 let mut rng = thread_rng();
542 let uniform = Uniform::new(-magnitude, magnitude).expect("valid distribution params");
543
544 input.mapv(|x| {
545 let noise = NumCast::from(uniform.sample(&mut rng)).unwrap_or(T::zero());
546 x + noise
547 })
548 }
549
550 fn apply_uniform_noise_1d<T>(&self, input: &Array1<T>, magnitude: f64) -> Array1<T>
552 where
553 T: FloatBounds + From<f64>,
554 {
555 let mut rng = thread_rng();
556 let uniform = Uniform::new(-magnitude, magnitude).expect("valid distribution params");
557
558 input.mapv(|x| {
559 let noise = NumCast::from(uniform.sample(&mut rng)).unwrap_or(T::zero());
560 x + noise
561 })
562 }
563
564 fn apply_salt_pepper_noise<T>(
566 &self,
567 input: &Array2<T>,
568 probability: f64,
569 min_value: f64,
570 max_value: f64,
571 ) -> Array2<T>
572 where
573 T: FloatBounds + From<f64>,
574 {
575 let mut rng = thread_rng();
576
577 input.mapv(|x| {
578 if rng.random::<f64>() < probability {
579 if rng.random::<bool>() {
580 NumCast::from(min_value).unwrap_or(T::zero())
581 } else {
582 NumCast::from(max_value).unwrap_or(T::zero())
583 }
584 } else {
585 x
586 }
587 })
588 }
589
590 fn apply_salt_pepper_noise_1d<T>(
592 &self,
593 input: &Array1<T>,
594 probability: f64,
595 min_value: f64,
596 max_value: f64,
597 ) -> Array1<T>
598 where
599 T: FloatBounds + From<f64>,
600 {
601 let mut rng = thread_rng();
602
603 input.mapv(|x| {
604 if rng.random::<f64>() < probability {
605 if rng.random::<bool>() {
606 NumCast::from(min_value).unwrap_or(T::zero())
607 } else {
608 NumCast::from(max_value).unwrap_or(T::zero())
609 }
610 } else {
611 x
612 }
613 })
614 }
615
616 fn apply_dropout_noise<T>(&self, input: &Array2<T>, probability: f64) -> Array2<T>
618 where
619 T: FloatBounds + From<f64>,
620 {
621 let mut rng = thread_rng();
622
623 input.mapv(|x| {
624 if rng.random::<f64>() < probability {
625 T::zero()
626 } else {
627 x / NumCast::from(1.0 - probability).unwrap_or_else(T::one)
629 }
630 })
631 }
632
633 fn apply_dropout_noise_1d<T>(&self, input: &Array1<T>, probability: f64) -> Array1<T>
635 where
636 T: FloatBounds + From<f64>,
637 {
638 let mut rng = thread_rng();
639
640 input.mapv(|x| {
641 if rng.random::<f64>() < probability {
642 T::zero()
643 } else {
644 x / NumCast::from(1.0 - probability).unwrap_or_else(T::one)
646 }
647 })
648 }
649}
650
651#[derive(Debug, Clone)]
653pub struct SpectralNormalization<T: FloatBounds> {
654 power_iterations: usize,
656 eps: T,
658 u: Option<Array1<T>>,
660 v: Option<Array1<T>>,
662 initialized: bool,
664}
665
666impl<T: FloatBounds + scirs2_core::ndarray::ScalarOperand> Default for SpectralNormalization<T> {
667 fn default() -> Self {
668 Self::new(1, T::from(1e-12).unwrap_or_else(|| T::epsilon()))
669 }
670}
671
672impl<T: FloatBounds + scirs2_core::ndarray::ScalarOperand> SpectralNormalization<T> {
673 pub fn new(power_iterations: usize, eps: T) -> Self {
675 Self {
676 power_iterations,
677 eps,
678 u: None,
679 v: None,
680 initialized: false,
681 }
682 }
683
684 pub fn normalize_weights(&mut self, weights: &Array2<T>) -> Array2<T>
686 where
687 T: scirs2_core::ndarray::ScalarOperand + Clone + std::fmt::Debug,
688 {
689 let (m, n) = weights.dim();
690
691 if !self.initialized || self.u.is_none() || self.v.is_none() {
693 self.initialize_vectors(m, n);
694 }
695
696 if let (Some(ref u), Some(ref v)) = (&self.u, &self.v) {
698 if u.len() != m || v.len() != n {
699 self.initialize_vectors(m, n);
700 }
701 }
702
703 let sigma = self.power_iteration(weights);
705
706 if sigma > self.eps {
708 weights / sigma
709 } else {
710 weights.clone()
711 }
712 }
713
714 fn initialize_vectors(&mut self, m: usize, n: usize) {
716 let mut rng = thread_rng();
717
718 let u_data: Vec<T> = (0..m)
720 .map(|_| T::from(rng.random::<f64>() * 2.0 - 1.0).unwrap_or(T::zero()))
721 .collect();
722 let mut u = Array1::from_vec(u_data);
723 self.normalize_vector(&mut u);
724 self.u = Some(u);
725
726 let v_data: Vec<T> = (0..n)
728 .map(|_| T::from(rng.random::<f64>() * 2.0 - 1.0).unwrap_or(T::zero()))
729 .collect();
730 let mut v = Array1::from_vec(v_data);
731 self.normalize_vector(&mut v);
732 self.v = Some(v);
733
734 self.initialized = true;
735 }
736
737 fn power_iteration(&mut self, weights: &Array2<T>) -> T
739 where
740 T: scirs2_core::ndarray::ScalarOperand + Clone,
741 {
742 for _ in 0..self.power_iterations {
743 let wt_u = {
745 let u = self.u.as_ref().expect("u not available - model not fitted");
746 weights.t().dot(u)
747 };
748 *self.v.as_mut().expect("v not available") = wt_u;
749 Self::normalize_vector_static(self.v.as_mut().expect("v not available"), self.eps);
750
751 let w_v = {
753 let v = self.v.as_ref().expect("v not available - model not fitted");
754 weights.dot(v)
755 };
756 *self.u.as_mut().expect("u not available") = w_v;
757 Self::normalize_vector_static(self.u.as_mut().expect("u not available"), self.eps);
758 }
759
760 let u = self.u.as_ref().expect("u not available - model not fitted");
762 let v = self.v.as_ref().expect("v not available - model not fitted");
763 let w_v = weights.dot(v);
764 u.dot(&w_v)
765 }
766
767 fn normalize_vector(&self, vector: &mut Array1<T>)
769 where
770 T: scirs2_core::ndarray::ScalarOperand + Clone,
771 {
772 Self::normalize_vector_static(vector, self.eps);
773 }
774
775 fn normalize_vector_static(vector: &mut Array1<T>, eps: T)
777 where
778 T: scirs2_core::ndarray::ScalarOperand + Clone,
779 {
780 let norm_squared = vector.iter().fold(T::zero(), |acc, &x| acc + x * x);
781 let norm = norm_squared.sqrt();
782
783 if norm > eps {
784 vector.mapv_inplace(|x| x / norm);
785 }
786 }
787
788 pub fn get_spectral_norm(&mut self, weights: &Array2<T>) -> T
790 where
791 T: scirs2_core::ndarray::ScalarOperand + Clone,
792 {
793 if !self.initialized {
794 let (m, n) = weights.dim();
795 self.initialize_vectors(m, n);
796 }
797
798 self.power_iteration(weights)
799 }
800
801 pub fn reset(&mut self) {
803 self.u = None;
804 self.v = None;
805 self.initialized = false;
806 }
807}
808
809#[derive(Debug, Clone)]
811pub struct SpectralNormLayer<T: FloatBounds> {
812 spectral_norm: SpectralNormalization<T>,
813 enabled: bool,
815}
816
817impl<T: FloatBounds + scirs2_core::ndarray::ScalarOperand> Default for SpectralNormLayer<T> {
818 fn default() -> Self {
819 Self {
820 spectral_norm: SpectralNormalization::default(),
821 enabled: true,
822 }
823 }
824}
825
826impl<T: FloatBounds + scirs2_core::ndarray::ScalarOperand> SpectralNormLayer<T> {
827 pub fn new(power_iterations: usize, eps: T) -> Self {
829 Self {
830 spectral_norm: SpectralNormalization::new(power_iterations, eps),
831 enabled: true,
832 }
833 }
834
835 pub fn set_enabled(&mut self, enabled: bool) {
837 self.enabled = enabled;
838 }
839
840 pub fn normalize(&mut self, weights: &Array2<T>) -> Array2<T>
842 where
843 T: scirs2_core::ndarray::ScalarOperand + Clone + std::fmt::Debug,
844 {
845 if self.enabled {
846 self.spectral_norm.normalize_weights(weights)
847 } else {
848 weights.clone()
849 }
850 }
851
852 pub fn spectral_norm(&mut self, weights: &Array2<T>) -> T
854 where
855 T: scirs2_core::ndarray::ScalarOperand + Clone,
856 {
857 self.spectral_norm.get_spectral_norm(weights)
858 }
859
860 pub fn reset(&mut self) {
862 self.spectral_norm.reset();
863 }
864}
865
866#[allow(non_snake_case)]
867#[cfg(test)]
868mod tests {
869 use super::*;
870 use approx::assert_abs_diff_eq;
871 use scirs2_core::ndarray::{array, Array2};
872
873 #[test]
874 fn test_l1_regularization() {
875 let config = RegularizationConfig::l1(0.1);
876 let regularizer = Regularizer::new(config);
877
878 let weights = array![[1.0, -2.0], [3.0, -4.0]];
879 let loss = regularizer.compute_loss(&weights, None);
880
881 assert_abs_diff_eq!(loss, 1.0, epsilon = 1e-10);
884 }
885
886 #[test]
887 fn test_l2_regularization() {
888 let config = RegularizationConfig::l2(0.1);
889 let regularizer = Regularizer::new(config);
890
891 let weights = array![[1.0, 2.0], [3.0, 4.0]];
892 let loss = regularizer.compute_loss(&weights, None);
893
894 assert_abs_diff_eq!(loss, 1.5, epsilon = 1e-10);
897 }
898
899 #[test]
900 fn test_elastic_net_regularization() {
901 let config = RegularizationConfig::elastic_net(0.1, 0.05);
902 let regularizer = Regularizer::new(config);
903
904 let weights = array![[1.0, -2.0], [3.0, -4.0]];
905 let loss = regularizer.compute_loss(&weights, None);
906
907 assert_abs_diff_eq!(loss, 1.75, epsilon = 1e-10);
910 }
911
912 #[test]
913 fn test_l1_gradients() {
914 let config = RegularizationConfig::l1(0.1);
915 let regularizer = Regularizer::new(config);
916
917 let weights = array![[1.0, -2.0, 0.0], [3.0, -4.0, 0.0]];
918 let gradients = regularizer.compute_weight_gradients(&weights);
919
920 let expected = array![[0.1, -0.1, 0.0], [0.1, -0.1, 0.0]];
921 for (g, e) in gradients.iter().zip(expected.iter()) {
923 assert_abs_diff_eq!(*g, *e, epsilon = 1e-10);
924 }
925 }
926
927 #[test]
928 fn test_l2_gradients() {
929 let config = RegularizationConfig::l2(0.1);
930 let regularizer = Regularizer::new(config);
931
932 let weights = array![[1.0, 2.0], [3.0, 4.0]];
933 let gradients = regularizer.compute_weight_gradients(&weights);
934
935 let expected = &weights * 0.1;
936 for (g, e) in gradients.iter().zip(expected.iter()) {
938 assert_abs_diff_eq!(*g, *e, epsilon = 1e-10);
939 }
940 }
941
942 #[test]
943 fn test_bias_regularization() {
944 let config = RegularizationConfig::l2(0.1).regularize_bias(true);
945 let regularizer = Regularizer::new(config);
946
947 let weights = array![[1.0, 2.0]];
948 let bias = array![3.0, 4.0];
949 let loss = regularizer.compute_loss(&weights, Some(&bias));
950
951 assert_abs_diff_eq!(loss, 1.5, epsilon = 1e-10);
955 }
956
957 #[test]
958 fn test_soft_threshold() {
959 assert_abs_diff_eq!(soft_threshold(3.0, 1.0), 2.0, epsilon = 1e-10);
960 assert_abs_diff_eq!(soft_threshold(-3.0, 1.0), -2.0, epsilon = 1e-10);
961 assert_abs_diff_eq!(soft_threshold(0.5, 1.0), 0.0, epsilon = 1e-10);
962 assert_abs_diff_eq!(soft_threshold(-0.5, 1.0), 0.0, epsilon = 1e-10);
963 }
964
965 #[test]
966 fn test_soft_threshold_array() {
967 let input = array![[3.0, -2.0, 0.5], [-0.3, 4.0, -1.5]];
968 let result = apply_soft_threshold_2d(&input, 1.0);
969
970 let expected = array![[2.0, -1.0, 0.0], [0.0, 3.0, -0.5]];
971 for (r, e) in result.iter().zip(expected.iter()) {
973 assert_abs_diff_eq!(*r, *e, epsilon = 1e-10);
974 }
975 }
976
977 #[test]
978 fn test_early_stopping_minimize() {
979 let mut early_stopping = EarlyStopping::new(3, 0.01, true);
980
981 assert!(!early_stopping.update(1.0));
983 assert!(!early_stopping.update(0.5));
984 assert!(!early_stopping.update(0.3));
985
986 assert!(!early_stopping.update(0.31)); assert!(!early_stopping.update(0.32)); assert!(early_stopping.update(0.33)); assert_abs_diff_eq!(
992 early_stopping
993 .best_value()
994 .expect("operation should succeed"),
995 0.3,
996 epsilon = 1e-10
997 );
998 }
999
1000 #[test]
1001 fn test_early_stopping_maximize() {
1002 let mut early_stopping = EarlyStopping::new(2, 0.01, false);
1003
1004 assert!(!early_stopping.update(0.7));
1006 assert!(!early_stopping.update(0.8));
1007 assert!(!early_stopping.update(0.9));
1008
1009 assert!(!early_stopping.update(0.89)); assert!(early_stopping.update(0.88)); assert_abs_diff_eq!(
1014 early_stopping
1015 .best_value()
1016 .expect("operation should succeed"),
1017 0.9,
1018 epsilon = 1e-10
1019 );
1020 }
1021
1022 #[test]
1023 fn test_early_stopping_reset() {
1024 let mut early_stopping = EarlyStopping::new(2, 0.01, true);
1025
1026 early_stopping.update(1.0);
1027 early_stopping.update(1.1); early_stopping.update(1.2); assert!(early_stopping.is_stopped());
1031
1032 early_stopping.reset();
1033 assert!(!early_stopping.is_stopped());
1034 assert!(early_stopping.best_value().is_none());
1035 }
1036
1037 #[test]
1038 fn test_gaussian_noise() {
1039 let config = NoiseConfig::gaussian(0.1);
1040 let injector = NoiseInjector::new(config);
1041
1042 let input: scirs2_core::ndarray::Array2<f64> = array![[1.0, 2.0], [3.0, 4.0]];
1043 let noisy_output = injector.apply_noise(&input, true);
1044
1045 assert_eq!(noisy_output.shape(), input.shape());
1047
1048 let mut has_differences = false;
1050 for (original, noisy) in input.iter().zip(noisy_output.iter()) {
1051 if (*original - *noisy).abs() > 1e-6_f64 {
1052 has_differences = true;
1053 break;
1054 }
1055 }
1056 assert!(has_differences);
1057 }
1058
1059 #[test]
1060 fn test_uniform_noise() {
1061 let config = NoiseConfig::uniform(0.5);
1062 let injector = NoiseInjector::new(config);
1063
1064 let input: scirs2_core::ndarray::Array2<f64> = array![[1.0, 2.0], [3.0, 4.0]];
1065 let noisy_output = injector.apply_noise(&input, true);
1066
1067 assert_eq!(noisy_output.shape(), input.shape());
1069
1070 for (original, noisy) in input.iter().zip(noisy_output.iter()) {
1072 let diff = (*original - *noisy).abs();
1073 assert!(diff <= 0.6_f64); }
1075 }
1076
1077 #[test]
1078 fn test_dropout_noise() {
1079 let config = NoiseConfig::dropout(0.5);
1080 let injector = NoiseInjector::new(config);
1081
1082 let input = Array2::from_elem((100, 4), 1.0); let noisy_output = injector.apply_noise(&input, true);
1084
1085 let zero_count = noisy_output.iter().filter(|&&x| x == 0.0).count();
1087 let total_count = noisy_output.len();
1088 let zero_ratio = zero_count as f64 / total_count as f64;
1089
1090 assert!(zero_ratio > 0.3 && zero_ratio < 0.7);
1092 }
1093
1094 #[test]
1095 fn test_salt_pepper_noise() {
1096 let config = NoiseConfig::salt_pepper(0.3, -1.0, 1.0);
1097 let injector = NoiseInjector::new(config);
1098
1099 let input = Array2::from_elem((10, 10), 0.5); let noisy_output = injector.apply_noise(&input, true);
1101
1102 let extreme_count = noisy_output
1104 .iter()
1105 .filter(|&&x| (x - (-1.0_f64)).abs() < 1e-6_f64 || (x - 1.0_f64).abs() < 1e-6_f64)
1106 .count();
1107
1108 assert!(extreme_count > 0);
1109 }
1110
1111 #[test]
1112 fn test_noise_training_only() {
1113 let config = NoiseConfig::gaussian(0.1).training_only(true);
1114 let injector = NoiseInjector::new(config);
1115
1116 let input = array![[1.0, 2.0], [3.0, 4.0]];
1117
1118 let training_output = injector.apply_noise(&input, true);
1120 assert_ne!(training_output, input);
1121
1122 let inference_output = injector.apply_noise(&input, false);
1124 assert_eq!(inference_output, input);
1125 }
1126
1127 #[test]
1128 fn test_noise_1d_arrays() {
1129 let config = NoiseConfig::gaussian(0.1);
1130 let injector = NoiseInjector::new(config);
1131
1132 let input = array![1.0, 2.0, 3.0, 4.0];
1133 let noisy_output = injector.apply_noise_1d(&input, true);
1134
1135 assert_eq!(noisy_output.len(), input.len());
1137
1138 assert_ne!(noisy_output, input);
1140 }
1141
1142 #[test]
1143 fn test_spectral_normalization_basic() {
1144 let mut spec_norm = SpectralNormalization::new(20, 1e-6);
1151
1152 let weights = array![[3.0, 0.0], [0.0, 2.0]]; let normalized = spec_norm.normalize_weights(&weights);
1156
1157 let spectral_norm = spec_norm.get_spectral_norm(&normalized);
1159 assert!((0.95..=1.05).contains(&spectral_norm));
1160 }
1161
1162 #[test]
1163 fn test_spectral_normalization_preserves_shape() {
1164 let mut spec_norm = SpectralNormalization::new(3, 1e-6);
1165
1166 let weights = array![[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]];
1167 let normalized = spec_norm.normalize_weights(&weights);
1168
1169 assert_eq!(normalized.shape(), weights.shape());
1171 }
1172
1173 #[test]
1174 fn test_spectral_normalization_identity() {
1175 let mut spec_norm = SpectralNormalization::new(1, 1e-6);
1176
1177 let weights = array![[1.0, 0.0], [0.0, 1.0]];
1179 let normalized = spec_norm.normalize_weights(&weights);
1180
1181 for (orig, norm) in weights.iter().zip(normalized.iter()) {
1183 assert_abs_diff_eq!(orig, norm, epsilon = 1e-1);
1184 }
1185 }
1186
1187 #[test]
1188 fn test_spectral_normalization_large_values() {
1189 let mut spec_norm = SpectralNormalization::new(10, 1e-8);
1190
1191 let weights = array![[100.0, 50.0], [75.0, 200.0]];
1193 let normalized = spec_norm.normalize_weights(&weights);
1194
1195 let spectral_norm = spec_norm.get_spectral_norm(&normalized);
1197 assert!((0.98..=1.02).contains(&spectral_norm));
1198 }
1199
1200 #[test]
1201 fn test_spectral_norm_layer() {
1202 let mut layer = SpectralNormLayer::default();
1203
1204 let weights = array![[5.0, 0.0], [0.0, 3.0]];
1205
1206 let normalized = layer.normalize(&weights);
1208 let spectral_norm = layer.spectral_norm(&normalized);
1209 assert!(
1210 (0.9..=1.3).contains(&spectral_norm),
1211 "Expected spectral norm between 0.9 and 1.3, got {}",
1212 spectral_norm
1213 );
1214
1215 layer.set_enabled(false);
1217 let not_normalized = layer.normalize(&weights);
1218 assert_eq!(not_normalized, weights);
1219 }
1220
1221 #[test]
1222 fn test_spectral_normalization_reset() {
1223 let mut spec_norm = SpectralNormalization::new(3, 1e-6);
1224
1225 let weights1 = array![[1.0, 2.0], [3.0, 4.0]];
1227 let _ = spec_norm.normalize_weights(&weights1);
1228 assert!(spec_norm.initialized);
1229
1230 spec_norm.reset();
1232 assert!(!spec_norm.initialized);
1233 assert!(spec_norm.u.is_none());
1234 assert!(spec_norm.v.is_none());
1235
1236 let weights2 = array![[1.0, 2.0, 3.0], [4.0, 5.0, 6.0], [7.0, 8.0, 9.0]];
1238 let _ = spec_norm.normalize_weights(&weights2);
1239 assert!(spec_norm.initialized);
1240 }
1241
1242 #[test]
1243 fn test_spectral_normalization_convergence() {
1244 let mut spec_norm = SpectralNormalization::new(1, 1e-6);
1245
1246 let weights = array![[2.0, 1.0], [1.0, 2.0]];
1247
1248 let norm_1_iter = spec_norm.get_spectral_norm(&weights);
1250
1251 spec_norm.power_iterations = 10;
1252 spec_norm.reset();
1253 let norm_10_iter = spec_norm.get_spectral_norm(&weights);
1254
1255 assert!((norm_10_iter - 3.0_f64).abs() <= (norm_1_iter - 3.0_f64).abs());
1258 }
1259}