use scirs2_core::ndarray::{s, Array, Array1, Array2, ArrayBase, Data, Dimension};
use scirs2_core::numeric::Float;
use scirs2_core::random::{rngs::StdRng, seeded_rng, thread_rng, CoreRandom};
use std::collections::{HashMap, VecDeque};
use std::fmt::Debug;
use super::LearnedOptimizerConfig;
use crate::common::cast_scalar;
use crate::error::{OptimError, Result};
pub mod bptt;
pub mod components;
pub mod features;
pub mod introspection;
pub mod trainer;
pub use bptt::{
BpttConfig, DiagonalQuadraticTask, FrozenRollout, MetaTrainingTask, NetworkGradients,
};
pub use features::build_lstm_features;
pub use trainer::MetaTrainer;
#[derive(Debug)]
pub struct LSTMOptimizer<T: Float + Debug + Send + Sync + 'static> {
config: LearnedOptimizerConfig,
lstm_network: LSTMNetwork<T>,
history_buffer: HistoryBuffer<T>,
meta_learner: MetaLearner<T>,
lr_controller: AdaptiveLearningRateController<T>,
state_tracker: OptimizationStateTracker<T>,
metrics: LSTMOptimizerMetrics,
step_count: usize,
}
#[derive(Debug, Clone)]
pub struct LSTMNetwork<T: Float + Debug + Send + Sync + 'static> {
layers: Vec<LSTMLayer<T>>,
output_projection: OutputProjection<T>,
attention: Option<AttentionMechanism<T>>,
layer_norms: Vec<LayerNormalization<T>>,
dropout_rate: f64,
training: bool,
}
#[derive(Debug, Clone)]
pub struct LSTMLayer<T: Float + Debug + Send + Sync + 'static> {
weight_ih: Array2<T>,
weight_hh: Array2<T>,
bias_ih: Array1<T>,
bias_hh: Array1<T>,
hidden_state: Array1<T>,
cell_state: Array1<T>,
hiddensize: usize,
}
#[derive(Debug, Clone)]
pub struct OutputProjection<T: Float + Debug + Send + Sync + 'static> {
weights: Array2<T>,
bias: Array1<T>,
output_transform: OutputTransform,
}
impl<T: Float + Debug + Send + Sync + 'static + Default + Clone> OutputProjection<T> {
pub fn new(
input_size: usize,
output_size: usize,
output_transform: OutputTransform,
) -> Result<Self> {
Self::new_with_rng(
input_size,
output_size,
output_transform,
&mut seeded_rng(thread_rng().random::<u64>()),
)
}
pub(crate) fn new_with_rng(
input_size: usize,
output_size: usize,
output_transform: OutputTransform,
rng: &mut CoreRandom<StdRng>,
) -> Result<Self> {
let limit = LSTMLayer::<T>::xavier_limit(input_size, output_size);
let weights = LSTMLayer::<T>::xavier_init(rng, output_size, input_size, limit);
let bias = Array1::zeros(output_size);
Ok(Self {
weights,
bias,
output_transform,
})
}
pub fn forward(&self, input: &Array1<T>) -> Result<Array1<T>> {
if input.len() != self.weights.ncols() {
return Err(OptimError::InvalidConfig(format!(
"OutputProjection expected {} input features, got {}",
self.weights.ncols(),
input.len()
)));
}
Ok(self.weights.dot(input) + &self.bias)
}
pub fn reset(&mut self, input_size: usize, output_size: usize) {
let limit = LSTMLayer::<T>::xavier_limit(input_size, output_size);
let mut rng = seeded_rng(thread_rng().random::<u64>());
self.weights = LSTMLayer::<T>::xavier_init(&mut rng, output_size, input_size, limit);
self.bias = Array1::zeros(output_size);
}
pub fn output_size(&self) -> usize {
self.weights.nrows()
}
}
#[derive(Debug, Clone, Copy)]
pub enum OutputTransform {
Identity,
Tanh,
ScaledTanh { scale: f64 },
AdaptiveScale,
LearnedNonlinear,
}
#[derive(Debug, Clone)]
pub struct AttentionMechanism<T: Float + Debug + Send + Sync + 'static> {
query_proj: Array2<T>,
key_proj: Array2<T>,
value_proj: Array2<T>,
output_proj: Array2<T>,
num_heads: usize,
head_size: usize,
attentionweights: Option<Array2<T>>,
head_logits: Option<Array1<T>>,
}
impl<T: Float + Debug + Send + Sync + 'static + Default + Clone> AttentionMechanism<T> {
pub fn new(config: &LearnedOptimizerConfig) -> Result<Self> {
Self::new_with_rng(config, &mut seeded_rng(thread_rng().random::<u64>()))
}
pub(crate) fn new_with_rng(
config: &LearnedOptimizerConfig,
rng: &mut CoreRandom<StdRng>,
) -> Result<Self> {
let hiddensize = config.hidden_size;
let num_heads = config.attention_heads.max(1);
if !hiddensize.is_multiple_of(num_heads) {
return Err(OptimError::InvalidConfig(format!(
"attention_heads ({num_heads}) must evenly divide hidden_size ({hiddensize})"
)));
}
let head_size = hiddensize / num_heads;
let limit = LSTMLayer::<T>::xavier_limit(hiddensize, hiddensize);
Ok(Self {
query_proj: LSTMLayer::<T>::xavier_init(rng, hiddensize, hiddensize, limit),
key_proj: LSTMLayer::<T>::xavier_init(rng, hiddensize, hiddensize, limit),
value_proj: LSTMLayer::<T>::xavier_init(rng, hiddensize, hiddensize, limit),
output_proj: LSTMLayer::<T>::xavier_init(rng, hiddensize, hiddensize, limit),
num_heads,
head_size,
attentionweights: None,
head_logits: None,
})
}
pub fn forward(&mut self, input: &Array1<T>) -> Result<Array1<T>> {
let dim = self.query_proj.nrows();
if input.len() != dim {
return Err(OptimError::InvalidConfig(format!(
"AttentionMechanism expected {dim} features, got {}",
input.len()
)));
}
let query = self.query_proj.dot(input);
let key = self.key_proj.dot(input);
let value = self.value_proj.dot(input);
let head_scale = T::one()
/ scirs2_core::numeric::NumCast::from(self.head_size)
.unwrap_or_else(T::one)
.sqrt();
let mut weights = Array2::zeros((self.num_heads, 1));
let mut logits = Array1::zeros(self.num_heads);
for h in 0..self.num_heads {
let start = h * self.head_size;
let end = start + self.head_size;
let score = query
.slice(s![start..end])
.iter()
.zip(key.slice(s![start..end]).iter())
.fold(T::zero(), |acc, (&q, &k)| acc + q * k)
* head_scale;
logits[h] = score;
weights[[h, 0]] = T::one();
}
self.attentionweights = Some(weights);
self.head_logits = Some(logits);
Ok(self.output_proj.dot(&value))
}
}
#[derive(Debug, Clone)]
pub struct LayerNormalization<T: Float + Debug + Send + Sync + 'static> {
gamma: Array1<T>,
beta: Array1<T>,
epsilon: T,
}
impl<T: Float + Debug + Send + Sync + 'static + Default + Clone> LayerNormalization<T> {
pub fn new(features: usize) -> Result<Self> {
Ok(Self {
gamma: Array1::ones(features),
beta: Array1::zeros(features),
epsilon: scirs2_core::numeric::NumCast::from(1e-5).unwrap_or_else(|| T::zero()),
})
}
pub fn forward(&self, input: &Array1<T>) -> Result<Array1<T>> {
if input.len() != self.gamma.len() {
return Err(OptimError::InvalidConfig(format!(
"LayerNormalization expected {} features, got {}",
self.gamma.len(),
input.len()
)));
}
if input.is_empty() {
return Ok(input.clone());
}
let n = scirs2_core::numeric::NumCast::from(input.len()).unwrap_or_else(T::one);
let mean = input.iter().copied().fold(T::zero(), |a, b| a + b) / n;
let variance = input
.iter()
.map(|&x| (x - mean) * (x - mean))
.fold(T::zero(), |a, b| a + b)
/ n;
let inv_std = T::one() / (variance + self.epsilon).sqrt();
let mut output = Array1::zeros(input.len());
for i in 0..input.len() {
output[i] = (input[i] - mean) * inv_std * self.gamma[i] + self.beta[i];
}
Ok(output)
}
}
#[derive(Debug, Clone)]
pub struct HistoryBuffer<T: Float + Debug + Send + Sync + 'static> {
gradients: VecDeque<Array1<T>>,
parameters: VecDeque<Array1<T>>,
losses: VecDeque<T>,
_maxlength: usize,
feature_cache: Option<Array2<T>>,
}
#[derive(Debug, Clone)]
pub struct MetaLearner<T: Float + Debug + Send + Sync + 'static> {
meta_parameters: HashMap<String, Array1<T>>,
meta_gradients: HashMap<String, Array1<T>>,
task_history: VecDeque<MetaTask<T>>,
meta_state: MetaLearningState<T>,
transfer_learner: TransferLearner<T>,
}
#[derive(Debug, Clone)]
pub struct MetaTask<T: Float + Debug + Send + Sync + 'static> {
pub id: String,
pub task_type: TaskType,
pub training_trajectory: Vec<TrajectoryPoint<T>>,
pub final_performance: T,
pub characteristics: TaskCharacteristics<T>,
pub weight: T,
}
#[derive(Debug, Clone, Copy)]
pub enum TaskType {
SupervisedLearning,
ReinforcementLearning,
UnsupervisedLearning,
FewShotLearning,
OnlineLearning,
AdversarialTraining,
}
#[derive(Debug, Clone)]
pub struct TrajectoryPoint<T: Float + Debug + Send + Sync + 'static> {
pub step: usize,
pub gradient: Array1<T>,
pub parameters: Array1<T>,
pub loss: T,
pub learning_rate: T,
pub update: Array1<T>,
}
#[derive(Debug, Clone)]
pub struct TaskCharacteristics<T: Float + Debug + Send + Sync + 'static> {
pub dimensionality: usize,
pub curvature: T,
pub noise_level: T,
pub conditioning: T,
pub difficulty: T,
pub domain_features: Array1<T>,
}
#[derive(Debug, Clone)]
pub struct MetaLearningState<T: Float + Debug + Send + Sync + 'static> {
pub meta_step: usize,
pub meta_lr: T,
pub adaptation_rate: T,
pub meta_validation_performance: T,
pub adaptation_history: VecDeque<AdaptationEvent<T>>,
pub inner_loop_state: InnerLoopState<T>,
}
#[derive(Debug, Clone)]
pub struct AdaptationEvent<T: Float + Debug + Send + Sync + 'static> {
pub source_task: String,
pub target_task: String,
pub adaptation_steps: usize,
pub transfer_efficiency: T,
pub performance_improvement: T,
}
#[derive(Debug, Clone)]
pub struct InnerLoopState<T: Float + Debug + Send + Sync + 'static> {
pub inner_step: usize,
pub inner_parameters: Array1<T>,
pub inner_optimizer_state: HashMap<String, Array1<T>>,
pub inner_performance: T,
}
#[derive(Debug, Clone)]
pub struct TransferLearner<T: Float + Debug + Send + Sync + 'static> {
pub source_knowledge: HashMap<String, Array1<T>>,
pub adaptation_parameters: Array1<T>,
pub transfer_metrics: TransferMetrics<T>,
pub similarity_estimator: DomainSimilarityEstimator<T>,
}
#[derive(Debug, Clone)]
pub struct TransferMetrics<T: Float + Debug + Send + Sync + 'static> {
pub efficiency: T,
pub adaptation_speed: T,
pub knowledge_retention: T,
pub negative_transfer_score: T,
}
#[derive(Debug, Clone)]
pub struct DomainSimilarityEstimator<T: Float + Debug + Send + Sync + 'static> {
pub domain_embeddings: HashMap<String, Array1<T>>,
pub similarity_params: Array1<T>,
pub similarity_function: SimilarityFunction,
}
#[derive(Debug, Clone, Copy)]
pub enum SimilarityFunction {
Cosine,
Euclidean,
LearnedMetric,
TaskSpecific,
}
#[derive(Debug, Clone)]
pub struct AdaptiveLearningRateController<T: Float + Debug + Send + Sync + 'static> {
base_lr: T,
current_lr: T,
adaptation_params: LRAdaptationParams<T>,
lr_history: VecDeque<T>,
performance_tracker: PerformanceTracker<T>,
}
#[derive(Debug, Clone)]
pub struct LRAdaptationParams<T: Float + Debug + Send + Sync + 'static> {
pub momentum: T,
pub gradient_sensitivity: T,
pub loss_sensitivity: T,
pub min_lr: T,
pub max_lr: T,
pub adaptation_rate: T,
}
#[derive(Debug, Clone)]
pub struct PerformanceTracker<T: Float + Debug + Send + Sync + 'static> {
recent_losses: VecDeque<T>,
trend: PerformanceTrend,
stagnation_counter: usize,
best_performance: T,
improvement_rate: T,
}
#[derive(Debug, Clone, Copy)]
pub enum PerformanceTrend {
Improving,
Stagnating,
Degrading,
Oscillating,
Unknown,
}
#[derive(Debug, Clone)]
pub struct OptimizationStateTracker<T: Float + Debug + Send + Sync + 'static> {
phase: OptimizationPhase,
convergence_indicators: ConvergenceIndicators<T>,
gradient_analyzer: GradientAnalyzer<T>,
landscape_analyzer: LossLandscapeAnalyzer<T>,
stability_metrics: StabilityMetrics<T>,
previous_gradient: Option<Array1<T>>,
previous_loss: Option<T>,
step_count: usize,
}
#[derive(Debug, Clone, Copy)]
pub enum OptimizationPhase {
InitialDescent,
SteadyProgress,
FineTuning,
Converged,
Plateau,
Diverging,
}
#[derive(Debug, Clone)]
pub struct ConvergenceIndicators<T: Float + Debug + Send + Sync + 'static> {
pub gradient_norm_trend: Vec<T>,
pub loss_change_trend: Vec<T>,
pub parameter_change_magnitude: T,
pub convergence_probability: T,
pub estimated_steps_to_convergence: Option<usize>,
}
#[derive(Debug, Clone)]
pub struct GradientAnalyzer<T: Float + Debug + Send + Sync + 'static> {
pub gradient_stats: GradientStatistics<T>,
pub correlation_tracker: GradientCorrelationTracker<T>,
pub noise_estimator: GradientNoiseEstimator<T>,
pub flow_analyzer: GradientFlowAnalyzer<T>,
}
#[derive(Debug, Clone)]
pub struct GradientStatistics<T: Float + Debug + Send + Sync + 'static> {
pub mean_norm: T,
pub norm_variance: T,
pub direction_consistency: T,
pub magnitude_distribution: Vec<T>,
pub component_stats: Array1<T>,
}
#[derive(Debug, Clone)]
pub struct GradientCorrelationTracker<T: Float + Debug + Send + Sync + 'static> {
pub correlation_matrix: Array2<T>,
pub temporal_correlations: VecDeque<T>,
pub cross_correlations: HashMap<String, T>,
}
#[derive(Debug, Clone)]
pub struct GradientNoiseEstimator<T: Float + Debug + Send + Sync + 'static> {
pub noise_level: T,
pub signal_to_noise_ratio: T,
pub noise_characteristics: NoiseCharacteristics<T>,
}
#[derive(Debug, Clone)]
pub struct NoiseCharacteristics<T: Float + Debug + Send + Sync + 'static> {
pub noise_type: NoiseType,
pub scale: T,
pub temporal_correlation: T,
pub spatial_correlation: T,
}
#[derive(Debug, Clone, Copy)]
pub enum NoiseType {
White,
Colored,
Structured,
Adaptive,
}
#[derive(Debug, Clone)]
pub struct GradientFlowAnalyzer<T: Float + Debug + Send + Sync + 'static> {
pub flow_field: Array2<T>,
pub critical_points: Vec<Array1<T>>,
pub stability: FlowStability,
pub attractors: Vec<Array1<T>>,
pub repellers: Vec<Array1<T>>,
}
#[derive(Debug, Clone, Copy)]
pub enum FlowStability {
Stable,
Unstable,
Chaotic,
Unknown,
}
#[derive(Debug, Clone)]
pub struct LossLandscapeAnalyzer<T: Float + Debug + Send + Sync + 'static> {
pub local_curvature: T,
pub hessian_eigenvalues: Option<Array1<T>>,
pub roughness: T,
pub basin_size: T,
pub barrier_heights: Vec<T>,
}
#[derive(Debug, Clone)]
pub struct StabilityMetrics<T: Float + Debug + Send + Sync + 'static> {
pub lyapunov_exponents: Array1<T>,
pub stability_margin: T,
pub perturbation_sensitivity: T,
pub robustness_score: T,
}
#[derive(Debug, Clone)]
pub struct LSTMOptimizerMetrics {
pub meta_learning_loss: f64,
pub avg_convergence_speed: f64,
pub generalization_performance: f64,
pub adaptation_efficiency: f64,
pub transfer_success_rate: f64,
pub memory_usage_mb: f64,
pub computational_overhead: f64,
pub lstm_stats: LSTMNetworkStats,
pub attention_stats: Option<AttentionStats>,
}
#[derive(Debug, Clone)]
pub struct LSTMNetworkStats {
pub gate_activations: GateActivationStats,
pub hidden_state_stats: StateStatistics,
pub cell_state_stats: StateStatistics,
pub gradient_flow_stats: GradientFlowStats,
}
#[derive(Debug, Clone)]
pub struct GateActivationStats {
pub input_gate: StateStatistics,
pub forget_gate: StateStatistics,
pub output_gate: StateStatistics,
pub cell_gate: StateStatistics,
}
#[derive(Debug, Clone)]
pub struct StateStatistics {
pub mean: f64,
pub std: f64,
pub min: f64,
pub max: f64,
pub saturation_percent: f64,
}
#[derive(Debug, Clone)]
pub struct GradientFlowStats {
pub layer_gradient_norms: Vec<f64>,
pub layer_correlations: Vec<f64>,
pub vanishing_gradient_score: f64,
pub exploding_gradient_score: f64,
}
#[derive(Debug, Clone)]
pub struct AttentionStats {
pub attention_entropy: f64,
pub attention_concentration: f64,
pub head_diversity: f64,
pub temporal_patterns: Vec<f64>,
}
impl<
T: Float
+ Default
+ Clone
+ Send
+ Sync
+ std::iter::Sum
+ for<'a> std::iter::Sum<&'a T>
+ scirs2_core::ndarray::ScalarOperand
+ std::fmt::Debug,
> LSTMOptimizer<T>
{
pub fn new(config: LearnedOptimizerConfig) -> Result<Self> {
Self::validate_config(&config)?;
let lstm_network = LSTMNetwork::new(&config)?;
let history_buffer = HistoryBuffer::new(config.gradient_history_size);
let meta_learner = MetaLearner::new(&config)?;
let lr_controller = AdaptiveLearningRateController::new(&config)?;
let state_tracker = OptimizationStateTracker::new();
let metrics = LSTMOptimizerMetrics::new();
Ok(Self {
config,
lstm_network,
history_buffer,
meta_learner,
lr_controller,
state_tracker,
metrics,
step_count: 0,
})
}
pub fn lstm_step<S, D>(
&mut self,
parameters: &ArrayBase<S, D>,
gradients: &ArrayBase<S, D>,
loss: Option<T>,
) -> Result<Array<T, D>>
where
S: Data<Elem = T>,
D: Dimension + Clone,
{
let flat_params = self.flatten_to_1d(parameters)?;
let flat_gradients = self.flatten_to_1d(gradients)?;
if self.lstm_network.output_projection.output_size() != flat_params.len() {
self.lstm_network
.output_projection
.reset(self.config.hidden_size, flat_params.len());
}
self.history_buffer
.update(&flat_params, &flat_gradients, loss);
let lstm_input = self.prepare_lstm_input(&flat_gradients)?;
let lstm_output = self.lstm_network.forward(&lstm_input)?;
let learning_rate =
self.lr_controller
.compute_lr(&flat_gradients, loss, &self.history_buffer)?;
let updates = self.generate_updates(&lstm_output, &flat_gradients, learning_rate)?;
let updated_flat = &flat_params - &updates;
self.state_tracker.update(&flat_gradients, &updates, loss);
self.update_metrics(&flat_gradients, &updates, learning_rate);
let updated_params = self.reshape_from_1d(&updated_flat, parameters.raw_dim())?;
self.step_count += 1;
Ok(updated_params)
}
pub fn meta_learning_step(&mut self, tasks: &[MetaTask<T>]) -> Result<T> {
let meta_loss = self.meta_learner.step(tasks, &mut self.lstm_network)?;
self.metrics.meta_learning_loss = meta_loss.to_f64().unwrap_or(0.0);
Ok(meta_loss)
}
pub fn transfer_to_domain(
&mut self,
target_tasks: &[MetaTask<T>],
) -> Result<TransferResults<T>> {
self.meta_learner
.transfer_learner
.transfer_to_domain(target_tasks, &mut self.lstm_network)
}
pub fn get_metrics(&self) -> &LSTMOptimizerMetrics {
&self.metrics
}
pub fn network(&self) -> &LSTMNetwork<T> {
&self.lstm_network
}
pub fn meta_learner(&self) -> &MetaLearner<T> {
&self.meta_learner
}
pub fn network_mut(&mut self) -> &mut LSTMNetwork<T> {
&mut self.lstm_network
}
pub fn current_learning_rate(&self) -> T {
self.lr_controller.current_lr()
}
pub fn stagnation_counter(&self) -> usize {
self.lr_controller.stagnation_counter()
}
pub fn get_state_analysis(&self) -> OptimizationStateAnalysis<T> {
OptimizationStateAnalysis {
current_phase: self.state_tracker.phase,
convergence_indicators: self.state_tracker.convergence_indicators.clone(),
gradient_analysis: self.state_tracker.gradient_analyzer.clone(),
landscape_analysis: self.state_tracker.landscape_analyzer.clone(),
stability_metrics: self.state_tracker.stability_metrics.clone(),
}
}
fn prepare_lstm_input(&self, gradients: &Array1<T>) -> Result<Array1<T>> {
let recent = self
.history_buffer
.get_recent_gradients(5)
.unwrap_or_default();
let loss_features = self.history_buffer.get_loss_features();
features::build_lstm_features(
gradients,
&recent,
loss_features.as_deref(),
self.config.input_features,
)
}
fn generate_updates(
&self,
lstm_output: &Array1<T>,
gradients: &Array1<T>,
learning_rate: T,
) -> Result<Array1<T>> {
let transformed_output = match self.lstm_network.output_projection.output_transform {
OutputTransform::Identity => lstm_output.clone(),
OutputTransform::Tanh => lstm_output.mapv(|x| x.tanh()),
OutputTransform::ScaledTanh { scale } => {
let scale_t =
scirs2_core::numeric::NumCast::from(scale).unwrap_or_else(|| T::zero());
lstm_output.mapv(|x| x.tanh() * scale_t)
}
OutputTransform::AdaptiveScale => {
let grad_norm = gradients.iter().map(|&g| g * g).sum::<T>().sqrt();
let adaptive_scale = T::one() / (T::one() + grad_norm);
lstm_output.mapv(|x| x * adaptive_scale)
}
OutputTransform::LearnedNonlinear => {
lstm_output.mapv(|x| {
let exp_x = x.exp();
(exp_x - (-x).exp()) / (exp_x + (-x).exp()) })
}
};
let updates = &transformed_output * learning_rate;
Ok(updates)
}
fn update_metrics(&mut self, gradients: &Array1<T>, updates: &Array1<T>, lr: T) {
let grad_norm = gradients.iter().map(|&g| g * g).sum::<T>().sqrt();
let update_norm = updates.iter().map(|&u| u * u).sum::<T>().sqrt();
self.update_lstm_stats();
let sgd_step_norm = lr.abs() * grad_norm;
self.metrics.adaptation_efficiency = if sgd_step_norm > T::zero() {
(update_norm / sgd_step_norm).to_f64().unwrap_or(1.0)
} else {
1.0
};
self.metrics.computational_overhead = self.estimate_computational_overhead();
self.metrics.memory_usage_mb = self.estimate_memory_usage();
}
fn update_lstm_stats(&mut self) {
for layer in self.lstm_network.layers.iter() {
let hidden_stats = self.compute_state_stats(&layer.hidden_state);
let cell_stats = self.compute_state_stats(&layer.cell_state);
self.metrics.lstm_stats.hidden_state_stats = hidden_stats;
self.metrics.lstm_stats.cell_state_stats = cell_stats;
}
if let Some(ref attention) = self.lstm_network.attention {
if let Some(ref attentionweights) = attention.attentionweights {
self.metrics.attention_stats = Some(
self.compute_attention_stats(attentionweights, attention.head_logits.as_ref()),
);
}
}
}
fn compute_state_stats(&self, state: &Array1<T>) -> StateStatistics {
let values: Vec<f64> = state.iter().map(|&x| x.to_f64().unwrap_or(0.0)).collect();
let mean = values.iter().sum::<f64>() / values.len() as f64;
let variance =
values.iter().map(|&x| (x - mean).powi(2)).sum::<f64>() / values.len() as f64;
let std = variance.sqrt();
let min = values.iter().cloned().fold(f64::INFINITY, f64::min);
let max = values.iter().cloned().fold(f64::NEG_INFINITY, f64::max);
let saturation_count = values.iter().filter(|&&x| x.abs() > 0.95).count();
let saturation_percent = saturation_count as f64 / values.len() as f64 * 100.0;
StateStatistics {
mean,
std,
min,
max,
saturation_percent,
}
}
fn compute_attention_stats(
&self,
attentionweights: &Array2<T>,
head_logits: Option<&Array1<T>>,
) -> AttentionStats {
let weights: Vec<f64> = attentionweights
.iter()
.map(|&w| w.to_f64().unwrap_or(0.0))
.collect();
let entropy = weights
.iter()
.filter(|&&w| w > 0.0)
.map(|&w| -w * w.ln())
.sum::<f64>();
let concentration = 1.0 / (1.0 + entropy);
let logits: Vec<f64> = head_logits
.map(|l| l.iter().map(|&x| x.to_f64().unwrap_or(0.0)).collect())
.unwrap_or_default();
let head_diversity = if logits.is_empty() {
0.0
} else {
let mean = logits.iter().sum::<f64>() / logits.len() as f64;
(logits.iter().map(|&x| (x - mean) * (x - mean)).sum::<f64>() / logits.len() as f64)
.sqrt()
};
AttentionStats {
attention_entropy: entropy,
attention_concentration: concentration,
head_diversity,
temporal_patterns: logits,
}
}
fn estimate_computational_overhead(&self) -> f64 {
let lstm_overhead = self.config.num_layers as f64 * 0.1;
let attention_overhead = if self.config.use_attention { 0.2 } else { 0.0 };
let meta_learning_overhead = 0.1;
1.0 + lstm_overhead + attention_overhead + meta_learning_overhead
}
fn estimate_memory_usage(&self) -> f64 {
let parameter_memory =
self.config.hidden_size as f64 * self.config.num_layers as f64 * 8.0 / 1024.0 / 1024.0;
let history_memory =
self.config.gradient_history_size as f64 * self.config.input_features as f64 * 8.0
/ 1024.0
/ 1024.0;
let lstm_state_memory =
self.config.hidden_size as f64 * self.config.num_layers as f64 * 2.0 * 8.0
/ 1024.0
/ 1024.0;
parameter_memory + history_memory + lstm_state_memory
}
fn validate_config(config: &LearnedOptimizerConfig) -> Result<()> {
if config.hidden_size == 0 {
return Err(OptimError::InvalidConfig(
"Hidden size must be positive".to_string(),
));
}
if config.num_layers == 0 {
return Err(OptimError::InvalidConfig(
"Number of layers must be positive".to_string(),
));
}
if config.input_features == 0 {
return Err(OptimError::InvalidConfig(
"Input features must be positive".to_string(),
));
}
if config.meta_learning_rate <= 0.0 {
return Err(OptimError::InvalidConfig(
"Meta learning rate must be positive".to_string(),
));
}
Ok(())
}
fn flatten_to_1d<S, D>(&self, array: &ArrayBase<S, D>) -> Result<Array1<T>>
where
S: Data<Elem = T>,
D: Dimension,
{
Ok(Array1::from_iter(array.iter().cloned()))
}
fn reshape_from_1d<D>(&self, flat: &Array1<T>, shape: D) -> Result<Array<T, D>>
where
D: Dimension + Clone,
{
Array::from_shape_vec(shape, flat.to_vec())
.map_err(|e| OptimError::InvalidConfig(format!("Reshape error: {}", e)))
}
}
impl<T: Float + Debug + Default + Clone + 'static + Send + Sync> LSTMNetwork<T> {
pub fn new(config: &LearnedOptimizerConfig) -> Result<Self> {
Self::new_seeded(config, thread_rng().random::<u64>())
}
pub fn new_seeded(config: &LearnedOptimizerConfig, seed: u64) -> Result<Self> {
let mut rng = seeded_rng(seed);
let mut layers = Vec::new();
for i in 0..config.num_layers {
let input_size = if i == 0 {
config.input_features
} else {
config.hidden_size
};
let layer = LSTMLayer::new(input_size, config.hidden_size, &mut rng)?;
layers.push(layer);
}
let output_projection = OutputProjection::new_with_rng(
config.hidden_size,
config.output_features,
OutputTransform::ScaledTanh { scale: 0.1 },
&mut rng,
)?;
let attention = if config.use_attention {
Some(AttentionMechanism::new_with_rng(config, &mut rng)?)
} else {
None
};
let layer_norms = (0..config.num_layers)
.map(|_| LayerNormalization::new(config.hidden_size))
.collect::<Result<Vec<_>>>()?;
Ok(Self {
layers,
output_projection,
attention,
layer_norms,
dropout_rate: config.dropout_rate,
training: false,
})
}
pub fn set_training(&mut self, training: bool) {
self.training = training;
}
pub fn is_training(&self) -> bool {
self.training
}
pub fn reset_state(&mut self) {
for layer in self.layers.iter_mut() {
layer.hidden_state.fill(T::zero());
layer.cell_state.fill(T::zero());
}
}
pub fn forward(&mut self, input: &Array1<T>) -> Result<Array1<T>> {
let mut current_input = input.clone();
for i in 0..self.layers.len() {
current_input = self.layers[i].forward(¤t_input)?;
current_input = self.layer_norms[i].forward(¤t_input)?;
if self.training && self.dropout_rate > 0.0 {
current_input = self.apply_dropout(¤t_input)?;
}
}
if let Some(ref mut attention) = self.attention {
current_input = attention.forward(¤t_input)?;
}
let output = self.output_projection.forward(¤t_input)?;
Ok(output)
}
fn apply_dropout(&self, input: &Array1<T>) -> Result<Array1<T>> {
if !(0.0..1.0).contains(&self.dropout_rate) {
return Err(OptimError::InvalidConfig(format!(
"dropout_rate must be in [0, 1), got {}",
self.dropout_rate
)));
}
if self.dropout_rate == 0.0 {
return Ok(input.clone());
}
let threshold: T = cast_scalar(self.dropout_rate)?;
let keep_scale: T = cast_scalar(1.0 - self.dropout_rate)?;
let mut rng = scirs2_core::random::thread_rng();
let mut out = input.clone();
for value in out.iter_mut() {
let sample: T = cast_scalar(rng.gen_range(0.0..1.0))?;
*value = if sample < threshold {
T::zero()
} else {
*value / keep_scale
};
}
Ok(out)
}
}
impl<T: Float + Debug + Default + Clone + 'static + Send + Sync> LSTMLayer<T> {
fn new(_input_size: usize, hiddensize: usize, rng: &mut CoreRandom<StdRng>) -> Result<Self> {
let limit_ih = Self::xavier_limit(_input_size, hiddensize);
let limit_hh = Self::xavier_limit(hiddensize, hiddensize);
Ok(Self {
weight_ih: Self::xavier_init(rng, 4 * hiddensize, _input_size, limit_ih),
weight_hh: Self::xavier_init(rng, 4 * hiddensize, hiddensize, limit_hh),
bias_ih: Array1::zeros(4 * hiddensize),
bias_hh: Array1::zeros(4 * hiddensize),
hidden_state: Array1::zeros(hiddensize),
cell_state: Array1::zeros(hiddensize),
hiddensize,
})
}
fn forward(&mut self, input: &Array1<T>) -> Result<Array1<T>> {
let ih_linear = self.weight_ih.dot(input) + &self.bias_ih;
let hh_linear = self.weight_hh.dot(&self.hidden_state) + &self.bias_hh;
let gates = ih_linear + hh_linear;
let input_gate = Self::sigmoid(&gates.slice(s![0..self.hiddensize]).to_owned());
let forget_gate = Self::sigmoid(
&gates
.slice(s![self.hiddensize..2 * self.hiddensize])
.to_owned(),
);
let cell_gate = Self::tanh(
&gates
.slice(s![2 * self.hiddensize..3 * self.hiddensize])
.to_owned(),
);
let output_gate = Self::sigmoid(
&gates
.slice(s![3 * self.hiddensize..4 * self.hiddensize])
.to_owned(),
);
self.cell_state = &forget_gate * &self.cell_state + &input_gate * &cell_gate;
self.hidden_state = &output_gate * &Self::tanh(&self.cell_state);
Ok(self.hidden_state.clone())
}
pub(crate) fn xavier_limit(fan_in: usize, fan_out: usize) -> f64 {
(6.0 / (fan_in + fan_out).max(1) as f64).sqrt()
}
fn xavier_init(
rng: &mut CoreRandom<StdRng>,
rows: usize,
cols: usize,
limit: f64,
) -> Array2<T> {
Array2::from_shape_fn((rows, cols), |_| {
let val = (rng.gen_range(0.0..1.0) - 0.5) * 2.0 * limit;
scirs2_core::numeric::NumCast::from(val).unwrap_or_else(|| T::zero())
})
}
fn sigmoid(x: &Array1<T>) -> Array1<T> {
x.mapv(|xi| T::one() / (T::one() + (-xi).exp()))
}
fn tanh(x: &Array1<T>) -> Array1<T> {
x.mapv(|xi| xi.tanh())
}
}
impl<T: Float + Debug + Send + Sync + 'static + Default + Clone> HistoryBuffer<T> {
fn new(_maxlength: usize) -> Self {
Self {
gradients: VecDeque::with_capacity(_maxlength),
parameters: VecDeque::with_capacity(_maxlength),
losses: VecDeque::with_capacity(_maxlength),
_maxlength,
feature_cache: None,
}
}
fn update(&mut self, params: &Array1<T>, grads: &Array1<T>, loss: Option<T>) {
self.parameters.push_back(params.clone());
self.gradients.push_back(grads.clone());
if let Some(l) = loss {
self.losses.push_back(l);
}
while self.parameters.len() > self._maxlength {
self.parameters.pop_front();
}
while self.gradients.len() > self._maxlength {
self.gradients.pop_front();
}
while self.losses.len() > self._maxlength {
self.losses.pop_front();
}
self.feature_cache = None;
}
fn get_recent_gradients(&self, count: usize) -> Option<Vec<&Array1<T>>> {
if self.gradients.len() < count {
return None;
}
Some(self.gradients.iter().rev().take(count).collect())
}
fn get_loss_features(&self) -> Option<Vec<T>> {
if self.losses.len() < 2 {
return None;
}
let current_loss = *self.losses.back()?;
let prev_loss = *self.losses.get(self.losses.len() - 2)?;
let loss_change = current_loss - prev_loss;
let loss_ratio = if prev_loss.abs()
> scirs2_core::numeric::NumCast::from(1e-8).unwrap_or_else(|| T::zero())
{
current_loss / prev_loss
} else {
T::one()
};
Some(vec![loss_change, loss_ratio])
}
}
#[derive(Debug, Clone)]
pub struct OptimizationStateAnalysis<T: Float + Debug + Send + Sync + 'static> {
pub current_phase: OptimizationPhase,
pub convergence_indicators: ConvergenceIndicators<T>,
pub gradient_analysis: GradientAnalyzer<T>,
pub landscape_analysis: LossLandscapeAnalyzer<T>,
pub stability_metrics: StabilityMetrics<T>,
}
#[derive(Debug, Clone)]
pub struct TransferResults<T: Float + Debug + Send + Sync + 'static> {
pub initial_performance: T,
pub final_performance: T,
pub adaptation_steps: usize,
pub transfer_efficiency: T,
}
impl Default for LSTMOptimizerMetrics {
fn default() -> Self {
Self::new()
}
}
impl LSTMOptimizerMetrics {
fn new() -> Self {
Self {
meta_learning_loss: 0.0,
avg_convergence_speed: 0.0,
generalization_performance: 0.0,
adaptation_efficiency: 0.0,
transfer_success_rate: 0.0,
memory_usage_mb: 0.0,
computational_overhead: 1.0,
lstm_stats: LSTMNetworkStats {
gate_activations: GateActivationStats {
input_gate: StateStatistics::default(),
forget_gate: StateStatistics::default(),
output_gate: StateStatistics::default(),
cell_gate: StateStatistics::default(),
},
hidden_state_stats: StateStatistics::default(),
cell_state_stats: StateStatistics::default(),
gradient_flow_stats: GradientFlowStats {
layer_gradient_norms: Vec::new(),
layer_correlations: Vec::new(),
vanishing_gradient_score: 0.0,
exploding_gradient_score: 0.0,
},
},
attention_stats: None,
}
}
}
impl Default for StateStatistics {
fn default() -> Self {
Self {
mean: 0.0,
std: 0.0,
min: 0.0,
max: 0.0,
saturation_percent: 0.0,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_lstm_optimizer_creation() {
let config = LearnedOptimizerConfig::default();
let optimizer = LSTMOptimizer::<f64>::new(config);
assert!(optimizer.is_ok());
}
#[test]
fn test_lstm_layer_creation() {
let layer = LSTMLayer::<f64>::new(10, 20, &mut seeded_rng(7));
assert!(layer.is_ok());
let layer = layer.expect("LSTMLayer::new should succeed");
assert_eq!(layer.hiddensize, 20);
assert_eq!(layer.weight_ih.shape(), &[80, 10]); assert_eq!(layer.weight_hh.shape(), &[80, 20]); }
#[test]
fn test_history_buffer() {
let mut buffer = HistoryBuffer::<f64>::new(5);
let params = Array1::from_vec(vec![1.0, 2.0, 3.0]);
let grads = Array1::from_vec(vec![0.1, 0.2, 0.3]);
buffer.update(¶ms, &grads, Some(0.5));
assert_eq!(buffer.gradients.len(), 1);
assert_eq!(buffer.parameters.len(), 1);
assert_eq!(buffer.losses.len(), 1);
}
#[test]
fn test_config_validation() {
let mut config = LearnedOptimizerConfig::default();
assert!(LSTMOptimizer::<f64>::validate_config(&config).is_ok());
config.hidden_size = 0;
assert!(LSTMOptimizer::<f64>::validate_config(&config).is_err());
}
#[test]
fn test_lstm_network_creation() {
let config = LearnedOptimizerConfig::default();
let network = LSTMNetwork::<f64>::new(&config);
assert!(network.is_ok());
let network = network.expect("LSTMNetwork::new should succeed");
assert_eq!(network.layers.len(), config.num_layers);
assert!(network.attention.is_some()); }
#[test]
fn test_metrics_initialization() {
let metrics = LSTMOptimizerMetrics::new();
assert_eq!(metrics.meta_learning_loss, 0.0);
assert_eq!(metrics.computational_overhead, 1.0);
assert!(metrics.attention_stats.is_none());
}
}