optirs_core/regularizers/
dropconnect.rs1use 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};
12use crate::regularizers::Regularizer;
13
14#[derive(Debug, Clone)]
37pub struct DropConnect<A: Float> {
38 drop_prob: A,
40}
41
42impl<A: Float + Debug + ScalarOperand + Send + Sync> DropConnect<A> {
43 pub fn new(dropprob: A) -> Result<Self> {
53 if dropprob < A::zero() || dropprob > A::one() {
54 return Err(OptimError::InvalidConfig(
55 "Drop probability must be between 0.0 and 1.0".to_string(),
56 ));
57 }
58
59 Ok(Self {
60 drop_prob: dropprob,
61 })
62 }
63
64 pub fn apply_to_weights<D: Dimension>(
71 &self,
72 weights: &Array<A, D>,
73 training: bool,
74 ) -> Array<A, D> {
75 if !training || self.drop_prob == A::zero() {
76 return weights.clone();
78 }
79
80 let keep_prob = A::one() - self.drop_prob;
82 let keep_prob_f64 = keep_prob
83 .to_f64()
84 .expect("DropConnect: keep_prob in [0, 1] (validated at construction) fits in f64");
85
86 let mut rng = thread_rng();
88 let mask = Array::from_shape_fn(weights.raw_dim(), |_| rng.random_bool(keep_prob_f64));
89
90 let mut result = weights.clone();
92 for (r, &m) in result.iter_mut().zip(mask.iter()) {
93 if !m {
94 *r = A::zero();
95 } else {
96 *r = *r / keep_prob;
98 }
99 }
100
101 result
102 }
103
104 pub fn apply_to_gradients<D: Dimension>(
109 &self,
110 gradients: &Array<A, D>,
111 weightsshape: D,
112 training: bool,
113 ) -> Array<A, D> {
114 if !training || self.drop_prob == A::zero() {
115 return gradients.clone();
116 }
117
118 let keep_prob = A::one() - self.drop_prob;
120 let keep_prob_f64 = keep_prob
121 .to_f64()
122 .expect("DropConnect: keep_prob in [0, 1] (validated at construction) fits in f64");
123
124 let mut rng = thread_rng();
126 let mask = Array::from_shape_fn(weightsshape, |_| rng.random_bool(keep_prob_f64));
127
128 let mut result = gradients.clone();
130 for (g, &m) in result.iter_mut().zip(mask.iter()) {
131 if !m {
132 *g = A::zero();
133 } else {
134 *g = *g / keep_prob;
136 }
137 }
138
139 result
140 }
141}
142
143impl<A: Float + Debug + ScalarOperand + Send + Sync, D: Dimension + Send + Sync> Regularizer<A, D>
144 for DropConnect<A>
145{
146 fn apply(&self, params: &Array<A, D>, gradients: &mut Array<A, D>) -> Result<A> {
147 let masked_gradients = self.apply_to_gradients(gradients, params.raw_dim(), true);
149
150 gradients.assign(&masked_gradients);
152
153 Ok(A::zero())
155 }
156
157 fn penalty(&self, _params: &Array<A, D>) -> Result<A> {
158 Ok(A::zero())
160 }
161}
162
163#[cfg(test)]
164mod tests {
165 use super::*;
166 use approx::assert_relative_eq;
167 use scirs2_core::ndarray::array;
168
169 #[test]
170 fn test_dropconnect_creation() {
171 let dc = DropConnect::<f64>::new(0.5)
173 .expect("DropConnect::<f64>::new succeeds in test_dropconnect_creation");
174 assert_eq!(dc.drop_prob, 0.5);
175
176 assert!(DropConnect::<f64>::new(-0.1).is_err());
178 assert!(DropConnect::<f64>::new(1.1).is_err());
179 }
180
181 #[test]
182 fn test_dropconnect_training_mode() {
183 let dc = DropConnect::new(0.5)
184 .expect("DropConnect::new succeeds in test_dropconnect_training_mode");
185 let weights = array![[1.0, 2.0], [3.0, 4.0]];
186
187 let masked_weights = dc.apply_to_weights(&weights, true);
189
190 let _zeros = masked_weights.iter().filter(|&&x| x == 0.0).count();
192
193 for (&original, &masked) in weights.iter().zip(masked_weights.iter()) {
195 if masked != 0.0 {
196 assert_relative_eq!(masked, original * 2.0, epsilon = 1e-10);
198 }
199 }
200 }
201
202 #[test]
203 fn test_dropconnect_inference_mode() {
204 let dc = DropConnect::new(0.5)
205 .expect("DropConnect::new succeeds in test_dropconnect_inference_mode");
206 let weights = array![[1.0, 2.0], [3.0, 4.0]];
207
208 let inference_weights = dc.apply_to_weights(&weights, false);
210 assert_eq!(weights, inference_weights);
211 }
212
213 #[test]
214 fn test_dropconnect_zero_probability() {
215 let dc = DropConnect::new(0.0)
216 .expect("DropConnect::new succeeds in test_dropconnect_zero_probability");
217 let weights = array![[1.0, 2.0], [3.0, 4.0]];
218
219 let result = dc.apply_to_weights(&weights, true);
221 assert_eq!(weights, result);
222 }
223
224 #[test]
225 fn test_dropconnect_gradients() {
226 let dc =
227 DropConnect::new(0.5).expect("DropConnect::new succeeds in test_dropconnect_gradients");
228 let gradients = array![[1.0, 1.0], [1.0, 1.0]];
229 let weightsshape = gradients.raw_dim();
230
231 let masked_grads = dc.apply_to_gradients(&gradients, weightsshape, true);
233
234 for &grad in masked_grads.iter() {
236 if grad != 0.0 {
237 assert_relative_eq!(grad, 2.0, epsilon = 1e-10);
238 }
239 }
240 }
241
242 #[test]
243 fn test_regularizer_trait() {
244 let dc =
245 DropConnect::new(0.3).expect("DropConnect::new succeeds in test_regularizer_trait");
246 let params = array![[1.0, 2.0], [3.0, 4.0]];
247 let mut gradient = array![[0.1, 0.2], [0.3, 0.4]];
248
249 let penalty = dc
251 .penalty(¶ms)
252 .expect("dc.penalty succeeds in test_regularizer_trait");
253 assert_eq!(penalty, 0.0); let penalty_from_apply = dc
257 .apply(¶ms, &mut gradient)
258 .expect("dc.apply succeeds in test_regularizer_trait");
259 assert_eq!(penalty_from_apply, 0.0);
260
261 let zeros = gradient.iter().filter(|&&x| x == 0.0).count();
263 assert!(zeros <= 4); }
265}