use scirs2_core::ndarray::{Array1, Array2};
use scirs2_core::numeric::Float;
use std::fmt::Debug;
use std::collections::HashMap;
use crate::error::Result;
use super::evaluation::EvaluationMetrics;
use super::config::ArchitectureSpec;
pub struct PerformancePredictor<T: Float + Debug + Send + Sync + 'static> {
model: PredictionModel<T>,
training_data: Vec<(String, EvaluationMetrics)>,
config: PredictorConfig,
feature_extractor: FeatureExtractor,
predictor_metrics: PredictorMetrics<T>,
}
impl<T: Float + Debug + Send + Sync + 'static> PerformancePredictor<T> {
pub fn new(config: &super::config::NASConfig) -> Result<Self> {
let predictor_config = PredictorConfig::from_nas_config(config);
let model = PredictionModel::new(&predictor_config)?;
let feature_extractor = FeatureExtractor::new();
let predictor_metrics = PredictorMetrics::new();
Ok(Self {
model,
training_data: Vec::new(),
config: predictor_config,
feature_extractor,
predictor_metrics,
})
}
pub fn predict(&mut self, architecture: &str) -> Result<Option<EvaluationMetrics>> {
if !self.model.is_trained() {
return Ok(None);
}
let features_f64 = self.feature_extractor.extract_features(architecture)?;
let features: Array1<T> = features_f64.mapv(|x| scirs2_core::numeric::NumCast::from(x).unwrap_or_else(|| T::zero()));
let prediction = self.model.predict(&features)?;
let metrics = self.prediction_to_metrics(prediction)?;
self.predictor_metrics.record_prediction();
Ok(Some(metrics))
}
pub fn train_on_sample(&mut self, architecture: &str, metrics: &EvaluationMetrics) -> Result<()> {
self.training_data.push((architecture.to_string(), metrics.clone()));
if self.training_data.len() % self.config.retrain_interval == 0 {
self.retrain_model()?;
}
Ok(())
}
fn retrain_model(&mut self) -> Result<()> {
if self.training_data.len() < self.config.min_training_samples {
return Ok(());
}
let mut features = Vec::new();
let mut targets = Vec::new();
for (architecture, metrics) in &self.training_data {
let feature_vec_f64 = self.feature_extractor.extract_features(architecture)?;
let feature_vec: Array1<T> = feature_vec_f64.mapv(|x| scirs2_core::numeric::NumCast::from(x).unwrap_or_else(|| T::zero()));
let target_vec = self.metrics_to_target(metrics)?;
features.push(feature_vec);
targets.push(target_vec);
}
self.model.train(features, targets)?;
self.predictor_metrics.record_training();
Ok(())
}
fn prediction_to_metrics(&self, prediction: Array1<T>) -> Result<EvaluationMetrics> {
if prediction.len() < 8 {
return Err(crate::error::OptimError::Other(
"Invalid prediction vector".to_string()
));
}
Ok(EvaluationMetrics {
accuracy: prediction[0].to_f64().unwrap_or(0.0),
training_time_seconds: prediction[1].to_f64().unwrap_or(0.0),
inference_time_ms: prediction[2].to_f64().unwrap_or(0.0),
memory_usage_mb: prediction[3].to_usize().unwrap_or(0),
flops: prediction[4].to_u64().unwrap_or(0),
parameters: prediction[5].to_usize().unwrap_or(0),
energy_consumption: prediction[6].to_f64().unwrap_or(0.0),
convergence_rate: prediction[7].to_f64().unwrap_or(0.0),
robustness_score: 0.7, generalization_score: prediction[0].to_f64().unwrap_or(0.0) * 0.9,
efficiency_score: 0.6,
valid: true,
})
}
fn metrics_to_target(&self, metrics: &EvaluationMetrics) -> Result<Array1<T>> {
let target = Array1::from(vec![
scirs2_core::numeric::NumCast::from(metrics.accuracy).unwrap_or_else(|| T::zero()),
scirs2_core::numeric::NumCast::from(metrics.training_time_seconds).unwrap_or_else(|| T::zero()),
scirs2_core::numeric::NumCast::from(metrics.inference_time_ms).unwrap_or_else(|| T::zero()),
scirs2_core::numeric::NumCast::from(metrics.memory_usage_mb as f64).unwrap_or_else(|| T::zero()),
scirs2_core::numeric::NumCast::from(metrics.flops as f64).unwrap_or_else(|| T::zero()),
scirs2_core::numeric::NumCast::from(metrics.parameters as f64).unwrap_or_else(|| T::zero()),
scirs2_core::numeric::NumCast::from(metrics.energy_consumption).unwrap_or_else(|| T::zero()),
scirs2_core::numeric::NumCast::from(metrics.convergence_rate).unwrap_or_else(|| T::zero()),
]);
Ok(target)
}
pub fn get_accuracy(&self) -> f64 {
self.predictor_metrics.get_accuracy()
}
pub fn get_statistics(&self) -> &PredictorMetrics<T> {
&self.predictor_metrics
}
}
pub struct PredictionModel<T: Float + Debug + Send + Sync + 'static> {
model_type: ModelType,
parameters: Option<ModelParameters<T>>,
config: PredictorConfig,
}
impl<T: Float + Debug + Send + Sync + 'static> PredictionModel<T> {
pub fn new(config: &PredictorConfig) -> Result<Self> {
Ok(Self {
model_type: config.model_type,
parameters: None,
config: config.clone(),
})
}
pub fn is_trained(&self) -> bool {
self.parameters.is_some()
}
pub fn train(&mut self, features: Vec<Array1<T>>, targets: Vec<Array1<T>>) -> Result<()> {
match self.model_type {
ModelType::LinearRegression => self.train_linear_regression(features, targets),
ModelType::RandomForest => self.train_random_forest(features, targets),
ModelType::NeuralNetwork => self.train_neural_network(features, targets),
ModelType::GaussianProcess => self.train_gaussian_process(features, targets),
}
}
pub fn predict(&self, features: &Array1<T>) -> Result<Array1<T>> {
match &self.parameters {
Some(params) => match self.model_type {
ModelType::LinearRegression => self.predict_linear_regression(features, params),
ModelType::RandomForest => self.predict_random_forest(features, params),
ModelType::NeuralNetwork => self.predict_neural_network(features, params),
ModelType::GaussianProcess => self.predict_gaussian_process(features, params),
},
None => Err(crate::error::OptimError::Other("Model not trained".to_string())),
}
}
fn train_linear_regression(&mut self, features: Vec<Array1<T>>, targets: Vec<Array1<T>>) -> Result<()> {
if features.is_empty() || targets.is_empty() {
return Err(crate::error::OptimError::Other("Empty training data".to_string()));
}
let input_dim = features[0].len();
let output_dim = targets[0].len();
let mut weights = Array2::<T>::zeros((input_dim, output_dim));
let mut bias = Array1::<T>::zeros(output_dim);
let learning_rate = scirs2_core::numeric::NumCast::from(0.01).unwrap_or_else(|| T::zero());
let epochs = 100;
for _ in 0..epochs {
for (feature, target) in features.iter().zip(targets.iter()) {
let prediction = weights.t().dot(feature) + &bias;
let error = target - &prediction;
for i in 0..input_dim {
for j in 0..output_dim {
weights[[i, j]] = weights[[i, j]] + learning_rate * feature[i] * error[j];
}
}
for j in 0..output_dim {
bias[j] = bias[j] + learning_rate * error[j];
}
}
}
self.parameters = Some(ModelParameters::LinearRegression { weights, bias });
Ok(())
}
fn predict_linear_regression(&self, features: &Array1<T>, params: &ModelParameters<T>) -> Result<Array1<T>> {
match params {
ModelParameters::LinearRegression { weights, bias } => {
Ok(weights.t().dot(features) + bias)
}
_ => Err(crate::error::OptimError::Other("Invalid parameters for linear regression".to_string())),
}
}
fn train_random_forest(&mut self, _features: Vec<Array1<T>>, _targets: Vec<Array1<T>>) -> Result<()> {
self.parameters = Some(ModelParameters::RandomForest);
Ok(())
}
fn predict_random_forest(&self, features: &Array1<T>, _params: &ModelParameters<T>) -> Result<Array1<T>> {
Ok(Array1::from(vec![scirs2_core::numeric::NumCast::from(0.5).unwrap_or_else(|| T::zero()); 8]))
}
fn train_neural_network(&mut self, _features: Vec<Array1<T>>, _targets: Vec<Array1<T>>) -> Result<()> {
self.parameters = Some(ModelParameters::NeuralNetwork);
Ok(())
}
fn predict_neural_network(&self, features: &Array1<T>, _params: &ModelParameters<T>) -> Result<Array1<T>> {
Ok(Array1::from(vec![scirs2_core::numeric::NumCast::from(0.6).unwrap_or_else(|| T::zero()); 8]))
}
fn train_gaussian_process(&mut self, _features: Vec<Array1<T>>, _targets: Vec<Array1<T>>) -> Result<()> {
self.parameters = Some(ModelParameters::GaussianProcess);
Ok(())
}
fn predict_gaussian_process(&self, features: &Array1<T>, _params: &ModelParameters<T>) -> Result<Array1<T>> {
Ok(Array1::from(vec![scirs2_core::numeric::NumCast::from(0.7).unwrap_or_else(|| T::zero()); 8]))
}
}
#[derive(Debug, Clone, Copy)]
pub enum ModelType {
LinearRegression,
RandomForest,
NeuralNetwork,
GaussianProcess,
}
pub enum ModelParameters<T: Float + Debug + Send + Sync + 'static> {
LinearRegression {
weights: Array2<T>,
bias: Array1<T>,
},
RandomForest,
NeuralNetwork,
GaussianProcess,
}
pub struct FeatureExtractor {
feature_dim: usize,
}
impl FeatureExtractor {
pub fn new() -> Self {
Self {
feature_dim: 20, }
}
pub fn extract_features(&self, architecture: &str) -> Result<Array1<f64>> {
let mut features = vec![0.0; self.feature_dim];
features[0] = architecture.len() as f64; features[1] = architecture.matches("Dense").count() as f64;
features[2] = architecture.matches("Conv").count() as f64;
features[3] = architecture.matches("LSTM").count() as f64;
features[4] = architecture.matches("Attention").count() as f64;
if let Ok(spec) = serde_json::from_str::<ArchitectureSpec>(architecture) {
features[5] = spec.layers.len() as f64;
features[6] = (spec.estimated_params as f64).log10();
features[7] = (spec.estimated_flops as f64).log10();
features[8] = spec.estimated_memory_mb as f64;
}
for i in 9..self.feature_dim {
features[i] = (features[i % 9] * (i as f64 + 1.0)).sin();
}
Ok(Array1::from(features))
}
}
#[derive(Debug, Clone)]
pub struct PredictorConfig {
pub model_type: ModelType,
pub min_training_samples: usize,
pub retrain_interval: usize,
pub validation_split: f64,
pub enable_cross_validation: bool,
}
impl PredictorConfig {
pub fn from_nas_config(config: &super::config::NASConfig) -> Self {
Self {
model_type: if config.enable_performance_prediction {
ModelType::LinearRegression
} else {
ModelType::LinearRegression
},
min_training_samples: 20,
retrain_interval: 10,
validation_split: 0.2,
enable_cross_validation: false,
}
}
}
#[derive(Debug)]
pub struct PredictorMetrics<T: Float + Debug + Send + Sync + 'static> {
total_predictions: usize,
total_trainings: usize,
prediction_errors: Vec<T>,
training_times: Vec<std::time::Duration>,
}
impl<T: Float + Debug + Send + Sync + 'static> PredictorMetrics<T> {
pub fn new() -> Self {
Self {
total_predictions: 0,
total_trainings: 0,
prediction_errors: Vec::new(),
training_times: Vec::new(),
}
}
pub fn record_prediction(&mut self) {
self.total_predictions += 1;
}
pub fn record_training(&mut self) {
self.total_trainings += 1;
}
pub fn record_prediction_error(&mut self, error: T) {
self.prediction_errors.push(error);
if self.prediction_errors.len() > 1000 {
self.prediction_errors.remove(0);
}
}
pub fn get_accuracy(&self) -> f64 {
if self.prediction_errors.is_empty() {
return 0.0;
}
let avg_error: T = self.prediction_errors.iter().cloned().fold(T::zero(), |acc, x| acc + x)
/ T::from(self.prediction_errors.len()).expect("unwrap failed");
(T::one() - avg_error).to_f64().unwrap_or(0.0).max(0.0).min(1.0)
}
}
pub struct PerformanceMetrics {
pub accuracy: f64,
pub latency: f64,
pub memory: f64,
pub energy: f64,
}
#[cfg(test)]
mod tests {
use super::*;
use crate::learned_optimizers::neural_architecture_search::config::NASConfig;
#[test]
fn test_performance_predictor_creation() {
let config = NASConfig::new();
let predictor = PerformancePredictor::<f32>::new(&config);
assert!(predictor.is_ok());
}
#[test]
fn test_feature_extractor() {
let extractor = FeatureExtractor::new();
let features = extractor.extract_features("test_architecture");
assert!(features.is_ok());
let feature_vec = features.expect("unwrap failed");
assert_eq!(feature_vec.len(), 20);
}
#[test]
fn test_prediction_model() {
let config = PredictorConfig {
model_type: ModelType::LinearRegression,
min_training_samples: 5,
retrain_interval: 2,
validation_split: 0.2,
enable_cross_validation: false,
};
let mut model = PredictionModel::<f32>::new(&config).expect("unwrap failed");
assert!(!model.is_trained());
}
}