use crate::base::{FeatureSelector, SelectorMixin};
use scirs2_core::ndarray::{Array1, Array2, Axis};
use sklears_core::{
error::{validate, Result as SklResult, SklearsError},
traits::{Estimator, Fit, Trained, Transform, Untrained},
types::Float,
};
use std::marker::PhantomData;
#[derive(Debug, Clone)]
pub struct ConvexFeatureSelector<State = Untrained> {
k: usize,
regularization: Float,
max_iter: usize,
tolerance: Float,
state: PhantomData<State>,
weights_: Option<Array1<Float>>,
selected_features_: Option<Vec<usize>>,
n_features_: Option<usize>,
objective_values_: Option<Vec<Float>>,
}
impl Default for ConvexFeatureSelector<Untrained> {
fn default() -> Self {
Self::new()
}
}
impl ConvexFeatureSelector<Untrained> {
pub fn new() -> Self {
Self {
k: 10,
regularization: 1.0,
max_iter: 1000,
tolerance: 1e-6,
state: PhantomData,
weights_: None,
selected_features_: None,
n_features_: None,
objective_values_: None,
}
}
pub fn k(mut self, k: usize) -> Self {
self.k = k;
self
}
pub fn regularization(mut self, regularization: Float) -> Self {
self.regularization = regularization;
self
}
pub fn max_iter(mut self, max_iter: usize) -> Self {
self.max_iter = max_iter;
self
}
pub fn tolerance(mut self, tolerance: Float) -> Self {
self.tolerance = tolerance;
self
}
fn solve_convex_optimization(
&self,
features: &Array2<Float>,
target: &Array1<Float>,
) -> SklResult<(Array1<Float>, Vec<Float>)> {
let n_features = features.ncols();
let n_samples = features.nrows();
let mut weights = Array1::from_elem(n_features, 1.0 / n_features as Float);
let mut objective_values = Vec::new();
for iter in 0..self.max_iter {
let predictions = features.dot(&weights);
let residuals = &predictions - target;
let data_gradient = features.t().dot(&residuals) / n_samples as Float;
let reg_gradient = weights.mapv(|w| {
if w > 0.0 {
self.regularization
} else if w < 0.0 {
-self.regularization
} else {
0.0 }
});
let gradient = data_gradient + reg_gradient;
let step_size = 0.01 / (iter + 1) as Float;
let new_weights = &weights - step_size * &gradient;
let new_weights = new_weights.mapv(|w| w.max(0.0));
let data_term = residuals.mapv(|r| r * r).sum() / (2.0 * n_samples as Float);
let reg_term = self.regularization * weights.mapv(|w| w.abs()).sum();
let objective = data_term + reg_term;
objective_values.push(objective);
let weight_diff = (&new_weights - &weights).mapv(|d| d.abs()).sum();
if weight_diff < self.tolerance {
break;
}
weights = new_weights;
}
Ok((weights, objective_values))
}
}
impl Estimator for ConvexFeatureSelector<Untrained> {
type Config = ();
type Error = SklearsError;
type Float = Float;
fn config(&self) -> &Self::Config {
&()
}
}
impl Fit<Array2<Float>, Array1<Float>> for ConvexFeatureSelector<Untrained> {
type Fitted = ConvexFeatureSelector<Trained>;
fn fit(self, features: &Array2<Float>, target: &Array1<Float>) -> SklResult<Self::Fitted> {
let n_features = features.ncols();
if n_features == 0 {
return Err(SklearsError::InvalidInput(
"No features provided".to_string(),
));
}
if self.k > n_features {
return Err(SklearsError::InvalidInput(format!(
"k ({}) cannot be greater than number of features ({})",
self.k, n_features
)));
}
let (weights, objective_values) = self.solve_convex_optimization(features, target)?;
let mut feature_indices: Vec<usize> = (0..n_features).collect();
feature_indices.sort_by(|&a, &b| {
weights[b]
.partial_cmp(&weights[a])
.unwrap_or(std::cmp::Ordering::Equal)
});
let selected_features = feature_indices.into_iter().take(self.k).collect();
Ok(ConvexFeatureSelector {
k: self.k,
regularization: self.regularization,
max_iter: self.max_iter,
tolerance: self.tolerance,
state: PhantomData,
weights_: Some(weights),
selected_features_: Some(selected_features),
n_features_: Some(n_features),
objective_values_: Some(objective_values),
})
}
}
impl Transform<Array2<Float>> for ConvexFeatureSelector<Trained> {
fn transform(&self, x: &Array2<Float>) -> SklResult<Array2<Float>> {
validate::check_n_features(x, self.n_features_.expect("operation should succeed"))?;
let selected_features = self
.selected_features_
.as_ref()
.expect("operation should succeed");
let n_samples = x.nrows();
let n_selected = selected_features.len();
let mut x_new = Array2::zeros((n_samples, n_selected));
for (new_idx, &old_idx) in selected_features.iter().enumerate() {
x_new.column_mut(new_idx).assign(&x.column(old_idx));
}
Ok(x_new)
}
}
impl SelectorMixin for ConvexFeatureSelector<Trained> {
fn get_support(&self) -> SklResult<Array1<bool>> {
let n_features = self.n_features_.expect("operation should succeed");
let selected_features = self
.selected_features_
.as_ref()
.expect("operation should succeed");
let mut support = Array1::from_elem(n_features, false);
for &idx in selected_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())
}
}
impl FeatureSelector for ConvexFeatureSelector<Trained> {
fn selected_features(&self) -> &Vec<usize> {
self.selected_features_
.as_ref()
.expect("operation should succeed")
}
}
impl ConvexFeatureSelector<Trained> {
pub fn weights(&self) -> &Array1<Float> {
self.weights_.as_ref().expect("operation should succeed")
}
pub fn objective_values(&self) -> &[Float] {
self.objective_values_
.as_ref()
.expect("operation should succeed")
}
pub fn n_features_out(&self) -> usize {
self.selected_features_
.as_ref()
.expect("operation should succeed")
.len()
}
}
#[derive(Debug, Clone)]
pub struct ProximalGradientSelector<State = Untrained> {
k: usize,
regularization: Float,
max_iter: usize,
tolerance: Float,
step_size: Float,
state: PhantomData<State>,
weights_: Option<Array1<Float>>,
selected_features_: Option<Vec<usize>>,
n_features_: Option<usize>,
objective_values_: Option<Vec<Float>>,
}
impl Default for ProximalGradientSelector<Untrained> {
fn default() -> Self {
Self::new()
}
}
impl ProximalGradientSelector<Untrained> {
pub fn new() -> Self {
Self {
k: 10,
regularization: 1.0,
max_iter: 1000,
tolerance: 1e-6,
step_size: 0.01,
state: PhantomData,
weights_: None,
selected_features_: None,
n_features_: None,
objective_values_: None,
}
}
pub fn k(mut self, k: usize) -> Self {
self.k = k;
self
}
pub fn regularization(mut self, regularization: Float) -> Self {
self.regularization = regularization;
self
}
pub fn max_iter(mut self, max_iter: usize) -> Self {
self.max_iter = max_iter;
self
}
pub fn tolerance(mut self, tolerance: Float) -> Self {
self.tolerance = tolerance;
self
}
pub fn step_size(mut self, step_size: Float) -> Self {
self.step_size = step_size;
self
}
fn soft_threshold(&self, x: Float, threshold: Float) -> Float {
if x > threshold {
x - threshold
} else if x < -threshold {
x + threshold
} else {
0.0
}
}
fn solve_proximal_gradient(
&self,
features: &Array2<Float>,
target: &Array1<Float>,
) -> SklResult<(Array1<Float>, Vec<Float>)> {
let n_features = features.ncols();
let n_samples = features.nrows();
let mut weights = Array1::zeros(n_features);
let mut objective_values = Vec::new();
for _iter in 0..self.max_iter {
let predictions = features.dot(&weights);
let residuals = &predictions - target;
let gradient = features.t().dot(&residuals) / n_samples as Float;
let temp_weights = &weights - self.step_size * &gradient;
let threshold = self.step_size * self.regularization;
let new_weights = temp_weights.mapv(|w| self.soft_threshold(w, threshold));
let data_term = residuals.mapv(|r| r * r).sum() / (2.0 * n_samples as Float);
let reg_term = self.regularization * weights.mapv(|w| w.abs()).sum();
let objective = data_term + reg_term;
objective_values.push(objective);
let weight_diff = (&new_weights - &weights).mapv(|d| d.abs()).sum();
if weight_diff < self.tolerance {
break;
}
weights = new_weights;
}
Ok((weights, objective_values))
}
}
impl Estimator for ProximalGradientSelector<Untrained> {
type Config = ();
type Error = SklearsError;
type Float = Float;
fn config(&self) -> &Self::Config {
&()
}
}
impl Fit<Array2<Float>, Array1<Float>> for ProximalGradientSelector<Untrained> {
type Fitted = ProximalGradientSelector<Trained>;
fn fit(self, features: &Array2<Float>, target: &Array1<Float>) -> SklResult<Self::Fitted> {
let n_features = features.ncols();
if n_features == 0 {
return Err(SklearsError::InvalidInput(
"No features provided".to_string(),
));
}
if self.k > n_features {
return Err(SklearsError::InvalidInput(format!(
"k ({}) cannot be greater than number of features ({})",
self.k, n_features
)));
}
let (weights, objective_values) = self.solve_proximal_gradient(features, target)?;
let mut feature_indices: Vec<usize> = (0..n_features).collect();
feature_indices.sort_by(|&a, &b| {
weights[b]
.abs()
.partial_cmp(&weights[a].abs())
.unwrap_or(std::cmp::Ordering::Equal)
});
let selected_features = feature_indices.into_iter().take(self.k).collect();
Ok(ProximalGradientSelector {
k: self.k,
regularization: self.regularization,
max_iter: self.max_iter,
tolerance: self.tolerance,
step_size: self.step_size,
state: PhantomData,
weights_: Some(weights),
selected_features_: Some(selected_features),
n_features_: Some(n_features),
objective_values_: Some(objective_values),
})
}
}
impl Transform<Array2<Float>> for ProximalGradientSelector<Trained> {
fn transform(&self, x: &Array2<Float>) -> SklResult<Array2<Float>> {
validate::check_n_features(x, self.n_features_.expect("operation should succeed"))?;
let selected_features = self
.selected_features_
.as_ref()
.expect("operation should succeed");
let n_samples = x.nrows();
let n_selected = selected_features.len();
let mut x_new = Array2::zeros((n_samples, n_selected));
for (new_idx, &old_idx) in selected_features.iter().enumerate() {
x_new.column_mut(new_idx).assign(&x.column(old_idx));
}
Ok(x_new)
}
}
impl SelectorMixin for ProximalGradientSelector<Trained> {
fn get_support(&self) -> SklResult<Array1<bool>> {
let n_features = self.n_features_.expect("operation should succeed");
let selected_features = self
.selected_features_
.as_ref()
.expect("operation should succeed");
let mut support = Array1::from_elem(n_features, false);
for &idx in selected_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())
}
}
impl FeatureSelector for ProximalGradientSelector<Trained> {
fn selected_features(&self) -> &Vec<usize> {
self.selected_features_
.as_ref()
.expect("operation should succeed")
}
}
impl ProximalGradientSelector<Trained> {
pub fn weights(&self) -> &Array1<Float> {
self.weights_.as_ref().expect("operation should succeed")
}
pub fn objective_values(&self) -> &[Float] {
self.objective_values_
.as_ref()
.expect("operation should succeed")
}
pub fn n_features_out(&self) -> usize {
self.selected_features_
.as_ref()
.expect("operation should succeed")
.len()
}
}
#[derive(Debug, Clone)]
pub struct ADMMFeatureSelector<State = Untrained> {
k: usize,
regularization: Float,
max_iter: usize,
tolerance: Float,
rho: Float, state: PhantomData<State>,
weights_: Option<Array1<Float>>,
selected_features_: Option<Vec<usize>>,
n_features_: Option<usize>,
objective_values_: Option<Vec<Float>>,
}
impl Default for ADMMFeatureSelector<Untrained> {
fn default() -> Self {
Self::new()
}
}
impl ADMMFeatureSelector<Untrained> {
pub fn new() -> Self {
Self {
k: 10,
regularization: 1.0,
max_iter: 1000,
tolerance: 1e-6,
rho: 1.0,
state: PhantomData,
weights_: None,
selected_features_: None,
n_features_: None,
objective_values_: None,
}
}
pub fn k(mut self, k: usize) -> Self {
self.k = k;
self
}
pub fn regularization(mut self, regularization: Float) -> Self {
self.regularization = regularization;
self
}
pub fn max_iter(mut self, max_iter: usize) -> Self {
self.max_iter = max_iter;
self
}
pub fn tolerance(mut self, tolerance: Float) -> Self {
self.tolerance = tolerance;
self
}
pub fn rho(mut self, rho: Float) -> Self {
self.rho = rho;
self
}
fn soft_threshold(&self, x: Float, threshold: Float) -> Float {
if x > threshold {
x - threshold
} else if x < -threshold {
x + threshold
} else {
0.0
}
}
fn solve_admm(
&self,
features: &Array2<Float>,
target: &Array1<Float>,
) -> SklResult<(Array1<Float>, Vec<Float>)> {
let n_features = features.ncols();
let n_samples = features.nrows();
let mut x = Array1::<Float>::zeros(n_features); let mut z = Array1::<Float>::zeros(n_features); let mut u = Array1::<Float>::zeros(n_features);
let mut objective_values = Vec::new();
let xtx = features.t().dot(features);
let xty = features.t().dot(target);
for _iter in 0..self.max_iter {
let _x_old = x.clone();
let z_old = z.clone();
let rhs = &xty + self.rho * (&z - &u);
for i in 0..n_features {
let diag_elem = xtx[[i, i]] + self.rho;
if diag_elem > 1e-12 {
let off_diag = (0..n_features)
.filter(|&j| j != i)
.map(|j| xtx[[i, j]] * x[j])
.sum::<Float>();
x[i] = (rhs[i] - off_diag) / diag_elem;
}
}
let threshold = self.regularization / self.rho;
for i in 0..n_features {
z[i] = self.soft_threshold(x[i] + u[i], threshold);
}
u = &u + &x - &z;
let predictions = features.dot(&x);
let residuals = &predictions - target;
let data_term = residuals.mapv(|r| r * r).sum() / (2.0 * n_samples as Float);
let reg_term = self.regularization * z.mapv(|z_i| z_i.abs()).sum();
let objective = data_term + reg_term;
objective_values.push(objective);
let primal_residual = (&x - &z).mapv(|r| r.abs()).sum();
let dual_residual = self.rho * (&z - &z_old).mapv(|r| r.abs()).sum();
if primal_residual < self.tolerance && dual_residual < self.tolerance {
break;
}
}
Ok((z, objective_values))
}
}
impl Estimator for ADMMFeatureSelector<Untrained> {
type Config = ();
type Error = SklearsError;
type Float = Float;
fn config(&self) -> &Self::Config {
&()
}
}
impl Fit<Array2<Float>, Array1<Float>> for ADMMFeatureSelector<Untrained> {
type Fitted = ADMMFeatureSelector<Trained>;
fn fit(self, features: &Array2<Float>, target: &Array1<Float>) -> SklResult<Self::Fitted> {
let n_features = features.ncols();
if n_features == 0 {
return Err(SklearsError::InvalidInput(
"No features provided".to_string(),
));
}
if self.k > n_features {
return Err(SklearsError::InvalidInput(format!(
"k ({}) cannot be greater than number of features ({})",
self.k, n_features
)));
}
let (weights, objective_values) = self.solve_admm(features, target)?;
let mut feature_indices: Vec<usize> = (0..n_features).collect();
feature_indices.sort_by(|&a, &b| {
weights[b]
.abs()
.partial_cmp(&weights[a].abs())
.unwrap_or(std::cmp::Ordering::Equal)
});
let selected_features = feature_indices.into_iter().take(self.k).collect();
Ok(ADMMFeatureSelector {
k: self.k,
regularization: self.regularization,
max_iter: self.max_iter,
tolerance: self.tolerance,
rho: self.rho,
state: PhantomData,
weights_: Some(weights),
selected_features_: Some(selected_features),
n_features_: Some(n_features),
objective_values_: Some(objective_values),
})
}
}
impl Transform<Array2<Float>> for ADMMFeatureSelector<Trained> {
fn transform(&self, x: &Array2<Float>) -> SklResult<Array2<Float>> {
validate::check_n_features(x, self.n_features_.expect("operation should succeed"))?;
let selected_features = self
.selected_features_
.as_ref()
.expect("operation should succeed");
let n_samples = x.nrows();
let n_selected = selected_features.len();
let mut x_new = Array2::zeros((n_samples, n_selected));
for (new_idx, &old_idx) in selected_features.iter().enumerate() {
x_new.column_mut(new_idx).assign(&x.column(old_idx));
}
Ok(x_new)
}
}
impl SelectorMixin for ADMMFeatureSelector<Trained> {
fn get_support(&self) -> SklResult<Array1<bool>> {
let n_features = self.n_features_.expect("operation should succeed");
let selected_features = self
.selected_features_
.as_ref()
.expect("operation should succeed");
let mut support = Array1::from_elem(n_features, false);
for &idx in selected_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())
}
}
impl FeatureSelector for ADMMFeatureSelector<Trained> {
fn selected_features(&self) -> &Vec<usize> {
self.selected_features_
.as_ref()
.expect("operation should succeed")
}
}
impl ADMMFeatureSelector<Trained> {
pub fn weights(&self) -> &Array1<Float> {
self.weights_.as_ref().expect("operation should succeed")
}
pub fn objective_values(&self) -> &[Float] {
self.objective_values_
.as_ref()
.expect("operation should succeed")
}
pub fn n_features_out(&self) -> usize {
self.selected_features_
.as_ref()
.expect("operation should succeed")
.len()
}
}
#[derive(Debug, Clone)]
pub struct SemidefiniteFeatureSelector<State = Untrained> {
k: usize,
max_iter: usize,
tolerance: Float,
regularization: Float,
state: PhantomData<State>,
feature_matrix_: Option<Array2<Float>>,
selected_features_: Option<Vec<usize>>,
n_features_: Option<usize>,
eigenvalues_: Option<Array1<Float>>,
objective_values_: Option<Vec<Float>>,
}
impl Default for SemidefiniteFeatureSelector<Untrained> {
fn default() -> Self {
Self::new()
}
}
impl SemidefiniteFeatureSelector<Untrained> {
pub fn new() -> Self {
Self {
k: 10,
max_iter: 100,
tolerance: 1e-6,
regularization: 1.0,
state: PhantomData,
feature_matrix_: None,
selected_features_: None,
n_features_: None,
eigenvalues_: None,
objective_values_: None,
}
}
pub fn k(mut self, k: usize) -> Self {
self.k = k;
self
}
pub fn max_iter(mut self, max_iter: usize) -> Self {
self.max_iter = max_iter;
self
}
pub fn tolerance(mut self, tolerance: Float) -> Self {
self.tolerance = tolerance;
self
}
pub fn regularization(mut self, regularization: Float) -> Self {
self.regularization = regularization;
self
}
fn project_psd(&self, matrix: &Array2<Float>) -> SklResult<Array2<Float>> {
let n = matrix.nrows();
let mut projected = Array2::zeros((n, n));
for i in 0..n {
for j in 0..n {
projected[[i, j]] = (matrix[[i, j]] + matrix[[j, i]]) / 2.0;
}
}
for i in 0..n {
if projected[[i, i]] < 0.0 {
projected[[i, i]] = 0.0;
}
}
Ok(projected)
}
fn solve_sdp_relaxation(
&self,
features: &Array2<Float>,
target: &Array1<Float>,
) -> SklResult<(Array2<Float>, Array1<Float>, Vec<Float>)> {
let n_features = features.ncols();
let centered_features = features
- &features
.mean_axis(Axis(0))
.expect("operation should succeed");
let cov_matrix =
centered_features.t().dot(¢ered_features) / (features.nrows() - 1) as Float;
let target_centered = target - target.mean().expect("operation should succeed");
let correlations =
centered_features.t().dot(&target_centered) / (features.nrows() - 1) as Float;
let mut x_matrix = Array2::eye(n_features) * 0.5; let mut objective_values = Vec::new();
for _iter in 0..self.max_iter {
let _x_old = x_matrix.clone();
let outer_corr = outer_product(&correlations, &correlations);
let grad = &outer_corr - self.regularization * &cov_matrix;
let step_size = 0.01;
let x_new = &x_matrix + step_size * &grad;
let mut x_projected = self.project_psd(&x_new)?;
for i in 0..n_features {
x_projected[[i, i]] = x_projected[[i, i]].clamp(0.0, 1.0);
}
let obj = correlations.dot(&x_projected.dot(&correlations))
- self.regularization * trace(&x_projected.dot(&cov_matrix));
objective_values.push(obj);
let diff = (&x_projected - &x_matrix).mapv(|x| x.abs()).sum();
if diff < self.tolerance {
break;
}
x_matrix = x_projected;
}
let eigenvalues = extract_diagonal(&x_matrix);
Ok((x_matrix, eigenvalues, objective_values))
}
}
impl Estimator for SemidefiniteFeatureSelector<Untrained> {
type Config = ();
type Error = SklearsError;
type Float = Float;
fn config(&self) -> &Self::Config {
&()
}
}
impl Fit<Array2<Float>, Array1<Float>> for SemidefiniteFeatureSelector<Untrained> {
type Fitted = SemidefiniteFeatureSelector<Trained>;
fn fit(self, features: &Array2<Float>, target: &Array1<Float>) -> SklResult<Self::Fitted> {
let n_features = features.ncols();
if n_features == 0 {
return Err(SklearsError::InvalidInput(
"No features provided".to_string(),
));
}
if self.k > n_features {
return Err(SklearsError::InvalidInput(format!(
"k ({}) cannot be greater than number of features ({})",
self.k, n_features
)));
}
let (feature_matrix, eigenvalues, objective_values) =
self.solve_sdp_relaxation(features, target)?;
let mut feature_indices: Vec<usize> = (0..n_features).collect();
feature_indices.sort_by(|&a, &b| {
eigenvalues[b]
.partial_cmp(&eigenvalues[a])
.unwrap_or(std::cmp::Ordering::Equal)
});
let selected_features = feature_indices.into_iter().take(self.k).collect();
Ok(SemidefiniteFeatureSelector {
k: self.k,
max_iter: self.max_iter,
tolerance: self.tolerance,
regularization: self.regularization,
state: PhantomData,
feature_matrix_: Some(feature_matrix),
selected_features_: Some(selected_features),
n_features_: Some(n_features),
eigenvalues_: Some(eigenvalues),
objective_values_: Some(objective_values),
})
}
}
impl Transform<Array2<Float>> for SemidefiniteFeatureSelector<Trained> {
fn transform(&self, x: &Array2<Float>) -> SklResult<Array2<Float>> {
validate::check_n_features(x, self.n_features_.expect("operation should succeed"))?;
let selected_features = self
.selected_features_
.as_ref()
.expect("operation should succeed");
let n_samples = x.nrows();
let n_selected = selected_features.len();
let mut x_new = Array2::zeros((n_samples, n_selected));
for (new_idx, &old_idx) in selected_features.iter().enumerate() {
x_new.column_mut(new_idx).assign(&x.column(old_idx));
}
Ok(x_new)
}
}
impl SelectorMixin for SemidefiniteFeatureSelector<Trained> {
fn get_support(&self) -> SklResult<Array1<bool>> {
let n_features = self.n_features_.expect("operation should succeed");
let selected_features = self
.selected_features_
.as_ref()
.expect("operation should succeed");
let mut support = Array1::from_elem(n_features, false);
for &idx in selected_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())
}
}
impl FeatureSelector for SemidefiniteFeatureSelector<Trained> {
fn selected_features(&self) -> &Vec<usize> {
self.selected_features_
.as_ref()
.expect("operation should succeed")
}
}
impl SemidefiniteFeatureSelector<Trained> {
pub fn feature_matrix(&self) -> &Array2<Float> {
self.feature_matrix_
.as_ref()
.expect("operation should succeed")
}
pub fn eigenvalues(&self) -> &Array1<Float> {
self.eigenvalues_
.as_ref()
.expect("operation should succeed")
}
pub fn objective_values(&self) -> &[Float] {
self.objective_values_
.as_ref()
.expect("operation should succeed")
}
pub fn n_features_out(&self) -> usize {
self.selected_features_
.as_ref()
.expect("operation should succeed")
.len()
}
}
#[derive(Debug, Clone)]
pub struct IntegerProgrammingFeatureSelector<State = Untrained> {
k: usize,
max_iter: usize,
tolerance: Float,
greedy_init: bool,
local_search: bool,
state: PhantomData<State>,
binary_solution_: Option<Array1<bool>>,
selected_features_: Option<Vec<usize>>,
n_features_: Option<usize>,
objective_value_: Option<Float>,
improvement_history_: Option<Vec<Float>>,
}
impl Default for IntegerProgrammingFeatureSelector<Untrained> {
fn default() -> Self {
Self::new()
}
}
impl IntegerProgrammingFeatureSelector<Untrained> {
pub fn new() -> Self {
Self {
k: 10,
max_iter: 1000,
tolerance: 1e-6,
greedy_init: true,
local_search: true,
state: PhantomData,
binary_solution_: None,
selected_features_: None,
n_features_: None,
objective_value_: None,
improvement_history_: None,
}
}
pub fn k(mut self, k: usize) -> Self {
self.k = k;
self
}
pub fn max_iter(mut self, max_iter: usize) -> Self {
self.max_iter = max_iter;
self
}
pub fn tolerance(mut self, tolerance: Float) -> Self {
self.tolerance = tolerance;
self
}
pub fn greedy_init(mut self, greedy_init: bool) -> Self {
self.greedy_init = greedy_init;
self
}
pub fn local_search(mut self, local_search: bool) -> Self {
self.local_search = local_search;
self
}
fn compute_feature_scores(
&self,
features: &Array2<Float>,
target: &Array1<Float>,
) -> SklResult<Array1<Float>> {
let n_features = features.ncols();
let mut scores = Array1::zeros(n_features);
for i in 0..n_features {
let feature_col = features.column(i);
let correlation = correlation_coefficient(&feature_col.to_owned(), target)?;
scores[i] = correlation.abs();
}
Ok(scores)
}
fn greedy_initialization(&self, scores: &Array1<Float>) -> Array1<bool> {
let n_features = scores.len();
let mut solution = Array1::from_elem(n_features, false);
let mut indices: Vec<usize> = (0..n_features).collect();
indices.sort_by(|&a, &b| {
scores[b]
.partial_cmp(&scores[a])
.unwrap_or(std::cmp::Ordering::Equal)
});
for &idx in indices.iter().take(self.k) {
solution[idx] = true;
}
solution
}
fn evaluate_objective(&self, solution: &Array1<bool>, scores: &Array1<Float>) -> Float {
let mut objective = 0.0;
let mut selected_count = 0;
for i in 0..solution.len() {
if solution[i] {
objective += scores[i];
selected_count += 1;
}
}
if selected_count != self.k {
objective -= 1000.0 * (selected_count as Float - self.k as Float).abs();
}
objective
}
fn local_search_improvement(
&self,
solution: &mut Array1<bool>,
scores: &Array1<Float>,
best_obj: &mut Float,
) -> bool {
let n_features = solution.len();
let mut improved = false;
for i in 0..n_features {
let original = solution[i];
solution[i] = !solution[i];
let new_obj = self.evaluate_objective(solution, scores);
if new_obj > *best_obj + self.tolerance {
*best_obj = new_obj;
improved = true;
} else {
solution[i] = original; }
}
if self.local_search {
for i in 0..n_features {
for j in (i + 1)..n_features {
if solution[i] == solution[j] {
continue; }
let temp = solution[i];
solution[i] = solution[j];
solution[j] = temp;
let new_obj = self.evaluate_objective(solution, scores);
if new_obj > *best_obj + self.tolerance {
*best_obj = new_obj;
improved = true;
} else {
let temp = solution[i];
solution[i] = solution[j];
solution[j] = temp;
}
}
}
}
improved
}
fn solve_integer_programming(
&self,
features: &Array2<Float>,
target: &Array1<Float>,
) -> SklResult<(Array1<bool>, Float, Vec<Float>)> {
let scores = self.compute_feature_scores(features, target)?;
let mut improvement_history = Vec::new();
let mut solution = if self.greedy_init {
self.greedy_initialization(&scores)
} else {
let mut random_solution = Array1::from_elem(scores.len(), false);
let indices: Vec<usize> = (0..scores.len()).collect();
for &idx in indices.iter().take(self.k) {
random_solution[idx] = true;
}
random_solution
};
let mut best_objective = self.evaluate_objective(&solution, &scores);
improvement_history.push(best_objective);
for _iter in 0..self.max_iter {
let prev_objective = best_objective;
let improved =
self.local_search_improvement(&mut solution, &scores, &mut best_objective);
improvement_history.push(best_objective);
if !improved || (best_objective - prev_objective).abs() < self.tolerance {
break;
}
}
Ok((solution, best_objective, improvement_history))
}
}
impl Estimator for IntegerProgrammingFeatureSelector<Untrained> {
type Config = ();
type Error = SklearsError;
type Float = Float;
fn config(&self) -> &Self::Config {
&()
}
}
impl Fit<Array2<Float>, Array1<Float>> for IntegerProgrammingFeatureSelector<Untrained> {
type Fitted = IntegerProgrammingFeatureSelector<Trained>;
fn fit(self, features: &Array2<Float>, target: &Array1<Float>) -> SklResult<Self::Fitted> {
let n_features = features.ncols();
if n_features == 0 {
return Err(SklearsError::InvalidInput(
"No features provided".to_string(),
));
}
if self.k > n_features {
return Err(SklearsError::InvalidInput(format!(
"k ({}) cannot be greater than number of features ({})",
self.k, n_features
)));
}
let (binary_solution, objective_value, improvement_history) =
self.solve_integer_programming(features, target)?;
let selected_features: Vec<usize> = binary_solution
.iter()
.enumerate()
.filter_map(|(i, &selected)| if selected { Some(i) } else { None })
.collect();
Ok(IntegerProgrammingFeatureSelector {
k: self.k,
max_iter: self.max_iter,
tolerance: self.tolerance,
greedy_init: self.greedy_init,
local_search: self.local_search,
state: PhantomData,
binary_solution_: Some(binary_solution),
selected_features_: Some(selected_features),
n_features_: Some(n_features),
objective_value_: Some(objective_value),
improvement_history_: Some(improvement_history),
})
}
}
impl Transform<Array2<Float>> for IntegerProgrammingFeatureSelector<Trained> {
fn transform(&self, x: &Array2<Float>) -> SklResult<Array2<Float>> {
validate::check_n_features(x, self.n_features_.expect("operation should succeed"))?;
let selected_features = self
.selected_features_
.as_ref()
.expect("operation should succeed");
let n_samples = x.nrows();
let n_selected = selected_features.len();
let mut x_new = Array2::zeros((n_samples, n_selected));
for (new_idx, &old_idx) in selected_features.iter().enumerate() {
x_new.column_mut(new_idx).assign(&x.column(old_idx));
}
Ok(x_new)
}
}
impl SelectorMixin for IntegerProgrammingFeatureSelector<Trained> {
fn get_support(&self) -> SklResult<Array1<bool>> {
Ok(self
.binary_solution_
.as_ref()
.expect("operation should succeed")
.clone())
}
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())
}
}
impl FeatureSelector for IntegerProgrammingFeatureSelector<Trained> {
fn selected_features(&self) -> &Vec<usize> {
self.selected_features_
.as_ref()
.expect("operation should succeed")
}
}
impl IntegerProgrammingFeatureSelector<Trained> {
pub fn binary_solution(&self) -> &Array1<bool> {
self.binary_solution_
.as_ref()
.expect("operation should succeed")
}
pub fn objective_value(&self) -> Float {
self.objective_value_.expect("operation should succeed")
}
pub fn improvement_history(&self) -> &[Float] {
self.improvement_history_
.as_ref()
.expect("operation should succeed")
}
pub fn n_features_out(&self) -> usize {
self.selected_features_
.as_ref()
.expect("operation should succeed")
.len()
}
}
fn outer_product(a: &Array1<Float>, b: &Array1<Float>) -> Array2<Float> {
let mut result = Array2::zeros((a.len(), b.len()));
for i in 0..a.len() {
for j in 0..b.len() {
result[[i, j]] = a[i] * b[j];
}
}
result
}
fn trace(matrix: &Array2<Float>) -> Float {
let n = matrix.nrows().min(matrix.ncols());
(0..n).map(|i| matrix[[i, i]]).sum()
}
fn extract_diagonal(matrix: &Array2<Float>) -> Array1<Float> {
let n = matrix.nrows().min(matrix.ncols());
let mut diag = Array1::zeros(n);
for i in 0..n {
diag[i] = matrix[[i, i]];
}
diag
}
fn correlation_coefficient(x: &Array1<Float>, y: &Array1<Float>) -> SklResult<Float> {
if x.len() != y.len() {
return Err(SklearsError::InvalidInput(
"Arrays must have the same length".to_string(),
));
}
let _n = x.len() as Float;
let mean_x = x.mean().expect("operation should succeed");
let mean_y = y.mean().expect("operation should succeed");
let mut num = 0.0;
let mut den_x = 0.0;
let mut den_y = 0.0;
for i in 0..x.len() {
let diff_x = x[i] - mean_x;
let diff_y = y[i] - mean_y;
num += diff_x * diff_y;
den_x += diff_x * diff_x;
den_y += diff_y * diff_y;
}
if den_x.abs() < 1e-10 || den_y.abs() < 1e-10 {
return Ok(0.0);
}
Ok(num / (den_x * den_y).sqrt())
}
#[allow(non_snake_case)]
#[cfg(test)]
mod tests {
use super::*;
use scirs2_core::ndarray::Array2;
fn create_test_data() -> (Array2<Float>, Array1<Float>) {
let n_samples = 50;
let n_features = 10;
let mut features = Array2::zeros((n_samples, n_features));
let mut target = Array1::zeros(n_samples);
for i in 0..n_samples {
for j in 0..n_features {
features[[i, j]] = (i as Float * 0.1 + j as Float * 0.01).sin() + 0.1 * j as Float;
}
target[i] = features[[i, 0]] + 0.5 * features[[i, 1]] + 0.1 * features[[i, 2]];
}
(features, target)
}
#[test]
fn test_convex_feature_selector() {
let (features, target) = create_test_data();
let selector = ConvexFeatureSelector::new()
.k(5)
.regularization(0.1)
.max_iter(100);
let trained = selector
.fit(&features, &target)
.expect("operation should succeed");
assert_eq!(trained.n_features_out(), 5);
let transformed = trained
.transform(&features)
.expect("operation should succeed");
assert_eq!(transformed.ncols(), 5);
assert_eq!(transformed.nrows(), features.nrows());
let weights = trained.weights();
assert_eq!(weights.len(), features.ncols());
assert!(weights.iter().all(|&x| x.is_finite()));
let obj_vals = trained.objective_values();
assert!(!obj_vals.is_empty());
assert!(obj_vals.iter().all(|&x| x.is_finite()));
}
#[test]
fn test_proximal_gradient_selector() {
let (features, target) = create_test_data();
let selector = ProximalGradientSelector::new()
.k(4)
.regularization(0.1)
.step_size(0.01)
.max_iter(100);
let trained = selector
.fit(&features, &target)
.expect("operation should succeed");
assert_eq!(trained.n_features_out(), 4);
let transformed = trained
.transform(&features)
.expect("operation should succeed");
assert_eq!(transformed.ncols(), 4);
assert_eq!(transformed.nrows(), features.nrows());
let weights = trained.weights();
assert_eq!(weights.len(), features.ncols());
assert!(weights.iter().all(|&x| x.is_finite()));
}
#[test]
fn test_admm_feature_selector() {
let (features, target) = create_test_data();
let selector = ADMMFeatureSelector::new()
.k(3)
.regularization(0.1)
.rho(1.0)
.max_iter(50);
let trained = selector
.fit(&features, &target)
.expect("operation should succeed");
assert_eq!(trained.n_features_out(), 3);
let transformed = trained
.transform(&features)
.expect("operation should succeed");
assert_eq!(transformed.ncols(), 3);
assert_eq!(transformed.nrows(), features.nrows());
let weights = trained.weights();
assert_eq!(weights.len(), features.ncols());
assert!(weights.iter().all(|&x| x.is_finite()));
}
#[test]
fn test_convex_selector_invalid_k() {
let (features, target) = create_test_data();
let selector = ConvexFeatureSelector::new().k(features.ncols() + 1);
assert!(selector.fit(&features, &target).is_err());
}
#[test]
fn test_proximal_selector_invalid_k() {
let (features, target) = create_test_data();
let selector = ProximalGradientSelector::new().k(features.ncols() + 1);
assert!(selector.fit(&features, &target).is_err());
}
#[test]
fn test_admm_selector_invalid_k() {
let (features, target) = create_test_data();
let selector = ADMMFeatureSelector::new().k(features.ncols() + 1);
assert!(selector.fit(&features, &target).is_err());
}
#[test]
fn test_semidefinite_feature_selector() {
let (features, target) = create_test_data();
let selector = SemidefiniteFeatureSelector::new()
.k(4)
.regularization(0.1)
.max_iter(50);
let trained = selector
.fit(&features, &target)
.expect("operation should succeed");
assert_eq!(trained.n_features_out(), 4);
let transformed = trained
.transform(&features)
.expect("operation should succeed");
assert_eq!(transformed.ncols(), 4);
assert_eq!(transformed.nrows(), features.nrows());
let feature_matrix = trained.feature_matrix();
assert_eq!(feature_matrix.nrows(), features.ncols());
assert_eq!(feature_matrix.ncols(), features.ncols());
let eigenvalues = trained.eigenvalues();
assert_eq!(eigenvalues.len(), features.ncols());
assert!(eigenvalues.iter().all(|&x| x.is_finite()));
let obj_vals = trained.objective_values();
assert!(!obj_vals.is_empty());
assert!(obj_vals.iter().all(|&x| x.is_finite()));
}
#[test]
fn test_integer_programming_feature_selector() {
let (features, target) = create_test_data();
let selector = IntegerProgrammingFeatureSelector::new()
.k(3)
.greedy_init(true)
.local_search(true)
.max_iter(100);
let trained = selector
.fit(&features, &target)
.expect("operation should succeed");
assert_eq!(trained.n_features_out(), 3);
let transformed = trained
.transform(&features)
.expect("operation should succeed");
assert_eq!(transformed.ncols(), 3);
assert_eq!(transformed.nrows(), features.nrows());
let binary_solution = trained.binary_solution();
assert_eq!(binary_solution.len(), features.ncols());
let selected_count = binary_solution.iter().filter(|&&x| x).count();
assert_eq!(selected_count, 3);
let obj_value = trained.objective_value();
assert!(obj_value.is_finite());
let improvement_history = trained.improvement_history();
assert!(!improvement_history.is_empty());
assert!(improvement_history.iter().all(|&x| x.is_finite()));
}
#[test]
fn test_semidefinite_selector_invalid_k() {
let (features, target) = create_test_data();
let selector = SemidefiniteFeatureSelector::new().k(features.ncols() + 1);
assert!(selector.fit(&features, &target).is_err());
}
#[test]
fn test_integer_programming_selector_invalid_k() {
let (features, target) = create_test_data();
let selector = IntegerProgrammingFeatureSelector::new().k(features.ncols() + 1);
assert!(selector.fit(&features, &target).is_err());
}
#[test]
fn test_correlation_coefficient() {
let x = Array1::from_vec(vec![1.0, 2.0, 3.0, 4.0, 5.0]);
let y = Array1::from_vec(vec![2.0, 4.0, 6.0, 8.0, 10.0]);
let corr = correlation_coefficient(&x, &y).expect("operation should succeed");
assert!((corr - 1.0).abs() < 1e-10);
let z = Array1::from_vec(vec![5.0, 4.0, 3.0, 2.0, 1.0]);
let corr2 = correlation_coefficient(&x, &z).expect("operation should succeed");
assert!((corr2 + 1.0).abs() < 1e-10); }
#[test]
fn test_helper_functions() {
let a = Array1::from_vec(vec![1.0, 2.0]);
let b = Array1::from_vec(vec![3.0, 4.0]);
let outer = outer_product(&a, &b);
assert_eq!(outer[[0, 0]], 3.0);
assert_eq!(outer[[0, 1]], 4.0);
assert_eq!(outer[[1, 0]], 6.0);
assert_eq!(outer[[1, 1]], 8.0);
let matrix = Array2::from_shape_vec((2, 2), vec![1.0, 2.0, 3.0, 4.0])
.expect("operation should succeed");
let tr = trace(&matrix);
assert_eq!(tr, 5.0);
let diag = extract_diagonal(&matrix);
assert_eq!(diag[0], 1.0);
assert_eq!(diag[1], 4.0);
}
}