1use super::config::{ActivationFunction, MetaLearningConfig};
4use super::feedforward::FeedForwardNetwork;
5use crate::common::cast_positive;
6use crate::error::Result;
7use scirs2_core::ndarray::{Array1, Array2};
8use scirs2_core::numeric::Float;
9use std::collections::VecDeque;
10use std::fmt::Debug;
11use std::time::Instant;
12
13#[derive(Debug, Clone, Copy, PartialEq)]
15pub enum MetaLearningStrategy {
16 MAML,
18 FOMAML,
20 Reptile,
22 GradientBased,
24 MemoryAugmented,
26}
27
28pub struct TransformerMetaLearning<
30 T: Float + Debug + scirs2_core::ndarray::ScalarOperand + Send + Sync + 'static,
31> {
32 strategy: MetaLearningStrategy,
34
35 config: MetaLearningConfig<T>,
37
38 meta_optimizer: MetaOptimizer<T>,
40
41 adaptation_network: AdaptationNetwork<T>,
43
44 memory_bank: MemoryBank<T>,
46
47 performance_tracker: PerformanceTracker<T>,
49
50 meta_state: MetaState<T>,
52}
53
54impl<
55 T: Float
56 + Debug
57 + scirs2_core::ndarray::ScalarOperand
58 + Send
59 + Sync
60 + 'static
61 + scirs2_core::ndarray::ScalarOperand,
62 > TransformerMetaLearning<T>
63{
64 pub fn new(config: &super::config::TransformerBasedOptimizerConfig<T>) -> Result<Self> {
66 let meta_config = config.meta_learning_config.clone();
67 let strategy = MetaLearningStrategy::MAML; let meta_optimizer = MetaOptimizer::new(&meta_config)?;
70 let adaptation_network =
71 AdaptationNetwork::new(config.model_dimension, config.feedforward_dimension)?;
72 let memory_bank = MemoryBank::new(1000, config.model_dimension)?;
73 let performance_tracker = PerformanceTracker::new();
74 let meta_state = MetaState::new(config.model_dimension)?;
75
76 Ok(Self {
77 strategy,
78 config: meta_config,
79 meta_optimizer,
80 adaptation_network,
81 memory_bank,
82 performance_tracker,
83 meta_state,
84 })
85 }
86
87 fn validate_batch_shapes(
92 tasks: &[TaskBatch<T>],
93 support_data: &[Array2<T>],
94 query_data: &[Array2<T>],
95 ) -> Result<()> {
96 if tasks.is_empty() {
97 return Err(crate::error::OptimError::InsufficientData(
98 "meta step requires at least one task".to_string(),
99 ));
100 }
101 if support_data.len() < tasks.len() || query_data.len() < tasks.len() {
102 return Err(crate::error::OptimError::InvalidConfig(format!(
103 "meta step got {} tasks but {} support and {} query batches",
104 tasks.len(),
105 support_data.len(),
106 query_data.len()
107 )));
108 }
109 Ok(())
110 }
111
112 pub fn meta_step(
114 &mut self,
115 tasks: &[TaskBatch<T>],
116 support_data: &[Array2<T>],
117 query_data: &[Array2<T>],
118 ) -> Result<MetaLearningResult<T>> {
119 match self.strategy {
120 MetaLearningStrategy::MAML => self.maml_step(tasks, support_data, query_data),
121 MetaLearningStrategy::FOMAML => self.fomaml_step(tasks, support_data, query_data),
122 MetaLearningStrategy::Reptile => self.reptile_step(tasks, support_data, query_data),
123 MetaLearningStrategy::GradientBased => {
124 self.gradient_based_step(tasks, support_data, query_data)
125 }
126 MetaLearningStrategy::MemoryAugmented => {
127 self.memory_augmented_step(tasks, support_data, query_data)
128 }
129 }
130 }
131
132 fn maml_step(
154 &mut self,
155 tasks: &[TaskBatch<T>],
156 support_data: &[Array2<T>],
157 query_data: &[Array2<T>],
158 ) -> Result<MetaLearningResult<T>> {
159 Self::validate_batch_shapes(tasks, support_data, query_data)?;
160 let start_time = Instant::now();
161 let mut total_loss = T::zero();
162 let mut task_adaptations = Vec::new();
163
164 for (i, task) in tasks.iter().enumerate() {
165 let mut adapted_params = self.meta_state.get_parameters().clone();
167 let support_loss_before =
168 self.compute_task_loss(&adapted_params, &support_data[i], task)?;
169
170 for _inner_step in 0..self.config.inner_steps {
171 let gradients = self.compute_gradients(&adapted_params, &support_data[i], task)?;
173
174 for (param, grad) in adapted_params.iter_mut().zip(gradients.iter()) {
176 *param = *param - self.config.inner_learning_rate * (*grad);
177 }
178 }
179
180 let query_loss = self.compute_task_loss(&adapted_params, &query_data[i], task)?;
182 total_loss = total_loss + query_loss;
183
184 let mut query_gradient =
187 self.compute_gradients(&adapted_params, &query_data[i], task)?;
188 if !self.config.first_order {
189 let alpha = self.config.inner_learning_rate;
190 for _ in 0..self.config.inner_steps {
191 let hv =
192 self.hessian_vector_product(&query_gradient, &support_data[i], task)?;
193 for (g, &h) in query_gradient.iter_mut().zip(hv.iter()) {
194 *g = *g - alpha * h;
195 }
196 }
197 }
198
199 task_adaptations.push(TaskAdaptation {
200 task_id: task.id.clone(),
201 adapted_parameters: adapted_params,
202 support_loss: support_loss_before,
203 query_loss,
204 query_gradient,
205 adaptation_steps: self.config.inner_steps,
206 });
207 }
208
209 let meta_loss = total_loss / T::from(tasks.len()).unwrap_or_else(|| T::one());
211 let meta_gradients = self.compute_meta_gradients(&task_adaptations)?;
212 self.meta_optimizer
213 .update(&mut self.meta_state, &meta_gradients)?;
214
215 for (i, adaptation) in task_adaptations.iter().enumerate() {
217 self.memory_bank.store_experience(
218 &tasks[i],
219 &adaptation.adapted_parameters,
220 adaptation.query_loss,
221 )?;
222 }
223
224 let result = MetaLearningResult {
225 meta_loss: meta_loss.to_f64().unwrap_or(0.0),
226 task_adaptations,
227 computation_time: start_time.elapsed(),
228 convergence_rate: self.estimate_convergence_rate()?,
229 };
230
231 self.performance_tracker.record_meta_step(result.clone());
232 Ok(result)
233 }
234
235 fn fomaml_step(
237 &mut self,
238 tasks: &[TaskBatch<T>],
239 support_data: &[Array2<T>],
240 query_data: &[Array2<T>],
241 ) -> Result<MetaLearningResult<T>> {
242 self.maml_step(tasks, support_data, query_data)
245 }
246
247 fn reptile_step(
249 &mut self,
250 tasks: &[TaskBatch<T>],
251 support_data: &[Array2<T>],
252 query_data: &[Array2<T>],
253 ) -> Result<MetaLearningResult<T>> {
254 Self::validate_batch_shapes(tasks, support_data, query_data)?;
255 let start_time = Instant::now();
256 let mut parameter_updates = Vec::new();
257 let mut total_loss = T::zero();
258
259 for (i, task) in tasks.iter().enumerate() {
260 let mut adapted_params = self.meta_state.get_parameters().clone();
262
263 for _ in 0..self.config.inner_steps {
264 let gradients = self.compute_gradients(&adapted_params, &support_data[i], task)?;
265
266 for (param, grad) in adapted_params.iter_mut().zip(gradients.iter()) {
267 *param = *param - self.config.inner_learning_rate * (*grad);
268 }
269 }
270
271 let original_params = self.meta_state.get_parameters();
273 let param_diff: Vec<T> = adapted_params
274 .iter()
275 .zip(original_params.iter())
276 .map(|(adapted, original)| *adapted - *original)
277 .collect();
278
279 parameter_updates.push(param_diff);
280
281 let query_loss = self.compute_task_loss(&adapted_params, &query_data[i], task)?;
283 total_loss = total_loss + query_loss;
284 }
285
286 let mut meta_update = vec![T::zero(); self.meta_state.get_parameters().len()];
288 for param_update in ¶meter_updates {
289 for (i, &update) in param_update.iter().enumerate() {
290 meta_update[i] = meta_update[i] + update;
291 }
292 }
293
294 let num_tasks: T = cast_positive(tasks.len(), "task batch size")?;
295 for update in meta_update.iter_mut() {
296 *update = *update / num_tasks;
297 }
298
299 self.meta_state
300 .update_parameters(&meta_update, self.config.meta_learning_rate)?;
301
302 let result = MetaLearningResult {
303 meta_loss: (total_loss / num_tasks).to_f64().unwrap_or(0.0),
304 task_adaptations: Vec::new(), computation_time: start_time.elapsed(),
306 convergence_rate: self.estimate_convergence_rate()?,
307 };
308
309 self.performance_tracker.record_meta_step(result.clone());
310 Ok(result)
311 }
312
313 fn gradient_based_step(
315 &mut self,
316 tasks: &[TaskBatch<T>],
317 support_data: &[Array2<T>],
318 query_data: &[Array2<T>],
319 ) -> Result<MetaLearningResult<T>> {
320 Self::validate_batch_shapes(tasks, support_data, query_data)?;
322 let start_time = Instant::now();
323 let mut total_loss = T::zero();
324
325 for (i, task) in tasks.iter().enumerate() {
326 let context_embedding = self.adaptation_network.encode_task_context(task)?;
328 let predicted_params = self
329 .adaptation_network
330 .predict_parameters(&context_embedding)?;
331
332 let mut adapted_params = predicted_params;
334 for _ in 0..self.config.inner_steps {
335 let gradients = self.compute_gradients(&adapted_params, &support_data[i], task)?;
336
337 for (param, grad) in adapted_params.iter_mut().zip(gradients.iter()) {
338 *param = *param - self.config.inner_learning_rate * (*grad);
339 }
340 }
341
342 let query_loss = self.compute_task_loss(&adapted_params, &query_data[i], task)?;
343 total_loss = total_loss + query_loss;
344 }
345
346 let result = MetaLearningResult {
347 meta_loss: (total_loss / T::from(tasks.len()).unwrap_or_else(|| T::one()))
348 .to_f64()
349 .unwrap_or(0.0),
350 task_adaptations: Vec::new(),
351 computation_time: start_time.elapsed(),
352 convergence_rate: self.estimate_convergence_rate()?,
353 };
354
355 Ok(result)
356 }
357
358 fn memory_augmented_step(
360 &mut self,
361 tasks: &[TaskBatch<T>],
362 support_data: &[Array2<T>],
363 query_data: &[Array2<T>],
364 ) -> Result<MetaLearningResult<T>> {
365 Self::validate_batch_shapes(tasks, support_data, query_data)?;
366 let start_time = Instant::now();
367 let mut total_loss = T::zero();
368
369 for (i, task) in tasks.iter().enumerate() {
370 let relevant_experiences = self.memory_bank.retrieve_similar_experiences(task, 5)?;
372
373 let memory_guided_params = self.initialize_from_memory(&relevant_experiences)?;
375
376 let mut adapted_params = memory_guided_params;
377 for _ in 0..self.config.inner_steps {
378 let gradients = self.compute_gradients(&adapted_params, &support_data[i], task)?;
379
380 for (param, grad) in adapted_params.iter_mut().zip(gradients.iter()) {
381 *param = *param - self.config.inner_learning_rate * (*grad);
382 }
383 }
384
385 let query_loss = self.compute_task_loss(&adapted_params, &query_data[i], task)?;
386 total_loss = total_loss + query_loss;
387
388 self.memory_bank
390 .store_experience(task, &adapted_params, query_loss)?;
391 }
392
393 let result = MetaLearningResult {
394 meta_loss: (total_loss / T::from(tasks.len()).unwrap_or_else(|| T::one()))
395 .to_f64()
396 .unwrap_or(0.0),
397 task_adaptations: Vec::new(),
398 computation_time: start_time.elapsed(),
399 convergence_rate: self.estimate_convergence_rate()?,
400 };
401
402 Ok(result)
403 }
404
405 pub fn generate_update(
407 &mut self,
408 transformer_output: &Array2<T>,
409 current_parameters: &Array1<T>,
410 ) -> Result<Array1<T>> {
411 let update = self
413 .adaptation_network
414 .generate_parameter_update(transformer_output, current_parameters)?;
415
416 let scaled_update = self.apply_meta_scaling(&update)?;
418
419 Ok(scaled_update)
420 }
421
422 pub fn update_from_loss(&mut self, loss: T) -> Result<()> {
424 self.meta_state.update_loss_history(loss);
425 self.performance_tracker
426 .record_loss(loss.to_f64().unwrap_or(0.0));
427 Ok(())
428 }
429
430 pub fn parameter_count(&self) -> usize {
432 self.adaptation_network.parameter_count()
433 }
434
435 pub fn meta_optimizer_mut(&mut self) -> &mut MetaOptimizer<T> {
437 &mut self.meta_optimizer
438 }
439
440 pub fn set_strategy(&mut self, strategy: MetaLearningStrategy) {
442 self.strategy = strategy;
443 }
444
445 pub fn get_strategy(&self) -> MetaLearningStrategy {
447 self.strategy
448 }
449
450 fn compute_task_loss(&self, params: &[T], data: &Array2<T>, task: &TaskBatch<T>) -> Result<T> {
477 let (n_rows, _n_cols) = Self::validate_task_data(params, data)?;
478 let predictions = Self::linear_forward(params, data);
479 let n = T::from(n_rows).unwrap_or_else(|| T::one());
480
481 let mut data_loss = T::zero();
482 for (i, &pred) in predictions.iter().enumerate() {
483 let residual = pred - Self::target_of(data, i);
484 data_loss = data_loss + residual * residual;
485 }
486 data_loss = data_loss / n;
487
488 let ridge = self.ridge_weight(params.len(), task);
489 let mut reg = T::zero();
490 for &p in params.iter().skip(1) {
491 reg = reg + p * p;
492 }
493
494 Ok(data_loss + ridge * reg)
495 }
496
497 fn compute_gradients(
502 &self,
503 params: &[T],
504 data: &Array2<T>,
505 task: &TaskBatch<T>,
506 ) -> Result<Vec<T>> {
507 let (n_rows, _) = Self::validate_task_data(params, data)?;
508 let predictions = Self::linear_forward(params, data);
509 let residuals: Vec<T> = predictions
510 .iter()
511 .enumerate()
512 .map(|(i, &pred)| pred - Self::target_of(data, i))
513 .collect();
514
515 let mut gradients =
516 Self::accumulate_design_gradient(&residuals, data, params.len(), n_rows);
517
518 let two = T::one() + T::one();
520 let ridge = self.ridge_weight(params.len(), task);
521 for (k, grad) in gradients.iter_mut().enumerate().skip(1) {
522 *grad = *grad + two * ridge * params[k];
523 }
524
525 Ok(gradients)
526 }
527
528 fn hessian_vector_product(
535 &self,
536 vector: &[T],
537 data: &Array2<T>,
538 task: &TaskBatch<T>,
539 ) -> Result<Vec<T>> {
540 let (n_rows, _) = Self::validate_task_data(vector, data)?;
541 let directional = Self::linear_forward(vector, data);
545 let mut out = Self::accumulate_design_gradient(&directional, data, vector.len(), n_rows);
546
547 let two = T::one() + T::one();
548 let ridge = self.ridge_weight(vector.len(), task);
549 for (k, slot) in out.iter_mut().enumerate().skip(1) {
550 *slot = *slot + two * ridge * vector[k];
551 }
552
553 Ok(out)
554 }
555
556 fn ridge_weight(&self, param_count: usize, task: &TaskBatch<T>) -> T {
558 let lambda: T = scirs2_core::numeric::NumCast::from(task.complexity.max(0.0))
559 .unwrap_or_else(|| T::zero());
560 let p = T::from(param_count.max(1)).unwrap_or_else(|| T::one());
561 lambda / p
562 }
563
564 fn validate_task_data(params: &[T], data: &Array2<T>) -> Result<(usize, usize)> {
566 if params.is_empty() {
567 return Err(crate::error::OptimError::InvalidConfig(
568 "task loss needs at least one parameter (the bias)".to_string(),
569 ));
570 }
571 let (n_rows, n_cols) = data.dim();
572 if n_rows == 0 || n_cols == 0 {
573 return Err(crate::error::OptimError::InsufficientData(format!(
574 "task data must be non-empty, got shape {n_rows}x{n_cols}"
575 )));
576 }
577 Ok((n_rows, n_cols))
578 }
579
580 fn effective_features(param_count: usize, n_cols: usize) -> usize {
582 n_cols.saturating_sub(1).min(param_count.saturating_sub(1))
583 }
584
585 fn target_of(data: &Array2<T>, i: usize) -> T {
587 let n_cols = data.ncols();
588 data[[i, n_cols - 1]]
589 }
590
591 fn linear_forward(weights: &[T], data: &Array2<T>) -> Vec<T> {
593 let (n_rows, n_cols) = data.dim();
594 let f = Self::effective_features(weights.len(), n_cols);
595 (0..n_rows)
596 .map(|i| {
597 let mut acc = weights[0];
598 for j in 0..f {
599 acc = acc + weights[j + 1] * data[[i, j]];
600 }
601 acc
602 })
603 .collect()
604 }
605
606 fn accumulate_design_gradient(
608 residuals: &[T],
609 data: &Array2<T>,
610 param_count: usize,
611 n_rows: usize,
612 ) -> Vec<T> {
613 let n_cols = data.ncols();
614 let f = Self::effective_features(param_count, n_cols);
615 let two = T::one() + T::one();
616 let scale = two / T::from(n_rows.max(1)).unwrap_or_else(|| T::one());
617
618 let mut out = vec![T::zero(); param_count];
619 for (i, &r) in residuals.iter().enumerate() {
620 out[0] = out[0] + r;
621 for j in 0..f {
622 out[j + 1] = out[j + 1] + r * data[[i, j]];
623 }
624 }
625 for slot in out.iter_mut() {
626 *slot = *slot * scale;
627 }
628 out
629 }
630
631 fn compute_meta_gradients(&self, adaptations: &[TaskAdaptation<T>]) -> Result<Vec<T>> {
642 let first = adaptations.first().ok_or_else(|| {
643 crate::error::OptimError::InsufficientData(
644 "no task adaptations to derive a meta-gradient from".to_string(),
645 )
646 })?;
647 let param_count = first.query_gradient.len();
648 let mut meta_gradients = vec![T::zero(); param_count];
649
650 for adaptation in adaptations {
651 if adaptation.query_gradient.len() != param_count {
652 return Err(crate::error::OptimError::ComputationError(format!(
653 "task '{}' reported {} meta-gradient components, expected {}",
654 adaptation.task_id,
655 adaptation.query_gradient.len(),
656 param_count
657 )));
658 }
659 for (slot, &g) in meta_gradients
660 .iter_mut()
661 .zip(adaptation.query_gradient.iter())
662 {
663 *slot = *slot + g;
664 }
665 }
666
667 let num_tasks = T::from(adaptations.len()).unwrap_or_else(|| T::one());
668 for grad in meta_gradients.iter_mut() {
669 *grad = *grad / num_tasks;
670 }
671
672 Ok(meta_gradients)
673 }
674
675 fn estimate_convergence_rate(&self) -> Result<f64> {
676 let loss_history = self.performance_tracker.get_loss_history();
677 if loss_history.len() < 2 {
678 return Ok(0.0);
679 }
680
681 let recent_losses: Vec<_> = loss_history.iter().rev().take(5).cloned().collect();
682 let (Some(oldest), Some(newest)) = (recent_losses.last(), recent_losses.first()) else {
686 return Ok(0.0);
687 };
688 Ok((oldest - newest).clamp(0.0, 1.0))
689 }
690
691 fn apply_meta_scaling(&self, update: &Array1<T>) -> Result<Array1<T>> {
692 let scale_factor = self.meta_state.get_scale_factor();
694 Ok(update * scale_factor)
695 }
696
697 fn initialize_from_memory(&self, experiences: &[MemoryExperience<T>]) -> Result<Vec<T>> {
698 if experiences.is_empty() {
699 return Ok(self.meta_state.get_parameters().clone());
700 }
701
702 let param_count = experiences[0].parameters.len();
704 let mut averaged_params = vec![T::zero(); param_count];
705
706 for experience in experiences {
707 for (i, ¶m) in experience.parameters.iter().enumerate() {
708 averaged_params[i] = averaged_params[i] + param;
709 }
710 }
711
712 let num_experiences: T = cast_positive(experiences.len(), "experience count")?;
713 for param in averaged_params.iter_mut() {
714 *param = *param / num_experiences;
715 }
716
717 Ok(averaged_params)
718 }
719}
720
721pub struct MetaOptimizer<
723 T: Float + Debug + scirs2_core::ndarray::ScalarOperand + Send + Sync + 'static,
724> {
725 learning_rate: T,
727
728 momentum: Option<T>,
730
731 velocity: Option<Vec<T>>,
733}
734
735impl<T: Float + Debug + scirs2_core::ndarray::ScalarOperand + Send + Sync + 'static>
736 MetaOptimizer<T>
737{
738 pub fn new(config: &MetaLearningConfig<T>) -> Result<Self> {
739 Ok(Self {
740 learning_rate: config.meta_learning_rate,
741 momentum: config.meta_momentum,
742 velocity: None,
743 })
744 }
745
746 pub fn update(&mut self, state: &mut MetaState<T>, gradients: &[T]) -> Result<()> {
756 let params = state.get_parameters_mut();
757 if gradients.len() < params.len() {
758 return Err(crate::error::OptimError::ComputationError(format!(
759 "meta-gradient has {} components but the meta-parameters have {}",
760 gradients.len(),
761 params.len()
762 )));
763 }
764
765 if let Some(momentum) = self.momentum {
766 let velocity = self
768 .velocity
769 .get_or_insert_with(|| vec![T::zero(); params.len()]);
770 if velocity.len() != params.len() {
771 velocity.resize(params.len(), T::zero());
772 }
773
774 for i in 0..params.len() {
775 velocity[i] = momentum * velocity[i] + self.learning_rate * gradients[i];
776 params[i] = params[i] - velocity[i];
777 }
778 } else {
779 for (param, &grad) in params.iter_mut().zip(gradients.iter()) {
781 *param = *param - self.learning_rate * grad;
782 }
783 }
784
785 Ok(())
786 }
787
788 pub fn set_momentum(&mut self, momentum: Option<T>) {
793 self.momentum = momentum;
794 if momentum.is_none() {
795 self.velocity = None;
796 }
797 }
798
799 pub fn momentum(&self) -> Option<T> {
801 self.momentum
802 }
803}
804
805pub struct AdaptationNetwork<
807 T: Float + Debug + scirs2_core::ndarray::ScalarOperand + Send + Sync + 'static,
808> {
809 context_encoder: FeedForwardNetwork<T>,
811
812 parameter_predictor: FeedForwardNetwork<T>,
814
815 update_generator: FeedForwardNetwork<T>,
817
818 model_dimension: usize,
820}
821
822impl<T: Float + Debug + scirs2_core::ndarray::ScalarOperand + Send + Sync + 'static>
823 AdaptationNetwork<T>
824{
825 pub fn new(model_dimension: usize, hidden_dimension: usize) -> Result<Self> {
826 let context_encoder =
827 FeedForwardNetwork::new(model_dimension, hidden_dimension, ActivationFunction::ReLU)?;
828
829 let parameter_predictor =
830 FeedForwardNetwork::new(hidden_dimension, model_dimension, ActivationFunction::Tanh)?;
831
832 let update_generator = FeedForwardNetwork::new(
833 model_dimension * 2, model_dimension,
835 ActivationFunction::ReLU,
836 )?;
837
838 Ok(Self {
839 context_encoder,
840 parameter_predictor,
841 update_generator,
842 model_dimension,
843 })
844 }
845
846 pub fn parameter_count(&self) -> usize {
857 self.context_encoder.parameter_count()
858 + self.parameter_predictor.parameter_count()
859 + self.update_generator.parameter_count()
860 }
861
862 pub fn encode_task_context(&mut self, task: &TaskBatch<T>) -> Result<Array2<T>> {
863 let descriptor = [task.difficulty, task.complexity, task.data_characteristics];
864 let mut task_features = Array2::zeros((1, self.model_dimension));
865 for k in 0..self.model_dimension {
866 let d = descriptor[k % descriptor.len()];
867 let harmonic = (k / descriptor.len()) as f64 + 1.0;
868 let phase = (k as f64) * std::f64::consts::FRAC_PI_4;
869 let value = (std::f64::consts::TAU * harmonic * d + phase).cos();
870 task_features[[0, k]] =
871 scirs2_core::numeric::NumCast::from(value).unwrap_or_else(|| T::zero());
872 }
873 self.context_encoder.forward(&task_features)
874 }
875
876 pub fn predict_parameters(&mut self, context: &Array2<T>) -> Result<Vec<T>> {
877 let predicted = self.parameter_predictor.forward(context)?;
878 Ok(predicted.row(0).to_vec())
879 }
880
881 pub fn generate_parameter_update(
882 &mut self,
883 transformer_output: &Array2<T>,
884 current_parameters: &Array1<T>,
885 ) -> Result<Array1<T>> {
886 let batch_size = transformer_output.shape()[0];
888 let mut input = Array2::zeros((batch_size, self.model_dimension * 2));
889
890 for i in 0..batch_size {
891 for j in 0..self.model_dimension {
892 input[[i, j]] = transformer_output[[i, j]];
893 if j < current_parameters.len() {
894 input[[i, j + self.model_dimension]] = current_parameters[j];
895 }
896 }
897 }
898
899 let update = self.update_generator.forward(&input)?;
900 Ok(update.row(0).to_owned())
901 }
902}
903
904pub struct MemoryBank<
906 T: Float + Debug + scirs2_core::ndarray::ScalarOperand + Send + Sync + 'static,
907> {
908 experiences: VecDeque<MemoryExperience<T>>,
910
911 max_size: usize,
913
914 parameter_dimension: usize,
916}
917
918impl<T: Float + Debug + scirs2_core::ndarray::ScalarOperand + Send + Sync + 'static> MemoryBank<T> {
919 pub fn new(max_size: usize, parameter_dimension: usize) -> Result<Self> {
920 Ok(Self {
921 experiences: VecDeque::new(),
922 max_size,
923 parameter_dimension,
924 })
925 }
926
927 pub fn store_experience(
936 &mut self,
937 task: &TaskBatch<T>,
938 parameters: &[T],
939 performance: T,
940 ) -> Result<()> {
941 if parameters.len() != self.parameter_dimension {
942 return Err(crate::error::OptimError::InvalidConfig(format!(
943 "MemoryBank stores {}-dimensional parameter vectors but was given {}",
944 self.parameter_dimension,
945 parameters.len()
946 )));
947 }
948 let experience = MemoryExperience {
949 task_signature: self.compute_task_signature(task),
950 parameters: parameters.to_vec(),
951 performance: performance.to_f64().unwrap_or(0.0),
952 timestamp: Instant::now(),
953 };
954
955 self.experiences.push_back(experience);
956
957 if self.experiences.len() > self.max_size {
958 self.experiences.pop_front();
959 }
960
961 Ok(())
962 }
963
964 pub fn retrieve_similar_experiences(
965 &self,
966 task: &TaskBatch<T>,
967 k: usize,
968 ) -> Result<Vec<MemoryExperience<T>>> {
969 let target_signature = self.compute_task_signature(task);
970
971 let mut scored_experiences: Vec<_> = self
972 .experiences
973 .iter()
974 .map(|exp| {
975 let similarity = self.compute_similarity(&target_signature, &exp.task_signature);
976 (similarity, exp.clone())
977 })
978 .collect();
979
980 scored_experiences.sort_by(|a, b| b.0.total_cmp(&a.0));
983
984 Ok(scored_experiences
985 .into_iter()
986 .take(k)
987 .map(|(_, exp)| exp)
988 .collect())
989 }
990
991 fn compute_task_signature(&self, task: &TaskBatch<T>) -> Vec<f64> {
992 vec![task.difficulty, task.complexity, task.data_characteristics]
994 }
995
996 fn compute_similarity(&self, sig1: &[f64], sig2: &[f64]) -> f64 {
997 if sig1.len() != sig2.len() {
998 return 0.0;
999 }
1000
1001 let dot_product: f64 = sig1.iter().zip(sig2.iter()).map(|(a, b)| a * b).sum();
1002 let norm1: f64 = sig1.iter().map(|x| x * x).sum::<f64>().sqrt();
1003 let norm2: f64 = sig2.iter().map(|x| x * x).sum::<f64>().sqrt();
1004
1005 if norm1 == 0.0 || norm2 == 0.0 {
1006 0.0
1007 } else {
1008 dot_product / (norm1 * norm2)
1009 }
1010 }
1011}
1012
1013#[derive(Debug, Clone)]
1015pub struct TaskBatch<T: Float + Debug + scirs2_core::ndarray::ScalarOperand + Send + Sync + 'static>
1016{
1017 pub id: String,
1018 pub difficulty: f64,
1019 pub complexity: f64,
1020 pub data_characteristics: f64,
1021 pub _phantom: std::marker::PhantomData<T>,
1022}
1023
1024#[derive(Debug, Clone)]
1025pub struct TaskAdaptation<
1026 T: Float + Debug + scirs2_core::ndarray::ScalarOperand + Send + Sync + 'static,
1027> {
1028 pub task_id: String,
1029 pub adapted_parameters: Vec<T>,
1030 pub support_loss: T,
1031 pub query_loss: T,
1032 pub query_gradient: Vec<T>,
1037 pub adaptation_steps: usize,
1038}
1039
1040#[derive(Debug, Clone)]
1041pub struct MetaLearningResult<
1042 T: Float + Debug + scirs2_core::ndarray::ScalarOperand + Send + Sync + 'static,
1043> {
1044 pub meta_loss: f64,
1045 pub task_adaptations: Vec<TaskAdaptation<T>>,
1046 pub computation_time: std::time::Duration,
1047 pub convergence_rate: f64,
1048}
1049
1050#[derive(Debug, Clone)]
1051pub struct MemoryExperience<
1052 T: Float + Debug + scirs2_core::ndarray::ScalarOperand + Send + Sync + 'static,
1053> {
1054 pub task_signature: Vec<f64>,
1055 pub parameters: Vec<T>,
1056 pub performance: f64,
1057 pub timestamp: Instant,
1058}
1059
1060pub struct PerformanceTracker<
1061 T: Float + Debug + scirs2_core::ndarray::ScalarOperand + Send + Sync + 'static,
1062> {
1063 loss_history: VecDeque<f64>,
1064 meta_results: VecDeque<MetaLearningResult<T>>,
1065}
1066
1067impl<T: Float + Debug + scirs2_core::ndarray::ScalarOperand + Send + Sync + 'static> Default
1068 for PerformanceTracker<T>
1069{
1070 fn default() -> Self {
1071 Self::new()
1072 }
1073}
1074
1075impl<T: Float + Debug + scirs2_core::ndarray::ScalarOperand + Send + Sync + 'static>
1076 PerformanceTracker<T>
1077{
1078 pub fn new() -> Self {
1079 Self {
1080 loss_history: VecDeque::new(),
1081 meta_results: VecDeque::new(),
1082 }
1083 }
1084
1085 pub fn record_loss(&mut self, loss: f64) {
1086 self.loss_history.push_back(loss);
1087 if self.loss_history.len() > 1000 {
1088 self.loss_history.pop_front();
1089 }
1090 }
1091
1092 pub fn record_meta_step(&mut self, result: MetaLearningResult<T>) {
1093 self.meta_results.push_back(result);
1094 if self.meta_results.len() > 100 {
1095 self.meta_results.pop_front();
1096 }
1097 }
1098
1099 pub fn get_loss_history(&self) -> &VecDeque<f64> {
1100 &self.loss_history
1101 }
1102}
1103
1104#[derive(Debug, Clone)]
1105pub struct MetaState<T: Float + Debug + scirs2_core::ndarray::ScalarOperand + Send + Sync + 'static>
1106{
1107 parameters: Vec<T>,
1108 loss_history: VecDeque<T>,
1109 scale_factor: T,
1110}
1111
1112impl<T: Float + Debug + scirs2_core::ndarray::ScalarOperand + Send + Sync + 'static> MetaState<T> {
1113 pub fn new(parameter_count: usize) -> Result<Self> {
1114 Ok(Self {
1115 parameters: vec![T::zero(); parameter_count],
1116 loss_history: VecDeque::new(),
1117 scale_factor: T::one(),
1118 })
1119 }
1120
1121 pub fn get_parameters(&self) -> &Vec<T> {
1122 &self.parameters
1123 }
1124
1125 pub fn get_parameters_mut(&mut self) -> &mut Vec<T> {
1126 &mut self.parameters
1127 }
1128
1129 pub fn update_parameters(&mut self, updates: &[T], learning_rate: T) -> Result<()> {
1130 for (param, &update) in self.parameters.iter_mut().zip(updates.iter()) {
1131 *param = *param + learning_rate * update;
1132 }
1133 Ok(())
1134 }
1135
1136 pub fn update_loss_history(&mut self, loss: T) {
1137 self.loss_history.push_back(loss);
1138 if self.loss_history.len() > 100 {
1139 self.loss_history.pop_front();
1140 }
1141 }
1142
1143 pub fn get_scale_factor(&self) -> T {
1144 self.scale_factor
1145 }
1146}
1147
1148#[cfg(test)]
1149mod tests {
1150 use super::*;
1151
1152 fn small_config(
1154 param_dim: usize,
1155 ) -> super::super::config::TransformerBasedOptimizerConfig<f64> {
1156 let mut config = super::super::config::TransformerBasedOptimizerConfig::<f64> {
1157 model_dimension: param_dim,
1158 feedforward_dimension: 2 * param_dim,
1159 num_attention_heads: 2,
1160 attention_head_dimension: param_dim / 2,
1161 num_transformer_layers: 1,
1162 sequence_length: 8,
1163 ..Default::default()
1164 };
1165 config.meta_learning_config.inner_steps = 3;
1166 config.meta_learning_config.inner_learning_rate = 0.05;
1167 config.meta_learning_config.meta_learning_rate = 0.1;
1168 config
1169 }
1170
1171 fn task(id: &str, difficulty: f64, complexity: f64, characteristics: f64) -> TaskBatch<f64> {
1172 TaskBatch {
1173 id: id.to_string(),
1174 difficulty,
1175 complexity,
1176 data_characteristics: characteristics,
1177 _phantom: std::marker::PhantomData,
1178 }
1179 }
1180
1181 fn linear_batch(rows: usize, offset: f64) -> Array2<f64> {
1183 Array2::from_shape_fn((rows, 3), |(i, j)| {
1184 let x0 = (i as f64 + offset) * 0.25;
1185 let x1 = (i as f64 - offset) * 0.5;
1186 match j {
1187 0 => x0,
1188 1 => x1,
1189 _ => 2.0 * x0 - x1 + 0.5,
1190 }
1191 })
1192 }
1193
1194 #[test]
1195 fn test_meta_learning_creation() {
1196 let config = super::super::config::TransformerBasedOptimizerConfig::<f32>::default();
1197 let meta_learning = TransformerMetaLearning::new(&config);
1198 assert!(meta_learning.is_ok());
1199 }
1200
1201 #[test]
1204 fn task_loss_actually_depends_on_the_parameters() {
1205 let ml = TransformerMetaLearning::new(&small_config(4)).expect("construction");
1206 let data = linear_batch(6, 1.0);
1207 let t = task("t", 0.5, 0.0, 0.3);
1208
1209 let exact = vec![0.5, 2.0, -1.0, 0.0];
1211 let wrong = vec![0.0, 0.0, 0.0, 0.0];
1212
1213 let loss_exact = ml.compute_task_loss(&exact, &data, &t).expect("loss");
1214 let loss_wrong = ml.compute_task_loss(&wrong, &data, &t).expect("loss");
1215
1216 assert!(
1217 loss_exact < 1e-18,
1218 "the exact solution should have ~zero loss, got {loss_exact}"
1219 );
1220 assert!(
1221 loss_wrong > 1.0,
1222 "the zero vector should have a large loss, got {loss_wrong}"
1223 );
1224 }
1225
1226 #[test]
1229 fn task_descriptor_changes_the_objective() {
1230 let ml = TransformerMetaLearning::new(&small_config(4)).expect("construction");
1231 let data = linear_batch(6, 1.0);
1232 let params = vec![0.4, 1.8, -0.9, 0.2];
1233
1234 let plain = ml
1235 .compute_task_loss(¶ms, &data, &task("a", 0.5, 0.0, 0.3))
1236 .expect("loss");
1237 let regularized = ml
1238 .compute_task_loss(¶ms, &data, &task("b", 0.5, 4.0, 0.3))
1239 .expect("loss");
1240
1241 assert!(
1242 regularized > plain,
1243 "a higher-complexity task must be regularized more: {plain} vs {regularized}"
1244 );
1245 }
1246
1247 #[test]
1250 fn compute_gradients_matches_finite_differences() {
1251 let ml = TransformerMetaLearning::new(&small_config(4)).expect("construction");
1252 let data = linear_batch(7, 0.5);
1253 let t = task("fd", 0.4, 1.5, 0.2);
1254 let params = vec![0.3, -0.7, 1.1, 0.45];
1255
1256 let analytic = ml.compute_gradients(¶ms, &data, &t).expect("gradient");
1257 let h = 1e-6;
1258 for k in 0..params.len() {
1259 let mut plus = params.clone();
1260 let mut minus = params.clone();
1261 plus[k] += h;
1262 minus[k] -= h;
1263 let numeric = (ml.compute_task_loss(&plus, &data, &t).expect("loss")
1264 - ml.compute_task_loss(&minus, &data, &t).expect("loss"))
1265 / (2.0 * h);
1266 let scale = numeric.abs().max(1.0);
1267 assert!(
1268 (analytic[k] - numeric).abs() / scale < 1e-6,
1269 "component {k}: analytic {} vs numeric {numeric}",
1270 analytic[k]
1271 );
1272 }
1273 }
1274
1275 #[test]
1278 fn hessian_vector_product_matches_finite_differences() {
1279 let ml = TransformerMetaLearning::new(&small_config(4)).expect("construction");
1280 let data = linear_batch(7, 0.5);
1281 let t = task("hvp", 0.4, 1.5, 0.2);
1282 let params = vec![0.3, -0.7, 1.1, 0.45];
1283 let direction = vec![0.9, -0.2, 0.4, 1.3];
1284
1285 let hv = ml
1286 .hessian_vector_product(&direction, &data, &t)
1287 .expect("hvp");
1288
1289 let h = 1e-6;
1290 let mut plus = params.clone();
1291 let mut minus = params.clone();
1292 for k in 0..params.len() {
1293 plus[k] += h * direction[k];
1294 minus[k] -= h * direction[k];
1295 }
1296 let gp = ml.compute_gradients(&plus, &data, &t).expect("grad+");
1297 let gm = ml.compute_gradients(&minus, &data, &t).expect("grad-");
1298
1299 for k in 0..params.len() {
1300 let numeric = (gp[k] - gm[k]) / (2.0 * h);
1301 let scale = numeric.abs().max(1.0);
1302 assert!(
1303 (hv[k] - numeric).abs() / scale < 1e-5,
1304 "component {k}: analytic {} vs numeric {numeric}",
1305 hv[k]
1306 );
1307 }
1308 }
1309
1310 #[test]
1313 fn maml_step_reduces_the_query_loss() {
1314 let mut ml = TransformerMetaLearning::new(&small_config(4)).expect("construction");
1315 let tasks = vec![task("t0", 0.4, 0.0, 0.2), task("t1", 0.6, 0.0, 0.7)];
1316 let support = vec![linear_batch(8, 0.0), linear_batch(8, 1.0)];
1317 let query = vec![linear_batch(6, 0.5), linear_batch(6, 1.5)];
1318
1319 let first = ml
1320 .maml_step(&tasks, &support, &query)
1321 .expect("first meta step")
1322 .meta_loss;
1323 let mut last = first;
1324 for _ in 0..40 {
1325 last = ml
1326 .maml_step(&tasks, &support, &query)
1327 .expect("meta step")
1328 .meta_loss;
1329 }
1330
1331 assert!(
1332 last < first,
1333 "meta-training did not reduce the query loss: {first} -> {last}"
1334 );
1335 }
1336
1337 #[test]
1341 fn second_order_meta_gradient_differs_from_first_order() {
1342 let tasks = vec![task("t0", 0.4, 0.0, 0.2)];
1343 let support = vec![linear_batch(8, 0.0)];
1344 let query = vec![linear_batch(6, 0.5)];
1345
1346 let mut cfg_first = small_config(4);
1347 cfg_first.meta_learning_config.first_order = true;
1348 let mut fo = TransformerMetaLearning::new(&cfg_first).expect("construction");
1349
1350 let mut cfg_second = small_config(4);
1351 cfg_second.meta_learning_config.first_order = false;
1352 let mut so = TransformerMetaLearning::new(&cfg_second).expect("construction");
1353
1354 let fo_result = fo.maml_step(&tasks, &support, &query).expect("fomaml");
1355 let so_result = so.maml_step(&tasks, &support, &query).expect("maml");
1356
1357 let fo_grad = &fo_result.task_adaptations[0].query_gradient;
1358 let so_grad = &so_result.task_adaptations[0].query_gradient;
1359 let delta = fo_grad
1360 .iter()
1361 .zip(so_grad.iter())
1362 .map(|(a, b)| (a - b).abs())
1363 .fold(0.0, f64::max);
1364 assert!(
1365 delta > 1e-9,
1366 "second-order correction had no effect (max delta {delta})"
1367 );
1368 }
1369
1370 #[test]
1373 fn task_context_encoding_depends_on_the_task() {
1374 let mut net = AdaptationNetwork::<f64>::new(8, 16).expect("network");
1375 let a = net
1376 .encode_task_context(&task("a", 0.1, 0.2, 0.3))
1377 .expect("encode a");
1378 let b = net
1379 .encode_task_context(&task("b", 0.8, 0.4, 0.9))
1380 .expect("encode b");
1381 let delta = (&a - &b).iter().map(|d| d.abs()).fold(0.0, f64::max);
1382 assert!(
1383 delta > 1e-9,
1384 "distinct tasks produced identical context embeddings (delta {delta})"
1385 );
1386 }
1387
1388 #[test]
1392 fn meta_optimizer_momentum_branch_is_reachable() {
1393 let mut cfg = MetaLearningConfig::<f64> {
1394 meta_learning_rate: 0.1,
1395 ..Default::default()
1396 };
1397 let mut sgd = MetaOptimizer::new(&cfg).expect("sgd");
1398 assert!(sgd.momentum().is_none(), "default must be plain SGD");
1399
1400 cfg.meta_momentum = Some(0.9);
1401 let mut momentum = MetaOptimizer::new(&cfg).expect("momentum");
1402 assert_eq!(momentum.momentum(), Some(0.9));
1403
1404 let mut state_sgd = MetaState::<f64>::new(3).expect("state");
1405 let mut state_mom = MetaState::<f64>::new(3).expect("state");
1406 let grad = vec![1.0, -2.0, 0.5];
1407 for _ in 0..3 {
1408 sgd.update(&mut state_sgd, &grad).expect("sgd update");
1409 momentum
1410 .update(&mut state_mom, &grad)
1411 .expect("momentum update");
1412 }
1413
1414 let delta = state_sgd
1415 .get_parameters()
1416 .iter()
1417 .zip(state_mom.get_parameters().iter())
1418 .map(|(a, b)| (a - b).abs())
1419 .fold(0.0, f64::max);
1420 assert!(
1421 delta > 1e-9,
1422 "momentum produced the same trajectory as SGD (delta {delta})"
1423 );
1424 }
1425
1426 #[test]
1429 fn meta_optimizer_rejects_short_gradients() {
1430 let cfg = MetaLearningConfig::<f64> {
1431 meta_momentum: Some(0.9),
1432 ..Default::default()
1433 };
1434 let mut opt = MetaOptimizer::new(&cfg).expect("optimizer");
1435 let mut state = MetaState::<f64>::new(4).expect("state");
1436 assert!(opt.update(&mut state, &[1.0, 2.0]).is_err());
1437 }
1438
1439 #[test]
1441 fn meta_step_rejects_mismatched_batches() {
1442 let mut ml = TransformerMetaLearning::new(&small_config(4)).expect("construction");
1443 let tasks = vec![task("t0", 0.4, 0.0, 0.2), task("t1", 0.6, 0.0, 0.7)];
1444 let support = vec![linear_batch(8, 0.0)];
1445 let query = vec![linear_batch(6, 0.5)];
1446 assert!(ml.meta_step(&tasks, &support, &query).is_err());
1447 assert!(ml.meta_step(&[], &support, &query).is_err());
1448 }
1449
1450 #[test]
1451 fn test_memory_bank() {
1452 let memory = MemoryBank::<f32>::new(100, 64);
1453 assert!(memory.is_ok());
1454
1455 let mut bank = memory.expect("MemoryBank::new should succeed");
1456 let task = TaskBatch {
1457 id: "test".to_string(),
1458 difficulty: 0.5,
1459 complexity: 0.7,
1460 data_characteristics: 0.3,
1461 _phantom: std::marker::PhantomData,
1462 };
1463
1464 let params = vec![0.1f32; 64];
1465 assert!(bank.store_experience(&task, ¶ms, 0.8).is_ok());
1466 }
1467
1468 #[test]
1469 fn test_adaptation_network() {
1470 let network = AdaptationNetwork::<f32>::new(128, 256);
1471 assert!(network.is_ok());
1472
1473 let mut net = network.expect("AdaptationNetwork::new should succeed");
1474 let task = TaskBatch {
1475 id: "test".to_string(),
1476 difficulty: 0.5,
1477 complexity: 0.7,
1478 data_characteristics: 0.3,
1479 _phantom: std::marker::PhantomData,
1480 };
1481
1482 let context = net.encode_task_context(&task);
1483 assert!(context.is_ok());
1484 }
1485}