use crate::base::SelectorMixin;
use scirs2_core::ndarray::{s, Array1, Array2, Axis};
use scirs2_core::rand_prelude::IndexedRandom;
use scirs2_core::random::{rngs::StdRng, thread_rng, RngExt, SeedableRng};
use sklears_core::{
error::{validate, Result as SklResult, SklearsError},
traits::{Estimator, Fit, Trained, Transform, Untrained},
types::Float,
};
use std::collections::HashMap;
use std::marker::PhantomData;
#[derive(Debug, Clone)]
pub struct NeuralFeatureSelector<State = Untrained> {
hidden_layers: Vec<usize>,
learning_rate: f64,
epochs: usize,
l1_reg: f64,
l2_reg: f64,
k: Option<usize>,
importance_threshold: f64,
random_state: Option<u64>,
state: PhantomData<State>,
_weights_: Option<Vec<Array2<Float>>>,
_biases_: Option<Vec<Array1<Float>>>,
feature_importances_: Option<Array1<Float>>,
selected_features_: Option<Vec<usize>>,
}
impl NeuralFeatureSelector<Untrained> {
pub fn new() -> Self {
Self {
hidden_layers: vec![64, 32],
learning_rate: 0.001,
epochs: 100,
l1_reg: 0.01,
l2_reg: 0.01,
k: None,
importance_threshold: 0.01,
random_state: None,
state: PhantomData,
_weights_: None,
_biases_: None,
feature_importances_: None,
selected_features_: None,
}
}
pub fn hidden_layers(mut self, layers: Vec<usize>) -> Self {
self.hidden_layers = layers;
self
}
pub fn learning_rate(mut self, lr: f64) -> Self {
self.learning_rate = lr;
self
}
pub fn epochs(mut self, epochs: usize) -> Self {
self.epochs = epochs;
self
}
pub fn l1_reg(mut self, l1_reg: f64) -> Self {
self.l1_reg = l1_reg;
self
}
pub fn l2_reg(mut self, l2_reg: f64) -> Self {
self.l2_reg = l2_reg;
self
}
pub fn k(mut self, k: Option<usize>) -> Self {
self.k = k;
self
}
pub fn importance_threshold(mut self, threshold: f64) -> Self {
self.importance_threshold = threshold;
self
}
pub fn random_state(mut self, seed: Option<u64>) -> Self {
self.random_state = seed;
self
}
}
impl Default for NeuralFeatureSelector<Untrained> {
fn default() -> Self {
Self::new()
}
}
impl Estimator for NeuralFeatureSelector<Untrained> {
type Config = ();
type Error = SklearsError;
type Float = f64;
fn config(&self) -> &Self::Config {
&()
}
}
impl Fit<Array2<Float>, Array1<Float>> for NeuralFeatureSelector<Untrained> {
type Fitted = NeuralFeatureSelector<Trained>;
fn fit(self, x: &Array2<Float>, y: &Array1<Float>) -> SklResult<Self::Fitted> {
validate::check_consistent_length(x, y)?;
let mut rng = match self.random_state {
Some(seed) => StdRng::seed_from_u64(seed),
None => StdRng::from_rng(&mut thread_rng()),
};
let (n_samples, n_features) = x.dim();
let mut layer_sizes = vec![n_features];
layer_sizes.extend(&self.hidden_layers);
layer_sizes.push(1);
let mut weights = Vec::new();
let mut biases = Vec::new();
for i in 0..(layer_sizes.len() - 1) {
let input_size = layer_sizes[i];
let output_size = layer_sizes[i + 1];
let scale = (2.0 / (input_size + output_size) as f64).sqrt();
let mut weight_matrix = Array2::zeros((input_size, output_size));
for elem in weight_matrix.iter_mut() {
*elem = rng.random::<f64>() * 2.0 * scale - scale;
}
weights.push(weight_matrix);
let mut bias_vector = Array1::zeros(output_size);
for elem in bias_vector.iter_mut() {
*elem = rng.random::<f64>() * 0.1 - 0.05;
}
biases.push(bias_vector);
}
for epoch in 0..self.epochs {
let mut total_loss = 0.0;
for i in 0..n_samples {
let input = x.row(i).to_owned();
let target = y[i];
let (activations, z_values) = self.forward_pass(&input, &weights, &biases);
let prediction = activations.last().expect("operation should succeed")[0];
let loss = 0.5 * (prediction - target).powi(2);
total_loss += loss;
let gradients =
self.backward_pass(&input, target, &activations, &z_values, &weights);
self.update_parameters(&mut weights, &mut biases, &gradients.0, &gradients.1);
}
if epoch % 10 == 0 {
let avg_loss = total_loss / n_samples as f64;
if avg_loss < 1e-6 {
break;
}
}
}
let input_weights = &weights[0];
let mut feature_importances = Array1::zeros(n_features);
for i in 0..n_features {
let importance: f64 = input_weights.row(i).iter().map(|&w| w.abs()).sum();
feature_importances[i] = importance;
}
let importance_sum: f64 = feature_importances.sum();
if importance_sum > 0.0 {
feature_importances /= importance_sum;
}
let selected_features = self.select_features_by_importance(&feature_importances);
Ok(NeuralFeatureSelector {
hidden_layers: self.hidden_layers,
learning_rate: self.learning_rate,
epochs: self.epochs,
l1_reg: self.l1_reg,
l2_reg: self.l2_reg,
k: self.k,
importance_threshold: self.importance_threshold,
random_state: self.random_state,
state: PhantomData,
_weights_: Some(weights),
_biases_: Some(biases),
feature_importances_: Some(feature_importances),
selected_features_: Some(selected_features),
})
}
}
impl NeuralFeatureSelector<Untrained> {
fn forward_pass(
&self,
input: &Array1<Float>,
weights: &[Array2<Float>],
biases: &[Array1<Float>],
) -> (Vec<Array1<Float>>, Vec<Array1<Float>>) {
let mut activations = vec![input.clone()];
let mut z_values = Vec::new();
let mut current_activation = input.clone();
for (i, (w, b)) in weights.iter().zip(biases.iter()).enumerate() {
let z = w.t().dot(¤t_activation) + b;
z_values.push(z.clone());
let activation = if i < weights.len() - 1 {
z.mapv(|x| x.max(0.0)) } else {
z };
activations.push(activation.clone());
current_activation = activation;
}
(activations, z_values)
}
fn backward_pass(
&self,
_input: &Array1<Float>,
target: Float,
activations: &[Array1<Float>],
z_values: &[Array1<Float>],
weights: &[Array2<Float>],
) -> (Vec<Array2<Float>>, Vec<Array1<Float>>) {
let n_layers = weights.len();
let mut weight_gradients = vec![Array2::zeros(weights[0].dim()); n_layers];
let mut bias_gradients = vec![Array1::zeros(z_values[0].len()); n_layers];
let output_error = activations.last().expect("operation should succeed")[0] - target;
let mut delta = Array1::from_vec(vec![output_error]);
for i in (0..n_layers).rev() {
let prev_activation = &activations[i];
for j in 0..weight_gradients[i].nrows() {
for k in 0..weight_gradients[i].ncols() {
weight_gradients[i][[j, k]] = prev_activation[j] * delta[k];
}
}
bias_gradients[i] = delta.clone();
if i > 0 {
let new_delta = weights[i].dot(&delta);
let z_prev = &z_values[i - 1];
for j in 0..new_delta.len() {
if z_prev[j] <= 0.0 {
}
}
delta = new_delta.mapv(|x| if x > 0.0 { x } else { 0.0 });
}
}
(weight_gradients, bias_gradients)
}
fn update_parameters(
&self,
weights: &mut [Array2<Float>],
biases: &mut [Array1<Float>],
weight_gradients: &[Array2<Float>],
bias_gradients: &[Array1<Float>],
) {
for i in 0..weights.len() {
for j in 0..weights[i].nrows() {
for k in 0..weights[i].ncols() {
let grad = weight_gradients[i][[j, k]];
let l1_penalty = self.l1_reg * weights[i][[j, k]].signum();
let l2_penalty = self.l2_reg * weights[i][[j, k]];
weights[i][[j, k]] -= self.learning_rate * (grad + l1_penalty + l2_penalty);
}
}
for j in 0..biases[i].len() {
biases[i][j] -= self.learning_rate * bias_gradients[i][j];
}
}
}
fn select_features_by_importance(&self, importances: &Array1<Float>) -> Vec<usize> {
let mut feature_indices: Vec<(usize, Float)> = importances
.indexed_iter()
.map(|(i, &importance)| (i, importance))
.collect();
feature_indices.sort_by(|a, b| b.1.partial_cmp(&a.1).expect("operation should succeed"));
let selected: Vec<usize> = if let Some(k) = self.k {
feature_indices
.iter()
.take(k.min(feature_indices.len()))
.map(|(i, _)| *i)
.collect()
} else {
feature_indices
.iter()
.filter(|(_, importance)| *importance >= self.importance_threshold)
.map(|(i, _)| *i)
.collect()
};
let mut selected_sorted = selected;
selected_sorted.sort();
selected_sorted
}
}
impl Transform<Array2<Float>> for NeuralFeatureSelector<Trained> {
fn transform(&self, x: &Array2<Float>) -> SklResult<Array2<Float>> {
let selected_features = self
.selected_features_
.as_ref()
.expect("operation should succeed");
if selected_features.is_empty() {
return Err(SklearsError::InvalidInput(
"No features were selected".to_string(),
));
}
let selected_indices: Vec<usize> = selected_features.to_vec();
Ok(x.select(Axis(1), &selected_indices))
}
}
impl SelectorMixin for NeuralFeatureSelector<Trained> {
fn get_support(&self) -> SklResult<Array1<bool>> {
let selected_features = self
.selected_features_
.as_ref()
.expect("operation should succeed");
let n_features = self
.feature_importances_
.as_ref()
.expect("operation should succeed")
.len();
let mut support = Array1::from_elem(n_features, false);
for &idx in selected_features {
if idx < n_features {
support[idx] = true;
}
}
Ok(support)
}
fn transform_features(&self, indices: &[usize]) -> SklResult<Vec<usize>> {
let selected_features = self
.selected_features_
.as_ref()
.expect("operation should succeed");
Ok(indices
.iter()
.filter_map(|&idx| selected_features.iter().position(|&f| f == idx))
.collect())
}
}
#[derive(Debug, Clone)]
pub struct AttentionFeatureSelector<State = Untrained> {
attention_dim: usize,
n_heads: usize,
learning_rate: f64,
epochs: usize,
temperature: f64,
k: Option<usize>,
attention_threshold: f64,
random_state: Option<u64>,
state: PhantomData<State>,
_attention_weights_: Option<Array2<Float>>,
feature_attention_: Option<Array1<Float>>,
selected_features_: Option<Vec<usize>>,
}
impl AttentionFeatureSelector<Untrained> {
pub fn new() -> Self {
Self {
attention_dim: 64,
n_heads: 8,
learning_rate: 0.001,
epochs: 50,
temperature: 1.0,
k: None,
attention_threshold: 0.01,
random_state: None,
state: PhantomData,
_attention_weights_: None,
feature_attention_: None,
selected_features_: None,
}
}
pub fn attention_dim(mut self, dim: usize) -> Self {
self.attention_dim = dim;
self
}
pub fn n_heads(mut self, n_heads: usize) -> Self {
self.n_heads = n_heads;
self
}
pub fn learning_rate(mut self, lr: f64) -> Self {
self.learning_rate = lr;
self
}
pub fn epochs(mut self, epochs: usize) -> Self {
self.epochs = epochs;
self
}
pub fn temperature(mut self, temperature: f64) -> Self {
self.temperature = temperature;
self
}
pub fn k(mut self, k: Option<usize>) -> Self {
self.k = k;
self
}
pub fn attention_threshold(mut self, threshold: f64) -> Self {
self.attention_threshold = threshold;
self
}
pub fn random_state(mut self, seed: Option<u64>) -> Self {
self.random_state = seed;
self
}
}
impl Default for AttentionFeatureSelector<Untrained> {
fn default() -> Self {
Self::new()
}
}
impl Estimator for AttentionFeatureSelector<Untrained> {
type Config = ();
type Error = SklearsError;
type Float = f64;
fn config(&self) -> &Self::Config {
&()
}
}
impl Fit<Array2<Float>, Array1<Float>> for AttentionFeatureSelector<Untrained> {
type Fitted = AttentionFeatureSelector<Trained>;
fn fit(self, x: &Array2<Float>, y: &Array1<Float>) -> SklResult<Self::Fitted> {
validate::check_consistent_length(x, y)?;
let mut rng = match self.random_state {
Some(seed) => StdRng::seed_from_u64(seed),
None => StdRng::from_rng(&mut thread_rng()),
};
let (n_samples, n_features) = x.dim();
let head_dim = self.attention_dim / self.n_heads;
let mut query_weights = Array2::zeros((n_features, self.attention_dim));
let mut key_weights = Array2::zeros((n_features, self.attention_dim));
let mut value_weights = Array2::zeros((n_features, self.attention_dim));
for w in [&mut query_weights, &mut key_weights, &mut value_weights] {
for elem in w.iter_mut() {
*elem = rng.random::<f64>() * 0.02 - 0.01;
}
}
for _epoch in 0..self.epochs {
let mut total_attention = Array1::zeros(n_features);
for i in 0..n_samples {
let input = x.row(i);
let queries = input.dot(&query_weights);
let keys = input.dot(&key_weights);
let values = input.dot(&value_weights);
let mut head_attentions = Vec::new();
for head in 0..self.n_heads {
let start_idx = head * head_dim;
let end_idx = (head + 1) * head_dim;
let q_head = queries.slice(s![start_idx..end_idx]);
let k_head = keys.slice(s![start_idx..end_idx]);
let _v_head = values.slice(s![start_idx..end_idx]);
let mut attention_scores = Array1::zeros(n_features);
for j in 0..n_features {
let score = q_head.dot(&k_head) / (head_dim as f64).sqrt();
attention_scores[j] = score;
}
let softmax_scores =
softmax_with_temperature(&attention_scores, self.temperature);
head_attentions.push(softmax_scores);
}
let mut avg_attention = Array1::zeros(n_features);
for head_attention in &head_attentions {
avg_attention += head_attention;
}
avg_attention /= self.n_heads as f64;
total_attention += &avg_attention;
}
total_attention /= n_samples as f64;
}
let mut feature_attention = Array1::zeros(n_features);
for i in 0..n_samples {
let input = x.row(i);
for j in 0..n_features {
let feature_var = input[j].abs();
let correlation = compute_simple_correlation(input[j], y[i]);
feature_attention[j] += feature_var * correlation.abs();
}
}
let attention_sum = feature_attention.sum();
if attention_sum > 0.0 {
feature_attention /= attention_sum;
}
let selected_features = self.select_features_by_attention(&feature_attention);
Ok(AttentionFeatureSelector {
attention_dim: self.attention_dim,
n_heads: self.n_heads,
learning_rate: self.learning_rate,
epochs: self.epochs,
temperature: self.temperature,
k: self.k,
attention_threshold: self.attention_threshold,
random_state: self.random_state,
state: PhantomData,
_attention_weights_: Some(query_weights), feature_attention_: Some(feature_attention),
selected_features_: Some(selected_features),
})
}
}
impl AttentionFeatureSelector<Untrained> {
fn select_features_by_attention(&self, attention: &Array1<Float>) -> Vec<usize> {
let mut feature_indices: Vec<(usize, Float)> =
attention.indexed_iter().map(|(i, &att)| (i, att)).collect();
feature_indices.sort_by(|a, b| b.1.partial_cmp(&a.1).expect("operation should succeed"));
let selected: Vec<usize> = if let Some(k) = self.k {
feature_indices
.iter()
.take(k.min(feature_indices.len()))
.map(|(i, _)| *i)
.collect()
} else {
feature_indices
.iter()
.filter(|(_, att)| *att >= self.attention_threshold)
.map(|(i, _)| *i)
.collect()
};
let mut selected_sorted = selected;
selected_sorted.sort();
selected_sorted
}
}
impl Transform<Array2<Float>> for AttentionFeatureSelector<Trained> {
fn transform(&self, x: &Array2<Float>) -> SklResult<Array2<Float>> {
let selected_features = self
.selected_features_
.as_ref()
.expect("operation should succeed");
if selected_features.is_empty() {
return Err(SklearsError::InvalidInput(
"No features were selected".to_string(),
));
}
let selected_indices: Vec<usize> = selected_features.to_vec();
Ok(x.select(Axis(1), &selected_indices))
}
}
impl SelectorMixin for AttentionFeatureSelector<Trained> {
fn get_support(&self) -> SklResult<Array1<bool>> {
let selected_features = self
.selected_features_
.as_ref()
.expect("operation should succeed");
let n_features = self
.feature_attention_
.as_ref()
.expect("operation should succeed")
.len();
let mut support = Array1::from_elem(n_features, false);
for &idx in selected_features {
if idx < n_features {
support[idx] = true;
}
}
Ok(support)
}
fn transform_features(&self, indices: &[usize]) -> SklResult<Vec<usize>> {
let selected_features = self
.selected_features_
.as_ref()
.expect("operation should succeed");
Ok(indices
.iter()
.filter_map(|&idx| selected_features.iter().position(|&f| f == idx))
.collect())
}
}
#[derive(Debug, Clone)]
pub struct RLFeatureSelector<State = Untrained> {
learning_rate: f64,
discount_factor: f64,
epsilon: f64,
epsilon_decay: f64,
episodes: usize,
max_steps: usize,
k: usize,
random_state: Option<u64>,
state: PhantomData<State>,
_q_table_: Option<HashMap<String, Array1<Float>>>,
selected_features_: Option<Vec<usize>>,
n_features_: Option<usize>,
}
impl RLFeatureSelector<Untrained> {
pub fn new(k: usize) -> Self {
Self {
learning_rate: 0.1,
discount_factor: 0.9,
epsilon: 1.0,
epsilon_decay: 0.995,
episodes: 100,
max_steps: 20,
k,
random_state: None,
state: PhantomData,
_q_table_: None,
selected_features_: None,
n_features_: None,
}
}
pub fn learning_rate(mut self, lr: f64) -> Self {
self.learning_rate = lr;
self
}
pub fn discount_factor(mut self, gamma: f64) -> Self {
self.discount_factor = gamma;
self
}
pub fn epsilon(mut self, epsilon: f64) -> Self {
self.epsilon = epsilon;
self
}
pub fn episodes(mut self, episodes: usize) -> Self {
self.episodes = episodes;
self
}
pub fn max_steps(mut self, max_steps: usize) -> Self {
self.max_steps = max_steps;
self
}
pub fn random_state(mut self, seed: Option<u64>) -> Self {
self.random_state = seed;
self
}
}
impl Estimator for RLFeatureSelector<Untrained> {
type Config = ();
type Error = SklearsError;
type Float = f64;
fn config(&self) -> &Self::Config {
&()
}
}
impl Fit<Array2<Float>, Array1<Float>> for RLFeatureSelector<Untrained> {
type Fitted = RLFeatureSelector<Trained>;
fn fit(self, x: &Array2<Float>, y: &Array1<Float>) -> SklResult<Self::Fitted> {
validate::check_consistent_length(x, y)?;
let mut rng = match self.random_state {
Some(seed) => StdRng::seed_from_u64(seed),
None => StdRng::from_rng(&mut thread_rng()),
};
let (_, n_features) = x.dim();
let mut q_table: HashMap<String, Array1<Float>> = HashMap::new();
let mut current_epsilon = self.epsilon;
for _episode in 0..self.episodes {
let mut selected_features = Vec::new();
let mut state = encode_state(&selected_features, n_features);
for _step in 0..self.max_steps {
if selected_features.len() >= self.k {
break;
}
let available_actions: Vec<usize> = (0..n_features)
.filter(|&i| !selected_features.contains(&i))
.collect();
if available_actions.is_empty() {
break;
}
let action = if rng.random::<f64>() < current_epsilon {
*available_actions
.choose(&mut rng)
.expect("operation should succeed")
} else {
let q_values = q_table
.entry(state.clone())
.or_insert_with(|| Array1::zeros(n_features));
let best_action = available_actions
.iter()
.max_by(|&&a, &&b| {
q_values[a]
.partial_cmp(&q_values[b])
.expect("operation should succeed")
})
.expect("operation should succeed");
*best_action
};
selected_features.push(action);
let new_state = encode_state(&selected_features, n_features);
let reward = self.compute_reward(x, y, &selected_features);
let current_q = q_table
.entry(state.clone())
.or_insert_with(|| Array1::zeros(n_features))[action];
let next_q_values = q_table
.entry(new_state.clone())
.or_insert_with(|| Array1::zeros(n_features));
let max_next_q = next_q_values
.iter()
.fold(f64::NEG_INFINITY, |a, &b| a.max(b));
let target_q = reward + self.discount_factor * max_next_q;
let updated_q = current_q + self.learning_rate * (target_q - current_q);
q_table
.entry(state.clone())
.or_insert_with(|| Array1::zeros(n_features))[action] = updated_q;
state = new_state;
}
current_epsilon *= self.epsilon_decay;
}
let mut best_features = Vec::new();
let mut current_state = encode_state(&best_features, n_features);
for _ in 0..self.k {
let available_actions: Vec<usize> = (0..n_features)
.filter(|&i| !best_features.contains(&i))
.collect();
if available_actions.is_empty() {
break;
}
let default_q_values = Array1::zeros(n_features);
let q_values = q_table.get(¤t_state).unwrap_or(&default_q_values);
let best_action = available_actions
.iter()
.max_by(|&&a, &&b| {
q_values[a]
.partial_cmp(&q_values[b])
.expect("operation should succeed")
})
.expect("operation should succeed");
best_features.push(*best_action);
current_state = encode_state(&best_features, n_features);
}
best_features.sort();
Ok(RLFeatureSelector {
learning_rate: self.learning_rate,
discount_factor: self.discount_factor,
epsilon: self.epsilon,
epsilon_decay: self.epsilon_decay,
episodes: self.episodes,
max_steps: self.max_steps,
k: self.k,
random_state: self.random_state,
state: PhantomData,
_q_table_: Some(q_table),
selected_features_: Some(best_features),
n_features_: Some(n_features),
})
}
}
impl RLFeatureSelector<Untrained> {
fn compute_reward(
&self,
x: &Array2<Float>,
y: &Array1<Float>,
selected_features: &[usize],
) -> f64 {
if selected_features.is_empty() {
return 0.0;
}
let mut reward = 0.0;
for &feature_idx in selected_features {
let feature = x.column(feature_idx);
let correlation = compute_simple_correlation_array(&feature.to_owned(), y);
reward += correlation.abs();
}
reward / selected_features.len() as f64
}
}
impl Transform<Array2<Float>> for RLFeatureSelector<Trained> {
fn transform(&self, x: &Array2<Float>) -> SklResult<Array2<Float>> {
let selected_features = self
.selected_features_
.as_ref()
.expect("operation should succeed");
if selected_features.is_empty() {
return Err(SklearsError::InvalidInput(
"No features were selected".to_string(),
));
}
let selected_indices: Vec<usize> = selected_features.to_vec();
Ok(x.select(Axis(1), &selected_indices))
}
}
impl SelectorMixin for RLFeatureSelector<Trained> {
fn get_support(&self) -> SklResult<Array1<bool>> {
let selected_features = self
.selected_features_
.as_ref()
.expect("operation should succeed");
let n_features = self.n_features_.expect("operation should succeed");
let mut support = Array1::from_elem(n_features, false);
for &idx in selected_features {
if idx < n_features {
support[idx] = true;
}
}
Ok(support)
}
fn transform_features(&self, indices: &[usize]) -> SklResult<Vec<usize>> {
let selected_features = self
.selected_features_
.as_ref()
.expect("operation should succeed");
Ok(indices
.iter()
.filter_map(|&idx| selected_features.iter().position(|&f| f == idx))
.collect())
}
}
#[derive(Debug, Clone)]
pub struct MetaLearningFeatureSelector<State = Untrained> {
base_methods: Vec<String>,
meta_learning_rate: f64,
meta_epochs: usize,
k: Option<usize>,
random_state: Option<u64>,
state: PhantomData<State>,
_meta_weights_: Option<Array1<Float>>,
base_selections_: Option<Vec<Vec<usize>>>,
selected_features_: Option<Vec<usize>>,
}
impl MetaLearningFeatureSelector<Untrained> {
pub fn new() -> Self {
Self {
base_methods: vec![
"correlation".to_string(),
"mutual_info".to_string(),
"f_test".to_string(),
"chi2".to_string(),
],
meta_learning_rate: 0.01,
meta_epochs: 50,
k: None,
random_state: None,
state: PhantomData,
_meta_weights_: None,
base_selections_: None,
selected_features_: None,
}
}
pub fn base_methods(mut self, methods: Vec<String>) -> Self {
self.base_methods = methods;
self
}
pub fn meta_learning_rate(mut self, lr: f64) -> Self {
self.meta_learning_rate = lr;
self
}
pub fn meta_epochs(mut self, epochs: usize) -> Self {
self.meta_epochs = epochs;
self
}
pub fn k(mut self, k: Option<usize>) -> Self {
self.k = k;
self
}
pub fn random_state(mut self, seed: Option<u64>) -> Self {
self.random_state = seed;
self
}
}
impl Default for MetaLearningFeatureSelector<Untrained> {
fn default() -> Self {
Self::new()
}
}
impl Estimator for MetaLearningFeatureSelector<Untrained> {
type Config = ();
type Error = SklearsError;
type Float = f64;
fn config(&self) -> &Self::Config {
&()
}
}
impl Fit<Array2<Float>, Array1<Float>> for MetaLearningFeatureSelector<Untrained> {
type Fitted = MetaLearningFeatureSelector<Trained>;
fn fit(self, x: &Array2<Float>, y: &Array1<Float>) -> SklResult<Self::Fitted> {
validate::check_consistent_length(x, y)?;
let _rng = match self.random_state {
Some(seed) => StdRng::seed_from_u64(seed),
None => StdRng::from_rng(&mut thread_rng()),
};
let (_, n_features) = x.dim();
let mut base_selections = Vec::new();
let mut base_scores = Vec::new();
for method in &self.base_methods {
let (selection, score) = match method.as_str() {
"correlation" => apply_correlation_selection(x, y, self.k),
"mutual_info" => apply_mutual_info_selection(x, y, self.k),
"f_test" => apply_f_test_selection(x, y, self.k),
"chi2" => apply_chi2_selection(x, y, self.k),
_ => (Vec::new(), 0.0),
};
base_selections.push(selection);
base_scores.push(score);
}
let mut meta_weights = Array1::from_vec(vec![
1.0 / self.base_methods.len() as f64;
self.base_methods.len()
]);
for _epoch in 0..self.meta_epochs {
let ensemble_selection =
self.compute_ensemble_selection(&base_selections, &meta_weights, n_features);
let ensemble_score = evaluate_feature_selection(x, y, &ensemble_selection);
for i in 0..meta_weights.len() {
let individual_score = base_scores[i];
let gradient = individual_score - ensemble_score;
meta_weights[i] += self.meta_learning_rate * gradient;
}
let weight_sum = meta_weights.sum();
if weight_sum > 0.0 {
meta_weights /= weight_sum;
}
}
let selected_features =
self.compute_ensemble_selection(&base_selections, &meta_weights, n_features);
Ok(MetaLearningFeatureSelector {
base_methods: self.base_methods,
meta_learning_rate: self.meta_learning_rate,
meta_epochs: self.meta_epochs,
k: self.k,
random_state: self.random_state,
state: PhantomData,
_meta_weights_: Some(meta_weights),
base_selections_: Some(base_selections),
selected_features_: Some(selected_features),
})
}
}
impl MetaLearningFeatureSelector<Untrained> {
fn compute_ensemble_selection(
&self,
base_selections: &[Vec<usize>],
weights: &Array1<Float>,
n_features: usize,
) -> Vec<usize> {
let mut feature_scores = Array1::zeros(n_features);
for (i, selection) in base_selections.iter().enumerate() {
let weight = weights[i];
for &feature_idx in selection {
if feature_idx < n_features {
feature_scores[feature_idx] += weight;
}
}
}
let mut feature_indices: Vec<(usize, Float)> = feature_scores
.indexed_iter()
.map(|(i, &score)| (i, score))
.collect();
feature_indices.sort_by(|a, b| b.1.partial_cmp(&a.1).expect("operation should succeed"));
let selected: Vec<usize> = if let Some(k) = self.k {
feature_indices
.iter()
.take(k.min(feature_indices.len()))
.filter(|(_, score)| *score > 0.0)
.map(|(i, _)| *i)
.collect()
} else {
feature_indices
.iter()
.filter(|(_, score)| *score > 0.1) .map(|(i, _)| *i)
.collect()
};
let mut selected_sorted = selected;
selected_sorted.sort();
selected_sorted
}
}
impl Transform<Array2<Float>> for MetaLearningFeatureSelector<Trained> {
fn transform(&self, x: &Array2<Float>) -> SklResult<Array2<Float>> {
let selected_features = self
.selected_features_
.as_ref()
.expect("operation should succeed");
if selected_features.is_empty() {
return Err(SklearsError::InvalidInput(
"No features were selected".to_string(),
));
}
let selected_indices: Vec<usize> = selected_features.to_vec();
Ok(x.select(Axis(1), &selected_indices))
}
}
impl SelectorMixin for MetaLearningFeatureSelector<Trained> {
fn get_support(&self) -> SklResult<Array1<bool>> {
let selected_features = self
.selected_features_
.as_ref()
.expect("operation should succeed");
let n_features = if let Some(ref selections) = self.base_selections_ {
selections.iter().flatten().max().unwrap_or(&0) + 1
} else {
selected_features.iter().max().unwrap_or(&0) + 1
};
let mut support = Array1::from_elem(n_features, false);
for &idx in selected_features {
if idx < n_features {
support[idx] = true;
}
}
Ok(support)
}
fn transform_features(&self, indices: &[usize]) -> SklResult<Vec<usize>> {
let selected_features = self
.selected_features_
.as_ref()
.expect("operation should succeed");
Ok(indices
.iter()
.filter_map(|&idx| selected_features.iter().position(|&f| f == idx))
.collect())
}
}
fn softmax_with_temperature(scores: &Array1<Float>, temperature: f64) -> Array1<Float> {
let max_score = scores.iter().fold(f64::NEG_INFINITY, |a, &b| a.max(b));
let exp_scores: Array1<Float> = scores.mapv(|x| ((x - max_score) / temperature).exp());
let sum_exp = exp_scores.sum();
exp_scores / sum_exp
}
fn compute_simple_correlation(x: Float, y: Float) -> Float {
x * y
}
pub fn compute_simple_correlation_array(x: &Array1<Float>, y: &Array1<Float>) -> Float {
let n = x.len().min(y.len());
if n < 2 {
return 0.0;
}
let x_mean = x.iter().take(n).sum::<Float>() / n as Float;
let y_mean = y.iter().take(n).sum::<Float>() / n as Float;
let mut numerator = 0.0;
let mut x_var = 0.0;
let mut y_var = 0.0;
for i in 0..n {
let x_i = x[i] - x_mean;
let y_i = y[i] - y_mean;
numerator += x_i * y_i;
x_var += x_i * x_i;
y_var += y_i * y_i;
}
let denominator = (x_var * y_var).sqrt();
if denominator.abs() < 1e-10 {
0.0
} else {
numerator / denominator
}
}
fn encode_state(selected_features: &[usize], n_features: usize) -> String {
let mut state = vec![false; n_features];
for &idx in selected_features {
if idx < n_features {
state[idx] = true;
}
}
state.iter().map(|&b| if b { '1' } else { '0' }).collect()
}
fn apply_correlation_selection(
x: &Array2<Float>,
y: &Array1<Float>,
k: Option<usize>,
) -> (Vec<usize>, f64) {
let (_, n_features) = x.dim();
let mut correlations = Vec::new();
for j in 0..n_features {
let feature = x.column(j);
let corr = compute_simple_correlation_array(&feature.to_owned(), y);
correlations.push((j, corr.abs()));
}
correlations.sort_by(|a, b| b.1.partial_cmp(&a.1).expect("operation should succeed"));
let selection_size = k.unwrap_or(n_features / 4).min(n_features);
let selection: Vec<usize> = correlations
.iter()
.take(selection_size)
.map(|(i, _)| *i)
.collect();
let score = correlations
.iter()
.take(selection_size)
.map(|(_, corr)| corr)
.sum::<f64>()
/ selection_size as f64;
(selection, score)
}
fn apply_mutual_info_selection(
x: &Array2<Float>,
y: &Array1<Float>,
k: Option<usize>,
) -> (Vec<usize>, f64) {
apply_correlation_selection(x, y, k)
}
fn apply_f_test_selection(
x: &Array2<Float>,
y: &Array1<Float>,
k: Option<usize>,
) -> (Vec<usize>, f64) {
apply_correlation_selection(x, y, k)
}
fn apply_chi2_selection(
x: &Array2<Float>,
y: &Array1<Float>,
k: Option<usize>,
) -> (Vec<usize>, f64) {
apply_correlation_selection(x, y, k)
}
fn evaluate_feature_selection(x: &Array2<Float>, y: &Array1<Float>, features: &[usize]) -> f64 {
if features.is_empty() {
return 0.0;
}
let mut total_corr = 0.0;
for &feature_idx in features {
let feature = x.column(feature_idx);
let corr = compute_simple_correlation_array(&feature.to_owned(), y);
total_corr += corr.abs();
}
total_corr / features.len() as f64
}