# AUTOGENERATED! DO NOT EDIT! File to edit: ../../modules/source/06_spatial/spatial_dev.ipynb. # %% auto 0 __all__ = ['conv2d_naive', 'Conv2D', 'flatten'] # %% ../../modules/source/06_spatial/spatial_dev.ipynb 1 import numpy as np import os import sys from typing import List, Tuple, Optional import matplotlib.pyplot as plt # Import from the main package - try package first, then local modules try: from tinytorch.core.tensor import Tensor from tinytorch.core.layers import Dense from tinytorch.core.activations import ReLU except ImportError: # For development, import from local modules sys.path.append(os.path.join(os.path.dirname(__file__), '..', '01_tensor')) sys.path.append(os.path.join(os.path.dirname(__file__), '..', '02_activations')) sys.path.append(os.path.join(os.path.dirname(__file__), '..', '03_layers')) from tensor_dev import Tensor from activations_dev import ReLU from layers_dev import Dense # %% ../../modules/source/06_spatial/spatial_dev.ipynb 2 def _should_show_plots(): """Check if we should show plots (disable during testing)""" # Check multiple conditions that indicate we're in test mode is_pytest = ( 'pytest' in sys.modules or 'test' in sys.argv or os.environ.get('PYTEST_CURRENT_TEST') is not None or any('test' in arg for arg in sys.argv) or any('pytest' in arg for arg in sys.argv) ) # Show plots in development mode (when not in test mode) return not is_pytest # %% ../../modules/source/06_spatial/spatial_dev.ipynb 6 def conv2d_naive(input: np.ndarray, kernel: np.ndarray) -> np.ndarray: """ Naive 2D convolution (single channel, no stride, no padding). Args: input: 2D input array (H, W) kernel: 2D filter (kH, kW) Returns: 2D output array (H-kH+1, W-kW+1) TODO: Implement the sliding window convolution using for-loops. APPROACH: 1. Get input dimensions: H, W = input.shape 2. Get kernel dimensions: kH, kW = kernel.shape 3. Calculate output dimensions: out_H = H - kH + 1, out_W = W - kW + 1 4. Create output array: np.zeros((out_H, out_W)) 5. Use nested loops to slide the kernel: - i loop: output rows (0 to out_H-1) - j loop: output columns (0 to out_W-1) - di loop: kernel rows (0 to kH-1) - dj loop: kernel columns (0 to kW-1) 6. For each (i,j), compute: output[i,j] += input[i+di, j+dj] * kernel[di, dj] EXAMPLE: Input: [[1, 2, 3], Kernel: [[1, 0], [4, 5, 6], [0, -1]] [7, 8, 9]] Output[0,0] = 1*1 + 2*0 + 4*0 + 5*(-1) = 1 - 5 = -4 Output[0,1] = 2*1 + 3*0 + 5*0 + 6*(-1) = 2 - 6 = -4 Output[1,0] = 4*1 + 5*0 + 7*0 + 8*(-1) = 4 - 8 = -4 Output[1,1] = 5*1 + 6*0 + 8*0 + 9*(-1) = 5 - 9 = -4 HINTS: - Start with output = np.zeros((out_H, out_W)) - Use four nested loops: for i in range(out_H): for j in range(out_W): for di in range(kH): for dj in range(kW): - Accumulate the sum: output[i,j] += input[i+di, j+dj] * kernel[di, dj] """ ### BEGIN SOLUTION # Get input and kernel dimensions H, W = input.shape kH, kW = kernel.shape # Calculate output dimensions out_H, out_W = H - kH + 1, W - kW + 1 # Initialize output array output = np.zeros((out_H, out_W), dtype=input.dtype) # Sliding window convolution with four nested loops for i in range(out_H): for j in range(out_W): for di in range(kH): for dj in range(kW): output[i, j] += input[i + di, j + dj] * kernel[di, dj] return output ### END SOLUTION # %% ../../modules/source/06_spatial/spatial_dev.ipynb 10 class Conv2D: """ 2D Convolutional Layer (single channel, single filter, no stride/pad). A learnable convolutional layer that applies a kernel to detect spatial patterns. Perfect for building the foundation of convolutional neural networks. """ def __init__(self, kernel_size: Tuple[int, int]): """ Initialize Conv2D layer with random kernel. Args: kernel_size: (kH, kW) - size of the convolution kernel TODO: Initialize a random kernel with small values. APPROACH: 1. Store kernel_size as instance variable 2. Initialize random kernel with small values 3. Use proper initialization for stable training EXAMPLE: Conv2D((2, 2)) creates: - kernel: shape (2, 2) with small random values HINTS: - Store kernel_size as self.kernel_size - Initialize kernel: np.random.randn(kH, kW) * 0.1 (small values) - Convert to float32 for consistency """ ### BEGIN SOLUTION # Store kernel size self.kernel_size = kernel_size kH, kW = kernel_size # Initialize random kernel with small values self.kernel = np.random.randn(kH, kW).astype(np.float32) * 0.1 ### END SOLUTION def forward(self, x): """ Forward pass: apply convolution to input tensor. Args: x: Input tensor (2D for simplicity) Returns: Output tensor after convolution TODO: Implement forward pass using conv2d_naive function. APPROACH: 1. Extract numpy array from input tensor 2. Apply conv2d_naive with stored kernel 3. Return result wrapped in Tensor EXAMPLE: x = Tensor([[1, 2, 3], [4, 5, 6], [7, 8, 9]]) # shape (3, 3) layer = Conv2D((2, 2)) y = layer(x) # shape (2, 2) HINTS: - Use x.data to get numpy array - Use conv2d_naive(x.data, self.kernel) - Return Tensor(result) to wrap the result """ ### BEGIN SOLUTION # Apply convolution using naive implementation result = conv2d_naive(x.data, self.kernel) return type(x)(result) ### END SOLUTION def __call__(self, x): """Make layer callable: layer(x) same as layer.forward(x)""" return self.forward(x) # %% ../../modules/source/06_spatial/spatial_dev.ipynb 14 def flatten(x): """ Flatten a 2D tensor to 1D (for connecting to Dense layers). Args: x: Input tensor to flatten Returns: Flattened tensor with batch dimension preserved TODO: Implement flattening operation. APPROACH: 1. Get the numpy array from the tensor 2. Use .flatten() to convert to 1D 3. Add batch dimension with [None, :] 4. Return Tensor wrapped around the result EXAMPLE: Input: Tensor([[1, 2], [3, 4]]) # shape (2, 2) Output: Tensor([[1, 2, 3, 4]]) # shape (1, 4) HINTS: - Use x.data.flatten() to get 1D array - Add batch dimension: result[None, :] - Return Tensor(result) """ ### BEGIN SOLUTION # Flatten the tensor and add batch dimension flattened = x.data.flatten() result = flattened[None, :] # Add batch dimension return type(x)(result) ### END SOLUTION