""" Checkpoint 4: Networks (After Module 5 - Dense) Question: "Can I build complete multi-layer neural networks?" """ import numpy as np import pytest def test_checkpoint_04_networks(): """ Checkpoint 4: Networks Validates that students can combine layers into complete multi-layer perceptrons - the first step toward building real neural networks that can solve complex problems. """ print("\nšŸ”— Checkpoint 4: Networks") print("=" * 50) try: from tinytorch.core.tensor import Tensor from tinytorch.core.layers import Linear from tinytorch.core.activations import ReLU, Sigmoid except ImportError as e: pytest.fail(f"āŒ Cannot import required classes - complete Modules 2-5 first: {e}") # Test 1: Simple 2-layer network print("šŸ—ļø Testing 2-layer network construction...") input_layer = Linear(input_size=4, output_size=8) output_layer = Linear(input_size=8, output_size=3) activation = ReLU() # Test network architecture sample_input = Tensor(np.random.randn(2, 4)) # 2 samples, 4 features # Forward pass through network hidden = input_layer(sample_input) hidden_activated = activation(hidden) output = output_layer(hidden_activated) assert output.shape == (2, 3), f"Network output should be (2, 3), got {output.shape}" print(f"āœ… 2-layer network: {sample_input.shape} → {hidden.shape} → {output.shape}") # Test 2: Deep network (3+ layers) print("šŸ¢ Testing deep network construction...") layer1 = Linear(10, 16) layer2 = Linear(16, 8) layer3 = Linear(8, 4) layer4 = Linear(4, 1) relu = ReLU() sigmoid = Sigmoid() # Build a classifier network x = Tensor(np.random.randn(1, 10)) # Deep forward pass h1 = relu(layer1(x)) h2 = relu(layer2(h1)) h3 = relu(layer3(h2)) prediction = sigmoid(layer4(h3)) assert prediction.shape == (1, 1), f"Prediction shape should be (1, 1), got {prediction.shape}" assert 0 <= prediction.data[0, 0] <= 1, "Sigmoid output should be between 0 and 1" print(f"āœ… Deep network: {x.shape} → 16 → 8 → 4 → {prediction.shape}") # Test 3: Network with different architectures print("šŸ”§ Testing flexible architectures...") # Wide network wide_net = [ Linear(5, 50), ReLU(), Linear(50, 50), ReLU(), Linear(50, 10) ] # Narrow network narrow_net = [ Linear(20, 10), ReLU(), Linear(10, 5), ReLU(), Linear(5, 2) ] # Test both architectures wide_input = Tensor(np.random.randn(1, 5)) narrow_input = Tensor(np.random.randn(1, 20)) # Wide network forward pass wide_x = wide_input for layer in wide_net: wide_x = layer(wide_x) # Narrow network forward pass narrow_x = narrow_input for layer in narrow_net: narrow_x = layer(narrow_x) assert wide_x.shape == (1, 10), f"Wide network output should be (1, 10), got {wide_x.shape}" assert narrow_x.shape == (1, 2), f"Narrow network output should be (1, 2), got {narrow_x.shape}" print(f"āœ… Flexible architectures: wide{wide_x.shape}, narrow{narrow_x.shape}") # Test 4: Parameter counting across network print("šŸ“Š Testing network parameter counting...") total_params = ( input_layer.weights.data.size + input_layer.bias.data.size + output_layer.weights.data.size + output_layer.bias.data.size ) expected_params = (4*8 + 8) + (8*3 + 3) # (weights + bias) for each layer assert total_params == expected_params, f"Total parameters should be {expected_params}, got {total_params}" print(f"āœ… Network parameters: {total_params} learnable parameters") # Test 5: Batch processing through network print("šŸ“¦ Testing batch processing...") batch_input = Tensor(np.random.randn(5, 4)) # 5 samples batch_hidden = input_layer(batch_input) batch_hidden_activated = activation(batch_hidden) batch_output = output_layer(batch_hidden_activated) assert batch_output.shape == (5, 3), f"Batch output should be (5, 3), got {batch_output.shape}" print(f"āœ… Batch processing: {batch_input.shape} → network → {batch_output.shape}") # Test 6: Universal approximation demonstration print("šŸŽÆ Testing nonlinear function approximation...") # Create a simple nonlinear function to approximate: f(x) = x^2 def target_function(x): return x * x # Generate training data x_data = np.linspace(-2, 2, 10).reshape(-1, 1) y_target = target_function(x_data) # Simple approximator network approx_net = [ Linear(1, 5), ReLU(), Linear(5, 5), ReLU(), Linear(5, 1) ] # Test that network can process the data x_tensor = Tensor(x_data) net_output = x_tensor for layer in approx_net: net_output = layer(net_output) assert net_output.shape == y_target.shape, f"Approximator output shape mismatch: {net_output.shape} vs {y_target.shape}" print(f"āœ… Function approximation setup: {x_tensor.shape} → network → {net_output.shape}") print("\nšŸŽ‰ Networks Complete!") print("šŸ“ You can now build complete multi-layer neural networks") print("šŸ”§ Built capabilities: Multi-layer perceptrons, deep networks, flexible architectures") print("🧠 Breakthrough: You have complete networks that can learn complex patterns!") print("šŸŽÆ Next: Add automatic differentiation for learning") if __name__ == "__main__": test_checkpoint_04_networks()