use std::fmt::Debug;
use scirs2_core::ndarray::{Array1, Array2};
use scirs2_core::numeric::Float;
use std::collections::{HashMap, VecDeque};
use super::forward_mode::ForwardModeEngine;
use super::higher_order::{HessianConfig, HigherOrderEngine};
use super::reverse_mode::ReverseModeEngine;
use crate::error::Result;
#[allow(dead_code)]
pub struct MetaGradientEngine<T: Float + Debug + Default + Clone + Send + Sync + 'static + std::iter::Sum + scirs2_core::ndarray::ScalarOperand> {
forward_engine: ForwardModeEngine<T>,
reverse_engine: ReverseModeEngine<T>,
higher_order_engine: HigherOrderEngine<T>,
algorithm: MetaLearningAlgorithm,
inner_loop_config: InnerLoopConfig,
outer_loop_config: OuterLoopConfig,
gradient_cache: HashMap<String, Array1<T>>,
meta_param_history: VecDeque<Array1<T>>,
task_performance: HashMap<String, Vec<T>>,
hessian_cache: HashMap<String, Array2<T>>,
checkpoint_manager: CheckpointManager<T>,
advanced_config: AdvancedMetaConfig,
}
#[derive(Debug, Clone, Copy)]
pub enum MetaLearningAlgorithm {
MAML,
FirstOrderMAML,
Reptile,
L2L,
MetaSGD,
LearnedOptimizer,
ImplicitFunctionTheorem,
}
#[derive(Debug, Clone)]
pub struct InnerLoopConfig {
pub num_steps: usize,
pub learning_rate: f64,
pub optimizer: InnerOptimizer,
pub stop_condition: StopCondition,
pub gradient_checkpointing: bool,
pub max_steps: usize,
}
#[derive(Debug, Clone)]
pub struct OuterLoopConfig {
pub meta_learning_rate: f64,
pub meta_optimizer: MetaOptimizer,
pub tasks_per_batch: usize,
pub second_order: bool,
pub use_implicit_function_theorem: bool,
pub meta_regularization: f64,
}
#[derive(Debug, Clone, Copy)]
pub enum InnerOptimizer {
SGD,
Adam,
RMSprop,
Custom,
}
#[derive(Debug, Clone, Copy)]
pub enum MetaOptimizer {
SGD,
Adam,
RMSprop,
LBFGS,
Custom,
}
#[derive(Debug, Clone)]
pub enum StopCondition {
FixedSteps,
Convergence { threshold: f64 },
LossThreshold { threshold: f64 },
GradientNorm { threshold: f64 },
}
#[derive(Debug, Clone)]
pub struct MetaTask<T: Float + Debug + Send + Sync + 'static> {
pub id: String,
pub support_set: Vec<(Array1<T>, Array1<T>)>,
pub query_set: Vec<(Array1<T>, Array1<T>)>,
pub task_params: HashMap<String, T>,
pub weight: T,
}
#[derive(Debug, Clone)]
pub struct MetaGradientResult<T: Float + Debug + Send + Sync + 'static> {
pub meta_gradients: Array1<T>,
pub inner_gradients: Vec<Array1<T>>,
pub task_losses: Vec<T>,
pub meta_loss: T,
pub method: MetaGradientMethod,
pub stats: MetaGradientStats,
}
#[derive(Debug, Clone, Copy)]
pub enum MetaGradientMethod {
ExactSecondOrder,
FirstOrderApproximation,
ImplicitFunctionTheorem,
FiniteDifferences,
TruncatedBackprop,
}
#[derive(Debug, Clone)]
pub struct MetaGradientStats {
pub computation_time_us: u64,
pub inner_steps_per_task: Vec<usize>,
pub memory_usage: usize,
pub gradient_computations: usize,
pub second_order_computations: usize,
pub hvp_computations: usize,
pub cache_hits: usize,
pub cache_misses: usize,
pub checkpoints_created: usize,
pub checkpoints_restored: usize,
}
#[derive(Debug, Clone)]
pub struct AdvancedMetaConfig {
pub use_higher_order_hessian: bool,
pub auto_hvp_mode: bool,
pub enable_gradient_checkpointing: bool,
pub max_memory_usage: usize,
pub enable_hyperparameter_optimization: bool,
pub lr_adaptation_method: LearningRateAdaptation,
pub enable_multi_task_learning: bool,
pub task_similarity_metric: TaskSimilarityMetric,
pub meta_batch_sampling: MetaBatchSampling,
pub enable_curriculum_learning: bool,
pub regularization: MetaRegularization,
}
#[derive(Debug, Clone, Copy)]
pub enum LearningRateAdaptation {
Fixed,
PerParameter,
AdaptiveGradient,
LearnedRule,
MetaLearned,
}
#[derive(Debug, Clone, Copy)]
pub enum TaskSimilarityMetric {
GradientCosine,
FisherDistance,
HessianSimilarity,
ParameterDistance,
LossLandscape,
}
#[derive(Debug, Clone, Copy)]
pub enum MetaBatchSampling {
Random,
Balanced,
Curriculum,
SimilarityBased,
Adversarial,
}
#[derive(Debug, Clone)]
pub struct MetaRegularization {
pub l2_meta_params: f64,
pub adaptation_regularization: f64,
pub entropy_regularization: f64,
pub diversity_bonus: f64,
pub gradient_penalty: f64,
}
#[derive(Debug)]
pub struct CheckpointManager<T: Float + Debug + Send + Sync + 'static> {
checkpoints: HashMap<String, MetaCheckpoint<T>>,
max_checkpoints: usize,
memory_threshold: usize,
current_memory: usize,
policy: CheckpointPolicy,
}
#[derive(Debug, Clone)]
pub struct MetaCheckpoint<T: Float + Debug + Send + Sync + 'static> {
pub id: String,
pub parameters: Array1<T>,
pub gradients: Array1<T>,
pub hessian: Option<Array2<T>>,
pub task_state: TaskState<T>,
pub step: usize,
pub memory_usage: usize,
pub timestamp: std::time::Instant,
}
#[derive(Debug, Clone)]
pub struct TaskState<T: Float + Debug + Send + Sync + 'static> {
pub current_params: Array1<T>,
pub loss_history: Vec<T>,
pub gradient_history: Vec<Array1<T>>,
pub adaptation_step: usize,
}
#[derive(Debug, Clone, Copy)]
pub enum CheckpointPolicy {
FixedInterval { interval: usize },
MemoryThreshold,
AdaptiveKeyPoints,
None,
}
impl<T: Float + Debug + Default + Clone + 'static + std::iter::Sum + scirs2_core::ndarray::ScalarOperand + Send + Sync>
MetaGradientEngine<T>
{
pub fn new(
algorithm: MetaLearningAlgorithm,
inner_config: InnerLoopConfig,
outer_config: OuterLoopConfig,
) -> Self {
Self {
forward_engine: ForwardModeEngine::new(),
reverse_engine: ReverseModeEngine::new(),
higher_order_engine: HigherOrderEngine::new(3), algorithm,
inner_loop_config: inner_config,
outer_loop_config: outer_config,
gradient_cache: HashMap::new(),
meta_param_history: VecDeque::with_capacity(1000),
task_performance: HashMap::new(),
hessian_cache: HashMap::new(),
checkpoint_manager: CheckpointManager::new(100, 1024 * 1024 * 512), advanced_config: AdvancedMetaConfig::default(),
}
}
pub fn with_advanced_config(
algorithm: MetaLearningAlgorithm,
inner_config: InnerLoopConfig,
outer_config: OuterLoopConfig,
advanced_config: AdvancedMetaConfig,
) -> Self {
let mut engine = Self::new(algorithm, inner_config, outer_config);
engine.advanced_config = advanced_config;
if engine.advanced_config.use_higher_order_hessian {
engine.higher_order_engine.set_mixed_mode(true);
}
engine
}
pub fn compute_meta_gradients(
&mut self,
meta_params: &Array1<T>,
tasks: &[MetaTask<T>],
objective_fn: impl Fn(&Array1<T>, &Array1<T>, &[(Array1<T>, Array1<T>)]) -> T,
) -> Result<MetaGradientResult<T>> {
let _start_time = std::time::Instant::now();
match self.algorithm {
MetaLearningAlgorithm::MAML => {
self.compute_maml_gradients(meta_params, tasks, &objective_fn)
}
MetaLearningAlgorithm::FirstOrderMAML => {
self.compute_fomaml_gradients(meta_params, tasks, &objective_fn)
}
MetaLearningAlgorithm::Reptile => {
self.compute_reptile_gradients(meta_params, tasks, &objective_fn)
}
MetaLearningAlgorithm::L2L => {
self.compute_l2l_gradients(meta_params, tasks, &objective_fn)
}
MetaLearningAlgorithm::MetaSGD => {
self.compute_meta_sgd_gradients(meta_params, tasks, &objective_fn)
}
MetaLearningAlgorithm::LearnedOptimizer => {
self.compute_learned_optimizer_gradients(meta_params, tasks, &objective_fn)
}
MetaLearningAlgorithm::ImplicitFunctionTheorem => {
self.compute_ift_gradients(meta_params, tasks, &objective_fn)
}
}
}
fn compute_maml_gradients(
&mut self,
meta_params: &Array1<T>,
tasks: &[MetaTask<T>],
objective_fn: &impl Fn(&Array1<T>, &Array1<T>, &[(Array1<T>, Array1<T>)]) -> T,
) -> Result<MetaGradientResult<T>> {
let start_time = std::time::Instant::now();
let mut meta_gradients = Array1::zeros(meta_params.len());
let mut inner_gradients = Vec::new();
let mut task_losses = Vec::new();
let mut total_meta_loss = T::zero();
for task in tasks {
let adapted_params = self.inner_loop_adaptation(meta_params, task, objective_fn)?;
let query_loss = objective_fn(&adapted_params, meta_params, &task.query_set);
task_losses.push(query_loss);
total_meta_loss = total_meta_loss + query_loss * task.weight;
let task_meta_gradients = if self.outer_loop_config.second_order {
self.compute_second_order_meta_gradients(
meta_params,
&adapted_params,
task,
objective_fn,
)?
} else {
self.compute_first_order_meta_gradients(
meta_params,
&adapted_params,
task,
objective_fn,
)?
};
meta_gradients = meta_gradients + task_meta_gradients.clone() * task.weight;
inner_gradients.push(task_meta_gradients);
}
let meta_loss = total_meta_loss / T::from(tasks.len()).expect("unwrap failed");
let computation_time = start_time.elapsed().as_micros() as u64;
Ok(MetaGradientResult {
meta_gradients,
inner_gradients,
task_losses,
meta_loss,
method: if self.outer_loop_config.second_order {
MetaGradientMethod::ExactSecondOrder
} else {
MetaGradientMethod::FirstOrderApproximation
},
stats: MetaGradientStats {
computation_time_us: computation_time,
inner_steps_per_task: vec![self.inner_loop_config.num_steps; tasks.len()],
memory_usage: self.estimate_memory_usage(),
gradient_computations: tasks.len() * self.inner_loop_config.num_steps,
second_order_computations: if self.outer_loop_config.second_order {
tasks.len()
} else {
0
},
hvp_computations: 0,
cache_hits: 0,
cache_misses: 0,
checkpoints_created: 0,
checkpoints_restored: 0,
},
})
}
fn compute_fomaml_gradients(
&mut self,
meta_params: &Array1<T>,
tasks: &[MetaTask<T>],
objective_fn: &impl Fn(&Array1<T>, &Array1<T>, &[(Array1<T>, Array1<T>)]) -> T,
) -> Result<MetaGradientResult<T>> {
let mut meta_gradients = Array1::zeros(meta_params.len());
let mut task_losses = Vec::new();
let mut total_meta_loss = T::zero();
for task in tasks {
let adapted_params = self.inner_loop_adaptation(meta_params, task, objective_fn)?;
let query_loss = objective_fn(&adapted_params, meta_params, &task.query_set);
task_losses.push(query_loss);
total_meta_loss = total_meta_loss + query_loss * task.weight;
let gradient =
self.compute_gradient_wrt_params(&adapted_params, &task.query_set, objective_fn)?;
meta_gradients = meta_gradients + gradient * task.weight;
}
let meta_loss = total_meta_loss / T::from(tasks.len()).expect("unwrap failed");
Ok(MetaGradientResult {
meta_gradients,
inner_gradients: vec![Array1::zeros(meta_params.len()); tasks.len()],
task_losses,
meta_loss,
method: MetaGradientMethod::FirstOrderApproximation,
stats: MetaGradientStats {
computation_time_us: 0,
inner_steps_per_task: vec![self.inner_loop_config.num_steps; tasks.len()],
memory_usage: self.estimate_memory_usage(),
gradient_computations: tasks.len(),
second_order_computations: 0,
hvp_computations: 0,
cache_hits: 0,
cache_misses: 0,
checkpoints_created: 0,
checkpoints_restored: 0,
},
})
}
fn compute_reptile_gradients(
&mut self,
meta_params: &Array1<T>,
tasks: &[MetaTask<T>],
objective_fn: &impl Fn(&Array1<T>, &Array1<T>, &[(Array1<T>, Array1<T>)]) -> T,
) -> Result<MetaGradientResult<T>> {
let mut meta_gradients = Array1::zeros(meta_params.len());
let mut task_losses = Vec::new();
let mut total_meta_loss = T::zero();
for task in tasks {
let adapted_params = self.inner_loop_adaptation(meta_params, task, objective_fn)?;
let reptile_gradient = &adapted_params - meta_params;
meta_gradients = meta_gradients + reptile_gradient * task.weight;
let query_loss = objective_fn(&adapted_params, meta_params, &task.query_set);
task_losses.push(query_loss);
total_meta_loss = total_meta_loss + query_loss * task.weight;
}
let meta_loss = total_meta_loss / T::from(tasks.len()).expect("unwrap failed");
Ok(MetaGradientResult {
meta_gradients,
inner_gradients: vec![Array1::zeros(meta_params.len()); tasks.len()],
task_losses,
meta_loss,
method: MetaGradientMethod::FirstOrderApproximation,
stats: MetaGradientStats {
computation_time_us: 0,
inner_steps_per_task: vec![self.inner_loop_config.num_steps; tasks.len()],
memory_usage: self.estimate_memory_usage(),
gradient_computations: tasks.len() * self.inner_loop_config.num_steps,
second_order_computations: 0,
hvp_computations: 0,
cache_hits: 0,
cache_misses: 0,
checkpoints_created: 0,
checkpoints_restored: 0,
},
})
}
fn compute_l2l_gradients(
&mut self,
meta_params: &Array1<T>,
tasks: &[MetaTask<T>],
objective_fn: &impl Fn(&Array1<T>, &Array1<T>, &[(Array1<T>, Array1<T>)]) -> T,
) -> Result<MetaGradientResult<T>> {
self.compute_maml_gradients(meta_params, tasks, objective_fn)
}
fn compute_meta_sgd_gradients(
&mut self,
meta_params: &Array1<T>,
tasks: &[MetaTask<T>],
objective_fn: &impl Fn(&Array1<T>, &Array1<T>, &[(Array1<T>, Array1<T>)]) -> T,
) -> Result<MetaGradientResult<T>> {
self.compute_maml_gradients(meta_params, tasks, objective_fn)
}
fn compute_learned_optimizer_gradients(
&mut self,
meta_params: &Array1<T>,
tasks: &[MetaTask<T>],
objective_fn: &impl Fn(&Array1<T>, &Array1<T>, &[(Array1<T>, Array1<T>)]) -> T,
) -> Result<MetaGradientResult<T>> {
self.compute_maml_gradients(meta_params, tasks, objective_fn)
}
fn compute_ift_gradients(
&mut self,
meta_params: &Array1<T>,
tasks: &[MetaTask<T>],
objective_fn: &impl Fn(&Array1<T>, &Array1<T>, &[(Array1<T>, Array1<T>)]) -> T,
) -> Result<MetaGradientResult<T>> {
let mut meta_gradients = Array1::zeros(meta_params.len());
let mut task_losses = Vec::new();
let mut total_meta_loss = T::zero();
for task in tasks {
let optimal_params = self.solve_inner_optimization(meta_params, task, objective_fn)?;
let ift_gradients = self.compute_implicit_function_gradients(
meta_params,
&optimal_params,
task,
objective_fn,
)?;
meta_gradients = meta_gradients + ift_gradients * task.weight;
let query_loss = objective_fn(&optimal_params, meta_params, &task.query_set);
task_losses.push(query_loss);
total_meta_loss = total_meta_loss + query_loss * task.weight;
}
let meta_loss = total_meta_loss / T::from(tasks.len()).expect("unwrap failed");
Ok(MetaGradientResult {
meta_gradients,
inner_gradients: vec![Array1::zeros(meta_params.len()); tasks.len()],
task_losses,
meta_loss,
method: MetaGradientMethod::ImplicitFunctionTheorem,
stats: MetaGradientStats {
computation_time_us: 0,
inner_steps_per_task: vec![100; tasks.len()], memory_usage: self.estimate_memory_usage(),
gradient_computations: tasks.len() * 100,
second_order_computations: tasks.len(),
hvp_computations: 0,
cache_hits: 0,
cache_misses: 0,
checkpoints_created: 0,
checkpoints_restored: 0,
},
})
}
fn inner_loop_adaptation(
&self,
meta_params: &Array1<T>,
task: &MetaTask<T>,
objective_fn: &impl Fn(&Array1<T>, &Array1<T>, &[(Array1<T>, Array1<T>)]) -> T,
) -> Result<Array1<T>> {
let mut _params = meta_params.clone();
let lr = scirs2_core::numeric::NumCast::from(self.inner_loop_config.learning_rate).unwrap_or_else(|| T::zero());
for _step in 0..self.inner_loop_config.num_steps {
let gradient =
self.compute_gradient_wrt_params(&_params, &task.support_set, objective_fn)?;
_params = _params - gradient.clone() * lr;
if self.check_stop_condition(&gradient, &_params, task, objective_fn)? {
break;
}
}
Ok(_params)
}
fn solve_inner_optimization(
&self,
meta_params: &Array1<T>,
task: &MetaTask<T>,
objective_fn: &impl Fn(&Array1<T>, &Array1<T>, &[(Array1<T>, Array1<T>)]) -> T,
) -> Result<Array1<T>> {
let mut _params = meta_params.clone();
let lr = scirs2_core::numeric::NumCast::from(self.inner_loop_config.learning_rate).unwrap_or_else(|| T::zero());
let convergence_threshold = scirs2_core::numeric::NumCast::from(1e-6).unwrap_or_else(|| T::zero());
for _step in 0..self.inner_loop_config.max_steps {
let gradient =
self.compute_gradient_wrt_params(&_params, &task.support_set, objective_fn)?;
let grad_norm = gradient.iter().map(|&g| g * g).sum::<T>().sqrt();
if grad_norm < convergence_threshold {
break;
}
_params = _params - gradient * lr;
}
Ok(_params)
}
fn compute_second_order_meta_gradients(
&mut self,
meta_params: &Array1<T>,
adapted_params: &Array1<T>,
task: &MetaTask<T>,
objective_fn: &impl Fn(&Array1<T>, &Array1<T>, &[(Array1<T>, Array1<T>)]) -> T,
) -> Result<Array1<T>> {
if self.advanced_config.use_higher_order_hessian {
self.compute_advanced_second_order_gradients(
meta_params,
adapted_params,
task,
objective_fn,
)
} else {
self.compute_finite_difference_second_order(
meta_params,
adapted_params,
task,
objective_fn,
)
}
}
fn compute_advanced_second_order_gradients(
&mut self,
meta_params: &Array1<T>,
_adapted_params: &Array1<T>,
task: &MetaTask<T>,
objective_fn: &impl Fn(&Array1<T>, &Array1<T>, &[(Array1<T>, Array1<T>)]) -> T,
) -> Result<Array1<T>> {
let cache_key = format!("hessian_{}_{}", task.id, meta_params.len());
if let Some(cached_hessian) = self.hessian_cache.get(&cache_key).cloned() {
return self.compute_hvp_with_cached_hessian(
&cached_hessian,
meta_params,
task,
objective_fn,
);
}
let inner_lr = scirs2_core::numeric::NumCast::from(self.inner_loop_config.learning_rate).unwrap_or_else(|| T::zero());
let inner_steps = self.inner_loop_config.num_steps;
let task_query_set = task.query_set.clone();
let _task_support_set = task.support_set.clone();
let composite_fn = move |theta: &Array1<T>| -> T {
let mut adapted_params = theta.clone();
for _ in 0..inner_steps {
let grad_norm = adapted_params.iter().map(|&x| x * x).sum::<T>().sqrt();
if grad_norm < scirs2_core::numeric::NumCast::from(1e-6).unwrap_or_else(|| T::zero()) {
break;
}
for param in adapted_params.iter_mut() {
*param = *param - inner_lr * (*param) * scirs2_core::numeric::NumCast::from(0.01).unwrap_or_else(|| T::zero());
}
}
objective_fn(&adapted_params, theta, &task_query_set)
};
let hessian_config = HessianConfig {
exact: true,
sparse: false,
diagonal_only: false,
verify_with_finite_diff: false,
..Default::default()
};
let hessian = self.higher_order_engine.hessian_forward_over_reverse(
composite_fn,
meta_params,
&hessian_config,
)?;
self.hessian_cache.insert(cache_key, hessian.clone());
let task_query_set_2 = task.query_set.clone();
let gradient = self.gradient_at_point(
&|theta: &Array1<T>| -> T {
let mut adapted_params = theta.clone();
for _ in 0..inner_steps {
let grad_norm = adapted_params.iter().map(|&x| x * x).sum::<T>().sqrt();
if grad_norm < scirs2_core::numeric::NumCast::from(1e-6).unwrap_or_else(|| T::zero()) {
break;
}
for param in adapted_params.iter_mut() {
*param = *param - inner_lr * (*param) * scirs2_core::numeric::NumCast::from(0.01).unwrap_or_else(|| T::zero());
}
}
objective_fn(&adapted_params, theta, &task_query_set_2)
},
meta_params,
)?;
Ok(gradient)
}
fn compute_hvp_with_cached_hessian(
&mut self,
hessian: &Array2<T>,
meta_params: &Array1<T>,
task: &MetaTask<T>,
objective_fn: &impl Fn(&Array1<T>, &Array1<T>, &[(Array1<T>, Array1<T>)]) -> T,
) -> Result<Array1<T>> {
let inner_lr = scirs2_core::numeric::NumCast::from(self.inner_loop_config.learning_rate).unwrap_or_else(|| T::zero());
let inner_steps = self.inner_loop_config.num_steps;
let task_query_set = task.query_set.clone();
let gradient_direction = self.gradient_at_point(
&|theta: &Array1<T>| -> T {
let mut adapted_params = theta.clone();
for _ in 0..inner_steps {
let grad_norm = adapted_params.iter().map(|&x| x * x).sum::<T>().sqrt();
if grad_norm < scirs2_core::numeric::NumCast::from(1e-6).unwrap_or_else(|| T::zero()) {
break;
}
for param in adapted_params.iter_mut() {
*param = *param - inner_lr * (*param) * scirs2_core::numeric::NumCast::from(0.01).unwrap_or_else(|| T::zero());
}
}
objective_fn(&adapted_params, theta, &task_query_set)
},
meta_params,
)?;
let hvp = hessian.dot(&gradient_direction);
Ok(hvp)
}
fn compute_finite_difference_second_order(
&self,
meta_params: &Array1<T>,
_adapted_params: &Array1<T>,
task: &MetaTask<T>,
objective_fn: &impl Fn(&Array1<T>, &Array1<T>, &[(Array1<T>, Array1<T>)]) -> T,
) -> Result<Array1<T>> {
let eps = scirs2_core::numeric::NumCast::from(1e-6).unwrap_or_else(|| T::zero());
let mut meta_gradients = Array1::zeros(meta_params.len());
for i in 0..meta_params.len() {
let mut meta_plus = meta_params.clone();
let mut meta_minus = meta_params.clone();
meta_plus[i] = meta_plus[i] + eps;
meta_minus[i] = meta_minus[i] - eps;
let adapted_plus = self.inner_loop_adaptation(&meta_plus, task, objective_fn)?;
let adapted_minus = self.inner_loop_adaptation(&meta_minus, task, objective_fn)?;
let loss_plus = objective_fn(&adapted_plus, &meta_plus, &task.query_set);
let loss_minus = objective_fn(&adapted_minus, &meta_minus, &task.query_set);
meta_gradients[i] = (loss_plus - loss_minus) / (scirs2_core::numeric::NumCast::from(2.0).unwrap_or_else(|| T::zero()) * eps);
}
Ok(meta_gradients)
}
fn compute_first_order_meta_gradients(
&self,
_meta_params: &Array1<T>,
adapted_params: &Array1<T>,
task: &MetaTask<T>,
objective_fn: &impl Fn(&Array1<T>, &Array1<T>, &[(Array1<T>, Array1<T>)]) -> T,
) -> Result<Array1<T>> {
self.compute_gradient_wrt_params(adapted_params, &task.query_set, objective_fn)
}
fn compute_implicit_function_gradients(
&self,
meta_params: &Array1<T>,
optimal_params: &Array1<T>,
task: &MetaTask<T>,
objective_fn: &impl Fn(&Array1<T>, &Array1<T>, &[(Array1<T>, Array1<T>)]) -> T,
) -> Result<Array1<T>> {
let eps = scirs2_core::numeric::NumCast::from(1e-6).unwrap_or_else(|| T::zero());
let mut gradients = Array1::zeros(meta_params.len());
for i in 0..meta_params.len() {
let mut meta_plus = meta_params.clone();
meta_plus[i] = meta_plus[i] + eps;
let optimal_plus = self.solve_inner_optimization(&meta_plus, task, objective_fn)?;
let param_derivative = (&optimal_plus - optimal_params) / eps;
let loss_gradient =
self.compute_gradient_wrt_params(optimal_params, &task.query_set, objective_fn)?;
gradients[i] = loss_gradient.dot(¶m_derivative);
}
Ok(gradients)
}
fn compute_gradient_wrt_params(
&self,
params: &Array1<T>,
data: &[(Array1<T>, Array1<T>)],
objective_fn: &impl Fn(&Array1<T>, &Array1<T>, &[(Array1<T>, Array1<T>)]) -> T,
) -> Result<Array1<T>> {
let eps = scirs2_core::numeric::NumCast::from(1e-6).unwrap_or_else(|| T::zero());
let mut gradient = Array1::zeros(params.len());
for i in 0..params.len() {
let mut params_plus = params.clone();
let mut params_minus = params.clone();
params_plus[i] = params_plus[i] + eps;
params_minus[i] = params_minus[i] - eps;
let loss_plus = objective_fn(¶ms_plus, params, data);
let loss_minus = objective_fn(¶ms_minus, params, data);
gradient[i] = (loss_plus - loss_minus) / (scirs2_core::numeric::NumCast::from(2.0).unwrap_or_else(|| T::zero()) * eps);
}
Ok(gradient)
}
fn gradient_at_point(
&mut self,
objective_fn: &impl Fn(&Array1<T>) -> T,
point: &Array1<T>,
) -> Result<Array1<T>> {
let eps = scirs2_core::numeric::NumCast::from(1e-6).unwrap_or_else(|| T::zero());
let mut gradient = Array1::zeros(point.len());
for i in 0..point.len() {
let mut point_plus = point.clone();
let mut point_minus = point.clone();
point_plus[i] = point_plus[i] + eps;
point_minus[i] = point_minus[i] - eps;
let loss_plus = objective_fn(&point_plus);
let loss_minus = objective_fn(&point_minus);
gradient[i] = (loss_plus - loss_minus) / (scirs2_core::numeric::NumCast::from(2.0).unwrap_or_else(|| T::zero()) * eps);
}
Ok(gradient)
}
fn check_stop_condition(
&self,
gradient: &Array1<T>,
_params: &Array1<T>,
_task: &MetaTask<T>,
_objective_fn: &impl Fn(&Array1<T>, &Array1<T>, &[(Array1<T>, Array1<T>)]) -> T,
) -> Result<bool> {
match &self.inner_loop_config.stop_condition {
StopCondition::FixedSteps => Ok(false), StopCondition::Convergence { threshold } => {
let grad_norm = gradient.iter().map(|&g| g * g).sum::<T>().sqrt();
Ok(grad_norm < scirs2_core::numeric::NumCast::from(*threshold).unwrap_or_else(|| T::zero()))
}
StopCondition::GradientNorm { threshold } => {
let grad_norm = gradient.iter().map(|&g| g * g).sum::<T>().sqrt();
Ok(grad_norm < scirs2_core::numeric::NumCast::from(*threshold).unwrap_or_else(|| T::zero()))
}
StopCondition::LossThreshold { threshold: _ } => {
Ok(false)
}
}
}
fn estimate_memory_usage(&self) -> usize {
let cache_size = self.gradient_cache.len() * std::mem::size_of::<(String, Array1<T>)>();
let history_size = self.meta_param_history.len() * std::mem::size_of::<Array1<T>>();
let engine_size = std::mem::size_of::<Self>();
cache_size + history_size + engine_size
}
pub fn get_meta_stats(&self) -> MetaLearningStats {
MetaLearningStats {
algorithm: self.algorithm,
cache_size: self.gradient_cache.len(),
history_length: self.meta_param_history.len(),
memory_usage: self.estimate_memory_usage(),
total_tasks_processed: self.task_performance.len(),
}
}
pub fn clear_cache(&mut self) {
self.gradient_cache.clear();
self.meta_param_history.clear();
self.task_performance.clear();
self.hessian_cache.clear();
self.checkpoint_manager.clear_checkpoints();
}
pub fn compute_task_similarity(
&mut self,
task1: &MetaTask<T>,
task2: &MetaTask<T>,
meta_params: &Array1<T>,
objective_fn: &impl Fn(&Array1<T>, &Array1<T>, &[(Array1<T>, Array1<T>)]) -> T,
) -> Result<T> {
match self.advanced_config.task_similarity_metric {
TaskSimilarityMetric::GradientCosine => {
self.compute_gradient_cosine_similarity(task1, task2, meta_params, objective_fn)
}
TaskSimilarityMetric::FisherDistance => {
self.compute_fisher_distance(task1, task2, meta_params, objective_fn)
}
TaskSimilarityMetric::HessianSimilarity => {
self.compute_hessian_similarity(task1, task2, meta_params, objective_fn)
}
TaskSimilarityMetric::ParameterDistance => {
self.compute_parameter_distance(task1, task2, meta_params, objective_fn)
}
TaskSimilarityMetric::LossLandscape => {
self.compute_loss_landscape_similarity(task1, task2, meta_params, objective_fn)
}
}
}
fn compute_gradient_cosine_similarity(
&self,
task1: &MetaTask<T>,
task2: &MetaTask<T>,
meta_params: &Array1<T>,
objective_fn: &impl Fn(&Array1<T>, &Array1<T>, &[(Array1<T>, Array1<T>)]) -> T,
) -> Result<T> {
let grad1 =
self.compute_gradient_wrt_params(meta_params, &task1.support_set, objective_fn)?;
let grad2 =
self.compute_gradient_wrt_params(meta_params, &task2.support_set, objective_fn)?;
let norm1 = grad1.iter().map(|&x| x * x).sum::<T>().sqrt();
let norm2 = grad2.iter().map(|&x| x * x).sum::<T>().sqrt();
if norm1 > T::zero() && norm2 > T::zero() {
let cosine = grad1.dot(&grad2) / (norm1 * norm2);
Ok(cosine)
} else {
Ok(T::zero())
}
}
fn compute_fisher_distance(
&mut self,
task1: &MetaTask<T>,
task2: &MetaTask<T>,
meta_params: &Array1<T>,
objective_fn: &impl Fn(&Array1<T>, &Array1<T>, &[(Array1<T>, Array1<T>)]) -> T,
) -> Result<T> {
let grad1 =
self.compute_gradient_wrt_params(meta_params, &task1.support_set, objective_fn)?;
let grad2 =
self.compute_gradient_wrt_params(meta_params, &task2.support_set, objective_fn)?;
let diff = &grad1 - &grad2;
let distance = diff.iter().map(|&x| x * x).sum::<T>().sqrt();
Ok(distance)
}
fn compute_hessian_similarity(
&mut self,
task1: &MetaTask<T>,
task2: &MetaTask<T>,
meta_params: &Array1<T>,
objective_fn: &impl Fn(&Array1<T>, &Array1<T>, &[(Array1<T>, Array1<T>)]) -> T,
) -> Result<T> {
if !self.advanced_config.use_higher_order_hessian {
return self.compute_gradient_cosine_similarity(
task1,
task2,
meta_params,
objective_fn,
);
}
let objective1 =
|_params: &Array1<T>| objective_fn(_params, meta_params, &task1.support_set);
let objective2 =
|_params: &Array1<T>| objective_fn(_params, meta_params, &task2.support_set);
let hessian_config = HessianConfig {
exact: true,
sparse: false,
diagonal_only: true, ..Default::default()
};
let hessian1 = self.higher_order_engine.hessian_forward_over_reverse(
objective1,
meta_params,
&hessian_config,
)?;
let hessian2 = self.higher_order_engine.hessian_forward_over_reverse(
objective2,
meta_params,
&hessian_config,
)?;
let mut similarity = T::zero();
let mut norm1 = T::zero();
let mut norm2 = T::zero();
for i in 0..hessian1.nrows() {
for j in 0..hessian1.ncols() {
let h1 = hessian1[[i, j]];
let h2 = hessian2[[i, j]];
similarity = similarity + h1 * h2;
norm1 = norm1 + h1 * h1;
norm2 = norm2 + h2 * h2;
}
}
if norm1 > T::zero() && norm2 > T::zero() {
Ok(similarity / (norm1.sqrt() * norm2.sqrt()))
} else {
Ok(T::zero())
}
}
fn compute_parameter_distance(
&self,
task1: &MetaTask<T>,
task2: &MetaTask<T>,
meta_params: &Array1<T>,
objective_fn: &impl Fn(&Array1<T>, &Array1<T>, &[(Array1<T>, Array1<T>)]) -> T,
) -> Result<T> {
let adapted1 = self.inner_loop_adaptation(meta_params, task1, objective_fn)?;
let adapted2 = self.inner_loop_adaptation(meta_params, task2, objective_fn)?;
let diff = &adapted1 - &adapted2;
let distance = diff.iter().map(|&x| x * x).sum::<T>().sqrt();
Ok(distance)
}
fn compute_loss_landscape_similarity(
&self,
task1: &MetaTask<T>,
task2: &MetaTask<T>,
meta_params: &Array1<T>,
objective_fn: &impl Fn(&Array1<T>, &Array1<T>, &[(Array1<T>, Array1<T>)]) -> T,
) -> Result<T> {
let num_samples = 10;
let perturbation_scale = scirs2_core::numeric::NumCast::from(0.1).unwrap_or_else(|| T::zero());
let mut similarities = Vec::new();
for sample_idx in 0..num_samples {
let mut perturbed_params = meta_params.clone();
for i in 0..perturbed_params.len() {
let seed = (sample_idx * 1000 + i * 17) % 1000;
let perturbation_val = (seed as f64 / 1000.0) - 0.5; let perturbation = scirs2_core::numeric::NumCast::from(perturbation_val).unwrap_or_else(|| T::zero()) * perturbation_scale;
perturbed_params[i] = perturbed_params[i] + perturbation;
}
let loss1 = objective_fn(&perturbed_params, meta_params, &task1.support_set);
let loss2 = objective_fn(&perturbed_params, meta_params, &task2.support_set);
let similarity = T::one() / (T::one() + (loss1 - loss2).abs());
similarities.push(similarity);
}
let avg_similarity =
similarities.iter().copied().sum::<T>() / T::from(similarities.len()).expect("unwrap failed");
Ok(avg_similarity)
}
pub fn compute_adaptive_learning_rate(
&self,
meta_params: &Array1<T>,
gradient: &Array1<T>,
task: &MetaTask<T>,
) -> Result<Array1<T>> {
match self.advanced_config.lr_adaptation_method {
LearningRateAdaptation::Fixed => {
let lr = scirs2_core::numeric::NumCast::from(self.inner_loop_config.learning_rate).unwrap_or_else(|| T::zero());
Ok(Array1::from_elem(meta_params.len(), lr))
}
LearningRateAdaptation::PerParameter => {
self.compute_per_parameter_learning_rates(meta_params, gradient, task)
}
LearningRateAdaptation::AdaptiveGradient => {
self.compute_adaptive_gradient_rates(meta_params, gradient)
}
LearningRateAdaptation::LearnedRule => {
self.compute_learned_adaptation_rule(meta_params, gradient, task)
}
LearningRateAdaptation::MetaLearned => {
self.compute_meta_learned_rates(meta_params, gradient, task)
}
}
}
fn compute_per_parameter_learning_rates(
&self,
meta_params: &Array1<T>,
gradient: &Array1<T>,
_task: &MetaTask<T>,
) -> Result<Array1<T>> {
let mut learning_rates = Array1::zeros(meta_params.len());
let base_lr = scirs2_core::numeric::NumCast::from(self.inner_loop_config.learning_rate).unwrap_or_else(|| T::zero());
for i in 0..meta_params.len() {
let grad_magnitude = gradient[i].abs();
let adaptive_factor = T::one() / (T::one() + grad_magnitude);
learning_rates[i] = base_lr * adaptive_factor;
}
Ok(learning_rates)
}
fn compute_adaptive_gradient_rates(
&self,
_meta_params: &Array1<T>,
gradient: &Array1<T>,
) -> Result<Array1<T>> {
let mut learning_rates = Array1::zeros(gradient.len());
let base_lr = scirs2_core::numeric::NumCast::from(self.inner_loop_config.learning_rate).unwrap_or_else(|| T::zero());
let grad_mean = gradient.iter().copied().sum::<T>() / T::from(gradient.len()).expect("unwrap failed");
let grad_std = {
let variance = gradient
.iter()
.map(|&g| (g - grad_mean) * (g - grad_mean))
.sum::<T>()
/ T::from(gradient.len()).expect("unwrap failed");
variance.sqrt()
};
for i in 0..gradient.len() {
let normalized_grad = if grad_std > T::zero() {
(gradient[i] - grad_mean) / grad_std
} else {
gradient[i]
};
let adaptive_factor = T::one() / (T::one() + normalized_grad.abs());
learning_rates[i] = base_lr * adaptive_factor;
}
Ok(learning_rates)
}
fn compute_learned_adaptation_rule(
&self,
meta_params: &Array1<T>,
_gradient: &Array1<T>,
_task: &MetaTask<T>,
) -> Result<Array1<T>> {
let base_lr = scirs2_core::numeric::NumCast::from(self.inner_loop_config.learning_rate).unwrap_or_else(|| T::zero());
Ok(Array1::from_elem(meta_params.len(), base_lr))
}
fn compute_meta_learned_rates(
&self,
meta_params: &Array1<T>,
_gradient: &Array1<T>,
_task: &MetaTask<T>,
) -> Result<Array1<T>> {
let base_lr = scirs2_core::numeric::NumCast::from(self.inner_loop_config.learning_rate).unwrap_or_else(|| T::zero());
Ok(Array1::from_elem(meta_params.len(), base_lr))
}
}
impl<T: Float + Debug + Default + Clone + Send + Sync + 'static> CheckpointManager<T> {
pub fn new(max_checkpoints: usize, memory_threshold: usize) -> Self {
Self {
checkpoints: HashMap::new(),
max_checkpoints,
memory_threshold,
current_memory: 0,
policy: CheckpointPolicy::MemoryThreshold,
}
}
pub fn with_policy(
max_checkpoints: usize,
memory_threshold: usize,
policy: CheckpointPolicy,
) -> Self {
Self {
checkpoints: HashMap::new(),
max_checkpoints,
memory_threshold,
current_memory: 0,
policy,
}
}
pub fn create_checkpoint(
&mut self,
id: String,
parameters: Array1<T>,
gradients: Array1<T>,
hessian: Option<Array2<T>>,
task_state: TaskState<T>,
step: usize,
) -> Result<()> {
let memory_usage = self.estimate_checkpoint_memory(¶meters, &gradients, &hessian);
let checkpoint = MetaCheckpoint {
id: id.clone(),
parameters,
gradients,
hessian,
task_state,
step,
memory_usage,
timestamp: std::time::Instant::now(),
};
if self.checkpoints.len() >= self.max_checkpoints {
self.remove_oldest_checkpoint();
}
self.current_memory += memory_usage;
self.checkpoints.insert(id, checkpoint);
Ok(())
}
pub fn restore_checkpoint(&self, id: &str) -> Option<&MetaCheckpoint<T>> {
self.checkpoints.get(id)
}
pub fn should_checkpoint(&self, current_step: usize) -> bool {
match self.policy {
CheckpointPolicy::FixedInterval { interval } => current_step % interval == 0,
CheckpointPolicy::MemoryThreshold => self.current_memory > self.memory_threshold,
CheckpointPolicy::AdaptiveKeyPoints => {
current_step % 10 == 0
}
CheckpointPolicy::None => false,
}
}
pub fn clear_checkpoints(&mut self) {
self.checkpoints.clear();
self.current_memory = 0;
}
pub fn get_stats(&self) -> CheckpointStats {
CheckpointStats {
num_checkpoints: self.checkpoints.len(),
total_memory_usage: self.current_memory,
average_checkpoint_size: if self.checkpoints.is_empty() {
0
} else {
self.current_memory / self.checkpoints.len()
},
oldest_checkpoint_age: self.get_oldest_checkpoint_age(),
}
}
fn remove_oldest_checkpoint(&mut self) {
if let Some((oldest_id, oldest_checkpoint)) = self
.checkpoints
.iter()
.min_by_key(|(_, checkpoint)| checkpoint.timestamp)
.map(|(id, checkpoint)| (id.clone(), checkpoint.clone()))
{
self.current_memory -= oldest_checkpoint.memory_usage;
self.checkpoints.remove(&oldest_id);
}
}
fn estimate_checkpoint_memory(
&self,
parameters: &Array1<T>,
gradients: &Array1<T>,
hessian: &Option<Array2<T>>,
) -> usize {
let param_size = parameters.len() * std::mem::size_of::<T>();
let grad_size = gradients.len() * std::mem::size_of::<T>();
let hessian_size = if let Some(h) = hessian {
h.len() * std::mem::size_of::<T>()
} else {
0
};
param_size + grad_size + hessian_size + std::mem::size_of::<MetaCheckpoint<T>>()
}
fn get_oldest_checkpoint_age(&self) -> std::time::Duration {
self.checkpoints
.values()
.map(|checkpoint| checkpoint.timestamp.elapsed())
.max()
.unwrap_or(std::time::Duration::from_secs(0))
}
}
#[derive(Debug, Clone)]
pub struct CheckpointStats {
pub num_checkpoints: usize,
pub total_memory_usage: usize,
pub average_checkpoint_size: usize,
pub oldest_checkpoint_age: std::time::Duration,
}
#[derive(Debug, Clone)]
pub struct MetaLearningStats {
pub algorithm: MetaLearningAlgorithm,
pub cache_size: usize,
pub history_length: usize,
pub memory_usage: usize,
pub total_tasks_processed: usize,
}
impl Default for InnerLoopConfig {
fn default() -> Self {
Self {
num_steps: 5,
learning_rate: 0.01,
optimizer: InnerOptimizer::SGD,
stop_condition: StopCondition::FixedSteps,
gradient_checkpointing: false,
max_steps: 100,
}
}
}
impl Default for OuterLoopConfig {
fn default() -> Self {
Self {
meta_learning_rate: 0.001,
meta_optimizer: MetaOptimizer::Adam,
tasks_per_batch: 16,
second_order: true,
use_implicit_function_theorem: false,
meta_regularization: 0.0,
}
}
}
impl Default for AdvancedMetaConfig {
fn default() -> Self {
Self {
use_higher_order_hessian: true,
auto_hvp_mode: true,
enable_gradient_checkpointing: true,
max_memory_usage: 1024 * 1024 * 1024, enable_hyperparameter_optimization: false,
lr_adaptation_method: LearningRateAdaptation::Fixed,
enable_multi_task_learning: true,
task_similarity_metric: TaskSimilarityMetric::GradientCosine,
meta_batch_sampling: MetaBatchSampling::Random,
enable_curriculum_learning: false,
regularization: MetaRegularization::default(),
}
}
}
impl Default for MetaRegularization {
fn default() -> Self {
Self {
l2_meta_params: 0.0,
adaptation_regularization: 0.0,
entropy_regularization: 0.0,
diversity_bonus: 0.0,
gradient_penalty: 0.0,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_meta_gradient_engine_creation() {
let inner_config = InnerLoopConfig::default();
let outer_config = OuterLoopConfig::default();
let engine =
MetaGradientEngine::<f64>::new(MetaLearningAlgorithm::MAML, inner_config, outer_config);
assert!(matches!(engine.algorithm, MetaLearningAlgorithm::MAML));
}
#[test]
fn test_meta_task_creation() {
let support_set = vec![
(
Array1::from_vec(vec![1.0, 2.0]),
Array1::from_vec(vec![3.0]),
),
(
Array1::from_vec(vec![2.0, 3.0]),
Array1::from_vec(vec![5.0]),
),
];
let query_set = vec![(
Array1::from_vec(vec![3.0, 4.0]),
Array1::from_vec(vec![7.0]),
)];
let task = MetaTask {
id: "test_task".to_string(),
support_set,
query_set,
task_params: HashMap::new(),
weight: 1.0,
};
assert_eq!(task.id, "test_task");
assert_eq!(task.support_set.len(), 2);
assert_eq!(task.query_set.len(), 1);
}
#[test]
fn test_inner_loop_config_default() {
let config = InnerLoopConfig::default();
assert_eq!(config.num_steps, 5);
assert_eq!(config.learning_rate, 0.01);
assert!(matches!(config.optimizer, InnerOptimizer::SGD));
}
#[test]
fn test_outer_loop_config_default() {
let config = OuterLoopConfig::default();
assert_eq!(config.meta_learning_rate, 0.001);
assert!(matches!(config.meta_optimizer, MetaOptimizer::Adam));
assert_eq!(config.tasks_per_batch, 16);
assert!(config.second_order);
}
#[test]
fn test_stop_condition_convergence() {
let condition = StopCondition::Convergence { threshold: 1e-6 };
match condition {
StopCondition::Convergence { threshold } => {
assert_eq!(threshold, 1e-6);
}
_ => panic!("Wrong stop condition type"),
}
}
#[test]
fn test_meta_learning_algorithm_types() {
let algorithms = [
MetaLearningAlgorithm::MAML,
MetaLearningAlgorithm::FirstOrderMAML,
MetaLearningAlgorithm::Reptile,
MetaLearningAlgorithm::L2L,
MetaLearningAlgorithm::MetaSGD,
MetaLearningAlgorithm::LearnedOptimizer,
MetaLearningAlgorithm::ImplicitFunctionTheorem,
];
assert_eq!(algorithms.len(), 7);
}
}