use scirs2_core::ndarray::{s, Array1, Array2, Array3};
use scirs2_core::numeric::Float;
use scirs2_core::random::Rng;
use scirs2_core::random::{Random, Rng as SCRRng};
use scirs2_core::ScientificNumber;
use std::collections::{HashMap, VecDeque};
use std::fmt::Debug;
use crate::architecture::{ComponentPosition, ComponentType};
#[allow(unused_imports)]
use crate::error::Result;
use crate::nas_engine::config::{ComponentType as ConfigComponentType, ParameterRange};
use crate::nas_engine::{OptimizerArchitecture, SearchResult, SearchSpaceConfig};
use crate::EvaluationMetric;
pub trait SearchStrategy<T: Float + Debug + Send + Sync + 'static>: Send + Sync {
fn initialize(&mut self, searchspace: &SearchSpaceConfig) -> Result<()>;
fn generate_architecture(
&mut self,
searchspace: &SearchSpaceConfig,
history: &VecDeque<SearchResult<T>>,
) -> Result<OptimizerArchitecture<T>>;
fn update_with_results(&mut self, results: &[SearchResult<T>]) -> Result<()>;
fn name(&self) -> &str;
fn get_statistics(&self) -> SearchStrategyStatistics<T>;
}
#[derive(Debug, Clone)]
pub struct SearchStrategyStatistics<T: Float + Debug + Send + Sync + 'static> {
pub total_architectures_generated: usize,
pub best_performance: T,
pub average_performance: T,
pub convergence_rate: T,
pub exploration_rate: T,
pub exploitation_rate: T,
}
pub struct RandomSearch<T: Float + Debug + Send + Sync + 'static + std::iter::Sum> {
rng: Random<scirs2_core::random::rngs::StdRng>,
statistics: SearchStrategyStatistics<T>,
searchspace: Option<SearchSpaceConfig>,
}
pub struct EvolutionarySearch<T: Float + Debug + Send + Sync + 'static> {
population: Vec<OptimizerArchitecture<T>>,
population_size: usize,
mutation_rate: f64,
crossover_rate: f64,
tournament_size: usize,
generation_count: usize,
statistics: SearchStrategyStatistics<T>,
elite_preservation: bool,
elitism_ratio: f64,
adaptive_rates: bool,
}
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,
}
pub struct DifferentiableSearch<T: Float + Debug + Send + Sync + 'static> {
architecture_weights: Array3<T>,
weight_optimizer: WeightOptimizer<T>,
temperature: T,
gumbel_softmax: bool,
continuous_relaxation: bool,
statistics: SearchStrategyStatistics<T>,
discretization_strategy: DiscretizationStrategy,
}
pub struct BayesianOptimization<T: Float + Debug + Send + Sync + 'static> {
gaussian_process: GaussianProcess<T>,
acquisition_function: AcquisitionFunction<T>,
observed_architectures: Vec<OptimizerArchitecture<T>>,
observed_performances: Vec<T>,
kernel: GPKernel<T>,
statistics: SearchStrategyStatistics<T>,
exploration_factor: T,
}
pub struct NeuralPredictorSearch<T: Float + Debug + Send + Sync + 'static> {
predictor_network: PredictorNetwork<T>,
architecture_encoder: ArchitectureEncoder<T>,
search_optimizer: SearchOptimizer<T>,
confidence_threshold: T,
statistics: SearchStrategyStatistics<T>,
uncertainty_sampling: bool,
}
#[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,
}
#[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,
}
#[derive(Debug)]
pub struct PolicyOptimizer<T: Float + Debug + Send + Sync + 'static> {
_learningrate: T,
momentum: T,
velocity: HashMap<String, Array2<T>>,
gradient_clip_norm: T,
}
#[derive(Debug)]
pub struct BaselinePredictor<T: Float + Debug + Send + Sync + 'static> {
network_weights: Vec<Array2<T>>,
network_biases: Vec<Array1<T>>,
optimizer: BaselineOptimizer<T>,
}
#[derive(Debug)]
pub struct WeightOptimizer<T: Float + Debug + Send + Sync + 'static> {
_learningrate: T,
momentum: T,
weight_decay: T,
velocity: Array3<T>,
}
#[derive(Debug, Clone, Copy)]
pub enum DiscretizationStrategy {
Greedy,
Sampling,
Progressive,
Threshold,
}
#[derive(Debug)]
pub struct GaussianProcess<T: Float + Debug + Send + Sync + 'static> {
kernel_matrix: Array2<T>,
inverse_kernel: Array2<T>,
noise_variance: T,
length_scales: Array1<T>,
signal_variance: T,
}
#[derive(Debug)]
pub struct AcquisitionFunction<T: Float + Debug + Send + Sync + 'static> {
function_type: AcquisitionType,
explorationweight: T,
current_best: T,
}
#[derive(Debug, Clone, Copy)]
pub enum AcquisitionType {
EI,
UCB,
PI,
Thompson,
InfoGain,
}
#[derive(Debug)]
pub struct GPKernel<T: Float + Debug + Send + Sync + 'static> {
_kerneltype: KernelType,
hyperparameters: Array1<T>,
}
#[derive(Debug, Clone, Copy)]
pub enum KernelType {
RBF,
Matern32,
Matern52,
Linear,
Polynomial,
}
#[derive(Debug)]
pub struct PredictorNetwork<T: Float + Debug + Send + Sync + 'static> {
layers: Vec<PredictorLayer<T>>,
dropout_rates: Vec<T>,
architecture: Vec<usize>,
}
#[derive(Debug)]
pub struct PredictorLayer<T: Float + Debug + Send + Sync + 'static> {
weights: Array2<T>,
bias: Array1<T>,
activation: ActivationFunction,
}
#[derive(Debug, Clone, Copy)]
pub enum ActivationFunction {
ReLU,
GELU,
Swish,
Tanh,
Sigmoid,
}
#[derive(Debug)]
pub struct ArchitectureEncoder<T: Float + Debug + Send + Sync + 'static> {
encoding_weights: Array2<T>,
_embeddingdim: usize,
max_components: usize,
}
#[derive(Debug)]
pub struct SearchOptimizer<T: Float + Debug + Send + Sync + 'static> {
optimizer_type: SearchOptimizerType,
_learningrate: T,
momentum: T,
parameters: HashMap<String, Array1<T>>,
}
#[derive(Debug, Clone, Copy)]
pub enum SearchOptimizerType {
Adam,
SGD,
RMSprop,
AdamW,
}
#[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 + 'static + std::fmt::Debug + std::iter::Sum,
> RandomSearch<T>
{
pub fn new(seed: Option<u64>) -> Self {
let rng = if let Some(seed) = seed {
Random::seed(seed)
} else {
Random::seed(42)
};
Self {
rng,
statistics: SearchStrategyStatistics::default(),
searchspace: None,
}
}
}
impl<
T: Float + Debug + Default + Clone + Send + Sync + 'static + std::fmt::Debug + std::iter::Sum,
> SearchStrategy<T> for RandomSearch<T>
{
fn initialize(&mut self, searchspace: &SearchSpaceConfig) -> Result<()> {
self.searchspace = Some(searchspace.clone());
Ok(())
}
fn generate_architecture(
&mut self,
searchspace: &SearchSpaceConfig,
_history: &VecDeque<SearchResult<T>>,
) -> Result<OptimizerArchitecture<T>> {
use crate::architecture::OptimizerComponent;
let num_components = self.rng.gen_range(1..5);
let mut components = Vec::new();
for idx in 0..num_components {
let component_config =
&searchspace.components[self.rng.gen_range(0..searchspace.components.len())];
let mut hyperparameters = HashMap::new();
for (param_name, param_range) in &component_config.hyperparameter_ranges {
let value = match param_range {
ParameterRange::Continuous(min, max) => self.rng.gen_range(*min..*max),
ParameterRange::LogUniform(min, max) => {
let log_min = min.ln();
let log_max = max.ln();
let log_val = self.rng.gen_range(log_min..log_max);
log_val.exp()
}
ParameterRange::Integer(min, max) => self.rng.gen_range(*min..*max) as f64,
ParameterRange::Boolean => {
if self.rng.random::<f64>() < 0.5 {
1.0
} else {
0.0
}
}
ParameterRange::Discrete(values) => {
let value_idx = self.rng.gen_range(0..values.len());
values[value_idx]
}
ParameterRange::Categorical(_values) => {
0.0
}
};
hyperparameters.insert(param_name.clone(), value);
}
let component_type = match component_config.component_type {
ConfigComponentType::SGD => ComponentType::SGD,
ConfigComponentType::Adam => ComponentType::Adam,
ConfigComponentType::AdamW => ComponentType::AdamW,
ConfigComponentType::RMSprop => ComponentType::RMSprop,
ConfigComponentType::AdaGrad => ComponentType::AdaGrad,
ConfigComponentType::AdaDelta => ComponentType::AdaDelta,
ConfigComponentType::LBFGS => ComponentType::LBFGS,
ConfigComponentType::Momentum => ComponentType::Momentum,
ConfigComponentType::Nesterov => ComponentType::Nesterov,
ConfigComponentType::Custom(_) => ComponentType::Custom,
_ => ComponentType::Adam, };
components.push(OptimizerComponent {
id: format!("comp_{}", idx),
component_type,
hyperparameters,
enabled: true,
position: ComponentPosition {
layer: 0,
index: idx as u32,
x: 0.0,
y: 0.0,
},
});
}
self.statistics.total_architectures_generated += 1;
Ok(OptimizerArchitecture {
components: components
.into_iter()
.map(|c| format!("{:?}", c.component_type))
.collect(),
parameters: HashMap::new(),
connections: Vec::new(),
metadata: HashMap::new(),
hyperparameters: HashMap::new(),
architecture_id: format!("arch_{}", self.rng.random::<u32>()),
})
}
fn update_with_results(&mut self, results: &[SearchResult<T>]) -> Result<()> {
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("unwrap failed");
}
}
Ok(())
}
fn name(&self) -> &str {
"RandomSearch"
}
fn get_statistics(&self) -> SearchStrategyStatistics<T> {
self.statistics.clone()
}
}
impl<T: Float + Debug + Default + Clone + Send + Sync + std::fmt::Debug + std::iter::Sum>
EvolutionarySearch<T>
{
pub fn new(
population_size: usize,
mutation_rate: f64,
crossover_rate: f64,
tournament_size: usize,
) -> Self {
Self {
population: Vec::new(),
population_size,
mutation_rate,
crossover_rate,
tournament_size,
generation_count: 0,
statistics: SearchStrategyStatistics::default(),
elite_preservation: true,
elitism_ratio: 0.1,
adaptive_rates: true,
}
}
fn initialize_population(&mut self, searchspace: &SearchSpaceConfig) -> Result<()> {
self.population.clear();
let mut random_search = RandomSearch::<T>::new(Some(42));
random_search.initialize(searchspace)?;
for _ in 0..self.population_size {
let architecture =
random_search.generate_architecture(searchspace, &VecDeque::new())?;
self.population.push(architecture);
}
Ok(())
}
fn selection(&self, fitnessscores: &[T]) -> Result<usize> {
let mut best_idx = 0;
let mut best_fitness = T::neg_infinity();
let mut rng = scirs2_core::random::thread_rng();
for _ in 0..self.tournament_size {
let idx = rng.gen_range(0..self.population.len());
if fitnessscores[idx] > best_fitness {
best_fitness = fitnessscores[idx];
best_idx = idx;
}
}
Ok(best_idx)
}
fn crossover(
&self,
parent1: &OptimizerArchitecture<T>,
parent2: &OptimizerArchitecture<T>,
) -> Result<OptimizerArchitecture<T>> {
let mut child_components = Vec::new();
let max_len = parent1.components.len().max(parent2.components.len());
for i in 0..max_len {
let component = if i < parent1.components.len() && i < parent2.components.len() {
if scirs2_core::random::Random::default().random::<f64>() < 0.5 {
parent1.components[i].clone()
} else {
parent2.components[i].clone()
}
} else if i < parent1.components.len() {
parent1.components[i].clone()
} else {
parent2.components[i].clone()
};
child_components.push(component);
}
let mut child_parameters = HashMap::new();
for (key, value) in &parent1.parameters {
if scirs2_core::random::Random::default().random::<f64>() < 0.5 {
child_parameters.insert(key.clone(), *value);
} else if let Some(parent2_value) = parent2.parameters.get(key) {
child_parameters.insert(key.clone(), *parent2_value);
} else {
child_parameters.insert(key.clone(), *value);
}
}
Ok(OptimizerArchitecture {
components: child_components,
parameters: child_parameters,
connections: Vec::new(),
metadata: HashMap::new(),
hyperparameters: HashMap::new(),
architecture_id: format!("arch_{}", Random::default().random::<u64>()),
})
}
fn mutate(
&self,
architecture: &mut OptimizerArchitecture<T>,
searchspace: &SearchSpaceConfig,
) -> Result<()> {
for (param_name, current_value) in architecture.parameters.iter_mut() {
if scirs2_core::random::Random::default().random::<f64>() < self.mutation_rate {
let param_range = searchspace
.components
.iter()
.flat_map(|c| c.hyperparameter_ranges.iter())
.find(|(name, _)| name == ¶m_name)
.map(|(_, range)| range);
if let Some(param_range) = param_range {
match param_range {
ParameterRange::Continuous(min, max) => {
let noise =
scirs2_core::random::Random::default().random::<f64>() * 0.1 - 0.05; let current_f64 = current_value.to_f64().unwrap_or(0.0);
let new_val = current_f64 + noise;
let clamped = new_val.max(*min).min(*max);
*current_value = scirs2_core::numeric::NumCast::from(clamped)
.unwrap_or_else(|| T::zero());
}
ParameterRange::LogUniform(min, max) => {
let current_f64 = current_value.to_f64().unwrap_or(0.001);
let log_val = current_f64.ln();
let noise =
scirs2_core::random::Random::default().random::<f64>() * 0.2 - 0.1;
let new_log = log_val + noise;
let new_val = new_log.exp().max(*min).min(*max);
*current_value = scirs2_core::numeric::NumCast::from(new_val)
.unwrap_or_else(|| T::zero());
}
_ => {
}
}
}
}
}
Ok(())
}
}
impl<T: Float + Debug + Default + Clone + Send + Sync + std::fmt::Debug + std::iter::Sum>
SearchStrategy<T> for EvolutionarySearch<T>
{
fn initialize(&mut self, searchspace: &SearchSpaceConfig) -> Result<()> {
self.initialize_population(searchspace)?;
self.generation_count = 0;
Ok(())
}
fn generate_architecture(
&mut self,
searchspace: &SearchSpaceConfig,
history: &VecDeque<SearchResult<T>>,
) -> Result<OptimizerArchitecture<T>> {
if self.population.is_empty() {
self.initialize_population(searchspace)?;
}
let recent_results: Vec<_> = history.iter().rev().take(self.population_size).collect();
if recent_results.len() >= self.population_size {
let fitnessscores: Vec<T> = recent_results
.iter()
.filter_map(|r| {
r.evaluation_results
.metric_scores
.get(&EvaluationMetric::FinalPerformance)
})
.cloned()
.collect();
if fitnessscores.len() == self.population_size {
let parent1_idx = self.selection(&fitnessscores)?;
let parent2_idx = self.selection(&fitnessscores)?;
let mut child = if scirs2_core::random::Random::default().random::<f64>()
< self.crossover_rate
{
self.crossover(&self.population[parent1_idx], &self.population[parent2_idx])?
} else {
self.population[parent1_idx].clone()
};
self.mutate(&mut child, searchspace)?;
let worst_idx = fitnessscores
.iter()
.enumerate()
.min_by(|(_, a), (_, b)| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal))
.map(|(idx, _)| idx)
.unwrap_or(0);
self.population[worst_idx] = child.clone();
self.generation_count += 1;
self.statistics.total_architectures_generated += 1;
return Ok(child);
}
}
let idx = scirs2_core::random::thread_rng().gen_range(0..self.population.len());
self.statistics.total_architectures_generated += 1;
Ok(self.population[idx].clone())
}
fn update_with_results(&mut self, results: &[SearchResult<T>]) -> Result<()> {
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("unwrap failed");
if self.adaptive_rates && self.generation_count > 10 {
let recent_improvement = self.calculate_recent_improvement(&performances);
if recent_improvement
< scirs2_core::numeric::NumCast::from(0.01).unwrap_or_else(|| T::zero())
{
self.mutation_rate = (self.mutation_rate * 1.1).min(0.5);
} else {
self.mutation_rate = (self.mutation_rate * 0.95).max(0.01);
}
}
}
}
Ok(())
}
fn name(&self) -> &str {
"EvolutionarySearch"
}
fn get_statistics(&self) -> SearchStrategyStatistics<T> {
let mut stats = self.statistics.clone();
stats.exploration_rate =
scirs2_core::numeric::NumCast::from(self.mutation_rate).unwrap_or_else(|| T::zero());
stats.exploitation_rate = scirs2_core::numeric::NumCast::from(1.0 - self.mutation_rate)
.unwrap_or_else(|| T::zero());
stats
}
}
impl<T: Float + Debug + Send + Sync + std::iter::Sum> EvolutionarySearch<T> {
fn calculate_recent_improvement(&self, performances: &[T]) -> T {
if performances.len() < 10 {
return T::zero();
}
let recent_avg = performances.iter().rev().take(5).cloned().sum::<T>()
/ scirs2_core::numeric::NumCast::from(5.0).unwrap_or_else(|| T::zero());
let earlier_avg = performances
.iter()
.rev()
.skip(5)
.take(5)
.cloned()
.sum::<T>()
/ scirs2_core::numeric::NumCast::from(5.0).unwrap_or_else(|| T::zero());
recent_avg - earlier_avg
}
}
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,
}
}
}
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<()> {
self.controller_network.reset_states();
Ok(())
}
fn generate_architecture(
&mut self,
searchspace: &SearchSpaceConfig,
_history: &VecDeque<SearchResult<T>>,
) -> Result<OptimizerArchitecture<T>> {
let state = self.encode_search_space(searchspace)?;
let actions = self.controller_network.forward(&state)?;
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<()> {
for result in results {
let reward = result
.evaluation_results
.metric_scores
.get(&EvaluationMetric::FinalPerformance)
.cloned()
.unwrap_or(T::zero());
self.experience_buffer.add_experience(
Array1::zeros(64), 0, reward,
Array1::zeros(64), true, );
}
if self.experience_buffer.size() > 1000 {
self.train_controller()?;
}
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("unwrap failed");
}
}
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
}
}
impl<T: Float + Debug + Send + Sync + 'static + Default + Clone> ReinforcementLearningSearch<T> {
fn encode_search_space(&self, _searchspace: &SearchSpaceConfig) -> Result<Array1<T>> {
Ok(Array1::zeros(64))
}
fn decode_actions_to_architecture(
&self,
actions: &Array1<T>,
searchspace: &SearchSpaceConfig,
) -> Result<OptimizerArchitecture<T>> {
use crate::architecture::OptimizerComponent;
let component_config = &searchspace.components[0];
let mut hyperparameters = HashMap::new();
for (param_name, param_range) in &component_config.hyperparameter_ranges {
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<()> {
Ok(())
}
}
impl<T: Float + Debug + Default + Send + Sync> Default for SearchStrategyStatistics<T> {
fn default() -> Self {
Self {
total_architectures_generated: 0,
best_performance: T::zero(),
average_performance: T::zero(),
convergence_rate: T::zero(),
exploration_rate: scirs2_core::numeric::NumCast::from(0.5).unwrap_or_else(|| T::zero()),
exploitation_rate: scirs2_core::numeric::NumCast::from(0.5)
.unwrap_or_else(|| T::zero()),
}
}
}
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();
for layer in 0..self.numlayers {
let output = self.lstm_weights[layer].dot(¤t_input) + &self.lstm_biases[layer];
self.hidden_states[layer] = output.mapv(|x| x.tanh());
current_input = self.hidden_states[layer].clone();
}
let output = self.output_weights.dot(¤t_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);
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,
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,
momentum: scirs2_core::numeric::NumCast::from(0.9).unwrap_or_else(|| T::zero()),
velocity: Vec::new(),
}
}
}
impl<
T: Float
+ Default
+ Clone
+ Send
+ Sync
+ std::fmt::Debug
+ scirs2_core::ndarray::ScalarOperand
+ std::iter::Sum,
> DifferentiableSearch<T>
{
pub fn new(
num_operations: usize,
num_edges: usize,
temperature: f64,
use_gumbel: bool,
) -> Self {
Self {
architecture_weights: Array3::zeros((num_edges, num_operations, 1)),
weight_optimizer: WeightOptimizer::new(
scirs2_core::numeric::NumCast::from(0.025).unwrap_or_else(|| T::zero()),
),
temperature: scirs2_core::numeric::NumCast::from(temperature)
.unwrap_or_else(|| T::zero()),
gumbel_softmax: use_gumbel,
continuous_relaxation: true,
statistics: SearchStrategyStatistics::default(),
discretization_strategy: DiscretizationStrategy::Progressive,
}
}
fn gumbel_softmax_sample(&self, logits: &Array1<T>) -> Array1<T> {
if !self.gumbel_softmax {
return self.softmax(logits);
}
let gumbel_noise: Array1<T> = Array1::from_shape_fn(logits.len(), |_| {
let u = scirs2_core::random::Random::default().random::<f64>();
T::from(-(-u.ln()).ln()).expect("unwrap failed")
});
let gumbel_logits = logits + &gumbel_noise;
let scaled_logits = gumbel_logits / self.temperature;
self.softmax(&scaled_logits)
}
fn softmax(&self, x: &Array1<T>) -> Array1<T> {
let max_val = x
.iter()
.cloned()
.fold(T::neg_infinity(), |a, b| if a > b { a } else { b });
let exp_x = x.mapv(|xi| (xi - max_val).exp());
let sum_exp = exp_x.sum();
exp_x / sum_exp
}
fn discretize_architecture(&self, weights: &Array3<T>) -> OptimizerArchitecture<T> {
use crate::architecture::OptimizerComponent;
let mut components = Vec::new();
for edge_idx in 0..weights.dim().0 {
let edge_weights = weights.slice(s![edge_idx, .., 0]);
let selected_op_idx = match self.discretization_strategy {
DiscretizationStrategy::Greedy => edge_weights
.iter()
.enumerate()
.max_by(|(_, a), (_, b)| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal))
.map(|(idx, _)| idx)
.unwrap_or(0),
DiscretizationStrategy::Sampling => {
let probs = self.softmax(&edge_weights.to_owned());
let rand_val = scirs2_core::random::Random::default().random::<f64>();
let mut cumsum = 0.0;
let mut selected_idx = 0;
for (idx, prob) in probs.iter().enumerate() {
cumsum += prob.to_f64().unwrap_or(0.0);
if cumsum >= rand_val {
selected_idx = idx;
break;
}
}
selected_idx
}
DiscretizationStrategy::Threshold => {
let threshold =
scirs2_core::numeric::NumCast::from(0.5).unwrap_or_else(|| T::zero());
edge_weights
.iter()
.enumerate()
.find(|(_, &weight)| weight > threshold)
.map(|(idx, _)| idx)
.unwrap_or(0)
}
DiscretizationStrategy::Progressive => {
let sharpened = edge_weights.mapv(|x| {
x.powf(
scirs2_core::numeric::NumCast::from(2.0).unwrap_or_else(|| T::zero()),
)
});
sharpened
.iter()
.enumerate()
.max_by(|(_, a), (_, b)| {
a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal)
})
.map(|(idx, _)| idx)
.unwrap_or(0)
}
};
let component_type = match selected_op_idx {
0 => ComponentType::SGD,
1 => ComponentType::Adam,
2 => ComponentType::AdaGrad,
3 => ComponentType::RMSprop,
_ => ComponentType::Adam,
};
let mut hyperparameters = HashMap::new();
hyperparameters.insert("_learningrate".to_string(), 0.001f64);
components.push(OptimizerComponent {
id: format!("comp_{}", edge_idx),
component_type,
hyperparameters,
enabled: true,
position: ComponentPosition {
layer: 0,
index: edge_idx as u32,
x: 0.0,
y: 0.0,
},
});
}
OptimizerArchitecture {
components: components
.iter()
.map(|c| c.component_type.to_string())
.collect(),
parameters: components
.iter()
.enumerate()
.flat_map(|(i, c)| {
c.hyperparameters.iter().map(move |(k, v)| {
(
format!("{}_{}", i, k),
scirs2_core::numeric::NumCast::from(*v).unwrap_or_else(|| T::zero()),
)
})
})
.collect(),
hyperparameters: components
.iter()
.enumerate()
.flat_map(|(i, c)| {
c.hyperparameters.iter().map(move |(k, v)| {
(
format!("{}_{}", i, k),
scirs2_core::numeric::NumCast::from(*v).unwrap_or_else(|| T::zero()),
)
})
})
.collect(),
connections: Vec::new(),
metadata: HashMap::new(),
architecture_id: format!("arch_{}", Random::default().random::<u64>()),
}
}
}
impl<
T: Float
+ Default
+ Clone
+ Send
+ Sync
+ std::fmt::Debug
+ std::iter::Sum
+ scirs2_core::ndarray::ScalarOperand,
> SearchStrategy<T> for DifferentiableSearch<T>
{
fn initialize(&mut self, _searchspace: &SearchSpaceConfig) -> Result<()> {
self.architecture_weights =
Array3::from_shape_fn(self.architecture_weights.raw_dim(), |_| {
T::from(scirs2_core::random::Random::default().random::<f64>() * 0.1 - 0.05)
.expect("unwrap failed")
});
Ok(())
}
fn generate_architecture(
&mut self,
_search_space: &SearchSpaceConfig,
_history: &VecDeque<SearchResult<T>>,
) -> Result<OptimizerArchitecture<T>> {
if self.continuous_relaxation {
let mut sampled_weights = Array3::zeros(self.architecture_weights.raw_dim());
for edge_idx in 0..self.architecture_weights.dim().0 {
let edge_weights = self.architecture_weights.slice(s![edge_idx, .., 0]);
let sampled = self.gumbel_softmax_sample(&edge_weights.to_owned());
for (op_idx, &weight) in sampled.iter().enumerate() {
sampled_weights[[edge_idx, op_idx, 0]] = weight;
}
}
self.statistics.total_architectures_generated += 1;
Ok(self.discretize_architecture(&sampled_weights))
} else {
self.statistics.total_architectures_generated += 1;
Ok(self.discretize_architecture(&self.architecture_weights))
}
}
fn update_with_results(&mut self, results: &[SearchResult<T>]) -> Result<()> {
if results.is_empty() {
return Ok(());
}
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("unwrap failed");
let baseline = self.statistics.average_performance;
let reward = performances[0] - baseline;
let _learningrate =
scirs2_core::numeric::NumCast::from(0.001).unwrap_or_else(|| T::zero());
self.architecture_weights = &self.architecture_weights
+ &(Array3::ones(self.architecture_weights.raw_dim()) * _learningrate * reward);
self.temperature = self.temperature
* scirs2_core::numeric::NumCast::from(0.999).unwrap_or_else(|| T::zero());
}
Ok(())
}
fn name(&self) -> &str {
"DifferentiableSearch"
}
fn get_statistics(&self) -> SearchStrategyStatistics<T> {
let mut stats = self.statistics.clone();
stats.exploration_rate = self.temperature;
stats.exploitation_rate = T::one() - self.temperature;
stats
}
}
impl<T: Float + Debug + Default + Clone + Send + Sync + std::fmt::Debug + std::iter::Sum>
BayesianOptimization<T>
{
pub fn new(
_kerneltype: KernelType,
acquisition_type: AcquisitionType,
exploration_factor: f64,
) -> Self {
Self {
gaussian_process: GaussianProcess::new(_kerneltype),
acquisition_function: AcquisitionFunction::new(
acquisition_type,
scirs2_core::numeric::NumCast::from(exploration_factor)
.unwrap_or_else(|| T::zero()),
),
observed_architectures: Vec::new(),
observed_performances: Vec::new(),
kernel: GPKernel::new(_kerneltype),
statistics: SearchStrategyStatistics::default(),
exploration_factor: scirs2_core::numeric::NumCast::from(exploration_factor)
.unwrap_or_else(|| T::zero()),
}
}
fn encode_architecture(&self, architecture: &OptimizerArchitecture<T>) -> Array1<T> {
let mut encoding = Vec::new();
for component_str in &architecture.components {
let hash = component_str
.bytes()
.fold(0u8, |acc, b| acc.wrapping_add(b));
encoding.push(scirs2_core::numeric::NumCast::from(hash).unwrap_or_else(|| T::zero()));
}
for &value in architecture.hyperparameters.values() {
encoding.push(value);
}
encoding.resize(64, T::zero());
Array1::from_vec(encoding)
}
fn fit_gp(&mut self) -> Result<()> {
if self.observed_architectures.len() < 2 {
return Ok(());
}
let encoded_archs: Vec<Array1<T>> = self
.observed_architectures
.iter()
.map(|arch| self.encode_architecture(arch))
.collect();
self.gaussian_process
.fit(&encoded_archs, &self.observed_performances)?;
Ok(())
}
fn suggest_next_architecture(
&mut self,
searchspace: &SearchSpaceConfig,
) -> Result<OptimizerArchitecture<T>> {
if self.observed_architectures.len() < 5 {
let mut random_search = RandomSearch::<T>::new(Some(42));
random_search.initialize(searchspace)?;
return random_search.generate_architecture(searchspace, &VecDeque::new());
}
let num_candidates = 100;
let mut candidates = Vec::new();
let mut random_search = RandomSearch::<T>::new(Some(42));
random_search.initialize(searchspace)?;
for _ in 0..num_candidates {
candidates.push(random_search.generate_architecture(searchspace, &VecDeque::new())?);
}
let mut best_architecture = candidates[0].clone();
let mut best_acquisition = T::neg_infinity();
for candidate in candidates {
let encoded = self.encode_architecture(&candidate);
let (mean, variance) = self.gaussian_process.predict(&encoded)?;
let acquisition_value = self.acquisition_function.evaluate(mean, variance);
if acquisition_value > best_acquisition {
best_acquisition = acquisition_value;
best_architecture = candidate;
}
}
Ok(best_architecture)
}
}
impl<T: Float + Debug + Default + Clone + Send + Sync + std::fmt::Debug + std::iter::Sum>
SearchStrategy<T> for BayesianOptimization<T>
{
fn initialize(&mut self, _searchspace: &SearchSpaceConfig) -> Result<()> {
self.observed_architectures.clear();
self.observed_performances.clear();
Ok(())
}
fn generate_architecture(
&mut self,
searchspace: &SearchSpaceConfig,
_history: &VecDeque<SearchResult<T>>,
) -> Result<OptimizerArchitecture<T>> {
let architecture = self.suggest_next_architecture(searchspace)?;
self.statistics.total_architectures_generated += 1;
Ok(architecture)
}
fn update_with_results(&mut self, results: &[SearchResult<T>]) -> Result<()> {
for result in results {
if let Some(&performance) = result
.evaluation_results
.metric_scores
.get(&EvaluationMetric::FinalPerformance)
{
self.observed_architectures
.push(result.architecture.clone());
self.observed_performances.push(performance);
if performance > self.statistics.best_performance {
self.statistics.best_performance = performance;
}
self.fit_gp()?;
}
}
if !self.observed_performances.is_empty() {
let sum: T = self.observed_performances.iter().cloned().sum();
self.statistics.average_performance =
sum / T::from(self.observed_performances.len()).expect("unwrap failed");
}
Ok(())
}
fn name(&self) -> &str {
"BayesianOptimization"
}
fn get_statistics(&self) -> SearchStrategyStatistics<T> {
let mut stats = self.statistics.clone();
stats.exploration_rate = self.exploration_factor;
stats.exploitation_rate = T::one() - self.exploration_factor;
stats
}
}
impl<T: Float + Debug + Default + Send + Sync> WeightOptimizer<T> {
fn new(_learningrate: T) -> Self {
Self {
_learningrate,
momentum: scirs2_core::numeric::NumCast::from(0.9).unwrap_or_else(|| T::zero()),
weight_decay: scirs2_core::numeric::NumCast::from(1e-4).unwrap_or_else(|| T::zero()),
velocity: Array3::zeros((0, 0, 0)),
}
}
}
impl<T: Float + Debug + Default + Send + Sync> GaussianProcess<T> {
fn new(_kerneltype: KernelType) -> Self {
Self {
kernel_matrix: Array2::zeros((0, 0)),
inverse_kernel: Array2::zeros((0, 0)),
noise_variance: scirs2_core::numeric::NumCast::from(1e-6).unwrap_or_else(|| T::zero()),
length_scales: Array1::ones(1),
signal_variance: T::one(),
}
}
fn fit(&mut self, x: &[Array1<T>], y: &[T]) -> Result<()> {
Ok(())
}
fn predict(&self, x: &Array1<T>) -> Result<(T, T)> {
Ok((
scirs2_core::numeric::NumCast::from(0.5).unwrap_or_else(|| T::zero()),
scirs2_core::numeric::NumCast::from(0.1).unwrap_or_else(|| T::zero()),
))
}
}
impl<T: Float + Debug + Default + Send + Sync> AcquisitionFunction<T> {
fn new(function_type: AcquisitionType, explorationweight: T) -> Self {
Self {
function_type,
explorationweight,
current_best: T::zero(),
}
}
fn evaluate(&self, mean: T, variance: T) -> T {
match self.function_type {
AcquisitionType::UCB => mean + self.explorationweight * variance.sqrt(),
AcquisitionType::EI => {
let std_dev = variance.sqrt();
if std_dev > scirs2_core::numeric::NumCast::from(1e-8).unwrap_or_else(|| T::zero())
{
let z = (mean - self.current_best) / std_dev;
z * std_dev
} else {
T::zero()
}
}
AcquisitionType::PI => {
if variance > scirs2_core::numeric::NumCast::from(1e-8).unwrap_or_else(|| T::zero())
{
let z = (mean - self.current_best) / variance.sqrt();
if z > T::zero() {
T::one()
} else {
T::zero()
}
} else {
T::zero()
}
}
AcquisitionType::Thompson => {
mean + variance.sqrt()
* T::from(scirs2_core::random::Random::default().random::<f64>())
.expect("unwrap failed")
}
AcquisitionType::InfoGain => {
variance.ln()
}
}
}
}
impl<T: Float + Debug + Default + Send + Sync> GPKernel<T> {
fn new(_kerneltype: KernelType) -> Self {
Self {
_kerneltype,
hyperparameters: Array1::ones(2), }
}
}
impl<
T: Float + Debug + Default + Clone + Send + Sync + std::fmt::Debug + 'static + std::iter::Sum,
> NeuralPredictorSearch<T>
{
pub fn new(
predictor_architecture: Vec<usize>,
_embeddingdim: usize,
confidence_threshold: f64,
) -> Self {
Self {
predictor_network: PredictorNetwork::new(predictor_architecture),
architecture_encoder: ArchitectureEncoder::new(_embeddingdim),
search_optimizer: SearchOptimizer::new(
SearchOptimizerType::Adam,
scirs2_core::numeric::NumCast::from(0.001).unwrap_or_else(|| T::zero()),
),
confidence_threshold: scirs2_core::numeric::NumCast::from(confidence_threshold)
.unwrap_or_else(|| T::zero()),
statistics: SearchStrategyStatistics::default(),
uncertainty_sampling: true,
}
}
fn predict_performance(&self, architecture: &OptimizerArchitecture<T>) -> Result<(T, T)> {
let encoded = self.architecture_encoder.encode(architecture)?;
let (prediction, uncertainty) =
self.predictor_network.forward_with_uncertainty(&encoded)?;
Ok((prediction, uncertainty))
}
fn train_predictor(
&mut self,
architectures: &[OptimizerArchitecture<T>],
performances: &[T],
) -> Result<()> {
if architectures.len() != performances.len() || architectures.is_empty() {
return Ok(());
}
let encoded_archs: std::result::Result<Vec<_>, _> = architectures
.iter()
.map(|arch| self.architecture_encoder.encode(arch))
.collect();
let encoded_archs = encoded_archs?;
for (encoded_arch, &target_performance) in encoded_archs.iter().zip(performances.iter()) {
let (prediction, _) = self
.predictor_network
.forward_with_uncertainty(encoded_arch)?;
let _loss = (prediction - target_performance)
.powf(scirs2_core::numeric::NumCast::from(2.0).unwrap_or_else(|| T::zero()));
self.predictor_network.backward_update(
encoded_arch,
target_performance,
&mut self.search_optimizer,
)?;
}
Ok(())
}
fn generate_candidate_with_uncertainty(
&mut self,
searchspace: &SearchSpaceConfig,
) -> Result<OptimizerArchitecture<T>> {
let num_candidates = 50;
let mut candidates = Vec::new();
let mut random_search = RandomSearch::<T>::new(None);
random_search.initialize(searchspace)?;
for _ in 0..num_candidates {
candidates.push(random_search.generate_architecture(searchspace, &VecDeque::new())?);
}
let mut best_candidate = candidates[0].clone();
let mut best_score = T::neg_infinity();
for candidate in candidates {
let (predicted_perf, uncertainty) = self.predict_performance(&candidate)?;
let score = if self.uncertainty_sampling {
predicted_perf + uncertainty } else {
predicted_perf };
if score > best_score {
best_score = score;
best_candidate = candidate;
}
}
Ok(best_candidate)
}
}
impl<
T: Float + Debug + Default + Clone + Send + Sync + std::fmt::Debug + 'static + std::iter::Sum,
> SearchStrategy<T> for NeuralPredictorSearch<T>
{
fn initialize(&mut self, _searchspace: &SearchSpaceConfig) -> Result<()> {
self.predictor_network.initialize()?;
Ok(())
}
fn generate_architecture(
&mut self,
searchspace: &SearchSpaceConfig,
history: &VecDeque<SearchResult<T>>,
) -> Result<OptimizerArchitecture<T>> {
if history.len() > 10 {
let architectures: Vec<_> = history.iter().map(|r| r.architecture.clone()).collect();
let performances: Vec<_> = history
.iter()
.filter_map(|r| {
r.evaluation_results
.metric_scores
.get(&EvaluationMetric::FinalPerformance)
})
.cloned()
.collect();
if architectures.len() == performances.len() {
self.train_predictor(&architectures, &performances)?;
}
}
let architecture = if history.len() > 5 {
self.generate_candidate_with_uncertainty(searchspace)?
} else {
let mut random_search = RandomSearch::<T>::new(None);
random_search.initialize(searchspace)?;
random_search.generate_architecture(searchspace, history)?
};
self.statistics.total_architectures_generated += 1;
Ok(architecture)
}
fn update_with_results(&mut self, results: &[SearchResult<T>]) -> Result<()> {
if results.is_empty() {
return Ok(());
}
let architectures: Vec<_> = results.iter().map(|r| r.architecture.clone()).collect();
let performances: Vec<_> = results
.iter()
.filter_map(|r| {
r.evaluation_results
.metric_scores
.get(&EvaluationMetric::FinalPerformance)
})
.cloned()
.collect();
if architectures.len() == performances.len() && !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("unwrap failed");
self.train_predictor(&architectures, &performances)?;
}
Ok(())
}
fn name(&self) -> &str {
"NeuralPredictorSearch"
}
fn get_statistics(&self) -> SearchStrategyStatistics<T> {
let mut stats = self.statistics.clone();
stats.exploration_rate = if self.uncertainty_sampling {
scirs2_core::numeric::NumCast::from(0.7).unwrap_or_else(|| T::zero())
} else {
scirs2_core::numeric::NumCast::from(0.3).unwrap_or_else(|| T::zero())
};
stats.exploitation_rate = T::one() - stats.exploration_rate;
stats
}
}
impl<T: Float + Debug + Default + Clone + 'static + std::iter::Sum + Send + Sync>
PredictorNetwork<T>
{
fn new(architecture: Vec<usize>) -> Self {
let mut layers = Vec::new();
for i in 0..architecture.len() - 1 {
layers.push(PredictorLayer::new(architecture[i], architecture[i + 1]));
}
Self {
layers,
dropout_rates: vec![
scirs2_core::numeric::NumCast::from(0.1)
.unwrap_or_else(|| T::zero());
architecture.len() - 1
],
architecture,
}
}
fn initialize(&mut self) -> Result<()> {
for layer in &mut self.layers {
layer.initialize()?;
}
Ok(())
}
fn forward_with_uncertainty(&self, input: &Array1<T>) -> Result<(T, T)> {
let mut current = input.clone();
for (i, layer) in self.layers.iter().enumerate() {
current = layer.forward(¤t)?;
if i < self.dropout_rates.len() {
current = self.apply_dropout(¤t, self.dropout_rates[i]);
}
}
let prediction = current[0];
let uncertainty = current.iter().map(|&x| x * x).sum::<T>().sqrt()
* scirs2_core::numeric::NumCast::from(0.1).unwrap_or_else(|| T::zero());
Ok((prediction, uncertainty))
}
fn backward_update(
&mut self,
_input: &Array1<T>,
_target: T,
_optimizer: &mut SearchOptimizer<T>,
) -> Result<()> {
Ok(())
}
fn apply_dropout(&self, input: &Array1<T>, dropoutrate: T) -> Array1<T> {
input.mapv(|x| {
if scirs2_core::random::Random::default().random::<f64>()
< dropoutrate.to_f64().unwrap_or(0.0)
{
T::zero()
} else {
x / (T::one() - dropoutrate)
}
})
}
}
impl<T: Float + Debug + Default + Clone + 'static + Send + Sync> PredictorLayer<T> {
fn new(input_size: usize, outputsize: usize) -> Self {
Self {
weights: Array2::zeros((outputsize, input_size)),
bias: Array1::zeros(outputsize),
activation: ActivationFunction::ReLU,
}
}
fn initialize(&mut self) -> Result<()> {
let fan_in = self.weights.ncols() as f64;
let fan_out = self.weights.nrows() as f64;
let scale = (6.0 / (fan_in + fan_out)).sqrt();
self.weights = Array2::from_shape_fn(self.weights.raw_dim(), |_| {
T::from(scirs2_core::random::Random::default().random::<f64>() * scale * 2.0 - scale)
.expect("unwrap failed")
});
Ok(())
}
fn forward(&self, input: &Array1<T>) -> Result<Array1<T>> {
let linear_output = self.weights.dot(input) + &self.bias;
Ok(self.apply_activation(&linear_output))
}
fn apply_activation(&self, x: &Array1<T>) -> Array1<T> {
match self.activation {
ActivationFunction::ReLU => x.mapv(|xi| if xi > T::zero() { xi } else { T::zero() }),
ActivationFunction::GELU => x.mapv(|xi| {
let x_f64 = xi.to_f64().unwrap_or(0.0);
let gelu_val = 0.5
* x_f64
* (1.0 + (x_f64 * 0.7978845608 * (1.0 + 0.044715 * x_f64 * x_f64)).tanh());
scirs2_core::numeric::NumCast::from(gelu_val).unwrap_or_else(|| T::zero())
}),
ActivationFunction::Swish => x.mapv(|xi| {
let sigmoid = T::one() / (T::one() + (-xi).exp());
xi * sigmoid
}),
ActivationFunction::Tanh => x.mapv(|xi| xi.tanh()),
ActivationFunction::Sigmoid => x.mapv(|xi| T::one() / (T::one() + (-xi).exp())),
}
}
}
impl<T: Float + Debug + Send + Sync + 'static + Default + Clone> ArchitectureEncoder<T> {
fn new(_embeddingdim: usize) -> Self {
Self {
encoding_weights: Array2::zeros((_embeddingdim, 64)), _embeddingdim,
max_components: 64,
}
}
fn encode(&self, architecture: &OptimizerArchitecture<T>) -> Result<Array1<T>> {
let mut encoding = Vec::new();
for (i, component) in architecture.components.iter().enumerate() {
if i >= self.max_components {
break;
}
let hash = component.bytes().fold(0u8, |acc, b| acc.wrapping_add(b));
encoding.push(scirs2_core::numeric::NumCast::from(hash).unwrap_or_else(|| T::zero()));
}
encoding.resize(self._embeddingdim, T::zero());
Ok(Array1::from_vec(encoding))
}
}
impl<T: Float + Debug + Send + Sync + 'static + Default + Clone> SearchOptimizer<T> {
fn new(optimizer_type: SearchOptimizerType, learningrate: T) -> Self {
Self {
optimizer_type,
_learningrate: learningrate,
momentum: scirs2_core::numeric::NumCast::from(0.9).unwrap_or_else(|| T::zero()),
parameters: HashMap::new(),
}
}
fn update_parameters(&mut self, gradients: &HashMap<String, Array1<T>>) -> Result<()> {
Ok(())
}
}
fn component_type_to_u8(_componenttype: &ComponentType) -> u8 {
match _componenttype {
ComponentType::SGD => 0,
ComponentType::Adam => 1,
ComponentType::AdaGrad => 2,
ComponentType::RMSprop => 3,
ComponentType::AdamW => 4,
ComponentType::LAMB => 5,
ComponentType::LARS => 6,
ComponentType::Lion => 7,
ComponentType::RAdam => 8,
ComponentType::Lookahead => 9,
ComponentType::SAM => 10,
ComponentType::LBFGS => 11,
ComponentType::SparseAdam => 12,
ComponentType::GroupedAdam => 13,
ComponentType::MAML => 14,
ComponentType::Reptile => 15,
ComponentType::MetaSGD => 16,
ComponentType::ConstantLR => 17,
ComponentType::ExponentialLR => 18,
ComponentType::StepLR => 19,
ComponentType::CosineAnnealingLR => 20,
ComponentType::OneCycleLR => 21,
ComponentType::CyclicLR => 22,
ComponentType::L1Regularizer => 23,
ComponentType::L2Regularizer => 24,
ComponentType::ElasticNetRegularizer => 25,
ComponentType::DropoutRegularizer => 26,
ComponentType::GradientClipping => 27,
ComponentType::WeightDecay => 28,
ComponentType::AdaptiveLR => 29,
ComponentType::AdaptiveMomentum => 30,
ComponentType::AdaptiveRegularization => 31,
ComponentType::LSTMOptimizer => 32,
ComponentType::TransformerOptimizer => 33,
ComponentType::AttentionOptimizer => 34,
ComponentType::AdaDelta => 35,
ComponentType::Momentum => 36,
ComponentType::Nesterov => 37,
ComponentType::LRScheduler => 38,
ComponentType::BatchNorm => 39,
ComponentType::Dropout => 40,
ComponentType::Custom => 255, }
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_random_search_creation() {
let search = RandomSearch::<f64>::new(Some(42));
assert_eq!(search.name(), "RandomSearch");
}
#[test]
fn test_evolutionary_search_creation() {
let search = EvolutionarySearch::<f64>::new(50, 0.1, 0.8, 3);
assert_eq!(search.name(), "EvolutionarySearch");
assert_eq!(search.population_size, 50);
}
#[test]
fn test_rl_search_creation() {
let search = ReinforcementLearningSearch::<f64>::new(256, 2, 0.001);
assert_eq!(search.name(), "ReinforcementLearningSearch");
}
}