Skip to main content

sklears_feature_selection/
ml_based.rs

1//! Machine learning-based feature selection methods
2//!
3//! This module provides advanced feature selection methods using machine learning techniques
4//! including neural networks, attention mechanisms, reinforcement learning, and meta-learning.
5
6use 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/// Neural network-based feature selection using importance scoring
20/// Neural network feature importance selector
21/// Uses a simple neural network to learn feature importances through gradient analysis
22#[derive(Debug, Clone)]
23pub struct NeuralFeatureSelector<State = Untrained> {
24    /// Number of hidden layers
25    hidden_layers: Vec<usize>,
26    /// Learning rate for gradient descent
27    learning_rate: f64,
28    /// Number of training epochs
29    epochs: usize,
30    /// L1 regularization parameter for sparsity
31    l1_reg: f64,
32    /// L2 regularization parameter
33    l2_reg: f64,
34    /// Number of top features to select
35    k: Option<usize>,
36    /// Threshold for feature importance
37    importance_threshold: f64,
38    /// Random seed for reproducibility
39    random_state: Option<u64>,
40    state: PhantomData<State>,
41    // Trained state
42    _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    /// new
50    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    /// hidden_layers
69    pub fn hidden_layers(mut self, layers: Vec<usize>) -> Self {
70        self.hidden_layers = layers;
71        self
72    }
73
74    /// learning_rate
75    pub fn learning_rate(mut self, lr: f64) -> Self {
76        self.learning_rate = lr;
77        self
78    }
79
80    /// epochs
81    pub fn epochs(mut self, epochs: usize) -> Self {
82        self.epochs = epochs;
83        self
84    }
85
86    /// l1_reg
87    pub fn l1_reg(mut self, l1_reg: f64) -> Self {
88        self.l1_reg = l1_reg;
89        self
90    }
91
92    /// l2_reg
93    pub fn l2_reg(mut self, l2_reg: f64) -> Self {
94        self.l2_reg = l2_reg;
95        self
96    }
97
98    /// k
99    pub fn k(mut self, k: Option<usize>) -> Self {
100        self.k = k;
101        self
102    }
103
104    /// importance_threshold
105    pub fn importance_threshold(mut self, threshold: f64) -> Self {
106        self.importance_threshold = threshold;
107        self
108    }
109
110    /// random_state
111    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        // Initialize network architecture
147        let mut layer_sizes = vec![n_features];
148        layer_sizes.extend(&self.hidden_layers);
149        layer_sizes.push(1); // Output layer for regression/binary classification
150
151        // Initialize weights and biases
152        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            // Xavier initialization
160            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        // Training loop
175        for epoch in 0..self.epochs {
176            let mut total_loss = 0.0;
177
178            // Forward and backward pass for each sample
179            for i in 0..n_samples {
180                let input = x.row(i).to_owned();
181                let target = y[i];
182
183                // Forward pass
184                let (activations, z_values) = self.forward_pass(&input, &weights, &biases);
185                let prediction = activations.last().expect("operation should succeed")[0];
186
187                // Compute loss (MSE for regression)
188                let loss = 0.5 * (prediction - target).powi(2);
189                total_loss += loss;
190
191                // Backward pass
192                let gradients =
193                    self.backward_pass(&input, target, &activations, &z_values, &weights);
194
195                // Update weights and biases
196                self.update_parameters(&mut weights, &mut biases, &gradients.0, &gradients.1);
197            }
198
199            // Optional: Early stopping or learning rate decay
200            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        // Compute feature importances using input layer weights
209        let input_weights = &weights[0];
210        let mut feature_importances = Array1::zeros(n_features);
211
212        for i in 0..n_features {
213            // Compute importance as the sum of absolute weights from this feature
214            let importance: f64 = input_weights.row(i).iter().map(|&w| w.abs()).sum();
215            feature_importances[i] = importance;
216        }
217
218        // Normalize importances
219        let importance_sum: f64 = feature_importances.sum();
220        if importance_sum > 0.0 {
221            feature_importances /= importance_sum;
222        }
223
224        // Select features based on importance
225        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            // Linear transformation: z = W^T * a + b
259            let z = w.t().dot(&current_activation) + b;
260            z_values.push(z.clone());
261
262            // Activation function (ReLU for hidden layers, linear for output)
263            let activation = if i < weights.len() - 1 {
264                z.mapv(|x| x.max(0.0)) // ReLU
265            } else {
266                z // Linear for output
267            };
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        // Output layer error
289        let output_error = activations.last().expect("operation should succeed")[0] - target;
290        let mut delta = Array1::from_vec(vec![output_error]);
291
292        // Backward propagation
293        for i in (0..n_layers).rev() {
294            // Compute gradients for current layer
295            let prev_activation = &activations[i];
296
297            // Weight gradients: dW = a_prev * delta^T
298            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: db = delta
305            bias_gradients[i] = delta.clone();
306
307            // Propagate error to previous layer
308            if i > 0 {
309                // delta_prev = W * delta * activation_derivative
310                let new_delta = weights[i].dot(&delta);
311
312                // Apply ReLU derivative (1 if z > 0, 0 otherwise)
313                let z_prev = &z_values[i - 1];
314                for j in 0..new_delta.len() {
315                    if z_prev[j] <= 0.0 {
316                        // delta[j] = 0.0; // This would modify the array, but we need to create a new one
317                    }
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            // Update weights with L1 and L2 regularization
335            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            // Update biases
346            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/// Attention-based feature selection
430/// Attention mechanism for feature selection
431/// Uses attention weights to identify important features
432#[derive(Debug, Clone)]
433pub struct AttentionFeatureSelector<State = Untrained> {
434    /// Dimension of attention space
435    attention_dim: usize,
436    /// Number of attention heads
437    n_heads: usize,
438    /// Learning rate
439    learning_rate: f64,
440    /// Number of training epochs
441    epochs: usize,
442    /// Temperature parameter for attention softmax
443    temperature: f64,
444    /// Number of top features to select
445    k: Option<usize>,
446    /// Threshold for attention weights
447    attention_threshold: f64,
448    /// Random seed
449    random_state: Option<u64>,
450    state: PhantomData<State>,
451    // Trained state
452    _attention_weights_: Option<Array2<Float>>,
453    feature_attention_: Option<Array1<Float>>,
454    selected_features_: Option<Vec<usize>>,
455}
456
457impl AttentionFeatureSelector<Untrained> {
458    /// new
459    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    /// attention_dim
477    pub fn attention_dim(mut self, dim: usize) -> Self {
478        self.attention_dim = dim;
479        self
480    }
481
482    /// n_heads
483    pub fn n_heads(mut self, n_heads: usize) -> Self {
484        self.n_heads = n_heads;
485        self
486    }
487
488    /// learning_rate
489    pub fn learning_rate(mut self, lr: f64) -> Self {
490        self.learning_rate = lr;
491        self
492    }
493
494    /// epochs
495    pub fn epochs(mut self, epochs: usize) -> Self {
496        self.epochs = epochs;
497        self
498    }
499
500    /// temperature
501    pub fn temperature(mut self, temperature: f64) -> Self {
502        self.temperature = temperature;
503        self
504    }
505
506    /// k
507    pub fn k(mut self, k: Option<usize>) -> Self {
508        self.k = k;
509        self
510    }
511
512    /// attention_threshold
513    pub fn attention_threshold(mut self, threshold: f64) -> Self {
514        self.attention_threshold = threshold;
515        self
516    }
517
518    /// random_state
519    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        // Initialize attention parameters
555        let head_dim = self.attention_dim / self.n_heads;
556
557        // Query, Key, Value matrices for each head
558        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        // Initialize with small random values
563        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        // Training loop
570        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                // Compute queries, keys, values
577                let queries = input.dot(&query_weights);
578                let keys = input.dot(&key_weights);
579                let values = input.dot(&value_weights);
580
581                // Multi-head attention
582                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                    // Compute attention scores
593                    let mut attention_scores = Array1::zeros(n_features);
594                    for j in 0..n_features {
595                        // Simplified attention: dot product of query and key projections
596                        let score = q_head.dot(&k_head) / (head_dim as f64).sqrt();
597                        attention_scores[j] = score;
598                    }
599
600                    // Apply softmax with temperature
601                    let softmax_scores =
602                        softmax_with_temperature(&attention_scores, self.temperature);
603                    head_attentions.push(softmax_scores);
604                }
605
606                // Average attention across heads
607                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            // Average attention across samples
617            total_attention /= n_samples as f64;
618        }
619
620        // Final attention computation (simplified version)
621        let mut feature_attention = Array1::zeros(n_features);
622        for i in 0..n_samples {
623            let input = x.row(i);
624
625            // Compute simple attention based on feature variance and correlation with target
626            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        // Normalize attention weights
634        let attention_sum = feature_attention.sum();
635        if attention_sum > 0.0 {
636            feature_attention /= attention_sum;
637        }
638
639        // Select features based on attention weights
640        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), // Store one of the weight matrices
653            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/// Reinforcement learning-based feature selection
736/// Q-learning based feature selection
737/// Treats feature selection as a sequential decision problem
738#[derive(Debug, Clone)]
739pub struct RLFeatureSelector<State = Untrained> {
740    /// Q-learning parameters
741    learning_rate: f64,
742    discount_factor: f64,
743    epsilon: f64,
744    epsilon_decay: f64,
745    /// Number of episodes for training
746    episodes: usize,
747    /// Maximum steps per episode
748    max_steps: usize,
749    /// Number of features to select
750    k: usize,
751    /// Random seed
752    random_state: Option<u64>,
753    state: PhantomData<State>,
754    // Trained state
755    _q_table_: Option<HashMap<String, Array1<Float>>>,
756    selected_features_: Option<Vec<usize>>,
757    n_features_: Option<usize>,
758}
759
760impl RLFeatureSelector<Untrained> {
761    /// new
762    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    /// learning_rate
780    pub fn learning_rate(mut self, lr: f64) -> Self {
781        self.learning_rate = lr;
782        self
783    }
784
785    /// discount_factor
786    pub fn discount_factor(mut self, gamma: f64) -> Self {
787        self.discount_factor = gamma;
788        self
789    }
790
791    /// epsilon
792    pub fn epsilon(mut self, epsilon: f64) -> Self {
793        self.epsilon = epsilon;
794        self
795    }
796
797    /// episodes
798    pub fn episodes(mut self, episodes: usize) -> Self {
799        self.episodes = episodes;
800        self
801    }
802
803    /// max_steps
804    pub fn max_steps(mut self, max_steps: usize) -> Self {
805        self.max_steps = max_steps;
806        self
807    }
808
809    /// random_state
810    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        // Q-learning training
842        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                // Get available actions (unselected features)
852                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                // Epsilon-greedy action selection
861                let action = if rng.random::<f64>() < current_epsilon {
862                    // Random action
863                    *available_actions
864                        .choose(&mut rng)
865                        .expect("operation should succeed")
866                } else {
867                    // Greedy action (highest Q-value)
868                    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                // Take action
884                selected_features.push(action);
885                let new_state = encode_state(&selected_features, n_features);
886
887                // Compute reward
888                let reward = self.compute_reward(x, y, &selected_features);
889
890                // Q-learning update
891                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            // Decay epsilon
913            current_epsilon *= self.epsilon_decay;
914        }
915
916        // Extract best feature selection
917        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(&current_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        // Simple reward: correlation of selected features with target
976        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        // Normalize by number of features to encourage efficiency
984        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/// Meta-learning feature selector
1034/// Meta-learning approach that learns from multiple feature selection tasks
1035#[derive(Debug, Clone)]
1036pub struct MetaLearningFeatureSelector<State = Untrained> {
1037    /// Base selectors to use in meta-learning
1038    base_methods: Vec<String>,
1039    /// Meta-model parameters
1040    meta_learning_rate: f64,
1041    meta_epochs: usize,
1042    /// Number of top features to select
1043    k: Option<usize>,
1044    /// Random seed
1045    random_state: Option<u64>,
1046    state: PhantomData<State>,
1047    // Trained state
1048    _meta_weights_: Option<Array1<Float>>,
1049    base_selections_: Option<Vec<Vec<usize>>>,
1050    selected_features_: Option<Vec<usize>>,
1051}
1052
1053impl MetaLearningFeatureSelector<Untrained> {
1054    /// new
1055    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    /// base_methods
1075    pub fn base_methods(mut self, methods: Vec<String>) -> Self {
1076        self.base_methods = methods;
1077        self
1078    }
1079
1080    /// meta_learning_rate
1081    pub fn meta_learning_rate(mut self, lr: f64) -> Self {
1082        self.meta_learning_rate = lr;
1083        self
1084    }
1085
1086    /// meta_epochs
1087    pub fn meta_epochs(mut self, epochs: usize) -> Self {
1088        self.meta_epochs = epochs;
1089        self
1090    }
1091
1092    /// k
1093    pub fn k(mut self, k: Option<usize>) -> Self {
1094        self.k = k;
1095        self
1096    }
1097
1098    /// random_state
1099    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        // Apply base feature selection methods
1135        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        // Initialize meta-weights
1151        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        // Meta-learning: optimize weights based on performance
1157        for _epoch in 0..self.meta_epochs {
1158            // Compute weighted ensemble selection
1159            let ensemble_selection =
1160                self.compute_ensemble_selection(&base_selections, &meta_weights, n_features);
1161
1162            // Compute ensemble performance
1163            let ensemble_score = evaluate_feature_selection(x, y, &ensemble_selection);
1164
1165            // Update meta-weights using gradient ascent
1166            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            // Normalize weights
1173            let weight_sum = meta_weights.sum();
1174            if weight_sum > 0.0 {
1175                meta_weights /= weight_sum;
1176            }
1177        }
1178
1179        // Final ensemble selection
1180        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        // Weight the contributions from each base method
1207        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        // Select top features
1217        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) // Threshold for selection
1235                .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
1295// Helper functions
1296
1297fn 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    // Simplified correlation for single values - in practice would need proper correlation
1306    x * y
1307}
1308
1309/// compute_simple_correlation_array
1310pub 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
1349// Simplified base method implementations
1350fn 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    // Simplified mutual information - in practice would use proper MI estimation
1388    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    // Simplified F-test - in practice would use proper F-statistic
1397    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    // Simplified chi-squared test - in practice would use proper chi-squared statistic
1406    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    // Simple evaluation: average correlation of selected features with target
1415    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}