1use crate::base::SelectorMixin;
7use scirs2_core::ndarray::{s, Array1, Array2, Axis};
8
9use scirs2_core::rand_prelude::IndexedRandom;
10use scirs2_core::random::{rngs::StdRng, thread_rng, RngExt, SeedableRng};
11use sklears_core::{
12 error::{validate, Result as SklResult, SklearsError},
13 traits::{Estimator, Fit, Trained, Transform, Untrained},
14 types::Float,
15};
16use std::collections::HashMap;
17use std::marker::PhantomData;
18
19#[derive(Debug, Clone)]
23pub struct NeuralFeatureSelector<State = Untrained> {
24 hidden_layers: Vec<usize>,
26 learning_rate: f64,
28 epochs: usize,
30 l1_reg: f64,
32 l2_reg: f64,
34 k: Option<usize>,
36 importance_threshold: f64,
38 random_state: Option<u64>,
40 state: PhantomData<State>,
41 _weights_: Option<Vec<Array2<Float>>>,
43 _biases_: Option<Vec<Array1<Float>>>,
44 feature_importances_: Option<Array1<Float>>,
45 selected_features_: Option<Vec<usize>>,
46}
47
48impl NeuralFeatureSelector<Untrained> {
49 pub fn new() -> Self {
51 Self {
52 hidden_layers: vec![64, 32],
53 learning_rate: 0.001,
54 epochs: 100,
55 l1_reg: 0.01,
56 l2_reg: 0.01,
57 k: None,
58 importance_threshold: 0.01,
59 random_state: None,
60 state: PhantomData,
61 _weights_: None,
62 _biases_: None,
63 feature_importances_: None,
64 selected_features_: None,
65 }
66 }
67
68 pub fn hidden_layers(mut self, layers: Vec<usize>) -> Self {
70 self.hidden_layers = layers;
71 self
72 }
73
74 pub fn learning_rate(mut self, lr: f64) -> Self {
76 self.learning_rate = lr;
77 self
78 }
79
80 pub fn epochs(mut self, epochs: usize) -> Self {
82 self.epochs = epochs;
83 self
84 }
85
86 pub fn l1_reg(mut self, l1_reg: f64) -> Self {
88 self.l1_reg = l1_reg;
89 self
90 }
91
92 pub fn l2_reg(mut self, l2_reg: f64) -> Self {
94 self.l2_reg = l2_reg;
95 self
96 }
97
98 pub fn k(mut self, k: Option<usize>) -> Self {
100 self.k = k;
101 self
102 }
103
104 pub fn importance_threshold(mut self, threshold: f64) -> Self {
106 self.importance_threshold = threshold;
107 self
108 }
109
110 pub fn random_state(mut self, seed: Option<u64>) -> Self {
112 self.random_state = seed;
113 self
114 }
115}
116
117impl Default for NeuralFeatureSelector<Untrained> {
118 fn default() -> Self {
119 Self::new()
120 }
121}
122
123impl Estimator for NeuralFeatureSelector<Untrained> {
124 type Config = ();
125 type Error = SklearsError;
126 type Float = f64;
127
128 fn config(&self) -> &Self::Config {
129 &()
130 }
131}
132
133impl Fit<Array2<Float>, Array1<Float>> for NeuralFeatureSelector<Untrained> {
134 type Fitted = NeuralFeatureSelector<Trained>;
135
136 fn fit(self, x: &Array2<Float>, y: &Array1<Float>) -> SklResult<Self::Fitted> {
137 validate::check_consistent_length(x, y)?;
138
139 let mut rng = match self.random_state {
140 Some(seed) => StdRng::seed_from_u64(seed),
141 None => StdRng::from_rng(&mut thread_rng()),
142 };
143
144 let (n_samples, n_features) = x.dim();
145
146 let mut layer_sizes = vec![n_features];
148 layer_sizes.extend(&self.hidden_layers);
149 layer_sizes.push(1); let mut weights = Vec::new();
153 let mut biases = Vec::new();
154
155 for i in 0..(layer_sizes.len() - 1) {
156 let input_size = layer_sizes[i];
157 let output_size = layer_sizes[i + 1];
158
159 let scale = (2.0 / (input_size + output_size) as f64).sqrt();
161 let mut weight_matrix = Array2::zeros((input_size, output_size));
162 for elem in weight_matrix.iter_mut() {
163 *elem = rng.random::<f64>() * 2.0 * scale - scale;
164 }
165 weights.push(weight_matrix);
166
167 let mut bias_vector = Array1::zeros(output_size);
168 for elem in bias_vector.iter_mut() {
169 *elem = rng.random::<f64>() * 0.1 - 0.05;
170 }
171 biases.push(bias_vector);
172 }
173
174 for epoch in 0..self.epochs {
176 let mut total_loss = 0.0;
177
178 for i in 0..n_samples {
180 let input = x.row(i).to_owned();
181 let target = y[i];
182
183 let (activations, z_values) = self.forward_pass(&input, &weights, &biases);
185 let prediction = activations.last().expect("operation should succeed")[0];
186
187 let loss = 0.5 * (prediction - target).powi(2);
189 total_loss += loss;
190
191 let gradients =
193 self.backward_pass(&input, target, &activations, &z_values, &weights);
194
195 self.update_parameters(&mut weights, &mut biases, &gradients.0, &gradients.1);
197 }
198
199 if epoch % 10 == 0 {
201 let avg_loss = total_loss / n_samples as f64;
202 if avg_loss < 1e-6 {
203 break;
204 }
205 }
206 }
207
208 let input_weights = &weights[0];
210 let mut feature_importances = Array1::zeros(n_features);
211
212 for i in 0..n_features {
213 let importance: f64 = input_weights.row(i).iter().map(|&w| w.abs()).sum();
215 feature_importances[i] = importance;
216 }
217
218 let importance_sum: f64 = feature_importances.sum();
220 if importance_sum > 0.0 {
221 feature_importances /= importance_sum;
222 }
223
224 let selected_features = self.select_features_by_importance(&feature_importances);
226
227 Ok(NeuralFeatureSelector {
228 hidden_layers: self.hidden_layers,
229 learning_rate: self.learning_rate,
230 epochs: self.epochs,
231 l1_reg: self.l1_reg,
232 l2_reg: self.l2_reg,
233 k: self.k,
234 importance_threshold: self.importance_threshold,
235 random_state: self.random_state,
236 state: PhantomData,
237 _weights_: Some(weights),
238 _biases_: Some(biases),
239 feature_importances_: Some(feature_importances),
240 selected_features_: Some(selected_features),
241 })
242 }
243}
244
245impl NeuralFeatureSelector<Untrained> {
246 fn forward_pass(
247 &self,
248 input: &Array1<Float>,
249 weights: &[Array2<Float>],
250 biases: &[Array1<Float>],
251 ) -> (Vec<Array1<Float>>, Vec<Array1<Float>>) {
252 let mut activations = vec![input.clone()];
253 let mut z_values = Vec::new();
254
255 let mut current_activation = input.clone();
256
257 for (i, (w, b)) in weights.iter().zip(biases.iter()).enumerate() {
258 let z = w.t().dot(¤t_activation) + b;
260 z_values.push(z.clone());
261
262 let activation = if i < weights.len() - 1 {
264 z.mapv(|x| x.max(0.0)) } else {
266 z };
268
269 activations.push(activation.clone());
270 current_activation = activation;
271 }
272
273 (activations, z_values)
274 }
275
276 fn backward_pass(
277 &self,
278 _input: &Array1<Float>,
279 target: Float,
280 activations: &[Array1<Float>],
281 z_values: &[Array1<Float>],
282 weights: &[Array2<Float>],
283 ) -> (Vec<Array2<Float>>, Vec<Array1<Float>>) {
284 let n_layers = weights.len();
285 let mut weight_gradients = vec![Array2::zeros(weights[0].dim()); n_layers];
286 let mut bias_gradients = vec![Array1::zeros(z_values[0].len()); n_layers];
287
288 let output_error = activations.last().expect("operation should succeed")[0] - target;
290 let mut delta = Array1::from_vec(vec![output_error]);
291
292 for i in (0..n_layers).rev() {
294 let prev_activation = &activations[i];
296
297 for j in 0..weight_gradients[i].nrows() {
299 for k in 0..weight_gradients[i].ncols() {
300 weight_gradients[i][[j, k]] = prev_activation[j] * delta[k];
301 }
302 }
303
304 bias_gradients[i] = delta.clone();
306
307 if i > 0 {
309 let new_delta = weights[i].dot(&delta);
311
312 let z_prev = &z_values[i - 1];
314 for j in 0..new_delta.len() {
315 if z_prev[j] <= 0.0 {
316 }
318 }
319 delta = new_delta.mapv(|x| if x > 0.0 { x } else { 0.0 });
320 }
321 }
322
323 (weight_gradients, bias_gradients)
324 }
325
326 fn update_parameters(
327 &self,
328 weights: &mut [Array2<Float>],
329 biases: &mut [Array1<Float>],
330 weight_gradients: &[Array2<Float>],
331 bias_gradients: &[Array1<Float>],
332 ) {
333 for i in 0..weights.len() {
334 for j in 0..weights[i].nrows() {
336 for k in 0..weights[i].ncols() {
337 let grad = weight_gradients[i][[j, k]];
338 let l1_penalty = self.l1_reg * weights[i][[j, k]].signum();
339 let l2_penalty = self.l2_reg * weights[i][[j, k]];
340
341 weights[i][[j, k]] -= self.learning_rate * (grad + l1_penalty + l2_penalty);
342 }
343 }
344
345 for j in 0..biases[i].len() {
347 biases[i][j] -= self.learning_rate * bias_gradients[i][j];
348 }
349 }
350 }
351
352 fn select_features_by_importance(&self, importances: &Array1<Float>) -> Vec<usize> {
353 let mut feature_indices: Vec<(usize, Float)> = importances
354 .indexed_iter()
355 .map(|(i, &importance)| (i, importance))
356 .collect();
357
358 feature_indices.sort_by(|a, b| b.1.partial_cmp(&a.1).expect("operation should succeed"));
359
360 let selected: Vec<usize> = if let Some(k) = self.k {
361 feature_indices
362 .iter()
363 .take(k.min(feature_indices.len()))
364 .map(|(i, _)| *i)
365 .collect()
366 } else {
367 feature_indices
368 .iter()
369 .filter(|(_, importance)| *importance >= self.importance_threshold)
370 .map(|(i, _)| *i)
371 .collect()
372 };
373
374 let mut selected_sorted = selected;
375 selected_sorted.sort();
376 selected_sorted
377 }
378}
379
380impl Transform<Array2<Float>> for NeuralFeatureSelector<Trained> {
381 fn transform(&self, x: &Array2<Float>) -> SklResult<Array2<Float>> {
382 let selected_features = self
383 .selected_features_
384 .as_ref()
385 .expect("operation should succeed");
386 if selected_features.is_empty() {
387 return Err(SklearsError::InvalidInput(
388 "No features were selected".to_string(),
389 ));
390 }
391
392 let selected_indices: Vec<usize> = selected_features.to_vec();
393 Ok(x.select(Axis(1), &selected_indices))
394 }
395}
396
397impl SelectorMixin for NeuralFeatureSelector<Trained> {
398 fn get_support(&self) -> SklResult<Array1<bool>> {
399 let selected_features = self
400 .selected_features_
401 .as_ref()
402 .expect("operation should succeed");
403 let n_features = self
404 .feature_importances_
405 .as_ref()
406 .expect("operation should succeed")
407 .len();
408 let mut support = Array1::from_elem(n_features, false);
409 for &idx in selected_features {
410 if idx < n_features {
411 support[idx] = true;
412 }
413 }
414 Ok(support)
415 }
416
417 fn transform_features(&self, indices: &[usize]) -> SklResult<Vec<usize>> {
418 let selected_features = self
419 .selected_features_
420 .as_ref()
421 .expect("operation should succeed");
422 Ok(indices
423 .iter()
424 .filter_map(|&idx| selected_features.iter().position(|&f| f == idx))
425 .collect())
426 }
427}
428
429#[derive(Debug, Clone)]
433pub struct AttentionFeatureSelector<State = Untrained> {
434 attention_dim: usize,
436 n_heads: usize,
438 learning_rate: f64,
440 epochs: usize,
442 temperature: f64,
444 k: Option<usize>,
446 attention_threshold: f64,
448 random_state: Option<u64>,
450 state: PhantomData<State>,
451 _attention_weights_: Option<Array2<Float>>,
453 feature_attention_: Option<Array1<Float>>,
454 selected_features_: Option<Vec<usize>>,
455}
456
457impl AttentionFeatureSelector<Untrained> {
458 pub fn new() -> Self {
460 Self {
461 attention_dim: 64,
462 n_heads: 8,
463 learning_rate: 0.001,
464 epochs: 50,
465 temperature: 1.0,
466 k: None,
467 attention_threshold: 0.01,
468 random_state: None,
469 state: PhantomData,
470 _attention_weights_: None,
471 feature_attention_: None,
472 selected_features_: None,
473 }
474 }
475
476 pub fn attention_dim(mut self, dim: usize) -> Self {
478 self.attention_dim = dim;
479 self
480 }
481
482 pub fn n_heads(mut self, n_heads: usize) -> Self {
484 self.n_heads = n_heads;
485 self
486 }
487
488 pub fn learning_rate(mut self, lr: f64) -> Self {
490 self.learning_rate = lr;
491 self
492 }
493
494 pub fn epochs(mut self, epochs: usize) -> Self {
496 self.epochs = epochs;
497 self
498 }
499
500 pub fn temperature(mut self, temperature: f64) -> Self {
502 self.temperature = temperature;
503 self
504 }
505
506 pub fn k(mut self, k: Option<usize>) -> Self {
508 self.k = k;
509 self
510 }
511
512 pub fn attention_threshold(mut self, threshold: f64) -> Self {
514 self.attention_threshold = threshold;
515 self
516 }
517
518 pub fn random_state(mut self, seed: Option<u64>) -> Self {
520 self.random_state = seed;
521 self
522 }
523}
524
525impl Default for AttentionFeatureSelector<Untrained> {
526 fn default() -> Self {
527 Self::new()
528 }
529}
530
531impl Estimator for AttentionFeatureSelector<Untrained> {
532 type Config = ();
533 type Error = SklearsError;
534 type Float = f64;
535
536 fn config(&self) -> &Self::Config {
537 &()
538 }
539}
540
541impl Fit<Array2<Float>, Array1<Float>> for AttentionFeatureSelector<Untrained> {
542 type Fitted = AttentionFeatureSelector<Trained>;
543
544 fn fit(self, x: &Array2<Float>, y: &Array1<Float>) -> SklResult<Self::Fitted> {
545 validate::check_consistent_length(x, y)?;
546
547 let mut rng = match self.random_state {
548 Some(seed) => StdRng::seed_from_u64(seed),
549 None => StdRng::from_rng(&mut thread_rng()),
550 };
551
552 let (n_samples, n_features) = x.dim();
553
554 let head_dim = self.attention_dim / self.n_heads;
556
557 let mut query_weights = Array2::zeros((n_features, self.attention_dim));
559 let mut key_weights = Array2::zeros((n_features, self.attention_dim));
560 let mut value_weights = Array2::zeros((n_features, self.attention_dim));
561
562 for w in [&mut query_weights, &mut key_weights, &mut value_weights] {
564 for elem in w.iter_mut() {
565 *elem = rng.random::<f64>() * 0.02 - 0.01;
566 }
567 }
568
569 for _epoch in 0..self.epochs {
571 let mut total_attention = Array1::zeros(n_features);
572
573 for i in 0..n_samples {
574 let input = x.row(i);
575
576 let queries = input.dot(&query_weights);
578 let keys = input.dot(&key_weights);
579 let values = input.dot(&value_weights);
580
581 let mut head_attentions = Vec::new();
583
584 for head in 0..self.n_heads {
585 let start_idx = head * head_dim;
586 let end_idx = (head + 1) * head_dim;
587
588 let q_head = queries.slice(s![start_idx..end_idx]);
589 let k_head = keys.slice(s![start_idx..end_idx]);
590 let _v_head = values.slice(s![start_idx..end_idx]);
591
592 let mut attention_scores = Array1::zeros(n_features);
594 for j in 0..n_features {
595 let score = q_head.dot(&k_head) / (head_dim as f64).sqrt();
597 attention_scores[j] = score;
598 }
599
600 let softmax_scores =
602 softmax_with_temperature(&attention_scores, self.temperature);
603 head_attentions.push(softmax_scores);
604 }
605
606 let mut avg_attention = Array1::zeros(n_features);
608 for head_attention in &head_attentions {
609 avg_attention += head_attention;
610 }
611 avg_attention /= self.n_heads as f64;
612
613 total_attention += &avg_attention;
614 }
615
616 total_attention /= n_samples as f64;
618 }
619
620 let mut feature_attention = Array1::zeros(n_features);
622 for i in 0..n_samples {
623 let input = x.row(i);
624
625 for j in 0..n_features {
627 let feature_var = input[j].abs();
628 let correlation = compute_simple_correlation(input[j], y[i]);
629 feature_attention[j] += feature_var * correlation.abs();
630 }
631 }
632
633 let attention_sum = feature_attention.sum();
635 if attention_sum > 0.0 {
636 feature_attention /= attention_sum;
637 }
638
639 let selected_features = self.select_features_by_attention(&feature_attention);
641
642 Ok(AttentionFeatureSelector {
643 attention_dim: self.attention_dim,
644 n_heads: self.n_heads,
645 learning_rate: self.learning_rate,
646 epochs: self.epochs,
647 temperature: self.temperature,
648 k: self.k,
649 attention_threshold: self.attention_threshold,
650 random_state: self.random_state,
651 state: PhantomData,
652 _attention_weights_: Some(query_weights), feature_attention_: Some(feature_attention),
654 selected_features_: Some(selected_features),
655 })
656 }
657}
658
659impl AttentionFeatureSelector<Untrained> {
660 fn select_features_by_attention(&self, attention: &Array1<Float>) -> Vec<usize> {
661 let mut feature_indices: Vec<(usize, Float)> =
662 attention.indexed_iter().map(|(i, &att)| (i, att)).collect();
663
664 feature_indices.sort_by(|a, b| b.1.partial_cmp(&a.1).expect("operation should succeed"));
665
666 let selected: Vec<usize> = if let Some(k) = self.k {
667 feature_indices
668 .iter()
669 .take(k.min(feature_indices.len()))
670 .map(|(i, _)| *i)
671 .collect()
672 } else {
673 feature_indices
674 .iter()
675 .filter(|(_, att)| *att >= self.attention_threshold)
676 .map(|(i, _)| *i)
677 .collect()
678 };
679
680 let mut selected_sorted = selected;
681 selected_sorted.sort();
682 selected_sorted
683 }
684}
685
686impl Transform<Array2<Float>> for AttentionFeatureSelector<Trained> {
687 fn transform(&self, x: &Array2<Float>) -> SklResult<Array2<Float>> {
688 let selected_features = self
689 .selected_features_
690 .as_ref()
691 .expect("operation should succeed");
692 if selected_features.is_empty() {
693 return Err(SklearsError::InvalidInput(
694 "No features were selected".to_string(),
695 ));
696 }
697
698 let selected_indices: Vec<usize> = selected_features.to_vec();
699 Ok(x.select(Axis(1), &selected_indices))
700 }
701}
702
703impl SelectorMixin for AttentionFeatureSelector<Trained> {
704 fn get_support(&self) -> SklResult<Array1<bool>> {
705 let selected_features = self
706 .selected_features_
707 .as_ref()
708 .expect("operation should succeed");
709 let n_features = self
710 .feature_attention_
711 .as_ref()
712 .expect("operation should succeed")
713 .len();
714 let mut support = Array1::from_elem(n_features, false);
715 for &idx in selected_features {
716 if idx < n_features {
717 support[idx] = true;
718 }
719 }
720 Ok(support)
721 }
722
723 fn transform_features(&self, indices: &[usize]) -> SklResult<Vec<usize>> {
724 let selected_features = self
725 .selected_features_
726 .as_ref()
727 .expect("operation should succeed");
728 Ok(indices
729 .iter()
730 .filter_map(|&idx| selected_features.iter().position(|&f| f == idx))
731 .collect())
732 }
733}
734
735#[derive(Debug, Clone)]
739pub struct RLFeatureSelector<State = Untrained> {
740 learning_rate: f64,
742 discount_factor: f64,
743 epsilon: f64,
744 epsilon_decay: f64,
745 episodes: usize,
747 max_steps: usize,
749 k: usize,
751 random_state: Option<u64>,
753 state: PhantomData<State>,
754 _q_table_: Option<HashMap<String, Array1<Float>>>,
756 selected_features_: Option<Vec<usize>>,
757 n_features_: Option<usize>,
758}
759
760impl RLFeatureSelector<Untrained> {
761 pub fn new(k: usize) -> Self {
763 Self {
764 learning_rate: 0.1,
765 discount_factor: 0.9,
766 epsilon: 1.0,
767 epsilon_decay: 0.995,
768 episodes: 100,
769 max_steps: 20,
770 k,
771 random_state: None,
772 state: PhantomData,
773 _q_table_: None,
774 selected_features_: None,
775 n_features_: None,
776 }
777 }
778
779 pub fn learning_rate(mut self, lr: f64) -> Self {
781 self.learning_rate = lr;
782 self
783 }
784
785 pub fn discount_factor(mut self, gamma: f64) -> Self {
787 self.discount_factor = gamma;
788 self
789 }
790
791 pub fn epsilon(mut self, epsilon: f64) -> Self {
793 self.epsilon = epsilon;
794 self
795 }
796
797 pub fn episodes(mut self, episodes: usize) -> Self {
799 self.episodes = episodes;
800 self
801 }
802
803 pub fn max_steps(mut self, max_steps: usize) -> Self {
805 self.max_steps = max_steps;
806 self
807 }
808
809 pub fn random_state(mut self, seed: Option<u64>) -> Self {
811 self.random_state = seed;
812 self
813 }
814}
815
816impl Estimator for RLFeatureSelector<Untrained> {
817 type Config = ();
818 type Error = SklearsError;
819 type Float = f64;
820
821 fn config(&self) -> &Self::Config {
822 &()
823 }
824}
825
826impl Fit<Array2<Float>, Array1<Float>> for RLFeatureSelector<Untrained> {
827 type Fitted = RLFeatureSelector<Trained>;
828
829 fn fit(self, x: &Array2<Float>, y: &Array1<Float>) -> SklResult<Self::Fitted> {
830 validate::check_consistent_length(x, y)?;
831
832 let mut rng = match self.random_state {
833 Some(seed) => StdRng::seed_from_u64(seed),
834 None => StdRng::from_rng(&mut thread_rng()),
835 };
836
837 let (_, n_features) = x.dim();
838 let mut q_table: HashMap<String, Array1<Float>> = HashMap::new();
839 let mut current_epsilon = self.epsilon;
840
841 for _episode in 0..self.episodes {
843 let mut selected_features = Vec::new();
844 let mut state = encode_state(&selected_features, n_features);
845
846 for _step in 0..self.max_steps {
847 if selected_features.len() >= self.k {
848 break;
849 }
850
851 let available_actions: Vec<usize> = (0..n_features)
853 .filter(|&i| !selected_features.contains(&i))
854 .collect();
855
856 if available_actions.is_empty() {
857 break;
858 }
859
860 let action = if rng.random::<f64>() < current_epsilon {
862 *available_actions
864 .choose(&mut rng)
865 .expect("operation should succeed")
866 } else {
867 let q_values = q_table
869 .entry(state.clone())
870 .or_insert_with(|| Array1::zeros(n_features));
871
872 let best_action = available_actions
873 .iter()
874 .max_by(|&&a, &&b| {
875 q_values[a]
876 .partial_cmp(&q_values[b])
877 .expect("operation should succeed")
878 })
879 .expect("operation should succeed");
880 *best_action
881 };
882
883 selected_features.push(action);
885 let new_state = encode_state(&selected_features, n_features);
886
887 let reward = self.compute_reward(x, y, &selected_features);
889
890 let current_q = q_table
892 .entry(state.clone())
893 .or_insert_with(|| Array1::zeros(n_features))[action];
894
895 let next_q_values = q_table
896 .entry(new_state.clone())
897 .or_insert_with(|| Array1::zeros(n_features));
898 let max_next_q = next_q_values
899 .iter()
900 .fold(f64::NEG_INFINITY, |a, &b| a.max(b));
901
902 let target_q = reward + self.discount_factor * max_next_q;
903 let updated_q = current_q + self.learning_rate * (target_q - current_q);
904
905 q_table
906 .entry(state.clone())
907 .or_insert_with(|| Array1::zeros(n_features))[action] = updated_q;
908
909 state = new_state;
910 }
911
912 current_epsilon *= self.epsilon_decay;
914 }
915
916 let mut best_features = Vec::new();
918 let mut current_state = encode_state(&best_features, n_features);
919
920 for _ in 0..self.k {
921 let available_actions: Vec<usize> = (0..n_features)
922 .filter(|&i| !best_features.contains(&i))
923 .collect();
924
925 if available_actions.is_empty() {
926 break;
927 }
928
929 let default_q_values = Array1::zeros(n_features);
930 let q_values = q_table.get(¤t_state).unwrap_or(&default_q_values);
931
932 let best_action = available_actions
933 .iter()
934 .max_by(|&&a, &&b| {
935 q_values[a]
936 .partial_cmp(&q_values[b])
937 .expect("operation should succeed")
938 })
939 .expect("operation should succeed");
940
941 best_features.push(*best_action);
942 current_state = encode_state(&best_features, n_features);
943 }
944
945 best_features.sort();
946
947 Ok(RLFeatureSelector {
948 learning_rate: self.learning_rate,
949 discount_factor: self.discount_factor,
950 epsilon: self.epsilon,
951 epsilon_decay: self.epsilon_decay,
952 episodes: self.episodes,
953 max_steps: self.max_steps,
954 k: self.k,
955 random_state: self.random_state,
956 state: PhantomData,
957 _q_table_: Some(q_table),
958 selected_features_: Some(best_features),
959 n_features_: Some(n_features),
960 })
961 }
962}
963
964impl RLFeatureSelector<Untrained> {
965 fn compute_reward(
966 &self,
967 x: &Array2<Float>,
968 y: &Array1<Float>,
969 selected_features: &[usize],
970 ) -> f64 {
971 if selected_features.is_empty() {
972 return 0.0;
973 }
974
975 let mut reward = 0.0;
977 for &feature_idx in selected_features {
978 let feature = x.column(feature_idx);
979 let correlation = compute_simple_correlation_array(&feature.to_owned(), y);
980 reward += correlation.abs();
981 }
982
983 reward / selected_features.len() as f64
985 }
986}
987
988impl Transform<Array2<Float>> for RLFeatureSelector<Trained> {
989 fn transform(&self, x: &Array2<Float>) -> SklResult<Array2<Float>> {
990 let selected_features = self
991 .selected_features_
992 .as_ref()
993 .expect("operation should succeed");
994 if selected_features.is_empty() {
995 return Err(SklearsError::InvalidInput(
996 "No features were selected".to_string(),
997 ));
998 }
999
1000 let selected_indices: Vec<usize> = selected_features.to_vec();
1001 Ok(x.select(Axis(1), &selected_indices))
1002 }
1003}
1004
1005impl SelectorMixin for RLFeatureSelector<Trained> {
1006 fn get_support(&self) -> SklResult<Array1<bool>> {
1007 let selected_features = self
1008 .selected_features_
1009 .as_ref()
1010 .expect("operation should succeed");
1011 let n_features = self.n_features_.expect("operation should succeed");
1012 let mut support = Array1::from_elem(n_features, false);
1013 for &idx in selected_features {
1014 if idx < n_features {
1015 support[idx] = true;
1016 }
1017 }
1018 Ok(support)
1019 }
1020
1021 fn transform_features(&self, indices: &[usize]) -> SklResult<Vec<usize>> {
1022 let selected_features = self
1023 .selected_features_
1024 .as_ref()
1025 .expect("operation should succeed");
1026 Ok(indices
1027 .iter()
1028 .filter_map(|&idx| selected_features.iter().position(|&f| f == idx))
1029 .collect())
1030 }
1031}
1032
1033#[derive(Debug, Clone)]
1036pub struct MetaLearningFeatureSelector<State = Untrained> {
1037 base_methods: Vec<String>,
1039 meta_learning_rate: f64,
1041 meta_epochs: usize,
1042 k: Option<usize>,
1044 random_state: Option<u64>,
1046 state: PhantomData<State>,
1047 _meta_weights_: Option<Array1<Float>>,
1049 base_selections_: Option<Vec<Vec<usize>>>,
1050 selected_features_: Option<Vec<usize>>,
1051}
1052
1053impl MetaLearningFeatureSelector<Untrained> {
1054 pub fn new() -> Self {
1056 Self {
1057 base_methods: vec![
1058 "correlation".to_string(),
1059 "mutual_info".to_string(),
1060 "f_test".to_string(),
1061 "chi2".to_string(),
1062 ],
1063 meta_learning_rate: 0.01,
1064 meta_epochs: 50,
1065 k: None,
1066 random_state: None,
1067 state: PhantomData,
1068 _meta_weights_: None,
1069 base_selections_: None,
1070 selected_features_: None,
1071 }
1072 }
1073
1074 pub fn base_methods(mut self, methods: Vec<String>) -> Self {
1076 self.base_methods = methods;
1077 self
1078 }
1079
1080 pub fn meta_learning_rate(mut self, lr: f64) -> Self {
1082 self.meta_learning_rate = lr;
1083 self
1084 }
1085
1086 pub fn meta_epochs(mut self, epochs: usize) -> Self {
1088 self.meta_epochs = epochs;
1089 self
1090 }
1091
1092 pub fn k(mut self, k: Option<usize>) -> Self {
1094 self.k = k;
1095 self
1096 }
1097
1098 pub fn random_state(mut self, seed: Option<u64>) -> Self {
1100 self.random_state = seed;
1101 self
1102 }
1103}
1104
1105impl Default for MetaLearningFeatureSelector<Untrained> {
1106 fn default() -> Self {
1107 Self::new()
1108 }
1109}
1110
1111impl Estimator for MetaLearningFeatureSelector<Untrained> {
1112 type Config = ();
1113 type Error = SklearsError;
1114 type Float = f64;
1115
1116 fn config(&self) -> &Self::Config {
1117 &()
1118 }
1119}
1120
1121impl Fit<Array2<Float>, Array1<Float>> for MetaLearningFeatureSelector<Untrained> {
1122 type Fitted = MetaLearningFeatureSelector<Trained>;
1123
1124 fn fit(self, x: &Array2<Float>, y: &Array1<Float>) -> SklResult<Self::Fitted> {
1125 validate::check_consistent_length(x, y)?;
1126
1127 let _rng = match self.random_state {
1128 Some(seed) => StdRng::seed_from_u64(seed),
1129 None => StdRng::from_rng(&mut thread_rng()),
1130 };
1131
1132 let (_, n_features) = x.dim();
1133
1134 let mut base_selections = Vec::new();
1136 let mut base_scores = Vec::new();
1137
1138 for method in &self.base_methods {
1139 let (selection, score) = match method.as_str() {
1140 "correlation" => apply_correlation_selection(x, y, self.k),
1141 "mutual_info" => apply_mutual_info_selection(x, y, self.k),
1142 "f_test" => apply_f_test_selection(x, y, self.k),
1143 "chi2" => apply_chi2_selection(x, y, self.k),
1144 _ => (Vec::new(), 0.0),
1145 };
1146 base_selections.push(selection);
1147 base_scores.push(score);
1148 }
1149
1150 let mut meta_weights = Array1::from_vec(vec![
1152 1.0 / self.base_methods.len() as f64;
1153 self.base_methods.len()
1154 ]);
1155
1156 for _epoch in 0..self.meta_epochs {
1158 let ensemble_selection =
1160 self.compute_ensemble_selection(&base_selections, &meta_weights, n_features);
1161
1162 let ensemble_score = evaluate_feature_selection(x, y, &ensemble_selection);
1164
1165 for i in 0..meta_weights.len() {
1167 let individual_score = base_scores[i];
1168 let gradient = individual_score - ensemble_score;
1169 meta_weights[i] += self.meta_learning_rate * gradient;
1170 }
1171
1172 let weight_sum = meta_weights.sum();
1174 if weight_sum > 0.0 {
1175 meta_weights /= weight_sum;
1176 }
1177 }
1178
1179 let selected_features =
1181 self.compute_ensemble_selection(&base_selections, &meta_weights, n_features);
1182
1183 Ok(MetaLearningFeatureSelector {
1184 base_methods: self.base_methods,
1185 meta_learning_rate: self.meta_learning_rate,
1186 meta_epochs: self.meta_epochs,
1187 k: self.k,
1188 random_state: self.random_state,
1189 state: PhantomData,
1190 _meta_weights_: Some(meta_weights),
1191 base_selections_: Some(base_selections),
1192 selected_features_: Some(selected_features),
1193 })
1194 }
1195}
1196
1197impl MetaLearningFeatureSelector<Untrained> {
1198 fn compute_ensemble_selection(
1199 &self,
1200 base_selections: &[Vec<usize>],
1201 weights: &Array1<Float>,
1202 n_features: usize,
1203 ) -> Vec<usize> {
1204 let mut feature_scores = Array1::zeros(n_features);
1205
1206 for (i, selection) in base_selections.iter().enumerate() {
1208 let weight = weights[i];
1209 for &feature_idx in selection {
1210 if feature_idx < n_features {
1211 feature_scores[feature_idx] += weight;
1212 }
1213 }
1214 }
1215
1216 let mut feature_indices: Vec<(usize, Float)> = feature_scores
1218 .indexed_iter()
1219 .map(|(i, &score)| (i, score))
1220 .collect();
1221
1222 feature_indices.sort_by(|a, b| b.1.partial_cmp(&a.1).expect("operation should succeed"));
1223
1224 let selected: Vec<usize> = if let Some(k) = self.k {
1225 feature_indices
1226 .iter()
1227 .take(k.min(feature_indices.len()))
1228 .filter(|(_, score)| *score > 0.0)
1229 .map(|(i, _)| *i)
1230 .collect()
1231 } else {
1232 feature_indices
1233 .iter()
1234 .filter(|(_, score)| *score > 0.1) .map(|(i, _)| *i)
1236 .collect()
1237 };
1238
1239 let mut selected_sorted = selected;
1240 selected_sorted.sort();
1241 selected_sorted
1242 }
1243}
1244
1245impl Transform<Array2<Float>> for MetaLearningFeatureSelector<Trained> {
1246 fn transform(&self, x: &Array2<Float>) -> SklResult<Array2<Float>> {
1247 let selected_features = self
1248 .selected_features_
1249 .as_ref()
1250 .expect("operation should succeed");
1251 if selected_features.is_empty() {
1252 return Err(SklearsError::InvalidInput(
1253 "No features were selected".to_string(),
1254 ));
1255 }
1256
1257 let selected_indices: Vec<usize> = selected_features.to_vec();
1258 Ok(x.select(Axis(1), &selected_indices))
1259 }
1260}
1261
1262impl SelectorMixin for MetaLearningFeatureSelector<Trained> {
1263 fn get_support(&self) -> SklResult<Array1<bool>> {
1264 let selected_features = self
1265 .selected_features_
1266 .as_ref()
1267 .expect("operation should succeed");
1268 let n_features = if let Some(ref selections) = self.base_selections_ {
1269 selections.iter().flatten().max().unwrap_or(&0) + 1
1270 } else {
1271 selected_features.iter().max().unwrap_or(&0) + 1
1272 };
1273
1274 let mut support = Array1::from_elem(n_features, false);
1275 for &idx in selected_features {
1276 if idx < n_features {
1277 support[idx] = true;
1278 }
1279 }
1280 Ok(support)
1281 }
1282
1283 fn transform_features(&self, indices: &[usize]) -> SklResult<Vec<usize>> {
1284 let selected_features = self
1285 .selected_features_
1286 .as_ref()
1287 .expect("operation should succeed");
1288 Ok(indices
1289 .iter()
1290 .filter_map(|&idx| selected_features.iter().position(|&f| f == idx))
1291 .collect())
1292 }
1293}
1294
1295fn softmax_with_temperature(scores: &Array1<Float>, temperature: f64) -> Array1<Float> {
1298 let max_score = scores.iter().fold(f64::NEG_INFINITY, |a, &b| a.max(b));
1299 let exp_scores: Array1<Float> = scores.mapv(|x| ((x - max_score) / temperature).exp());
1300 let sum_exp = exp_scores.sum();
1301 exp_scores / sum_exp
1302}
1303
1304fn compute_simple_correlation(x: Float, y: Float) -> Float {
1305 x * y
1307}
1308
1309pub fn compute_simple_correlation_array(x: &Array1<Float>, y: &Array1<Float>) -> Float {
1311 let n = x.len().min(y.len());
1312 if n < 2 {
1313 return 0.0;
1314 }
1315
1316 let x_mean = x.iter().take(n).sum::<Float>() / n as Float;
1317 let y_mean = y.iter().take(n).sum::<Float>() / n as Float;
1318
1319 let mut numerator = 0.0;
1320 let mut x_var = 0.0;
1321 let mut y_var = 0.0;
1322
1323 for i in 0..n {
1324 let x_i = x[i] - x_mean;
1325 let y_i = y[i] - y_mean;
1326 numerator += x_i * y_i;
1327 x_var += x_i * x_i;
1328 y_var += y_i * y_i;
1329 }
1330
1331 let denominator = (x_var * y_var).sqrt();
1332 if denominator.abs() < 1e-10 {
1333 0.0
1334 } else {
1335 numerator / denominator
1336 }
1337}
1338
1339fn encode_state(selected_features: &[usize], n_features: usize) -> String {
1340 let mut state = vec![false; n_features];
1341 for &idx in selected_features {
1342 if idx < n_features {
1343 state[idx] = true;
1344 }
1345 }
1346 state.iter().map(|&b| if b { '1' } else { '0' }).collect()
1347}
1348
1349fn apply_correlation_selection(
1351 x: &Array2<Float>,
1352 y: &Array1<Float>,
1353 k: Option<usize>,
1354) -> (Vec<usize>, f64) {
1355 let (_, n_features) = x.dim();
1356 let mut correlations = Vec::new();
1357
1358 for j in 0..n_features {
1359 let feature = x.column(j);
1360 let corr = compute_simple_correlation_array(&feature.to_owned(), y);
1361 correlations.push((j, corr.abs()));
1362 }
1363
1364 correlations.sort_by(|a, b| b.1.partial_cmp(&a.1).expect("operation should succeed"));
1365
1366 let selection_size = k.unwrap_or(n_features / 4).min(n_features);
1367 let selection: Vec<usize> = correlations
1368 .iter()
1369 .take(selection_size)
1370 .map(|(i, _)| *i)
1371 .collect();
1372 let score = correlations
1373 .iter()
1374 .take(selection_size)
1375 .map(|(_, corr)| corr)
1376 .sum::<f64>()
1377 / selection_size as f64;
1378
1379 (selection, score)
1380}
1381
1382fn apply_mutual_info_selection(
1383 x: &Array2<Float>,
1384 y: &Array1<Float>,
1385 k: Option<usize>,
1386) -> (Vec<usize>, f64) {
1387 apply_correlation_selection(x, y, k)
1389}
1390
1391fn apply_f_test_selection(
1392 x: &Array2<Float>,
1393 y: &Array1<Float>,
1394 k: Option<usize>,
1395) -> (Vec<usize>, f64) {
1396 apply_correlation_selection(x, y, k)
1398}
1399
1400fn apply_chi2_selection(
1401 x: &Array2<Float>,
1402 y: &Array1<Float>,
1403 k: Option<usize>,
1404) -> (Vec<usize>, f64) {
1405 apply_correlation_selection(x, y, k)
1407}
1408
1409fn evaluate_feature_selection(x: &Array2<Float>, y: &Array1<Float>, features: &[usize]) -> f64 {
1410 if features.is_empty() {
1411 return 0.0;
1412 }
1413
1414 let mut total_corr = 0.0;
1416 for &feature_idx in features {
1417 let feature = x.column(feature_idx);
1418 let corr = compute_simple_correlation_array(&feature.to_owned(), y);
1419 total_corr += corr.abs();
1420 }
1421
1422 total_corr / features.len() as f64
1423}