1use scirs2_core::ndarray_ext::{Array1, Array2, ArrayView1, ArrayView2, Axis};
10use scirs2_core::random::Random;
11use sklears_core::{
12 error::{Result as SklResult, SklearsError},
13 traits::{Estimator, Fit, Predict, PredictProba, Untrained},
14 types::Float,
15};
16
17#[derive(Debug, Clone)]
59pub struct LadderNetworks<S = Untrained> {
60 state: S,
61 layer_sizes: Vec<usize>,
62 noise_std: f64,
63 lambda_unsupervised: f64,
64 lambda_supervised: f64,
65 denoising_cost_weights: Vec<f64>,
66 learning_rate: f64,
67 max_iter: usize,
68 batch_size: usize,
69 beta1: f64,
70 beta2: f64,
71 epsilon: f64,
72 random_state: Option<u64>,
73}
74
75impl LadderNetworks<Untrained> {
76 pub fn new() -> Self {
78 Self {
79 state: Untrained,
80 layer_sizes: vec![10, 6, 4, 2], noise_std: 0.3,
82 lambda_unsupervised: 1.0,
83 lambda_supervised: 1.0,
84 denoising_cost_weights: vec![1000.0, 10.0, 0.1, 0.1],
85 learning_rate: 0.002,
86 max_iter: 100,
87 batch_size: 32,
88 beta1: 0.9,
89 beta2: 0.999,
90 epsilon: 1e-8,
91 random_state: None,
92 }
93 }
94
95 pub fn layer_sizes(mut self, sizes: Vec<usize>) -> Self {
97 self.layer_sizes = sizes;
98 self
99 }
100
101 pub fn noise_std(mut self, std: f64) -> Self {
103 self.noise_std = std;
104 self
105 }
106
107 pub fn lambda_unsupervised(mut self, lambda: f64) -> Self {
109 self.lambda_unsupervised = lambda;
110 self
111 }
112
113 pub fn lambda_supervised(mut self, lambda: f64) -> Self {
115 self.lambda_supervised = lambda;
116 self
117 }
118
119 pub fn denoising_cost_weights(mut self, weights: Vec<f64>) -> Self {
121 self.denoising_cost_weights = weights;
122 self
123 }
124
125 pub fn learning_rate(mut self, lr: f64) -> Self {
127 self.learning_rate = lr;
128 self
129 }
130
131 pub fn max_iter(mut self, max_iter: usize) -> Self {
133 self.max_iter = max_iter;
134 self
135 }
136
137 pub fn batch_size(mut self, batch_size: usize) -> Self {
139 self.batch_size = batch_size;
140 self
141 }
142
143 pub fn beta1(mut self, beta1: f64) -> Self {
145 self.beta1 = beta1;
146 self
147 }
148
149 pub fn beta2(mut self, beta2: f64) -> Self {
151 self.beta2 = beta2;
152 self
153 }
154
155 pub fn random_state(mut self, seed: u64) -> Self {
157 self.random_state = Some(seed);
158 self
159 }
160
161 fn initialize_weights(&self, input_size: usize) -> LadderWeights {
162 let mut layer_sizes = self.layer_sizes.clone();
163 layer_sizes[0] = input_size; let n_layers = layer_sizes.len();
166 let mut encoder_weights = Vec::with_capacity(n_layers - 1);
167 let mut encoder_biases = Vec::with_capacity(n_layers - 1);
168 let mut decoder_weights = Vec::with_capacity(n_layers - 1);
169 let mut decoder_biases = Vec::with_capacity(n_layers - 1);
170
171 for i in 0..(n_layers - 1) {
173 let fan_in = layer_sizes[i];
174 let fan_out = layer_sizes[i + 1];
175 let xavier_std = (2.0 / (fan_in + fan_out) as f64).sqrt();
176
177 let mut rng = Random::default();
179 let mut w_enc = Array2::zeros((fan_in, fan_out));
180 for i in 0..fan_in {
181 for j in 0..fan_out {
182 w_enc[[i, j]] = rng.random_range(-3.0..3.0) / 3.0 * xavier_std;
183 }
184 }
185 let b_enc = Array1::zeros(fan_out);
186 encoder_weights.push(w_enc);
187 encoder_biases.push(b_enc);
188
189 let mut w_dec = Array2::zeros((fan_out, fan_in));
191 for i in 0..fan_out {
192 for j in 0..fan_in {
193 w_dec[[i, j]] = rng.random_range(-3.0..3.0) / 3.0 * xavier_std;
194 }
195 }
196 let b_dec = Array1::zeros(fan_in);
197 decoder_weights.push(w_dec);
198 decoder_biases.push(b_dec);
199 }
200
201 LadderWeights {
202 encoder_weights,
203 encoder_biases,
204 decoder_weights,
205 decoder_biases,
206 layer_sizes,
207 }
208 }
209
210 fn add_noise(&self, x: &Array2<f64>) -> Array2<f64> {
211 let mut rng = Random::default();
212 let mut noise = Array2::zeros(x.dim());
213 for i in 0..x.nrows() {
214 for j in 0..x.ncols() {
215 noise[[i, j]] = rng.random_range(-3.0..3.0) / 3.0 * self.noise_std;
216 }
217 }
218 x + &noise
219 }
220
221 fn relu(&self, x: &Array2<f64>) -> Array2<f64> {
222 x.mapv(|v| v.max(0.0))
223 }
224
225 #[allow(dead_code)]
226 pub(crate) fn relu_derivative(&self, x: &Array2<f64>) -> Array2<f64> {
227 x.mapv(|v| if v > 0.0 { 1.0 } else { 0.0 })
228 }
229
230 fn softmax(&self, x: &Array2<f64>) -> Array2<f64> {
231 let mut result = Array2::zeros(x.dim());
232 for (i, row) in x.axis_iter(Axis(0)).enumerate() {
233 let max_val = row.iter().fold(f64::NEG_INFINITY, |a, &b| a.max(b));
234 let exp_row: Array1<f64> = row.mapv(|v| (v - max_val).exp());
235 let sum_exp: f64 = exp_row.sum();
236 let softmax_row = exp_row / sum_exp;
237 result.row_mut(i).assign(&softmax_row);
238 }
239 result
240 }
241
242 fn forward_encoder(
243 &self,
244 x: &Array2<f64>,
245 weights: &LadderWeights,
246 add_noise: bool,
247 ) -> EncoderOutput {
248 let mut activations = Vec::new();
249 let mut noisy_activations = Vec::new();
250 let mut pre_activations = Vec::new();
251
252 let mut current = x.clone();
253 activations.push(current.clone());
254
255 if add_noise {
256 current = self.add_noise(¤t);
257 }
258 noisy_activations.push(current.clone());
259
260 for i in 0..weights.encoder_weights.len() {
261 let z = current.dot(&weights.encoder_weights[i]) + &weights.encoder_biases[i];
263 pre_activations.push(z.clone());
264
265 current = if i == weights.encoder_weights.len() - 1 {
267 z.clone() } else {
269 self.relu(&z)
270 };
271
272 activations.push(current.clone());
273
274 if add_noise && i < weights.encoder_weights.len() - 1 {
276 current = self.add_noise(¤t);
277 }
278 noisy_activations.push(current.clone());
279 }
280
281 EncoderOutput {
282 activations,
283 noisy_activations,
284 pre_activations,
285 }
286 }
287
288 fn forward_decoder(
289 &self,
290 top_activation: &Array2<f64>,
291 weights: &LadderWeights,
292 ) -> Vec<Array2<f64>> {
293 let mut decoder_outputs = Vec::new();
294 let mut current = top_activation.clone();
295
296 for i in (0..weights.decoder_weights.len()).rev() {
298 current = current.dot(&weights.decoder_weights[i]) + &weights.decoder_biases[i];
299
300 if i > 0 {
302 current = self.relu(¤t);
303 }
304
305 decoder_outputs.push(current.clone());
306 }
307
308 decoder_outputs.reverse(); decoder_outputs
310 }
311
312 fn compute_denoising_cost(
313 &self,
314 clean_activations: &[Array2<f64>],
315 reconstructed_activations: &[Array2<f64>],
316 ) -> f64 {
317 let mut total_cost = 0.0;
318
319 for (layer_idx, (clean, reconstructed)) in clean_activations
320 .iter()
321 .zip(reconstructed_activations.iter())
322 .enumerate()
323 {
324 if layer_idx < self.denoising_cost_weights.len() {
325 let diff = clean - reconstructed;
326 let mse = diff
327 .mapv(|x| x * x)
328 .mean()
329 .expect("operation should succeed");
330 total_cost += self.denoising_cost_weights[layer_idx] * mse;
331 }
332 }
333
334 total_cost
335 }
336
337 fn compute_supervised_cost(&self, predictions: &Array2<f64>, targets: &Array2<f64>) -> f64 {
338 let epsilon = 1e-15;
340 let clipped_predictions = predictions.mapv(|x| x.max(epsilon).min(1.0 - epsilon));
341 let log_predictions = clipped_predictions.mapv(|x| x.ln());
342
343 let mut cost = 0.0;
344 for i in 0..targets.nrows() {
345 for j in 0..targets.ncols() {
346 cost -= targets[[i, j]] * log_predictions[[i, j]];
347 }
348 }
349
350 cost / targets.nrows() as f64
351 }
352
353 fn create_target_matrix(&self, y: &Array1<i32>, classes: &[i32]) -> Array2<f64> {
354 let n_samples = y.len();
355 let n_classes = classes.len();
356 let mut targets = Array2::zeros((n_samples, n_classes));
357
358 for (i, &label) in y.iter().enumerate() {
359 if label != -1 {
360 if let Some(class_idx) = classes.iter().position(|&c| c == label) {
361 targets[[i, class_idx]] = 1.0;
362 }
363 }
364 }
365
366 targets
367 }
368
369 fn update_weights_adam(
370 &self,
371 weights: &mut LadderWeights,
372 gradients: &LadderWeights,
373 momentum: &mut LadderWeights,
374 velocity: &mut LadderWeights,
375 iteration: usize,
376 ) {
377 let beta1_t = self.beta1.powi(iteration as i32);
378 let beta2_t = self.beta2.powi(iteration as i32);
379 let alpha_t = self.learning_rate * (1.0 - beta2_t).sqrt() / (1.0 - beta1_t);
380
381 for i in 0..weights.encoder_weights.len() {
383 momentum.encoder_weights[i] = self.beta1 * &momentum.encoder_weights[i]
385 + (1.0 - self.beta1) * &gradients.encoder_weights[i];
386 velocity.encoder_weights[i] = self.beta2 * &velocity.encoder_weights[i]
387 + (1.0 - self.beta2) * gradients.encoder_weights[i].mapv(|x| x * x);
388
389 let momentum_corrected = &momentum.encoder_weights[i] / (1.0 - beta1_t);
391 let velocity_corrected = &velocity.encoder_weights[i] / (1.0 - beta2_t);
392 let update = momentum_corrected / velocity_corrected.mapv(|x| x.sqrt() + self.epsilon);
393 weights.encoder_weights[i] = &weights.encoder_weights[i] - alpha_t * update;
394
395 momentum.encoder_biases[i] = self.beta1 * &momentum.encoder_biases[i]
397 + (1.0 - self.beta1) * &gradients.encoder_biases[i];
398 velocity.encoder_biases[i] = self.beta2 * &velocity.encoder_biases[i]
399 + (1.0 - self.beta2) * gradients.encoder_biases[i].mapv(|x| x * x);
400
401 let momentum_corrected_b = &momentum.encoder_biases[i] / (1.0 - beta1_t);
402 let velocity_corrected_b = &velocity.encoder_biases[i] / (1.0 - beta2_t);
403 let update_b =
404 momentum_corrected_b / velocity_corrected_b.mapv(|x| x.sqrt() + self.epsilon);
405 weights.encoder_biases[i] = &weights.encoder_biases[i] - alpha_t * update_b;
406 }
407
408 for i in 0..weights.decoder_weights.len() {
410 momentum.decoder_weights[i] = self.beta1 * &momentum.decoder_weights[i]
411 + (1.0 - self.beta1) * &gradients.decoder_weights[i];
412 velocity.decoder_weights[i] = self.beta2 * &velocity.decoder_weights[i]
413 + (1.0 - self.beta2) * gradients.decoder_weights[i].mapv(|x| x * x);
414
415 let momentum_corrected = &momentum.decoder_weights[i] / (1.0 - beta1_t);
416 let velocity_corrected = &velocity.decoder_weights[i] / (1.0 - beta2_t);
417 let update = momentum_corrected / velocity_corrected.mapv(|x| x.sqrt() + self.epsilon);
418 weights.decoder_weights[i] = &weights.decoder_weights[i] - alpha_t * update;
419
420 momentum.decoder_biases[i] = self.beta1 * &momentum.decoder_biases[i]
421 + (1.0 - self.beta1) * &gradients.decoder_biases[i];
422 velocity.decoder_biases[i] = self.beta2 * &velocity.decoder_biases[i]
423 + (1.0 - self.beta2) * gradients.decoder_biases[i].mapv(|x| x * x);
424
425 let momentum_corrected_b = &momentum.decoder_biases[i] / (1.0 - beta1_t);
426 let velocity_corrected_b = &velocity.decoder_biases[i] / (1.0 - beta2_t);
427 let update_b =
428 momentum_corrected_b / velocity_corrected_b.mapv(|x| x.sqrt() + self.epsilon);
429 weights.decoder_biases[i] = &weights.decoder_biases[i] - alpha_t * update_b;
430 }
431 }
432
433 fn compute_gradients(
434 &self,
435 x: &Array2<f64>,
436 targets: &Array2<f64>,
437 weights: &LadderWeights,
438 ) -> SklResult<LadderWeights> {
439 let _noisy_encoder_output = self.forward_encoder(x, weights, true);
441
442 let mut gradient_weights = LadderWeights {
444 encoder_weights: weights
445 .encoder_weights
446 .iter()
447 .map(|w| Array2::zeros(w.dim()))
448 .collect(),
449 encoder_biases: weights
450 .encoder_biases
451 .iter()
452 .map(|b| Array1::zeros(b.len()))
453 .collect(),
454 decoder_weights: weights
455 .decoder_weights
456 .iter()
457 .map(|w| Array2::zeros(w.dim()))
458 .collect(),
459 decoder_biases: weights
460 .decoder_biases
461 .iter()
462 .map(|b| Array1::zeros(b.len()))
463 .collect(),
464 layer_sizes: weights.layer_sizes.clone(),
465 };
466
467 let delta = 1e-5;
470
471 for i in 0..weights.encoder_weights.len() {
473 for j in 0..weights.encoder_weights[i].nrows() {
474 for k in 0..weights.encoder_weights[i].ncols() {
475 let mut weights_plus = weights.clone();
477 let mut weights_minus = weights.clone();
478 weights_plus.encoder_weights[i][[j, k]] += delta;
479 weights_minus.encoder_weights[i][[j, k]] -= delta;
480
481 let cost_plus = self.compute_total_cost(x, targets, &weights_plus)?;
483 let cost_minus = self.compute_total_cost(x, targets, &weights_minus)?;
484
485 gradient_weights.encoder_weights[i][[j, k]] =
487 (cost_plus - cost_minus) / (2.0 * delta);
488 }
489 }
490 }
491
492 Ok(gradient_weights)
493 }
494
495 fn compute_total_cost(
496 &self,
497 x: &Array2<f64>,
498 targets: &Array2<f64>,
499 weights: &LadderWeights,
500 ) -> SklResult<f64> {
501 let clean_encoder_output = self.forward_encoder(x, weights, false);
503 let noisy_encoder_output = self.forward_encoder(x, weights, true);
504 let decoder_outputs = self.forward_decoder(
505 noisy_encoder_output
506 .activations
507 .last()
508 .expect("operation should succeed"),
509 weights,
510 );
511
512 let predictions = self.softmax(
514 noisy_encoder_output
515 .activations
516 .last()
517 .expect("operation should succeed"),
518 );
519 let supervised_cost = self.compute_supervised_cost(&predictions, targets);
520
521 let denoising_cost =
523 self.compute_denoising_cost(&clean_encoder_output.activations, &decoder_outputs);
524
525 let total_cost =
526 self.lambda_supervised * supervised_cost + self.lambda_unsupervised * denoising_cost;
527 Ok(total_cost)
528 }
529}
530
531#[derive(Debug, Clone)]
532pub struct LadderWeights {
533 pub encoder_weights: Vec<Array2<f64>>,
535 pub encoder_biases: Vec<Array1<f64>>,
537 pub decoder_weights: Vec<Array2<f64>>,
539 pub decoder_biases: Vec<Array1<f64>>,
541 pub layer_sizes: Vec<usize>,
543}
544
545#[derive(Debug)]
546pub struct EncoderOutput {
547 pub activations: Vec<Array2<f64>>,
549 pub noisy_activations: Vec<Array2<f64>>,
551 pub pre_activations: Vec<Array2<f64>>,
553}
554
555impl Default for LadderNetworks<Untrained> {
556 fn default() -> Self {
557 Self::new()
558 }
559}
560
561impl Estimator for LadderNetworks<Untrained> {
562 type Config = ();
563 type Error = SklearsError;
564 type Float = Float;
565
566 fn config(&self) -> &Self::Config {
567 &()
568 }
569}
570
571impl Fit<ArrayView2<'_, Float>, ArrayView1<'_, i32>> for LadderNetworks<Untrained> {
572 type Fitted = LadderNetworks<LadderNetworksTrained>;
573
574 #[allow(non_snake_case)]
575 fn fit(self, X: &ArrayView2<'_, Float>, y: &ArrayView1<'_, i32>) -> SklResult<Self::Fitted> {
576 let X = X.to_owned();
577 let y = y.to_owned();
578 let (_n_samples, n_features) = X.dim();
579
580 let mut classes = std::collections::HashSet::new();
582 for &label in y.iter() {
583 if label != -1 {
584 classes.insert(label);
585 }
586 }
587
588 if classes.is_empty() {
589 return Err(SklearsError::InvalidInput(
590 "No labeled samples provided".to_string(),
591 ));
592 }
593
594 let classes: Vec<i32> = classes.into_iter().collect();
595 let n_classes = classes.len();
596
597 let mut layer_sizes = self.layer_sizes.clone();
599 layer_sizes[0] = n_features;
600 let last_idx = layer_sizes.len() - 1;
601 layer_sizes[last_idx] = n_classes;
602
603 let mut weights = self.initialize_weights(n_features);
605 weights.layer_sizes = layer_sizes;
606
607 let mut momentum = weights.clone();
609 let mut velocity = weights.clone();
610
611 for i in 0..momentum.encoder_weights.len() {
613 momentum.encoder_weights[i].fill(0.0);
614 momentum.encoder_biases[i].fill(0.0);
615 velocity.encoder_weights[i].fill(0.0);
616 velocity.encoder_biases[i].fill(0.0);
617 }
618 for i in 0..momentum.decoder_weights.len() {
619 momentum.decoder_weights[i].fill(0.0);
620 momentum.decoder_biases[i].fill(0.0);
621 velocity.decoder_weights[i].fill(0.0);
622 velocity.decoder_biases[i].fill(0.0);
623 }
624
625 let targets = self.create_target_matrix(&y, &classes);
627
628 for iteration in 1..=self.max_iter {
630 let total_cost = self.compute_total_cost(&X, &targets, &weights)?;
634
635 if iteration % 10 == 0 {
636 println!("Iteration {}: Total cost = {:.6}", iteration, total_cost);
637 }
638
639 let gradients = self.compute_gradients(&X, &targets, &weights)?;
641
642 self.update_weights_adam(
644 &mut weights,
645 &gradients,
646 &mut momentum,
647 &mut velocity,
648 iteration,
649 );
650 }
651
652 let final_encoder_output = self.forward_encoder(&X, &weights, false);
654 let final_predictions = self.softmax(
655 final_encoder_output
656 .activations
657 .last()
658 .expect("operation should succeed"),
659 );
660
661 Ok(LadderNetworks {
662 state: LadderNetworksTrained {
663 X_train: X,
664 y_train: y,
665 classes: Array1::from(classes),
666 weights,
667 label_distributions: final_predictions,
668 },
669 layer_sizes: self.layer_sizes,
670 noise_std: self.noise_std,
671 lambda_unsupervised: self.lambda_unsupervised,
672 lambda_supervised: self.lambda_supervised,
673 denoising_cost_weights: self.denoising_cost_weights,
674 learning_rate: self.learning_rate,
675 max_iter: self.max_iter,
676 batch_size: self.batch_size,
677 beta1: self.beta1,
678 beta2: self.beta2,
679 epsilon: self.epsilon,
680 random_state: self.random_state,
681 })
682 }
683}
684
685impl LadderNetworks<LadderNetworksTrained> {
686 fn forward_encoder(
687 &self,
688 x: &Array2<f64>,
689 weights: &LadderWeights,
690 add_noise: bool,
691 ) -> EncoderOutput {
692 let mut activations = Vec::new();
693 let mut noisy_activations = Vec::new();
694 let mut pre_activations = Vec::new();
695
696 let mut current = x.clone();
697 activations.push(current.clone());
698
699 if add_noise {
700 current = self.add_noise(¤t);
701 }
702 noisy_activations.push(current.clone());
703
704 for i in 0..weights.encoder_weights.len() {
705 let z = current.dot(&weights.encoder_weights[i]) + &weights.encoder_biases[i];
707 pre_activations.push(z.clone());
708
709 current = if i == weights.encoder_weights.len() - 1 {
711 z.clone() } else {
713 self.relu(&z)
714 };
715
716 activations.push(current.clone());
717
718 if add_noise && i < weights.encoder_weights.len() - 1 {
720 current = self.add_noise(¤t);
721 }
722 noisy_activations.push(current.clone());
723 }
724
725 EncoderOutput {
726 activations,
727 noisy_activations,
728 pre_activations,
729 }
730 }
731
732 fn add_noise(&self, x: &Array2<f64>) -> Array2<f64> {
733 let mut rng = Random::default();
734 let mut noise = Array2::zeros(x.dim());
735 for i in 0..x.nrows() {
736 for j in 0..x.ncols() {
737 noise[[i, j]] = rng.random_range(-3.0..3.0) / 3.0 * self.noise_std;
738 }
739 }
740 x + &noise
741 }
742
743 fn relu(&self, x: &Array2<f64>) -> Array2<f64> {
744 x.mapv(|v| v.max(0.0))
745 }
746
747 fn softmax(&self, x: &Array2<f64>) -> Array2<f64> {
748 let mut result = Array2::zeros(x.dim());
749 for (i, row) in x.axis_iter(Axis(0)).enumerate() {
750 let max_val = row.iter().fold(f64::NEG_INFINITY, |a, &b| a.max(b));
751 let exp_row: Array1<f64> = row.mapv(|v| (v - max_val).exp());
752 let sum_exp: f64 = exp_row.sum();
753 let softmax_row = exp_row / sum_exp;
754 result.row_mut(i).assign(&softmax_row);
755 }
756 result
757 }
758}
759
760impl Predict<ArrayView2<'_, Float>, Array1<i32>> for LadderNetworks<LadderNetworksTrained> {
761 #[allow(non_snake_case)]
762 fn predict(&self, X: &ArrayView2<'_, Float>) -> SklResult<Array1<i32>> {
763 let X = X.to_owned();
764 let encoder_output = self.forward_encoder(&X, &self.state.weights, false);
765 let predictions = self.softmax(
766 encoder_output
767 .activations
768 .last()
769 .expect("operation should succeed"),
770 );
771
772 let mut result = Array1::zeros(X.nrows());
773 for i in 0..X.nrows() {
774 let max_idx = predictions
775 .row(i)
776 .iter()
777 .enumerate()
778 .max_by(|a, b| a.1.partial_cmp(b.1).expect("operation should succeed"))
779 .expect("operation should succeed")
780 .0;
781 result[i] = self.state.classes[max_idx];
782 }
783
784 Ok(result)
785 }
786}
787
788impl PredictProba<ArrayView2<'_, Float>, Array2<f64>> for LadderNetworks<LadderNetworksTrained> {
789 #[allow(non_snake_case)]
790 fn predict_proba(&self, X: &ArrayView2<'_, Float>) -> SklResult<Array2<f64>> {
791 let X = X.to_owned();
792 let encoder_output = self.forward_encoder(&X, &self.state.weights, false);
793 let predictions = self.softmax(
794 encoder_output
795 .activations
796 .last()
797 .expect("operation should succeed"),
798 );
799 Ok(predictions)
800 }
801}
802
803#[derive(Debug, Clone)]
805#[allow(non_snake_case)] pub struct LadderNetworksTrained {
807 pub X_train: Array2<f64>,
809 pub y_train: Array1<i32>,
811 pub classes: Array1<i32>,
813 pub weights: LadderWeights,
815 pub label_distributions: Array2<f64>,
817}
818
819#[allow(non_snake_case)]
820#[cfg(test)]
821mod tests {
822 use super::*;
823 use scirs2_core::array;
824
825 #[test]
826 #[allow(non_snake_case)]
827 fn test_ladder_networks_basic() {
828 let X = array![
829 [1.0, 2.0, 0.5],
830 [2.0, 3.0, 1.0],
831 [3.0, 4.0, 1.5],
832 [4.0, 5.0, 2.0],
833 [5.0, 6.0, 2.5],
834 [6.0, 7.0, 3.0]
835 ];
836 let y = array![0, 1, 0, 1, -1, -1]; let ln = LadderNetworks::new()
839 .layer_sizes(vec![3, 4, 2])
840 .noise_std(0.1)
841 .lambda_unsupervised(0.5)
842 .lambda_supervised(1.0)
843 .max_iter(5); let fitted = ln
845 .fit(&X.view(), &y.view())
846 .expect("operation should succeed");
847
848 let predictions = fitted.predict(&X.view()).expect("operation should succeed");
849 assert_eq!(predictions.len(), 6);
850
851 let probas = fitted
852 .predict_proba(&X.view())
853 .expect("operation should succeed");
854 assert_eq!(probas.dim(), (6, 2));
855
856 for i in 0..6 {
858 let sum: f64 = probas.row(i).sum();
859 assert!((sum - 1.0).abs() < 0.1); }
861 }
862
863 #[test]
864 fn test_ladder_networks_initialization() {
865 let ln = LadderNetworks::new()
866 .layer_sizes(vec![4, 6, 3])
867 .noise_std(0.2);
868
869 let weights = ln.initialize_weights(4);
870
871 assert_eq!(weights.layer_sizes, vec![4, 6, 3]);
873 assert_eq!(weights.encoder_weights.len(), 2);
874 assert_eq!(weights.encoder_weights[0].dim(), (4, 6));
875 assert_eq!(weights.encoder_weights[1].dim(), (6, 3));
876
877 assert_eq!(weights.decoder_weights.len(), 2);
878 assert_eq!(weights.decoder_weights[0].dim(), (6, 4));
879 assert_eq!(weights.decoder_weights[1].dim(), (3, 6));
880 }
881
882 #[test]
883 fn test_ladder_networks_noise_addition() {
884 let ln = LadderNetworks::new().noise_std(0.1);
885 let x = array![[1.0, 2.0], [3.0, 4.0]];
886
887 let noisy_x = ln.add_noise(&x);
888
889 assert_eq!(noisy_x.dim(), x.dim());
891
892 let diff = (&noisy_x - &x).mapv(|v| v.abs()).sum();
894 assert!(diff > 0.0);
895 }
896
897 #[test]
898 fn test_ladder_networks_activations() {
899 let ln = LadderNetworks::new();
900
901 let x = array![[-1.0, 0.0, 1.0, 2.0]];
903 let relu_result = ln.relu(&x);
904 let expected = array![[0.0, 0.0, 1.0, 2.0]];
905
906 for i in 0..x.ncols() {
907 assert!((relu_result[[0, i]] - expected[[0, i]]).abs() < 1e-10);
908 }
909
910 let relu_deriv = ln.relu_derivative(&x);
912 let expected_deriv = array![[0.0, 0.0, 1.0, 1.0]];
913
914 for i in 0..x.ncols() {
915 assert!((relu_deriv[[0, i]] - expected_deriv[[0, i]]).abs() < 1e-10);
916 }
917 }
918
919 #[test]
920 fn test_ladder_networks_softmax() {
921 let ln = LadderNetworks::new();
922 let x = array![[1.0, 2.0, 3.0], [0.0, 0.0, 0.0]];
923
924 let softmax_result = ln.softmax(&x);
925
926 assert_eq!(softmax_result.dim(), x.dim());
928
929 for i in 0..x.nrows() {
931 let sum: f64 = softmax_result.row(i).sum();
932 assert!((sum - 1.0).abs() < 1e-10);
933 }
934
935 for i in 0..x.nrows() {
937 for j in 0..x.ncols() {
938 assert!(softmax_result[[i, j]] > 0.0);
939 }
940 }
941 }
942
943 #[test]
944 fn test_ladder_networks_forward_encoder() {
945 let ln = LadderNetworks::new();
946 let weights = ln.initialize_weights(2);
947 let x = array![[1.0, 2.0], [3.0, 4.0]];
948
949 let encoder_output = ln.forward_encoder(&x, &weights, false);
950
951 assert_eq!(encoder_output.activations.len(), weights.layer_sizes.len());
953 assert_eq!(
954 encoder_output.noisy_activations.len(),
955 weights.layer_sizes.len()
956 );
957
958 assert_eq!(encoder_output.activations[0].dim(), x.dim());
960 }
961
962 #[test]
963 fn test_ladder_networks_target_matrix() {
964 let ln = LadderNetworks::new();
965 let y = array![0, 1, -1, 0]; let classes = vec![0, 1];
967
968 let targets = ln.create_target_matrix(&y, &classes);
969
970 assert_eq!(targets.dim(), (4, 2));
972
973 assert_eq!(targets[[0, 0]], 1.0); assert_eq!(targets[[0, 1]], 0.0);
976 assert_eq!(targets[[1, 0]], 0.0); assert_eq!(targets[[1, 1]], 1.0);
978
979 assert_eq!(targets[[2, 0]], 0.0);
981 assert_eq!(targets[[2, 1]], 0.0);
982 }
983}