1use crate::kernels::Kernel;
41use scirs2_core::ndarray::{Array1, Array2};
42use scirs2_core::random::{Random, RngExt}; use sklears_core::error::{Result as SklResult, SklearsError};
44use sklears_core::traits::{Estimator, Fit, Predict};
45
46#[derive(Debug, Clone)]
48pub struct Untrained;
49
50#[derive(Debug, Clone)]
52pub struct Trained {
53 pub layers: Vec<DeepGPLayer>,
54 pub inducing_points: Vec<Array2<f64>>,
55 pub variational_means: Vec<Array1<f64>>,
56 pub variational_vars: Vec<Array1<f64>>,
57 pub training_data: (Array2<f64>, Array1<f64>),
58 pub log_likelihood: f64,
59}
60
61#[derive(Debug, Clone)]
63pub struct DeepGPLayer {
64 pub kernel: Box<dyn Kernel>,
65 pub inducing_points: Array2<f64>,
66 pub variational_mean: Array1<f64>,
67 pub variational_var: Array1<f64>,
68 pub output_dim: usize,
69 pub input_dim: usize,
70 pub use_residual: bool,
72 pub residual_weight: f64,
74 pub use_attention: bool,
76 pub attention_query_weights: Option<Array2<f64>>,
78 pub attention_key_weights: Option<Array2<f64>>,
80 pub attention_value_weights: Option<Array2<f64>>,
82 pub n_attention_heads: usize,
84}
85
86impl DeepGPLayer {
87 pub fn new(kernel: Box<dyn Kernel>, inducing_points: Array2<f64>, output_dim: usize) -> Self {
89 let n_inducing = inducing_points.nrows();
90 let input_dim = inducing_points.ncols();
91
92 Self {
93 kernel,
94 inducing_points,
95 variational_mean: Array1::zeros(n_inducing),
96 variational_var: Array1::ones(n_inducing),
97 output_dim,
98 input_dim,
99 use_residual: false,
100 residual_weight: 0.0,
101 use_attention: false,
102 attention_query_weights: None,
103 attention_key_weights: None,
104 attention_value_weights: None,
105 n_attention_heads: 1,
106 }
107 }
108
109 pub fn with_residual(mut self, weight: f64) -> Self {
111 self.use_residual = true;
112 self.residual_weight = weight.clamp(0.0, 1.0);
113 self
114 }
115
116 pub fn with_attention(mut self, n_heads: usize, attention_dim: usize) -> Self {
118 use scirs2_core::random::rngs::StdRng;
119 use scirs2_core::random::{RngExt, SeedableRng};
120
121 self.use_attention = true;
122 self.n_attention_heads = n_heads.max(1);
123
124 let mut rng = StdRng::seed_from_u64(42);
126 let scale = (1.0 / (self.input_dim as f64)).sqrt();
127
128 let mut query_weights = Array2::zeros((self.input_dim, attention_dim));
130 let mut key_weights = Array2::zeros((self.input_dim, attention_dim));
131 let mut value_weights = Array2::zeros((self.input_dim, attention_dim));
132
133 for i in 0..self.input_dim {
134 for j in 0..attention_dim {
135 query_weights[[i, j]] = rng.random_range(-scale..scale);
136 key_weights[[i, j]] = rng.random_range(-scale..scale);
137 value_weights[[i, j]] = rng.random_range(-scale..scale);
138 }
139 }
140
141 self.attention_query_weights = Some(query_weights);
142 self.attention_key_weights = Some(key_weights);
143 self.attention_value_weights = Some(value_weights);
144
145 self
146 }
147
148 fn compute_attention(&self, inputs: &Array2<f64>) -> SklResult<Array2<f64>> {
150 if !self.use_attention {
151 return Ok(inputs.clone());
152 }
153
154 let query_weights = self.attention_query_weights.as_ref().ok_or_else(|| {
155 SklearsError::InvalidInput("Attention weights not initialized".to_string())
156 })?;
157 let key_weights = self.attention_key_weights.as_ref().ok_or_else(|| {
158 SklearsError::InvalidInput("Attention weights not initialized".to_string())
159 })?;
160 let value_weights = self.attention_value_weights.as_ref().ok_or_else(|| {
161 SklearsError::InvalidInput("Attention weights not initialized".to_string())
162 })?;
163
164 let queries = inputs.dot(query_weights); let keys = inputs.dot(key_weights); let values = inputs.dot(value_weights); let n_points = queries.nrows();
170 let attention_dim = queries.ncols();
171
172 let scale = (attention_dim as f64).sqrt();
174 let mut attention_scores = Array2::zeros((n_points, n_points));
175
176 for i in 0..n_points {
177 for j in 0..n_points {
178 let mut score = 0.0;
179 for k in 0..attention_dim {
180 score += queries[[i, k]] * keys[[j, k]];
181 }
182 attention_scores[[i, j]] = score / scale;
183 }
184 }
185
186 let mut attention_weights = Array2::zeros((n_points, n_points));
188 for i in 0..n_points {
189 let mut row_sum = 0.0;
190 let max_score = attention_scores
191 .row(i)
192 .iter()
193 .fold(f64::NEG_INFINITY, |a, &b| a.max(b));
194
195 for j in 0..n_points {
196 let exp_score = (attention_scores[[i, j]] - max_score).exp();
197 attention_weights[[i, j]] = exp_score;
198 row_sum += exp_score;
199 }
200
201 for j in 0..n_points {
203 attention_weights[[i, j]] /= row_sum;
204 }
205 }
206
207 let attended_values = attention_weights.dot(&values);
209
210 Ok(attended_values)
211 }
212
213 pub fn forward(&self, inputs: &Array2<f64>) -> SklResult<(Array2<f64>, Array2<f64>)> {
215 let n_points = inputs.nrows();
216 let n_inducing = self.inducing_points.nrows();
217
218 let attended_inputs = if self.use_attention {
220 self.compute_attention(inputs)?
221 } else {
222 inputs.clone()
223 };
224
225 let k_uf = self
227 .kernel
228 .compute_kernel_matrix(&self.inducing_points, Some(&attended_inputs))?;
229 let k_uu = self
230 .kernel
231 .compute_kernel_matrix(&self.inducing_points, None)?;
232
233 let mut k_uu_reg = k_uu.clone();
235 for i in 0..n_inducing {
236 k_uu_reg[[i, i]] += 1e-6;
237 }
238
239 let k_uu_inv = self.matrix_inverse(&k_uu_reg)?;
241 let alpha = k_uu_inv.dot(&self.variational_mean);
242
243 let mean_pred = k_uf.t().dot(&alpha);
244
245 let mut var_pred = Array2::zeros((n_points, self.output_dim));
247 for i in 0..n_points {
248 for j in 0..self.output_dim {
249 var_pred[[i, j]] = 1.0; }
251 }
252
253 let mut mean_matrix = Array2::zeros((n_points, self.output_dim));
255 for i in 0..n_points {
256 for j in 0..self.output_dim {
257 mean_matrix[[i, j]] = mean_pred[i];
258 }
259 }
260
261 if self.use_residual
263 && self.input_dim == self.output_dim
264 && inputs.ncols() == self.output_dim
265 {
266 for i in 0..n_points {
269 for j in 0..self.output_dim {
270 mean_matrix[[i, j]] = (1.0 - self.residual_weight) * mean_matrix[[i, j]]
271 + self.residual_weight * inputs[[i, j]];
272 }
273 }
274 }
275
276 Ok((mean_matrix, var_pred))
277 }
278
279 fn matrix_inverse(&self, matrix: &Array2<f64>) -> SklResult<Array2<f64>> {
281 let n = matrix.nrows();
282 let mut inv = Array2::eye(n);
283
284 for i in 0..n {
286 if matrix[[i, i]] > 1e-12 {
287 inv[[i, i]] = 1.0 / matrix[[i, i]];
288 } else {
289 inv[[i, i]] = 1e12; }
291 }
292
293 Ok(inv)
294 }
295}
296
297#[derive(Debug, Clone)]
302pub struct DeepGaussianProcessRegressor<S = Untrained> {
303 layers: Vec<(Box<dyn Kernel>, usize)>, learning_rate: f64,
305 max_epochs: usize,
306 convergence_threshold: f64,
307 jitter: f64,
308 random_seed: Option<u64>,
309 data_batch_size: Option<usize>,
311 inducing_batch_size: Option<usize>,
313 use_doubly_stochastic: bool,
315 use_residual_connections: bool,
317 residual_weight: f64,
319 use_attention: bool,
321 n_attention_heads: usize,
323 attention_dim: usize,
325 _state: S,
326}
327
328impl Default for DeepGaussianProcessRegressor<Untrained> {
329 fn default() -> Self {
330 Self::new()
331 }
332}
333
334impl DeepGaussianProcessRegressor<Untrained> {
335 pub fn new() -> Self {
337 Self {
338 layers: Vec::new(),
339 learning_rate: 0.01,
340 max_epochs: 100,
341 convergence_threshold: 1e-6,
342 jitter: 1e-6,
343 random_seed: None,
344 data_batch_size: None,
345 inducing_batch_size: None,
346 use_doubly_stochastic: false,
347 use_residual_connections: false,
348 residual_weight: 0.5,
349 use_attention: false,
350 n_attention_heads: 1,
351 attention_dim: 64,
352 _state: Untrained,
353 }
354 }
355
356 pub fn builder() -> DeepGaussianProcessRegressorBuilder {
358 DeepGaussianProcessRegressorBuilder::new()
359 }
360
361 pub fn add_layer(mut self, kernel: Box<dyn Kernel>, n_inducing: usize) -> Self {
363 self.layers.push((kernel, n_inducing));
364 self
365 }
366
367 pub fn with_learning_rate(mut self, learning_rate: f64) -> Self {
369 self.learning_rate = learning_rate;
370 self
371 }
372
373 pub fn with_max_epochs(mut self, max_epochs: usize) -> Self {
375 self.max_epochs = max_epochs;
376 self
377 }
378
379 pub fn with_convergence_threshold(mut self, threshold: f64) -> Self {
381 self.convergence_threshold = threshold;
382 self
383 }
384
385 pub fn with_jitter(mut self, jitter: f64) -> Self {
387 self.jitter = jitter;
388 self
389 }
390
391 pub fn with_random_seed(mut self, seed: u64) -> Self {
393 self.random_seed = Some(seed);
394 self
395 }
396
397 fn initialize_inducing_points<R: RngExt>(
399 &self,
400 x_train: &Array2<f64>,
401 n_inducing: usize,
402 input_dim: usize,
403 rng: &mut R,
404 ) -> Array2<f64> {
405 if n_inducing >= x_train.nrows() {
406 return x_train.clone();
408 }
409
410 let mut inducing_points = Array2::zeros((n_inducing, input_dim));
412 for i in 0..n_inducing {
413 let idx = rng.random_range(0..x_train.nrows());
414 for j in 0..input_dim {
415 inducing_points[[i, j]] = x_train[[idx, j]];
416 }
417 }
418
419 inducing_points
420 }
421
422 pub fn fit(
424 self,
425 x_train: &Array2<f64>,
426 y_train: &Array1<f64>,
427 ) -> SklResult<DeepGaussianProcessRegressor<Trained>> {
428 if x_train.nrows() != y_train.len() {
429 return Err(SklearsError::DimensionMismatch {
430 expected: x_train.nrows(),
431 actual: y_train.len(),
432 });
433 }
434
435 if self.layers.is_empty() {
436 return Err(SklearsError::InvalidInput(
437 "At least one layer must be specified".to_string(),
438 ));
439 }
440
441 let mut rng = if let Some(seed) = self.random_seed {
442 Random::seed(seed)
443 } else {
444 Random::seed(42)
445 };
446
447 let mut deep_layers = Vec::new();
448 let mut all_inducing_points = Vec::new();
449 let mut all_variational_means = Vec::new();
450 let mut all_variational_vars = Vec::new();
451
452 let current_input = x_train.clone();
453 let mut current_input_dim = x_train.ncols();
454
455 for (layer_idx, (kernel, n_inducing)) in self.layers.iter().enumerate() {
457 let output_dim = if layer_idx == self.layers.len() - 1 {
458 1 } else {
460 current_input_dim };
462
463 let inducing_points = self.initialize_inducing_points(
464 ¤t_input,
465 *n_inducing,
466 current_input_dim,
467 &mut rng,
468 );
469
470 let layer = DeepGPLayer::new(kernel.clone_box(), inducing_points.clone(), output_dim);
471
472 all_inducing_points.push(inducing_points);
473 all_variational_means.push(layer.variational_mean.clone());
474 all_variational_vars.push(layer.variational_var.clone());
475 deep_layers.push(layer);
476
477 current_input_dim = output_dim;
481 }
482
483 let mut best_log_likelihood = f64::NEG_INFINITY;
485 let mut current_log_likelihood;
486
487 for _epoch in 0..self.max_epochs {
488 current_log_likelihood = self.compute_simplified_elbo(x_train, y_train, &deep_layers);
490
491 if (current_log_likelihood - best_log_likelihood).abs() < self.convergence_threshold {
493 break;
494 }
495
496 best_log_likelihood = current_log_likelihood;
497
498 for layer in &mut deep_layers {
501 for i in 0..layer.variational_mean.len() {
502 layer.variational_mean[i] += rng.random_range(-0.01..0.01) * self.learning_rate;
503 }
504 }
505 }
506
507 Ok(DeepGaussianProcessRegressor {
508 layers: self.layers,
509 learning_rate: self.learning_rate,
510 max_epochs: self.max_epochs,
511 convergence_threshold: self.convergence_threshold,
512 jitter: self.jitter,
513 random_seed: self.random_seed,
514 data_batch_size: self.data_batch_size,
515 inducing_batch_size: self.inducing_batch_size,
516 use_doubly_stochastic: self.use_doubly_stochastic,
517 use_residual_connections: self.use_residual_connections,
518 residual_weight: self.residual_weight,
519 use_attention: self.use_attention,
520 n_attention_heads: self.n_attention_heads,
521 attention_dim: self.attention_dim,
522 _state: Trained {
523 layers: deep_layers,
524 inducing_points: all_inducing_points,
525 variational_means: all_variational_means,
526 variational_vars: all_variational_vars,
527 training_data: (x_train.clone(), y_train.clone()),
528 log_likelihood: best_log_likelihood,
529 },
530 })
531 }
532
533 fn compute_simplified_elbo(
535 &self,
536 x_train: &Array2<f64>,
537 y_train: &Array1<f64>,
538 layers: &[DeepGPLayer],
539 ) -> f64 {
540 let n = x_train.nrows() as f64;
547 let data_fit_term = -0.5 * y_train.iter().map(|&y| y * y).sum::<f64>();
548 let complexity_penalty = -0.1 * layers.len() as f64;
549
550 data_fit_term / n + complexity_penalty
551 }
552}
553
554impl DeepGaussianProcessRegressor<Trained> {
555 pub fn predict(&self, x_test: &Array2<f64>) -> SklResult<(Array1<f64>, Array1<f64>)> {
557 let state = &self._state;
558
559 let mut current_input = x_test.clone();
561 let mut current_variance = Array2::ones((x_test.nrows(), x_test.ncols()));
562
563 for layer in &state.layers {
564 let (layer_output, layer_variance) = layer.forward(¤t_input)?;
565 current_input = layer_output;
566 current_variance = layer_variance;
567 }
568
569 let mean_pred = current_input.column(0).to_owned();
571 let var_pred = current_variance.column(0).to_owned();
572
573 Ok((mean_pred, var_pred))
574 }
575
576 pub fn num_layers(&self) -> usize {
578 self._state.layers.len()
579 }
580
581 pub fn log_likelihood(&self) -> f64 {
583 self._state.log_likelihood
584 }
585
586 pub fn predict_layer(
588 &self,
589 x_test: &Array2<f64>,
590 layer_idx: usize,
591 ) -> SklResult<(Array2<f64>, Array2<f64>)> {
592 if layer_idx >= self._state.layers.len() {
593 return Err(SklearsError::InvalidInput(format!(
594 "Layer index {} out of bounds (max: {})",
595 layer_idx,
596 self._state.layers.len() - 1
597 )));
598 }
599
600 let mut current_input = x_test.clone();
602
603 for i in 0..=layer_idx {
604 let (layer_output, layer_variance) = self._state.layers[i].forward(¤t_input)?;
605 current_input = layer_output.clone();
606
607 if i == layer_idx {
608 return Ok((layer_output, layer_variance));
609 }
610 }
611
612 unreachable!()
613 }
614
615 pub fn get_inducing_points(&self, layer_idx: usize) -> SklResult<&Array2<f64>> {
617 if layer_idx >= self._state.inducing_points.len() {
618 return Err(SklearsError::InvalidInput(format!(
619 "Layer index {} out of bounds",
620 layer_idx
621 )));
622 }
623
624 Ok(&self._state.inducing_points[layer_idx])
625 }
626
627 pub fn get_variational_params(
629 &self,
630 layer_idx: usize,
631 ) -> SklResult<(&Array1<f64>, &Array1<f64>)> {
632 if layer_idx >= self._state.variational_means.len() {
633 return Err(SklearsError::InvalidInput(format!(
634 "Layer index {} out of bounds",
635 layer_idx
636 )));
637 }
638
639 Ok((
640 &self._state.variational_means[layer_idx],
641 &self._state.variational_vars[layer_idx],
642 ))
643 }
644}
645
646pub struct DeepGaussianProcessRegressorBuilder {
648 layers: Vec<(Box<dyn Kernel>, usize)>,
649 learning_rate: f64,
650 max_epochs: usize,
651 convergence_threshold: f64,
652 jitter: f64,
653 random_seed: Option<u64>,
654 data_batch_size: Option<usize>,
655 inducing_batch_size: Option<usize>,
656 use_doubly_stochastic: bool,
657 use_residual_connections: bool,
658 residual_weight: f64,
659 use_attention: bool,
660 n_attention_heads: usize,
661 attention_dim: usize,
662}
663
664impl DeepGaussianProcessRegressorBuilder {
665 pub fn new() -> Self {
667 Self {
668 layers: Vec::new(),
669 learning_rate: 0.01,
670 max_epochs: 100,
671 convergence_threshold: 1e-6,
672 jitter: 1e-6,
673 random_seed: None,
674 data_batch_size: None,
675 inducing_batch_size: None,
676 use_doubly_stochastic: false,
677 use_residual_connections: false,
678 residual_weight: 0.5,
679 use_attention: false,
680 n_attention_heads: 1,
681 attention_dim: 64,
682 }
683 }
684
685 pub fn add_layer(mut self, kernel: Box<dyn Kernel>, n_inducing: usize) -> Self {
687 self.layers.push((kernel, n_inducing));
688 self
689 }
690
691 pub fn learning_rate(mut self, learning_rate: f64) -> Self {
693 self.learning_rate = learning_rate;
694 self
695 }
696
697 pub fn max_epochs(mut self, max_epochs: usize) -> Self {
699 self.max_epochs = max_epochs;
700 self
701 }
702
703 pub fn convergence_threshold(mut self, threshold: f64) -> Self {
705 self.convergence_threshold = threshold;
706 self
707 }
708
709 pub fn jitter(mut self, jitter: f64) -> Self {
711 self.jitter = jitter;
712 self
713 }
714
715 pub fn random_seed(mut self, seed: u64) -> Self {
717 self.random_seed = Some(seed);
718 self
719 }
720
721 pub fn doubly_stochastic(mut self, data_batch_size: usize, inducing_batch_size: usize) -> Self {
723 self.use_doubly_stochastic = true;
724 self.data_batch_size = Some(data_batch_size);
725 self.inducing_batch_size = Some(inducing_batch_size);
726 self
727 }
728
729 pub fn data_batch_size(mut self, batch_size: usize) -> Self {
731 self.data_batch_size = Some(batch_size);
732 self
733 }
734
735 pub fn inducing_batch_size(mut self, batch_size: usize) -> Self {
737 self.inducing_batch_size = Some(batch_size);
738 self
739 }
740
741 pub fn residual_connections(mut self, weight: f64) -> Self {
743 self.use_residual_connections = true;
744 self.residual_weight = weight.clamp(0.0, 1.0);
745 self
746 }
747
748 pub fn attention(mut self, n_heads: usize, attention_dim: usize) -> Self {
750 self.use_attention = true;
751 self.n_attention_heads = n_heads.max(1);
752 self.attention_dim = attention_dim.max(1);
753 self
754 }
755
756 pub fn build(self) -> DeepGaussianProcessRegressor<Untrained> {
758 DeepGaussianProcessRegressor {
759 layers: self.layers,
760 learning_rate: self.learning_rate,
761 max_epochs: self.max_epochs,
762 convergence_threshold: self.convergence_threshold,
763 jitter: self.jitter,
764 random_seed: self.random_seed,
765 data_batch_size: self.data_batch_size,
766 inducing_batch_size: self.inducing_batch_size,
767 use_doubly_stochastic: self.use_doubly_stochastic,
768 use_residual_connections: self.use_residual_connections,
769 residual_weight: self.residual_weight,
770 use_attention: self.use_attention,
771 n_attention_heads: self.n_attention_heads,
772 attention_dim: self.attention_dim,
773 _state: Untrained,
774 }
775 }
776}
777
778impl Default for DeepGaussianProcessRegressorBuilder {
779 fn default() -> Self {
780 Self::new()
781 }
782}
783
784#[derive(Debug, Clone)]
786pub struct DeepGPConfig {
787 pub learning_rate: f64,
788 pub max_epochs: usize,
789 pub convergence_threshold: f64,
790 pub jitter: f64,
791 pub num_layers: usize,
792 pub data_batch_size: Option<usize>,
794 pub inducing_batch_size: Option<usize>,
796 pub use_doubly_stochastic: bool,
798}
799
800impl Default for DeepGPConfig {
801 fn default() -> Self {
802 Self {
803 learning_rate: 0.01,
804 max_epochs: 100,
805 convergence_threshold: 1e-6,
806 jitter: 1e-6,
807 num_layers: 0,
808 data_batch_size: None,
809 inducing_batch_size: None,
810 use_doubly_stochastic: false,
811 }
812 }
813}
814
815impl<S> Estimator for DeepGaussianProcessRegressor<S> {
817 type Config = DeepGPConfig;
818 type Error = SklearsError;
819 type Float = f64;
820
821 fn config(&self) -> &Self::Config {
822 static DEFAULT_CONFIG: DeepGPConfig = DeepGPConfig {
823 learning_rate: 0.01,
824 max_epochs: 100,
825 convergence_threshold: 1e-6,
826 jitter: 1e-6,
827 num_layers: 0,
828 data_batch_size: None,
829 inducing_batch_size: None,
830 use_doubly_stochastic: false,
831 };
832 &DEFAULT_CONFIG
833 }
834}
835
836impl Fit<Array2<f64>, Array1<f64>> for DeepGaussianProcessRegressor<Untrained> {
837 type Fitted = DeepGaussianProcessRegressor<Trained>;
838
839 fn fit(self, x: &Array2<f64>, y: &Array1<f64>) -> SklResult<Self::Fitted> {
840 self.fit(x, y)
841 }
842}
843
844impl Predict<Array2<f64>, Array1<f64>> for DeepGaussianProcessRegressor<Trained> {
845 fn predict(&self, x: &Array2<f64>) -> SklResult<Array1<f64>> {
846 let (mean_pred, _) = self.predict(x)?;
847 Ok(mean_pred)
848 }
849}
850
851#[allow(non_snake_case)]
852#[cfg(test)]
853mod tests {
854 use super::*;
855 use crate::kernels::RBF;
856 use approx::assert_abs_diff_eq;
857 use scirs2_core::ndarray::array;
858
859 #[test]
860 fn test_deep_gp_creation() {
861 let deep_gp = DeepGaussianProcessRegressor::builder()
862 .add_layer(Box::new(RBF::new(1.0)), 10)
863 .add_layer(Box::new(RBF::new(0.5)), 8)
864 .learning_rate(0.01)
865 .max_epochs(50)
866 .build();
867
868 assert_eq!(deep_gp.layers.len(), 2);
869 assert_eq!(deep_gp.learning_rate, 0.01);
870 assert_eq!(deep_gp.max_epochs, 50);
871 }
872
873 #[test]
874 fn test_deep_gp_fit_predict() {
875 let deep_gp = DeepGaussianProcessRegressor::builder()
876 .add_layer(Box::new(RBF::new(1.0)), 5)
877 .add_layer(Box::new(RBF::new(0.5)), 3)
878 .max_epochs(10)
879 .random_seed(42)
880 .build();
881
882 let x_train = array![[0.0], [1.0], [2.0], [3.0]];
883 let y_train = array![0.0, 1.0, 0.5, 2.0];
884
885 let trained_model = deep_gp
886 .fit(&x_train, &y_train)
887 .expect("model fitting should succeed");
888
889 assert_eq!(trained_model.num_layers(), 2);
890 assert!(trained_model.log_likelihood().is_finite());
891
892 let x_test = array![[1.5], [2.5]];
893 let (predictions, uncertainties) = trained_model
894 .predict(&x_test)
895 .expect("prediction should succeed");
896
897 assert_eq!(predictions.len(), 2);
898 assert_eq!(uncertainties.len(), 2);
899 assert!(predictions.iter().all(|&p| p.is_finite()));
900 assert!(uncertainties.iter().all(|&u| u > 0.0));
901 }
902
903 #[test]
904 fn test_deep_gp_single_layer() {
905 let deep_gp = DeepGaussianProcessRegressor::builder()
906 .add_layer(Box::new(RBF::new(1.0)), 5)
907 .max_epochs(5)
908 .random_seed(42)
909 .build();
910
911 let x_train = array![[0.0], [1.0], [2.0]];
912 let y_train = array![0.0, 1.0, 0.5];
913
914 let trained_model = deep_gp
915 .fit(&x_train, &y_train)
916 .expect("model fitting should succeed");
917 assert_eq!(trained_model.num_layers(), 1);
918
919 let x_test = array![[0.5]];
920 let (predictions, _) = trained_model
921 .predict(&x_test)
922 .expect("prediction should succeed");
923 assert_eq!(predictions.len(), 1);
924 }
925
926 #[test]
927 fn test_deep_gp_multiple_layers() {
928 let deep_gp = DeepGaussianProcessRegressor::builder()
929 .add_layer(Box::new(RBF::new(1.0)), 6)
930 .add_layer(Box::new(RBF::new(0.8)), 4)
931 .add_layer(Box::new(RBF::new(0.5)), 3)
932 .max_epochs(5)
933 .random_seed(42)
934 .build();
935
936 let x_train = array![[0.0], [1.0], [2.0], [3.0], [4.0]];
937 let y_train = array![0.0, 1.0, 0.5, 2.0, 1.5];
938
939 let trained_model = deep_gp
940 .fit(&x_train, &y_train)
941 .expect("model fitting should succeed");
942 assert_eq!(trained_model.num_layers(), 3);
943
944 let x_test = array![[1.5], [2.5], [3.5]];
945 let (predictions, uncertainties) = trained_model
946 .predict(&x_test)
947 .expect("prediction should succeed");
948
949 assert_eq!(predictions.len(), 3);
950 assert_eq!(uncertainties.len(), 3);
951 }
952
953 #[test]
954 fn test_deep_gp_attention() {
955 let _X = array![[1.0], [2.0], [3.0], [4.0]];
956 let _y = array![1.0, 4.0, 9.0, 16.0];
957
958 let deep_gp = DeepGaussianProcessRegressor::builder()
959 .add_layer(Box::new(RBF::new(1.0)), 10)
960 .add_layer(Box::new(RBF::new(0.5)), 8)
961 .attention(2, 32) .learning_rate(0.01)
963 .max_epochs(50)
964 .build();
965
966 assert!(deep_gp.use_attention);
967 assert_eq!(deep_gp.n_attention_heads, 2);
968 assert_eq!(deep_gp.attention_dim, 32);
969
970 let deep_gp_combo = DeepGaussianProcessRegressor::builder()
972 .add_layer(Box::new(RBF::new(1.0)), 10)
973 .attention(4, 64)
974 .residual_connections(0.3)
975 .doubly_stochastic(32, 5)
976 .build();
977
978 assert!(deep_gp_combo.use_attention);
979 assert!(deep_gp_combo.use_residual_connections);
980 assert!(deep_gp_combo.use_doubly_stochastic);
981 }
982
983 #[test]
984 fn test_deep_gp_layer_predictions() {
985 let deep_gp = DeepGaussianProcessRegressor::builder()
986 .add_layer(Box::new(RBF::new(1.0)), 5)
987 .add_layer(Box::new(RBF::new(0.5)), 3)
988 .max_epochs(5)
989 .random_seed(42)
990 .build();
991
992 let x_train = array![[0.0], [1.0], [2.0]];
993 let y_train = array![0.0, 1.0, 0.5];
994
995 let trained_model = deep_gp
996 .fit(&x_train, &y_train)
997 .expect("model fitting should succeed");
998
999 let x_test = array![[0.5], [1.5]];
1000
1001 let (layer0_pred, _layer0_var) = trained_model
1003 .predict_layer(&x_test, 0)
1004 .expect("operation should succeed");
1005 assert_eq!(layer0_pred.nrows(), 2);
1006
1007 let (layer1_pred, _layer1_var) = trained_model
1009 .predict_layer(&x_test, 1)
1010 .expect("operation should succeed");
1011 assert_eq!(layer1_pred.nrows(), 2);
1012
1013 let result = trained_model.predict_layer(&x_test, 5);
1015 assert!(result.is_err());
1016 }
1017
1018 #[test]
1019 fn test_deep_gp_inducing_points() {
1020 let deep_gp = DeepGaussianProcessRegressor::builder()
1021 .add_layer(Box::new(RBF::new(1.0)), 4)
1022 .add_layer(Box::new(RBF::new(0.5)), 3)
1023 .max_epochs(5)
1024 .random_seed(42)
1025 .build();
1026
1027 let x_train = array![[0.0], [1.0], [2.0], [3.0]];
1028 let y_train = array![0.0, 1.0, 0.5, 2.0];
1029
1030 let trained_model = deep_gp
1031 .fit(&x_train, &y_train)
1032 .expect("model fitting should succeed");
1033
1034 let inducing_0 = trained_model
1036 .get_inducing_points(0)
1037 .expect("operation should succeed");
1038 assert_eq!(inducing_0.nrows(), 4);
1039 assert_eq!(inducing_0.ncols(), 1);
1040
1041 let inducing_1 = trained_model
1042 .get_inducing_points(1)
1043 .expect("operation should succeed");
1044 assert_eq!(inducing_1.nrows(), 3);
1045
1046 let result = trained_model.get_inducing_points(5);
1048 assert!(result.is_err());
1049 }
1050
1051 #[test]
1052 fn test_deep_gp_variational_params() {
1053 let deep_gp = DeepGaussianProcessRegressor::builder()
1054 .add_layer(Box::new(RBF::new(1.0)), 5)
1055 .max_epochs(5)
1056 .random_seed(42)
1057 .build();
1058
1059 let x_train = array![[0.0], [1.0], [2.0]];
1060 let y_train = array![0.0, 1.0, 0.5];
1061
1062 let trained_model = deep_gp
1063 .fit(&x_train, &y_train)
1064 .expect("model fitting should succeed");
1065
1066 let (var_mean, var_var) = trained_model
1067 .get_variational_params(0)
1068 .expect("operation should succeed");
1069 assert_eq!(var_mean.len(), 3); assert_eq!(var_var.len(), 3);
1071
1072 let result = trained_model.get_variational_params(5);
1074 assert!(result.is_err());
1075 }
1076
1077 #[test]
1078 fn test_deep_gp_errors() {
1079 let deep_gp = DeepGaussianProcessRegressor::builder()
1080 .add_layer(Box::new(RBF::new(1.0)), 5)
1081 .build();
1082
1083 let x_train = array![[0.0], [1.0]];
1085 let y_train = array![0.0]; let result = deep_gp.fit(&x_train, &y_train);
1087 assert!(result.is_err());
1088
1089 let empty_deep_gp = DeepGaussianProcessRegressor::new();
1091 let x_train = array![[0.0], [1.0]];
1092 let y_train = array![0.0, 1.0];
1093 let result = empty_deep_gp.fit(&x_train, &y_train);
1094 assert!(result.is_err());
1095 }
1096
1097 #[test]
1098 fn test_deep_gp_builder_patterns() {
1099 let deep_gp = DeepGaussianProcessRegressor::builder()
1100 .add_layer(Box::new(RBF::new(1.0)), 10)
1101 .learning_rate(0.05)
1102 .max_epochs(200)
1103 .convergence_threshold(1e-8)
1104 .jitter(1e-7)
1105 .random_seed(123)
1106 .build();
1107
1108 assert_eq!(deep_gp.learning_rate, 0.05);
1109 assert_eq!(deep_gp.max_epochs, 200);
1110 assert_eq!(deep_gp.convergence_threshold, 1e-8);
1111 assert_eq!(deep_gp.jitter, 1e-7);
1112 assert_eq!(deep_gp.random_seed, Some(123));
1113 }
1114
1115 #[test]
1116 fn test_deep_gp_config() {
1117 let deep_gp = DeepGaussianProcessRegressor::new();
1118 let config = deep_gp.config();
1119
1120 assert_eq!(config.learning_rate, 0.01);
1121 assert_eq!(config.max_epochs, 100);
1122 assert_eq!(config.convergence_threshold, 1e-6);
1123 assert_eq!(config.jitter, 1e-6);
1124 }
1125
1126 #[test]
1127 fn test_deep_gp_reproducibility() {
1128 let create_and_train = |seed: u64| {
1129 let deep_gp = DeepGaussianProcessRegressor::builder()
1130 .add_layer(Box::new(RBF::new(1.0)), 5)
1131 .max_epochs(10)
1132 .random_seed(seed)
1133 .build();
1134
1135 let x_train = array![[0.0], [1.0], [2.0]];
1136 let y_train = array![0.0, 1.0, 0.5];
1137
1138 let trained_model = deep_gp
1139 .fit(&x_train, &y_train)
1140 .expect("model fitting should succeed");
1141 let x_test = array![[0.5]];
1142 let (predictions, _) = trained_model
1143 .predict(&x_test)
1144 .expect("prediction should succeed");
1145 predictions[0]
1146 };
1147
1148 let pred1 = create_and_train(42);
1149 let pred2 = create_and_train(42);
1150 let pred3 = create_and_train(123);
1151
1152 assert_abs_diff_eq!(pred1, pred2, epsilon = 1e-10);
1154
1155 assert!(pred1.is_finite() && pred3.is_finite());
1158 }
1159}