380 lines
10 KiB
Rust
380 lines
10 KiB
Rust
#![cfg(feature = "disabled_tests")]
|
|
|
|
use rtx_automeasure::{AutoMLResult, TaskType};
|
|
use rtx_tensor::{Device, Tensor};
|
|
|
|
// Note: This test file is a placeholder. The transfer_learning module
|
|
// has a complex API that would require significant implementation.
|
|
// These basic tests verify conceptual understanding.
|
|
|
|
#[tokio::test]
|
|
async fn test_placeholder() -> AutoMLResult<()> {
|
|
// Placeholder test to ensure the test file compiles
|
|
let device = Device::cpu();
|
|
let _x = Tensor::randn(&[10, 5], &device)?;
|
|
Ok(())
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_transfer_learning_concepts() -> AutoMLResult<()> {
|
|
let device = Device::cpu();
|
|
|
|
// Source domain data (pre-trained on large dataset)
|
|
let x_source = Tensor::randn(&[500, 20], &device)?;
|
|
let y_source = Tensor::zeros(&[500], &device)?;
|
|
|
|
// Target domain data (smaller dataset, related task)
|
|
let x_target = Tensor::randn(&[50, 20], &device)?;
|
|
let y_target = Tensor::zeros(&[50], &device)?;
|
|
|
|
assert_eq!(x_source.shape()[1], x_target.shape()[1]); // Same feature dimension
|
|
assert!(x_source.shape()[0] > x_target.shape()[0]); // Source has more data
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_domain_adaptation_concepts() {
|
|
// Test domain similarity calculation concepts
|
|
|
|
struct DomainCharacteristics {
|
|
n_samples: usize,
|
|
n_features: usize,
|
|
task_type: TaskType,
|
|
}
|
|
|
|
let source = DomainCharacteristics {
|
|
n_samples: 1000,
|
|
n_features: 784,
|
|
task_type: TaskType::Classification,
|
|
};
|
|
|
|
let target = DomainCharacteristics {
|
|
n_samples: 100,
|
|
n_features: 784,
|
|
task_type: TaskType::Classification,
|
|
};
|
|
|
|
// Similarity score based on task type and feature count
|
|
let task_similarity = if source.task_type == target.task_type {
|
|
1.0
|
|
} else {
|
|
0.0
|
|
};
|
|
let feature_similarity = if source.n_features == target.n_features {
|
|
1.0
|
|
} else {
|
|
0.0
|
|
};
|
|
|
|
assert_eq!(task_similarity, 1.0);
|
|
assert_eq!(feature_similarity, 1.0);
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_knowledge_transfer_methods() {
|
|
// Test different knowledge transfer strategies
|
|
|
|
enum TransferMethod {
|
|
FeatureExtraction, // Freeze early layers, train final layers
|
|
FineTuning, // Train all layers with small learning rate
|
|
DomainAdaptation, // Align source and target distributions
|
|
MultiTask, // Joint training on related tasks
|
|
}
|
|
|
|
let methods = vec![
|
|
TransferMethod::FeatureExtraction,
|
|
TransferMethod::FineTuning,
|
|
TransferMethod::DomainAdaptation,
|
|
TransferMethod::MultiTask,
|
|
];
|
|
|
|
assert_eq!(methods.len(), 4);
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_layer_freezing_concept() {
|
|
// Test layer freezing strategy for transfer learning
|
|
|
|
struct Layer {
|
|
name: String,
|
|
is_frozen: bool,
|
|
}
|
|
|
|
let mut layers = vec![
|
|
Layer {
|
|
name: "input".to_string(),
|
|
is_frozen: true,
|
|
},
|
|
Layer {
|
|
name: "hidden1".to_string(),
|
|
is_frozen: true,
|
|
},
|
|
Layer {
|
|
name: "hidden2".to_string(),
|
|
is_frozen: true,
|
|
},
|
|
Layer {
|
|
name: "hidden3".to_string(),
|
|
is_frozen: false,
|
|
},
|
|
Layer {
|
|
name: "output".to_string(),
|
|
is_frozen: false,
|
|
},
|
|
];
|
|
|
|
// Feature extraction: freeze early layers
|
|
let frozen_count = layers.iter().filter(|l| l.is_frozen).count();
|
|
let trainable_count = layers.iter().filter(|l| !l.is_frozen).count();
|
|
|
|
assert_eq!(frozen_count, 3);
|
|
assert_eq!(trainable_count, 2);
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_learning_rate_scaling() {
|
|
// Test learning rate adjustment for fine-tuning
|
|
|
|
let base_lr = 0.01;
|
|
|
|
// Different learning rates for different layer groups
|
|
let frozen_layer_lr = 0.0;
|
|
let middle_layer_lr = base_lr * 0.1; // Reduced for pre-trained layers
|
|
let new_layer_lr = base_lr; // Full rate for new layers
|
|
|
|
assert_eq!(frozen_layer_lr, 0.0);
|
|
assert!(middle_layer_lr < new_layer_lr);
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_domain_discrepancy_concepts() {
|
|
// Test Maximum Mean Discrepancy (MMD) concept
|
|
|
|
fn calculate_kernel_similarity(x1: f64, x2: f64, gamma: f64) -> f64 {
|
|
(-(x1 - x2).powi(2) * gamma).exp()
|
|
}
|
|
|
|
// Sample features from source and target domains
|
|
let source_features = vec![1.0, 2.0, 3.0];
|
|
let target_features = vec![1.1, 2.2, 3.1];
|
|
|
|
let gamma = 1.0;
|
|
let mut total_similarity = 0.0;
|
|
|
|
for (&sf, &tf) in source_features.iter().zip(target_features.iter()) {
|
|
total_similarity += calculate_kernel_similarity(sf, tf, gamma);
|
|
}
|
|
|
|
let avg_similarity = total_similarity / source_features.len() as f64;
|
|
assert!(avg_similarity > 0.5); // High similarity indicates related domains
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_source_selection_strategy() {
|
|
// Test selecting best source domain for transfer
|
|
|
|
struct SourceDomain {
|
|
id: String,
|
|
similarity_score: f64,
|
|
performance_score: f64,
|
|
}
|
|
|
|
let sources = vec![
|
|
SourceDomain {
|
|
id: "source1".to_string(),
|
|
similarity_score: 0.9,
|
|
performance_score: 0.85,
|
|
},
|
|
SourceDomain {
|
|
id: "source2".to_string(),
|
|
similarity_score: 0.7,
|
|
performance_score: 0.95,
|
|
},
|
|
SourceDomain {
|
|
id: "source3".to_string(),
|
|
similarity_score: 0.8,
|
|
performance_score: 0.90,
|
|
},
|
|
];
|
|
|
|
// Combined score: weighted sum of similarity and performance
|
|
let mut best_source = &sources[0];
|
|
let mut best_score = 0.0;
|
|
|
|
for source in &sources {
|
|
let combined_score = source.similarity_score * 0.6 + source.performance_score * 0.4;
|
|
if combined_score > best_score {
|
|
best_score = combined_score;
|
|
best_source = source;
|
|
}
|
|
}
|
|
|
|
assert_eq!(best_source.id, "source1");
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_progressive_fine_tuning() {
|
|
// Test progressive unfreezing strategy
|
|
|
|
struct TrainingPhase {
|
|
phase: u32,
|
|
unfrozen_layers: Vec<usize>,
|
|
learning_rate: f64,
|
|
}
|
|
|
|
let phases = vec![
|
|
TrainingPhase {
|
|
phase: 1,
|
|
unfrozen_layers: vec![4], // Only last layer
|
|
learning_rate: 0.01,
|
|
},
|
|
TrainingPhase {
|
|
phase: 2,
|
|
unfrozen_layers: vec![3, 4], // Last two layers
|
|
learning_rate: 0.005,
|
|
},
|
|
TrainingPhase {
|
|
phase: 3,
|
|
unfrozen_layers: vec![2, 3, 4], // Last three layers
|
|
learning_rate: 0.001,
|
|
},
|
|
];
|
|
|
|
// Verify progressive unfreezing and learning rate decay
|
|
for i in 1..phases.len() {
|
|
assert!(phases[i].unfrozen_layers.len() > phases[i - 1].unfrozen_layers.len());
|
|
assert!(phases[i].learning_rate < phases[i - 1].learning_rate);
|
|
}
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_task_relatedness_metric() {
|
|
// Test measuring task relatedness
|
|
|
|
fn task_overlap(source_labels: &[i32], target_labels: &[i32]) -> f64 {
|
|
let source_set: std::collections::HashSet<_> = source_labels.iter().collect();
|
|
let target_set: std::collections::HashSet<_> = target_labels.iter().collect();
|
|
|
|
let intersection: std::collections::HashSet<_> =
|
|
source_set.intersection(&target_set).collect();
|
|
|
|
intersection.len() as f64 / source_set.len().max(target_set.len()) as f64
|
|
}
|
|
|
|
let source_labels = vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; // 10 classes
|
|
let target_labels = vec![0, 1, 2, 3, 4]; // 5 classes (subset)
|
|
|
|
let overlap = task_overlap(&source_labels, &target_labels);
|
|
assert_eq!(overlap, 0.5); // 50% overlap
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_few_shot_learning_concepts() -> AutoMLResult<()> {
|
|
let device = Device::cpu();
|
|
|
|
// Few-shot scenario: very small target dataset
|
|
let n_shots = 5; // 5 examples per class
|
|
let n_classes = 3;
|
|
let n_samples = n_shots * n_classes;
|
|
|
|
let x_target = Tensor::randn(&[n_samples, 20], &device)?;
|
|
let y_target = Tensor::zeros(&[n_samples], &device)?;
|
|
|
|
assert_eq!(x_target.shape()[0], 15);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_zero_shot_learning_concepts() {
|
|
// Zero-shot learning: no target examples, only class descriptions
|
|
|
|
struct ClassDescription {
|
|
class_name: String,
|
|
attributes: Vec<String>,
|
|
}
|
|
|
|
let source_classes = vec![
|
|
ClassDescription {
|
|
class_name: "cat".to_string(),
|
|
attributes: vec!["furry".to_string(), "four_legs".to_string()],
|
|
},
|
|
ClassDescription {
|
|
class_name: "dog".to_string(),
|
|
attributes: vec!["furry".to_string(), "four_legs".to_string()],
|
|
},
|
|
];
|
|
|
|
let target_class = ClassDescription {
|
|
class_name: "fox".to_string(),
|
|
attributes: vec!["furry".to_string(), "four_legs".to_string()],
|
|
};
|
|
|
|
// Similarity based on shared attributes
|
|
let shared_attrs: Vec<_> = source_classes[0]
|
|
.attributes
|
|
.iter()
|
|
.filter(|attr| target_class.attributes.contains(attr))
|
|
.collect();
|
|
|
|
assert!(!shared_attrs.is_empty());
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_negative_transfer_detection() {
|
|
// Test detecting when transfer learning hurts performance
|
|
|
|
struct TransferResult {
|
|
baseline_score: f64, // Training from scratch
|
|
transfer_score: f64, // With transfer learning
|
|
}
|
|
|
|
let results = vec![
|
|
TransferResult {
|
|
baseline_score: 0.85,
|
|
transfer_score: 0.90, // Positive transfer
|
|
},
|
|
TransferResult {
|
|
baseline_score: 0.80,
|
|
transfer_score: 0.75, // Negative transfer!
|
|
},
|
|
];
|
|
|
|
let negative_transfer = results
|
|
.iter()
|
|
.filter(|r| r.transfer_score < r.baseline_score)
|
|
.count();
|
|
|
|
assert_eq!(negative_transfer, 1);
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_multi_source_transfer() {
|
|
// Test transferring from multiple source domains
|
|
|
|
struct SourceContribution {
|
|
source_id: String,
|
|
weight: f64,
|
|
}
|
|
|
|
let contributions = vec![
|
|
SourceContribution {
|
|
source_id: "source1".to_string(),
|
|
weight: 0.5,
|
|
},
|
|
SourceContribution {
|
|
source_id: "source2".to_string(),
|
|
weight: 0.3,
|
|
},
|
|
SourceContribution {
|
|
source_id: "source3".to_string(),
|
|
weight: 0.2,
|
|
},
|
|
];
|
|
|
|
// Weights should sum to 1.0
|
|
let total_weight: f64 = contributions.iter().map(|c| c.weight).sum();
|
|
assert!((total_weight - 1.0).abs() < 1e-6);
|
|
}
|