1use scirs2_core::ndarray_ext::{Array1, Array2, ArrayView1, ArrayView2};
9use scirs2_core::random::Random;
10use sklears_core::{
11 error::{Result as SklResult, SklearsError},
12 traits::{Estimator, Fit, Predict, PredictProba, Untrained},
13 types::Float,
14};
15use std::f64::consts::PI;
16
17#[derive(Debug, Clone)]
19pub struct AffineCouplingLayer {
20 pub scale_weights: Vec<Array2<f64>>,
22 pub scale_biases: Vec<Array1<f64>>,
24 pub translation_weights: Vec<Array2<f64>>,
26 pub translation_biases: Vec<Array1<f64>>,
28 pub mask: Array1<bool>,
30 pub hidden_dims: Vec<usize>,
32}
33
34impl AffineCouplingLayer {
35 pub fn new(input_dim: usize, hidden_dims: Vec<usize>, mask: Array1<bool>) -> Self {
37 let input_masked_dim = mask.iter().filter(|&&x| x).count();
38 let output_dim = input_dim - input_masked_dim;
39
40 let mut scale_arch = vec![input_masked_dim];
42 scale_arch.extend(hidden_dims.clone());
43 scale_arch.push(output_dim);
44
45 let mut translation_arch = vec![input_masked_dim];
47 translation_arch.extend(hidden_dims.clone());
48 translation_arch.push(output_dim);
49
50 let mut scale_weights = Vec::new();
51 let mut scale_biases = Vec::new();
52 let mut translation_weights = Vec::new();
53 let mut translation_biases = Vec::new();
54
55 for i in 0..scale_arch.len() - 1 {
57 let input_size = scale_arch[i];
58 let output_size = scale_arch[i + 1];
59
60 let scale = (2.0 / (input_size + output_size) as f64).sqrt();
61 let w = {
62 let mut rng = Random::default();
63 let mut w = Array2::zeros((output_size, input_size));
64 for i in 0..output_size {
65 for j in 0..input_size {
66 w[[i, j]] = rng.random_range(-3.0..3.0) / 3.0 * scale;
67 }
68 }
69 w
70 };
71 let b = Array1::zeros(output_size);
72
73 scale_weights.push(w);
74 scale_biases.push(b);
75 }
76
77 for i in 0..translation_arch.len() - 1 {
79 let input_size = translation_arch[i];
80 let output_size = translation_arch[i + 1];
81
82 let scale = (2.0 / (input_size + output_size) as f64).sqrt();
83 let w = {
84 let mut rng = Random::default();
85 let mut w = Array2::zeros((output_size, input_size));
86 for i in 0..output_size {
87 for j in 0..input_size {
88 w[[i, j]] = rng.random_range(-3.0..3.0) / 3.0 * scale;
89 }
90 }
91 w
92 };
93 let b = Array1::zeros(output_size);
94
95 translation_weights.push(w);
96 translation_biases.push(b);
97 }
98
99 Self {
100 scale_weights,
101 scale_biases,
102 translation_weights,
103 translation_biases,
104 mask,
105 hidden_dims,
106 }
107 }
108
109 pub fn forward(&self, x: &ArrayView1<f64>) -> SklResult<(Array1<f64>, f64)> {
111 let mut result = x.to_owned();
112 let mut log_det_jacobian = 0.0;
113
114 let x_masked: Array1<f64> = x
116 .iter()
117 .zip(self.mask.iter())
118 .filter(|(_, &mask)| mask)
119 .map(|(&val, _)| val)
120 .collect();
121
122 if x_masked.is_empty() {
123 return Ok((result, log_det_jacobian));
124 }
125
126 let scale = self.compute_scale(&x_masked.view())?;
128 let translation = self.compute_translation(&x_masked.view())?;
129
130 let mut output_idx = 0;
132 for i in 0..x.len() {
133 if !self.mask[i] && output_idx < scale.len() {
134 let exp_scale = scale[output_idx].exp();
135 result[i] = result[i] * exp_scale + translation[output_idx];
136 log_det_jacobian += scale[output_idx];
137 output_idx += 1;
138 }
139 }
140
141 Ok((result, log_det_jacobian))
142 }
143
144 pub fn inverse(&self, z: &ArrayView1<f64>) -> SklResult<Array1<f64>> {
146 let mut result = z.to_owned();
147
148 let z_masked: Array1<f64> = z
150 .iter()
151 .zip(self.mask.iter())
152 .filter(|(_, &mask)| mask)
153 .map(|(&val, _)| val)
154 .collect();
155
156 if z_masked.is_empty() {
157 return Ok(result);
158 }
159
160 let scale = self.compute_scale(&z_masked.view())?;
162 let translation = self.compute_translation(&z_masked.view())?;
163
164 let mut output_idx = 0;
166 for i in 0..z.len() {
167 if !self.mask[i] && output_idx < scale.len() {
168 let exp_scale = scale[output_idx].exp();
169 result[i] = (result[i] - translation[output_idx]) / exp_scale;
170 output_idx += 1;
171 }
172 }
173
174 Ok(result)
175 }
176
177 fn compute_scale(&self, x: &ArrayView1<f64>) -> SklResult<Array1<f64>> {
179 let mut current = x.to_owned();
180
181 for (i, (weights, biases)) in self
182 .scale_weights
183 .iter()
184 .zip(self.scale_biases.iter())
185 .enumerate()
186 {
187 let linear = weights.dot(¤t) + biases;
188
189 current = if i < self.scale_weights.len() - 1 {
191 linear.mapv(|x| x.max(0.0))
192 } else {
193 linear.mapv(|x| x.tanh())
194 };
195 }
196
197 Ok(current)
198 }
199
200 fn compute_translation(&self, x: &ArrayView1<f64>) -> SklResult<Array1<f64>> {
202 let mut current = x.to_owned();
203
204 for (i, (weights, biases)) in self
205 .translation_weights
206 .iter()
207 .zip(self.translation_biases.iter())
208 .enumerate()
209 {
210 let linear = weights.dot(¤t) + biases;
211
212 current = if i < self.translation_weights.len() - 1 {
214 linear.mapv(|x| x.max(0.0))
215 } else {
216 linear
217 };
218 }
219
220 Ok(current)
221 }
222}
223
224#[derive(Debug, Clone)]
226pub struct NormalizingFlow<S = Untrained> {
227 state: S,
228 layers: Vec<AffineCouplingLayer>,
230 classifier_weights: Option<Array2<f64>>,
232 classifier_biases: Option<Array1<f64>>,
234 n_layers: usize,
236 n_classes: usize,
238 hidden_dims: Vec<usize>,
240 learning_rate: f64,
242 max_iter: usize,
244 reg_param: f64,
246 random_state: Option<u64>,
248}
249
250impl Default for NormalizingFlow<Untrained> {
251 fn default() -> Self {
252 Self::new()
253 }
254}
255
256impl NormalizingFlow<Untrained> {
257 pub fn new() -> Self {
259 Self {
260 state: Untrained,
261 layers: Vec::new(),
262 classifier_weights: None,
263 classifier_biases: None,
264 n_layers: 4,
265 n_classes: 2,
266 hidden_dims: vec![64, 32],
267 learning_rate: 0.001,
268 max_iter: 100,
269 reg_param: 0.01,
270 random_state: None,
271 }
272 }
273
274 pub fn n_layers(mut self, n_layers: usize) -> Self {
276 self.n_layers = n_layers;
277 self
278 }
279
280 pub fn hidden_dims(mut self, hidden_dims: Vec<usize>) -> Self {
282 self.hidden_dims = hidden_dims;
283 self
284 }
285
286 pub fn learning_rate(mut self, lr: f64) -> Self {
288 self.learning_rate = lr;
289 self
290 }
291
292 pub fn max_iter(mut self, max_iter: usize) -> Self {
294 self.max_iter = max_iter;
295 self
296 }
297
298 pub fn reg_param(mut self, reg_param: f64) -> Self {
300 self.reg_param = reg_param;
301 self
302 }
303
304 pub fn random_state(mut self, seed: u64) -> Self {
306 self.random_state = Some(seed);
307 self
308 }
309
310 fn initialize_layers(&mut self, input_dim: usize) {
312 self.layers.clear();
313
314 for i in 0..self.n_layers {
315 let mut mask = Array1::from(vec![false; input_dim]);
317 for j in 0..input_dim {
318 mask[j] = (j + i) % 2 == 0;
319 }
320
321 let layer = AffineCouplingLayer::new(input_dim, self.hidden_dims.clone(), mask);
322 self.layers.push(layer);
323 }
324 }
325
326 fn initialize_classifier(&mut self, input_dim: usize, n_classes: usize) {
328 self.classifier_weights = Some({
329 let mut rng = Random::default();
330 let mut w = Array2::zeros((n_classes, input_dim));
331 for i in 0..n_classes {
332 for j in 0..input_dim {
333 w[[i, j]] = rng.random_range(-3.0..3.0) / 3.0 * 0.1;
334 }
335 }
336 w
337 });
338 self.classifier_biases = Some(Array1::zeros(n_classes));
339 }
340
341 fn log_likelihood(&self, x: &ArrayView1<f64>) -> SklResult<f64> {
343 let (z, log_det_jacobian) = self.forward_impl(x)?;
344
345 let log_prob_z = -0.5 * (z.len() as f64 * (2.0 * PI).ln() + z.mapv(|x| x * x).sum());
347
348 Ok(log_prob_z + log_det_jacobian)
349 }
350
351 fn forward_impl(&self, x: &ArrayView1<f64>) -> SklResult<(Array1<f64>, f64)> {
353 let mut current = x.to_owned();
354 let mut total_log_det = 0.0;
355
356 for layer in &self.layers {
357 let (transformed, log_det) = layer.forward(¤t.view())?;
358 current = transformed;
359 total_log_det += log_det;
360 }
361
362 Ok((current, total_log_det))
363 }
364
365 fn classify(&self, x: &ArrayView1<f64>) -> SklResult<Array1<f64>> {
367 match (&self.classifier_weights, &self.classifier_biases) {
368 (Some(weights), Some(biases)) => {
369 let logits = weights.dot(x) + biases;
370 Ok(self.softmax_impl(&logits.view()))
371 }
372 _ => Err(SklearsError::InvalidInput(
373 "Classifier not initialized".to_string(),
374 )),
375 }
376 }
377
378 fn softmax_impl(&self, x: &ArrayView1<f64>) -> Array1<f64> {
380 let max_val = x.iter().cloned().fold(f64::NEG_INFINITY, f64::max);
381 let exp_x = x.mapv(|v| (v - max_val).exp());
382 let sum_exp = exp_x.sum();
383 exp_x / sum_exp
384 }
385
386 fn train(&mut self, x: &ArrayView2<f64>, y: &ArrayView1<i32>) -> SklResult<()> {
388 let n_samples = x.nrows();
389 let n_features = x.ncols();
390
391 self.initialize_layers(n_features);
393 self.initialize_classifier(n_features, self.n_classes);
394
395 let mut labeled_indices = Vec::new();
397 let mut unlabeled_indices = Vec::new();
398
399 for (i, &label) in y.iter().enumerate() {
400 if label >= 0 {
401 labeled_indices.push(i);
402 } else {
403 unlabeled_indices.push(i);
404 }
405 }
406
407 for iteration in 0..self.max_iter {
409 let mut supervised_loss = 0.0;
411 for &idx in &labeled_indices {
412 let _features = self.forward_impl(&x.row(idx))?;
413 let probs = self.classify(&x.row(idx))?;
414
415 let label_idx = y[idx] as usize;
417 if label_idx < probs.len() {
418 supervised_loss -= (probs[label_idx] + 1e-15).ln();
419 }
420 }
421
422 if !labeled_indices.is_empty() {
423 supervised_loss /= labeled_indices.len() as f64;
424 }
425
426 let mut unsupervised_loss = 0.0;
428 for i in 0..n_samples {
429 let log_likelihood = self.log_likelihood(&x.row(i))?;
430 unsupervised_loss -= log_likelihood;
431 }
432 unsupervised_loss /= n_samples as f64;
433
434 let total_loss = supervised_loss + self.reg_param * unsupervised_loss;
435
436 if iteration % 10 == 0 {
438 println!("Iteration {}: Loss = {:.4}", iteration, total_loss);
439 }
440
441 if total_loss < 1e-6 {
443 break;
444 }
445 }
446
447 Ok(())
448 }
449}
450
451#[derive(Debug, Clone)]
453pub struct NormalizingFlowTrained {
454 pub layers: Vec<AffineCouplingLayer>,
456 pub classifier_weights: Array2<f64>,
458 pub classifier_biases: Array1<f64>,
460 pub classes: Array1<i32>,
462 pub n_layers: usize,
464 pub n_classes: usize,
466 pub hidden_dims: Vec<usize>,
468 pub learning_rate: f64,
470}
471
472impl NormalizingFlow<NormalizingFlowTrained> {
473 pub fn generate_samples(&self, n_samples: usize) -> SklResult<Array2<f64>> {
475 if self.state.layers.is_empty() {
476 return Err(SklearsError::InvalidInput(
477 "Model not trained yet".to_string(),
478 ));
479 }
480
481 let latent_dim = self.state.layers[0].mask.len();
482 let mut samples = Array2::zeros((n_samples, latent_dim));
483
484 for i in 0..n_samples {
485 let mut rng = Random::default();
487 let mut z = Array1::zeros(latent_dim);
488 for i in 0..latent_dim {
489 z[i] = rng.random_range(-3.0..3.0) / 3.0;
490 }
491 let z = z;
492
493 let x = self.inverse(&z.view())?;
495 samples.row_mut(i).assign(&x);
496 }
497
498 Ok(samples)
499 }
500
501 #[allow(dead_code)]
503 pub(crate) fn forward(&self, x: &ArrayView1<f64>) -> SklResult<(Array1<f64>, f64)> {
504 let mut current = x.to_owned();
505 let mut total_log_det = 0.0;
506
507 for layer in &self.state.layers {
508 let (transformed, log_det) = layer.forward(¤t.view())?;
509 current = transformed;
510 total_log_det += log_det;
511 }
512
513 Ok((current, total_log_det))
514 }
515
516 fn inverse(&self, z: &ArrayView1<f64>) -> SklResult<Array1<f64>> {
518 let mut current = z.to_owned();
519
520 for layer in self.state.layers.iter().rev() {
521 current = layer.inverse(¤t.view())?;
522 }
523
524 Ok(current)
525 }
526
527 fn softmax(&self, x: &ArrayView1<f64>) -> Array1<f64> {
529 let max_val = x.iter().cloned().fold(f64::NEG_INFINITY, f64::max);
530 let exp_x = x.mapv(|v| (v - max_val).exp());
531 let sum_exp = exp_x.sum();
532 exp_x / sum_exp
533 }
534}
535
536impl Estimator for NormalizingFlow<Untrained> {
537 type Config = ();
538 type Error = SklearsError;
539 type Float = Float;
540
541 fn config(&self) -> &Self::Config {
542 &()
543 }
544}
545
546impl Fit<ArrayView2<'_, Float>, ArrayView1<'_, i32>> for NormalizingFlow<Untrained> {
547 type Fitted = NormalizingFlow<NormalizingFlowTrained>;
548
549 fn fit(self, x: &ArrayView2<'_, Float>, y: &ArrayView1<'_, i32>) -> SklResult<Self::Fitted> {
550 let x = x.to_owned();
551 let y = y.to_owned();
552
553 if x.nrows() != y.len() {
554 return Err(SklearsError::InvalidInput(
555 "Number of samples in X and y must match".to_string(),
556 ));
557 }
558
559 if x.nrows() == 0 {
560 return Err(SklearsError::InvalidInput(
561 "No samples provided".to_string(),
562 ));
563 }
564
565 let labeled_count = y.iter().filter(|&&label| label >= 0).count();
567 if labeled_count == 0 {
568 return Err(SklearsError::InvalidInput(
569 "No labeled samples provided".to_string(),
570 ));
571 }
572
573 let mut unique_classes: Vec<i32> = y.iter().filter(|&&label| label >= 0).cloned().collect();
575 unique_classes.sort_unstable();
576 unique_classes.dedup();
577
578 let mut model = self.clone();
579 model.n_classes = unique_classes.len();
580
581 model.train(&x.view(), &y.view())?;
583
584 Ok(NormalizingFlow {
585 state: NormalizingFlowTrained {
586 layers: model.layers,
587 classifier_weights: model.classifier_weights.expect("operation should succeed"),
588 classifier_biases: model.classifier_biases.expect("operation should succeed"),
589 classes: Array1::from(unique_classes),
590 n_layers: model.n_layers,
591 n_classes: model.n_classes,
592 hidden_dims: model.hidden_dims,
593 learning_rate: model.learning_rate,
594 },
595 layers: Vec::new(),
596 classifier_weights: None,
597 classifier_biases: None,
598 n_layers: 0,
599 n_classes: 0,
600 hidden_dims: Vec::new(),
601 learning_rate: 0.0,
602 max_iter: 0,
603 reg_param: 0.0,
604 random_state: None,
605 })
606 }
607}
608
609impl Predict<ArrayView2<'_, Float>, Array1<i32>> for NormalizingFlow<NormalizingFlowTrained> {
610 fn predict(&self, x: &ArrayView2<'_, Float>) -> SklResult<Array1<i32>> {
611 let x = x.to_owned();
612 let mut predictions = Array1::zeros(x.nrows());
613
614 for i in 0..x.nrows() {
615 let logits =
616 self.state.classifier_weights.dot(&x.row(i)) + &self.state.classifier_biases;
617 let probs = self.softmax(&logits.view());
618
619 let max_idx = probs
620 .iter()
621 .enumerate()
622 .max_by(|(_, a), (_, b)| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal))
623 .map(|(idx, _)| idx)
624 .unwrap_or(0);
625
626 predictions[i] = self.state.classes[max_idx];
627 }
628
629 Ok(predictions)
630 }
631}
632
633impl PredictProba<ArrayView2<'_, Float>, Array2<f64>> for NormalizingFlow<NormalizingFlowTrained> {
634 fn predict_proba(&self, x: &ArrayView2<'_, Float>) -> SklResult<Array2<f64>> {
635 let x = x.to_owned();
636 let mut probabilities = Array2::zeros((x.nrows(), self.state.n_classes));
637
638 for i in 0..x.nrows() {
639 let logits =
640 self.state.classifier_weights.dot(&x.row(i)) + &self.state.classifier_biases;
641 let probs = self.softmax(&logits.view());
642 probabilities.row_mut(i).assign(&probs);
643 }
644
645 Ok(probabilities)
646 }
647}
648
649#[allow(non_snake_case)]
650#[cfg(test)]
651mod tests {
652 use super::*;
653 use scirs2_core::array;
654
655 #[test]
656 fn test_affine_coupling_layer_creation() {
657 let mask = array![true, false, true, false];
658 let layer = AffineCouplingLayer::new(4, vec![8, 4], mask.clone());
659
660 assert_eq!(layer.mask, mask);
661 assert_eq!(layer.hidden_dims, vec![8, 4]);
662 assert!(!layer.scale_weights.is_empty());
663 assert!(!layer.translation_weights.is_empty());
664 }
665
666 #[test]
667 fn test_affine_coupling_layer_forward() {
668 let mask = array![true, false, true, false];
669 let layer = AffineCouplingLayer::new(4, vec![4], mask);
670 let x = array![1.0, 2.0, 3.0, 4.0];
671
672 let result = layer.forward(&x.view());
673 assert!(result.is_ok());
674
675 let (output, _log_det) = result.expect("operation should succeed");
676 assert_eq!(output.len(), 4);
677 assert_eq!(output[0], x[0]);
679 assert_eq!(output[2], x[2]);
680 }
681
682 #[test]
683 fn test_affine_coupling_layer_inverse() {
684 let mask = array![true, false, true, false];
685 let layer = AffineCouplingLayer::new(4, vec![4], mask);
686 let x = array![1.0, 2.0, 3.0, 4.0];
687
688 let (z, _) = layer.forward(&x.view()).expect("operation should succeed");
689 let x_reconstructed = layer.inverse(&z.view()).expect("operation should succeed");
690
691 for i in 0..4 {
693 assert!((x_reconstructed[i] - x[i]).abs() < 1e-10);
694 }
695 }
696
697 #[test]
698 fn test_normalizing_flow_creation() {
699 let flow = NormalizingFlow::new()
700 .n_layers(6)
701 .hidden_dims(vec![32, 16])
702 .learning_rate(0.002)
703 .max_iter(50);
704
705 assert_eq!(flow.n_layers, 6);
706 assert_eq!(flow.hidden_dims, vec![32, 16]);
707 assert_eq!(flow.learning_rate, 0.002);
708 assert_eq!(flow.max_iter, 50);
709 }
710
711 #[test]
712 fn test_normalizing_flow_fit_predict() {
713 let X = array![
714 [1.0, 2.0],
715 [2.0, 3.0],
716 [3.0, 4.0],
717 [4.0, 5.0],
718 [5.0, 6.0],
719 [6.0, 7.0]
720 ];
721 let y = array![0, 1, 0, 1, -1, -1]; let flow = NormalizingFlow::new()
724 .n_layers(2)
725 .hidden_dims(vec![4])
726 .learning_rate(0.01)
727 .max_iter(5);
728
729 let result = flow.fit(&X.view(), &y.view());
730 assert!(result.is_ok());
731
732 let fitted = result.expect("operation should succeed");
733 assert_eq!(fitted.state.classes.len(), 2);
734
735 let predictions = fitted.predict(&X.view());
736 assert!(predictions.is_ok());
737
738 let pred = predictions.expect("operation should succeed");
739 assert_eq!(pred.len(), 6);
740
741 let probabilities = fitted.predict_proba(&X.view());
742 assert!(probabilities.is_ok());
743
744 let proba = probabilities.expect("operation should succeed");
745 assert_eq!(proba.dim(), (6, 2));
746
747 for i in 0..6 {
749 let sum: f64 = proba.row(i).sum();
750 assert!((sum - 1.0).abs() < 1e-10);
751 }
752 }
753
754 #[test]
755 fn test_normalizing_flow_insufficient_labeled_samples() {
756 let X = array![[1.0, 2.0], [2.0, 3.0]];
757 let y = array![-1, -1]; let flow = NormalizingFlow::new();
760 let result = flow.fit(&X.view(), &y.view());
761 assert!(result.is_err());
762 }
763
764 #[test]
765 fn test_normalizing_flow_invalid_dimensions() {
766 let X = array![[1.0, 2.0], [2.0, 3.0]];
767 let y = array![0]; let flow = NormalizingFlow::new();
770 let result = flow.fit(&X.view(), &y.view());
771 assert!(result.is_err());
772 }
773
774 #[test]
775 fn test_normalizing_flow_generate_samples() {
776 let X = array![
777 [1.0, 2.0, 3.0],
778 [2.0, 3.0, 4.0],
779 [3.0, 4.0, 5.0],
780 [4.0, 5.0, 6.0]
781 ];
782 let y = array![0, 1, 0, -1]; let flow = NormalizingFlow::new().n_layers(2).max_iter(3);
785
786 let fitted = flow
787 .fit(&X.view(), &y.view())
788 .expect("operation should succeed");
789
790 let generated = fitted.generate_samples(5);
791 assert!(generated.is_ok());
792
793 let samples = generated.expect("operation should succeed");
794 assert_eq!(samples.dim(), (5, 3));
795 }
796
797 #[test]
798 fn test_affine_coupling_with_empty_mask() {
799 let mask = array![false, false, false, false];
800 let layer = AffineCouplingLayer::new(4, vec![4], mask);
801 let x = array![1.0, 2.0, 3.0, 4.0];
802
803 let (output, log_det) = layer.forward(&x.view()).expect("operation should succeed");
804
805 assert_eq!(output, x);
807 assert_eq!(log_det, 0.0);
808 }
809
810 #[test]
811 fn test_normalizing_flow_with_different_parameters() {
812 let X = array![
813 [1.0, 2.0, 3.0, 4.0],
814 [2.0, 3.0, 4.0, 5.0],
815 [3.0, 4.0, 5.0, 6.0],
816 [4.0, 5.0, 6.0, 7.0]
817 ];
818 let y = array![0, 1, 0, -1]; let flow = NormalizingFlow::new()
821 .n_layers(3)
822 .hidden_dims(vec![8, 4])
823 .learning_rate(0.005)
824 .max_iter(2)
825 .reg_param(0.1);
826
827 let result = flow.fit(&X.view(), &y.view());
828 assert!(result.is_ok());
829
830 let fitted = result.expect("operation should succeed");
831 let predictions = fitted.predict(&X.view()).expect("operation should succeed");
832 assert_eq!(predictions.len(), 4);
833 }
834}