452 lines
15 KiB
Plaintext
452 lines
15 KiB
Plaintext
//! Comprehensive tests for MobileNet implementation
|
|
//!
|
|
//! Following Test-Driven Development (TDD) methodology
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use rtx_vision::architectures::{MobileNet, MobileNetConfig, MobileNetVariant};
|
|
use rtx_vision::{Device, Tensor};
|
|
|
|
fn get_test_device() -> Device {
|
|
Device::cpu()
|
|
}
|
|
|
|
#[test]
|
|
fn test_mobilenet_config_default() {
|
|
let config = MobileNetConfig::default();
|
|
assert!(matches!(config.variant, MobileNetVariant::V1));
|
|
assert_eq!(config.num_classes, 1000);
|
|
assert_eq!(config.width_multiplier, 1.0);
|
|
assert_eq!(config.resolution_multiplier, 1.0);
|
|
}
|
|
|
|
#[test]
|
|
fn test_mobilenet_config_variants() {
|
|
let config_v1 = MobileNetConfig::mobilenet_v1();
|
|
assert!(matches!(config_v1.variant, MobileNetVariant::MobileNetV1));
|
|
|
|
let config_v2 = MobileNetConfig::mobilenet_v2();
|
|
assert!(matches!(config_v2.variant, MobileNetVariant::MobileNetV2));
|
|
|
|
let config_v3_small = MobileNetConfig::mobilenet_v3_small();
|
|
assert!(matches!(config_v3_small.variant, MobileNetVariant::MobileNetV3Small));
|
|
|
|
let config_v3_large = MobileNetConfig::mobilenet_v3_large();
|
|
assert!(matches!(config_v3_large.variant, MobileNetVariant::MobileNetV3Large));
|
|
}
|
|
|
|
#[test]
|
|
fn test_mobilenet_config_builders() {
|
|
let config = MobileNetConfig::mobilenet_v2()
|
|
.with_num_classes(10)
|
|
.with_width_multiplier(0.5)
|
|
.with_dropout_rate(0.3)
|
|
.with_reduced_tail(true);
|
|
|
|
assert_eq!(config.num_classes, 10);
|
|
assert_eq!(config.width_multiplier, 0.5);
|
|
assert_eq!(config.dropout_rate, 0.3);
|
|
assert!(config.reduced_tail);
|
|
}
|
|
|
|
#[test]
|
|
fn test_mobilenet_v1_creation() {
|
|
let device = get_test_device();
|
|
let config = MobileNetConfig::mobilenet_v1();
|
|
let model = MobileNet::new(config, &device);
|
|
|
|
assert!(model.is_ok());
|
|
let model = model.unwrap();
|
|
assert_eq!(model.num_parameters(), 4_253_864);
|
|
}
|
|
|
|
#[test]
|
|
fn test_mobilenet_v2_creation() {
|
|
let device = get_test_device();
|
|
let config = MobileNetConfig::mobilenet_v2();
|
|
let model = MobileNet::new(config, &device);
|
|
|
|
assert!(model.is_ok());
|
|
let model = model.unwrap();
|
|
assert_eq!(model.num_parameters(), 3_504_872);
|
|
}
|
|
|
|
#[test]
|
|
fn test_mobilenet_v3_small_creation() {
|
|
let device = get_test_device();
|
|
let config = MobileNetConfig::mobilenet_v3_small();
|
|
let model = MobileNet::new(config, &device);
|
|
|
|
assert!(model.is_ok());
|
|
let model = model.unwrap();
|
|
assert_eq!(model.num_parameters(), 2_945_000);
|
|
}
|
|
|
|
#[test]
|
|
fn test_mobilenet_v3_large_creation() {
|
|
let device = get_test_device();
|
|
let config = MobileNetConfig::mobilenet_v3_large();
|
|
let model = MobileNet::new(config, &device);
|
|
|
|
assert!(model.is_ok());
|
|
let model = model.unwrap();
|
|
assert_eq!(model.num_parameters(), 5_483_000);
|
|
}
|
|
|
|
#[test]
|
|
fn test_mobilenet_v2_forward_pass() {
|
|
let device = get_test_device();
|
|
let config = MobileNetConfig::mobilenet_v2();
|
|
let model = MobileNet::new(config, &device).unwrap();
|
|
|
|
let input = Tensor::randn(&[1, 3, 224, 224], &device).unwrap();
|
|
let output = model.forward(&input);
|
|
|
|
assert!(output.is_ok());
|
|
let output = output.unwrap();
|
|
assert_eq!(output.shape().dims(), &[1, 1000]);
|
|
}
|
|
|
|
#[test]
|
|
fn test_mobilenet_batch_forward() {
|
|
let device = get_test_device();
|
|
let config = MobileNetConfig::mobilenet_v1();
|
|
let model = MobileNet::new(config, &device).unwrap();
|
|
|
|
let input = Tensor::randn(&[4, 3, 224, 224], &device).unwrap();
|
|
let output = model.forward(&input);
|
|
|
|
assert!(output.is_ok());
|
|
let output = output.unwrap();
|
|
assert_eq!(output.shape().dims(), &[4, 1000]);
|
|
}
|
|
|
|
#[test]
|
|
fn test_mobilenet_custom_classes() {
|
|
let device = get_test_device();
|
|
let config = MobileNetConfig::mobilenet_v2().with_num_classes(10);
|
|
let model = MobileNet::new(config, &device).unwrap();
|
|
|
|
let input = Tensor::randn(&[2, 3, 224, 224], &device).unwrap();
|
|
let output = model.forward(&input);
|
|
|
|
assert!(output.is_ok());
|
|
let output = output.unwrap();
|
|
assert_eq!(output.shape().dims(), &[2, 10]);
|
|
}
|
|
|
|
#[test]
|
|
fn test_mobilenet_width_multipliers() {
|
|
let device = get_test_device();
|
|
let width_multipliers = vec![0.25, 0.5, 0.75, 1.0, 1.25];
|
|
|
|
for width_mult in width_multipliers {
|
|
let config = MobileNetConfig::mobilenet_v2()
|
|
.with_width_multiplier(width_mult);
|
|
let model = MobileNet::new(config, &device);
|
|
|
|
assert!(model.is_ok(), "Failed with width multiplier {}", width_mult);
|
|
|
|
let model = model.unwrap();
|
|
let input = Tensor::randn(&[1, 3, 224, 224], &device).unwrap();
|
|
let output = model.forward(&input);
|
|
|
|
assert!(output.is_ok());
|
|
assert_eq!(output.unwrap().shape().dims(), &[1, 1000]);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_mobilenet_different_input_sizes() {
|
|
let device = get_test_device();
|
|
let config = MobileNetConfig::mobilenet_v2();
|
|
let model = MobileNet::new(config, &device).unwrap();
|
|
|
|
let sizes = vec![
|
|
(1, 3, 32, 32), // CIFAR-10 size
|
|
(1, 3, 128, 128), // Medium images
|
|
(1, 3, 224, 224), // Standard ImageNet
|
|
];
|
|
|
|
for (b, c, h, w) in sizes {
|
|
let input = Tensor::randn(&[b, c, h, w], &device).unwrap();
|
|
let output = model.forward(&input);
|
|
|
|
assert!(output.is_ok(), "Failed for input size {}x{}x{}x{}", b, c, h, w);
|
|
let output = output.unwrap();
|
|
assert_eq!(output.shape().dims(), &[b, 1000]);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_mobilenet_feature_extraction() {
|
|
let device = get_test_device();
|
|
let config = MobileNetConfig::mobilenet_v2();
|
|
let model = MobileNet::new(config, &device).unwrap();
|
|
|
|
let input = Tensor::randn(&[1, 3, 224, 224], &device).unwrap();
|
|
let features = model.extract_features(&input);
|
|
|
|
assert!(features.is_ok());
|
|
let features = features.unwrap();
|
|
let feature_dims = features.shape().dims();
|
|
// Features should be spatial size 1x1 after global average pooling
|
|
assert_eq!(feature_dims[2], 1);
|
|
assert_eq!(feature_dims[3], 1);
|
|
}
|
|
|
|
#[test]
|
|
fn test_mobilenet_edge_cases() {
|
|
let device = get_test_device();
|
|
|
|
// Test minimum viable input
|
|
let config = MobileNetConfig::mobilenet_v2()
|
|
.with_num_classes(1)
|
|
.with_width_multiplier(0.25);
|
|
let model = MobileNet::new(config, &device).unwrap();
|
|
|
|
let input = Tensor::randn(&[1, 3, 64, 64], &device).unwrap();
|
|
let output = model.forward(&input);
|
|
|
|
assert!(output.is_ok());
|
|
let output = output.unwrap();
|
|
assert_eq!(output.shape().dims(), &[1, 1]);
|
|
}
|
|
|
|
#[test]
|
|
fn test_mobilenet_deterministic_output() {
|
|
let device = get_test_device();
|
|
let config = MobileNetConfig::mobilenet_v1();
|
|
let model = MobileNet::new(config, &device).unwrap();
|
|
|
|
let input = Tensor::ones(&[1, 3, 224, 224], &device).unwrap();
|
|
let output1 = model.forward(&input).unwrap();
|
|
let output2 = model.forward(&input).unwrap();
|
|
|
|
// In mock implementation, we check they have the same shape
|
|
assert_eq!(output1.shape().dims(), output2.shape().dims());
|
|
}
|
|
|
|
#[test]
|
|
fn test_mobilenet_large_batch() {
|
|
let device = get_test_device();
|
|
let config = MobileNetConfig::mobilenet_v2();
|
|
let model = MobileNet::new(config, &device).unwrap();
|
|
|
|
let input = Tensor::randn(&[16, 3, 224, 224], &device).unwrap();
|
|
let output = model.forward(&input);
|
|
|
|
assert!(output.is_ok());
|
|
let output = output.unwrap();
|
|
assert_eq!(output.shape().dims(), &[16, 1000]);
|
|
}
|
|
|
|
#[test]
|
|
fn test_mobilenet_memory_consistency() {
|
|
let device = get_test_device();
|
|
let config = MobileNetConfig::mobilenet_v3_small();
|
|
let model = MobileNet::new(config, &device).unwrap();
|
|
|
|
for i in 1..=5 {
|
|
let input = Tensor::randn(&[2, 3, 224, 224], &device).unwrap();
|
|
let output = model.forward(&input);
|
|
assert!(output.is_ok(), "Forward pass {} failed", i);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_mobilenet_dropout_variations() {
|
|
let device = get_test_device();
|
|
let dropout_rates = vec![0.0, 0.1, 0.2, 0.5];
|
|
|
|
for dropout in dropout_rates {
|
|
let config = MobileNetConfig::mobilenet_v2()
|
|
.with_dropout_rate(dropout);
|
|
let model = MobileNet::new(config, &device);
|
|
assert!(model.is_ok(), "Failed with dropout rate {}", dropout);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_mobilenet_parameter_counting() {
|
|
let device = get_test_device();
|
|
|
|
let models = vec![
|
|
(MobileNetConfig::mobilenet_v1(), 4_253_864),
|
|
(MobileNetConfig::mobilenet_v2(), 3_504_872),
|
|
(MobileNetConfig::mobilenet_v3_small(), 2_945_000),
|
|
(MobileNetConfig::mobilenet_v3_large(), 5_483_000),
|
|
];
|
|
|
|
for (config, expected_params) in models {
|
|
let model = MobileNet::new(config, &device).unwrap();
|
|
assert_eq!(model.num_parameters(), expected_params);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_mobilenet_serialization() {
|
|
let config = MobileNetConfig::mobilenet_v2()
|
|
.with_num_classes(10)
|
|
.with_width_multiplier(0.75);
|
|
|
|
let serialized = serde_json::to_string(&config);
|
|
assert!(serialized.is_ok());
|
|
|
|
let deserialized: Result<MobileNetConfig, _> = serde_json::from_str(&serialized.unwrap());
|
|
assert!(deserialized.is_ok());
|
|
|
|
let deserialized = deserialized.unwrap();
|
|
assert!(matches!(deserialized.variant, MobileNetVariant::MobileNetV2));
|
|
assert_eq!(deserialized.num_classes, 10);
|
|
assert_eq!(deserialized.width_multiplier, 0.75);
|
|
}
|
|
|
|
#[test]
|
|
fn test_mobilenet_forward_consistency() {
|
|
let device = get_test_device();
|
|
let config = MobileNetConfig::mobilenet_v2();
|
|
let model = MobileNet::new(config, &device).unwrap();
|
|
|
|
let input = Tensor::zeros(&[1, 3, 224, 224], &device).unwrap();
|
|
|
|
let results: Vec<_> = (0..3)
|
|
.map(|_| model.forward(&input))
|
|
.collect();
|
|
|
|
assert!(results.iter().all(|r| r.is_ok()));
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod integration_tests {
|
|
use super::*;
|
|
use rtx_vision::architectures::{MobileNet, MobileNetConfig, MobileNetVariant};
|
|
use rtx_vision::{Device, Tensor};
|
|
|
|
#[test]
|
|
fn test_mobilenet_transfer_learning_setup() {
|
|
let device = Device::cpu();
|
|
let config = MobileNetConfig::mobilenet_v2()
|
|
.with_num_classes(10) // Fine-tune for CIFAR-10
|
|
.with_width_multiplier(0.5)
|
|
.with_dropout_rate(0.3);
|
|
|
|
let model = MobileNet::new(config, &device).unwrap();
|
|
|
|
let input = Tensor::randn(&[4, 3, 32, 32], &device).unwrap();
|
|
let output = model.forward(&input);
|
|
|
|
assert!(output.is_ok());
|
|
assert_eq!(output.unwrap().shape().dims(), &[4, 10]);
|
|
}
|
|
|
|
#[test]
|
|
fn test_mobilenet_efficiency_comparison() {
|
|
let device = Device::cpu();
|
|
|
|
// Compare parameter counts across variants
|
|
let variants = vec![
|
|
MobileNetVariant::MobileNetV1,
|
|
MobileNetVariant::MobileNetV2,
|
|
MobileNetVariant::MobileNetV3Small,
|
|
MobileNetVariant::MobileNetV3Large,
|
|
];
|
|
|
|
let mut param_counts = Vec::new();
|
|
|
|
for variant in variants {
|
|
let config = MobileNetConfig {
|
|
variant: variant.clone(),
|
|
..Default::default()
|
|
};
|
|
|
|
let model = MobileNet::new(config, &device).unwrap();
|
|
param_counts.push(model.num_parameters());
|
|
|
|
let input = Tensor::randn(&[2, 3, 224, 224], &device).unwrap();
|
|
let output = model.forward(&input);
|
|
|
|
assert!(output.is_ok(), "Failed for variant {:?}", variant);
|
|
assert_eq!(output.unwrap().shape().dims(), &[2, 1000]);
|
|
}
|
|
|
|
// Verify V3-Small has fewer parameters than others
|
|
assert!(param_counts[2] < param_counts[0]); // V3-Small < V1
|
|
assert!(param_counts[2] < param_counts[1]); // V3-Small < V2
|
|
}
|
|
|
|
#[test]
|
|
fn test_mobilenet_width_scaling() {
|
|
let device = Device::cpu();
|
|
|
|
let width_multipliers = vec![0.5, 1.0, 1.25];
|
|
let mut outputs = Vec::new();
|
|
|
|
for width_mult in width_multipliers {
|
|
let config = MobileNetConfig::mobilenet_v2()
|
|
.with_width_multiplier(width_mult);
|
|
let model = MobileNet::new(config, &device).unwrap();
|
|
|
|
let input = Tensor::randn(&[1, 3, 224, 224], &device).unwrap();
|
|
let features = model.extract_features(&input).unwrap();
|
|
outputs.push(features);
|
|
}
|
|
|
|
// All should produce valid outputs
|
|
assert_eq!(outputs.len(), 3);
|
|
}
|
|
|
|
#[test]
|
|
fn test_mobilenet_depthwise_separable_convolutions() {
|
|
let device = Device::cpu();
|
|
let config = MobileNetConfig::mobilenet_v1(); // Uses depthwise separable convs
|
|
|
|
let model = MobileNet::new(config, &device).unwrap();
|
|
let input = Tensor::randn(&[1, 3, 224, 224], &device).unwrap();
|
|
|
|
let output = model.forward(&input).unwrap();
|
|
assert_eq!(output.shape().dims(), &[1, 1000]);
|
|
|
|
// Test feature extraction
|
|
let features = model.extract_features(&input).unwrap();
|
|
assert!(features.shape().dims()[1] > 0); // Should have channels
|
|
}
|
|
|
|
#[test]
|
|
fn test_mobilenet_inverted_residuals() {
|
|
let device = Device::cpu();
|
|
let config = MobileNetConfig::mobilenet_v2(); // Uses inverted residuals
|
|
|
|
let model = MobileNet::new(config, &device).unwrap();
|
|
let input = Tensor::randn(&[2, 3, 224, 224], &device).unwrap();
|
|
|
|
let output = model.forward(&input).unwrap();
|
|
assert_eq!(output.shape().dims(), &[2, 1000]);
|
|
}
|
|
|
|
#[test]
|
|
fn test_mobilenet_computational_efficiency() {
|
|
use std::time::Instant;
|
|
|
|
let device = Device::cpu();
|
|
|
|
let configs = vec![
|
|
MobileNetConfig::mobilenet_v1(),
|
|
MobileNetConfig::mobilenet_v2(),
|
|
MobileNetConfig::mobilenet_v3_small(),
|
|
];
|
|
|
|
for config in configs {
|
|
let model = MobileNet::new(config, &device).unwrap();
|
|
let input = Tensor::randn(&[1, 3, 224, 224], &device).unwrap();
|
|
|
|
let start = Instant::now();
|
|
let _output = model.forward(&input).unwrap();
|
|
let _duration = start.elapsed();
|
|
|
|
// In a real implementation, we would assert on performance characteristics
|
|
// MobileNets should be faster than standard CNNs
|
|
}
|
|
}
|
|
} |