use crate::autodiff::optimizers::Optimizer;
use crate::error::{MLError, Result};
use crate::optimization::OptimizationMethod;
use crate::qnn::{QNNLayerType, QuantumNeuralNetwork};
use quantrs2_circuit::builder::{Circuit, Simulator};
use quantrs2_core::gate::{
single::{RotationX, RotationY, RotationZ},
GateOp,
};
use quantrs2_sim::statevector::StateVectorSimulator;
use scirs2_core::ndarray::{s, Array1, Array2, Array3, Axis};
use scirs2_core::random::prelude::*;
use std::collections::HashMap;
#[derive(Debug, Clone, Copy)]
pub enum MetaLearningAlgorithm {
MAML {
inner_steps: usize,
inner_lr: f64,
first_order: bool,
},
Reptile { inner_steps: usize, inner_lr: f64 },
ProtoMAML {
inner_steps: usize,
inner_lr: f64,
proto_weight: f64,
},
MetaSGD { inner_steps: usize },
ANIL { inner_steps: usize, inner_lr: f64 },
}
#[derive(Debug, Clone)]
pub struct MetaTask {
pub id: String,
pub train_data: Vec<(Array1<f64>, usize)>,
pub test_data: Vec<(Array1<f64>, usize)>,
pub num_classes: usize,
pub metadata: HashMap<String, f64>,
}
pub struct QuantumMetaLearner {
algorithm: MetaLearningAlgorithm,
model: QuantumNeuralNetwork,
meta_params: Array1<f64>,
per_param_lr: Option<Array1<f64>>,
task_embeddings: HashMap<String, Array1<f64>>,
history: MetaLearningHistory,
}
#[derive(Debug, Clone)]
pub struct MetaLearningHistory {
pub meta_train_losses: Vec<f64>,
pub meta_val_accuracies: Vec<f64>,
pub task_performance: HashMap<String, Vec<f64>>,
}
impl QuantumMetaLearner {
pub fn new(algorithm: MetaLearningAlgorithm, model: QuantumNeuralNetwork) -> Self {
let num_params = model.parameters.len();
let meta_params = model.parameters.clone();
let per_param_lr = match algorithm {
MetaLearningAlgorithm::MetaSGD { .. } => Some(Array1::from_elem(num_params, 0.01)),
_ => None,
};
Self {
algorithm,
model,
meta_params,
per_param_lr,
task_embeddings: HashMap::new(),
history: MetaLearningHistory {
meta_train_losses: Vec::new(),
meta_val_accuracies: Vec::new(),
task_performance: HashMap::new(),
},
}
}
pub fn meta_train(
&mut self,
tasks: &[MetaTask],
meta_optimizer: &mut dyn Optimizer,
meta_epochs: usize,
tasks_per_batch: usize,
) -> Result<()> {
println!("Starting meta-training with {} tasks...", tasks.len());
for epoch in 0..meta_epochs {
let mut epoch_loss = 0.0;
let mut epoch_acc = 0.0;
let task_batch = self.sample_task_batch(tasks, tasks_per_batch);
match self.algorithm {
MetaLearningAlgorithm::MAML { .. } => {
let (loss, acc) = self.maml_update(&task_batch, meta_optimizer)?;
epoch_loss += loss;
epoch_acc += acc;
}
MetaLearningAlgorithm::Reptile { .. } => {
let (loss, acc) = self.reptile_update(&task_batch, meta_optimizer)?;
epoch_loss += loss;
epoch_acc += acc;
}
MetaLearningAlgorithm::ProtoMAML { .. } => {
let (loss, acc) = self.protomaml_update(&task_batch, meta_optimizer)?;
epoch_loss += loss;
epoch_acc += acc;
}
MetaLearningAlgorithm::MetaSGD { .. } => {
let (loss, acc) = self.metasgd_update(&task_batch, meta_optimizer)?;
epoch_loss += loss;
epoch_acc += acc;
}
MetaLearningAlgorithm::ANIL { .. } => {
let (loss, acc) = self.anil_update(&task_batch, meta_optimizer)?;
epoch_loss += loss;
epoch_acc += acc;
}
}
self.history.meta_train_losses.push(epoch_loss);
self.history.meta_val_accuracies.push(epoch_acc);
if epoch % 10 == 0 {
println!(
"Epoch {}: Loss = {:.4}, Accuracy = {:.2}%",
epoch,
epoch_loss,
epoch_acc * 100.0
);
}
}
Ok(())
}
fn maml_update(
&mut self,
tasks: &[MetaTask],
optimizer: &mut dyn Optimizer,
) -> Result<(f64, f64)> {
let (inner_steps, inner_lr, first_order) = match self.algorithm {
MetaLearningAlgorithm::MAML {
inner_steps,
inner_lr,
first_order,
} => (inner_steps, inner_lr, first_order),
_ => unreachable!(),
};
let mut total_loss = 0.0;
let mut total_acc = 0.0;
let mut meta_gradients = Array1::zeros(self.meta_params.len());
for task in tasks {
let mut task_params = self.meta_params.clone();
for _ in 0..inner_steps {
let grad = self.compute_task_gradient(&task.train_data, &task_params)?;
task_params = task_params - inner_lr * &grad;
}
let (query_loss, query_acc) = self.evaluate_task(&task.test_data, &task_params)?;
total_loss += query_loss;
total_acc += query_acc;
if !first_order {
let meta_grad = self.compute_maml_gradient(task, &task_params, inner_lr)?;
meta_gradients = meta_gradients + meta_grad;
} else {
let grad = self.compute_task_gradient(&task.test_data, &task_params)?;
meta_gradients = meta_gradients + grad;
}
}
meta_gradients = meta_gradients / tasks.len() as f64;
self.meta_params = self.meta_params.clone() - 0.001 * &meta_gradients;
Ok((
total_loss / tasks.len() as f64,
total_acc / tasks.len() as f64,
))
}
fn reptile_update(
&mut self,
tasks: &[MetaTask],
optimizer: &mut dyn Optimizer,
) -> Result<(f64, f64)> {
let (inner_steps, inner_lr) = match self.algorithm {
MetaLearningAlgorithm::Reptile {
inner_steps,
inner_lr,
} => (inner_steps, inner_lr),
_ => unreachable!(),
};
let mut total_loss = 0.0;
let mut total_acc = 0.0;
let epsilon = 0.1;
for task in tasks {
let mut task_params = self.meta_params.clone();
for _ in 0..inner_steps {
let grad = self.compute_task_gradient(&task.train_data, &task_params)?;
task_params = task_params - inner_lr * &grad;
}
let (loss, acc) = self.evaluate_task(&task.test_data, &task_params)?;
total_loss += loss;
total_acc += acc;
let direction = &task_params - &self.meta_params;
self.meta_params = &self.meta_params + epsilon * &direction;
}
Ok((
total_loss / tasks.len() as f64,
total_acc / tasks.len() as f64,
))
}
fn protomaml_update(
&mut self,
tasks: &[MetaTask],
optimizer: &mut dyn Optimizer,
) -> Result<(f64, f64)> {
let (inner_steps, inner_lr, proto_weight) = match self.algorithm {
MetaLearningAlgorithm::ProtoMAML {
inner_steps,
inner_lr,
proto_weight,
} => (inner_steps, inner_lr, proto_weight),
_ => unreachable!(),
};
let mut total_loss = 0.0;
let mut total_acc = 0.0;
for task in tasks {
let prototypes = self.compute_prototypes(&task.train_data, task.num_classes)?;
let mut task_params = self.meta_params.clone();
for _ in 0..inner_steps {
let grad = self.compute_task_gradient(&task.train_data, &task_params)?;
let proto_reg =
self.prototype_regularization(&task.train_data, &prototypes, &task_params)?;
task_params = task_params - inner_lr * (&grad + proto_weight * &proto_reg);
}
let (loss, acc) =
self.evaluate_with_prototypes(&task.test_data, &prototypes, &task_params)?;
total_loss += loss;
total_acc += acc;
}
Ok((
total_loss / tasks.len() as f64,
total_acc / tasks.len() as f64,
))
}
fn metasgd_update(
&mut self,
tasks: &[MetaTask],
optimizer: &mut dyn Optimizer,
) -> Result<(f64, f64)> {
let inner_steps = match self.algorithm {
MetaLearningAlgorithm::MetaSGD { inner_steps } => inner_steps,
_ => unreachable!(),
};
let mut total_loss = 0.0;
let mut total_acc = 0.0;
let mut meta_lr_gradients = Array1::zeros(self.meta_params.len());
for task in tasks {
let mut task_params = self.meta_params.clone();
for _ in 0..inner_steps {
let grad = self.compute_task_gradient(&task.train_data, &task_params)?;
let lr = self
.per_param_lr
.as_ref()
.expect("per_param_lr must be initialized for MetaSGD");
task_params = task_params - lr * &grad;
}
let (loss, acc) = self.evaluate_task(&task.test_data, &task_params)?;
total_loss += loss;
total_acc += acc;
let lr_grad = self.compute_lr_gradient(task, &task_params)?;
meta_lr_gradients = meta_lr_gradients + lr_grad;
}
if let Some(ref mut lr) = self.per_param_lr {
*lr = lr.clone() - &(0.001 * &meta_lr_gradients / tasks.len() as f64);
}
Ok((
total_loss / tasks.len() as f64,
total_acc / tasks.len() as f64,
))
}
fn anil_update(
&mut self,
tasks: &[MetaTask],
optimizer: &mut dyn Optimizer,
) -> Result<(f64, f64)> {
let (inner_steps, inner_lr) = match self.algorithm {
MetaLearningAlgorithm::ANIL {
inner_steps,
inner_lr,
} => (inner_steps, inner_lr),
_ => unreachable!(),
};
let num_params = self.meta_params.len();
let final_layer_start = (num_params * 3) / 4;
let mut total_loss = 0.0;
let mut total_acc = 0.0;
for task in tasks {
let mut task_params = self.meta_params.clone();
for _ in 0..inner_steps {
let grad = self.compute_task_gradient(&task.train_data, &task_params)?;
for i in final_layer_start..num_params {
task_params[i] -= inner_lr * grad[i];
}
}
let (loss, acc) = self.evaluate_task(&task.test_data, &task_params)?;
total_loss += loss;
total_acc += acc;
}
Ok((
total_loss / tasks.len() as f64,
total_acc / tasks.len() as f64,
))
}
fn compute_task_gradient(
&self,
data: &[(Array1<f64>, usize)],
params: &Array1<f64>,
) -> Result<Array1<f64>> {
Ok(Array1::zeros(params.len()))
}
fn evaluate_task(
&self,
data: &[(Array1<f64>, usize)],
params: &Array1<f64>,
) -> Result<(f64, f64)> {
let loss = 0.5 + 0.5 * thread_rng().random::<f64>();
let acc = 0.5 + 0.3 * thread_rng().random::<f64>();
Ok((loss, acc))
}
fn compute_maml_gradient(
&self,
task: &MetaTask,
adapted_params: &Array1<f64>,
inner_lr: f64,
) -> Result<Array1<f64>> {
Ok(Array1::zeros(self.meta_params.len()))
}
fn compute_prototypes(
&self,
data: &[(Array1<f64>, usize)],
num_classes: usize,
) -> Result<Vec<Array1<f64>>> {
let feature_dim = 16; let mut prototypes = vec![Array1::zeros(feature_dim); num_classes];
let mut counts = vec![0; num_classes];
for (x, label) in data {
counts[*label] += 1;
}
Ok(prototypes)
}
fn prototype_regularization(
&self,
data: &[(Array1<f64>, usize)],
prototypes: &[Array1<f64>],
params: &Array1<f64>,
) -> Result<Array1<f64>> {
Ok(Array1::zeros(params.len()))
}
fn evaluate_with_prototypes(
&self,
data: &[(Array1<f64>, usize)],
prototypes: &[Array1<f64>],
params: &Array1<f64>,
) -> Result<(f64, f64)> {
Ok((0.3, 0.7))
}
fn compute_lr_gradient(
&self,
task: &MetaTask,
adapted_params: &Array1<f64>,
) -> Result<Array1<f64>> {
Ok(Array1::zeros(self.meta_params.len()))
}
fn sample_task_batch(&self, tasks: &[MetaTask], batch_size: usize) -> Vec<MetaTask> {
let mut batch = Vec::new();
let mut rng = thread_rng();
for _ in 0..batch_size.min(tasks.len()) {
let idx = rng.random_range(0..tasks.len());
batch.push(tasks[idx].clone());
}
batch
}
pub fn adapt_to_task(&mut self, task: &MetaTask) -> Result<Array1<f64>> {
let adapted_params = match self.algorithm {
MetaLearningAlgorithm::MAML {
inner_steps,
inner_lr,
..
}
| MetaLearningAlgorithm::Reptile {
inner_steps,
inner_lr,
}
| MetaLearningAlgorithm::ProtoMAML {
inner_steps,
inner_lr,
..
}
| MetaLearningAlgorithm::ANIL {
inner_steps,
inner_lr,
} => {
let mut params = self.meta_params.clone();
for _ in 0..inner_steps {
let grad = self.compute_task_gradient(&task.train_data, ¶ms)?;
params = params - inner_lr * &grad;
}
params
}
MetaLearningAlgorithm::MetaSGD { inner_steps } => {
let mut params = self.meta_params.clone();
let lr = self
.per_param_lr
.as_ref()
.expect("per_param_lr must be initialized for MetaSGD");
for _ in 0..inner_steps {
let grad = self.compute_task_gradient(&task.train_data, ¶ms)?;
params = params - lr * &grad;
}
params
}
};
Ok(adapted_params)
}
pub fn get_task_embedding(&self, task_id: &str) -> Option<&Array1<f64>> {
self.task_embeddings.get(task_id)
}
pub fn meta_params(&self) -> &Array1<f64> {
&self.meta_params
}
pub fn per_param_lr(&self) -> Option<&Array1<f64>> {
self.per_param_lr.as_ref()
}
}
pub struct ContinualMetaLearner {
meta_learner: QuantumMetaLearner,
memory_buffer: Vec<MetaTask>,
memory_capacity: usize,
replay_ratio: f64,
}
impl ContinualMetaLearner {
pub fn new(
meta_learner: QuantumMetaLearner,
memory_capacity: usize,
replay_ratio: f64,
) -> Self {
Self {
meta_learner,
memory_buffer: Vec::new(),
memory_capacity,
replay_ratio,
}
}
pub fn learn_task(&mut self, new_task: MetaTask) -> Result<()> {
if self.memory_buffer.len() < self.memory_capacity {
self.memory_buffer.push(new_task.clone());
} else {
let idx = fastrand::usize(0..self.memory_buffer.len());
self.memory_buffer[idx] = new_task.clone();
}
let num_replay = (self.memory_buffer.len() as f64 * self.replay_ratio) as usize;
let mut task_batch = vec![new_task];
for _ in 0..num_replay {
let idx = fastrand::usize(0..self.memory_buffer.len());
task_batch.push(self.memory_buffer[idx].clone());
}
let mut dummy_optimizer = crate::autodiff::optimizers::Adam::new(0.001);
self.meta_learner
.meta_train(&task_batch, &mut dummy_optimizer, 10, task_batch.len())?;
Ok(())
}
pub fn memory_buffer_len(&self) -> usize {
self.memory_buffer.len()
}
}
pub struct TaskGenerator {
feature_dim: usize,
num_classes: usize,
task_params: HashMap<String, f64>,
}
impl TaskGenerator {
pub fn new(feature_dim: usize, num_classes: usize) -> Self {
Self {
feature_dim,
num_classes,
task_params: HashMap::new(),
}
}
pub fn generate_sinusoid_task(&self, num_samples: usize) -> MetaTask {
let amplitude = 0.1 + 4.9 * thread_rng().random::<f64>();
let phase = 2.0 * std::f64::consts::PI * thread_rng().random::<f64>();
let mut train_data = Vec::new();
let mut test_data = Vec::new();
for i in 0..num_samples {
let x = -5.0 + 10.0 * thread_rng().random::<f64>();
let y = amplitude * (x + phase).sin();
let input = Array1::from_vec(vec![x]);
let label = if y > 0.0 { 1 } else { 0 };
if i < num_samples / 2 {
train_data.push((input, label));
} else {
test_data.push((input, label));
}
}
MetaTask {
id: format!("sin_a{:.2}_p{:.2}", amplitude, phase),
train_data,
test_data,
num_classes: 2,
metadata: vec![
("amplitude".to_string(), amplitude),
("phase".to_string(), phase),
]
.into_iter()
.collect(),
}
}
pub fn generate_rotation_task(&self, num_samples: usize) -> MetaTask {
let angle = 2.0 * std::f64::consts::PI * thread_rng().random::<f64>();
let cos_a = angle.cos();
let sin_a = angle.sin();
let mut train_data = Vec::new();
let mut test_data = Vec::new();
for i in 0..num_samples {
let mut features = Array1::zeros(self.feature_dim);
let label = i % self.num_classes;
for j in 0..self.feature_dim {
features[j] = if j % self.num_classes == label {
1.0
} else {
0.0
};
features[j] += 0.1 * thread_rng().random::<f64>();
}
if self.feature_dim >= 2 {
let x = features[0];
let y = features[1];
features[0] = cos_a * x - sin_a * y;
features[1] = sin_a * x + cos_a * y;
}
if i < num_samples / 2 {
train_data.push((features, label));
} else {
test_data.push((features, label));
}
}
MetaTask {
id: format!("rot_{:.2}", angle),
train_data,
test_data,
num_classes: self.num_classes,
metadata: vec![("rotation_angle".to_string(), angle)]
.into_iter()
.collect(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::autodiff::optimizers::Adam;
use crate::qnn::QNNLayerType;
#[test]
fn test_task_generator() {
let generator = TaskGenerator::new(4, 2);
let sin_task = generator.generate_sinusoid_task(20);
assert_eq!(sin_task.train_data.len(), 10);
assert_eq!(sin_task.test_data.len(), 10);
let rot_task = generator.generate_rotation_task(30);
assert_eq!(rot_task.train_data.len(), 15);
assert_eq!(rot_task.test_data.len(), 15);
}
#[test]
fn test_meta_learner_creation() {
let layers = vec![
QNNLayerType::EncodingLayer { num_features: 4 },
QNNLayerType::VariationalLayer { num_params: 8 },
QNNLayerType::MeasurementLayer {
measurement_basis: "computational".to_string(),
},
];
let qnn = QuantumNeuralNetwork::new(layers, 4, 4, 2).expect("Failed to create QNN");
let maml_algo = MetaLearningAlgorithm::MAML {
inner_steps: 5,
inner_lr: 0.01,
first_order: true,
};
let meta_learner = QuantumMetaLearner::new(maml_algo, qnn);
assert!(meta_learner.per_param_lr.is_none());
let layers2 = vec![
QNNLayerType::EncodingLayer { num_features: 4 },
QNNLayerType::VariationalLayer { num_params: 8 },
];
let qnn2 =
QuantumNeuralNetwork::new(layers2, 4, 4, 2).expect("Failed to create QNN for Meta-SGD");
let metasgd_algo = MetaLearningAlgorithm::MetaSGD { inner_steps: 3 };
let meta_sgd = QuantumMetaLearner::new(metasgd_algo, qnn2);
assert!(meta_sgd.per_param_lr.is_some());
}
#[test]
fn test_task_adaptation() {
let layers = vec![
QNNLayerType::EncodingLayer { num_features: 2 },
QNNLayerType::VariationalLayer { num_params: 6 },
];
let qnn = QuantumNeuralNetwork::new(layers, 4, 2, 2).expect("Failed to create QNN");
let algo = MetaLearningAlgorithm::Reptile {
inner_steps: 5,
inner_lr: 0.01,
};
let mut meta_learner = QuantumMetaLearner::new(algo, qnn);
let generator = TaskGenerator::new(2, 2);
let task = generator.generate_rotation_task(20);
let adapted_params = meta_learner
.adapt_to_task(&task)
.expect("Task adaptation should succeed");
assert_eq!(adapted_params.len(), meta_learner.meta_params.len());
}
}