optirs_core/regularizers/
shakedrop.rs1use scirs2_core::ndarray::{Array, ArrayBase, Data, Dimension, ScalarOperand};
2use scirs2_core::numeric::{Float, FromPrimitive};
3use std::cell::RefCell;
4use std::fmt::Debug;
5
6use crate::error::{OptimError, Result};
7use crate::regularizers::Regularizer;
8
9type ShakeDropResult<A, D> = (Array<A, D>, (A, A, A));
11
12#[derive(Debug)]
31pub struct ShakeDrop<A: Float + FromPrimitive + Debug> {
32 pub p: A,
34 pub alpha_range: (A, A),
36 pub beta_range: (A, A),
38 rng: RefCell<scirs2_core::random::Random<scirs2_core::random::rngs::StdRng>>,
40}
41
42impl<A: Float + FromPrimitive + Debug + Send + Sync> ShakeDrop<A> {
43 pub fn new(p: A) -> Self {
55 let zero = A::zero();
56 let one = A::one();
57 let neg_one = zero - one;
58
59 Self {
60 p,
61 alpha_range: (neg_one, one),
62 beta_range: (zero, one),
63 rng: RefCell::new(scirs2_core::random::Random::seed(42)),
64 }
65 }
66
67 pub fn new_with_ranges(p: A, alpharange: (A, A), beta_range: (A, A)) -> Self {
79 Self {
80 p,
81 alpha_range: alpharange,
82 beta_range,
83 rng: RefCell::new(scirs2_core::random::Random::seed(42)),
84 }
85 }
86
87 fn random_in_range(&self, range: (A, A)) -> Result<A> {
89 let (min, max) = range;
90 let min_f = min
91 .to_f64()
92 .ok_or_else(|| OptimError::InvalidConfig("Failed to convert min to f64".to_string()))?;
93 let max_f = max
94 .to_f64()
95 .ok_or_else(|| OptimError::InvalidConfig("Failed to convert max to f64".to_string()))?;
96
97 if (max_f - min_f).abs() < 1e-10 {
99 return Ok(min);
100 }
101
102 let random_val = self.rng.borrow_mut().gen_range(min_f..max_f);
103 A::from_f64(random_val).ok_or_else(|| {
104 OptimError::InvalidConfig("Failed to convert random value from f64".to_string())
105 })
106 }
107
108 fn get_gate(&self) -> Result<(A, A, A)> {
117 let zero = A::zero();
118 let one = A::one();
119
120 let u: f64 = self.rng.borrow_mut().gen_range(0.0..1.0);
122 let p_f64 = self
123 .p
124 .to_f64()
125 .ok_or_else(|| OptimError::InvalidConfig("Failed to convert p to f64".to_string()))?;
126 let b = if u < p_f64 { one } else { zero };
127
128 let alpha = if b > zero {
130 self.random_in_range(self.alpha_range)?
131 } else {
132 zero
133 };
134
135 let beta = self.random_in_range(self.beta_range)?;
137
138 Ok((b, alpha, beta))
139 }
140
141 pub fn forward<S, D>(&self, x: &ArrayBase<S, D>) -> Result<ShakeDropResult<A, D>>
151 where
152 S: Data<Elem = A>,
153 D: Dimension,
154 {
155 let (b, alpha, beta) = self.get_gate()?;
157
158 let factor = b + alpha - b * alpha;
161 let result = x.mapv(|v| v * factor);
162
163 Ok((result, (b, alpha, beta)))
164 }
165
166 pub fn backward<S, D>(
177 &self,
178 grad_output: &ArrayBase<S, D>,
179 gate_params: (A, A, A),
180 ) -> Array<A, D>
181 where
182 S: Data<Elem = A>,
183 D: Dimension,
184 {
185 let (b, _alpha, beta) = gate_params;
186
187 let factor = b + beta - b * beta;
191 grad_output.mapv(|g| g * factor)
192 }
193}
194
195impl<A: Float + FromPrimitive + Debug + ScalarOperand, D: Dimension + Send + Sync> Regularizer<A, D>
196 for ShakeDrop<A>
197{
198 fn apply(&self, _params: &Array<A, D>, _gradients: &mut Array<A, D>) -> Result<A> {
199 Err(OptimError::InvalidConfig(
204 "ShakeDrop should be applied to activations during forward/backward passes, \
205 not through the Regularizer trait's apply method"
206 .to_string(),
207 ))
208 }
209
210 fn penalty(&self, _params: &Array<A, D>) -> Result<A> {
211 Ok(A::zero())
213 }
214}
215
216#[cfg(test)]
217mod tests {
218 use super::*;
219 use approx::assert_abs_diff_eq;
220 use scirs2_core::ndarray::{Array1, Array2};
221
222 #[test]
223 fn test_shakedrop_new() {
224 let sd = ShakeDrop::new(0.5f64);
225 assert_eq!(sd.p, 0.5);
226 assert_eq!(sd.alpha_range, (-1.0, 1.0));
227 assert_eq!(sd.beta_range, (0.0, 1.0));
228 }
229
230 #[test]
231 fn test_shakedrop_new_with_ranges() {
232 let sd = ShakeDrop::new_with_ranges(0.7f64, (-0.5, 0.5), (0.2, 0.8));
233 assert_eq!(sd.p, 0.7);
234 assert_eq!(sd.alpha_range, (-0.5, 0.5));
235 assert_eq!(sd.beta_range, (0.2, 0.8));
236 }
237
238 #[test]
239 fn test_shakedrop_forward_backward() {
240 let x = Array2::from_elem((2, 3), 1.0f64);
242
243 let sd = ShakeDrop::new_with_ranges(1.0f64, (0.5, 0.500001), (0.5, 0.500001));
246
247 let (output, gate_params) = sd.forward(&x).expect("forward failed");
249
250 assert_eq!(gate_params.0, 1.0); assert_abs_diff_eq!(gate_params.1, 0.5, epsilon = 1e-5); assert_abs_diff_eq!(gate_params.2, 0.5, epsilon = 1e-5); for &val in output.iter() {
257 assert_abs_diff_eq!(val, 1.0, epsilon = 1e-5);
258 }
259
260 let grad_output = Array2::from_elem((2, 3), 2.0f64);
262 let grad_input = sd.backward(&grad_output, gate_params);
263
264 for &val in grad_input.iter() {
266 assert_abs_diff_eq!(val, 2.0, epsilon = 1e-5);
267 }
268 }
269
270 #[test]
271 fn test_shakedrop_forward_inactive() {
272 let x = Array1::from_vec(vec![1.0f64, 2.0, 3.0]);
274
275 let sd = ShakeDrop::new_with_ranges(0.0f64, (-0.5, -0.499999), (0.5, 0.500001));
278
279 let (output, gate_params) = sd.forward(&x).expect("forward failed");
281
282 assert_eq!(gate_params.0, 0.0); assert_eq!(gate_params.1, 0.0); assert_abs_diff_eq!(gate_params.2, 0.5, epsilon = 1e-5); for &val in output.iter() {
289 assert_abs_diff_eq!(val, 0.0, epsilon = 1e-10);
290 }
291 }
292
293 #[test]
294 fn test_shakedrop_gen_range() {
295 let sd = ShakeDrop::new(0.5f64);
296
297 for _ in 0..100 {
299 let value = sd
300 .random_in_range((-0.5, 0.5))
301 .expect("random_in_range failed");
302 assert!((-0.5..=0.5).contains(&value));
303 }
304
305 let value = sd
307 .random_in_range((0.5, 0.5))
308 .expect("random_in_range failed");
309 assert_eq!(value, 0.5);
310 }
311
312 #[test]
313 fn test_shakedrop_get_gate() {
314 let sd = ShakeDrop::new(1.0f64);
316 for _ in 0..10 {
317 let (b, alpha, beta) = sd.get_gate().expect("get_gate failed");
318 assert_eq!(b, 1.0);
319 assert!((-1.0..=1.0).contains(&alpha));
320 assert!((0.0..=1.0).contains(&beta));
321 }
322
323 let sd = ShakeDrop::new(0.0f64);
325 for _ in 0..10 {
326 let (b, alpha, beta) = sd.get_gate().expect("get_gate failed");
327 assert_eq!(b, 0.0);
328 assert_eq!(alpha, 0.0);
329 assert!((0.0..=1.0).contains(&beta));
330 }
331 }
332
333 #[test]
334 fn test_regularizer_trait() {
335 let sd = ShakeDrop::new(0.5f64);
336 let params = Array2::from_elem((2, 3), 1.0f64);
337 let mut grads = Array2::from_elem((2, 3), 1.0f64);
338
339 assert!(sd.apply(¶ms, &mut grads).is_err());
341
342 let penalty = sd.penalty(¶ms).expect("penalty failed");
344 assert_eq!(penalty, 0.0);
345 }
346}