1use scirs2_core::ndarray::{Array, Dimension, ScalarOperand};
7use scirs2_core::numeric::Float;
8use scirs2_core::random::thread_rng;
9use std::fmt::Debug;
10
11use crate::error::{OptimError, Result};
12
13#[derive(Debug, Clone)]
15pub struct GradientClipConfig<A: Float> {
16 pub max_value: Option<A>,
18 pub min_value: Option<A>,
20 pub maxnorm: Option<A>,
22 pub max_l1norm: Option<A>,
24 pub centralization: bool,
26 pub zero_threshold: Option<A>,
28}
29
30impl<A: Float + Send + Sync> Default for GradientClipConfig<A> {
31 fn default() -> Self {
32 Self {
33 max_value: None,
34 min_value: None,
35 maxnorm: None,
36 max_l1norm: None,
37 centralization: false,
38 zero_threshold: None,
39 }
40 }
41}
42
43pub struct GradientProcessor<A: Float> {
45 config: GradientClipConfig<A>,
46}
47
48impl<A: Float + ScalarOperand + Debug + Send + Sync> Default for GradientProcessor<A> {
49 fn default() -> Self {
50 Self {
51 config: GradientClipConfig::default(),
52 }
53 }
54}
55
56impl<A: Float + ScalarOperand + Debug + Send + Sync> GradientProcessor<A> {
57 pub fn new() -> Self {
59 Self::default()
60 }
61
62 pub fn with_config(config: GradientClipConfig<A>) -> Self {
64 Self { config }
65 }
66
67 pub fn set_max_value(&mut self, value: A) -> &mut Self {
69 self.config.max_value = Some(value);
70 self
71 }
72
73 pub fn set_min_value(&mut self, value: A) -> &mut Self {
75 self.config.min_value = Some(value);
76 self
77 }
78
79 pub fn set_max_norm(&mut self, value: A) -> &mut Self {
81 self.config.maxnorm = Some(value);
82 self
83 }
84
85 pub fn set_max_l1_norm(&mut self, value: A) -> &mut Self {
87 self.config.max_l1norm = Some(value);
88 self
89 }
90
91 pub fn set_centralization(&mut self, enabled: bool) -> &mut Self {
93 self.config.centralization = enabled;
94 self
95 }
96
97 pub fn set_zero_threshold(&mut self, value: A) -> &mut Self {
99 self.config.zero_threshold = Some(value);
100 self
101 }
102
103 pub fn set_value_clip(&mut self, min: A, max: A) -> &mut Self {
105 self.config.min_value = Some(min);
106 self.config.max_value = Some(max);
107 self
108 }
109
110 pub fn set_norm_clip(&mut self, maxnorm: A) -> &mut Self {
112 self.config.maxnorm = Some(maxnorm);
113 self
114 }
115
116 pub fn set_l1_norm_clip(&mut self, max_l1norm: A) -> &mut Self {
118 self.config.max_l1norm = Some(max_l1norm);
119 self
120 }
121
122 pub fn enable_centralization(&mut self) -> &mut Self {
124 self.config.centralization = true;
125 self
126 }
127
128 pub fn process<D: Dimension>(&self, gradients: &mut Array<A, D>) -> Result<()> {
130 if let (Some(min), Some(max)) = (self.config.min_value, self.config.max_value) {
132 clip_gradients_by_value(gradients, min, max);
133 }
134
135 if let Some(maxnorm) = self.config.maxnorm {
137 clip_gradient_norm(gradients, maxnorm)?;
138 }
139
140 if let Some(max_l1norm) = self.config.max_l1norm {
142 clip_gradient_l1_norm(gradients, max_l1norm)?;
143 }
144
145 if self.config.centralization {
147 gradient_centralization(gradients);
148 }
149
150 if let Some(threshold) = self.config.zero_threshold {
152 zero_small_gradients(gradients, threshold);
153 }
154
155 Ok(())
156 }
157}
158
159pub fn clip_gradients_by_value<A, D>(
161 gradients: &mut Array<A, D>,
162 min_value: A,
163 max_value: A,
164) -> &mut Array<A, D>
165where
166 A: Float + ScalarOperand,
167 D: Dimension,
168{
169 gradients.mapv_inplace(|x| {
170 if x < min_value {
171 min_value
172 } else if x > max_value {
173 max_value
174 } else {
175 x
176 }
177 });
178 gradients
179}
180
181pub fn clip_gradient_norm<A, D>(gradients: &mut Array<A, D>, maxnorm: A) -> Result<&mut Array<A, D>>
183where
184 A: Float + ScalarOperand,
185 D: Dimension,
186{
187 if maxnorm <= A::zero() {
188 return Err(OptimError::InvalidConfig(
189 "maxnorm must be positive".to_string(),
190 ));
191 }
192
193 let _norm = gradients
195 .iter()
196 .fold(A::zero(), |acc, &x| acc + x * x)
197 .sqrt();
198
199 if _norm > maxnorm {
201 let scale = maxnorm / _norm;
202 gradients.mapv_inplace(|x| x * scale);
203 }
204
205 Ok(gradients)
206}
207
208pub fn clip_gradient_l1_norm<A, D>(
210 gradients: &mut Array<A, D>,
211 max_l1norm: A,
212) -> Result<&mut Array<A, D>>
213where
214 A: Float + ScalarOperand,
215 D: Dimension,
216{
217 if max_l1norm <= A::zero() {
218 return Err(OptimError::InvalidConfig(
219 "max_l1norm must be positive".to_string(),
220 ));
221 }
222
223 let l1_norm = gradients.iter().fold(A::zero(), |acc, &x| acc + x.abs());
225
226 if l1_norm > max_l1norm {
228 let scale = max_l1norm / l1_norm;
229 gradients.mapv_inplace(|x| x * scale);
230 }
231
232 Ok(gradients)
233}
234
235pub fn gradient_centralization<A, D>(gradients: &mut Array<A, D>) -> &mut Array<A, D>
237where
238 A: Float + ScalarOperand,
239 D: Dimension,
240{
241 let sum = gradients.iter().fold(A::zero(), |acc, &x| acc + x);
243 let mean = sum / A::from(gradients.len()).unwrap_or(A::one());
244
245 gradients.mapv_inplace(|x| x - mean);
247
248 gradients
249}
250
251pub fn zero_small_gradients<A, D>(gradients: &mut Array<A, D>, threshold: A) -> &mut Array<A, D>
253where
254 A: Float + ScalarOperand,
255 D: Dimension,
256{
257 let abs_threshold = threshold.abs();
258
259 gradients.mapv_inplace(|x| {
260 if x.abs() < abs_threshold {
261 A::zero()
262 } else {
263 x
264 }
265 });
266
267 gradients
268}
269
270#[derive(Debug, Clone)]
272pub struct GradientAccumulator<A: Float, D: Dimension> {
273 accumulated_gradients: Option<Array<A, D>>,
275 num_accumulated: usize,
277 accumulation_steps: usize,
279 averagegradients: bool,
281}
282
283impl<A: Float + ScalarOperand + Debug, D: Dimension + Send + Sync> GradientAccumulator<A, D> {
284 pub fn new(_accumulation_steps: usize, averagegradients: bool) -> Self {
291 Self {
292 accumulated_gradients: None,
293 num_accumulated: 0,
294 accumulation_steps: _accumulation_steps,
295 averagegradients,
296 }
297 }
298
299 pub fn accumulate(&mut self, gradients: &Array<A, D>) -> bool {
309 if let Some(acc) = &mut self.accumulated_gradients {
310 for (acc_val, &grad_val) in acc.iter_mut().zip(gradients.iter()) {
311 *acc_val = *acc_val + grad_val;
312 }
313 } else {
314 self.accumulated_gradients = Some(gradients.clone());
315 }
316
317 self.num_accumulated += 1;
318 self.num_accumulated >= self.accumulation_steps
319 }
320
321 pub fn get_and_reset(&mut self) -> Option<Array<A, D>> {
327 if let Some(mut gradients) = self.accumulated_gradients.take() {
328 if self.averagegradients && self.num_accumulated > 0 {
329 let scale = A::one() / A::from(self.num_accumulated).unwrap_or(A::one());
330 gradients.mapv_inplace(|x| x * scale);
331 }
332 self.num_accumulated = 0;
333 Some(gradients)
334 } else {
335 None
336 }
337 }
338
339 pub fn progress(&self) -> (usize, usize) {
341 (self.num_accumulated, self.accumulation_steps)
342 }
343
344 pub fn is_ready(&self) -> bool {
346 self.num_accumulated >= self.accumulation_steps
347 }
348
349 pub fn reset(&mut self) {
351 self.accumulated_gradients = None;
352 self.num_accumulated = 0;
353 }
354
355 pub fn set_accumulation_steps(&mut self, steps: usize) {
357 self.accumulation_steps = steps;
358 }
359}
360
361pub fn adaptive_gradient_clipping<'a, A, D>(
366 gradients: &'a mut Array<A, D>,
367 parameters: &Array<A, D>,
368 max_ratio: A,
369) -> Result<&'a mut Array<A, D>>
370where
371 A: Float + ScalarOperand,
372 D: Dimension,
373{
374 if max_ratio <= A::zero() {
375 return Err(OptimError::InvalidConfig(
376 "max_ratio must be positive".to_string(),
377 ));
378 }
379
380 let grad_norm = gradients
381 .iter()
382 .fold(A::zero(), |acc, &x| acc + x * x)
383 .sqrt();
384
385 let param_norm = parameters
386 .iter()
387 .fold(A::zero(), |acc, &x| acc + x * x)
388 .sqrt();
389
390 if param_norm > A::zero() && grad_norm > A::zero() {
391 let _ratio = grad_norm / param_norm;
392 if _ratio > max_ratio {
393 let scale = max_ratio / _ratio;
394 gradients.mapv_inplace(|x| x * scale);
395 }
396 }
397
398 Ok(gradients)
399}
400
401pub fn add_gradient_noise<A, D>(
411 gradients: &mut Array<A, D>,
412 noise_std: A,
413 seed: Option<u64>,
414) -> &mut Array<A, D>
415where
416 A: Float + ScalarOperand,
417 D: Dimension,
418{
419 use scirs2_core::random::{seeded_rng, RandNormal};
420
421 if noise_std <= A::zero() {
422 return gradients;
423 }
424
425 let Some(std_f64) = noise_std.to_f64() else {
426 return gradients;
430 };
431 let Ok(normal) = RandNormal::new(0.0, std_f64) else {
432 return gradients;
433 };
434
435 let count = gradients.len();
439 let samples: Vec<f64> = match seed {
440 Some(seed) => seeded_rng(seed).sample_vec(normal, count),
441 None => thread_rng().sample_vec(normal, count),
442 };
443
444 for (g, &n) in gradients.iter_mut().zip(samples.iter()) {
445 *g = *g + A::from(n).unwrap_or(A::zero());
446 }
447
448 gradients
449}
450
451#[derive(Debug, Clone)]
455pub struct GradientMask<A: Float, D: Dimension> {
456 mask: Array<bool, D>,
458 lr_multipliers: Option<Array<A, D>>,
460}
461
462impl<A: Float + ScalarOperand + Debug, D: Dimension + Send + Sync> GradientMask<A, D> {
463 pub fn new(mask: Array<bool, D>) -> Self {
469 Self {
470 mask,
471 lr_multipliers: None,
472 }
473 }
474
475 pub fn freeze_all(shape: D) -> Self {
477 Self {
478 mask: Array::from_elem(shape, false),
479 lr_multipliers: None,
480 }
481 }
482
483 pub fn update_all(shape: D) -> Self {
485 Self {
486 mask: Array::from_elem(shape, true),
487 lr_multipliers: None,
488 }
489 }
490
491 pub fn with_lr_multipliers(mut self, multipliers: Array<A, D>) -> Self {
493 self.lr_multipliers = Some(multipliers);
494 self
495 }
496
497 pub fn apply_mask<'a>(&self, gradients: &'a mut Array<A, D>) -> &'a mut Array<A, D> {
507 gradients.zip_mut_with(&self.mask, |grad, &should_update| {
508 if !should_update {
509 *grad = A::zero();
510 }
511 });
512
513 if let Some(multipliers) = &self.lr_multipliers {
515 gradients.zip_mut_with(multipliers, |grad, &mult| {
516 *grad = *grad * mult;
517 });
518 }
519
520 gradients
521 }
522
523 pub fn freeze_indices(&mut self, indices: &[usize]) -> Result<()> {
525 let flat_mask = self.mask.as_slice_mut().ok_or_else(|| {
526 OptimError::InvalidConfig("Cannot access mask as flat slice".to_string())
527 })?;
528
529 for &idx in indices {
530 if idx < flat_mask.len() {
531 flat_mask[idx] = false;
532 } else {
533 return Err(OptimError::InvalidConfig(format!(
534 "Index {} out of bounds for mask of size {}",
535 idx,
536 flat_mask.len()
537 )));
538 }
539 }
540 Ok(())
541 }
542
543 pub fn unfreeze_indices(&mut self, indices: &[usize]) -> Result<()> {
545 let flat_mask = self.mask.as_slice_mut().ok_or_else(|| {
546 OptimError::InvalidConfig("Cannot access mask as flat slice".to_string())
547 })?;
548
549 for &idx in indices {
550 if idx < flat_mask.len() {
551 flat_mask[idx] = true;
552 } else {
553 return Err(OptimError::InvalidConfig(format!(
554 "Index {} out of bounds for mask of size {}",
555 idx,
556 flat_mask.len()
557 )));
558 }
559 }
560 Ok(())
561 }
562
563 pub fn num_frozen(&self) -> usize {
565 self.mask.iter().filter(|&&x| !x).count()
566 }
567
568 pub fn num_active(&self) -> usize {
570 self.mask.iter().filter(|&&x| x).count()
571 }
572}
573
574#[cfg(test)]
575mod tests {
576 use super::*;
577 use approx::assert_relative_eq;
578 use scirs2_core::ndarray::Array1;
579
580 #[test]
581 fn test_gradient_processor() {
582 let config = GradientClipConfig::<f64> {
583 max_value: Some(5.0),
584 min_value: Some(-5.0),
585 maxnorm: Some(10.0),
586 ..Default::default()
587 };
588
589 let processor = GradientProcessor::with_config(config);
590
591 let mut gradients = Array1::from_vec(vec![-8.0, 3.0, 7.0, -2.0, 6.0]);
592 processor.process(&mut gradients).expect("unwrap failed");
593
594 assert_eq!(gradients[0], -5.0);
596 assert_eq!(gradients[2], 5.0);
597 assert_eq!(gradients[4], 5.0);
598 }
599
600 #[test]
601 fn test_adaptive_clipping() {
602 let mut gradients = Array1::from_vec(vec![3.0, 4.0]); let parameters = Array1::from_vec(vec![1.0, 0.0]); adaptive_gradient_clipping(&mut gradients, ¶meters, 2.0).expect("unwrap failed");
607
608 let new_grad_norm = gradients.iter().fold(0.0, |acc, &x| acc + x * x).sqrt();
610 assert!((new_grad_norm - 2.0).abs() < 1e-6);
611 }
612
613 #[test]
614 fn test_gradient_accumulator() {
615 let mut accumulator = GradientAccumulator::new(3, true);
616
617 let grad1 = Array1::from_vec(vec![1.0, 2.0, 3.0]);
619 assert!(!accumulator.accumulate(&grad1));
620 assert_eq!(accumulator.progress(), (1, 3));
621
622 let grad2 = Array1::from_vec(vec![2.0, 3.0, 4.0]);
624 assert!(!accumulator.accumulate(&grad2));
625 assert_eq!(accumulator.progress(), (2, 3));
626
627 let grad3 = Array1::from_vec(vec![3.0, 4.0, 5.0]);
629 assert!(accumulator.accumulate(&grad3));
630 assert!(accumulator.is_ready());
631
632 let final_grads = accumulator.get_and_reset().expect("unwrap failed");
634 assert_relative_eq!(final_grads[0], 2.0, epsilon = 1e-6); assert_relative_eq!(final_grads[1], 3.0, epsilon = 1e-6); assert_relative_eq!(final_grads[2], 4.0, epsilon = 1e-6); assert_eq!(accumulator.progress(), (0, 3));
640 assert!(!accumulator.is_ready());
641 }
642
643 #[test]
644 fn test_gradient_accumulator_sum_mode() {
645 let mut accumulator = GradientAccumulator::new(2, false); let grad1 = Array1::from_vec(vec![1.0, 2.0]);
648 let grad2 = Array1::from_vec(vec![3.0, 4.0]);
649
650 accumulator.accumulate(&grad1);
651 accumulator.accumulate(&grad2);
652
653 let final_grads = accumulator.get_and_reset().expect("unwrap failed");
654 assert_relative_eq!(final_grads[0], 4.0, epsilon = 1e-6); assert_relative_eq!(final_grads[1], 6.0, epsilon = 1e-6); }
657
658 #[test]
659 fn test_gradient_noise() {
660 let mut gradients = Array1::from_vec(vec![1.0, 2.0, 3.0]);
661 let original = gradients.clone();
662
663 add_gradient_noise(&mut gradients, 0.1, Some(42));
665
666 for (i, (&orig, &noisy)) in original.iter().zip(gradients.iter()).enumerate() {
668 assert!(
669 (orig - noisy).abs() < 1.0,
670 "Index {}: {} vs {}",
671 i,
672 orig,
673 noisy
674 );
675 }
676 }
677
678 #[test]
679 fn test_gradient_noise_zero_std() {
680 let mut gradients = Array1::from_vec(vec![1.0, 2.0, 3.0]);
681 let original = gradients.clone();
682
683 add_gradient_noise(&mut gradients, 0.0, Some(42));
685
686 for (orig, noisy) in original.iter().zip(gradients.iter()) {
687 assert_relative_eq!(*orig, *noisy, epsilon = 1e-10);
688 }
689 }
690
691 #[test]
692 fn test_gradient_mask_creation() {
693 let mask = Array1::from_vec(vec![true, false, true]);
694 let grad_mask: GradientMask<f64, scirs2_core::ndarray::Ix1> = GradientMask::new(mask);
695
696 assert_eq!(grad_mask.num_active(), 2);
697 assert_eq!(grad_mask.num_frozen(), 1);
698 }
699
700 #[test]
701 fn test_gradient_mask_apply() {
702 let mask = Array1::from_vec(vec![true, false, true]);
703 let grad_mask: GradientMask<f64, scirs2_core::ndarray::Ix1> = GradientMask::new(mask);
704 let mut gradients = Array1::from_vec(vec![1.0, 2.0, 3.0]);
705
706 grad_mask.apply_mask(&mut gradients);
707
708 assert_eq!(
709 gradients.as_slice().expect("unwrap failed"),
710 &[1.0, 0.0, 3.0]
711 );
712 }
713
714 #[test]
715 fn test_gradient_mask_freeze_unfreeze() {
716 let mask = Array1::from_vec(vec![true, true, true]);
717 let mut grad_mask: GradientMask<f64, scirs2_core::ndarray::Ix1> = GradientMask::new(mask);
718
719 grad_mask.freeze_indices(&[0, 2]).expect("unwrap failed");
721 assert_eq!(grad_mask.num_frozen(), 2);
722 assert_eq!(grad_mask.num_active(), 1);
723
724 grad_mask.unfreeze_indices(&[0]).expect("unwrap failed");
726 assert_eq!(grad_mask.num_frozen(), 1);
727 assert_eq!(grad_mask.num_active(), 2);
728 }
729
730 #[test]
731 fn test_gradient_mask_with_lr_multipliers() {
732 let mask = Array1::from_vec(vec![true, true, true]);
733 let multipliers = Array1::from_vec(vec![1.0, 0.5, 2.0]);
734 let grad_mask: GradientMask<f64, scirs2_core::ndarray::Ix1> =
735 GradientMask::new(mask).with_lr_multipliers(multipliers);
736 let mut gradients = Array1::from_vec(vec![1.0, 2.0, 3.0]);
737
738 grad_mask.apply_mask(&mut gradients);
739
740 assert_relative_eq!(gradients[0], 1.0, epsilon = 1e-6);
741 assert_relative_eq!(gradients[1], 1.0, epsilon = 1e-6); assert_relative_eq!(gradients[2], 6.0, epsilon = 1e-6); }
744
745 #[test]
746 fn test_gradient_mask_freeze_all() {
747 let grad_mask = GradientMask::<f64, scirs2_core::ndarray::Ix1>::freeze_all(
748 scirs2_core::ndarray::Ix1(3),
749 );
750 assert_eq!(grad_mask.num_frozen(), 3);
751 assert_eq!(grad_mask.num_active(), 0);
752 }
753
754 #[test]
755 fn test_gradient_mask_update_all() {
756 let grad_mask = GradientMask::<f64, scirs2_core::ndarray::Ix1>::update_all(
757 scirs2_core::ndarray::Ix1(3),
758 );
759 assert_eq!(grad_mask.num_frozen(), 0);
760 assert_eq!(grad_mask.num_active(), 3);
761 }
762}