461 lines
14 KiB
Rust
461 lines
14 KiB
Rust
//! Comprehensive tests for ResNet implementation
|
|
//!
|
|
//! Following Test-Driven Development (TDD) methodology
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use rtx_vision::architectures::{ResNet, ResNetConfig, ResNetVariant};
|
|
use rtx_vision::{Device, Tensor};
|
|
|
|
fn get_test_device() -> Device {
|
|
Device::cpu()
|
|
}
|
|
|
|
#[test]
|
|
fn test_resnet_config_default() {
|
|
let config = ResNetConfig::default();
|
|
assert!(matches!(config.variant, ResNetVariant::ResNet50));
|
|
assert_eq!(config.num_classes, 1000);
|
|
}
|
|
|
|
#[test]
|
|
fn test_resnet_config_variants() {
|
|
let config18 = ResNetConfig::resnet18();
|
|
assert!(matches!(config18.variant, ResNetVariant::ResNet18));
|
|
|
|
let config34 = ResNetConfig::resnet34();
|
|
assert!(matches!(config34.variant, ResNetVariant::ResNet34));
|
|
|
|
let config50 = ResNetConfig::resnet50();
|
|
assert!(matches!(config50.variant, ResNetVariant::ResNet50));
|
|
|
|
let config101 = ResNetConfig::resnet101();
|
|
assert!(matches!(config101.variant, ResNetVariant::ResNet101));
|
|
|
|
let config152 = ResNetConfig::resnet152();
|
|
assert!(matches!(config152.variant, ResNetVariant::ResNet152));
|
|
}
|
|
|
|
#[test]
|
|
fn test_resnet_config_builders() {
|
|
let config = ResNetConfig::resnet50()
|
|
.with_num_classes(10)
|
|
.with_pretrained(true);
|
|
|
|
assert_eq!(config.num_classes, 10);
|
|
}
|
|
|
|
#[test]
|
|
fn test_resnet18_creation() {
|
|
let _device = get_test_device();
|
|
let config = ResNetConfig::resnet18();
|
|
let model = ResNet::new(config);
|
|
|
|
assert!(model.is_ok());
|
|
let model = model.unwrap();
|
|
assert_eq!(model.num_parameters(), 11_689_512);
|
|
}
|
|
|
|
#[test]
|
|
fn test_resnet34_creation() {
|
|
let _device = get_test_device();
|
|
let config = ResNetConfig::resnet34();
|
|
let model = ResNet::new(config);
|
|
|
|
assert!(model.is_ok());
|
|
let model = model.unwrap();
|
|
assert_eq!(model.num_parameters(), 21_797_672);
|
|
}
|
|
|
|
#[test]
|
|
fn test_resnet50_creation() {
|
|
let _device = get_test_device();
|
|
let config = ResNetConfig::resnet50();
|
|
let model = ResNet::new(config);
|
|
|
|
assert!(model.is_ok());
|
|
let model = model.unwrap();
|
|
assert_eq!(model.num_parameters(), 25_557_032);
|
|
}
|
|
|
|
#[test]
|
|
fn test_resnet101_creation() {
|
|
let _device = get_test_device();
|
|
let config = ResNetConfig::resnet101();
|
|
let model = ResNet::new(config);
|
|
|
|
assert!(model.is_ok());
|
|
let model = model.unwrap();
|
|
assert_eq!(model.num_parameters(), 44_549_160);
|
|
}
|
|
|
|
#[test]
|
|
fn test_resnet152_creation() {
|
|
let _device = get_test_device();
|
|
let config = ResNetConfig::resnet152();
|
|
let model = ResNet::new(config);
|
|
|
|
assert!(model.is_ok());
|
|
let model = model.unwrap();
|
|
assert_eq!(model.num_parameters(), 60_192_808);
|
|
}
|
|
|
|
#[test]
|
|
fn test_resnet18_forward_pass() {
|
|
let device = get_test_device();
|
|
let config = ResNetConfig::resnet18();
|
|
let model = ResNet::new(config).unwrap();
|
|
|
|
// Test with standard ImageNet input size
|
|
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_resnet50_forward_pass() {
|
|
let device = get_test_device();
|
|
let config = ResNetConfig::resnet50();
|
|
let model = ResNet::new(config).unwrap();
|
|
|
|
// Test with standard ImageNet input size
|
|
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_resnet_batch_forward() {
|
|
let device = get_test_device();
|
|
let config = ResNetConfig::resnet18();
|
|
let model = ResNet::new(config).unwrap();
|
|
|
|
// Test with batch size > 1
|
|
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_resnet_custom_classes() {
|
|
let device = get_test_device();
|
|
let config = ResNetConfig::resnet18().with_num_classes(10);
|
|
let model = ResNet::new(config).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_resnet_different_input_sizes() {
|
|
let device = get_test_device();
|
|
let config = ResNetConfig::resnet18();
|
|
let model = ResNet::new(config).unwrap();
|
|
|
|
// Test with different input sizes
|
|
let sizes = vec![
|
|
(1, 3, 32, 32), // CIFAR-10 size
|
|
(1, 3, 64, 64), // Small images
|
|
(1, 3, 128, 128), // Medium images
|
|
(1, 3, 224, 224), // Standard ImageNet
|
|
(1, 3, 299, 299), // Large images
|
|
];
|
|
|
|
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_resnet_feature_extraction() {
|
|
let device = get_test_device();
|
|
let config = ResNetConfig::resnet50();
|
|
let model = ResNet::new(config).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();
|
|
// Features should be [batch_size, 2048, 1, 1] for ResNet-50
|
|
assert_eq!(features.shape().dims(), &[1, 2048, 1, 1]);
|
|
}
|
|
|
|
#[test]
|
|
fn test_resnet18_feature_extraction() {
|
|
let device = get_test_device();
|
|
let config = ResNetConfig::resnet18();
|
|
let model = ResNet::new(config).unwrap();
|
|
|
|
let input = Tensor::randn(&[2, 3, 224, 224], &device).unwrap();
|
|
let features = model.extract_features(&input);
|
|
|
|
assert!(features.is_ok());
|
|
let features = features.unwrap();
|
|
// Features should be [batch_size, 512, 1, 1] for ResNet-18/34
|
|
assert_eq!(features.shape().dims(), &[2, 512, 1, 1]);
|
|
}
|
|
|
|
#[test]
|
|
fn test_resnet_edge_cases() {
|
|
let device = get_test_device();
|
|
|
|
// Test minimum viable input
|
|
let config = ResNetConfig::resnet18().with_num_classes(1);
|
|
let model = ResNet::new(config).unwrap();
|
|
|
|
let input = Tensor::randn(&[1, 3, 32, 32], &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_resnet_deterministic_output() {
|
|
let device = get_test_device();
|
|
let config = ResNetConfig::resnet18();
|
|
let model = ResNet::new(config).unwrap();
|
|
|
|
let input = Tensor::ones(&[1, 3, 224, 224], &device).unwrap();
|
|
let output1 = model.forward(&input).unwrap();
|
|
let output2 = model.forward(&input).unwrap();
|
|
|
|
// In a real implementation, these would be identical
|
|
// For mock implementation, we check they have the same shape
|
|
assert_eq!(output1.shape().dims(), output2.shape().dims());
|
|
}
|
|
|
|
#[test]
|
|
fn test_resnet_large_batch() {
|
|
let device = get_test_device();
|
|
let config = ResNetConfig::resnet18();
|
|
let model = ResNet::new(config).unwrap();
|
|
|
|
// Test with larger batch size
|
|
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_resnet_memory_consistency() {
|
|
let device = get_test_device();
|
|
let config = ResNetConfig::resnet50();
|
|
let model = ResNet::new(config).unwrap();
|
|
|
|
// Multiple forward passes should not fail
|
|
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_resnet_with_groups() {
|
|
let _device = get_test_device();
|
|
let config = ResNetConfig::resnet50();
|
|
|
|
let model = ResNet::new(config);
|
|
assert!(model.is_ok());
|
|
}
|
|
|
|
#[test]
|
|
fn test_resnet_with_dilation() {
|
|
let device = get_test_device();
|
|
let config = ResNetConfig::resnet50();
|
|
|
|
let model = ResNet::new(config);
|
|
assert!(model.is_ok());
|
|
|
|
let input = Tensor::randn(&[1, 3, 224, 224], &device).unwrap();
|
|
let output = model.unwrap().forward(&input);
|
|
assert!(output.is_ok());
|
|
}
|
|
|
|
#[test]
|
|
fn test_resnet_parameter_counting() {
|
|
let _device = get_test_device();
|
|
|
|
let models = vec![
|
|
(ResNetConfig::resnet18(), 11_689_512),
|
|
(ResNetConfig::resnet34(), 21_797_672),
|
|
(ResNetConfig::resnet50(), 25_557_032),
|
|
(ResNetConfig::resnet101(), 44_549_160),
|
|
(ResNetConfig::resnet152(), 60_192_808),
|
|
];
|
|
|
|
for (config, expected_params) in models {
|
|
let model = ResNet::new(config).unwrap();
|
|
assert_eq!(model.num_parameters(), expected_params);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_resnet_serialization() {
|
|
let config = ResNetConfig::resnet18().with_num_classes(10);
|
|
let serialized = serde_json::to_string(&config);
|
|
assert!(serialized.is_ok());
|
|
|
|
let deserialized: Result<ResNetConfig, _> = serde_json::from_str(&serialized.unwrap());
|
|
assert!(deserialized.is_ok());
|
|
|
|
let deserialized = deserialized.unwrap();
|
|
assert!(matches!(deserialized.variant, ResNetVariant::ResNet18));
|
|
assert_eq!(deserialized.num_classes, 10);
|
|
}
|
|
|
|
#[test]
|
|
fn test_basic_block_expansion() {
|
|
use rtx_vision::architectures::resnet::BasicBlock;
|
|
assert_eq!(BasicBlock::EXPANSION, 1);
|
|
}
|
|
|
|
#[test]
|
|
fn test_bottleneck_expansion() {
|
|
use rtx_vision::architectures::resnet::Bottleneck;
|
|
assert_eq!(Bottleneck::EXPANSION, 4);
|
|
}
|
|
|
|
#[test]
|
|
fn test_resnet_invalid_configurations() {
|
|
let _device = get_test_device();
|
|
|
|
// Test invalid number of classes
|
|
let config = ResNetConfig::resnet18().with_num_classes(0);
|
|
let model = ResNet::new(config);
|
|
// This should work in our implementation, but in production might fail
|
|
assert!(model.is_ok());
|
|
}
|
|
|
|
#[test]
|
|
fn test_resnet_forward_consistency() {
|
|
let device = get_test_device();
|
|
let config = ResNetConfig::resnet18();
|
|
let model = ResNet::new(config).unwrap();
|
|
|
|
let input = Tensor::zeros(&[1, 3, 224, 224], &device).unwrap();
|
|
|
|
// Multiple forwards should succeed
|
|
let results: Vec<_> = (0..3).map(|_| model.forward(&input)).collect();
|
|
|
|
assert!(results.iter().all(|r| r.is_ok()));
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod integration_tests {
|
|
use rtx_vision::architectures::{ResNet, ResNetConfig, ResNetVariant};
|
|
use rtx_vision::{Device, Tensor};
|
|
|
|
#[test]
|
|
fn test_resnet_transfer_learning_setup() {
|
|
let device = Device::cpu();
|
|
let config = ResNetConfig::resnet50()
|
|
.with_num_classes(10) // Fine-tune for CIFAR-10
|
|
.with_pretrained(true);
|
|
|
|
let model = ResNet::new(config).unwrap();
|
|
|
|
// Test that model works with different input size
|
|
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_resnet_feature_pyramid() {
|
|
let device = Device::cpu();
|
|
let config = ResNetConfig::resnet50();
|
|
let model = ResNet::new(config).unwrap();
|
|
|
|
let input = Tensor::randn(&[1, 3, 224, 224], &device).unwrap();
|
|
|
|
// Extract features for feature pyramid networks
|
|
let features = model.extract_features(&input).unwrap();
|
|
|
|
// Verify feature map dimensions
|
|
assert_eq!(features.shape().dims(), &[1, 2048, 1, 1]);
|
|
}
|
|
|
|
#[test]
|
|
fn test_all_resnet_variants_consistency() {
|
|
let device = Device::cpu();
|
|
let variants = vec![
|
|
ResNetVariant::ResNet18,
|
|
ResNetVariant::ResNet34,
|
|
ResNetVariant::ResNet50,
|
|
ResNetVariant::ResNet101,
|
|
ResNetVariant::ResNet152,
|
|
];
|
|
|
|
for variant in variants {
|
|
let config = ResNetConfig {
|
|
variant: variant.clone(),
|
|
..Default::default()
|
|
};
|
|
|
|
let model = ResNet::new(config).unwrap();
|
|
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]);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_resnet_computational_efficiency() {
|
|
use std::time::Instant;
|
|
|
|
let device = Device::cpu();
|
|
|
|
// Compare computational time between variants (mock test)
|
|
let configs = vec![ResNetConfig::resnet18(), ResNetConfig::resnet50()];
|
|
|
|
for config in configs {
|
|
let model = ResNet::new(config).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
|
|
// For mock, we just verify it completes
|
|
}
|
|
}
|
|
}
|