1use crate::error::{Result, TransformError};
7use scirs2_core::ndarray::{Array1, ArrayStatCompat, ArrayView1, ArrayView2};
8use scirs2_core::random::seq::SliceRandom;
9use scirs2_core::validation::check_not_empty;
10use std::collections::HashMap;
11
12#[cfg(feature = "auto-feature-engineering")]
13use std::collections::VecDeque;
14
15use statrs::statistics::Statistics;
16#[cfg(feature = "auto-feature-engineering")]
17use tch::{nn, Device, Tensor};
18
19#[derive(Debug, Clone)]
21pub struct DatasetMetaFeatures {
22 pub n_samples: usize,
24 pub n_features: usize,
26 pub sparsity: f64,
28 pub mean_correlation: f64,
30 pub std_correlation: f64,
32 pub mean_skewness: f64,
34 pub mean_kurtosis: f64,
36 pub missing_ratio: f64,
38 pub variance_ratio: f64,
40 pub outlier_ratio: f64,
42 pub has_missing: bool,
44}
45
46#[derive(Debug, Clone, PartialEq, Eq, Hash)]
48pub enum TransformationType {
49 StandardScaler,
51 MinMaxScaler,
53 RobustScaler,
55 PowerTransformer,
57 PolynomialFeatures,
59 PCA,
61 VarianceThreshold,
63 QuantileTransformer,
65 BinaryEncoder,
67 TargetEncoder,
69}
70
71#[derive(Debug, Clone)]
73pub struct TransformationConfig {
74 pub transformation_type: TransformationType,
76 pub parameters: HashMap<String, f64>,
78 pub expected_performance: f64,
80}
81
82#[cfg(feature = "auto-feature-engineering")]
84pub struct MetaLearningModel {
85 model: nn::Sequential,
87 vs: nn::VarStore,
89 device: Device,
91 training_cache: Vec<(DatasetMetaFeatures, Vec<TransformationConfig>)>,
93}
94
95#[cfg(feature = "auto-feature-engineering")]
96impl MetaLearningModel {
97 pub fn new() -> Result<Self> {
99 let device = Device::cuda_if_available();
100 let vs = nn::VarStore::new(device);
101 let root = vs.root();
102
103 let model = nn::seq()
105 .add(nn::linear(&root / "layer1", 10, 64, Default::default()))
106 .add_fn(|xs| xs.relu())
107 .add(nn::linear(&root / "layer2", 64, 32, Default::default()))
108 .add_fn(|xs| xs.relu())
109 .add(nn::linear(&root / "layer3", 32, 16, Default::default()))
110 .add_fn(|xs| xs.relu())
111 .add(nn::linear(&root / "output", 16, 10, Default::default()))
112 .add_fn(|xs| xs.softmax(-1, tch::Kind::Float));
113
114 Ok(MetaLearningModel {
115 model,
116 vs,
117 device,
118 training_cache: Vec::new(),
119 })
120 }
121
122 pub fn train(
124 &mut self,
125 training_data: Vec<(DatasetMetaFeatures, Vec<TransformationConfig>)>,
126 ) -> Result<()> {
127 self.training_cache.extend(training_data.clone());
128
129 let (input_features, target_scores) = self.prepare_training_data(&training_data)?;
131
132 for epoch in 0..100 {
137 let predicted = input_features.apply(&self.model);
138 let loss = predicted.mse_loss(&target_scores, tch::Reduction::Mean);
139
140 if epoch % 20 == 0 {
141 println!("Epoch {epoch}: Loss = {:.4}", loss.double_value(&[]));
142 }
143
144 }
146
147 Ok(())
148 }
149
150 pub fn predict_transformations(
152 &self,
153 meta_features: &DatasetMetaFeatures,
154 ) -> Result<Vec<TransformationConfig>> {
155 let input_tensor = self.meta_features_to_tensor(meta_features)?;
156 let prediction = input_tensor.apply(&self.model);
157
158 self.tensor_to_transformations(&prediction)
160 }
161
162 fn prepare_training_data(
163 &self,
164 training_data: &[(DatasetMetaFeatures, Vec<TransformationConfig>)],
165 ) -> Result<(Tensor, Tensor)> {
166 if training_data.is_empty() {
167 return Err(TransformError::InvalidInput(
168 "Training _data cannot be empty".to_string(),
169 ));
170 }
171
172 let n_samples = training_data.len();
173 let mut input_features = Vec::with_capacity(n_samples * 10);
174 let mut target_scores = Vec::with_capacity(n_samples * 10);
175
176 for (meta_features, transformations) in training_data {
177 let features = vec![
179 (meta_features.n_samples as f64).ln().max(0.0), (meta_features.n_features as f64).ln().max(0.0), meta_features.sparsity.clamp(0.0, 1.0), meta_features.mean_correlation.clamp(-1.0, 1.0), meta_features.std_correlation.max(0.0), meta_features.mean_skewness.clamp(-10.0, 10.0), meta_features.mean_kurtosis.clamp(-10.0, 10.0), meta_features.missing_ratio.clamp(0.0, 1.0), meta_features.variance_ratio.max(0.0), meta_features.outlier_ratio.clamp(0.0, 1.0), ];
190
191 if features.iter().any(|&f| !f.is_finite()) {
193 return Err(TransformError::ComputationError(
194 "Non-finite values detected in meta-features".to_string(),
195 ));
196 }
197
198 input_features.extend(features);
199
200 let mut scores = vec![0.0f64; 10]; for config in transformations {
203 let idx = self.transformation_type_to_index(&config.transformation_type);
204 let performance = config.expected_performance.clamp(0.0, 1.0); scores[idx] = scores[idx].max(performance as f64); }
207 target_scores.extend(scores);
208 }
209
210 let input_tensor = Tensor::from_slice(&input_features)
211 .reshape(&[n_samples as i64, 10])
212 .to_device(self.device);
213 let target_tensor = Tensor::from_slice(&target_scores)
214 .reshape(&[n_samples as i64, 10])
215 .to_device(self.device);
216
217 Ok((input_tensor, target_tensor))
218 }
219
220 fn meta_features_to_tensor(&self, meta_features: &DatasetMetaFeatures) -> Result<Tensor> {
221 let features = vec![
223 (meta_features.n_samples as f64).ln().max(0.0),
224 (meta_features.n_features as f64).ln().max(0.0),
225 meta_features.sparsity.clamp(0.0, 1.0),
226 meta_features.mean_correlation.clamp(-1.0, 1.0),
227 meta_features.std_correlation.max(0.0),
228 meta_features.mean_skewness.clamp(-10.0, 10.0),
229 meta_features.mean_kurtosis.clamp(-10.0, 10.0),
230 meta_features.missing_ratio.clamp(0.0, 1.0),
231 meta_features.variance_ratio.max(0.0),
232 meta_features.outlier_ratio.clamp(0.0, 1.0),
233 ];
234
235 if features.iter().any(|&f| !f.is_finite()) {
237 return Err(TransformError::ComputationError(
238 "Non-finite values detected in meta-features".to_string(),
239 ));
240 }
241
242 Ok(Tensor::from_slice(&features)
243 .reshape(&[1, 10])
244 .to_device(self.device))
245 }
246
247 fn tensor_to_transformations(&self, prediction: &Tensor) -> Result<Vec<TransformationConfig>> {
248 let scores: Vec<f64> = prediction.try_into().map_err(|e| {
249 TransformError::ComputationError(format!("Failed to extract tensor data: {:?}", e))
250 })?;
251
252 if scores.len() != 10 {
253 return Err(TransformError::ComputationError(format!(
254 "Expected 10 prediction scores, got {}",
255 scores.len()
256 )));
257 }
258
259 let mut transformations = Vec::new();
260
261 let max_score = scores.iter().fold(0.0f64, |a, &b| a.max(b));
263 let mean_score = scores.iter().sum::<f64>() / scores.len() as f64;
264 let threshold = (max_score * 0.7 + mean_score * 0.3).max(0.3); for (i, &score) in scores.iter().enumerate() {
267 if score > threshold && score.is_finite() {
268 let transformation_type = self.index_to_transformation_type(i);
269 let config = TransformationConfig {
270 transformation_type: transformation_type.clone(),
271 parameters: self.get_default_parameters_for_type(&transformation_type),
272 expected_performance: score.clamp(0.0, 1.0), };
274 transformations.push(config);
275 }
276 }
277
278 if transformations.is_empty() {
280 let mut score_indices: Vec<(usize, f64)> = scores
281 .iter()
282 .enumerate()
283 .filter(|(_, &score)| score.is_finite())
284 .map(|(i, &score)| (i, score))
285 .collect();
286
287 score_indices
288 .sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap_or(std::cmp::Ordering::Equal));
289
290 for (i, score) in score_indices.into_iter().take(3) {
291 let transformation_type = self.index_to_transformation_type(i);
292 let config = TransformationConfig {
293 transformation_type: transformation_type.clone(),
294 parameters: self.get_default_parameters_for_type(&transformation_type),
295 expected_performance: score.clamp(0.0, 1.0),
296 };
297 transformations.push(config);
298 }
299 }
300
301 transformations.sort_by(|a, b| {
303 b.expected_performance
304 .partial_cmp(&a.expected_performance)
305 .unwrap_or(std::cmp::Ordering::Equal)
306 });
307
308 Ok(transformations)
309 }
310
311 fn transformation_type_to_index(&self, t_type: &TransformationType) -> usize {
312 match t_type {
313 TransformationType::StandardScaler => 0,
314 TransformationType::MinMaxScaler => 1,
315 TransformationType::RobustScaler => 2,
316 TransformationType::PowerTransformer => 3,
317 TransformationType::PolynomialFeatures => 4,
318 TransformationType::PCA => 5,
319 TransformationType::VarianceThreshold => 6,
320 TransformationType::QuantileTransformer => 7,
321 TransformationType::BinaryEncoder => 8,
322 TransformationType::TargetEncoder => 9,
323 }
324 }
325
326 fn index_to_transformation_type(&self, index: usize) -> TransformationType {
327 match index {
328 0 => TransformationType::StandardScaler,
329 1 => TransformationType::MinMaxScaler,
330 2 => TransformationType::RobustScaler,
331 3 => TransformationType::PowerTransformer,
332 4 => TransformationType::PolynomialFeatures,
333 5 => TransformationType::PCA,
334 6 => TransformationType::VarianceThreshold,
335 7 => TransformationType::QuantileTransformer,
336 8 => TransformationType::BinaryEncoder,
337 _ => TransformationType::StandardScaler,
338 }
339 }
340
341 fn get_default_parameters_for_type(&self, t_type: &TransformationType) -> HashMap<String, f64> {
342 let mut params = HashMap::new();
343 match t_type {
344 TransformationType::PCA => {
345 params.insert("n_components".to_string(), 0.95); }
347 TransformationType::PolynomialFeatures => {
348 params.insert("degree".to_string(), 2.0);
349 params.insert("include_bias".to_string(), 0.0);
350 }
351 TransformationType::VarianceThreshold => {
352 params.insert("threshold".to_string(), 0.01);
353 }
354 _ => {} }
356 params
357 }
358}
359
360pub struct AutoFeatureEngineer {
362 #[cfg(feature = "auto-feature-engineering")]
363 meta_model: MetaLearningModel,
364 #[cfg(feature = "auto-feature-engineering")]
366 transformation_history: Vec<(DatasetMetaFeatures, Vec<TransformationConfig>, f64)>,
367}
368
369impl AutoFeatureEngineer {
370 #[allow(dead_code)]
372 pub fn pearson_correlation(&self, x: &ArrayView1<f64>, y: &ArrayView1<f64>) -> Result<f64> {
373 self.pearson_correlation_internal(x, y)
374 }
375 pub fn new() -> Result<Self> {
377 #[cfg(feature = "auto-feature-engineering")]
378 let meta_model = MetaLearningModel::new()?;
379
380 Ok(AutoFeatureEngineer {
381 #[cfg(feature = "auto-feature-engineering")]
382 meta_model,
383 #[cfg(feature = "auto-feature-engineering")]
384 transformation_history: Vec::new(),
385 })
386 }
387
388 pub fn extract_meta_features(&self, x: &ArrayView2<f64>) -> Result<DatasetMetaFeatures> {
390 check_not_empty(x, "x")?;
391
392 for &val in x.iter() {
394 if !val.is_finite() {
395 return Err(crate::error::TransformError::DataValidationError(
396 "Data contains non-finite values".to_string(),
397 ));
398 }
399 }
400
401 let (n_samples, n_features) = x.dim();
402
403 if n_samples < 2 || n_features < 1 {
404 return Err(TransformError::InvalidInput(
405 "Dataset must have at least 2 samples and 1 feature".to_string(),
406 ));
407 }
408
409 let zeros = x.iter().filter(|&&val| val == 0.0).count();
411 let sparsity = zeros as f64 / (n_samples * n_features) as f64;
412
413 let correlations = self.compute_feature_correlations(x)?;
415 let mean_correlation = correlations.mean();
416 let std_correlation = 0.0; let (mean_skewness, mean_kurtosis) = self.compute_distribution_stats(x)?;
420
421 let missing_count = x.iter().filter(|val| val.is_nan()).count();
423 let missing_ratio = missing_count as f64 / (n_samples * n_features) as f64;
424 let has_missing = missing_count > 0;
425
426 let variances: Array1<f64> = x.var_axis(scirs2_core::ndarray::Axis(0), 0.0);
428 let finite_variances: Vec<f64> = variances
429 .iter()
430 .filter(|&&v| v.is_finite() && v >= 0.0)
431 .copied()
432 .collect();
433
434 let variance_ratio = if finite_variances.is_empty() {
435 0.0
436 } else {
437 let mean_var = finite_variances.iter().sum::<f64>() / finite_variances.len() as f64;
438 if mean_var < f64::EPSILON {
439 0.0
440 } else {
441 let var_of_vars = finite_variances
442 .iter()
443 .map(|&v| (v - mean_var).powi(2))
444 .sum::<f64>()
445 / finite_variances.len() as f64;
446 (var_of_vars.sqrt() / mean_var).min(100.0) }
448 };
449
450 let outlier_ratio = self.compute_outlier_ratio(x)?;
452
453 Ok(DatasetMetaFeatures {
454 n_samples,
455 n_features,
456 sparsity,
457 mean_correlation,
458 std_correlation,
459 mean_skewness,
460 mean_kurtosis,
461 missing_ratio,
462 variance_ratio,
463 outlier_ratio,
464 has_missing,
465 })
466 }
467
468 #[cfg(feature = "auto-feature-engineering")]
470 pub fn recommend_transformations(
471 &self,
472 x: &ArrayView2<f64>,
473 ) -> Result<Vec<TransformationConfig>> {
474 let meta_features = self.extract_meta_features(x)?;
475 self.meta_model.predict_transformations(&meta_features)
476 }
477
478 #[cfg(not(feature = "auto-feature-engineering"))]
480 pub fn recommend_transformations(
481 &self,
482 x: &ArrayView2<f64>,
483 ) -> Result<Vec<TransformationConfig>> {
484 self.rule_based_recommendations(x)
486 }
487
488 fn rule_based_recommendations(&self, x: &ArrayView2<f64>) -> Result<Vec<TransformationConfig>> {
490 let meta_features = self.extract_meta_features(x)?;
491 let mut recommendations = Vec::new();
492
493 if meta_features.mean_skewness.abs() > 1.0 {
495 recommendations.push(TransformationConfig {
496 transformation_type: TransformationType::PowerTransformer,
497 parameters: HashMap::new(),
498 expected_performance: 0.8,
499 });
500 }
501
502 if meta_features.n_features > 100 {
504 let mut params = HashMap::new();
505 params.insert("n_components".to_string(), 0.95);
506 recommendations.push(TransformationConfig {
507 transformation_type: TransformationType::PCA,
508 parameters: params,
509 expected_performance: 0.75,
510 });
511 }
512
513 if meta_features.variance_ratio > 1.0 {
515 recommendations.push(TransformationConfig {
516 transformation_type: TransformationType::StandardScaler,
517 parameters: HashMap::new(),
518 expected_performance: 0.9,
519 });
520 }
521
522 if meta_features.outlier_ratio > 0.1 {
524 recommendations.push(TransformationConfig {
525 transformation_type: TransformationType::RobustScaler,
526 parameters: HashMap::new(),
527 expected_performance: 0.85,
528 });
529 }
530
531 recommendations.sort_by(|a, b| {
533 b.expected_performance
534 .partial_cmp(&a.expected_performance)
535 .unwrap()
536 });
537
538 Ok(recommendations)
539 }
540
541 #[cfg(feature = "auto-feature-engineering")]
543 pub fn update_model(
544 &mut self,
545 meta_features: DatasetMetaFeatures,
546 transformations: Vec<TransformationConfig>,
547 performance: f64,
548 ) -> Result<()> {
549 self.transformation_history.push((
550 meta_features.clone(),
551 transformations.clone(),
552 performance,
553 ));
554
555 if self.transformation_history.len() % 10 == 0 {
557 let training_data: Vec<_> = self
558 .transformation_history
559 .iter()
560 .map(|(meta, trans, _perf)| (meta.clone(), trans.clone()))
561 .collect();
562 self.meta_model.train(training_data)?;
563 }
564
565 Ok(())
566 }
567
568 fn compute_feature_correlations(&self, x: &ArrayView2<f64>) -> Result<Array1<f64>> {
569 let n_features = x.ncols();
570
571 if n_features < 2 {
572 return Ok(Array1::zeros(0));
573 }
574
575 let mut correlations = Vec::with_capacity((n_features * (n_features - 1)) / 2);
576
577 for i in 0..n_features {
578 for j in i + 1..n_features {
579 let col_i = x.column(i);
580 let col_j = x.column(j);
581 let correlation = self.pearson_correlation_internal(&col_i, &col_j)?;
582 correlations.push(correlation);
583 }
584 }
585
586 Ok(Array1::from_vec(correlations))
587 }
588
589 fn pearson_correlation_internal(
590 &self,
591 x: &ArrayView1<f64>,
592 y: &ArrayView1<f64>,
593 ) -> Result<f64> {
594 if x.len() != y.len() {
595 return Err(TransformError::InvalidInput(
596 "Arrays must have the same length for correlation calculation".to_string(),
597 ));
598 }
599
600 if x.len() < 2 {
601 return Ok(0.0);
602 }
603
604 let _n = x.len() as f64;
605 let mean_x = x.mean_or(0.0);
606 let mean_y = y.mean_or(0.0);
607
608 let numerator: f64 = x
609 .iter()
610 .zip(y.iter())
611 .map(|(&xi, &yi)| (xi - mean_x) * (yi - mean_y))
612 .sum();
613
614 let sum_sq_x: f64 = x.iter().map(|&xi| (xi - mean_x).powi(2)).sum();
615 let sum_sq_y: f64 = y.iter().map(|&yi| (yi - mean_y).powi(2)).sum();
616
617 let denominator = (sum_sq_x * sum_sq_y).sqrt();
618
619 if denominator < f64::EPSILON {
620 Ok(0.0)
621 } else {
622 let correlation = numerator / denominator;
623 Ok(correlation.clamp(-1.0, 1.0))
625 }
626 }
627
628 fn compute_distribution_stats(&self, x: &ArrayView2<f64>) -> Result<(f64, f64)> {
629 let mut skewness_values = Vec::new();
630 let mut kurtosis_values = Vec::new();
631
632 for col in x.columns() {
633 let finite_values: Vec<f64> = col
635 .iter()
636 .filter(|&&val| val.is_finite())
637 .copied()
638 .collect();
639
640 if finite_values.len() < 3 {
641 continue; }
643
644 let n = finite_values.len() as f64;
645 let mean = finite_values.iter().sum::<f64>() / n;
646
647 let variance = finite_values
649 .iter()
650 .map(|&val| (val - mean).powi(2))
651 .sum::<f64>()
652 / (n - 1.0); let std = variance.sqrt();
655
656 if std > f64::EPSILON * 1000.0 {
657 let m3: f64 = finite_values
660 .iter()
661 .map(|&val| ((val - mean) / std).powi(3))
662 .sum::<f64>()
663 / n;
664
665 let skew = if n > 2.0 {
666 m3 * (n * (n - 1.0)).sqrt() / (n - 2.0) } else {
668 m3
669 };
670
671 let m4: f64 = finite_values
673 .iter()
674 .map(|&val| ((val - mean) / std).powi(4))
675 .sum::<f64>()
676 / n;
677
678 let kurt = if n > 3.0 {
679 let numerator = (n - 1.0) * ((n + 1.0) * m4 - 3.0 * (n - 1.0));
681 let denominator = (n - 2.0) * (n - 3.0);
682 numerator / denominator
683 } else {
684 m4 - 3.0 };
686
687 skewness_values.push(skew.clamp(-20.0, 20.0));
689 kurtosis_values.push(kurt.clamp(-20.0, 20.0));
690 }
691 }
692
693 let mean_skewness = if skewness_values.is_empty() {
694 0.0
695 } else {
696 skewness_values.iter().sum::<f64>() / skewness_values.len() as f64
697 };
698
699 let mean_kurtosis = if kurtosis_values.is_empty() {
700 0.0
701 } else {
702 kurtosis_values.iter().sum::<f64>() / kurtosis_values.len() as f64
703 };
704
705 Ok((mean_skewness, mean_kurtosis))
706 }
707
708 fn compute_outlier_ratio(&self, x: &ArrayView2<f64>) -> Result<f64> {
709 let mut total_outliers = 0;
710 let mut total_values = 0;
711
712 for col in x.columns() {
713 let mut sorted_col: Vec<f64> = col
714 .iter()
715 .filter(|&&val| val.is_finite())
716 .copied()
717 .collect();
718
719 if sorted_col.is_empty() {
720 continue;
721 }
722
723 sorted_col.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
724
725 let n = sorted_col.len();
726 if n < 4 {
727 continue;
728 }
729
730 let q1_idx = (n as f64 * 0.25) as usize;
732 let q3_idx = (n as f64 * 0.75) as usize;
733 let q1 = sorted_col[q1_idx.min(n - 1)];
734 let q3 = sorted_col[q3_idx.min(n - 1)];
735
736 let iqr = q3 - q1;
737
738 if iqr < f64::EPSILON {
740 continue;
741 }
742
743 let lower_bound = q1 - 1.5 * iqr;
744 let upper_bound = q3 + 1.5 * iqr;
745
746 let outliers = col
747 .iter()
748 .filter(|&&val| val.is_finite() && (val < lower_bound || val > upper_bound))
749 .count();
750
751 total_outliers += outliers;
752 total_values += col.len();
753 }
754
755 if total_values == 0 {
756 Ok(0.0)
757 } else {
758 Ok(total_outliers as f64 / total_values as f64)
759 }
760 }
761}
762
763#[cfg(feature = "auto-feature-engineering")]
765pub struct AdvancedMetaLearningSystem {
766 deep_model: nn::Sequential,
768 transformer_model: nn::Sequential,
770 rl_agent: Option<RLAgent>,
772 device: Device,
774 performance_db: Vec<PerformanceRecord>,
776 optimization_weights: FeatureOptimizationWeights,
778 transfer_cache: HashMap<String, Tensor>,
780}
781
782#[cfg(feature = "auto-feature-engineering")]
784pub struct RLAgent {
785 q_network: nn::Sequential,
787 target_network: nn::Sequential,
789 replay_buffer: VecDeque<Experience>,
791 epsilon: f64,
793 learning_rate: f64,
795 gamma: f64,
797}
798
799#[cfg(feature = "auto-feature-engineering")]
801#[derive(Debug, Clone)]
802pub struct Experience {
803 state: Vec<f64>,
805 action: usize,
807 reward: f64,
809 next_state: Vec<f64>,
811 done: bool,
813}
814
815#[cfg(feature = "auto-feature-engineering")]
817#[derive(Debug, Clone)]
818pub struct PerformanceRecord {
819 meta_features: DatasetMetaFeatures,
821 transformations: Vec<TransformationConfig>,
823 metrics: PerformanceMetrics,
825 computational_cost: f64,
827 timestamp: u64,
829}
830
831#[cfg(feature = "auto-feature-engineering")]
833#[derive(Debug, Clone)]
834pub struct FeatureOptimizationWeights {
835 performance_weight: f64,
837 efficiency_weight: f64,
839 interpretability_weight: f64,
841 robustness_weight: f64,
843}
844
845#[cfg(feature = "auto-feature-engineering")]
846impl Default for FeatureOptimizationWeights {
847 fn default() -> Self {
848 FeatureOptimizationWeights {
849 performance_weight: 0.5,
850 efficiency_weight: 0.3,
851 interpretability_weight: 0.1,
852 robustness_weight: 0.1,
853 }
854 }
855}
856
857#[cfg(feature = "auto-feature-engineering")]
859#[derive(Debug, Clone)]
860pub struct PerformanceMetrics {
861 accuracy: f64,
863 training_time: f64,
865 memory_usage: f64,
867 complexity_score: f64,
869 cv_score: f64,
871}
872
873#[cfg(feature = "auto-feature-engineering")]
875#[derive(Debug, Clone)]
876pub struct EnhancedMetaFeatures {
877 pub base_features: DatasetMetaFeatures,
879 pub manifold_dimension: f64,
881 pub clustering_tendency: f64,
883 pub mutual_information_mean: f64,
885 pub entropy_estimate: f64,
887 pub condition_number: f64,
889 pub volume_ratio: f64,
891 pub autocorrelation: f64,
893 pub trend_strength: f64,
895 pub connectivity: f64,
897 pub clustering_coefficient: f64,
899}
900
901#[cfg(feature = "auto-feature-engineering")]
903#[derive(Debug, Clone)]
904pub struct MultiObjectiveRecommendation {
905 pub transformation: TransformationConfig,
907 pub performance_score: f64,
909 pub efficiency_score: f64,
911 pub interpretability_score: f64,
913 pub robustness_score: f64,
915 pub overall_score: f64,
917}
918
919#[cfg(feature = "auto-feature-engineering")]
920impl AdvancedMetaLearningSystem {
921 pub fn new() -> Result<Self> {
923 let device = Device::cuda_if_available();
924 let vs = nn::VarStore::new(device);
925 let root = vs.root();
926
927 let deep_model = nn::seq()
929 .add(nn::linear(
930 &root / "deep_layer1",
931 20,
932 128,
933 Default::default(),
934 ))
935 .add_fn(|xs| xs.relu())
936 .add_fn(|xs| xs.dropout(0.3, false))
937 .add(nn::linear(
938 &root / "deep_layer2",
939 128,
940 256,
941 Default::default(),
942 ))
943 .add_fn(|xs| xs.relu())
944 .add(nn::linear(
945 &root / "deep_layer3",
946 256,
947 128,
948 Default::default(),
949 ))
950 .add_fn(|xs| xs.relu())
951 .add_fn(|xs| xs.dropout(0.3, false))
952 .add(nn::linear(
953 &root / "deep_layer4",
954 128,
955 64,
956 Default::default(),
957 ))
958 .add_fn(|xs| xs.relu())
959 .add(nn::linear(
960 &root / "deep_output",
961 64,
962 20,
963 Default::default(),
964 ))
965 .add_fn(|xs| xs.softmax(-1, tch::Kind::Float));
966
967 let transformer_model = nn::seq()
969 .add(nn::linear(&root / "trans_embed", 20, 256, Default::default()))
970 .add(nn::linear(&root / "trans_layer1", 256, 256, Default::default()))
972 .add_fn(|xs| xs.relu())
973 .add(nn::linear(&root / "trans_layer2", 256, 128, Default::default()))
974 .add_fn(|xs| xs.relu())
975 .add(nn::linear(&root / "trans_output", 128, 20, Default::default()))
976 .add_fn(|xs| xs.softmax(-1, tch::Kind::Float));
977
978 Ok(AdvancedMetaLearningSystem {
979 deep_model,
980 transformer_model,
981 rl_agent: None,
982 device,
983 performance_db: Vec::new(),
984 optimization_weights: FeatureOptimizationWeights::default(),
985 transfer_cache: HashMap::new(),
986 })
987 }
988
989 pub fn initialize_rl_agent(&mut self) -> Result<()> {
991 let vs = nn::VarStore::new(self.device);
992 let root = vs.root();
993
994 let q_network = nn::seq()
996 .add(nn::linear(&root / "q_layer1", 20, 128, Default::default()))
997 .add_fn(|xs| xs.relu())
998 .add(nn::linear(&root / "q_layer2", 128, 256, Default::default()))
999 .add_fn(|xs| xs.relu())
1000 .add(nn::linear(&root / "q_layer3", 256, 128, Default::default()))
1001 .add_fn(|xs| xs.relu())
1002 .add(nn::linear(&root / "q_output", 128, 20, Default::default())); let target_vs = nn::VarStore::new(self.device);
1006 let target_root = target_vs.root();
1007 let target_network = nn::seq()
1008 .add(nn::linear(
1009 &target_root / "target_layer1",
1010 20,
1011 128,
1012 Default::default(),
1013 ))
1014 .add_fn(|xs| xs.relu())
1015 .add(nn::linear(
1016 &target_root / "target_layer2",
1017 128,
1018 256,
1019 Default::default(),
1020 ))
1021 .add_fn(|xs| xs.relu())
1022 .add(nn::linear(
1023 &target_root / "target_layer3",
1024 256,
1025 128,
1026 Default::default(),
1027 ))
1028 .add_fn(|xs| xs.relu())
1029 .add(nn::linear(
1030 &target_root / "target_output",
1031 128,
1032 20,
1033 Default::default(),
1034 ));
1035
1036 self.rl_agent = Some(RLAgent {
1037 q_network,
1038 target_network,
1039 replay_buffer: VecDeque::with_capacity(10000),
1040 epsilon: 0.1,
1041 learning_rate: 0.001,
1042 gamma: 0.99,
1043 });
1044
1045 Ok(())
1046 }
1047
1048 pub fn extract_enhanced_meta_features(
1050 &self,
1051 x: &ArrayView2<f64>,
1052 ) -> Result<EnhancedMetaFeatures> {
1053 let auto_engineer = AutoFeatureEngineer::new()?;
1054 let base_features = auto_engineer.extract_meta_features(x)?;
1055
1056 let _n_samples_n_features = x.dim();
1058
1059 let manifold_dimension = self.estimate_intrinsic_dimension(x)?;
1061 let clustering_tendency = self.hopkins_statistic(x)?;
1062
1063 let mutual_information_mean = self.average_mutual_information(x)?;
1065 let entropy_estimate = self.differential_entropy_estimate(x)?;
1066
1067 let condition_number = self.estimate_condition_number(x)?;
1069 let volume_ratio = self.estimate_volume_ratio(x)?;
1070
1071 let autocorrelation = self.estimate_autocorrelation(x)?;
1073 let trend_strength = self.estimate_trend_strength(x)?;
1074
1075 let connectivity = self.estimate_feature_connectivity(x)?;
1077 let clustering_coefficient = self.feature_clustering_coefficient(x)?;
1078
1079 Ok(EnhancedMetaFeatures {
1080 base_features,
1081 manifold_dimension,
1082 clustering_tendency,
1083 mutual_information_mean,
1084 entropy_estimate,
1085 condition_number,
1086 volume_ratio,
1087 autocorrelation,
1088 trend_strength,
1089 connectivity,
1090 clustering_coefficient,
1091 })
1092 }
1093
1094 pub fn recommend_multi_objective_transformations(
1096 &self,
1097 meta_features: &EnhancedMetaFeatures,
1098 ) -> Result<Vec<MultiObjectiveRecommendation>> {
1099 let deep_input = self.enhanced_meta_features_to_tensor(meta_features)?;
1101 let deep_predictions = deep_input.apply(&self.deep_model);
1102
1103 let transformer_predictions = deep_input.apply(&self.transformer_model);
1105
1106 let ensemble_predictions = (&deep_predictions * 0.6) + (&transformer_predictions * 0.4);
1108
1109 let final_predictions = if let Some(ref rl_agent) = self.rl_agent {
1111 let rl_q_values = deep_input.apply(&rl_agent.q_network);
1112 let rl_softmax = rl_q_values.softmax(-1, tch::Kind::Float);
1113 (&ensemble_predictions * 0.7) + (&rl_softmax * 0.3)
1114 } else {
1115 ensemble_predictions
1116 };
1117
1118 self.tensor_to_multi_objective_recommendations(&final_predictions, meta_features)
1120 }
1121
1122 pub fn apply_transfer_learning(
1124 &mut self,
1125 target_meta_features: &EnhancedMetaFeatures,
1126 ) -> Result<Vec<TransformationConfig>> {
1127 let similar_records = self.find_similar_datasets(target_meta_features, 5)?;
1129
1130 if similar_records.is_empty() {
1131 return self.fallback_recommendations(target_meta_features);
1132 }
1133
1134 let mut transformation_votes: HashMap<TransformationType, (f64, usize)> = HashMap::new();
1136
1137 for record in &similar_records {
1138 let similarity =
1139 self.compute_dataset_similarity(target_meta_features, &record.meta_features)?;
1140
1141 for transformation in &record.transformations {
1142 let performance_score = record.metrics.accuracy * similarity;
1143 let entry = transformation_votes
1144 .entry(transformation.transformation_type.clone())
1145 .or_insert((0.0, 0));
1146 entry.0 += performance_score;
1147 entry.1 += 1;
1148 }
1149 }
1150
1151 let mut ranked_transformations: Vec<_> = transformation_votes
1153 .into_iter()
1154 .map(|(t_type, (total_score, count))| (t_type, total_score / count as f64))
1155 .collect();
1156
1157 ranked_transformations
1158 .sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap_or(std::cmp::Ordering::Equal));
1159
1160 let mut recommendations = Vec::new();
1162 for (t_type, score) in ranked_transformations.into_iter().take(5) {
1163 recommendations.push(TransformationConfig {
1164 transformation_type: t_type.clone(),
1165 parameters: self
1166 .get_optimized_parameters_for_type(&t_type, target_meta_features)?,
1167 expected_performance: score.min(1.0).max(0.0),
1168 });
1169 }
1170
1171 Ok(recommendations)
1172 }
1173
1174 fn estimate_intrinsic_dimension(&self, x: &ArrayView2<f64>) -> Result<f64> {
1176 let n_samples = x.nrows();
1178 if n_samples < 10 {
1179 return Ok(1.0);
1180 }
1181
1182 use scirs2_core::random::Rng;
1184 let mut rng = scirs2_core::random::rng();
1185 let sample_size = 100.min(n_samples);
1186 let mut distances = Vec::new();
1187
1188 for _ in 0..sample_size {
1189 let i = rng.gen_range(0..n_samples);
1190 let j = rng.gen_range(0..n_samples);
1191 if i != j {
1192 let dist = self.euclidean_distance(&x.row(i), &x.row(j));
1193 distances.push(dist);
1194 }
1195 }
1196
1197 distances.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
1199
1200 if distances.is_empty() || distances[0] == 0.0 {
1201 return Ok(1.0);
1202 }
1203
1204 let thresholds = [0.1, 0.2, 0.5, 1.0];
1206 let mut dimension_estimates = Vec::new();
1207
1208 for &threshold in &thresholds {
1209 let count = distances.iter().filter(|&&d| d < threshold).count();
1210 if count > 1 {
1211 let correlation_sum = (count as f64).ln();
1212 let threshold_ln = threshold.ln();
1213 if threshold_ln != 0.0 {
1214 dimension_estimates.push(correlation_sum / threshold_ln);
1215 }
1216 }
1217 }
1218
1219 let avg_dimension = if dimension_estimates.is_empty() {
1220 1.0
1221 } else {
1222 dimension_estimates.iter().sum::<f64>() / dimension_estimates.len() as f64
1223 };
1224
1225 Ok(avg_dimension.max(1.0).min(x.ncols() as f64))
1226 }
1227
1228 fn hopkins_statistic(&self, x: &ArrayView2<f64>) -> Result<f64> {
1229 let (n_samples, n_features) = x.dim();
1231 if n_samples < 10 {
1232 return Ok(0.5); }
1234
1235 use scirs2_core::random::Rng;
1236 let mut rng = scirs2_core::random::rng();
1237 let sample_size = 10.min(n_samples / 2);
1238
1239 let mut min_vals = vec![f64::INFINITY; n_features];
1241 let mut max_vals = vec![f64::NEG_INFINITY; n_features];
1242
1243 for row in x.rows() {
1244 for (j, &val) in row.iter().enumerate() {
1245 min_vals[j] = min_vals[j].min(val);
1246 max_vals[j] = max_vals[j].max(val);
1247 }
1248 }
1249
1250 let mut u_distances = Vec::new();
1251 let mut w_distances = Vec::new();
1252
1253 for _ in 0..sample_size {
1255 let mut random_point = vec![0.0; n_features];
1257 for j in 0..n_features {
1258 random_point[j] = rng.gen_range(min_vals[j]..=max_vals[j]);
1259 }
1260
1261 let mut min_dist_u = f64::INFINITY;
1263 for row in x.rows() {
1264 let dist = self.euclidean_distance_vec(&random_point, &row.to_vec());
1265 min_dist_u = min_dist_u.min(dist);
1266 }
1267 u_distances.push(min_dist_u);
1268
1269 let random_idx = rng.gen_range(0..n_samples);
1271 let data_point = x.row(random_idx).to_vec();
1272
1273 let mut min_dist_w = f64::INFINITY;
1275 for (i, row) in x.rows().into_iter().enumerate() {
1276 if i != random_idx {
1277 let dist = self.euclidean_distance_vec(&data_point, &row.to_vec());
1278 min_dist_w = min_dist_w.min(dist);
1279 }
1280 }
1281 w_distances.push(min_dist_w);
1282 }
1283
1284 let sum_u: f64 = u_distances.iter().sum();
1285 let sum_w: f64 = w_distances.iter().sum();
1286
1287 if sum_u + sum_w == 0.0 {
1288 Ok(0.5)
1289 } else {
1290 Ok(sum_u / (sum_u + sum_w))
1291 }
1292 }
1293
1294 fn average_mutual_information(&self, x: &ArrayView2<f64>) -> Result<f64> {
1295 let (_, n_features) = x.dim();
1296 if n_features < 2 {
1297 return Ok(0.0);
1298 }
1299
1300 let mut mi_sum = 0.0;
1301 let mut pair_count = 0;
1302
1303 use scirs2_core::random::Rng;
1305 let mut rng = scirs2_core::random::rng();
1306 let max_pairs = 50.min((n_features * (n_features - 1)) / 2);
1307
1308 for _ in 0..max_pairs {
1309 let i = rng.gen_range(0..n_features);
1310 let j = rng.gen_range(0..n_features);
1311 if i != j {
1312 let mi = self.estimate_mutual_information(&x.column(i), &x.column(j))?;
1313 mi_sum += mi;
1314 pair_count += 1;
1315 }
1316 }
1317
1318 Ok(if pair_count > 0 {
1319 mi_sum / pair_count as f64
1320 } else {
1321 0.0
1322 })
1323 }
1324
1325 fn estimate_mutual_information(
1326 &self,
1327 x: &scirs2_core::ndarray::ArrayView1<f64>,
1328 y: &scirs2_core::ndarray::ArrayView1<f64>,
1329 ) -> Result<f64> {
1330 let n_bins = 10;
1332 let x_bins = self.create_bins(x, n_bins);
1333 let y_bins = self.create_bins(y, n_bins);
1334
1335 let mut joint_hist = vec![vec![0; n_bins]; n_bins];
1337 let mut x_hist = vec![0; n_bins];
1338 let mut y_hist = vec![0; n_bins];
1339
1340 for (&xi, &yi) in x.iter().zip(y.iter()) {
1341 if xi.is_finite() && yi.is_finite() {
1342 let x_bin = self.find_bin(xi, &x_bins).min(n_bins - 1);
1343 let y_bin = self.find_bin(yi, &y_bins).min(n_bins - 1);
1344 joint_hist[x_bin][y_bin] += 1;
1345 x_hist[x_bin] += 1;
1346 y_hist[y_bin] += 1;
1347 }
1348 }
1349
1350 let total = x.len() as f64;
1351 let mut mi = 0.0;
1352
1353 for i in 0..n_bins {
1354 for j in 0..n_bins {
1355 let p_xy = joint_hist[i][j] as f64 / total;
1356 let p_x = x_hist[i] as f64 / total;
1357 let p_y = y_hist[j] as f64 / total;
1358
1359 if p_xy > 0.0 && p_x > 0.0 && p_y > 0.0 {
1360 mi += p_xy * (p_xy / (p_x * p_y)).ln();
1361 }
1362 }
1363 }
1364
1365 Ok(mi.max(0.0))
1366 }
1367
1368 fn differential_entropy_estimate(&self, x: &ArrayView2<f64>) -> Result<f64> {
1369 let (n_samples, n_features) = x.dim();
1371 if n_samples < 2 {
1372 return Ok(0.0);
1373 }
1374
1375 let mut entropy_sum = 0.0;
1376 for col in x.columns() {
1377 let variance = col.variance();
1378 if variance > 0.0 {
1379 entropy_sum +=
1381 0.5 * (2.0 * std::f64::consts::PI * std::f64::consts::E * variance).ln();
1382 }
1383 }
1384
1385 Ok(entropy_sum / n_features as f64)
1386 }
1387
1388 fn estimate_condition_number(&self, x: &ArrayView2<f64>) -> Result<f64> {
1389 let (n_samples, n_features) = x.dim();
1391 if n_samples < n_features || n_features < 2 {
1392 return Ok(1.0);
1393 }
1394
1395 let mut corr_sum = 0.0;
1397 let mut corr_count = 0;
1398
1399 for i in 0..n_features {
1400 for j in (i + 1)..n_features {
1401 let col_i = x.column(i);
1402 let col_j = x.column(j);
1403 if let Ok(corr) = self.quick_correlation(&col_i, &col_j) {
1404 corr_sum += corr.abs();
1405 corr_count += 1;
1406 }
1407 }
1408 }
1409
1410 let avg_correlation = if corr_count > 0 {
1411 corr_sum / corr_count as f64
1412 } else {
1413 0.0
1414 };
1415
1416 Ok(if avg_correlation > 0.9 {
1418 100.0 } else if avg_correlation > 0.7 {
1420 10.0 } else {
1422 1.0 })
1424 }
1425
1426 fn euclidean_distance(
1428 &self,
1429 a: &scirs2_core::ndarray::ArrayView1<f64>,
1430 b: &scirs2_core::ndarray::ArrayView1<f64>,
1431 ) -> f64 {
1432 a.iter()
1433 .zip(b.iter())
1434 .map(|(&ai, &bi)| (ai - bi).powi(2))
1435 .sum::<f64>()
1436 .sqrt()
1437 }
1438
1439 fn euclidean_distance_vec(&self, a: &[f64], b: &[f64]) -> f64 {
1440 a.iter()
1441 .zip(b.iter())
1442 .map(|(&ai, &bi)| (ai - bi).powi(2))
1443 .sum::<f64>()
1444 .sqrt()
1445 }
1446
1447 fn create_bins(&self, data: &scirs2_core::ndarray::ArrayView1<f64>, n_bins: usize) -> Vec<f64> {
1448 let mut sorted: Vec<f64> = data.iter().filter(|&&x| x.is_finite()).copied().collect();
1449 sorted.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
1450
1451 if sorted.is_empty() {
1452 return vec![0.0; n_bins + 1];
1453 }
1454
1455 let mut bins = Vec::new();
1456 for i in 0..=n_bins {
1457 let idx = (i * (sorted.len() - 1)) / n_bins;
1458 bins.push(sorted[idx]);
1459 }
1460 bins
1461 }
1462
1463 fn find_bin(&self, value: f64, bins: &[f64]) -> usize {
1464 for (i, &bin_edge) in bins.iter().enumerate().take(bins.len() - 1) {
1465 if value <= bin_edge {
1466 return i;
1467 }
1468 }
1469 bins.len() - 2
1470 }
1471
1472 fn estimate_volume_ratio(&self, x: &ArrayView2<f64>) -> Result<f64> {
1474 let (n_samples, n_features) = x.dim();
1475 if n_samples < 4 || n_features < 2 {
1476 return Ok(1.0); }
1478
1479 let sample_size = 1000.min(n_samples);
1481
1482 let mut rng = scirs2_core::random::rng();
1483 let mut all_indices: Vec<usize> = (0..n_samples).collect();
1484 all_indices.shuffle(&mut rng);
1485 let indices: Vec<usize> = all_indices.into_iter().take(sample_size).collect();
1486
1487 let mut min_vals = vec![f64::INFINITY; n_features];
1489 let mut max_vals = vec![f64::NEG_INFINITY; n_features];
1490
1491 for &idx in &indices {
1492 let row = x.row(idx);
1493 for (j, &val) in row.iter().enumerate() {
1494 if val.is_finite() {
1495 min_vals[j] = min_vals[j].min(val);
1496 max_vals[j] = max_vals[j].max(val);
1497 }
1498 }
1499 }
1500
1501 let mut box_volume = 1.0;
1503 for j in 0..n_features {
1504 let range = max_vals[j] - min_vals[j];
1505 if range > f64::EPSILON {
1506 box_volume *= range;
1507 } else {
1508 return Ok(0.0); }
1510 }
1511
1512 let mut variance_product = 1.0;
1516 for j in 0..n_features {
1517 let col_values: Vec<f64> = indices
1518 .iter()
1519 .map(|&idx| x[[idx, j]])
1520 .filter(|&val| val.is_finite())
1521 .collect();
1522
1523 if col_values.len() > 1 {
1524 let mean = col_values.iter().sum::<f64>() / col_values.len() as f64;
1525 let variance = col_values
1526 .iter()
1527 .map(|&val| (val - mean).powi(2))
1528 .sum::<f64>()
1529 / (col_values.len() - 1) as f64;
1530 variance_product *= variance.sqrt();
1531 }
1532 }
1533
1534 if box_volume > f64::EPSILON {
1536 let ratio = (variance_product / box_volume).min(1.0).max(0.0);
1537 Ok(ratio)
1538 } else {
1539 Ok(0.0)
1540 }
1541 }
1542
1543 fn estimate_autocorrelation(&self, x: &ArrayView2<f64>) -> Result<f64> {
1545 let (n_samples, n_features) = x.dim();
1546 if n_samples < 3 {
1547 return Ok(0.0);
1548 }
1549
1550 let mut autocorr_sum = 0.0;
1551 let mut feature_count = 0;
1552
1553 for j in 0..n_features {
1555 let col = x.column(j);
1556 let values: Vec<f64> = col
1557 .iter()
1558 .filter(|&&val| val.is_finite())
1559 .copied()
1560 .collect();
1561
1562 if values.len() < 3 {
1563 continue;
1564 }
1565
1566 let mean = values.iter().sum::<f64>() / values.len() as f64;
1568 let mut numerator = 0.0;
1569 let mut denominator = 0.0;
1570
1571 for i in 0..values.len() - 1 {
1572 numerator += (values[i] - mean) * (values[i + 1] - mean);
1573 }
1574
1575 for &val in &values {
1576 denominator += (val - mean).powi(2);
1577 }
1578
1579 if denominator > f64::EPSILON {
1580 autocorr_sum += numerator / denominator;
1581 feature_count += 1;
1582 }
1583 }
1584
1585 if feature_count > 0 {
1586 Ok((autocorr_sum / feature_count as f64).abs())
1587 } else {
1588 Ok(0.0)
1589 }
1590 }
1591
1592 fn estimate_trend_strength(&self, x: &ArrayView2<f64>) -> Result<f64> {
1594 let (n_samples, n_features) = x.dim();
1595 if n_samples < 5 {
1596 return Ok(0.0);
1597 }
1598
1599 let mut trend_sum = 0.0;
1600 let mut feature_count = 0;
1601
1602 for j in 0..n_features {
1604 let col = x.column(j);
1605 let values: Vec<(f64, f64)> = col
1606 .iter()
1607 .enumerate()
1608 .filter(|(_, val)| val.is_finite())
1609 .map(|(i, val)| (i as f64, *val))
1610 .collect();
1611
1612 if values.len() < 5 {
1613 continue;
1614 }
1615
1616 let n = values.len() as f64;
1618 let sum_x: f64 = values.iter().map(|(x, _)| x).sum();
1619 let sum_y: f64 = values.iter().map(|(_, y)| y).sum();
1620 let sum_xy: f64 = values.iter().map(|(x, y)| x * y).sum();
1621 let sum_x2: f64 = values.iter().map(|(x, _)| x * x).sum();
1622
1623 let denominator = n * sum_x2 - sum_x * sum_x;
1624 if denominator.abs() > f64::EPSILON {
1625 let slope = (n * sum_xy - sum_x * sum_y) / denominator;
1626 let intercept = (sum_y - slope * sum_x) / n;
1627
1628 let y_mean = sum_y / n;
1630 let mut ss_tot = 0.0;
1631 let mut ss_res = 0.0;
1632
1633 for (x_val, y_val) in &values {
1634 let y_pred = slope * x_val + intercept;
1635 ss_tot += (y_val - y_mean).powi(2);
1636 ss_res += (y_val - y_pred).powi(2);
1637 }
1638
1639 if ss_tot > f64::EPSILON {
1640 let r_squared = 1.0 - (ss_res / ss_tot);
1641 trend_sum += r_squared.max(0.0);
1642 feature_count += 1;
1643 }
1644 }
1645 }
1646
1647 if feature_count > 0 {
1648 Ok(trend_sum / feature_count as f64)
1649 } else {
1650 Ok(0.0)
1651 }
1652 }
1653
1654 fn estimate_feature_connectivity(&self, x: &ArrayView2<f64>) -> Result<f64> {
1656 let (_, n_features) = x.dim();
1657 if n_features < 2 {
1658 return Ok(0.0);
1659 }
1660
1661 let mut strong_connections = 0;
1662 let mut total_connections = 0;
1663 let threshold = 0.5; let max_pairs = 100.min((n_features * (n_features - 1)) / 2);
1667 use scirs2_core::random::Rng;
1668 let mut rng = scirs2_core::random::rng();
1669
1670 for _ in 0..max_pairs {
1671 let i = rng.gen_range(0..n_features);
1672 let j = rng.gen_range(0..n_features);
1673
1674 if i != j {
1675 let col_i = x.column(i);
1676 let col_j = x.column(j);
1677
1678 if let Ok(corr) = self.quick_correlation(&col_i, &col_j) {
1679 if corr.abs() > threshold {
1680 strong_connections += 1;
1681 }
1682 total_connections += 1;
1683 }
1684 }
1685 }
1686
1687 if total_connections > 0 {
1688 Ok(strong_connections as f64 / total_connections as f64)
1689 } else {
1690 Ok(0.0)
1691 }
1692 }
1693
1694 fn quick_correlation(
1696 &self,
1697 x: &scirs2_core::ndarray::ArrayView1<f64>,
1698 y: &scirs2_core::ndarray::ArrayView1<f64>,
1699 ) -> Result<f64> {
1700 if x.len() != y.len() || x.len() < 2 {
1701 return Ok(0.0);
1702 }
1703
1704 let n = x.len() as f64;
1705 let mean_x = x.iter().sum::<f64>() / n;
1706 let mean_y = y.iter().sum::<f64>() / n;
1707
1708 let mut numerator = 0.0;
1709 let mut sum_sq_x = 0.0;
1710 let mut sum_sq_y = 0.0;
1711
1712 for (&xi, &yi) in x.iter().zip(y.iter()) {
1713 if xi.is_finite() && yi.is_finite() {
1714 let diff_x = xi - mean_x;
1715 let diff_y = yi - mean_y;
1716 numerator += diff_x * diff_y;
1717 sum_sq_x += diff_x * diff_x;
1718 sum_sq_y += diff_y * diff_y;
1719 }
1720 }
1721
1722 let denominator = (sum_sq_x * sum_sq_y).sqrt();
1723
1724 if denominator < f64::EPSILON {
1725 Ok(0.0)
1726 } else {
1727 let correlation = numerator / denominator;
1728 Ok(correlation.max(-1.0).min(1.0))
1729 }
1730 }
1731
1732 fn feature_clustering_coefficient(&self, x: &ArrayView2<f64>) -> Result<f64> {
1734 let (_, n_features) = x.dim();
1735 if n_features < 3 {
1736 return Ok(0.0);
1737 }
1738
1739 let sample_size = 20.min(n_features);
1741
1742 let mut rng = scirs2_core::random::rng();
1743 let mut all_features: Vec<usize> = (0..n_features).collect();
1744 all_features.shuffle(&mut rng);
1745 let sampled_features: Vec<usize> = all_features.into_iter().take(sample_size).collect();
1746
1747 let threshold = 0.5;
1748 let mut adjacency = vec![vec![false; sample_size]; sample_size];
1749
1750 for (i, &feat_i) in sampled_features.iter().enumerate() {
1752 for (j, &feat_j) in sampled_features.iter().enumerate() {
1753 if i != j {
1754 let col_i = x.column(feat_i);
1755 let col_j = x.column(feat_j);
1756
1757 if let Ok(corr) = self.quick_correlation(&col_i, &col_j) {
1758 adjacency[i][j] = corr.abs() > threshold;
1759 }
1760 }
1761 }
1762 }
1763
1764 let mut total_coefficient = 0.0;
1766 let mut node_count = 0;
1767
1768 for i in 0..sample_size {
1769 let neighbors: Vec<usize> = (0..sample_size).filter(|&j| adjacency[i][j]).collect();
1771
1772 if neighbors.len() >= 2 {
1773 let mut edges_between_neighbors = 0;
1775 let mut possible_edges = 0;
1776
1777 for (ni, &neighbor_i) in neighbors.iter().enumerate() {
1778 for &neighbor_j in neighbors.iter().skip(ni + 1) {
1779 possible_edges += 1;
1780 if adjacency[neighbor_i][neighbor_j] {
1781 edges_between_neighbors += 1;
1782 }
1783 }
1784 }
1785
1786 if possible_edges > 0 {
1787 total_coefficient += edges_between_neighbors as f64 / possible_edges as f64;
1788 node_count += 1;
1789 }
1790 }
1791 }
1792
1793 if node_count > 0 {
1794 Ok(total_coefficient / node_count as f64)
1795 } else {
1796 Ok(0.0)
1797 }
1798 }
1799
1800 fn enhanced_meta_features_to_tensor(&self, features: &EnhancedMetaFeatures) -> Result<Tensor> {
1802 let feature_vec = vec![
1804 (features.base_features.n_samples as f64).ln().max(0.0),
1806 (features.base_features.n_features as f64).ln().max(0.0),
1807 features.base_features.sparsity.max(0.0).min(1.0),
1808 features.base_features.mean_correlation.max(-1.0).min(1.0),
1809 features.base_features.std_correlation.max(0.0),
1810 features.base_features.mean_skewness.max(-10.0).min(10.0),
1811 features.base_features.mean_kurtosis.max(-10.0).min(10.0),
1812 features.base_features.missing_ratio.max(0.0).min(1.0),
1813 features.base_features.variance_ratio.max(0.0),
1814 features.base_features.outlier_ratio.max(0.0).min(1.0),
1815 features
1817 .manifold_dimension
1818 .max(1.0)
1819 .min(features.base_features.n_features as f64)
1820 .ln(),
1821 features.clustering_tendency.max(0.0).min(1.0),
1822 features.mutual_information_mean.max(0.0),
1823 features.entropy_estimate.max(0.0),
1824 (features.condition_number.max(1.0)).ln(),
1825 features.volume_ratio.max(0.0).min(1.0),
1826 features.autocorrelation.max(-1.0).min(1.0),
1827 features.trend_strength.max(0.0).min(1.0),
1828 features.connectivity.max(0.0).min(1.0),
1829 features.clustering_coefficient.max(0.0).min(1.0),
1830 ];
1831
1832 if feature_vec.iter().any(|&f| !f.is_finite()) {
1834 return Err(TransformError::ComputationError(
1835 "Non-finite values in enhanced meta-features".to_string(),
1836 ));
1837 }
1838
1839 Ok(Tensor::f_from_slice(&feature_vec)?
1840 .reshape(&[1, 20])
1841 .to_device(self.device))
1842 }
1843
1844 fn tensor_to_multi_objective_recommendations(
1846 &self,
1847 tensor: &Tensor,
1848 features: &EnhancedMetaFeatures,
1849 ) -> Result<Vec<MultiObjectiveRecommendation>> {
1850 let scores: Vec<f64> = tensor.try_into().map_err(|e| {
1851 TransformError::ComputationError(format!("Failed to extract tensor data: {:?}", e))
1852 })?;
1853
1854 if scores.len() != 20 {
1855 return Err(TransformError::ComputationError(format!(
1856 "Expected 20 prediction scores, got {}",
1857 scores.len()
1858 )));
1859 }
1860
1861 let mut recommendations = Vec::new();
1862
1863 let transformation_types = [
1865 TransformationType::StandardScaler,
1866 TransformationType::MinMaxScaler,
1867 TransformationType::RobustScaler,
1868 TransformationType::PowerTransformer,
1869 TransformationType::PolynomialFeatures,
1870 TransformationType::PCA,
1871 TransformationType::VarianceThreshold,
1872 TransformationType::QuantileTransformer,
1873 TransformationType::BinaryEncoder,
1874 TransformationType::TargetEncoder,
1875 ];
1876
1877 for (i, t_type) in transformation_types.iter().enumerate() {
1878 if i < scores.len() && scores[i].is_finite() && scores[i] > 0.3 {
1879 let performance_score = scores[i].max(0.0).min(1.0);
1881
1882 let efficiency_score = self.estimate_efficiency_score(t_type, features)?;
1884
1885 let interpretability_score = self.estimate_interpretability_score(t_type);
1887
1888 let robustness_score = self.estimate_robustness_score(t_type, features);
1890
1891 let weights = FeatureOptimizationWeights::default();
1893 let overall_score = performance_score * weights.performance_weight
1894 + efficiency_score * weights.efficiency_weight
1895 + interpretability_score * weights.interpretability_weight
1896 + robustness_score * weights.robustness_weight;
1897
1898 recommendations.push(MultiObjectiveRecommendation {
1899 transformation: TransformationConfig {
1900 transformation_type: t_type.clone(),
1901 parameters: self.get_optimized_parameters_for_type(t_type, features)?,
1902 expected_performance: performance_score,
1903 },
1904 performance_score,
1905 efficiency_score,
1906 interpretability_score,
1907 robustness_score,
1908 overall_score,
1909 });
1910 }
1911 }
1912
1913 recommendations.sort_by(|a, b| {
1915 b.overall_score
1916 .partial_cmp(&a.overall_score)
1917 .unwrap_or(std::cmp::Ordering::Equal)
1918 });
1919
1920 Ok(recommendations)
1921 }
1922
1923 fn find_similar_datasets(
1925 &self,
1926 target: &EnhancedMetaFeatures,
1927 k: usize,
1928 ) -> Result<Vec<PerformanceRecord>> {
1929 if self.performance_db.is_empty() {
1930 return Ok(vec![]);
1931 }
1932
1933 let mut similarities: Vec<(usize, f64)> = Vec::new();
1934
1935 for (i, record) in self.performance_db.iter().enumerate() {
1936 let similarity = self.compute_dataset_similarity(target, &record.meta_features)?;
1937 similarities.push((i, similarity));
1938 }
1939
1940 similarities.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap_or(std::cmp::Ordering::Equal));
1942
1943 let mut similar_records = Vec::new();
1944 for (idx, _similarity) in similarities.iter().take(k) {
1945 similar_records.push(self.performance_db[*idx].clone());
1946 }
1947
1948 Ok(similar_records)
1949 }
1950
1951 fn compute_dataset_similarity(
1953 &self,
1954 a: &EnhancedMetaFeatures,
1955 b: &DatasetMetaFeatures,
1956 ) -> Result<f64> {
1957 let features_a = &a.base_features;
1959
1960 let scale_similarity = |val_a: f64, val_b: f64, max_val: f64| -> f64 {
1962 if max_val > 0.0 {
1963 1.0 - (val_a - val_b).abs() / max_val
1964 } else {
1965 if (val_a - val_b).abs() < f64::EPSILON {
1966 1.0
1967 } else {
1968 0.0
1969 }
1970 }
1971 };
1972
1973 let similarities = vec![
1975 scale_similarity(
1976 (features_a.n_samples as f64).ln(),
1977 (b.n_samples as f64).ln(),
1978 20.0, ),
1980 scale_similarity(
1981 (features_a.n_features as f64).ln(),
1982 (b.n_features as f64).ln(),
1983 15.0, ),
1985 scale_similarity(features_a.sparsity, b.sparsity, 1.0),
1986 scale_similarity(features_a.mean_correlation, b.mean_correlation, 2.0),
1987 scale_similarity(features_a.std_correlation, b.std_correlation, 1.0),
1988 scale_similarity(features_a.mean_skewness, b.mean_skewness, 20.0),
1989 scale_similarity(features_a.mean_kurtosis, b.mean_kurtosis, 20.0),
1990 scale_similarity(features_a.missing_ratio, b.missing_ratio, 1.0),
1991 scale_similarity(features_a.variance_ratio, b.variance_ratio, 10.0),
1992 scale_similarity(features_a.outlier_ratio, b.outlier_ratio, 1.0),
1993 ];
1994
1995 let weights = vec![0.15, 0.15, 0.1, 0.15, 0.05, 0.1, 0.1, 0.05, 0.1, 0.05];
1997 let weighted_similarity = similarities
1998 .iter()
1999 .zip(weights.iter())
2000 .map(|(sim, weight)| sim * weight)
2001 .sum::<f64>();
2002
2003 Ok(weighted_similarity.max(0.0).min(1.0))
2004 }
2005
2006 fn fallback_recommendations(
2008 &self,
2009 features: &EnhancedMetaFeatures,
2010 ) -> Result<Vec<TransformationConfig>> {
2011 let mut recommendations = Vec::new();
2012 let base_features = &features.base_features;
2013
2014 recommendations.push(TransformationConfig {
2018 transformation_type: TransformationType::StandardScaler,
2019 parameters: HashMap::new(),
2020 expected_performance: 0.8,
2021 });
2022
2023 if base_features.n_features > 100 || base_features.n_features > base_features.n_samples {
2025 let mut params = HashMap::new();
2026 params.insert("n_components".to_string(), 0.95); recommendations.push(TransformationConfig {
2028 transformation_type: TransformationType::PCA,
2029 parameters: params,
2030 expected_performance: 0.75,
2031 });
2032 }
2033
2034 if base_features.outlier_ratio > 0.1 {
2036 recommendations.push(TransformationConfig {
2037 transformation_type: TransformationType::RobustScaler,
2038 parameters: HashMap::new(),
2039 expected_performance: 0.85,
2040 });
2041 }
2042
2043 if base_features.mean_skewness.abs() > 1.5 {
2045 recommendations.push(TransformationConfig {
2046 transformation_type: TransformationType::PowerTransformer,
2047 parameters: HashMap::new(),
2048 expected_performance: 0.8,
2049 });
2050 }
2051
2052 if base_features.variance_ratio < 0.1 {
2054 let mut params = HashMap::new();
2055 params.insert("threshold".to_string(), 0.01);
2056 recommendations.push(TransformationConfig {
2057 transformation_type: TransformationType::VarianceThreshold,
2058 parameters: params,
2059 expected_performance: 0.7,
2060 });
2061 }
2062
2063 recommendations.sort_by(|a, b| {
2065 b.expected_performance
2066 .partial_cmp(&a.expected_performance)
2067 .unwrap_or(std::cmp::Ordering::Equal)
2068 });
2069
2070 Ok(recommendations.into_iter().take(3).collect()) }
2072
2073 fn get_optimized_parameters_for_type(
2075 &self,
2076 t_type: &TransformationType,
2077 features: &EnhancedMetaFeatures,
2078 ) -> Result<HashMap<String, f64>> {
2079 let mut params = HashMap::new();
2080 let base_features = &features.base_features;
2081
2082 match t_type {
2083 TransformationType::PCA => {
2084 let variance_threshold = if base_features.n_features > 1000 {
2086 0.99
2087 } else {
2088 0.95
2089 };
2090 params.insert("variance_threshold".to_string(), variance_threshold);
2091
2092 let max_components = base_features.n_features.min(base_features.n_samples);
2094 let estimated_components = if base_features.n_features > base_features.n_samples {
2095 (base_features.n_samples as f64 * 0.8) as usize
2096 } else {
2097 (max_components as f64 * variance_threshold) as usize
2098 };
2099 params.insert(
2100 "n_components".to_string(),
2101 estimated_components.max(1) as f64,
2102 );
2103 }
2104
2105 TransformationType::PolynomialFeatures => {
2106 let degree = if base_features.n_features > 50 { 2 } else { 3 };
2108 params.insert("degree".to_string(), degree as f64);
2109 params.insert("include_bias".to_string(), 1.0);
2110 params.insert(
2111 "interaction_only".to_string(),
2112 if base_features.n_features > 20 {
2113 1.0
2114 } else {
2115 0.0
2116 },
2117 );
2118 }
2119
2120 TransformationType::VarianceThreshold => {
2121 let threshold = if base_features.variance_ratio < 0.01 {
2123 0.001
2124 } else {
2125 0.01
2126 };
2127 params.insert("threshold".to_string(), threshold);
2128 }
2129
2130 TransformationType::PowerTransformer => {
2131 let method = if base_features.has_missing || base_features.outlier_ratio > 0.2 {
2133 "yeo-johnson" } else {
2135 "box-cox" };
2137 params.insert(
2138 "method".to_string(),
2139 if method == "yeo-johnson" { 1.0 } else { 0.0 },
2140 );
2141 params.insert("standardize".to_string(), 1.0);
2142 }
2143
2144 TransformationType::QuantileTransformer => {
2145 let n_quantiles = (base_features.n_samples / 10).max(10).min(1000);
2147 params.insert("n_quantiles".to_string(), n_quantiles as f64);
2148 params.insert("output_distribution".to_string(), 0.0); }
2150
2151 _ => {
2152 }
2154 }
2155
2156 Ok(params)
2157 }
2158
2159 fn estimate_efficiency_score(
2161 &self,
2162 t_type: &TransformationType,
2163 features: &EnhancedMetaFeatures,
2164 ) -> Result<f64> {
2165 let base_features = &features.base_features;
2166 let data_size_factor = (base_features.n_samples * base_features.n_features) as f64;
2167 let log_size = data_size_factor.ln();
2168
2169 let score = match t_type {
2170 TransformationType::StandardScaler | TransformationType::MinMaxScaler => {
2171 1.0 - (log_size / 25.0).min(0.3) }
2173 TransformationType::RobustScaler => {
2174 0.9 - (log_size / 20.0).min(0.3) }
2176 TransformationType::PCA => {
2177 let complexity_penalty = if base_features.n_features > base_features.n_samples {
2178 0.5 } else {
2180 0.3
2181 };
2182 0.7 - complexity_penalty - (log_size / 30.0).min(0.2)
2183 }
2184 TransformationType::PolynomialFeatures => {
2185 let feature_penalty = (base_features.n_features as f64 / 100.0).min(0.5);
2186 0.5 - feature_penalty - (log_size / 15.0).min(0.3)
2187 }
2188 TransformationType::PowerTransformer => 0.8 - (log_size / 25.0).min(0.2),
2189 _ => 0.7, };
2191
2192 Ok(score.max(0.1).min(1.0))
2193 }
2194
2195 fn estimate_interpretability_score(&self, t_type: &TransformationType) -> f64 {
2197 match t_type {
2198 TransformationType::StandardScaler | TransformationType::MinMaxScaler => 0.9,
2199 TransformationType::RobustScaler => 0.85,
2200 TransformationType::VarianceThreshold => 0.95,
2201 TransformationType::QuantileTransformer => 0.6,
2202 TransformationType::PowerTransformer => 0.7,
2203 TransformationType::PCA => 0.4, TransformationType::PolynomialFeatures => 0.3, TransformationType::BinaryEncoder | TransformationType::TargetEncoder => 0.5,
2206 }
2207 }
2208
2209 fn estimate_robustness_score(
2211 &self,
2212 t_type: &TransformationType,
2213 features: &EnhancedMetaFeatures,
2214 ) -> f64 {
2215 let base_features = &features.base_features;
2216
2217 let base_score = match t_type {
2218 TransformationType::RobustScaler => 0.95,
2219 TransformationType::QuantileTransformer => 0.9,
2220 TransformationType::StandardScaler => 0.7,
2221 TransformationType::MinMaxScaler => 0.6,
2222 TransformationType::PowerTransformer => 0.8,
2223 TransformationType::PCA => 0.7,
2224 TransformationType::PolynomialFeatures => 0.7,
2225 TransformationType::VarianceThreshold => 0.75,
2226 TransformationType::BinaryEncoder => 0.65,
2227 TransformationType::TargetEncoder => 0.6,
2228 };
2229
2230 let outlier_penalty = if base_features.outlier_ratio > 0.1 {
2232 match t_type {
2233 TransformationType::RobustScaler | TransformationType::QuantileTransformer => 0.0,
2234 _ => 0.2,
2235 }
2236 } else {
2237 0.0
2238 };
2239
2240 let missing_penalty = if base_features.has_missing { 0.1 } else { 0.0 };
2241
2242 let score: f64 = base_score - outlier_penalty - missing_penalty;
2243 score.max(0.1f64).min(1.0f64)
2244 }
2245}
2246
2247#[cfg(not(feature = "auto-feature-engineering"))]
2250pub struct AdvancedMetaLearningSystem;
2251
2252#[cfg(not(feature = "auto-feature-engineering"))]
2254pub struct EnhancedMetaFeatures;
2255
2256#[cfg(not(feature = "auto-feature-engineering"))]
2258pub struct MultiObjectiveRecommendation;