sklears_feature_selection/genetic_optimization/
functions.rs1use super::types::*;
6use scirs2_core::ndarray::{Array1, Array2, ArrayBase, Axis, Dim, OwnedRepr};
7use scirs2_core::random::rngs::StdRng;
8use scirs2_core::random::RngExt;
9use sklears_core::{
10 error::Result as SklResult,
11 traits::{Fit, Score},
12};
13
14type Mat2f64 = ArrayBase<OwnedRepr<f64>, Dim<[usize; 2]>, f64>;
16type Vec1f64 = ArrayBase<OwnedRepr<f64>, Dim<[usize; 1]>, f64>;
17type Vec1i32 = ArrayBase<OwnedRepr<i32>, Dim<[usize; 1]>, i32>;
18type Vec1usize = ArrayBase<OwnedRepr<usize>, Dim<[usize; 1]>, usize>;
19pub(crate) fn generate_random_chromosome(
21 n_features: usize,
22 min_features: usize,
23 max_features: usize,
24 rng: &mut StdRng,
25) -> Vec<bool> {
26 let mut chromosome = vec![false; n_features];
27 let n_selected = rng.random_range(min_features..max_features.min(n_features + 1));
28 let mut selected_indices: Vec<usize> = (0..n_features).collect();
29 for _ in 0..n_selected {
30 let idx = rng.random_range(0..selected_indices.len());
31 let feature_idx = selected_indices.swap_remove(idx);
32 chromosome[feature_idx] = true;
33 }
34 chromosome
35}
36pub(crate) fn tournament_selection<'a>(
38 population: &'a [Vec<bool>],
39 fitness_scores: &[f64],
40 tournament_size: usize,
41 rng: &mut StdRng,
42) -> &'a Vec<bool> {
43 let mut best_idx = rng.random_range(0..population.len());
44 let mut best_fitness = fitness_scores[best_idx];
45 for _ in 1..tournament_size {
46 let idx = rng.random_range(0..population.len());
47 if fitness_scores[idx] > best_fitness {
48 best_idx = idx;
49 best_fitness = fitness_scores[idx];
50 }
51 }
52 &population[best_idx]
53}
54pub(crate) fn uniform_crossover(
56 parent1: &[bool],
57 parent2: &[bool],
58 rng: &mut StdRng,
59) -> (Vec<bool>, Vec<bool>) {
60 let mut child1 = Vec::with_capacity(parent1.len());
61 let mut child2 = Vec::with_capacity(parent1.len());
62 for i in 0..parent1.len() {
63 if rng.random::<bool>() {
64 child1.push(parent1[i]);
65 child2.push(parent2[i]);
66 } else {
67 child1.push(parent2[i]);
68 child2.push(parent1[i]);
69 }
70 }
71 (child1, child2)
72}
73pub(crate) fn flip_mutation(
75 chromosome: &mut [bool],
76 min_features: usize,
77 max_features: usize,
78 rng: &mut StdRng,
79) {
80 let flip_idx = rng.random_range(0..chromosome.len());
81 chromosome[flip_idx] = !chromosome[flip_idx];
82 let new_count = chromosome.iter().filter(|&&x| x).count();
83 if new_count < min_features {
84 let need_to_add = min_features - new_count;
85 let false_indices: Vec<usize> = chromosome
86 .iter()
87 .enumerate()
88 .filter_map(|(i, &val)| if !val { Some(i) } else { None })
89 .collect();
90 for _ in 0..need_to_add {
91 if !false_indices.is_empty() {
92 let idx = rng.random_range(0..false_indices.len());
93 chromosome[false_indices[idx]] = true;
94 }
95 }
96 } else if new_count > max_features {
97 let need_to_remove = new_count - max_features;
98 let true_indices: Vec<usize> = chromosome
99 .iter()
100 .enumerate()
101 .filter_map(|(i, &val)| if val { Some(i) } else { None })
102 .collect();
103 for _ in 0..need_to_remove {
104 if !true_indices.is_empty() {
105 let idx = rng.random_range(0..true_indices.len());
106 chromosome[true_indices[idx]] = false;
107 }
108 }
109 }
110}
111pub(crate) fn cross_validate_score_f64<E>(
113 estimator: &E,
114 x: &Mat2f64,
115 y: &Vec1f64,
116 cv_folds: usize,
117) -> SklResult<f64>
118where
119 E: Clone + Fit<Mat2f64, Vec1f64>,
120 E::Fitted: Score<Mat2f64, Vec1f64>,
121 <E::Fitted as Score<Mat2f64, Vec1f64>>::Float: Into<f64>,
122{
123 let n_samples = x.nrows();
124 let cv = KFold::new(cv_folds).shuffle(true);
125 let mut scores = Vec::new();
126 for (train_idx, test_idx) in cv.split(n_samples) {
127 let x_train = x.select(Axis(0), &train_idx);
128 let y_train = Array1::from_iter(train_idx.iter().map(|&i| y[i]));
129 let x_test = x.select(Axis(0), &test_idx);
130 let y_test = Array1::from_iter(test_idx.iter().map(|&i| y[i]));
131 let fitted = estimator.clone().fit(&x_train, &y_train)?;
132 let score = fitted.score(&x_test, &y_test)?;
133 scores.push(score);
134 }
135 let mut sum = 0.0;
136 for score in &scores {
137 sum += (*score).into();
138 }
139 Ok(sum / scores.len() as f64)
140}
141
142pub(crate) fn cross_validate_score_i32<E>(
144 estimator: &E,
145 x: &Mat2f64,
146 y: &Vec1i32,
147 cv_folds: usize,
148) -> SklResult<f64>
149where
150 E: Clone + Fit<Mat2f64, Vec1i32>,
151 E::Fitted: Score<Mat2f64, Vec1i32>,
152 <E::Fitted as Score<Mat2f64, Vec1i32>>::Float: Into<f64>,
153{
154 let n_samples = x.nrows();
155 let cv = KFold::new(cv_folds).shuffle(true);
156 let mut scores = Vec::new();
157 for (train_idx, test_idx) in cv.split(n_samples) {
158 let x_train = x.select(Axis(0), &train_idx);
159 let y_train = Array1::from_iter(train_idx.iter().map(|&i| y[i]));
160 let x_test = x.select(Axis(0), &test_idx);
161 let y_test = Array1::from_iter(test_idx.iter().map(|&i| y[i]));
162 let fitted = estimator.clone().fit(&x_train, &y_train)?;
163 let score = fitted.score(&x_test, &y_test)?;
164 scores.push(score);
165 }
166 let mut sum = 0.0;
167 for score in &scores {
168 sum += (*score).into();
169 }
170 Ok(sum / scores.len() as f64)
171}
172
173pub(crate) fn cross_validate_score_usize<E>(
175 estimator: &E,
176 x: &Mat2f64,
177 y: &Vec1usize,
178 cv_folds: usize,
179) -> SklResult<f64>
180where
181 E: Clone + Fit<Mat2f64, Vec1usize>,
182 E::Fitted: Score<Mat2f64, Vec1usize>,
183 <E::Fitted as Score<Mat2f64, Vec1usize>>::Float: Into<f64>,
184{
185 let n_samples = x.nrows();
186 let cv = KFold::new(cv_folds).shuffle(true);
187 let mut scores = Vec::new();
188 for (train_idx, test_idx) in cv.split(n_samples) {
189 let x_train = x.select(Axis(0), &train_idx);
190 let y_train = Array1::from_iter(train_idx.iter().map(|&i| y[i]));
191 let x_test = x.select(Axis(0), &test_idx);
192 let y_test = Array1::from_iter(test_idx.iter().map(|&i| y[i]));
193 let fitted = estimator.clone().fit(&x_train, &y_train)?;
194 let score = fitted.score(&x_test, &y_test)?;
195 scores.push(score);
196 }
197 let mut sum = 0.0;
198 for score in &scores {
199 sum += (*score).into();
200 }
201 Ok(sum / scores.len() as f64)
202}
203pub trait ObjectiveFunction: Send + Sync + std::fmt::Debug {
205 fn evaluate(
208 &self,
209 x: &Array2<f64>,
210 y_classif: Option<&Array1<i32>>,
211 y_regression: Option<&Array1<f64>>,
212 feature_mask: &[bool],
213 ) -> SklResult<f64>;
214 fn name(&self) -> &str;
216 fn is_minimization(&self) -> bool {
218 false
219 }
220 fn clone_box(&self) -> Box<dyn ObjectiveFunction>;
222}
223impl Clone for Box<dyn ObjectiveFunction> {
224 fn clone(&self) -> Self {
225 self.clone_box()
226 }
227}
228pub(crate) fn compute_pearson_correlation(x: &Array1<f64>, y: &Array1<f64>) -> f64 {
230 let n = x.len() as f64;
231 if n < 2.0 {
232 return 0.0;
233 }
234 let mean_x = x.mean().unwrap_or(0.0);
235 let mean_y = y.mean().unwrap_or(0.0);
236 let mut numerator = 0.0;
237 let mut sum_sq_x = 0.0;
238 let mut sum_sq_y = 0.0;
239 for (&xi, &yi) in x.iter().zip(y.iter()) {
240 let dx = xi - mean_x;
241 let dy = yi - mean_y;
242 numerator += dx * dy;
243 sum_sq_x += dx * dx;
244 sum_sq_y += dy * dy;
245 }
246 let denominator = (sum_sq_x * sum_sq_y).sqrt();
247 if denominator == 0.0 {
248 0.0
249 } else {
250 numerator / denominator
251 }
252}