""" Module 03: Activations - Integration Tests Tests that activations work with Tensor and enable non-linear networks """ 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 TestActivationTensorIntegration: """Test activations work seamlessly with Tensor.""" def test_relu_with_tensor(self): """Test ReLU activation with Tensor inputs.""" from tinytorch.core.activations import ReLU from tinytorch.core.tensor import Tensor relu = ReLU() x = Tensor(np.array([-2, -1, 0, 1, 2])) output = relu(x) assert isinstance(output, Tensor) assert np.array_equal(output.data, [0, 0, 0, 1, 2]) assert output.shape == x.shape def test_sigmoid_with_tensor(self): """Test Sigmoid activation with Tensor inputs.""" from tinytorch.core.activations import Sigmoid from tinytorch.core.tensor import Tensor sigmoid = Sigmoid() x = Tensor(np.array([0, 1, -1, 2])) output = sigmoid(x) assert isinstance(output, Tensor) assert output.shape == x.shape assert np.all(output.data >= 0) and np.all(output.data <= 1) assert np.isclose(output.data[0], 0.5, atol=1e-6) # sigmoid(0) = 0.5 def test_tanh_with_tensor(self): """Test Tanh activation with Tensor inputs.""" from tinytorch.core.activations import Tanh from tinytorch.core.tensor import Tensor tanh = Tanh() x = Tensor(np.array([0, 1, -1])) output = tanh(x) assert isinstance(output, Tensor) assert output.shape == x.shape assert np.all(output.data >= -1) and np.all(output.data <= 1) assert np.isclose(output.data[0], 0, atol=1e-6) # tanh(0) = 0 class TestActivationNetworkIntegration: """Test activations enable non-linear neural networks.""" def test_xor_requires_nonlinearity(self): """Test that XOR problem demonstrates need for activations.""" from tinytorch.core.tensor import Tensor from tinytorch.core.activations import ReLU # XOR inputs and targets X = Tensor(np.array([[0, 0], [0, 1], [1, 0], [1, 1]])) Y_target = np.array([0, 1, 1, 0]) # Simple linear transformation (no activation) W = Tensor(np.array([[1, -1], [-1, 1]])) # Arbitrary weights linear_output = Tensor(X.data @ W.data) # With ReLU activation relu = ReLU() nonlinear_output = relu(linear_output) # Outputs should be different (activation adds non-linearity) assert not np.array_equal(linear_output.data, nonlinear_output.data) assert nonlinear_output.shape == linear_output.shape def test_activation_chaining(self): """Test chaining multiple activations.""" from tinytorch.core.activations import ReLU, Sigmoid from tinytorch.core.tensor import Tensor x = Tensor(np.random.randn(10, 5)) relu = ReLU() sigmoid = Sigmoid() # Chain: input -> ReLU -> Sigmoid h1 = relu(x) output = sigmoid(h1) assert output.shape == x.shape assert np.all(output.data >= 0) and np.all(output.data <= 1) def test_activation_with_negative_inputs(self): """Test activations handle negative inputs correctly.""" from tinytorch.core.activations import ReLU, Sigmoid, Tanh from tinytorch.core.tensor import Tensor x = Tensor(np.array([-5, -1, 0, 1, 5])) relu = ReLU() sigmoid = Sigmoid() tanh = Tanh() # Test each activation with negative inputs relu_out = relu(x) sig_out = sigmoid(x) tanh_out = tanh(x) # ReLU zeros out negatives assert np.array_equal(relu_out.data, [0, 0, 0, 1, 5]) # Sigmoid maps all to (0,1) assert np.all(sig_out.data > 0) and np.all(sig_out.data < 1) # Tanh maps all to (-1,1) assert np.all(tanh_out.data > -1) and np.all(tanh_out.data < 1) class TestActivationDerivatives: """Test activation derivatives for future gradient computation.""" def test_relu_derivative(self): """Test ReLU derivative behavior.""" from tinytorch.core.activations import ReLU from tinytorch.core.tensor import Tensor relu = ReLU() x = Tensor(np.array([-1, 0, 1])) output = relu(x) # ReLU derivative is 0 for x < 0, undefined at 0, 1 for x > 0 # For implementation, we usually use: derivative = (output > 0) derivative_mask = output.data > 0 expected_derivative = np.array([False, False, True]) assert np.array_equal(derivative_mask, expected_derivative) def test_sigmoid_derivative_property(self): """Test sigmoid has the derivative property: σ'(x) = σ(x)(1-σ(x)).""" from tinytorch.core.activations import Sigmoid from tinytorch.core.tensor import Tensor sigmoid = Sigmoid() x = Tensor(np.array([0, 1, -1])) output = sigmoid(x) # Sigmoid derivative: σ(x) * (1 - σ(x)) derivative = output.data * (1 - output.data) # At x=0, σ(0)=0.5, so derivative should be 0.5*0.5=0.25 assert np.isclose(derivative[0], 0.25, atol=1e-6)