Created
November 14, 2025 11:58
-
-
Save AhmedCoolProjects/8283bc04546d61057d5251199091351f to your computer and use it in GitHub Desktop.
CNN application on MNIST.ipynb
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| { | |
| "nbformat": 4, | |
| "nbformat_minor": 0, | |
| "metadata": { | |
| "colab": { | |
| "provenance": [], | |
| "authorship_tag": "ABX9TyNwSYB4cHyoP/S1XVdQVdSW", | |
| "include_colab_link": true | |
| }, | |
| "kernelspec": { | |
| "name": "python3", | |
| "display_name": "Python 3" | |
| }, | |
| "language_info": { | |
| "name": "python" | |
| } | |
| }, | |
| "cells": [ | |
| { | |
| "cell_type": "markdown", | |
| "metadata": { | |
| "id": "view-in-github", | |
| "colab_type": "text" | |
| }, | |
| "source": [ | |
| "<a href=\"https://colab.research.google.com/gist/AhmedCoolProjects/8283bc04546d61057d5251199091351f/cnn-application-on-mnist.ipynb\" target=\"_parent\"><img src=\"https://colab.research.google.com/assets/colab-badge.svg\" alt=\"Open In Colab\"/></a>" | |
| ] | |
| }, | |
| { | |
| "cell_type": "markdown", | |
| "source": [ | |
| "# First Application of CNN on MNIST" | |
| ], | |
| "metadata": { | |
| "id": "o9_Ufm9_yGfh" | |
| } | |
| }, | |
| { | |
| "cell_type": "code", | |
| "source": [ | |
| "import numpy as np\n", | |
| "from sklearn.datasets import fetch_openml\n", | |
| "from sklearn.model_selection import train_test_split\n", | |
| "from sklearn.preprocessing import OneHotEncoder\n", | |
| "\n", | |
| "def load_and_prepare_data_cnn():\n", | |
| " print(\"Loading MNIST dataset...\")\n", | |
| " X, y = fetch_openml('mnist_784', version=1, return_X_y=True, as_frame=False, parser='auto')\n", | |
| "\n", | |
| " # 1. Scale the data\n", | |
| " X = X / 255.0\n", | |
| "\n", | |
| " # 2. Reshape for CNN: (num_samples, 784) -> (num_samples, 28, 28, 1)\n", | |
| " X_reshaped = X.reshape(-1, 28, 28, 1)\n", | |
| "\n", | |
| " # 3. One-hot encode labels\n", | |
| " y_reshaped = y.reshape(-1, 1)\n", | |
| " encoder = OneHotEncoder(sparse_output=False, categories='auto')\n", | |
| " Y_onehot = encoder.fit_transform(y_reshaped)\n", | |
| "\n", | |
| " print(\"Data loaded and prepared.\")\n", | |
| "\n", | |
| " # 4. Split the data\n", | |
| " X_train, X_test, Y_train_onehot, Y_test_onehot, _, y_test_orig = train_test_split(\n", | |
| " X_reshaped, Y_onehot, y.flatten(), test_size=0.1, random_state=42\n", | |
| " )\n", | |
| "\n", | |
| " # 5. Transpose Y to (classes, m)\n", | |
| " Y_train = Y_train_onehot.T\n", | |
| " Y_test = Y_test_onehot.T\n", | |
| "\n", | |
| " y_test_orig = y_test_orig.astype(int)\n", | |
| "\n", | |
| " return X_train, Y_train, X_test, Y_test, y_test_orig" | |
| ], | |
| "metadata": { | |
| "id": "aepl0422yBCP" | |
| }, | |
| "execution_count": 26, | |
| "outputs": [] | |
| }, | |
| { | |
| "cell_type": "code", | |
| "source": [ | |
| "import matplotlib.pyplot as plt\n", | |
| "\n", | |
| "# Verifying\n", | |
| "X_train, X_test, Y_train, Y_test, y_test_orig = load_and_prepare_data()\n", | |
| "\n", | |
| "image_index = 0 # You can change this to view other images\n", | |
| "\n", | |
| "# Reshape the 784-feature vector back to a 28x28 image\n", | |
| "image = X_test[:, image_index].reshape(28, 28)\n", | |
| "label = y_test_orig[image_index]\n", | |
| "label_one_hot = Y_test[:, image_index]\n", | |
| "\n", | |
| "plt.imshow(image, cmap='gray')\n", | |
| "plt.title(f\"Example Image - Label: {label}\")\n", | |
| "plt.axis('off') # Hide axes ticks and labels\n", | |
| "plt.show()\n", | |
| "print(label)\n", | |
| "print(label_one_hot)" | |
| ], | |
| "metadata": { | |
| "colab": { | |
| "base_uri": "https://localhost:8080/", | |
| "height": 497 | |
| }, | |
| "id": "O-rEeXFbyJrE", | |
| "outputId": "893afee5-4b4c-4ff2-db05-23d8b44fffae" | |
| }, | |
| "execution_count": 13, | |
| "outputs": [ | |
| { | |
| "output_type": "stream", | |
| "name": "stdout", | |
| "text": [ | |
| "Loading MNIST dataset...\n", | |
| "Data loaded and prepared.\n" | |
| ] | |
| }, | |
| { | |
| "output_type": "display_data", | |
| "data": { | |
| "text/plain": [ | |
| "<Figure size 640x480 with 1 Axes>" | |
| ], | |
| "image/png": "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\n" | |
| }, | |
| "metadata": {} | |
| }, | |
| { | |
| "output_type": "stream", | |
| "name": "stdout", | |
| "text": [ | |
| "8\n", | |
| "[0. 0. 0. 0. 0. 0. 0. 0. 1. 0.]\n" | |
| ] | |
| } | |
| ] | |
| }, | |
| { | |
| "cell_type": "code", | |
| "source": [ | |
| "class Activation_Functions:\n", | |
| " @staticmethod\n", | |
| " def heaviside(x: np.ndarray) -> np.ndarray:\n", | |
| " return np.where(x < 0, 0, 1)\n", | |
| "\n", | |
| " @staticmethod\n", | |
| " def relu(x: np.ndarray) -> np.ndarray:\n", | |
| " return np.maximum(0, x)\n", | |
| "\n", | |
| " @staticmethod\n", | |
| " def sigmoid(x: np.ndarray) -> np.ndarray:\n", | |
| " return 1 / (1 + np.exp(-x))\n", | |
| "\n", | |
| " @staticmethod\n", | |
| " def softmax(x: np.ndarray) -> np.ndarray:\n", | |
| " exp_x = np.exp(x - np.max(x)) # for numerical stability\n", | |
| " return exp_x / exp_x.sum(axis=0, keepdims=True) # axis=0 for column-wise\n", | |
| "\n", | |
| " @staticmethod\n", | |
| " def sigmoid_derivative(x: np.ndarray) -> np.ndarray:\n", | |
| " sig = Activation_Functions.sigmoid(x)\n", | |
| " return sig * (1 - sig)\n", | |
| "\n", | |
| " @staticmethod\n", | |
| " def relu_derivative(x: np.ndarray) -> np.ndarray:\n", | |
| " return np.where(x > 0, 1, 0)\n", | |
| "" | |
| ], | |
| "metadata": { | |
| "id": "EFdO8uh2Cl4W" | |
| }, | |
| "execution_count": 8, | |
| "outputs": [] | |
| }, | |
| { | |
| "cell_type": "code", | |
| "source": [ | |
| "class Cost_Functions:\n", | |
| " @staticmethod\n", | |
| " def mse(y_true: np.ndarray, y_pred: np.ndarray) -> float:\n", | |
| " sq_errors = (y_true - y_pred) ** 2\n", | |
| " return np.mean(sq_errors)\n", | |
| "\n", | |
| " @staticmethod\n", | |
| " def ce(y_true: np.ndarray, y_pred: np.ndarray) -> float:\n", | |
| " # Adding a small value to avoid log(0)\n", | |
| " epsilon = 1e-15\n", | |
| " m = y_pred.shape[1]\n", | |
| " y_pred = np.clip(y_pred, epsilon, 1 - epsilon)\n", | |
| " ce_loss_ = -np.sum(y_true * np.log(y_pred), axis=0) # -> (L, m)\n", | |
| " ce_loss = (1/m) * np.sum(ce_loss_)\n", | |
| " return np.squeeze(ce_loss)\n", | |
| "\n", | |
| " @staticmethod\n", | |
| " def bce(y_true: np.ndarray, y_pred: np.ndarray) -> float:\n", | |
| " # Adding a small value to avoid log(0)\n", | |
| " epsilon = 1e-15\n", | |
| " y_pred = np.clip(y_pred, epsilon, 1 - epsilon)\n", | |
| " bce_loss_ = - (y_true * np.log(y_pred) + (1 - y_true) * np.log(1 - y_pred))\n", | |
| " bce_loss = np.mean(bce_loss_)\n", | |
| " return bce_loss\n", | |
| "\n", | |
| " @staticmethod\n", | |
| " def ce_grad(y_true: np.ndarray, y_pred: np.ndarray) -> np.ndarray:\n", | |
| " # Gradient of Cross-Entropy Loss with respect to predictions\n", | |
| " epsilon = 1e-15\n", | |
| " y_pred = np.clip(y_pred, epsilon, 1 - epsilon)\n", | |
| " grad = - (y_true / y_pred)\n", | |
| " return grad\n", | |
| "\n", | |
| " @staticmethod\n", | |
| " def bce_grad(y_true: np.ndarray, y_pred: np.ndarray) -> np.ndarray:\n", | |
| " # Gradient of Binary Cross-Entropy Loss with respect to predictions\n", | |
| " epsilon = 1e-15\n", | |
| " y_pred = np.clip(y_pred, epsilon, 1 - epsilon)\n", | |
| " grad = - (y_true / y_pred) + ((1 - y_true) / (1 - y_pred))\n", | |
| " return grad" | |
| ], | |
| "metadata": { | |
| "id": "rjhkf1NVCjDG" | |
| }, | |
| "execution_count": 9, | |
| "outputs": [] | |
| }, | |
| { | |
| "cell_type": "code", | |
| "source": [ | |
| "from typing import List, Tuple\n", | |
| "\n", | |
| "import numpy as np\n", | |
| "\n", | |
| "np.random.seed(42)\n", | |
| "\n", | |
| "class NN:\n", | |
| " def __init__(self, s: List[int]):\n", | |
| " '''\n", | |
| " s: List of layer sizes, where s[0] is the input layer size, s[1] is the first hidden layer size, ..., s[L] is the output layer size\n", | |
| " '''\n", | |
| " self.s = s\n", | |
| " self.L = len(s) - 1\n", | |
| " # Xavier (Glorot) initialization for weights\n", | |
| " self.weights = [np.random.randn(s[l], s[l-1]) * np.sqrt(1 / s[l-1]) for l in range(1, self.L + 1)]\n", | |
| " self.biases = [np.zeros((s[l], 1)) for l in range(1, self.L + 1)]\n", | |
| " self.f = Activation_Functions.sigmoid\n", | |
| " self.f_output = Activation_Functions.softmax\n", | |
| " self.cost = Cost_Functions.ce\n", | |
| " self.cost_grad = Cost_Functions.ce_grad\n", | |
| "\n", | |
| " def forward_pass(self, X: np.ndarray) -> Tuple[np.ndarray, List[tuple]]:\n", | |
| " '''\n", | |
| " X: Input data of shape (n, m)\n", | |
| " '''\n", | |
| " cache = [] # only hidden and output layers\n", | |
| " A = X\n", | |
| "\n", | |
| " for l in range(self.L):\n", | |
| " A_prev = A\n", | |
| "\n", | |
| " W = self.weights[l]\n", | |
| " b = self.biases[l]\n", | |
| " # Linear Step\n", | |
| " Z = np.dot(W, A_prev) + b\n", | |
| " # Activation Step\n", | |
| " if l == self.L - 1:\n", | |
| " A = self.f_output(Z)\n", | |
| " else:\n", | |
| " A = self.f(Z)\n", | |
| "\n", | |
| " cache.append((A_prev, W, b, Z))\n", | |
| "\n", | |
| " return A, cache\n", | |
| "\n", | |
| " def compute_cost(self, AL: np.ndarray, Y: np.ndarray) -> float:\n", | |
| " cost = self.cost(Y, AL)\n", | |
| " return cost\n", | |
| "\n", | |
| " def backpropagation(self, AL: np.ndarray, Y: np.ndarray, cache: List[tuple]) -> dict:\n", | |
| " '''\n", | |
| " AL: Output of the network from forward pass\n", | |
| " Y: True labels (one-hot encoded)\n", | |
| " cache: List of tuples containing (A_prev, W, b, Z) for each layer from forward pass\n", | |
| "\n", | |
| " Returns:\n", | |
| " grads: Dictionary containing gradients for weights and biases e.g., {'dW1': ..., 'db1': ..., 'dW2': ..., 'db2': ..., ...}\n", | |
| " '''\n", | |
| " grads = {}\n", | |
| " m = Y.shape[1] # number of examples\n", | |
| "\n", | |
| " # ---- 1. Output Layer ----\n", | |
| " Delta_L = AL - Y # (s[L], m)\n", | |
| " A_prev, W_L, b_L, Z_L = cache[-1]\n", | |
| "\n", | |
| " # grads\n", | |
| " grads[f'dW{self.L}'] = (1/m) * np.dot(Delta_L, A_prev.T)\n", | |
| " grads[f'db{self.L}'] = (1/m) * np.sum(Delta_L, axis=1, keepdims=True)\n", | |
| "\n", | |
| " # ---- 2. Hidden Layers ----\n", | |
| " Delta_next = Delta_L\n", | |
| " dA_prev = np.dot(W_L.T, Delta_next)\n", | |
| "\n", | |
| " for l in reversed(range(self.L - 1)):\n", | |
| " A_prev, W_l, b_l, Z_l = cache[l]\n", | |
| "\n", | |
| " Delta_l = dA_prev * Activation_Functions.sigmoid_derivative(Z_l)\n", | |
| "\n", | |
| " # grads\n", | |
| " grads[f'dW{l+1}'] = (1/m) * np.dot(Delta_l, A_prev.T)\n", | |
| " grads[f'db{l+1}'] = (1/m) * np.sum(Delta_l, axis=1, keepdims=True)\n", | |
| "\n", | |
| " dA_prev = np.dot(W_l.T, Delta_l)\n", | |
| "\n", | |
| " return grads, dA_prev\n", | |
| "\n", | |
| " def update_parameters(self, grads: dict, learning_rate: float = 0.001):\n", | |
| " for l in range(self.L):\n", | |
| " self.weights[l] -= learning_rate * grads[f'dW{l+1}']\n", | |
| " self.biases[l] -= learning_rate * grads[f'db{l+1}']\n", | |
| "\n", | |
| " def train(self, X: np.ndarray, Y: np.ndarray, epochs: int = 100, learning_rate: float = 0.001, batch_size: int = 20, print_cost: bool = True) -> None:\n", | |
| " '''\n", | |
| " The main training loop with mini-batch gradient descent.\n", | |
| " '''\n", | |
| " costs = []\n", | |
| " m = X.shape[1] # total number of samples\n", | |
| "\n", | |
| " for epoch in range(epochs):\n", | |
| " # Shuffle the training data at the beginning of each epoch\n", | |
| " permutation = np.random.permutation(m)\n", | |
| " shuffled_X = X[:, permutation]\n", | |
| " shuffled_Y = Y[:, permutation]\n", | |
| "\n", | |
| " # Iterate through mini-batches\n", | |
| " for i in range(0, m, batch_size):\n", | |
| " X_batch = shuffled_X[:, i:i + batch_size]\n", | |
| " Y_batch = shuffled_Y[:, i:i + batch_size]\n", | |
| "\n", | |
| " # 1. Forward Pass for the batch\n", | |
| " AL_batch, cache_batch = self.forward_pass(X_batch)\n", | |
| " # 2. Backpropagation for the batch\n", | |
| " grads_batch = self.backpropagation(AL_batch, Y_batch, cache_batch)\n", | |
| " # 3. Update Parameters using batch gradients\n", | |
| " self.update_parameters(grads_batch, learning_rate)\n", | |
| "\n", | |
| " # Calculate and print the overall cost after all batches for the epoch\n", | |
| " AL_full, _ = self.forward_pass(X) # Forward pass on full dataset for cost calculation\n", | |
| " cost = self.compute_cost(AL_full, Y)\n", | |
| "\n", | |
| " if print_cost and epoch % (epochs // 10 if epochs >= 10 else 1) == 0:\n", | |
| " print(f\"Cost after epoch {epoch}: {cost:.4f}\")\n", | |
| " if epoch % 10 == 0:\n", | |
| " costs.append(cost)\n", | |
| " return costs\n", | |
| "\n", | |
| " def predict(self, X: np.ndarray) -> np.ndarray:\n", | |
| " AL, _ = self.forward_pass(X)\n", | |
| " predictions = np.argmax(AL, axis=0)\n", | |
| " return predictions\n" | |
| ], | |
| "metadata": { | |
| "id": "cGnid1IlCh_r" | |
| }, | |
| "execution_count": 55, | |
| "outputs": [] | |
| }, | |
| { | |
| "cell_type": "code", | |
| "source": [ | |
| "class Conv_Layer:\n", | |
| " def __init__(self, in_channels, num_filters, filter_size, stride=1, padding=0):\n", | |
| " '''\n", | |
| " in_channels: Depth of the input volume (e.g., 1 for grayscale, 3 for RGB)\n", | |
| " num_filters: Number of filters to use (this will be the depth of the output volume)\n", | |
| " filter_size: An integer, e.g., 3 for a 3x3 filter\n", | |
| " stride: Step size of the filter\n", | |
| " padding: The amount of zero-padding to add to the input\n", | |
| " '''\n", | |
| " self.C = num_filters\n", | |
| " self.F = filter_size\n", | |
| " self.S = stride\n", | |
| " self.P = padding\n", | |
| "\n", | |
| " # 1. Initialize the filters with random weights and biases\n", | |
| " self.filters = np.random.randn(self.F, self.F, in_channels, self.C) * 0.1\n", | |
| " # one bias per filter\n", | |
| " self.biases = np.zeros((1, 1, 1, self.C))\n", | |
| "\n", | |
| " def _zero_pad(self, X, pad):\n", | |
| " '''\n", | |
| " Helper function to add zero-padding to a batch of images X\n", | |
| " X: Input data of shape (m, H, W, C)\n", | |
| " '''\n", | |
| " if self.P > 0:\n", | |
| " return np.pad(X, ((0, 0), (pad, pad), (pad, pad), (0, 0)), 'constant', constant_values=0)\n", | |
| " return X\n", | |
| "\n", | |
| " def _relu(self, Z):\n", | |
| " '''\n", | |
| " ReLU activation function\n", | |
| " '''\n", | |
| " return np.maximum(0, Z)\n", | |
| "\n", | |
| " def _relu_derivative(self, Z):\n", | |
| " '''\n", | |
| " Derivative of ReLU\n", | |
| " '''\n", | |
| " dZ = np.array(Z, copy=True)\n", | |
| " dZ[Z <= 0] = 0\n", | |
| " dZ[Z > 0] = 1\n", | |
| " return dZ\n", | |
| "\n", | |
| " def forward_pass(self, A_prev):\n", | |
| " '''\n", | |
| " A_prev: Input data from the previous layer\n", | |
| " Shape: (m, H_prev, W_prev, C_prev)\n", | |
| " m: batch size\n", | |
| " H_prev: height of input\n", | |
| " W_prev: width of input\n", | |
| " C_prev: number of channels in input\n", | |
| "\n", | |
| " Returns:\n", | |
| " A: Output of the layer (after activation)\n", | |
| " cache: A tuple of values needed for backward pass\n", | |
| " '''\n", | |
| "\n", | |
| " # 1. Get dimensions\n", | |
| " m, H_prev, W_prev, C_prev = A_prev.shape\n", | |
| " # _, F, _, C_out = self.filters.shape # No need\n", | |
| "\n", | |
| " # 2. Compute output dimensions of the conv layer\n", | |
| " # (H - F + 2P) / S + 1\n", | |
| " H_out = int((H_prev - self.F + 2 * self.P) / self.S) + 1\n", | |
| " W_out = int((W_prev - self.F + 2 * self.P) / self.S) + 1\n", | |
| "\n", | |
| " # 3. Apply padding to the input\n", | |
| " X_padded = self._zero_pad(A_prev, self.P)\n", | |
| "\n", | |
| " # 4. Initialize output volume Z\n", | |
| " Z = np.zeros((m, H_out, W_out, self.C))\n", | |
| "\n", | |
| " # 5. --- The Convolution Loop ---\n", | |
| " for i in range(m): # loop over batch size\n", | |
| " x_i = X_padded[i] # ith input image\n", | |
| "\n", | |
| " for h in range(H_out):\n", | |
| " for w in range(W_out):\n", | |
| " # find the corners of the current 3D slice\n", | |
| " h_start = h * self.S\n", | |
| " h_end = h_start + self.F\n", | |
| " w_start = w * self.S\n", | |
| " w_end = w_start + self.F\n", | |
| " x_slice = x_i[h_start:h_end, w_start:w_end, :]\n", | |
| "\n", | |
| " # convolve with all filters\n", | |
| " for c in range(self.C): # loop over filters\n", | |
| " # Element-wise multiplication, then sum all 3 dimensions\n", | |
| " conv_sum = np.sum(x_slice * self.filters[:, :, :, c])\n", | |
| "\n", | |
| " # Add bias\n", | |
| " Z[i, h, w, c] = conv_sum + self.biases[0, 0, 0, c]\n", | |
| "\n", | |
| " # 6. Apply ReLU activation\n", | |
| " A = self._relu(Z)\n", | |
| "\n", | |
| " # 7. Store values for backward pass\n", | |
| " # we will need X_prev and our parameters to calculate gradients later\n", | |
| " cache = (A_prev, Z, self.filters, self.biases, self.S, self.P)\n", | |
| "\n", | |
| " return A, cache\n", | |
| "\n", | |
| " def backpropagation(self, dA, cache):\n", | |
| " '''\n", | |
| " dA: Gradient w.r.t the output (A), shape: (m, H_out, W_out, C_out)\n", | |
| " cache: Tuple of values from forward pass\n", | |
| "\n", | |
| " Returns:\n", | |
| " dA_prev: Gradient w.r.t the input (A_prev), shape: (m, H_prev, W_prev, C_prev)\n", | |
| " dW: Gradient w.r.t the filters (weights), shape: (F, F, C_prev, C_out)\n", | |
| " db: Gradient w.r.t the biases\n", | |
| " '''\n", | |
| "\n", | |
| " # 1. Unpack cache\n", | |
| " (A_prev, Z, W, b, S, P) = cache\n", | |
| " # 2. Get dimensions\n", | |
| " (m, H_prev, W_prev, C_prev) = A_prev.shape\n", | |
| " (_, H_out, W_out, C_out) = dA.shape\n", | |
| " (F, _, _, _) = W.shape\n", | |
| "\n", | |
| " # 3. Initialize gradients\n", | |
| " dA_prev = np.zeros_like(A_prev) # (m, H_prev, W_prev, C_prev)\n", | |
| " dW = np.zeros_like(W) # (F, F, C_prev, C_out)\n", | |
| " db = np.zeros_like(b) # (1, 1, 1, C_out)\n", | |
| "\n", | |
| " # 4. Calculate dZ\n", | |
| " dZ = dA * self._relu_derivative(Z) # (m, H_out, W_out, C_out)\n", | |
| "\n", | |
| " # 5. Pad A_prev and dA_prev\n", | |
| " A_prev_padded = self._zero_pad(A_prev, P)\n", | |
| " dA_prev_padded = self._zero_pad(dA_prev, P)\n", | |
| "\n", | |
| " # 6. --- The Backpropagation Loop ---\n", | |
| " for i in range(m):\n", | |
| " x_padded_i = A_prev_padded[i]\n", | |
| " da_prev_padded_i = dA_prev_padded[i]\n", | |
| "\n", | |
| " for h in range(H_out):\n", | |
| " for w in range(W_out):\n", | |
| " for c in range(C_out):\n", | |
| " # Corners\n", | |
| " h_start = h * S\n", | |
| " h_end = h_start + F\n", | |
| " w_start = w * S\n", | |
| " w_end = w_start + F\n", | |
| "\n", | |
| " # Slice of A_prev_padded\n", | |
| " x_slice = x_padded_i[h_start:h_end, w_start:w_end, :]\n", | |
| " dz = dZ[i, h, w, c]\n", | |
| "\n", | |
| " # Calculate gradients\n", | |
| " # 1. --- dA_prev (Transposed Convolution) ---\n", | |
| " da_prev_padded_i[h_start:h_end, w_start:w_end, :] += W[:, :, :, c] * dz\n", | |
| "\n", | |
| " # 2. --- dW (Convolution with input) ---\n", | |
| " dW[:, :, :, c] += x_slice * dz\n", | |
| "\n", | |
| " # 3. --- db (Sum) ---\n", | |
| " db[:, :, :, c] += dz\n", | |
| "\n", | |
| " # Unpad dA_prev\n", | |
| " if P > 0:\n", | |
| " dA_prev[i, :, :, :] = da_prev_padded_i[P:-P, P:-P, :]\n", | |
| " else:\n", | |
| " dA_prev[i, :, :, :] = da_prev_padded_i\n", | |
| "\n", | |
| " self.dW = dW / m\n", | |
| " self.db = db / m\n", | |
| "\n", | |
| " return dA_prev, self.dW, self.db\n", | |
| "\n", | |
| " def update_parameters(self, learning_rate):\n", | |
| " self.filters -= learning_rate * self.dW\n", | |
| " self.biases -= learning_rate * self.db" | |
| ], | |
| "metadata": { | |
| "id": "z_v-NzBNyMHx" | |
| }, | |
| "execution_count": 65, | |
| "outputs": [] | |
| }, | |
| { | |
| "cell_type": "code", | |
| "source": [ | |
| "class Pool_Layer:\n", | |
| " def __init__(self, filter_size=2, stride=2, mode='max'):\n", | |
| " '''\n", | |
| " mode: 'max' for Max Pooling, 'avg' for Mean Pooling\n", | |
| " '''\n", | |
| " self.filter_size = filter_size\n", | |
| " self.stride = stride\n", | |
| " self.mode = mode\n", | |
| "\n", | |
| " def _create_mask_from_window(self, x_slice):\n", | |
| " '''\n", | |
| " Helper function for max pooling backpropagation.\n", | |
| " Creates a mask of same shape as x_slice, with a 1 at the position of the max value.\n", | |
| " '''\n", | |
| "\n", | |
| " mask = (x_slice == np.max(x_slice))\n", | |
| " return mask\n", | |
| "\n", | |
| " def forward_pass(self, A_prev):\n", | |
| " '''\n", | |
| " A_prev: Input data from the previous layer\n", | |
| " Shape: (m, H_prev, W_prev, C_prev)\n", | |
| "\n", | |
| " Returns:\n", | |
| " A: Output of the pooling layer\n", | |
| " cache: A tuple of values needed for backpropagation\n", | |
| " '''\n", | |
| "\n", | |
| " (m, H_prev, W_prev, C_prev) = A_prev.shape\n", | |
| " F = self.filter_size\n", | |
| " S = self.stride\n", | |
| "\n", | |
| " # Calculate output dimensions\n", | |
| " H_out = int((H_prev - F) / S) + 1\n", | |
| " W_out = int((W_prev - F) / S) + 1\n", | |
| "\n", | |
| " # Initialize output volume A\n", | |
| " A = np.zeros((m, H_out, W_out, C_prev))\n", | |
| "\n", | |
| " # --- The Pooling Loop ---\n", | |
| " for i in range(m): # loop over batch size\n", | |
| " for h in range(H_out):\n", | |
| " for w in range(W_out):\n", | |
| " for c in range(C_prev):\n", | |
| " h_start = h * S\n", | |
| " h_end = h_start + F\n", | |
| " w_start = w * S\n", | |
| " w_end = w_start + F\n", | |
| "\n", | |
| " # get a slice of the input\n", | |
| " a_slice = A_prev[i, h_start:h_end, w_start:w_end, c]\n", | |
| "\n", | |
| " # Perform pooling operation\n", | |
| " if self.mode == 'max':\n", | |
| " A[i, h, w, c] = np.max(a_slice)\n", | |
| " elif self.mode == 'avg':\n", | |
| " A[i, h, w, c] = np.mean(a_slice)\n", | |
| " # Store values for backpropagation\n", | |
| " cache = (A_prev, self.filter_size, self.stride, self.mode)\n", | |
| "\n", | |
| " return A, cache\n", | |
| "\n", | |
| " def backpropagation(self, dA, cache):\n", | |
| " '''\n", | |
| " dA: Gradient w.r.t the output of the pooling layer, shape: (m, H_out, W_out, C_out)\n", | |
| " cache: Tuple of values from forward pass\n", | |
| "\n", | |
| " Returns:\n", | |
| " dA_prev: Gradient w.r.t the input of the pooling layer, shape: (m, H_prev, W_prev, C_prev)\n", | |
| " '''\n", | |
| "\n", | |
| " # 1. Unpack cache\n", | |
| " (A_prev, F, S, mode) = cache\n", | |
| "\n", | |
| " # 2. Get dimensions\n", | |
| " (m, H_prev, W_prev, C_prev) = A_prev.shape\n", | |
| " (m, H_out, W_out, C_out) = dA.shape\n", | |
| "\n", | |
| " # 3. Initialize dA_prev\n", | |
| " dA_prev = np.zeros_like(A_prev) # (m, H_prev, W_prev, C_prev)\n", | |
| " # 4. --- The Backpropagation Loop ---\n", | |
| " for i in range(m):\n", | |
| " x_prev_i = A_prev[i]\n", | |
| " for h in range(H_out):\n", | |
| " for w in range(W_out):\n", | |
| " for c in range(C_prev):\n", | |
| " h_start = h * S\n", | |
| " h_end = h_start + F\n", | |
| " w_start = w * S\n", | |
| " w_end = w_start + F\n", | |
| "\n", | |
| " da = dA[i, h, w, c]\n", | |
| "\n", | |
| " if mode == 'max':\n", | |
| " x_slice = x_prev_i[h_start:h_end, w_start:w_end, c]\n", | |
| " # Create mask\n", | |
| " mask = self._create_mask_from_window(x_slice)\n", | |
| " # Route the gradient to the position of the max\n", | |
| " dA_prev[i, h_start:h_end, w_start:w_end, c] += mask * da\n", | |
| " elif mode == 'avg':\n", | |
| " # Distribute the gradient evenly\n", | |
| " shape = (F, F)\n", | |
| " average = da / (F * F)\n", | |
| " dA_prev[i, h_start:h_end, w_start:w_end, c] += np.ones(shape) * average\n", | |
| "\n", | |
| " return dA_prev" | |
| ], | |
| "metadata": { | |
| "id": "X6Miyy2DCVX8" | |
| }, | |
| "execution_count": 6, | |
| "outputs": [] | |
| }, | |
| { | |
| "cell_type": "code", | |
| "source": [ | |
| "class Flatten_Layer:\n", | |
| " '''\n", | |
| " A layer to flatten the 4D output of the Pool Layer into a 2D vector to be fed into the Dense NN.\n", | |
| " '''\n", | |
| " def __init__(self):\n", | |
| " self.cache = None\n", | |
| "\n", | |
| " def forward_pass(self, A_prev: np.ndarray) -> np.ndarray:\n", | |
| " '''\n", | |
| " Converts (m, H, W, C) to (H*W*C, m)\n", | |
| "\n", | |
| " Returns:\n", | |
| " A: np.ndarray -- Flattened output of shape (H*W*C, m)\n", | |
| " cache: Original shape for backpropagation\n", | |
| " '''\n", | |
| " self.cache = A_prev.shape\n", | |
| " (m, H, W, C) = A_prev.shape\n", | |
| "\n", | |
| " A = A_prev.reshape(m, -1).T # (H*W*C, m), -1 infers the size\n", | |
| "\n", | |
| " return A, self.cache\n", | |
| "\n", | |
| " def backpropagation(self, dA: np.ndarray) -> np.ndarray:\n", | |
| " '''\n", | |
| " Reshapes dA back to the original shape stored in cache.\n", | |
| "\n", | |
| " Returns:\n", | |
| " dA_prev: np.ndarray -- Gradient reshaped to original dimensions\n", | |
| " '''\n", | |
| " original_shape = self.cache\n", | |
| " dA_prev = dA.T.reshape(original_shape) # (m, H, W, C)\n", | |
| "\n", | |
| " return dA_prev" | |
| ], | |
| "metadata": { | |
| "id": "BsjTn1tNCY0g" | |
| }, | |
| "execution_count": 7, | |
| "outputs": [] | |
| }, | |
| { | |
| "cell_type": "code", | |
| "source": [], | |
| "metadata": { | |
| "id": "Wyrz5LJfCbA1" | |
| }, | |
| "execution_count": null, | |
| "outputs": [] | |
| }, | |
| { | |
| "cell_type": "code", | |
| "source": [ | |
| "class CNN:\n", | |
| " def __init__(self):\n", | |
| " print(\"Initializing CNN...\")\n", | |
| " # Layer 1: Conv -> Pool\n", | |
| " self.conv1 = Conv_Layer(in_channels=1, num_filters=8, filter_size=3, stride=1, padding=0) # (m, 28, 28, 1) -> (m, 26, 26, 8)\n", | |
| "\n", | |
| " self.pool1 = Pool_Layer(filter_size=2, stride=2, mode='max') # (m, 26, 26, 8) -> (m, 13, 13, 8)\n", | |
| " # Layer 2: Flatten\n", | |
| " self.flatten = Flatten_Layer() # (m, 13, 13, 8) -> (m, 1352)\n", | |
| " # Layer 3: Dense NN\n", | |
| " dense_input_size = 13*13*8\n", | |
| " self.dense_nn = NN(s=[dense_input_size, 64, 10]) # (1352, m) -> (10, m)\n", | |
| " self.layers = [self.conv1, self.pool1, self.flatten, self.dense_nn]\n", | |
| " print(\"CNN initialized.\")\n", | |
| "\n", | |
| " def forward_pass(self, X):\n", | |
| " # Conv\n", | |
| " A_conv1, cache_conv1 = self.conv1.forward_pass(X)\n", | |
| " # Pool\n", | |
| " A_pool1, cache_pool1 = self.pool1.forward_pass(A_conv1)\n", | |
| " # Flatten\n", | |
| " A_flat, cache_flat = self.flatten.forward_pass(A_pool1)\n", | |
| " # Dense NN\n", | |
| " AL, cache_dense = self.dense_nn.forward_pass(A_flat)\n", | |
| "\n", | |
| " # store all caches for backpropagation\n", | |
| " caches = (\n", | |
| " cache_conv1,\n", | |
| " cache_pool1,\n", | |
| " cache_flat,\n", | |
| " cache_dense\n", | |
| " )\n", | |
| "\n", | |
| " return AL, caches\n", | |
| "\n", | |
| " def compute_cost(self, AL, Y):\n", | |
| " return self.dense_nn.compute_cost(AL, Y)\n", | |
| "\n", | |
| " def backpropagation(self, AL, Y, caches):\n", | |
| " (cache_conv1, cache_pool1, cache_flat, cache_dense) = caches\n", | |
| " # 4. Dense\n", | |
| " grads_dense, dA_flat = self.dense_nn.backpropagation(AL, Y, cache_dense)\n", | |
| " # 3. Flatten\n", | |
| " dA_pool1 = self.flatten.backpropagation(dA_flat)\n", | |
| " # 2. Pool\n", | |
| " dA_conv1 = self.pool1.backpropagation(dA_pool1, cache_pool1)\n", | |
| " # 1. Conv\n", | |
| " dA_prev = self.conv1.backpropagation(dA_conv1, cache_conv1)\n", | |
| "\n", | |
| " # Store dense grads\n", | |
| " self.dense_nn_grads = grads_dense\n", | |
| "\n", | |
| " def update_parameters(self, learning_rate=0.01):\n", | |
| " # Update Conv layer parameters\n", | |
| " self.conv1.update_parameters(learning_rate)\n", | |
| " # Update Dense NN parameters\n", | |
| " self.dense_nn.update_parameters(self.dense_nn_grads, learning_rate)\n", | |
| "\n", | |
| " def train(self, X, Y, epochs, learning_rate=0.01, batch_size=32):\n", | |
| " print(f\"Starting training for {epochs} epochs...\")\n", | |
| " costs = []\n", | |
| " m = X.shape[0]\n", | |
| " for epoch in range(epochs):\n", | |
| " epoch_cost = 0\n", | |
| " for i in range(0, m, batch_size):\n", | |
| " X_batch = X[i:i+batch_size, :, :, :]\n", | |
| " Y_batch = Y[:, i:i+batch_size]\n", | |
| "\n", | |
| " AL_batch, caches_batch = self.forward_pass(X_batch)\n", | |
| " cost = self.compute_cost(AL_batch, Y_batch)\n", | |
| " epoch_cost += cost * X_batch.shape[0]\n", | |
| " self.backpropagation(AL_batch, Y_batch, caches_batch)\n", | |
| " self.update_parameters(learning_rate)\n", | |
| "\n", | |
| " mean_epoch_cost = epoch_cost\n", | |
| " costs.append(mean_epoch_cost)\n", | |
| "\n", | |
| " print(f\"Epoch {epoch+1}/{epochs}, Cost: {mean_epoch_cost:.4f}\")\n", | |
| "\n", | |
| " print(\"Training completed.\")\n", | |
| " return costs\n", | |
| "\n", | |
| " def predict(self, X):\n", | |
| " AL, _ = self.forward_pass(X)\n", | |
| " predictions = np.argmax(AL, axis=0)\n", | |
| " return predictions" | |
| ], | |
| "metadata": { | |
| "id": "SjHNxEsHCu2H" | |
| }, | |
| "execution_count": 73, | |
| "outputs": [] | |
| }, | |
| { | |
| "cell_type": "code", | |
| "source": [ | |
| "X_train, Y_train, X_test, Y_test, y_test_orig = load_and_prepare_data_cnn()" | |
| ], | |
| "metadata": { | |
| "colab": { | |
| "base_uri": "https://localhost:8080/" | |
| }, | |
| "id": "pOhLOzN9OMf6", | |
| "outputId": "0c08e4f4-d9b9-4822-8601-3991d75ca189" | |
| }, | |
| "execution_count": 42, | |
| "outputs": [ | |
| { | |
| "output_type": "stream", | |
| "name": "stdout", | |
| "text": [ | |
| "Loading MNIST dataset...\n", | |
| "Data loaded and prepared.\n", | |
| "Initializing CNN...\n", | |
| "CNN initialized.\n" | |
| ] | |
| } | |
| ] | |
| }, | |
| { | |
| "cell_type": "code", | |
| "source": [], | |
| "metadata": { | |
| "id": "hQTuk_x8P7YD" | |
| }, | |
| "execution_count": null, | |
| "outputs": [] | |
| }, | |
| { | |
| "cell_type": "code", | |
| "source": [ | |
| "cnn_model = CNN()\n", | |
| "SUBSET_SIZE = 1000" | |
| ], | |
| "metadata": { | |
| "colab": { | |
| "base_uri": "https://localhost:8080/" | |
| }, | |
| "id": "MhO-hXVEPBAo", | |
| "outputId": "498e52d9-8f18-4f5f-89b7-104ab795266d" | |
| }, | |
| "execution_count": 74, | |
| "outputs": [ | |
| { | |
| "output_type": "stream", | |
| "name": "stdout", | |
| "text": [ | |
| "Initializing CNN...\n", | |
| "CNN initialized.\n" | |
| ] | |
| } | |
| ] | |
| }, | |
| { | |
| "cell_type": "code", | |
| "source": [ | |
| "print(\"Training on subset of data...\")\n", | |
| "X_train_subset = X_train[:SUBSET_SIZE]\n", | |
| "Y_train_subset = Y_train[:, :SUBSET_SIZE]\n", | |
| "costs = cnn_model.train(X_train_subset, Y_train_subset, epochs=25, learning_rate=0.01, batch_size=8)" | |
| ], | |
| "metadata": { | |
| "colab": { | |
| "base_uri": "https://localhost:8080/" | |
| }, | |
| "id": "c2PG1URXHR7a", | |
| "outputId": "040f3c8c-b9c0-4c52-ce1a-dc2f6163455b" | |
| }, | |
| "execution_count": 75, | |
| "outputs": [ | |
| { | |
| "output_type": "stream", | |
| "name": "stdout", | |
| "text": [ | |
| "Training on subset of data...\n", | |
| "Starting training for 25 epochs...\n", | |
| "Epoch 1/25, Cost: 2324.9798\n", | |
| "Epoch 2/25, Cost: 2261.9634\n", | |
| "Epoch 3/25, Cost: 2189.5778\n", | |
| "Epoch 4/25, Cost: 2037.9250\n", | |
| "Epoch 5/25, Cost: 1783.1098\n", | |
| "Epoch 6/25, Cost: 1486.0922\n", | |
| "Epoch 7/25, Cost: 1232.4294\n", | |
| "Epoch 8/25, Cost: 1042.2106\n", | |
| "Epoch 9/25, Cost: 902.0981\n", | |
| "Epoch 10/25, Cost: 796.7148\n", | |
| "Epoch 11/25, Cost: 714.8593\n", | |
| "Epoch 12/25, Cost: 649.2456\n", | |
| "Epoch 13/25, Cost: 595.2510\n", | |
| "Epoch 14/25, Cost: 549.9257\n", | |
| "Epoch 15/25, Cost: 511.3004\n", | |
| "Epoch 16/25, Cost: 477.9693\n", | |
| "Epoch 17/25, Cost: 448.9134\n", | |
| "Epoch 18/25, Cost: 423.3421\n", | |
| "Epoch 19/25, Cost: 400.6551\n", | |
| "Epoch 20/25, Cost: 380.3701\n", | |
| "Epoch 21/25, Cost: 362.0987\n", | |
| "Epoch 22/25, Cost: 345.5339\n", | |
| "Epoch 23/25, Cost: 330.4288\n", | |
| "Epoch 24/25, Cost: 316.5674\n", | |
| "Epoch 25/25, Cost: 303.7871\n", | |
| "Training completed.\n" | |
| ] | |
| } | |
| ] | |
| }, | |
| { | |
| "cell_type": "code", | |
| "source": [ | |
| "print(\"Evaluating on test set...\")\n", | |
| "SUBSET_TEST_SIZE = 200\n", | |
| "X_test_subset = X_test[:SUBSET_TEST_SIZE]\n", | |
| "y_test_subset_orig = y_test_orig[:SUBSET_TEST_SIZE]\n", | |
| "predictions = cnn_model.predict(X_test_subset)\n", | |
| "accuracy = np.mean(predictions == y_test_subset_orig) * 100\n", | |
| "print(f\"Test Set Accuracy: {accuracy:.2f}%\")" | |
| ], | |
| "metadata": { | |
| "colab": { | |
| "base_uri": "https://localhost:8080/" | |
| }, | |
| "id": "UpBwdim8OSel", | |
| "outputId": "4a697d2b-6a1a-401d-b22c-81f29df2cd91" | |
| }, | |
| "execution_count": 76, | |
| "outputs": [ | |
| { | |
| "output_type": "stream", | |
| "name": "stdout", | |
| "text": [ | |
| "Evaluating on test set...\n", | |
| "Test Set Accuracy: 88.50%\n" | |
| ] | |
| } | |
| ] | |
| }, | |
| { | |
| "cell_type": "code", | |
| "source": [], | |
| "metadata": { | |
| "id": "J90kzxWNW6ah" | |
| }, | |
| "execution_count": null, | |
| "outputs": [] | |
| } | |
| ] | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment