optirs-nas 0.3.1

OptiRS Neural Architecture Search and hyperparameter optimization
Documentation
// Reinforcement learning-based search using policy gradients

use scirs2_core::ndarray::{Array1, Array2};
use scirs2_core::numeric::Float;
use scirs2_core::random::Random;
use scirs2_core::RngExt;
use std::collections::{HashMap, VecDeque};
use std::fmt::Debug;

#[allow(unused_imports)]
use crate::error::Result;
use crate::nas_engine::{OptimizerArchitecture, SearchResult, SearchSpaceConfig};
use crate::EvaluationMetric;

use super::{SearchStrategy, SearchStrategyStatistics};

/// Reinforcement learning-based search using policy gradients
pub struct ReinforcementLearningSearch<T: Float + Debug + Send + Sync + 'static> {
    controller_network: ControllerNetwork<T>,
    experience_buffer: ExperienceBuffer<T>,
    policy_optimizer: PolicyOptimizer<T>,
    baseline_predictor: BaselinePredictor<T>,
    epsilon: f64,
    exploration_decay: f64,
    statistics: SearchStrategyStatistics<T>,
    entropy_bonus: f64,
}

/// Controller network for RL-based search
#[derive(Debug)]
pub struct ControllerNetwork<T: Float + Debug + Send + Sync + 'static> {
    lstm_weights: Vec<Array2<T>>,
    lstm_biases: Vec<Array1<T>>,
    output_weights: Array2<T>,
    output_bias: Array1<T>,
    hidden_states: Vec<Array1<T>>,
    cell_states: Vec<Array1<T>>,
    numlayers: usize,
    hidden_size: usize,
}

/// Experience buffer for RL training
#[derive(Debug)]
pub struct ExperienceBuffer<T: Float + Debug + Send + Sync + 'static> {
    states: VecDeque<Array1<T>>,
    actions: VecDeque<usize>,
    rewards: VecDeque<T>,
    next_states: VecDeque<Array1<T>>,
    dones: VecDeque<bool>,
    capacity: usize,
}

/// Policy optimizer for RL controller
#[derive(Debug)]
pub struct PolicyOptimizer<T: Float + Debug + Send + Sync + 'static> {
    _learningrate: T,
    momentum: T,
    velocity: HashMap<String, Array2<T>>,
    gradient_clip_norm: T,
}

/// Baseline predictor for variance reduction
#[derive(Debug)]
pub struct BaselinePredictor<T: Float + Debug + Send + Sync + 'static> {
    network_weights: Vec<Array2<T>>,
    network_biases: Vec<Array1<T>>,
    optimizer: BaselineOptimizer<T>,
}

/// Baseline optimizer
#[derive(Debug)]
pub struct BaselineOptimizer<T: Float + Debug + Send + Sync + 'static> {
    _learningrate: T,
    momentum: T,
    velocity: Vec<Array2<T>>,
}

impl<T: Float + Debug + Default + Clone + Send + Sync + std::fmt::Debug + 'static>
    ReinforcementLearningSearch<T>
{
    pub fn new(
        controller_hidden_size: usize,
        controller_num_layers: usize,
        learningrate: f64,
    ) -> Self {
        Self {
            controller_network: ControllerNetwork::new(
                controller_hidden_size,
                controller_num_layers,
            ),
            experience_buffer: ExperienceBuffer::new(10000),
            policy_optimizer: PolicyOptimizer::new(
                scirs2_core::numeric::NumCast::from(learningrate).unwrap_or_else(|| T::zero()),
            ),
            baseline_predictor: BaselinePredictor::new(),
            epsilon: 0.1,
            exploration_decay: 0.995,
            statistics: SearchStrategyStatistics::default(),
            entropy_bonus: 0.01,
        }
    }

    fn encode_search_space(&self, _searchspace: &SearchSpaceConfig) -> Result<Array1<T>> {
        // Simplified encoding - in practice this would be more sophisticated
        Ok(Array1::zeros(64))
    }

    fn decode_actions_to_architecture(
        &self,
        _actions: &Array1<T>,
        searchspace: &SearchSpaceConfig,
    ) -> Result<OptimizerArchitecture<T>> {
        // Simplified decoding - randomly select for now
        let component_config = &searchspace.components[0];
        let mut hyperparameters = HashMap::new();

        for param_name in component_config.hyperparameter_ranges.keys() {
            hyperparameters.insert(param_name.clone(), 0.01f64);
        }

        Ok(OptimizerArchitecture {
            components: vec![format!("{:?}", component_config.component_type)],
            parameters: hyperparameters
                .iter()
                .map(|(k, v)| {
                    (
                        k.clone(),
                        scirs2_core::numeric::NumCast::from(*v).unwrap_or_else(|| T::zero()),
                    )
                })
                .collect(),
            connections: Vec::new(),
            metadata: HashMap::new(),
            hyperparameters: hyperparameters
                .iter()
                .map(|(k, v)| {
                    (
                        k.clone(),
                        scirs2_core::numeric::NumCast::from(*v).unwrap_or_else(|| T::zero()),
                    )
                })
                .collect(),
            architecture_id: format!("arch_{}", Random::default().random::<u64>()),
        })
    }

    fn train_controller(&mut self) -> Result<()> {
        // Simplified controller training
        // In practice, this would implement policy gradient methods
        Ok(())
    }
}

impl<
        T: Float + Debug + Default + Clone + Send + Sync + std::fmt::Debug + 'static + std::iter::Sum,
    > SearchStrategy<T> for ReinforcementLearningSearch<T>
{
    fn initialize(&mut self, _searchspace: &SearchSpaceConfig) -> Result<()> {
        // Initialize controller network
        self.controller_network.reset_states();
        Ok(())
    }

    fn generate_architecture(
        &mut self,
        searchspace: &SearchSpaceConfig,
        _history: &VecDeque<SearchResult<T>>,
    ) -> Result<OptimizerArchitecture<T>> {
        // Use controller to generate architecture
        let state = self.encode_search_space(searchspace)?;
        let actions = self.controller_network.forward(&state)?;

        // Decode actions to architecture
        let architecture = self.decode_actions_to_architecture(&actions, searchspace)?;

        self.statistics.total_architectures_generated += 1;
        Ok(architecture)
    }

    fn update_with_results(&mut self, results: &[SearchResult<T>]) -> Result<()> {
        // Update experience buffer and train controller
        for result in results {
            let reward = result
                .evaluation_results
                .metric_scores
                .get(&EvaluationMetric::FinalPerformance)
                .cloned()
                .unwrap_or(T::zero());

            // Store experience and train if buffer is full enough
            self.experience_buffer.add_experience(
                Array1::zeros(64), // Simplified state
                0,                 // Simplified action
                reward,
                Array1::zeros(64), // Simplified next state
                true,              // Done flag
            );
        }

        if self.experience_buffer.size() > 1000 {
            self.train_controller()?;
        }

        // Update statistics
        if !results.is_empty() {
            let performances: Vec<T> = results
                .iter()
                .filter_map(|r| {
                    r.evaluation_results
                        .metric_scores
                        .get(&EvaluationMetric::FinalPerformance)
                })
                .cloned()
                .collect();

            if !performances.is_empty() {
                self.statistics.best_performance = performances
                    .iter()
                    .max_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal))
                    .cloned()
                    .unwrap_or(T::zero());

                let sum: T = performances.iter().cloned().sum();
                self.statistics.average_performance =
                    sum / T::from(performances.len()).expect("conversion from usize to T failed");
            }
        }

        // Decay exploration
        self.epsilon *= self.exploration_decay;

        Ok(())
    }

    fn name(&self) -> &str {
        "ReinforcementLearningSearch"
    }

    fn get_statistics(&self) -> SearchStrategyStatistics<T> {
        let mut stats = self.statistics.clone();
        stats.exploration_rate =
            scirs2_core::numeric::NumCast::from(self.epsilon).unwrap_or_else(|| T::zero());
        stats.exploitation_rate =
            scirs2_core::numeric::NumCast::from(1.0 - self.epsilon).unwrap_or_else(|| T::zero());
        stats
    }
}

// Implementation stubs for complex components
impl<T: Float + Debug + Default + Clone + 'static + Send + Sync> ControllerNetwork<T> {
    fn new(hidden_size: usize, numlayers: usize) -> Self {
        let mut lstm_weights = Vec::new();
        let mut lstm_biases = Vec::new();
        let mut hidden_states = Vec::new();
        let mut cell_states = Vec::new();

        for _ in 0..numlayers {
            lstm_weights.push(Array2::zeros((hidden_size * 4, hidden_size)));
            lstm_biases.push(Array1::zeros(hidden_size * 4));
            hidden_states.push(Array1::zeros(hidden_size));
            cell_states.push(Array1::zeros(hidden_size));
        }

        Self {
            lstm_weights,
            lstm_biases,
            output_weights: Array2::zeros((hidden_size, hidden_size)),
            output_bias: Array1::zeros(hidden_size),
            hidden_states,
            cell_states,
            numlayers,
            hidden_size,
        }
    }

    fn reset_states(&mut self) {
        for i in 0..self.numlayers {
            self.hidden_states[i].fill(T::zero());
            self.cell_states[i].fill(T::zero());
        }
    }

    fn forward(&mut self, input: &Array1<T>) -> Result<Array1<T>> {
        let mut current_input = input.clone();

        // Simplified LSTM forward pass
        for layer in 0..self.numlayers {
            let output = self.lstm_weights[layer].dot(&current_input) + &self.lstm_biases[layer];
            // In practice, this would implement proper LSTM cell computation
            self.hidden_states[layer] = output.mapv(|x| x.tanh());
            current_input = self.hidden_states[layer].clone();
        }

        // Output projection
        let output = self.output_weights.dot(&current_input) + self.output_bias.clone();
        Ok(output)
    }
}

impl<T: Float + Debug + Default + Send + Sync> ExperienceBuffer<T> {
    fn new(capacity: usize) -> Self {
        Self {
            states: VecDeque::new(),
            actions: VecDeque::new(),
            rewards: VecDeque::new(),
            next_states: VecDeque::new(),
            dones: VecDeque::new(),
            capacity,
        }
    }

    fn add_experience(
        &mut self,
        state: Array1<T>,
        action: usize,
        reward: T,
        next_state: Array1<T>,
        done: bool,
    ) {
        self.states.push_back(state);
        self.actions.push_back(action);
        self.rewards.push_back(reward);
        self.next_states.push_back(next_state);
        self.dones.push_back(done);

        // Remove oldest if at capacity
        if self.states.len() > self.capacity {
            self.states.pop_front();
            self.actions.pop_front();
            self.rewards.pop_front();
            self.next_states.pop_front();
            self.dones.pop_front();
        }
    }

    fn size(&self) -> usize {
        self.states.len()
    }
}

impl<T: Float + Debug + Default + Send + Sync> PolicyOptimizer<T> {
    fn new(learningrate: T) -> Self {
        Self {
            _learningrate: learningrate,
            momentum: scirs2_core::numeric::NumCast::from(0.9).unwrap_or_else(|| T::zero()),
            velocity: HashMap::new(),
            gradient_clip_norm: scirs2_core::numeric::NumCast::from(1.0)
                .unwrap_or_else(|| T::zero()),
        }
    }
}

impl<T: Float + Debug + Default + Send + Sync> BaselinePredictor<T> {
    fn new() -> Self {
        Self {
            network_weights: vec![Array2::zeros((64, 64)), Array2::zeros((1, 64))],
            network_biases: vec![Array1::zeros(64), Array1::zeros(1)],
            optimizer: BaselineOptimizer::new(
                scirs2_core::numeric::NumCast::from(0.001).unwrap_or_else(|| T::zero()),
            ),
        }
    }
}

impl<T: Float + Debug + Default + Send + Sync> BaselineOptimizer<T> {
    fn new(learningrate: T) -> Self {
        Self {
            _learningrate: learningrate,
            momentum: scirs2_core::numeric::NumCast::from(0.9).unwrap_or_else(|| T::zero()),
            velocity: Vec::new(),
        }
    }
}