1#![allow(non_snake_case)] use crate::utils::*;
8use scirs2_core::ndarray::{s, Array1, Array2, ArrayView2, Axis};
10use sklears_core::{
11 error::{Result as SklResult, SklearsError},
12 traits::{Estimator, Fit, Predict, Untrained},
13 types::Float,
14};
15
16#[derive(Debug, Clone)]
35pub struct ClassifierChain<S = Untrained> {
36 state: S,
37 order: Option<Vec<usize>>,
38 cv: Option<usize>,
39 random_state: Option<u64>,
40}
41
42impl ClassifierChain<Untrained> {
43 pub fn new() -> Self {
45 Self {
46 state: Untrained,
47 order: None,
48 cv: None,
49 random_state: None,
50 }
51 }
52
53 pub fn order(mut self, order: Vec<usize>) -> Self {
55 self.order = Some(order);
56 self
57 }
58
59 pub fn cv(mut self, cv: usize) -> Self {
61 self.cv = Some(cv);
62 self
63 }
64
65 pub fn random_state(mut self, random_state: u64) -> Self {
67 self.random_state = Some(random_state);
68 self
69 }
70}
71
72impl Default for ClassifierChain<Untrained> {
73 fn default() -> Self {
74 Self::new()
75 }
76}
77
78impl Estimator for ClassifierChain<Untrained> {
79 type Config = ();
80 type Error = SklearsError;
81 type Float = Float;
82
83 fn config(&self) -> &Self::Config {
84 &()
85 }
86}
87
88impl ClassifierChain<Untrained> {
89 pub fn fit_simple(
91 self,
92 X: &ArrayView2<'_, Float>,
93 y: &Array2<i32>,
94 ) -> SklResult<ClassifierChain<ClassifierChainTrained>> {
95 let (n_samples, n_features) = X.dim();
96 let n_labels = y.ncols();
97
98 if n_samples != y.nrows() {
99 return Err(SklearsError::InvalidInput(
100 "X and y must have the same number of samples".to_string(),
101 ));
102 }
103
104 let order = self
106 .order
107 .clone()
108 .unwrap_or_else(|| (0..n_labels).collect());
109
110 if order.len() != n_labels {
111 return Err(SklearsError::InvalidInput(
112 "Chain order must contain all label indices".to_string(),
113 ));
114 }
115
116 let mut models = Vec::new();
118 let mut current_features = X.to_owned();
119
120 for (i, &label_idx) in order.iter().enumerate() {
121 let y_binary = y.column(label_idx).to_owned();
122
123 let model = train_binary_classifier(¤t_features.view(), &y_binary)?;
125 models.push(model);
126
127 if i < order.len() - 1 {
129 let predictions = predict_binary_classifier(¤t_features.view(), &models[i]);
130 let n_current_features = current_features.ncols();
131 let mut new_features = Array2::<Float>::zeros((n_samples, n_current_features + 1));
132
133 new_features
135 .slice_mut(s![.., ..n_current_features])
136 .assign(¤t_features);
137
138 for j in 0..n_samples {
140 new_features[[j, n_current_features]] = predictions[j] as Float;
141 }
142
143 current_features = new_features;
144 }
145 }
146
147 let trained_state = ClassifierChainTrained {
148 models,
149 order,
150 n_features,
151 n_labels,
152 };
153
154 Ok(ClassifierChain {
155 state: trained_state,
156 order: self.order,
157 cv: self.cv,
158 random_state: self.random_state,
159 })
160 }
161}
162
163impl Fit<ArrayView2<'_, Float>, Array2<i32>, ClassifierChainTrained>
164 for ClassifierChain<Untrained>
165{
166 type Fitted = ClassifierChain<ClassifierChainTrained>;
167
168 fn fit(self, X: &ArrayView2<'_, Float>, y: &Array2<i32>) -> SklResult<Self::Fitted> {
169 self.fit_simple(X, y)
170 }
171}
172
173#[derive(Debug, Clone)]
175pub struct ClassifierChainTrained {
176 models: Vec<SimpleBinaryModel>,
177 order: Vec<usize>,
178 n_features: usize,
179 n_labels: usize,
180}
181
182impl Predict<ArrayView2<'_, Float>, Array2<i32>> for ClassifierChain<ClassifierChainTrained> {
183 fn predict(&self, X: &ArrayView2<'_, Float>) -> SklResult<Array2<i32>> {
184 let (n_samples, n_features) = X.dim();
185 if n_features != self.state.n_features {
186 return Err(SklearsError::InvalidInput(
187 "X has different number of features than training data".to_string(),
188 ));
189 }
190
191 let mut predictions = Array2::<i32>::zeros((n_samples, self.state.n_labels));
192 let mut current_features = X.to_owned();
193
194 for (i, &label_idx) in self.state.order.iter().enumerate() {
196 let model = &self.state.models[i];
197 let label_predictions = predict_binary_classifier(¤t_features.view(), model);
198
199 for j in 0..n_samples {
201 predictions[[j, label_idx]] = label_predictions[j];
202 }
203
204 if i < self.state.order.len() - 1 {
206 let n_current_features = current_features.ncols();
207 let mut new_features = Array2::<Float>::zeros((n_samples, n_current_features + 1));
208
209 new_features
211 .slice_mut(s![.., ..n_current_features])
212 .assign(¤t_features);
213
214 for j in 0..n_samples {
216 new_features[[j, n_current_features]] = label_predictions[j] as Float;
217 }
218
219 current_features = new_features;
220 }
221 }
222
223 Ok(predictions)
224 }
225}
226
227impl ClassifierChain<ClassifierChainTrained> {
228 pub fn predict_proba(&self, X: &ArrayView2<'_, Float>) -> SklResult<Array2<Float>> {
230 let (n_samples, n_features) = X.dim();
231 if n_features != self.state.n_features {
232 return Err(SklearsError::InvalidInput(
233 "X has different number of features than training data".to_string(),
234 ));
235 }
236
237 let mut probabilities = Array2::<Float>::zeros((n_samples, self.state.n_labels));
238 let mut current_features = X.to_owned();
239
240 for (i, &label_idx) in self.state.order.iter().enumerate() {
242 let model = &self.state.models[i];
243 let label_probabilities = predict_binary_probabilities(¤t_features.view(), model);
244
245 for j in 0..n_samples {
247 probabilities[[j, label_idx]] = label_probabilities[j];
248 }
249
250 if i < self.state.order.len() - 1 {
252 let label_predictions =
253 label_probabilities.mapv(|p| if p > 0.5 { 1.0 } else { 0.0 });
254 let n_current_features = current_features.ncols();
255 let mut new_features = Array2::<Float>::zeros((n_samples, n_current_features + 1));
256
257 new_features
259 .slice_mut(s![.., ..n_current_features])
260 .assign(¤t_features);
261
262 for j in 0..n_samples {
264 new_features[[j, n_current_features]] = label_predictions[j];
265 }
266
267 current_features = new_features;
268 }
269 }
270
271 Ok(probabilities)
272 }
273
274 pub fn chain_order(&self) -> &[usize] {
276 &self.state.order
277 }
278
279 pub fn n_models(&self) -> usize {
281 self.state.models.len()
282 }
283
284 pub fn n_targets(&self) -> usize {
286 self.state.n_labels
287 }
288
289 pub fn predict_simple(&self, X: &ArrayView2<'_, Float>) -> SklResult<Array2<i32>> {
291 self.predict(X)
292 }
293
294 pub fn predict_monte_carlo(
296 &self,
297 X: &ArrayView2<'_, Float>,
298 n_samples: usize,
299 _random_state: Option<u64>,
300 ) -> SklResult<Array2<Float>> {
301 if n_samples == 0 {
302 return Err(SklearsError::InvalidInput(
303 "n_samples must be greater than 0".to_string(),
304 ));
305 }
306 self.predict_proba(X)
308 }
309
310 pub fn predict_monte_carlo_labels(
312 &self,
313 X: &ArrayView2<'_, Float>,
314 n_samples: usize,
315 _random_state: Option<u64>,
316 ) -> SklResult<Array2<i32>> {
317 if n_samples == 0 {
318 return Err(SklearsError::InvalidInput(
319 "n_samples must be greater than 0".to_string(),
320 ));
321 }
322 self.predict(X)
324 }
325}
326
327#[derive(Debug, Clone)]
346pub struct RegressorChain<S = Untrained> {
347 state: S,
348 order: Option<Vec<usize>>,
349 cv: Option<usize>,
350 random_state: Option<u64>,
351}
352
353impl RegressorChain<Untrained> {
354 pub fn new() -> Self {
356 Self {
357 state: Untrained,
358 order: None,
359 cv: None,
360 random_state: None,
361 }
362 }
363
364 pub fn order(mut self, order: Vec<usize>) -> Self {
366 self.order = Some(order);
367 self
368 }
369
370 pub fn cv(mut self, cv: usize) -> Self {
372 self.cv = Some(cv);
373 self
374 }
375
376 pub fn random_state(mut self, random_state: u64) -> Self {
378 self.random_state = Some(random_state);
379 self
380 }
381}
382
383impl Default for RegressorChain<Untrained> {
384 fn default() -> Self {
385 Self::new()
386 }
387}
388
389impl Estimator for RegressorChain<Untrained> {
390 type Config = ();
391 type Error = SklearsError;
392 type Float = Float;
393
394 fn config(&self) -> &Self::Config {
395 &()
396 }
397}
398
399impl RegressorChain<Untrained> {
400 pub fn fit_simple(
402 self,
403 X: &ArrayView2<'_, Float>,
404 y: &Array2<Float>,
405 ) -> SklResult<RegressorChain<RegressorChainTrained>> {
406 let (n_samples, n_features) = X.dim();
407 let n_targets = y.ncols();
408
409 if n_samples != y.nrows() {
410 return Err(SklearsError::InvalidInput(
411 "X and y must have the same number of samples".to_string(),
412 ));
413 }
414
415 let order = self
417 .order
418 .clone()
419 .unwrap_or_else(|| (0..n_targets).collect());
420
421 if order.len() != n_targets {
422 return Err(SklearsError::InvalidInput(
423 "Chain order must contain all target indices".to_string(),
424 ));
425 }
426
427 let mut models = Vec::new();
429 let mut current_features = X.to_owned();
430
431 for (i, &target_idx) in order.iter().enumerate() {
432 let y_target = y.column(target_idx).to_owned();
433
434 let model = train_simple_linear_classifier(¤t_features.view(), &y_target)?;
436 models.push(model);
437
438 if i < order.len() - 1 {
440 let predictions = predict_simple_linear(¤t_features.view(), &models[i]);
441 let n_current_features = current_features.ncols();
442 let mut new_features = Array2::<Float>::zeros((n_samples, n_current_features + 1));
443
444 new_features
446 .slice_mut(s![.., ..n_current_features])
447 .assign(¤t_features);
448
449 for j in 0..n_samples {
451 new_features[[j, n_current_features]] = predictions[j];
452 }
453
454 current_features = new_features;
455 }
456 }
457
458 let trained_state = RegressorChainTrained {
459 models,
460 order,
461 n_features,
462 n_targets,
463 };
464
465 Ok(RegressorChain {
466 state: trained_state,
467 order: self.order,
468 cv: self.cv,
469 random_state: self.random_state,
470 })
471 }
472}
473
474impl Fit<ArrayView2<'_, Float>, Array2<Float>, RegressorChainTrained>
475 for RegressorChain<Untrained>
476{
477 type Fitted = RegressorChain<RegressorChainTrained>;
478
479 fn fit(self, X: &ArrayView2<'_, Float>, y: &Array2<Float>) -> SklResult<Self::Fitted> {
480 self.fit_simple(X, y)
481 }
482}
483
484#[derive(Debug, Clone)]
486pub struct RegressorChainTrained {
487 models: Vec<SimpleLinearClassifier>,
488 order: Vec<usize>,
489 n_features: usize,
490 n_targets: usize,
491}
492
493impl Predict<ArrayView2<'_, Float>, Array2<Float>> for RegressorChain<RegressorChainTrained> {
494 fn predict(&self, X: &ArrayView2<'_, Float>) -> SklResult<Array2<Float>> {
495 let (n_samples, n_features) = X.dim();
496 if n_features != self.state.n_features {
497 return Err(SklearsError::InvalidInput(
498 "X has different number of features than training data".to_string(),
499 ));
500 }
501
502 let mut predictions = Array2::<Float>::zeros((n_samples, self.state.n_targets));
503 let mut current_features = X.to_owned();
504
505 for (i, &target_idx) in self.state.order.iter().enumerate() {
507 let model = &self.state.models[i];
508 let target_predictions = predict_simple_linear(¤t_features.view(), model);
509
510 for j in 0..n_samples {
512 predictions[[j, target_idx]] = target_predictions[j];
513 }
514
515 if i < self.state.order.len() - 1 {
517 let n_current_features = current_features.ncols();
518 let mut new_features = Array2::<Float>::zeros((n_samples, n_current_features + 1));
519
520 new_features
522 .slice_mut(s![.., ..n_current_features])
523 .assign(¤t_features);
524
525 for j in 0..n_samples {
527 new_features[[j, n_current_features]] = target_predictions[j];
528 }
529
530 current_features = new_features;
531 }
532 }
533
534 Ok(predictions)
535 }
536}
537
538impl RegressorChain<RegressorChainTrained> {
539 pub fn chain_order(&self) -> &[usize] {
541 &self.state.order
542 }
543
544 pub fn n_models(&self) -> usize {
546 self.state.models.len()
547 }
548
549 pub fn get_model(&self, index: usize) -> Option<&SimpleLinearClassifier> {
551 self.state.models.get(index)
552 }
553
554 pub fn n_targets(&self) -> usize {
556 self.state.n_targets
557 }
558
559 pub fn predict_simple(&self, X: &ArrayView2<'_, Float>) -> SklResult<Array2<Float>> {
561 self.predict(X)
562 }
563}
564
565#[derive(Debug, Clone)]
583pub struct EnsembleOfChains<S = Untrained> {
584 state: S,
585 n_chains: usize,
586 chain_method: ChainMethod,
587 random_state: Option<u64>,
588}
589
590#[derive(Debug, Clone, Copy, PartialEq)]
592pub enum ChainMethod {
593 Random,
595 Fixed,
597 Bootstrap,
599}
600
601impl EnsembleOfChains<Untrained> {
602 pub fn new() -> Self {
604 Self {
605 state: Untrained,
606 n_chains: 10,
607 chain_method: ChainMethod::Random,
608 random_state: None,
609 }
610 }
611
612 pub fn n_chains(mut self, n_chains: usize) -> Self {
614 self.n_chains = n_chains;
615 self
616 }
617
618 pub fn chain_method(mut self, method: ChainMethod) -> Self {
620 self.chain_method = method;
621 self
622 }
623
624 pub fn random_state(mut self, random_state: u64) -> Self {
626 self.random_state = Some(random_state);
627 self
628 }
629}
630
631impl Default for EnsembleOfChains<Untrained> {
632 fn default() -> Self {
633 Self::new()
634 }
635}
636
637impl Estimator for EnsembleOfChains<Untrained> {
638 type Config = ();
639 type Error = SklearsError;
640 type Float = Float;
641
642 fn config(&self) -> &Self::Config {
643 &()
644 }
645}
646
647impl EnsembleOfChains<Untrained> {
648 pub fn fit_simple(
650 self,
651 X: &ArrayView2<'_, Float>,
652 y: &Array2<i32>,
653 ) -> SklResult<EnsembleOfChains<EnsembleOfChainsTrained>> {
654 let (n_samples, n_features) = X.dim();
655 let n_labels = y.ncols();
656
657 if n_samples != y.nrows() {
658 return Err(SklearsError::InvalidInput(
659 "X and y must have the same number of samples".to_string(),
660 ));
661 }
662
663 let mut chains = Vec::new();
664 let mut rng_state = self.random_state.unwrap_or(42);
665
666 for i in 0..self.n_chains {
667 let chain_order = match self.chain_method {
669 ChainMethod::Random => {
670 let mut order: Vec<usize> = (0..n_labels).collect();
671 for j in (1..order.len()).rev() {
673 rng_state = rng_state.wrapping_mul(1664525).wrapping_add(1013904223);
674 let k = (rng_state as usize) % (j + 1);
675 order.swap(j, k);
676 }
677 order
678 }
679 ChainMethod::Fixed => {
680 let mut order: Vec<usize> = (0..n_labels).collect();
682 order.rotate_left(i % n_labels);
683 order
684 }
685 ChainMethod::Bootstrap => {
686 let mut order: Vec<usize> = (0..n_labels).collect();
688 for j in (1..order.len()).rev() {
689 rng_state = rng_state.wrapping_mul(1664525).wrapping_add(1013904223);
690 let k = (rng_state as usize) % (j + 1);
691 order.swap(j, k);
692 }
693 order
694 }
695 };
696
697 let chain = ClassifierChain::new()
699 .order(chain_order)
700 .random_state(rng_state);
701
702 let trained_chain = chain.fit_simple(X, y)?;
703 chains.push(trained_chain);
704
705 rng_state = rng_state.wrapping_add(1);
706 }
707
708 let trained_state = EnsembleOfChainsTrained {
709 chains,
710 n_features,
711 n_labels,
712 };
713
714 Ok(EnsembleOfChains {
715 state: trained_state,
716 n_chains: self.n_chains,
717 chain_method: self.chain_method,
718 random_state: self.random_state,
719 })
720 }
721}
722
723impl Fit<ArrayView2<'_, Float>, Array2<i32>, EnsembleOfChainsTrained>
724 for EnsembleOfChains<Untrained>
725{
726 type Fitted = EnsembleOfChains<EnsembleOfChainsTrained>;
727
728 fn fit(self, X: &ArrayView2<'_, Float>, y: &Array2<i32>) -> SklResult<Self::Fitted> {
729 self.fit_simple(X, y)
730 }
731}
732
733#[derive(Debug, Clone)]
735pub struct EnsembleOfChainsTrained {
736 chains: Vec<ClassifierChain<ClassifierChainTrained>>,
737 n_features: usize,
738 n_labels: usize,
739}
740
741impl Predict<ArrayView2<'_, Float>, Array2<i32>> for EnsembleOfChains<EnsembleOfChainsTrained> {
742 fn predict(&self, X: &ArrayView2<'_, Float>) -> SklResult<Array2<i32>> {
743 let (n_samples, n_features) = X.dim();
744 if n_features != self.state.n_features {
745 return Err(SklearsError::InvalidInput(
746 "X has different number of features than training data".to_string(),
747 ));
748 }
749
750 let mut all_predictions = Vec::new();
752 for chain in &self.state.chains {
753 let predictions = chain.predict(X)?;
754 all_predictions.push(predictions);
755 }
756
757 let mut final_predictions = Array2::<i32>::zeros((n_samples, self.state.n_labels));
759
760 for i in 0..n_samples {
761 for j in 0..self.state.n_labels {
762 let mut votes = 0;
763 for predictions in &all_predictions {
764 votes += predictions[[i, j]];
765 }
766 final_predictions[[i, j]] = if votes > (self.state.chains.len() as i32) / 2 {
768 1
769 } else {
770 0
771 };
772 }
773 }
774
775 Ok(final_predictions)
776 }
777}
778
779impl EnsembleOfChains<EnsembleOfChainsTrained> {
780 pub fn predict_proba(&self, X: &ArrayView2<'_, Float>) -> SklResult<Array2<Float>> {
782 let (n_samples, n_features) = X.dim();
783 if n_features != self.state.n_features {
784 return Err(SklearsError::InvalidInput(
785 "X has different number of features than training data".to_string(),
786 ));
787 }
788
789 let mut all_probabilities = Vec::new();
791 for chain in &self.state.chains {
792 let probabilities = chain.predict_proba(X)?;
793 all_probabilities.push(probabilities);
794 }
795
796 let mut final_probabilities = Array2::<Float>::zeros((n_samples, self.state.n_labels));
798
799 for i in 0..n_samples {
800 for j in 0..self.state.n_labels {
801 let mut prob_sum = 0.0;
802 for probabilities in &all_probabilities {
803 prob_sum += probabilities[[i, j]];
804 }
805 final_probabilities[[i, j]] = prob_sum / self.state.chains.len() as Float;
806 }
807 }
808
809 Ok(final_probabilities)
810 }
811
812 pub fn n_chains(&self) -> usize {
814 self.state.chains.len()
815 }
816
817 pub fn get_chain(&self, index: usize) -> Option<&ClassifierChain<ClassifierChainTrained>> {
819 self.state.chains.get(index)
820 }
821
822 pub fn chain_diversity(&self) -> Float {
824 if self.state.chains.len() < 2 {
825 return 0.0;
826 }
827
828 let mut diversity_sum = 0.0;
829 let mut count = 0;
830
831 for i in 0..self.state.chains.len() {
833 for j in (i + 1)..self.state.chains.len() {
834 let order1 = self.state.chains[i].chain_order();
835 let order2 = self.state.chains[j].chain_order();
836
837 let mut agreements = 0;
839 for k in 0..order1.len() {
840 if order1[k] == order2[k] {
841 agreements += 1;
842 }
843 }
844
845 let similarity = agreements as Float / order1.len() as Float;
846 diversity_sum += 1.0 - similarity;
847 count += 1;
848 }
849 }
850
851 if count > 0 {
852 diversity_sum / count as Float
853 } else {
854 0.0
855 }
856 }
857
858 pub fn n_targets(&self) -> usize {
860 self.state.n_labels
861 }
862
863 pub fn predict_simple(&self, X: &ArrayView2<'_, Float>) -> SklResult<Array2<i32>> {
865 self.predict(X)
866 }
867
868 pub fn predict_proba_simple(&self, X: &ArrayView2<'_, Float>) -> SklResult<Array2<Float>> {
870 self.predict_proba(X)
871 }
872}
873
874#[derive(Debug, Clone)]
895pub struct BayesianClassifierChain<S = Untrained> {
896 state: S,
897 pub order: Option<Vec<usize>>,
899 pub n_samples: usize,
901 pub prior_strength: Float,
903 pub random_state: Option<u64>,
905}
906
907impl BayesianClassifierChain<Untrained> {
908 pub fn new() -> Self {
910 Self {
911 state: Untrained,
912 order: None,
913 n_samples: 100,
914 prior_strength: 1.0,
915 random_state: None,
916 }
917 }
918
919 pub fn order(mut self, order: Vec<usize>) -> Self {
921 self.order = Some(order);
922 self
923 }
924
925 pub fn n_samples(mut self, n_samples: usize) -> Self {
927 self.n_samples = n_samples;
928 self
929 }
930
931 pub fn prior_strength(mut self, prior_strength: Float) -> Self {
933 self.prior_strength = prior_strength;
934 self
935 }
936
937 pub fn random_state(mut self, random_state: u64) -> Self {
939 self.random_state = Some(random_state);
940 self
941 }
942}
943
944impl Default for BayesianClassifierChain<Untrained> {
945 fn default() -> Self {
946 Self::new()
947 }
948}
949
950impl Estimator for BayesianClassifierChain<Untrained> {
951 type Config = ();
952 type Error = SklearsError;
953 type Float = Float;
954
955 fn config(&self) -> &Self::Config {
956 &()
957 }
958}
959
960impl BayesianClassifierChain<Untrained> {
961 #[allow(non_snake_case)]
963 pub fn fit_simple(
964 self,
965 X: &ArrayView2<'_, Float>,
966 y: &Array2<i32>,
967 ) -> SklResult<BayesianClassifierChain<BayesianClassifierChainTrained>> {
968 let (n_samples, n_features) = X.dim();
969 let n_labels = y.ncols();
970
971 if n_samples != y.nrows() {
972 return Err(SklearsError::InvalidInput(
973 "X and y must have the same number of samples".to_string(),
974 ));
975 }
976
977 for &val in y.iter() {
979 if val != 0 && val != 1 {
980 return Err(SklearsError::InvalidInput(
981 "y must contain only binary values (0 or 1)".to_string(),
982 ));
983 }
984 }
985
986 let order = self
988 .order
989 .clone()
990 .unwrap_or_else(|| (0..n_labels).collect());
991
992 if order.len() != n_labels {
993 return Err(SklearsError::InvalidInput(
994 "Chain order must contain all label indices".to_string(),
995 ));
996 }
997
998 let feature_means = X
1000 .mean_axis(Axis(0))
1001 .expect("array should have elements for mean computation");
1002 let feature_stds = X.std_axis(Axis(0), 0.0);
1003 let X_standardized = standardize_features_simple(X, &feature_means, &feature_stds);
1004
1005 let mut bayesian_models = Vec::new();
1007 let mut current_features = X_standardized;
1008
1009 for (i, &label_idx) in order.iter().enumerate() {
1010 let y_binary = y.column(label_idx).to_owned();
1011
1012 let model = train_bayesian_binary_classifier(
1014 ¤t_features,
1015 &y_binary,
1016 self.prior_strength,
1017 )?;
1018 bayesian_models.push(model);
1019
1020 if i < order.len() - 1 {
1022 let predictions =
1023 predict_bayesian_mean(¤t_features.view(), &bayesian_models[i]);
1024 let n_current_features = current_features.ncols();
1025 let mut new_features = Array2::<Float>::zeros((n_samples, n_current_features + 1));
1026
1027 new_features
1029 .slice_mut(s![.., ..n_current_features])
1030 .assign(¤t_features);
1031
1032 for j in 0..n_samples {
1034 new_features[[j, n_current_features]] = predictions[j];
1035 }
1036
1037 current_features = new_features;
1038 }
1039 }
1040
1041 let trained_state = BayesianClassifierChainTrained {
1042 bayesian_models,
1043 order,
1044 n_features,
1045 n_labels,
1046 feature_means,
1047 feature_stds,
1048 };
1049
1050 Ok(BayesianClassifierChain {
1051 state: trained_state,
1052 order: None,
1053 n_samples: self.n_samples,
1054 prior_strength: self.prior_strength,
1055 random_state: self.random_state,
1056 })
1057 }
1058}
1059
1060impl Fit<ArrayView2<'_, Float>, Array2<i32>, BayesianClassifierChainTrained>
1061 for BayesianClassifierChain<Untrained>
1062{
1063 type Fitted = BayesianClassifierChain<BayesianClassifierChainTrained>;
1064
1065 fn fit(self, X: &ArrayView2<'_, Float>, y: &Array2<i32>) -> SklResult<Self::Fitted> {
1066 self.fit_simple(X, y)
1067 }
1068}
1069
1070#[derive(Debug, Clone)]
1072pub struct BayesianClassifierChainTrained {
1073 bayesian_models: Vec<BayesianBinaryModel>,
1074 order: Vec<usize>,
1075 #[allow(dead_code)]
1076 n_features: usize,
1077 n_labels: usize,
1078 feature_means: Array1<Float>,
1079 feature_stds: Array1<Float>,
1080}
1081
1082impl Predict<ArrayView2<'_, Float>, Array2<i32>>
1083 for BayesianClassifierChain<BayesianClassifierChainTrained>
1084{
1085 #[allow(non_snake_case)]
1086 fn predict(&self, X: &ArrayView2<'_, Float>) -> SklResult<Array2<i32>> {
1087 let (n_samples, n_features) = X.dim();
1088 if n_features != self.state.feature_means.len() {
1089 return Err(SklearsError::InvalidInput(
1090 "X has different number of features than training data".to_string(),
1091 ));
1092 }
1093
1094 let X_standardized =
1096 standardize_features_simple(X, &self.state.feature_means, &self.state.feature_stds);
1097
1098 let mut predictions = Array2::<i32>::zeros((n_samples, self.state.n_labels));
1099 let mut current_features = X_standardized;
1100
1101 for (chain_pos, &label_idx) in self.state.order.iter().enumerate() {
1103 let model = &self.state.bayesian_models[chain_pos];
1104
1105 let label_predictions = predict_bayesian_binary(¤t_features.view(), model);
1107
1108 for i in 0..n_samples {
1110 predictions[[i, label_idx]] = if label_predictions[i] > 0.5 { 1 } else { 0 };
1111 }
1112
1113 if chain_pos < self.state.order.len() - 1 {
1115 let mut new_features =
1116 Array2::<Float>::zeros((n_samples, current_features.ncols() + 1));
1117
1118 new_features
1120 .slice_mut(s![.., ..current_features.ncols()])
1121 .assign(¤t_features);
1122
1123 for i in 0..n_samples {
1125 new_features[[i, current_features.ncols()]] =
1126 predictions[[i, label_idx]] as Float;
1127 }
1128
1129 current_features = new_features;
1130 }
1131 }
1132
1133 Ok(predictions)
1134 }
1135}
1136
1137impl BayesianClassifierChain<BayesianClassifierChainTrained> {
1138 #[allow(non_snake_case)]
1140 pub fn predict_uncertainty(&self, X: &ArrayView2<'_, Float>) -> SklResult<Array2<Float>> {
1141 let (n_samples, n_features) = X.dim();
1142 if n_features != self.state.feature_means.len() {
1143 return Err(SklearsError::InvalidInput(
1144 "X has different number of features than training data".to_string(),
1145 ));
1146 }
1147
1148 let X_standardized =
1150 standardize_features_simple(X, &self.state.feature_means, &self.state.feature_stds);
1151
1152 let mut uncertainties = Array2::<Float>::zeros((n_samples, self.state.n_labels));
1153 let mut current_features = X_standardized;
1154
1155 for (chain_pos, &label_idx) in self.state.order.iter().enumerate() {
1157 let model = &self.state.bayesian_models[chain_pos];
1158
1159 let (means, variances) = predict_bayesian_uncertainty(¤t_features.view(), model)?;
1161
1162 for i in 0..n_samples {
1164 uncertainties[[i, label_idx]] = variances[i];
1165 }
1166
1167 if chain_pos < self.state.order.len() - 1 {
1169 let mut new_features =
1170 Array2::<Float>::zeros((n_samples, current_features.ncols() + 1));
1171
1172 new_features
1174 .slice_mut(s![.., ..current_features.ncols()])
1175 .assign(¤t_features);
1176
1177 for i in 0..n_samples {
1179 new_features[[i, current_features.ncols()]] = means[i];
1180 }
1181
1182 current_features = new_features;
1183 }
1184 }
1185
1186 Ok(uncertainties)
1187 }
1188
1189 #[allow(non_snake_case)]
1191 pub fn predict_proba(&self, X: &ArrayView2<'_, Float>) -> SklResult<Array2<Float>> {
1192 let (n_samples, n_features) = X.dim();
1193 if n_features != self.state.feature_means.len() {
1194 return Err(SklearsError::InvalidInput(
1195 "X has different number of features than training data".to_string(),
1196 ));
1197 }
1198
1199 let X_standardized =
1201 standardize_features_simple(X, &self.state.feature_means, &self.state.feature_stds);
1202
1203 let mut probabilities = Array2::<Float>::zeros((n_samples, self.state.n_labels));
1204 let mut current_features = X_standardized;
1205
1206 for (chain_pos, &label_idx) in self.state.order.iter().enumerate() {
1208 let model = &self.state.bayesian_models[chain_pos];
1209
1210 let label_probabilities = predict_bayesian_binary(¤t_features.view(), model);
1212
1213 for i in 0..n_samples {
1215 probabilities[[i, label_idx]] = label_probabilities[i];
1216 }
1217
1218 if chain_pos < self.state.order.len() - 1 {
1220 let mut new_features =
1221 Array2::<Float>::zeros((n_samples, current_features.ncols() + 1));
1222
1223 new_features
1225 .slice_mut(s![.., ..current_features.ncols()])
1226 .assign(¤t_features);
1227
1228 for i in 0..n_samples {
1230 new_features[[i, current_features.ncols()]] = label_probabilities[i];
1231 }
1232
1233 current_features = new_features;
1234 }
1235 }
1236
1237 Ok(probabilities)
1238 }
1239
1240 pub fn chain_order(&self) -> &[usize] {
1242 &self.state.order
1243 }
1244
1245 pub fn n_models(&self) -> usize {
1247 self.state.bayesian_models.len()
1248 }
1249
1250 pub fn model_posterior_stats(
1252 &self,
1253 model_idx: usize,
1254 ) -> Option<(&Array1<Float>, &Array2<Float>)> {
1255 self.state
1256 .bayesian_models
1257 .get(model_idx)
1258 .map(|model| (&model.weight_mean, &model.weight_cov))
1259 }
1260
1261 pub fn order(&self) -> &[usize] {
1263 &self.state.order
1264 }
1265}
1266
1267fn predict_binary_classifier(X: &ArrayView2<Float>, model: &SimpleBinaryModel) -> Array1<i32> {
1271 let raw_scores = X.dot(&model.weights) + model.bias;
1272 raw_scores.mapv(|x| if x > 0.0 { 1 } else { 0 })
1273}