1#[derive(Debug, Clone, Copy, PartialEq, Eq)]
8pub enum RegularizerType {
9 L1,
11 L2,
13 ElasticNet,
15}
16
17#[derive(Debug, Clone)]
19pub struct RegularizerConfig {
20 pub reg_type: RegularizerType,
22 pub lambda: f64,
24 pub elastic_alpha: f64,
27}
28
29impl Default for RegularizerConfig {
30 fn default() -> Self {
31 Self {
32 reg_type: RegularizerType::L2,
33 lambda: 0.01,
34 elastic_alpha: 0.5,
35 }
36 }
37}
38
39#[derive(Debug, Clone)]
41pub struct RegularizerStats {
42 pub reg_type: RegularizerType,
44 pub lambda: f64,
46 pub computations: u64,
48}
49
50pub struct TensorRegularizer {
69 config: RegularizerConfig,
70 computations: u64,
71}
72
73impl TensorRegularizer {
74 pub fn new(config: RegularizerConfig) -> Self {
76 Self {
77 config,
78 computations: 0,
79 }
80 }
81
82 pub fn penalty(&mut self, weights: &[f64]) -> f64 {
88 self.computations += 1;
89 match self.config.reg_type {
90 RegularizerType::L1 => self.l1_penalty(weights),
91 RegularizerType::L2 => self.l2_penalty(weights),
92 RegularizerType::ElasticNet => self.elastic_penalty(weights),
93 }
94 }
95
96 pub fn gradient(&mut self, weights: &[f64]) -> Vec<f64> {
98 self.computations += 1;
99 match self.config.reg_type {
100 RegularizerType::L1 => self.l1_gradient(weights),
101 RegularizerType::L2 => self.l2_gradient(weights),
102 RegularizerType::ElasticNet => self.elastic_gradient(weights),
103 }
104 }
105
106 pub fn l1_penalty(&self, weights: &[f64]) -> f64 {
112 let sum_abs: f64 = weights.iter().map(|w| w.abs()).sum();
113 self.config.lambda * sum_abs
114 }
115
116 pub fn l1_gradient(&self, weights: &[f64]) -> Vec<f64> {
120 weights
121 .iter()
122 .map(|&w| {
123 if w > 0.0 {
124 self.config.lambda
125 } else if w < 0.0 {
126 -self.config.lambda
127 } else {
128 0.0
129 }
130 })
131 .collect()
132 }
133
134 pub fn l2_penalty(&self, weights: &[f64]) -> f64 {
140 let sum_sq: f64 = weights.iter().map(|w| w * w).sum();
141 self.config.lambda * sum_sq
142 }
143
144 pub fn l2_gradient(&self, weights: &[f64]) -> Vec<f64> {
146 weights
147 .iter()
148 .map(|&w| 2.0 * self.config.lambda * w)
149 .collect()
150 }
151
152 pub fn elastic_penalty(&self, weights: &[f64]) -> f64 {
158 let alpha = self.config.elastic_alpha;
159 alpha * self.l1_penalty(weights) + (1.0 - alpha) * self.l2_penalty(weights)
160 }
161
162 pub fn elastic_gradient(&self, weights: &[f64]) -> Vec<f64> {
164 let alpha = self.config.elastic_alpha;
165 let l1 = self.l1_gradient(weights);
166 let l2 = self.l2_gradient(weights);
167 l1.iter()
168 .zip(l2.iter())
169 .map(|(&a, &b)| alpha * a + (1.0 - alpha) * b)
170 .collect()
171 }
172
173 pub fn stats(&self) -> RegularizerStats {
179 RegularizerStats {
180 reg_type: self.config.reg_type,
181 lambda: self.config.lambda,
182 computations: self.computations,
183 }
184 }
185}
186
187#[cfg(test)]
192mod tests {
193 use super::*;
194
195 fn make(reg_type: RegularizerType, lambda: f64, elastic_alpha: f64) -> TensorRegularizer {
196 TensorRegularizer::new(RegularizerConfig {
197 reg_type,
198 lambda,
199 elastic_alpha,
200 })
201 }
202
203 #[test]
208 fn l1_penalty_positive_weights() {
209 let mut r = make(RegularizerType::L1, 0.1, 0.5);
210 let p = r.penalty(&[1.0, 2.0, 3.0]);
211 assert!((p - 0.6).abs() < 1e-12);
212 }
213
214 #[test]
215 fn l1_penalty_negative_weights() {
216 let mut r = make(RegularizerType::L1, 0.1, 0.5);
217 let p = r.penalty(&[-1.0, -2.0, -3.0]);
218 assert!((p - 0.6).abs() < 1e-12);
219 }
220
221 #[test]
222 fn l1_penalty_mixed_weights() {
223 let mut r = make(RegularizerType::L1, 0.5, 0.5);
224 let p = r.penalty(&[1.0, -2.0, 0.0]);
225 assert!((p - 1.5).abs() < 1e-12); }
227
228 #[test]
229 fn l1_penalty_zero_weights() {
230 let mut r = make(RegularizerType::L1, 0.1, 0.5);
231 let p = r.penalty(&[0.0, 0.0, 0.0]);
232 assert!((p - 0.0).abs() < 1e-12);
233 }
234
235 #[test]
236 fn l1_penalty_single_weight() {
237 let mut r = make(RegularizerType::L1, 0.2, 0.5);
238 let p = r.penalty(&[5.0]);
239 assert!((p - 1.0).abs() < 1e-12); }
241
242 #[test]
243 fn l1_penalty_empty_weights() {
244 let mut r = make(RegularizerType::L1, 1.0, 0.5);
245 let p = r.penalty(&[]);
246 assert!((p - 0.0).abs() < 1e-12);
247 }
248
249 #[test]
254 fn l1_gradient_correctness() {
255 let mut r = make(RegularizerType::L1, 0.1, 0.5);
256 let g = r.gradient(&[3.0, -2.0, 0.0]);
257 assert!((g[0] - 0.1).abs() < 1e-12);
258 assert!((g[1] - (-0.1)).abs() < 1e-12);
259 assert!((g[2] - 0.0).abs() < 1e-12);
260 }
261
262 #[test]
263 fn l1_gradient_all_positive() {
264 let r = make(RegularizerType::L1, 0.5, 0.5);
265 let g = r.l1_gradient(&[1.0, 2.0, 3.0]);
266 assert!(g.iter().all(|&v| (v - 0.5).abs() < 1e-12));
267 }
268
269 #[test]
270 fn l1_gradient_all_negative() {
271 let r = make(RegularizerType::L1, 0.5, 0.5);
272 let g = r.l1_gradient(&[-1.0, -2.0, -3.0]);
273 assert!(g.iter().all(|&v| (v - (-0.5)).abs() < 1e-12));
274 }
275
276 #[test]
281 fn l2_penalty_positive_weights() {
282 let mut r = make(RegularizerType::L2, 0.1, 0.5);
283 let p = r.penalty(&[1.0, 2.0, 3.0]);
284 assert!((p - 1.4).abs() < 1e-12);
286 }
287
288 #[test]
289 fn l2_penalty_negative_weights() {
290 let mut r = make(RegularizerType::L2, 0.1, 0.5);
291 let p = r.penalty(&[-1.0, -2.0, -3.0]);
292 assert!((p - 1.4).abs() < 1e-12);
293 }
294
295 #[test]
296 fn l2_penalty_zero_weights() {
297 let mut r = make(RegularizerType::L2, 0.1, 0.5);
298 let p = r.penalty(&[0.0, 0.0]);
299 assert!((p - 0.0).abs() < 1e-12);
300 }
301
302 #[test]
303 fn l2_penalty_single_weight() {
304 let mut r = make(RegularizerType::L2, 0.5, 0.5);
305 let p = r.penalty(&[4.0]);
306 assert!((p - 8.0).abs() < 1e-12); }
308
309 #[test]
314 fn l2_gradient_correctness() {
315 let mut r = make(RegularizerType::L2, 0.1, 0.5);
316 let g = r.gradient(&[1.0, -2.0, 3.0]);
317 assert!((g[0] - 0.2).abs() < 1e-12);
319 assert!((g[1] - (-0.4)).abs() < 1e-12);
320 assert!((g[2] - 0.6).abs() < 1e-12);
321 }
322
323 #[test]
324 fn l2_gradient_zero() {
325 let r = make(RegularizerType::L2, 0.5, 0.5);
326 let g = r.l2_gradient(&[0.0, 0.0]);
327 assert!(g.iter().all(|&v| v.abs() < 1e-12));
328 }
329
330 #[test]
335 fn elastic_penalty_balanced() {
336 let mut r = make(RegularizerType::ElasticNet, 0.1, 0.5);
337 let w = [1.0, -2.0, 3.0];
338 let expected = 0.5 * r.l1_penalty(&w) + 0.5 * r.l2_penalty(&w);
339 let p = r.penalty(&w);
340 assert!((p - expected).abs() < 1e-12);
341 }
342
343 #[test]
344 fn elastic_penalty_pure_l1() {
345 let mut elastic = make(RegularizerType::ElasticNet, 0.1, 1.0);
347 let mut l1 = make(RegularizerType::L1, 0.1, 0.5);
348 let w = [2.0, -3.0, 0.5];
349 let pe = elastic.penalty(&w);
350 let pl = l1.penalty(&w);
351 assert!((pe - pl).abs() < 1e-12);
352 }
353
354 #[test]
355 fn elastic_penalty_pure_l2() {
356 let mut elastic = make(RegularizerType::ElasticNet, 0.1, 0.0);
358 let mut l2 = make(RegularizerType::L2, 0.1, 0.5);
359 let w = [2.0, -3.0, 0.5];
360 let pe = elastic.penalty(&w);
361 let pl = l2.penalty(&w);
362 assert!((pe - pl).abs() < 1e-12);
363 }
364
365 #[test]
366 fn elastic_penalty_zero_weights() {
367 let mut r = make(RegularizerType::ElasticNet, 0.5, 0.3);
368 let p = r.penalty(&[0.0, 0.0]);
369 assert!((p - 0.0).abs() < 1e-12);
370 }
371
372 #[test]
377 fn elastic_gradient_balanced() {
378 let mut r = make(RegularizerType::ElasticNet, 0.1, 0.5);
379 let w = [1.0, -2.0, 3.0];
380 let l1g = r.l1_gradient(&w);
381 let l2g = r.l2_gradient(&w);
382 let expected: Vec<f64> = l1g
383 .iter()
384 .zip(l2g.iter())
385 .map(|(&a, &b)| 0.5 * a + 0.5 * b)
386 .collect();
387 let g = r.gradient(&w);
388 for (i, (&got, &exp)) in g.iter().zip(expected.iter()).enumerate() {
389 assert!(
390 (got - exp).abs() < 1e-12,
391 "mismatch at index {i}: got {got}, expected {exp}"
392 );
393 }
394 }
395
396 #[test]
397 fn elastic_gradient_pure_l1() {
398 let mut elastic = make(RegularizerType::ElasticNet, 0.2, 1.0);
399 let mut l1 = make(RegularizerType::L1, 0.2, 0.5);
400 let w = [1.0, -1.0, 0.0];
401 let ge = elastic.gradient(&w);
402 let gl = l1.gradient(&w);
403 for (a, b) in ge.iter().zip(gl.iter()) {
404 assert!((a - b).abs() < 1e-12);
405 }
406 }
407
408 #[test]
409 fn elastic_gradient_pure_l2() {
410 let mut elastic = make(RegularizerType::ElasticNet, 0.2, 0.0);
411 let mut l2 = make(RegularizerType::L2, 0.2, 0.5);
412 let w = [1.0, -1.0, 0.5];
413 let ge = elastic.gradient(&w);
414 let gl = l2.gradient(&w);
415 for (a, b) in ge.iter().zip(gl.iter()) {
416 assert!((a - b).abs() < 1e-12);
417 }
418 }
419
420 #[test]
425 fn lambda_scaling_l1() {
426 let r1 = make(RegularizerType::L1, 0.1, 0.5);
427 let r2 = make(RegularizerType::L1, 0.2, 0.5);
428 let w = [1.0, 2.0, 3.0];
429 let p1 = r1.l1_penalty(&w);
430 let p2 = r2.l1_penalty(&w);
431 assert!((p2 / p1 - 2.0).abs() < 1e-12);
432 }
433
434 #[test]
435 fn lambda_scaling_l2() {
436 let r1 = make(RegularizerType::L2, 0.1, 0.5);
437 let r2 = make(RegularizerType::L2, 0.3, 0.5);
438 let w = [1.0, 2.0];
439 let p1 = r1.l2_penalty(&w);
440 let p2 = r2.l2_penalty(&w);
441 assert!((p2 / p1 - 3.0).abs() < 1e-12);
442 }
443
444 #[test]
449 fn stats_initial() {
450 let r = make(RegularizerType::L2, 0.01, 0.5);
451 let s = r.stats();
452 assert_eq!(s.reg_type, RegularizerType::L2);
453 assert!((s.lambda - 0.01).abs() < 1e-12);
454 assert_eq!(s.computations, 0);
455 }
456
457 #[test]
458 fn stats_after_operations() {
459 let mut r = make(RegularizerType::L1, 0.1, 0.5);
460 r.penalty(&[1.0]);
461 r.penalty(&[2.0]);
462 r.gradient(&[3.0]);
463 let s = r.stats();
464 assert_eq!(s.computations, 3);
465 }
466
467 #[test]
472 fn default_config() {
473 let cfg = RegularizerConfig::default();
474 assert_eq!(cfg.reg_type, RegularizerType::L2);
475 assert!((cfg.lambda - 0.01).abs() < 1e-12);
476 assert!((cfg.elastic_alpha - 0.5).abs() < 1e-12);
477 }
478
479 #[test]
484 fn large_weight_l2() {
485 let mut r = make(RegularizerType::L2, 1.0, 0.5);
486 let p = r.penalty(&[1000.0]);
487 assert!((p - 1_000_000.0).abs() < 1e-6);
488 }
489
490 #[test]
491 fn very_small_lambda() {
492 let mut r = make(RegularizerType::L1, 1e-10, 0.5);
493 let p = r.penalty(&[1.0, 2.0, 3.0]);
494 assert!((p - 6e-10).abs() < 1e-20);
495 }
496
497 #[test]
498 fn elastic_alpha_0_25() {
499 let mut r = make(RegularizerType::ElasticNet, 1.0, 0.25);
500 let w = [2.0];
501 let p = r.penalty(&w);
503 assert!((p - 3.5).abs() < 1e-12);
504 }
505
506 #[test]
507 fn gradient_empty_weights() {
508 let mut r = make(RegularizerType::L2, 0.1, 0.5);
509 let g = r.gradient(&[]);
510 assert!(g.is_empty());
511 }
512}