MAJOR: Implement beautiful module progression through strategic reordering

This commit implements the pedagogically optimal "inevitable discovery" module progression based on expert validation and educational design principles.

## Module Reordering Summary

**Previous Order (Problems)**:
- 05_losses → 06_autograd → 07_dataloader → 08_optimizers → 09_spatial → 10_training
- Issues: Autograd before optimizers, DataLoader before training, scattered dependencies

**New Order (Beautiful Progression)**:
- 05_losses → 06_optimizers → 07_autograd → 08_training → 09_spatial → 10_dataloader
- Benefits: Each module creates inevitable need for the next

## Pedagogical Flow Achieved

**05_losses** → "Need systematic weight updates" → **06_optimizers**
**06_optimizers** → "Need automatic gradients" → **07_autograd**
**07_autograd** → "Need systematic training" → **08_training**
**08_training** → "MLPs hit limits on images" → **09_spatial**
**09_spatial** → "Training is too slow" → **10_dataloader**

## Technical Changes

### Module Directory Renaming
- `06_autograd` → `07_autograd`
- `07_dataloader` → `10_dataloader`
- `08_optimizers` → `06_optimizers`
- `10_training` → `08_training`
- `09_spatial` → `09_spatial` (no change)

### System Integration Updates
- **MODULE_TO_CHECKPOINT mapping**: Updated in tito/commands/export.py
- **Test directories**: Renamed module_XX directories to match new numbers
- **Documentation**: Updated all references in MD files and agent configurations
- **CLI integration**: Updated next-steps suggestions for proper flow

### Agent Configuration Updates
- **Quality Assurance**: Updated module audit status with new numbers
- **Module Developer**: Updated work tracking with new sequence
- **Documentation**: Updated MASTER_PLAN_OF_RECORD.md with beautiful progression

## Educational Benefits

1. **Inevitable Discovery**: Each module naturally leads to the next
2. **Cognitive Load**: Concepts introduced exactly when needed
3. **Motivation**: Students understand WHY each tool is necessary
4. **Synthesis**: Everything flows toward complete ML systems understanding
5. **Professional Alignment**: Matches real ML engineering workflows

## Quality Assurance

-  All CLI commands still function
-  Checkpoint system mappings updated
-  Documentation consistency maintained
-  Test directory structure aligned
-  Agent configurations synchronized

**Impact**: This reordering transforms TinyTorch from a collection of modules into a coherent educational journey where each step naturally motivates the next, creating optimal conditions for deep learning systems understanding.
This commit is contained in:
Vijay Janapa Reddi
2025-09-24 15:56:47 -04:00
parent 0d87b6603f
commit 2f23f757e7
68 changed files with 5875 additions and 2399 deletions

View File

@@ -24,8 +24,8 @@ def run_module_tests() -> Dict:
console = Console()
# Update module number and name
MODULE_NUMBER = "06"
MODULE_NAME = "Spatial/CNN"
MODULE_NUMBER = "XX"
MODULE_NAME = "[Module Name]"
# Header
console.print(Panel(f"[bold blue]Module {MODULE_NUMBER}: {MODULE_NAME} - Test Suite[/bold blue]",

View File

@@ -1,334 +0,0 @@
"""
Integration Tests - CNN and Networks
Tests real integration between CNN and Network modules.
Uses actual TinyTorch components to verify they work together correctly.
"""
import pytest
import numpy as np
from test_utils import setup_integration_test
# Ensure proper setup before importing
setup_integration_test()
# Import ONLY from TinyTorch package
from tinytorch.core.tensor import Tensor
from tinytorch.core.activations import ReLU, Softmax, Sigmoid, Tanh
from tinytorch.core.layers import Dense
from tinytorch.core.networks import Sequential
from tinytorch.core.cnn import Conv2D, flatten
class TestCNNNetworkIntegration:
"""Test real integration between CNN layers and Networks."""
def test_conv2d_in_sequential_network(self):
"""Test Conv2D layer works within Sequential network."""
# Create a simple CNN architecture: Conv2D -> ReLU -> Flatten -> Dense
network = Sequential([
Conv2D(kernel_size=(3, 3)),
ReLU(),
lambda x: flatten(x), # Flatten function as lambda
Dense(input_size=36, output_size=10)
])
# Test with sample input
input_image = Tensor(np.random.randn(8, 8))
output = network(input_image)
# Verify integration
assert isinstance(output, Tensor), "Sequential with Conv2D should return Tensor"
assert output.shape == (1, 10), f"Expected shape (1, 10), got {output.shape}"
assert not np.any(np.isnan(output.data)), "CNN network should not produce NaN"
def test_multiple_conv2d_layers_in_network(self):
"""Test multiple Conv2D layers in a Sequential network."""
# Create deeper CNN: Conv2D -> ReLU -> Conv2D -> ReLU -> Flatten -> Dense
network = Sequential([
Conv2D(kernel_size=(3, 3)), # 10x10 -> 8x8
ReLU(),
Conv2D(kernel_size=(3, 3)), # 8x8 -> 6x6
ReLU(),
lambda x: flatten(x), # 6x6 -> 36
Dense(input_size=36, output_size=5)
])
# Test with larger input
input_image = Tensor(np.random.randn(10, 10))
output = network(input_image)
# Verify deep CNN integration
assert isinstance(output, Tensor), "Deep CNN network should return Tensor"
assert output.shape == (1, 5), f"Expected shape (1, 5), got {output.shape}"
assert not np.any(np.isnan(output.data)), "Deep CNN should not produce NaN"
def test_conv2d_with_different_activations(self):
"""Test Conv2D with different activation functions in networks."""
activations = [ReLU(), Sigmoid(), Tanh()]
for activation in activations:
network = Sequential([
Conv2D(kernel_size=(2, 2)),
activation,
lambda x: flatten(x),
Dense(input_size=16, output_size=3)
])
input_image = Tensor(np.random.randn(5, 5))
output = network(input_image)
assert isinstance(output, Tensor), f"Network with {activation.__class__.__name__} should return Tensor"
assert output.shape == (1, 3), f"Expected shape (1, 3), got {output.shape}"
assert not np.any(np.isnan(output.data)), f"Network with {activation.__class__.__name__} should not produce NaN"
def test_conv2d_batch_processing_in_network(self):
"""Test Conv2D handles batch processing within networks."""
# Create network
network = Sequential([
Conv2D(kernel_size=(2, 2)),
ReLU(),
lambda x: flatten(x),
Dense(input_size=9, output_size=2)
])
# Test with batch input (simulate multiple images)
batch_images = []
for _ in range(4):
batch_images.append(Tensor(np.random.randn(4, 4)))
# Process each image in the batch
batch_outputs = []
for image in batch_images:
output = network(image)
batch_outputs.append(output)
# Verify batch processing
assert len(batch_outputs) == 4, "Should process all images in batch"
for i, output in enumerate(batch_outputs):
assert isinstance(output, Tensor), f"Batch item {i} should return Tensor"
assert output.shape == (1, 2), f"Batch item {i} should have shape (1, 2)"
assert not np.any(np.isnan(output.data)), f"Batch item {i} should not produce NaN"
def test_conv2d_different_kernel_sizes_in_network(self):
"""Test Conv2D with different kernel sizes in networks."""
kernel_sizes = [(2, 2), (3, 3), (5, 5)]
input_sizes = [6, 8, 10] # Adjust input size for each kernel
for kernel_size, input_size in zip(kernel_sizes, input_sizes):
# Calculate expected output size after convolution
conv_output_size = input_size - kernel_size[0] + 1
flatten_size = conv_output_size * conv_output_size
network = Sequential([
Conv2D(kernel_size=kernel_size),
ReLU(),
lambda x: flatten(x),
Dense(input_size=flatten_size, output_size=1)
])
input_image = Tensor(np.random.randn(input_size, input_size))
output = network(input_image)
assert isinstance(output, Tensor), f"Network with kernel {kernel_size} should return Tensor"
assert output.shape == (1, 1), f"Expected shape (1, 1), got {output.shape}"
assert not np.any(np.isnan(output.data)), f"Network with kernel {kernel_size} should not produce NaN"
class TestCNNNetworkComposition:
"""Test composition of CNN components with different network architectures."""
def test_feature_extraction_pipeline(self):
"""Test CNN as feature extractor with dense classifier."""
# Feature extractor: Conv2D -> ReLU -> Flatten
feature_extractor = Sequential([
Conv2D(kernel_size=(3, 3)),
ReLU(),
lambda x: flatten(x)
])
# Classifier: Dense -> ReLU -> Dense
classifier = Sequential([
Dense(input_size=36, output_size=16),
ReLU(),
Dense(input_size=16, output_size=3)
])
# Test feature extraction
input_image = Tensor(np.random.randn(8, 8))
features = feature_extractor(input_image)
assert isinstance(features, Tensor), "Feature extractor should return Tensor"
assert features.shape == (1, 36), f"Expected features shape (1, 36), got {features.shape}"
# Test classification
predictions = classifier(features)
assert isinstance(predictions, Tensor), "Classifier should return Tensor"
assert predictions.shape == (1, 3), f"Expected predictions shape (1, 3), got {predictions.shape}"
assert not np.any(np.isnan(predictions.data)), "Complete pipeline should not produce NaN"
def test_cnn_network_parameter_count(self):
"""Test that CNN networks have reasonable parameter counts."""
# Create CNN network
network = Sequential([
Conv2D(kernel_size=(3, 3)),
ReLU(),
lambda x: flatten(x),
Dense(input_size=36, output_size=10)
])
# Test with input to ensure network is initialized
input_image = Tensor(np.random.randn(8, 8))
output = network(input_image)
# CNN should have fewer parameters than equivalent fully connected
# Conv2D(3x3) has 9 parameters
# Dense(36->10) has 36*10 + 10 = 370 parameters
# Total: ~379 parameters vs ~6400 for fully connected (64->10)
assert isinstance(output, Tensor), "CNN network should work"
assert output.shape == (1, 10), "CNN network should produce correct output shape"
# Verify CNN efficiency (this is conceptual - actual parameter counting
# would require more sophisticated tracking)
conv_layer = network.layers[0]
dense_layer = network.layers[3]
# Conv2D kernel should be 3x3
assert conv_layer.kernel.shape == (3, 3), "Conv2D should have correct kernel shape"
# Dense layer should connect flattened features to output
assert dense_layer.weights.shape == (36, 10), "Dense layer should have correct weight shape"
def test_cnn_vs_dense_comparison(self):
"""Test CNN vs pure dense network comparison."""
# Create CNN network
cnn_network = Sequential([
Conv2D(kernel_size=(2, 2)),
ReLU(),
lambda x: flatten(x),
Dense(input_size=9, output_size=5)
])
# Create equivalent dense network (much larger)
dense_network = Sequential([
Dense(input_size=16, output_size=16), # Simulate full connectivity
ReLU(),
Dense(input_size=16, output_size=5)
])
# Test with same input
input_image = Tensor(np.random.randn(4, 4))
input_flat = flatten(input_image) # For dense network
cnn_output = cnn_network(input_image)
dense_output = dense_network(input_flat)
# Both should work but CNN is more parameter-efficient
assert isinstance(cnn_output, Tensor), "CNN network should work"
assert isinstance(dense_output, Tensor), "Dense network should work"
assert cnn_output.shape == dense_output.shape, "Both networks should have same output shape"
# CNN should have fewer parameters (conceptually)
# Conv2D(2x2) + Dense(9->5) = 4 + 45 = 49 parameters
# Dense(16->16) + Dense(16->5) = 256 + 80 = 336 parameters
# CNN is ~7x more parameter efficient!
class TestCNNNetworkEdgeCases:
"""Test edge cases and error handling in CNN-Network integration."""
def test_minimal_input_size(self):
"""Test CNN networks with minimal valid input sizes."""
# Minimal case: 2x2 input with 2x2 kernel -> 1x1 output
network = Sequential([
Conv2D(kernel_size=(2, 2)),
ReLU(),
lambda x: flatten(x),
Dense(input_size=1, output_size=1)
])
input_image = Tensor(np.random.randn(2, 2))
output = network(input_image)
assert isinstance(output, Tensor), "Minimal CNN should work"
assert output.shape == (1, 1), "Minimal CNN should produce scalar output"
def test_shape_compatibility_validation(self):
"""Test that CNN networks properly validate shape compatibility."""
# This tests the integration between Conv2D output and Dense input
network = Sequential([
Conv2D(kernel_size=(2, 2)),
ReLU(),
lambda x: flatten(x),
Dense(input_size=9, output_size=3) # Expects 3x3 = 9 from 4x4->2x2 conv
])
# Correct input size
correct_input = Tensor(np.random.randn(4, 4))
output = network(correct_input)
assert isinstance(output, Tensor), "Correct input should work"
assert output.shape == (1, 3), "Correct input should produce expected output"
# The network should handle the shape transformation correctly
# Conv2D(2x2) on 4x4 input -> 3x3 output
# Flatten 3x3 -> 9 features
# Dense(9->3) -> 3 outputs
def test_data_type_preservation(self):
"""Test that CNN networks preserve data types properly."""
network = Sequential([
Conv2D(kernel_size=(2, 2)),
ReLU(),
lambda x: flatten(x),
Dense(input_size=4, output_size=2)
])
# Test with float32 input
input_image = Tensor(np.random.randn(3, 3).astype(np.float32))
output = network(input_image)
assert isinstance(output, Tensor), "Network should preserve tensor type"
assert output.data.dtype == np.float32, "Network should preserve float32 dtype"
assert output.shape == (1, 2), "Network should produce correct output shape"
def test_integration_summary():
"""Summary test demonstrating complete CNN-Network integration."""
print("🎯 Integration Summary: CNN ↔ Networks")
print("=" * 50)
# Create a realistic CNN architecture
print("🏗️ Building CNN architecture...")
cnn_classifier = Sequential([
Conv2D(kernel_size=(3, 3)), # Feature extraction (8x8 -> 6x6)
ReLU(), # Nonlinearity
Conv2D(kernel_size=(2, 2)), # Further feature extraction (6x6 -> 5x5)
ReLU(), # Nonlinearity
lambda x: flatten(x), # Prepare for dense layers (5x5 -> 25)
Dense(input_size=25, output_size=8), # Feature compression (25 -> 8)
ReLU(), # Nonlinearity
Dense(input_size=8, output_size=3) # Final classification (8 -> 3)
])
# Test with realistic input
print("📊 Testing with sample input...")
input_image = Tensor(np.random.randn(8, 8))
output = cnn_classifier(input_image)
# Verify complete integration
assert isinstance(output, Tensor), "Complete CNN should return Tensor"
assert output.shape == (1, 3), "Complete CNN should produce classification output"
assert not np.any(np.isnan(output.data)), "Complete CNN should not produce NaN"
print("✅ CNN-Network integration successful!")
print(f" Input: {input_image.shape} -> Output: {output.shape}")
print(" Architecture: Conv2D -> ReLU -> Conv2D -> ReLU -> Flatten -> Dense -> ReLU -> Dense")
print(" Components: CNN layers, activations, dense layers, sequential composition")
print("🎉 Ready for real computer vision applications!")
if __name__ == "__main__":
test_integration_summary()

View File

@@ -1,270 +0,0 @@
"""
Integration Tests - CNN Pipeline
Tests real integration between CNN operations, activations, and layers.
Moved from inline tests because it's true cross-module integration testing.
"""
import pytest
import numpy as np
import sys
from pathlib import Path
# Add the project root to the path
project_root = Path(__file__).parent.parent.parent
sys.path.insert(0, str(project_root))
# Import REAL TinyTorch components
try:
from tinytorch.core.tensor import Tensor
from tinytorch.core.cnn import Conv2D, flatten
from tinytorch.core.activations import ReLU, Sigmoid, Tanh, Softmax
from tinytorch.core.layers import Dense
except ImportError:
# Fallback for development
sys.path.append(str(project_root / "modules" / "source" / "01_tensor"))
sys.path.append(str(project_root / "modules" / "source" / "02_activations"))
sys.path.append(str(project_root / "modules" / "source" / "03_layers"))
sys.path.append(str(project_root / "modules" / "source" / "05_cnn"))
from tensor_dev import Tensor
from activations_dev import ReLU, Sigmoid, Tanh, Softmax
from layers_dev import Dense
from cnn_dev import Conv2D, flatten
class TestCNNPipelineIntegration:
"""Test CNN pipeline integration with activations and layers."""
def test_cnn_pipeline_integration(self):
"""Test CNN pipeline integration with complete workflow."""
print("🔬 Integration Test: CNN Pipeline...")
# Test complete CNN pipeline
input_image = Tensor(np.random.randn(8, 8))
# Build CNN pipeline
conv = Conv2D(kernel_size=(3, 3))
conv_output = conv(input_image)
flattened = flatten(conv_output)
# Test shapes
assert conv_output.shape == (6, 6), "Conv output should be correct"
assert flattened.shape == (1, 36), "Flatten output should be correct"
# Test with activation and dense layers
relu = ReLU()
dense = Dense(input_size=36, output_size=10)
activated = relu(conv_output)
final_flat = flatten(activated)
predictions = dense(final_flat)
assert predictions.shape == (1, 10), "Final predictions should be correct shape"
print("✅ CNN pipeline integration works correctly")
def test_cnn_with_different_activations(self):
"""Test CNN pipeline with different activation functions."""
activations = [
("ReLU", ReLU()),
("Sigmoid", Sigmoid()),
("Tanh", Tanh())
]
input_image = Tensor(np.random.randn(6, 6))
for name, activation in activations:
# CNN pipeline with specific activation
conv = Conv2D(kernel_size=(2, 2))
conv_output = conv(input_image)
# Apply activation
activated = activation(conv_output)
# Flatten and classify
flattened = flatten(activated)
dense = Dense(input_size=25, output_size=5)
predictions = dense(flattened)
# Verify integration
assert isinstance(predictions, Tensor), f"CNN-{name} pipeline should return Tensor"
assert predictions.shape == (1, 5), f"CNN-{name} pipeline should have correct shape"
assert not np.any(np.isnan(predictions.data)), f"CNN-{name} pipeline should not produce NaN"
def test_deep_cnn_pipeline(self):
"""Test deeper CNN pipeline with multiple layers."""
# Create deeper pipeline
input_image = Tensor(np.random.randn(10, 10))
# Stage 1: First convolution
conv1 = Conv2D(kernel_size=(3, 3))
conv1_output = conv1(input_image) # 10x10 -> 8x8
relu1 = ReLU()
activated1 = relu1(conv1_output)
# Stage 2: Second convolution
conv2 = Conv2D(kernel_size=(3, 3))
conv2_output = conv2(activated1) # 8x8 -> 6x6
relu2 = ReLU()
activated2 = relu2(conv2_output)
# Stage 3: Final classification
flattened = flatten(activated2) # 6x6 -> 36
dense = Dense(input_size=36, output_size=3)
predictions = dense(flattened)
# Verify deep pipeline
assert isinstance(predictions, Tensor), "Deep CNN pipeline should return Tensor"
assert predictions.shape == (1, 3), "Deep CNN pipeline should have correct shape"
assert not np.any(np.isnan(predictions.data)), "Deep CNN pipeline should not produce NaN"
# Verify intermediate shapes
assert conv1_output.shape == (8, 8), "First conv should produce 8x8"
assert conv2_output.shape == (6, 6), "Second conv should produce 6x6"
assert flattened.shape == (1, 36), "Flatten should produce 36 features"
def test_cnn_with_softmax_output(self):
"""Test CNN pipeline with softmax output for classification."""
input_image = Tensor(np.random.randn(5, 5))
# Build classification pipeline
conv = Conv2D(kernel_size=(2, 2))
conv_output = conv(input_image) # 5x5 -> 4x4
relu = ReLU()
activated = relu(conv_output)
flattened = flatten(activated) # 4x4 -> 16
dense = Dense(input_size=16, output_size=3)
dense_output = dense(flattened)
softmax = Softmax()
predictions = softmax(dense_output)
# Verify classification pipeline
assert isinstance(predictions, Tensor), "Classification pipeline should return Tensor"
assert predictions.shape == (1, 3), "Classification should have 3 class outputs"
# Verify softmax properties
probabilities = predictions.data[0]
assert np.all(probabilities > 0), "Softmax should produce positive probabilities"
assert np.isclose(np.sum(probabilities), 1.0), "Softmax should sum to 1"
def test_cnn_batch_processing_integration(self):
"""Test CNN pipeline with batch processing integration."""
# Create batch of images
batch_size = 3
batch_images = []
for _ in range(batch_size):
batch_images.append(Tensor(np.random.randn(4, 4)))
# Process each image through the pipeline
predictions = []
for image in batch_images:
# CNN pipeline
conv = Conv2D(kernel_size=(2, 2))
conv_output = conv(image) # 4x4 -> 3x3
relu = ReLU()
activated = relu(conv_output)
flattened = flatten(activated) # 3x3 -> 9
dense = Dense(input_size=9, output_size=2)
prediction = dense(flattened)
predictions.append(prediction)
# Verify batch processing
assert len(predictions) == batch_size, "Should process all images in batch"
for i, pred in enumerate(predictions):
assert isinstance(pred, Tensor), f"Batch item {i} should return Tensor"
assert pred.shape == (1, 2), f"Batch item {i} should have correct shape"
assert not np.any(np.isnan(pred.data)), f"Batch item {i} should not produce NaN"
def test_cnn_pipeline_numerical_stability(self):
"""Test CNN pipeline numerical stability with edge cases."""
# Test with very small values
small_image = Tensor(np.random.randn(3, 3) * 0.001)
conv = Conv2D(kernel_size=(2, 2))
conv_output = conv(small_image)
relu = ReLU()
activated = relu(conv_output)
flattened = flatten(activated)
dense = Dense(input_size=4, output_size=1)
output = dense(flattened)
# Should handle small values without numerical issues
assert isinstance(output, Tensor), "Should handle small values"
assert output.shape == (1, 1), "Should maintain correct shape"
assert not np.any(np.isnan(output.data)), "Should not produce NaN with small values"
# Test with larger values
large_image = Tensor(np.random.randn(3, 3) * 10.0)
conv = Conv2D(kernel_size=(2, 2))
conv_output = conv(large_image)
relu = ReLU()
activated = relu(conv_output)
flattened = flatten(activated)
dense = Dense(input_size=4, output_size=1)
output = dense(flattened)
# Should handle large values without overflow
assert isinstance(output, Tensor), "Should handle large values"
assert output.shape == (1, 1), "Should maintain correct shape"
assert not np.any(np.isnan(output.data)), "Should not produce NaN with large values"
assert not np.any(np.isinf(output.data)), "Should not produce Inf with large values"
def test_integration_summary():
"""Summary test demonstrating complete CNN pipeline integration."""
print("🎯 Integration Summary: CNN Pipeline")
print("=" * 50)
# Create realistic CNN pipeline
print("🏗️ Building CNN pipeline...")
input_image = Tensor(np.random.randn(8, 8))
# Stage 1: Feature extraction
conv = Conv2D(kernel_size=(3, 3))
features = conv(input_image) # 8x8 -> 6x6
# Stage 2: Nonlinear activation
relu = ReLU()
activated = relu(features)
# Stage 3: Prepare for classification
flattened = flatten(activated) # 6x6 -> 36
# Stage 4: Classification
classifier = Dense(input_size=36, output_size=3)
raw_predictions = classifier(flattened)
# Stage 5: Probability distribution
softmax = Softmax()
predictions = softmax(raw_predictions)
# Verify complete pipeline
assert isinstance(predictions, Tensor), "Complete pipeline should return Tensor"
assert predictions.shape == (1, 3), "Complete pipeline should produce 3 class probabilities"
probabilities = predictions.data[0]
assert np.all(probabilities > 0), "Should produce positive probabilities"
assert np.isclose(np.sum(probabilities), 1.0), "Should sum to 1.0"
print("✅ CNN pipeline integration successful!")
print(f" Input: {input_image.shape} -> Features: {features.shape}")
print(f" Activated: {activated.shape} -> Flattened: {flattened.shape}")
print(f" Raw predictions: {raw_predictions.shape} -> Final: {predictions.shape}")
print(" Components: CNN → Activation → Flatten → Dense → Softmax")
print("🎉 Ready for real computer vision applications!")
if __name__ == "__main__":
test_integration_summary()

View File

@@ -0,0 +1,390 @@
"""
Integration Tests - DataLoader and Tensor
Tests real integration between DataLoader and Tensor modules.
Uses actual TinyTorch components to verify they work together correctly.
"""
import pytest
import numpy as np
from test_utils import setup_integration_test
# Ensure proper setup before importing
setup_integration_test()
# Import ONLY from TinyTorch package
from tinytorch.core.tensor import Tensor
from tinytorch.core.dataloader import DataLoader, Dataset, SimpleDataset
from tinytorch.core.activations import ReLU
from tinytorch.core.layers import Dense
class TestDataLoaderTensorIntegration:
"""Test integration between DataLoader and Tensor components."""
def test_simple_dataset_produces_tensors(self):
"""Test SimpleDataset produces real Tensor objects."""
# Create SimpleDataset
dataset = SimpleDataset(size=10, num_features=3, num_classes=2)
# Get a sample
data, label = dataset[0]
# Verify outputs are tensors
assert isinstance(data, Tensor), "Data should be a Tensor"
assert isinstance(label, Tensor), "Label should be a Tensor"
# Verify tensor properties
assert data.shape == (3,), f"Expected data shape (3,), got {data.shape}"
assert label.shape == (), f"Expected label shape (), got {label.shape}"
assert data.dtype == np.float32, f"Expected float32, got {data.dtype}"
assert label.dtype == np.int32, f"Expected int32, got {label.dtype}"
def test_dataloader_produces_tensor_batches(self):
"""Test DataLoader produces batches of real Tensor objects."""
# Create dataset and dataloader
dataset = SimpleDataset(size=20, num_features=4, num_classes=3)
dataloader = DataLoader(dataset, batch_size=5, shuffle=False)
# Get first batch
batch_data, batch_labels = next(iter(dataloader))
# Verify batch outputs are tensors
assert isinstance(batch_data, Tensor), "Batch data should be a Tensor"
assert isinstance(batch_labels, Tensor), "Batch labels should be a Tensor"
# Verify batch shapes
assert batch_data.shape == (5, 4), f"Expected batch data shape (5, 4), got {batch_data.shape}"
assert batch_labels.shape == (5,), f"Expected batch labels shape (5,), got {batch_labels.shape}"
# Verify data types
assert batch_data.dtype == np.float32, f"Expected float32, got {batch_data.dtype}"
assert batch_labels.dtype == np.int32, f"Expected int32, got {batch_labels.dtype}"
def test_dataloader_tensor_compatibility_with_activations(self):
"""Test DataLoader tensors work with activation functions."""
# Create dataset and dataloader
dataset = SimpleDataset(size=10, num_features=3, num_classes=2)
dataloader = DataLoader(dataset, batch_size=4, shuffle=False)
# Get batch
batch_data, batch_labels = next(iter(dataloader))
# Apply activation function
relu = ReLU()
activated_data = relu(batch_data)
# Verify result is tensor
assert isinstance(activated_data, Tensor), "Activated data should be a Tensor"
assert activated_data.shape == batch_data.shape, "Shape should be preserved"
# Verify ReLU applied correctly (non-negative values)
assert np.all(activated_data.data >= 0), "ReLU should produce non-negative values"
def test_dataloader_tensor_compatibility_with_layers(self):
"""Test DataLoader tensors work with neural network layers."""
# Create dataset and dataloader
dataset = SimpleDataset(size=10, num_features=3, num_classes=2)
dataloader = DataLoader(dataset, batch_size=4, shuffle=False)
# Get batch
batch_data, batch_labels = next(iter(dataloader))
# Apply dense layer
dense = Dense(input_size=3, output_size=2)
output = dense(batch_data)
# Verify result is tensor
assert isinstance(output, Tensor), "Layer output should be a Tensor"
assert output.shape == (4, 2), f"Expected output shape (4, 2), got {output.shape}"
assert output.dtype == np.float32, f"Expected float32, got {output.dtype}"
def test_dataloader_full_pipeline_integration(self):
"""Test DataLoader tensors in complete ML pipeline."""
# Create dataset and dataloader
dataset = SimpleDataset(size=12, num_features=4, num_classes=3)
dataloader = DataLoader(dataset, batch_size=6, shuffle=False)
# Get batch
batch_data, batch_labels = next(iter(dataloader))
# Apply full pipeline: Dense → ReLU → Dense
dense1 = Dense(input_size=4, output_size=8)
relu = ReLU()
dense2 = Dense(input_size=8, output_size=3)
# Forward pass
hidden = dense1(batch_data)
activated = relu(hidden)
output = dense2(activated)
# Verify all outputs are tensors
assert isinstance(hidden, Tensor), "Hidden layer should be Tensor"
assert isinstance(activated, Tensor), "Activated layer should be Tensor"
assert isinstance(output, Tensor), "Output layer should be Tensor"
# Verify shapes through pipeline
assert hidden.shape == (6, 8), f"Hidden shape should be (6, 8), got {hidden.shape}"
assert activated.shape == (6, 8), f"Activated shape should be (6, 8), got {activated.shape}"
assert output.shape == (6, 3), f"Output shape should be (6, 3), got {output.shape}"
class TestDataLoaderTensorBatching:
"""Test DataLoader batching with tensor integration."""
def test_different_batch_sizes(self):
"""Test DataLoader with different batch sizes produces correct tensors."""
dataset = SimpleDataset(size=20, num_features=3, num_classes=2)
batch_sizes = [1, 4, 8, 10]
for batch_size in batch_sizes:
dataloader = DataLoader(dataset, batch_size=batch_size, shuffle=False)
batch_data, batch_labels = next(iter(dataloader))
# Verify tensor shapes
assert batch_data.shape == (batch_size, 3), f"Data shape should be ({batch_size}, 3), got {batch_data.shape}"
assert batch_labels.shape == (batch_size,), f"Label shape should be ({batch_size},), got {batch_labels.shape}"
# Verify tensor types
assert isinstance(batch_data, Tensor), "Batch data should be Tensor"
assert isinstance(batch_labels, Tensor), "Batch labels should be Tensor"
def test_shuffling_preserves_tensor_integrity(self):
"""Test that shuffling preserves tensor data integrity."""
dataset = SimpleDataset(size=10, num_features=2, num_classes=2)
# Create two dataloaders with different shuffle settings
dataloader_no_shuffle = DataLoader(dataset, batch_size=5, shuffle=False)
dataloader_shuffle = DataLoader(dataset, batch_size=5, shuffle=True)
# Get batches
batch_no_shuffle = next(iter(dataloader_no_shuffle))
batch_shuffle = next(iter(dataloader_shuffle))
# Both should produce valid tensors
for batch_data, batch_labels in [batch_no_shuffle, batch_shuffle]:
assert isinstance(batch_data, Tensor), "Data should be Tensor"
assert isinstance(batch_labels, Tensor), "Labels should be Tensor"
assert batch_data.shape == (5, 2), f"Expected shape (5, 2), got {batch_data.shape}"
assert batch_labels.shape == (5,), f"Expected shape (5,), got {batch_labels.shape}"
def test_iteration_produces_consistent_tensors(self):
"""Test that iterating through DataLoader produces consistent tensors."""
dataset = SimpleDataset(size=12, num_features=3, num_classes=2)
dataloader = DataLoader(dataset, batch_size=4, shuffle=False)
batch_count = 0
for batch_data, batch_labels in dataloader:
batch_count += 1
# Verify each batch produces valid tensors
assert isinstance(batch_data, Tensor), f"Batch {batch_count} data should be Tensor"
assert isinstance(batch_labels, Tensor), f"Batch {batch_count} labels should be Tensor"
# Verify shapes (last batch might be smaller)
assert batch_data.shape[1] == 3, f"Feature dim should be 3, got {batch_data.shape[1]}"
assert batch_data.shape[0] == batch_labels.shape[0], "Batch and label sizes should match"
# Verify data types
assert batch_data.dtype == np.float32, "Data should be float32"
assert batch_labels.dtype == np.int32, "Labels should be int32"
# Should have processed all data
assert batch_count == 3, f"Expected 3 batches, got {batch_count}"
class TestDataLoaderTensorDataTypes:
"""Test DataLoader tensor data type handling."""
def test_float32_tensor_production(self):
"""Test DataLoader produces float32 tensors for data."""
dataset = SimpleDataset(size=8, num_features=2, num_classes=2)
dataloader = DataLoader(dataset, batch_size=4, shuffle=False)
batch_data, batch_labels = next(iter(dataloader))
# Verify data types
assert batch_data.dtype == np.float32, f"Expected float32, got {batch_data.dtype}"
assert isinstance(batch_data.data, np.ndarray), "Underlying data should be numpy array"
assert batch_data.data.dtype == np.float32, "Underlying array should be float32"
def test_int32_tensor_production(self):
"""Test DataLoader produces int32 tensors for labels."""
dataset = SimpleDataset(size=8, num_features=2, num_classes=3)
dataloader = DataLoader(dataset, batch_size=4, shuffle=False)
batch_data, batch_labels = next(iter(dataloader))
# Verify label types
assert batch_labels.dtype == np.int32, f"Expected int32, got {batch_labels.dtype}"
assert isinstance(batch_labels.data, np.ndarray), "Underlying labels should be numpy array"
assert batch_labels.data.dtype == np.int32, "Underlying array should be int32"
def test_tensor_data_ranges(self):
"""Test DataLoader produces tensors with reasonable data ranges."""
dataset = SimpleDataset(size=10, num_features=3, num_classes=2)
dataloader = DataLoader(dataset, batch_size=5, shuffle=False)
batch_data, batch_labels = next(iter(dataloader))
# Check data ranges
assert np.all(np.isfinite(batch_data.data)), "Data should be finite"
assert np.all(batch_labels.data >= 0), "Labels should be non-negative"
assert np.all(batch_labels.data < 2), "Labels should be less than num_classes"
class TestDataLoaderTensorRealisticScenarios:
"""Test DataLoader with realistic tensor scenarios."""
def test_training_loop_simulation(self):
"""Test DataLoader tensors in training loop simulation."""
dataset = SimpleDataset(size=16, num_features=4, num_classes=2)
dataloader = DataLoader(dataset, batch_size=8, shuffle=True)
# Simulate training loop
epoch_batches = 0
for epoch in range(2):
batch_count = 0
for batch_data, batch_labels in dataloader:
batch_count += 1
# Simulate forward pass
dense = Dense(input_size=4, output_size=2)
output = dense(batch_data)
# Verify tensor operations work
assert isinstance(output, Tensor), "Forward pass should produce Tensor"
assert output.shape == (8, 2), f"Expected shape (8, 2), got {output.shape}"
# Simulate loss computation (simplified)
loss = output.data.mean() # Simple loss
assert np.isfinite(loss), "Loss should be finite"
epoch_batches += 1
assert batch_count == 2, f"Expected 2 batches per epoch, got {batch_count}"
assert epoch_batches == 4, f"Expected 4 total batches, got {epoch_batches}"
def test_different_dataset_sizes(self):
"""Test DataLoader with different dataset sizes."""
test_cases = [
(5, 2), # Small dataset
(32, 8), # Medium dataset
(100, 16), # Large dataset
]
for dataset_size, batch_size in test_cases:
dataset = SimpleDataset(size=dataset_size, num_features=3, num_classes=2)
dataloader = DataLoader(dataset, batch_size=batch_size, shuffle=False)
total_samples = 0
for batch_data, batch_labels in dataloader:
# Verify tensor properties
assert isinstance(batch_data, Tensor), "Data should be Tensor"
assert isinstance(batch_labels, Tensor), "Labels should be Tensor"
# Count samples
total_samples += batch_data.shape[0]
# Verify shapes
assert batch_data.shape[1] == 3, "Feature dim should be 3"
assert batch_data.shape[0] == batch_labels.shape[0], "Batch sizes should match"
assert total_samples == dataset_size, f"Should process all {dataset_size} samples, got {total_samples}"
def test_dataloader_with_complex_pipeline(self):
"""Test DataLoader integration with complex neural network pipeline."""
dataset = SimpleDataset(size=20, num_features=5, num_classes=3)
dataloader = DataLoader(dataset, batch_size=10, shuffle=False)
# Create complex pipeline
dense1 = Dense(input_size=5, output_size=16)
relu1 = ReLU()
dense2 = Dense(input_size=16, output_size=8)
relu2 = ReLU()
dense3 = Dense(input_size=8, output_size=3)
# Process batches
for batch_data, batch_labels in dataloader:
# Forward pass through complex pipeline
x = dense1(batch_data)
x = relu1(x)
x = dense2(x)
x = relu2(x)
output = dense3(x)
# Verify final output
assert isinstance(output, Tensor), "Final output should be Tensor"
assert output.shape == (10, 3), f"Expected shape (10, 3), got {output.shape}"
assert output.dtype == np.float32, "Output should be float32"
assert np.all(np.isfinite(output.data)), "Output should be finite"
def test_dataloader_memory_efficiency(self):
"""Test DataLoader memory efficiency with tensor operations."""
dataset = SimpleDataset(size=50, num_features=10, num_classes=5)
dataloader = DataLoader(dataset, batch_size=25, shuffle=False)
# Process batches and verify memory usage patterns
processed_batches = []
for batch_data, batch_labels in dataloader:
# Store tensor info (not the actual tensors to avoid memory issues)
batch_info = {
'data_shape': batch_data.shape,
'label_shape': batch_labels.shape,
'data_type': batch_data.dtype,
'label_type': batch_labels.dtype
}
processed_batches.append(batch_info)
# Verify tensors are properly formed
assert isinstance(batch_data, Tensor), "Data should be Tensor"
assert isinstance(batch_labels, Tensor), "Labels should be Tensor"
# Verify we processed expected number of batches
assert len(processed_batches) == 2, f"Expected 2 batches, got {len(processed_batches)}"
# Verify consistency across batches
for i, batch_info in enumerate(processed_batches):
assert batch_info['data_shape'][1] == 10, f"Batch {i} should have 10 features"
assert batch_info['data_type'] == np.float32, f"Batch {i} data should be float32"
assert batch_info['label_type'] == np.int32, f"Batch {i} labels should be int32"
class TestCustomDatasetIntegration:
"""Test custom dataset integration with tensor operations."""
def test_custom_dataset_with_tensors(self):
"""Test custom dataset that produces tensors works with DataLoader."""
class CustomTensorDataset(Dataset):
def __init__(self, size: int):
self.size = size
self.data = [Tensor(np.random.rand(3).astype(np.float32)) for _ in range(size)]
self.labels = [Tensor(np.random.randint(0, 2, dtype=np.int32)) for _ in range(size)]
def __len__(self):
return self.size
def __getitem__(self, index):
return self.data[index], self.labels[index]
# Create custom dataset and dataloader
dataset = CustomTensorDataset(size=12)
dataloader = DataLoader(dataset, batch_size=4, shuffle=False)
# Test integration
batch_data, batch_labels = next(iter(dataloader))
# Verify tensor properties
assert isinstance(batch_data, Tensor), "Batch data should be Tensor"
assert isinstance(batch_labels, Tensor), "Batch labels should be Tensor"
assert batch_data.shape == (4, 3), f"Expected shape (4, 3), got {batch_data.shape}"
assert batch_labels.shape == (4,), f"Expected shape (4,), got {batch_labels.shape}"
# Test with neural network components
dense = Dense(input_size=3, output_size=2)
output = dense(batch_data)
assert isinstance(output, Tensor), "Dense output should be Tensor"
assert output.shape == (4, 2), f"Expected shape (4, 2), got {output.shape}"

File diff suppressed because it is too large Load Diff

View File

@@ -1,336 +0,0 @@
"""
Module 06: Spatial - Core Functionality Tests
Tests convolutional layers and spatial operations for computer vision
"""
import numpy as np
import sys
from pathlib import Path
# Add project root to path
sys.path.insert(0, str(Path(__file__).parent.parent.parent))
class TestConv2DLayer:
"""Test 2D convolution layer."""
def test_conv2d_creation(self):
"""Test Conv2D layer creation."""
try:
from tinytorch.core.spatial import Conv2D
conv = Conv2D(in_channels=3, out_channels=16, kernel_size=3)
assert conv.in_channels == 3
assert conv.out_channels == 16
assert conv.kernel_size == 3
except ImportError:
assert True, "Conv2D not implemented yet"
def test_conv2d_weight_shape(self):
"""Test Conv2D weight tensor has correct shape."""
try:
from tinytorch.core.spatial import Conv2D
conv = Conv2D(in_channels=3, out_channels=16, kernel_size=5)
# Weights should be (out_channels, in_channels, kernel_height, kernel_width)
expected_shape = (16, 3, 5, 5)
if hasattr(conv, 'weights'):
assert conv.weights.shape == expected_shape
elif hasattr(conv, 'weight'):
assert conv.weight.shape == expected_shape
except ImportError:
assert True, "Conv2D weights not implemented yet"
def test_conv2d_forward_shape(self):
"""Test Conv2D forward pass output shape."""
try:
from tinytorch.core.spatial import Conv2D
from tinytorch.core.tensor import Tensor
conv = Conv2D(in_channels=3, out_channels=16, kernel_size=3)
# Input: (batch_size, height, width, channels) - NHWC format
x = Tensor(np.random.randn(8, 32, 32, 3))
output = conv(x)
# With kernel_size=3 and no padding, output should be 30x30
# Output: (batch_size, new_height, new_width, out_channels)
expected_shape = (8, 30, 30, 16)
assert output.shape == expected_shape
except ImportError:
assert True, "Conv2D forward pass not implemented yet"
def test_conv2d_simple_convolution(self):
"""Test simple convolution operation."""
try:
from tinytorch.core.spatial import Conv2D
from tinytorch.core.tensor import Tensor
# Simple 1-channel convolution
conv = Conv2D(in_channels=1, out_channels=1, kernel_size=3)
# Set known kernel for testing
if hasattr(conv, 'weights'):
conv.weights = Tensor(np.ones((1, 1, 3, 3))) # Sum kernel
elif hasattr(conv, 'weight'):
conv.weight = Tensor(np.ones((1, 1, 3, 3)))
# Simple input
x = Tensor(np.ones((1, 5, 5, 1))) # All ones
output = conv(x)
# With all-ones input and all-ones kernel, output should be 9 everywhere
expected_value = 9.0
if output.shape == (1, 3, 3, 1):
assert np.allclose(output.data, expected_value)
except ImportError:
assert True, "Conv2D convolution operation not implemented yet"
class TestPoolingLayers:
"""Test pooling layers."""
def test_maxpool2d_creation(self):
"""Test MaxPool2D layer creation."""
try:
from tinytorch.core.spatial import MaxPool2D
pool = MaxPool2D(pool_size=2)
assert pool.pool_size == 2
except ImportError:
assert True, "MaxPool2D not implemented yet"
def test_maxpool2d_forward_shape(self):
"""Test MaxPool2D forward pass output shape."""
try:
from tinytorch.core.spatial import MaxPool2D
from tinytorch.core.tensor import Tensor
pool = MaxPool2D(pool_size=2)
# Input: (batch_size, height, width, channels)
x = Tensor(np.random.randn(4, 28, 28, 32))
output = pool(x)
# Pooling by 2 should halve spatial dimensions
expected_shape = (4, 14, 14, 32)
assert output.shape == expected_shape
except ImportError:
assert True, "MaxPool2D forward pass not implemented yet"
def test_maxpool2d_operation(self):
"""Test MaxPool2D actually finds maximum values."""
try:
from tinytorch.core.spatial import MaxPool2D
from tinytorch.core.tensor import Tensor
pool = MaxPool2D(pool_size=2)
# Create input with known pattern
# 4x4 input with values [1,2,3,4] in each 2x2 block
x_data = np.array([[[[1, 2],
[3, 4]],
[[5, 6],
[7, 8]]]]) # Shape: (1, 2, 2, 2)
x = Tensor(x_data)
output = pool(x)
# MaxPool should select [4, 8] - the max from each 2x2 region
if output.shape == (1, 1, 1, 2):
assert output.data[0, 0, 0, 0] == 4 # Max of [1,2,3,4]
assert output.data[0, 0, 0, 1] == 8 # Max of [5,6,7,8]
except ImportError:
assert True, "MaxPool2D operation not implemented yet"
def test_avgpool2d_operation(self):
"""Test average pooling."""
try:
from tinytorch.core.spatial import AvgPool2D
from tinytorch.core.tensor import Tensor
pool = AvgPool2D(pool_size=2)
# 2x2 input with known values
x_data = np.array([[[[1, 2],
[3, 4]]]]) # Shape: (1, 2, 2, 1)
x = Tensor(x_data)
output = pool(x)
# Average should be (1+2+3+4)/4 = 2.5
if output.shape == (1, 1, 1, 1):
assert np.isclose(output.data[0, 0, 0, 0], 2.5)
except ImportError:
assert True, "AvgPool2D not implemented yet"
class TestSpatialUtilities:
"""Test spatial operation utilities."""
def test_padding_operation(self):
"""Test padding functionality."""
try:
from tinytorch.core.spatial import pad2d
from tinytorch.core.tensor import Tensor
# Simple 2x2 input
x = Tensor(np.array([[[[1, 2],
[3, 4]]]])) # Shape: (1, 2, 2, 1)
# Pad with 1 pixel on all sides
padded = pad2d(x, padding=1, value=0)
# Should become 4x4 with zeros around border
expected_shape = (1, 4, 4, 1)
assert padded.shape == expected_shape
# Center should contain original values
assert padded.data[0, 1, 1, 0] == 1
assert padded.data[0, 1, 2, 0] == 2
assert padded.data[0, 2, 1, 0] == 3
assert padded.data[0, 2, 2, 0] == 4
except ImportError:
assert True, "Padding operation not implemented yet"
def test_im2col_operation(self):
"""Test im2col operation for efficient convolution."""
try:
from tinytorch.core.spatial import im2col
from tinytorch.core.tensor import Tensor
# Simple 3x3 input
x = Tensor(np.arange(9).reshape(1, 3, 3, 1))
# Extract 2x2 patches
patches = im2col(x, kernel_size=2, stride=1)
# Should get 4 patches (2x2 sliding window on 3x3 input)
# Each patch should have 4 values (2x2 kernel)
expected_num_patches = 4
expected_patch_size = 4
if hasattr(patches, 'shape'):
assert patches.shape[1] == expected_patch_size
except ImportError:
assert True, "im2col operation not implemented yet"
def test_spatial_dimensions(self):
"""Test spatial dimension calculations."""
try:
from tinytorch.core.spatial import calc_output_size
# Common convolution size calculation
input_size = 32
kernel_size = 5
stride = 1
padding = 2
output_size = calc_output_size(input_size, kernel_size, stride, padding)
# Formula: (input + 2*padding - kernel) / stride + 1
expected = (32 + 2*2 - 5) // 1 + 1 # = 32
assert output_size == expected
except ImportError:
# Manual calculation test
input_size = 32
kernel_size = 5
stride = 1
padding = 2
output_size = (input_size + 2*padding - kernel_size) // stride + 1
assert output_size == 32
class TestCNNArchitecture:
"""Test CNN architecture components working together."""
def test_conv_relu_pool_chain(self):
"""Test Conv -> ReLU -> Pool chain."""
try:
from tinytorch.core.spatial import Conv2D, MaxPool2D
from tinytorch.core.activations import ReLU
from tinytorch.core.tensor import Tensor
# Build simple CNN block
conv = Conv2D(3, 16, kernel_size=3)
relu = ReLU()
pool = MaxPool2D(pool_size=2)
# Input image
x = Tensor(np.random.randn(1, 32, 32, 3))
# Forward pass
h1 = conv(x) # (1, 30, 30, 16)
h2 = relu(h1) # (1, 30, 30, 16)
output = pool(h2) # (1, 15, 15, 16)
expected_shape = (1, 15, 15, 16)
assert output.shape == expected_shape
except ImportError:
assert True, "CNN architecture chaining not ready yet"
def test_feature_map_progression(self):
"""Test feature map size progression through CNN."""
try:
from tinytorch.core.spatial import Conv2D, MaxPool2D
from tinytorch.core.tensor import Tensor
# Typical CNN progression: increase channels, decrease spatial size
conv1 = Conv2D(3, 32, kernel_size=3) # 3 -> 32 channels
pool1 = MaxPool2D(pool_size=2) # /2 spatial size
conv2 = Conv2D(32, 64, kernel_size=3) # 32 -> 64 channels
pool2 = MaxPool2D(pool_size=2) # /2 spatial size
x = Tensor(np.random.randn(1, 32, 32, 3)) # Start: 32x32x3
h1 = conv1(x) # 30x30x32
h2 = pool1(h1) # 15x15x32
h3 = conv2(h2) # 13x13x64
h4 = pool2(h3) # 6x6x64 (or 7x7x64)
# Should progressively reduce spatial size, increase channels
assert h1.shape[3] == 32 # More channels
assert h2.shape[1] < h1.shape[1] # Smaller spatial
assert h3.shape[3] == 64 # Even more channels
assert h4.shape[1] < h3.shape[1] # Even smaller spatial
except ImportError:
assert True, "Feature map progression not ready yet"
def test_global_average_pooling(self):
"""Test global average pooling for classification."""
try:
from tinytorch.core.spatial import GlobalAvgPool2D
from tinytorch.core.tensor import Tensor
gap = GlobalAvgPool2D()
# Feature maps from CNN
x = Tensor(np.random.randn(1, 7, 7, 512)) # Typical CNN output
output = gap(x)
# Should average over spatial dimensions
expected_shape = (1, 1, 1, 512) # or (1, 512)
assert output.shape == expected_shape or output.shape == (1, 512)
except ImportError:
# Manual global average pooling
x_data = np.random.randn(1, 7, 7, 512)
output_data = np.mean(x_data, axis=(1, 2), keepdims=True)
assert output_data.shape == (1, 1, 1, 512)

View File

@@ -1,266 +0,0 @@
"""
Integration Tests: Tensor ↔ CNN Operations
Tests the integration between core Tensor data structures and CNN operations:
- Conv2D operations with real tensors
- Flatten operations with real tensors
- CNN data flow with proper tensor shapes
- Error handling with real tensor inputs
These tests verify that CNN operations work correctly with real TinyTorch tensors,
not mocks or synthetic data.
"""
import pytest
import numpy as np
from tinytorch.core.tensor import Tensor
from tinytorch.core.cnn import Conv2D, conv2d_naive, flatten
class TestTensorCNNIntegration:
"""Test integration between Tensor and CNN components."""
def test_conv2d_naive_with_real_tensors(self):
"""Test conv2d_naive function with real tensor data."""
# Create real tensor data
input_data = np.array([[1.0, 2.0, 3.0],
[4.0, 5.0, 6.0],
[7.0, 8.0, 9.0]], dtype=np.float32)
kernel_data = np.array([[1.0, 0.0],
[0.0, -1.0]], dtype=np.float32)
# Test with real numpy arrays (function takes arrays, not tensors)
result = conv2d_naive(input_data, kernel_data)
# Verify correct shape
assert result.shape == (2, 2), f"Expected shape (2, 2), got {result.shape}"
# Verify correct computation
expected = np.array([[-4.0, -4.0],
[-4.0, -4.0]], dtype=np.float32)
np.testing.assert_array_almost_equal(result, expected, decimal=5)
def test_conv2d_layer_with_real_tensors(self):
"""Test Conv2D layer with real tensor inputs."""
# Create real tensor input
input_tensor = Tensor([[1.0, 2.0, 3.0],
[4.0, 5.0, 6.0],
[7.0, 8.0, 9.0]])
# Create Conv2D layer
conv_layer = Conv2D(kernel_size=(2, 2))
# Test forward pass
output = conv_layer(input_tensor)
# Verify output is a tensor
assert isinstance(output, Tensor), "Conv2D output should be a Tensor"
# Verify correct shape
assert output.shape == (2, 2), f"Expected shape (2, 2), got {output.shape}"
# Verify data type consistency
assert output.dtype == np.float32, f"Expected float32, got {output.dtype}"
def test_flatten_with_real_tensors(self):
"""Test flatten function with real tensor inputs."""
# Create real 2D tensor
input_tensor = Tensor([[1.0, 2.0], [3.0, 4.0]])
# Test flatten
output = flatten(input_tensor)
# Verify output is a tensor
assert isinstance(output, Tensor), "Flatten output should be a Tensor"
# Verify correct shape (batch dimension added)
assert output.shape == (1, 4), f"Expected shape (1, 4), got {output.shape}"
# Verify correct data
expected_data = np.array([[1.0, 2.0, 3.0, 4.0]], dtype=np.float32)
np.testing.assert_array_almost_equal(output.data, expected_data, decimal=5)
def test_cnn_pipeline_with_real_tensors(self):
"""Test complete CNN pipeline with real tensor data flow."""
# Create real input tensor (small image)
input_tensor = Tensor([[1.0, 2.0, 3.0, 4.0],
[5.0, 6.0, 7.0, 8.0],
[9.0, 10.0, 11.0, 12.0],
[13.0, 14.0, 15.0, 16.0]])
# Create CNN components
conv_layer = Conv2D(kernel_size=(2, 2))
# Test complete pipeline
conv_output = conv_layer(input_tensor)
flattened_output = flatten(conv_output)
# Verify shapes through pipeline
assert conv_output.shape == (3, 3), f"Conv output shape should be (3, 3), got {conv_output.shape}"
assert flattened_output.shape == (1, 9), f"Flattened shape should be (1, 9), got {flattened_output.shape}"
# Verify all outputs are tensors
assert isinstance(conv_output, Tensor), "Conv output should be a Tensor"
assert isinstance(flattened_output, Tensor), "Flattened output should be a Tensor"
# Verify data types
assert conv_output.dtype == np.float32, "Conv output should be float32"
assert flattened_output.dtype == np.float32, "Flattened output should be float32"
class TestTensorCNNShapeHandling:
"""Test CNN operations with various tensor shapes."""
def test_conv2d_with_different_input_shapes(self):
"""Test Conv2D with different input tensor shapes."""
# Test with different input sizes
test_cases = [
(Tensor([[1.0, 2.0], [3.0, 4.0]]), (2, 2), (1, 1)), # Minimal size
(Tensor([[1.0, 2.0, 3.0], [4.0, 5.0, 6.0], [7.0, 8.0, 9.0]]), (2, 2), (2, 2)), # Standard size
(Tensor(np.random.rand(5, 5).astype(np.float32)), (3, 3), (3, 3)), # Larger input
]
for input_tensor, kernel_size, expected_shape in test_cases:
conv_layer = Conv2D(kernel_size=kernel_size)
output = conv_layer(input_tensor)
assert output.shape == expected_shape, f"For input {input_tensor.shape} and kernel {kernel_size}, expected {expected_shape}, got {output.shape}"
assert isinstance(output, Tensor), "Output should be a Tensor"
def test_flatten_with_different_shapes(self):
"""Test flatten with different tensor shapes."""
test_cases = [
(Tensor([[1.0, 2.0]]), (1, 2)), # 1x2 → 1x2
(Tensor([[1.0, 2.0], [3.0, 4.0]]), (1, 4)), # 2x2 → 1x4
(Tensor([[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]]), (1, 6)), # 2x3 → 1x6
]
for input_tensor, expected_shape in test_cases:
output = flatten(input_tensor)
assert output.shape == expected_shape, f"For input {input_tensor.shape}, expected {expected_shape}, got {output.shape}"
assert isinstance(output, Tensor), "Output should be a Tensor"
class TestTensorCNNDataTypes:
"""Test CNN operations with different tensor data types."""
def test_conv2d_preserves_data_types(self):
"""Test that Conv2D preserves appropriate data types."""
# Create input with specific dtype
input_data = np.array([[1.0, 2.0, 3.0],
[4.0, 5.0, 6.0],
[7.0, 8.0, 9.0]], dtype=np.float32)
input_tensor = Tensor(input_data)
conv_layer = Conv2D(kernel_size=(2, 2))
output = conv_layer(input_tensor)
# Verify data type consistency
assert output.dtype == np.float32, f"Expected float32, got {output.dtype}"
assert input_tensor.dtype == np.float32, f"Input should be float32, got {input_tensor.dtype}"
def test_flatten_preserves_data_types(self):
"""Test that flatten preserves tensor data types."""
# Test with float32
input_float = Tensor(np.array([[1.0, 2.0], [3.0, 4.0]], dtype=np.float32))
output_float = flatten(input_float)
assert output_float.dtype == np.float32, f"Expected float32, got {output_float.dtype}"
# Test with int32
input_int = Tensor(np.array([[1, 2], [3, 4]], dtype=np.int32))
output_int = flatten(input_int)
assert output_int.dtype == np.int32, f"Expected int32, got {output_int.dtype}"
class TestTensorCNNErrorHandling:
"""Test error handling in CNN operations with real tensors."""
def test_conv2d_with_minimal_valid_tensor(self):
"""Test Conv2D with minimal valid tensor input."""
# Test with minimal valid input (2x2 with 2x2 kernel)
minimal_input = Tensor([[1.0, 2.0], [3.0, 4.0]])
conv_layer = Conv2D(kernel_size=(2, 2))
# Should produce 1x1 output
output = conv_layer(minimal_input)
assert output.shape == (1, 1), f"Expected (1, 1), got {output.shape}"
assert isinstance(output, Tensor), "Output should be a Tensor"
def test_conv2d_naive_with_edge_case_shapes(self):
"""Test conv2d_naive with edge case but valid shapes."""
# Test with minimal valid case
input_data = np.array([[1.0, 2.0], [3.0, 4.0]], dtype=np.float32)
kernel_data = np.array([[1.0, 0.0], [0.0, -1.0]], dtype=np.float32)
# Should produce 1x1 output
result = conv2d_naive(input_data, kernel_data)
assert result.shape == (1, 1), f"Expected (1, 1), got {result.shape}"
assert isinstance(result, np.ndarray), "Result should be numpy array"
class TestTensorCNNRealisticScenarios:
"""Test CNN operations with realistic tensor scenarios."""
def test_image_processing_pipeline(self):
"""Test CNN operations with image-like tensor data."""
# Create realistic image-like data (8x8 "image")
image_data = np.random.rand(8, 8).astype(np.float32)
image_tensor = Tensor(image_data)
# Apply 3x3 convolution (common in CNNs)
conv_layer = Conv2D(kernel_size=(3, 3))
features = conv_layer(image_tensor)
# Flatten for fully connected layer
flattened = flatten(features)
# Verify realistic shapes
assert features.shape == (6, 6), f"Expected (6, 6) feature map, got {features.shape}"
assert flattened.shape == (1, 36), f"Expected (1, 36) flattened, got {flattened.shape}"
# Verify realistic data ranges
assert np.all(np.isfinite(features.data)), "Features should be finite"
assert np.all(np.isfinite(flattened.data)), "Flattened data should be finite"
def test_multiple_convolutions(self):
"""Test multiple convolution operations in sequence."""
# Start with larger input
input_tensor = Tensor(np.random.rand(6, 6).astype(np.float32))
# Apply first convolution
conv1 = Conv2D(kernel_size=(3, 3))
features1 = conv1(input_tensor)
# Apply second convolution
conv2 = Conv2D(kernel_size=(2, 2))
features2 = conv2(features1)
# Verify shape progression
assert features1.shape == (4, 4), f"First conv should produce (4, 4), got {features1.shape}"
assert features2.shape == (3, 3), f"Second conv should produce (3, 3), got {features2.shape}"
# Verify all are tensors
assert isinstance(features1, Tensor), "First features should be Tensor"
assert isinstance(features2, Tensor), "Second features should be Tensor"
def test_conv_to_dense_integration_preparation(self):
"""Test CNN output preparation for dense layer integration."""
# Create input that will work with dense layers
input_tensor = Tensor(np.random.rand(5, 5).astype(np.float32))
# Apply convolution
conv_layer = Conv2D(kernel_size=(2, 2))
conv_output = conv_layer(input_tensor)
# Flatten for dense layer
flattened = flatten(conv_output)
# Verify shape is suitable for dense layer (batch_size, features)
assert len(flattened.shape) == 2, f"Flattened should be 2D, got {flattened.shape}"
assert flattened.shape[0] == 1, f"Batch size should be 1, got {flattened.shape[0]}"
# Verify data is ready for dense layer consumption
assert flattened.dtype == np.float32, "Data should be float32 for dense layers"
assert np.all(np.isfinite(flattened.data)), "Data should be finite for dense layers"