{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.10.14","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"nvidiaTeslaT4","dataSources":[],"dockerImageVersionId":30787,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"# MNIST Digit Classification with TinyCNN in PyTorch 🖊️🔢\n\nIn this notebook, we’ll classify handwritten digits from the **MNIST** dataset using a custom **TinyCNN** model in PyTorch. This model is designed to be lightweight and fast to train, making it ideal for quick demonstrations or workshop settings where training time is limited.\n\nThe TinyCNN architecture consists of just a few convolutional and pooling layers, followed by a single fully connected layer. This minimal design keeps the model small and efficient, allowing us to focus on essential CNN concepts without long training times.\n\nWe’ll:\n1. Build a TinyCNN model with essential layers for image classification.\n2. Prepare the MNIST dataset.\n3. Train the TinyCNN model on this dataset.\n4. Evaluate its performance with metrics like accuracy.\n5. Visualize predictions to see how well the model distinguishes between the digit classes.\n\nLet’s get started!","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19"}},{"cell_type":"markdown","source":"## Step 1: Import Libraries\n\nHere, we import the essential libraries for this project:\n\n- **`torch`, `torch.nn`, `torch.optim`, and `torch.nn.functional`**: Core PyTorch libraries for building and training our neural network.\n- **`DataLoader`, `datasets`, and `transforms`**: Tools from `torchvision` for loading the MNIST dataset and applying necessary transformations.\n- **`matplotlib.pyplot`**: For visualizing images and model predictions.\n- **`tqdm`**: To display progress bars during training for better tracking of each epoch.\n\nThe last line, **`%matplotlib inline`**, makes plots appear directly within the notebook.\n","metadata":{}},{"cell_type":"code","source":"import torch\nimport torch.nn as nn\nimport torch.optim as optim\nimport torch.nn.functional as F\nfrom torch.utils.data import DataLoader\nfrom torchvision import datasets, transforms\nimport matplotlib.pyplot as plt\nfrom collections import Counter\nfrom tqdm import tqdm\nfrom sklearn.metrics import confusion_matrix, classification_report\nimport seaborn as sns\nimport time\nimport torch\nimport random\nimport numpy as np\nimport seaborn as sns\nimport matplotlib.pyplot as plt\nimport time\nimport torch\nimport cv2\nfrom PIL import Image, ImageEnhance\n\n%matplotlib inline","metadata":{"execution":{"iopub.status.busy":"2024-10-27T20:02:34.788185Z","iopub.execute_input":"2024-10-27T20:02:34.788567Z","iopub.status.idle":"2024-10-27T20:02:34.798863Z","shell.execute_reply.started":"2024-10-27T20:02:34.788529Z","shell.execute_reply":"2024-10-27T20:02:34.797805Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Step 2: Define Data Transformations\n\nWe apply transformations to preprocess the images before feeding them into the model:\n- **Convert to Tensor**: Converts the image to a PyTorch tensor.\n- **Normalize**: Scales pixel values to the range \\[-1, 1\\] for faster and more stable training.\n","metadata":{}},{"cell_type":"code","source":"# Define transformations: Resize and normalize to [-1, 1]\ntransform = transforms.Compose([\n    transforms.ToTensor(),\n    transforms.Normalize((0.5,), (0.5,))\n])","metadata":{"execution":{"iopub.status.busy":"2024-10-27T20:02:34.800653Z","iopub.execute_input":"2024-10-27T20:02:34.800961Z","iopub.status.idle":"2024-10-27T20:02:34.809178Z","shell.execute_reply.started":"2024-10-27T20:02:34.800930Z","shell.execute_reply":"2024-10-27T20:02:34.808391Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Step 3: Load the MNIST Dataset\n\nWe’ll load the MNIST dataset, which consists of 60,000 training images and 10,000 test images of handwritten digits (0-9). Each image is a grayscale image of size 28x28 pixels.\n\nTo monitor the download process, we’ll use `tqdm` to show a progress bar, making it easier to track the dataset download.\n","metadata":{}},{"cell_type":"code","source":"# Define a custom function to download with a progress bar\ndef download_mnist_data():\n    with tqdm(total=1, desc=\"Downloading MNIST dataset\") as pbar:\n        train_dataset = datasets.MNIST(root=\"./data\", train=True, transform=transform, download=True)\n        test_dataset = datasets.MNIST(root=\"./data\", train=False, transform=transform, download=True)\n        pbar.update()\n    return train_dataset, test_dataset\n\n# Load the datasets with progress visualization\ntrain_dataset, test_dataset = download_mnist_data()","metadata":{"execution":{"iopub.status.busy":"2024-10-27T20:02:34.810359Z","iopub.execute_input":"2024-10-27T20:02:34.810948Z","iopub.status.idle":"2024-10-27T20:02:39.378484Z","shell.execute_reply.started":"2024-10-27T20:02:34.810907Z","shell.execute_reply":"2024-10-27T20:02:39.377312Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Load a sample image from the training dataset\nsample_img, _ = train_dataset[0]  # Get the first image and its label\n\n# Display the original image\nplt.figure(figsize=(5, 5))\nplt.imshow(sample_img.squeeze(), cmap=\"gray\")\nplt.title(\"Original Image (28x28)\")\nplt.axis(\"off\")\nplt.show()","metadata":{"execution":{"iopub.status.busy":"2024-10-27T20:02:39.379876Z","iopub.execute_input":"2024-10-27T20:02:39.380321Z","iopub.status.idle":"2024-10-27T20:02:39.613577Z","shell.execute_reply.started":"2024-10-27T20:02:39.380275Z","shell.execute_reply":"2024-10-27T20:02:39.612600Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Step 4: Set Up DataLoaders\n\nThe **DataLoader** in PyTorch helps manage and load the dataset in mini-batches, which is essential for efficient training and evaluation. We configure the DataLoader with:\n\n- **batch_size**: Defines the number of images per batch (e.g., 32).\n- **shuffle**: Randomly shuffles the training data at the start of each epoch to improve model generalization.\n- **num_workers**: Sets the number of subprocesses for data loading, which can speed up data loading on multi-core systems.\n\nFor MNIST, we have:\n- **Training Set**: Used to train the model.\n- **Test Set**: Used to evaluate the model’s performance after training.\n\nThe DataLoaders manage loading the data in batches, making it easier to train and evaluate the model.\n","metadata":{}},{"cell_type":"code","source":"# Define DataLoaders for training and testing\ntrain_loader = DataLoader(train_dataset, batch_size=32, shuffle=True, num_workers=2)\ntest_loader = DataLoader(test_dataset, batch_size=32, shuffle=False, num_workers=2)","metadata":{"execution":{"iopub.status.busy":"2024-10-27T20:02:39.615824Z","iopub.execute_input":"2024-10-27T20:02:39.616139Z","iopub.status.idle":"2024-10-27T20:02:39.621220Z","shell.execute_reply.started":"2024-10-27T20:02:39.616106Z","shell.execute_reply":"2024-10-27T20:02:39.620240Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Step 5: Visualize a Batch of Images\n\nTo get a better sense of the data, we’ll visualize a mini-batch of images and their labels from the **MNIST** dataset.\n\nThe images will be displayed in a 4x8 grid, with each image labeled by the corresponding digit (0–9). This provides a quick overview of the dataset and helps us verify that the data loading and transformations are set up correctly.\n","metadata":{}},{"cell_type":"code","source":"# Get a single batch of images and labels\nbatch_images, batch_labels = next(iter(train_loader))\n\n# Display the batch of images\nfig, axes = plt.subplots(4, 8, figsize=(12, 6))\nfig.suptitle(f\"Mini-Batch of {len(batch_images)} Images\", fontsize=16)\n\n# Loop through each image in the batch and display it\nfor i, (img, label) in enumerate(zip(batch_images[:32], batch_labels[:32])):  # Limit to 32 images for display\n    ax = axes[i // 8, i % 8]\n    img = img.squeeze().numpy()  # Convert from Tensor format to NumPy array for display\n    img = img * 0.5 + 0.5  # De-normalize for visualization\n    ax.imshow(img, cmap=\"gray\")\n    ax.set_title(str(label.item()))\n    ax.axis(\"off\")\n\nplt.tight_layout(rect=[0, 0.03, 1, 0.95])\nplt.show()","metadata":{"execution":{"iopub.status.busy":"2024-10-27T20:02:39.622377Z","iopub.execute_input":"2024-10-27T20:02:39.623176Z","iopub.status.idle":"2024-10-27T20:02:42.152379Z","shell.execute_reply.started":"2024-10-27T20:02:39.623128Z","shell.execute_reply":"2024-10-27T20:02:42.151381Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Step 6: Check Data Distribution in the MNIST Dataset\n\nBefore training, we’ll examine the distribution of digit classes in the MNIST dataset to ensure it’s balanced. Understanding the data distribution can help us identify potential class imbalances that might affect model performance.\n\n1. **Count Images by Class**: We’ll go through the dataset and count the number of images for each digit (0–9).\n2. **Visualize the Distribution**: Plot the class distribution to visually confirm balance across classes.\n\nSince MNIST already has separate training and test sets, we won’t need to manually split the data.\n","metadata":{}},{"cell_type":"code","source":"# Count the number of images for each digit in the training set\ntrain_labels = [label for _, label in train_dataset]\nclass_counts = Counter(train_labels)\n\n# Plot the class distribution\nclasses = list(class_counts.keys())\ncounts = list(class_counts.values())\n\nplt.figure(figsize=(10, 6))\nplt.bar(classes, counts, color='skyblue')\nplt.xlabel(\"Digit Class\")\nplt.ylabel(\"Number of Images\")\nplt.title(\"Distribution of Digit Classes in MNIST Training Set\")\nplt.xticks(classes)\nplt.show()","metadata":{"execution":{"iopub.status.busy":"2024-10-27T20:02:42.153807Z","iopub.execute_input":"2024-10-27T20:02:42.154164Z","iopub.status.idle":"2024-10-27T20:02:53.803917Z","shell.execute_reply.started":"2024-10-27T20:02:42.154127Z","shell.execute_reply":"2024-10-27T20:02:53.803025Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Step 7: Build and Initialize TinyCNN for MNIST\n\nFor the MNIST classification task, we’ll use a custom **TinyCNN** model. This model is lightweight and efficient, designed to train quickly on small datasets like MNIST, making it ideal for quick demonstrations.\n\n1. **Define the TinyCNN Architecture**: This model includes minimal convolutional and pooling layers to capture basic image features, followed by a fully connected layer for classification.\n2. **Initialize the Model**: We’ll set up TinyCNN with compact layers to keep the number of trainable parameters low.\n3. **Specify Output Classes**: The final layer outputs 10 classes, one for each digit (0-9).\n4. **Move Model to Device**: Send the model to GPU if available, or to CPU otherwise, for faster processing.\n\nThis setup allows us to train quickly on the MNIST dataset while still achieving meaningful results.\n","metadata":{}},{"cell_type":"code","source":"# Define a Depthwise Separable Convolutional Layer\nclass DepthwiseSeparableConv(nn.Module):\n    def __init__(self, in_channels, out_channels, kernel_size=3, stride=1, padding=1):\n        super(DepthwiseSeparableConv, self).__init__()\n        # Depthwise convolution\n        self.depthwise = nn.Conv2d(in_channels, in_channels, kernel_size=kernel_size, \n                                   stride=stride, padding=padding, groups=in_channels)\n        # Pointwise convolution\n        self.pointwise = nn.Conv2d(in_channels, out_channels, kernel_size=1)\n    \n    def forward(self, x):\n        x = self.depthwise(x)\n        x = self.pointwise(x)\n        return x\n\n# Define the TinyCNN model using Depthwise Separable Convolutions\nclass TinyCNN(nn.Module):\n    def __init__(self):\n        super(TinyCNN, self).__init__()\n        # Use Depthwise Separable Convolutions for efficient computation\n        self.conv1 = DepthwiseSeparableConv(1, 16, kernel_size=3, stride=1, padding=1)  # 1 input channel, 16 output channels\n        self.conv2 = DepthwiseSeparableConv(16, 32, kernel_size=3, stride=1, padding=1)  # 16 input channels, 32 output channels\n        self.pool = nn.MaxPool2d(kernel_size=2, stride=2)\n        \n        # Fully connected layer\n        self.fc1 = nn.Linear(32 * 7 * 7, 10)  # Output layer for 10 classes (0-9 for MNIST)\n    \n    def forward(self, x):\n        x = self.pool(F.relu(self.conv1(x)))  # Conv1 + ReLU + Pooling\n        x = self.pool(F.relu(self.conv2(x)))  # Conv2 + ReLU + Pooling\n        x = x.view(-1, 32 * 7 * 7)  # Flatten for fully connected layer\n        x = self.fc1(x)  # Output layer\n        return x\n\n# Initialize the model\nmodel = TinyCNN()\ndevice = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\nmodel.to(device)","metadata":{"execution":{"iopub.status.busy":"2024-10-27T20:02:53.805279Z","iopub.execute_input":"2024-10-27T20:02:53.805964Z","iopub.status.idle":"2024-10-27T20:02:54.058503Z","shell.execute_reply.started":"2024-10-27T20:02:53.805928Z","shell.execute_reply":"2024-10-27T20:02:54.057414Z"},"collapsed":true,"jupyter":{"outputs_hidden":true},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Check number of parameters\ntotal_params = sum(p.numel() for p in model.parameters())\nprint(f\"Total parameters: {total_params}\")","metadata":{"execution":{"iopub.status.busy":"2024-10-27T20:02:54.059888Z","iopub.execute_input":"2024-10-27T20:02:54.060341Z","iopub.status.idle":"2024-10-27T20:02:54.066613Z","shell.execute_reply.started":"2024-10-27T20:02:54.060291Z","shell.execute_reply":"2024-10-27T20:02:54.065503Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Step 8: Define Loss Function and Optimizer\n\nTo train our MobileNetV3 model, we need:\n1. **Loss Function (Criterion)**: `CrossEntropyLoss` measures how well the model’s predictions match the actual labels.\n2. **Optimizer**: `Adam` optimizer helps update the model’s weights based on the computed gradients.\n","metadata":{}},{"cell_type":"code","source":"# Define the loss function\ncriterion = nn.CrossEntropyLoss()\n\n# Define the optimizer\noptimizer = optim.Adam(model.parameters(), lr=0.001)","metadata":{"execution":{"iopub.status.busy":"2024-10-27T20:02:54.067633Z","iopub.execute_input":"2024-10-27T20:02:54.067964Z","iopub.status.idle":"2024-10-27T20:02:54.077086Z","shell.execute_reply.started":"2024-10-27T20:02:54.067927Z","shell.execute_reply":"2024-10-27T20:02:54.076172Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Step 9: Train and Evaluate TinyCNN\n\nNow, we’ll train our **TinyCNN** model on the MNIST dataset. The training loop will iterate over multiple epochs, updating the model’s parameters to minimize the loss on the training set. After each epoch, we’ll evaluate the model on the test set to monitor its performance.\n\nHere’s an overview of the training process:\n\n1. **Training Phase**: For each mini-batch, we calculate the loss, perform backpropagation, and update the model parameters. We also keep track of the training accuracy.\n2. **Validation Phase**: After each training epoch, we evaluate the model on the test set to measure its generalization performance.\n3. **Metrics**: After each epoch, we’ll print the training loss, training accuracy, validation loss, and validation accuracy.\n\nLet’s begin!\n","metadata":{}},{"cell_type":"code","source":"# Move model to the device (CPU or GPU)\ndevice = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\nmodel.to(device)\n\n# Training parameters\nnum_epochs = 5\n\n# Initialize lists to store losses and accuracies for each epoch\ntrain_losses, val_losses = [], []\ntrain_accuracies, val_accuracies = [], []\n\n# Training loop with metrics tracking\nfor epoch in range(num_epochs):\n    model.train()\n    running_loss, correct, total = 0.0, 0, 0\n\n    for images, labels in tqdm(train_loader, desc=f\"Epoch {epoch+1}/{num_epochs} - Training\"):\n        images, labels = images.to(device), labels.to(device)\n        \n        # Forward pass and loss calculation\n        outputs = model(images)\n        loss = criterion(outputs, labels)\n\n        # Backward pass and optimization\n        optimizer.zero_grad()\n        loss.backward()\n        optimizer.step()\n\n        # Update metrics\n        running_loss += loss.item() * images.size(0)\n        _, predicted = torch.max(outputs, 1)\n        correct += (predicted == labels).sum().item()\n        total += labels.size(0)\n    \n    # Calculate and store training loss and accuracy\n    train_loss = running_loss / total\n    train_accuracy = correct / total\n    train_losses.append(train_loss)\n    train_accuracies.append(train_accuracy)\n\n    print(f\"Epoch [{epoch+1}/{num_epochs}] - Train Loss: {train_loss:.4f}, Train Accuracy: {train_accuracy:.4f}\")\n\n    # Validation phase\n    model.eval()\n    val_running_loss, correct, total = 0.0, 0, 0\n    with torch.no_grad():\n        for images, labels in test_loader:\n            images, labels = images.to(device), labels.to(device)\n            outputs = model(images)\n            loss = criterion(outputs, labels)\n\n            # Update validation metrics\n            val_running_loss += loss.item() * images.size(0)\n            _, predicted = torch.max(outputs, 1)\n            correct += (predicted == labels).sum().item()\n            total += labels.size(0)\n    \n    # Calculate and store validation loss and accuracy\n    val_loss = val_running_loss / total\n    val_accuracy = correct / total\n    val_losses.append(val_loss)\n    val_accuracies.append(val_accuracy)\n\n    print(f\"Validation Loss: {val_loss:.4f}, Validation Accuracy: {val_accuracy:.4f}\")\n","metadata":{"execution":{"iopub.status.busy":"2024-10-27T20:02:54.078409Z","iopub.execute_input":"2024-10-27T20:02:54.079127Z","iopub.status.idle":"2024-10-27T20:04:03.049676Z","shell.execute_reply.started":"2024-10-27T20:02:54.079086Z","shell.execute_reply":"2024-10-27T20:04:03.048519Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Step 10: Plot Training and Validation Metrics\n\nVisualizing the loss and accuracy over epochs helps us understand how well the model is learning. Ideally, both training and validation loss should decrease, while accuracy should increase.\n","metadata":{}},{"cell_type":"code","source":"# Plot Loss\nplt.figure(figsize=(12, 5))\nplt.subplot(1, 2, 1)\nplt.plot(range(1, num_epochs + 1), train_losses, label='Train Loss', color='blue', marker='o')\nplt.plot(range(1, num_epochs + 1), val_losses, label='Validation Loss', color='orange', marker='o')\nplt.xlabel(\"Epoch\")\nplt.ylabel(\"Loss\")\nplt.legend()\nplt.title(\"Training and Validation Loss\")\n\n# Plot Accuracy\nplt.subplot(1, 2, 2)\nplt.plot(range(1, num_epochs + 1), train_accuracies, label='Train Accuracy', color='blue', marker='o')\nplt.plot(range(1, num_epochs + 1), val_accuracies, label='Validation Accuracy', color='orange', marker='o')\nplt.xlabel(\"Epoch\")\nplt.ylabel(\"Accuracy\")\nplt.legend()\nplt.title(\"Training and Validation Accuracy\")\n\nplt.tight_layout()\nplt.show()","metadata":{"execution":{"iopub.status.busy":"2024-10-27T20:04:03.051116Z","iopub.execute_input":"2024-10-27T20:04:03.051472Z","iopub.status.idle":"2024-10-27T20:04:03.718510Z","shell.execute_reply.started":"2024-10-27T20:04:03.051437Z","shell.execute_reply":"2024-10-27T20:04:03.717558Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Choose a random image from the test set\nrandom_index = random.randint(0, len(test_dataset) - 1)\nrandom_img, random_label = test_dataset[random_index]\n\n# Prepare the image for inference\nmodel.eval()  # Set the model to evaluation mode\nwith torch.no_grad():\n    random_img_tensor = random_img.unsqueeze(0).to(device)  # Add batch dimension and move to device\n    output = model(random_img_tensor)  # Get model output\n    probabilities = torch.softmax(output, dim=1)  # Convert logits to probabilities\n    predicted_label = torch.argmax(probabilities, dim=1).item()\n    predicted_prob = probabilities[0, predicted_label].item()\n\n# Display the random test image with the predicted label and probability\nplt.figure(figsize=(4, 4))\nplt.imshow(random_img.squeeze(), cmap=\"gray\")\nplt.title(f\"Predicted: {predicted_label} (Confidence: {predicted_prob*100:.2f}%)\\nTrue Label: {random_label}\")\nplt.axis(\"off\")\nplt.show()","metadata":{"execution":{"iopub.status.busy":"2024-10-27T20:04:03.720012Z","iopub.execute_input":"2024-10-27T20:04:03.720337Z","iopub.status.idle":"2024-10-27T20:04:03.891256Z","shell.execute_reply.started":"2024-10-27T20:04:03.720301Z","shell.execute_reply":"2024-10-27T20:04:03.890177Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Step 11: Model Evaluation and Real-Time Performance Analysis\n\nIn this step, we’ll assess our model’s performance on the MNIST test set by measuring:\n\n1. **Test Accuracy and Loss**: Calculate the model’s accuracy and average loss on the test set to gauge overall performance.\n2. **Confusion Matrix**: Visualize the model’s performance across each digit (0–9), showing correct and incorrect predictions.\n3. **Inference Time**: Measure the average time per image, assessing the model’s suitability for real-time applications.\n4. **Memory Usage**: Check memory consumption on the device to understand resource efficiency.\n\nThese metrics provide a complete view of the model’s accuracy, efficiency, and resource requirements.\n","metadata":{}},{"cell_type":"code","source":"test_loader = DataLoader(test_dataset, batch_size=32, shuffle=False, num_workers=2)","metadata":{"execution":{"iopub.status.busy":"2024-10-27T20:04:03.895697Z","iopub.execute_input":"2024-10-27T20:04:03.896084Z","iopub.status.idle":"2024-10-27T20:04:03.901377Z","shell.execute_reply.started":"2024-10-27T20:04:03.896053Z","shell.execute_reply":"2024-10-27T20:04:03.900253Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Initialize lists and counters for test metrics\ntest_loss = 0.0\ncorrect = 0\ntotal = 0\nall_preds = []\nall_labels = []\n\n# Measure inference time\nstart_time = time.time()\n\nmodel.eval()\nwith torch.no_grad():\n    for images, labels in test_loader:\n        images, labels = images.to(device), labels.to(device)\n        \n        # Forward pass and predictions\n        outputs = model(images)\n        loss = criterion(outputs, labels)\n        \n        # Update test loss\n        test_loss += loss.item() * images.size(0)\n        \n        # Get predictions and ground truth\n        _, predicted = torch.max(outputs, 1)\n        correct += (predicted == labels).sum().item()\n        total += labels.size(0)\n        \n        # Store predictions and labels for confusion matrix\n        all_preds.extend(predicted.cpu().numpy())\n        all_labels.extend(labels.cpu().numpy())\n\n# Calculate test accuracy and average loss\ntest_accuracy = correct / total\ntest_loss /= total\nend_time = time.time()\ninference_time = (end_time - start_time) / total\n\nprint(f\"Test Loss: {test_loss:.4f}\")\nprint(f\"Test Accuracy: {test_accuracy:.4f}\")\nprint(f\"Average Inference Time per Image: {inference_time * 1000:.2f} ms\")","metadata":{"execution":{"iopub.status.busy":"2024-10-27T20:04:03.903210Z","iopub.execute_input":"2024-10-27T20:04:03.903897Z","iopub.status.idle":"2024-10-27T20:04:05.835661Z","shell.execute_reply.started":"2024-10-27T20:04:03.903850Z","shell.execute_reply":"2024-10-27T20:04:05.834487Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Generate confusion matrix and classification report\nconf_matrix = confusion_matrix(all_labels, all_preds)\nprint(\"\\nClassification Report:\\n\", classification_report(all_labels, all_preds))","metadata":{"execution":{"iopub.status.busy":"2024-10-27T20:04:05.837121Z","iopub.execute_input":"2024-10-27T20:04:05.837450Z","iopub.status.idle":"2024-10-27T20:04:05.872819Z","shell.execute_reply.started":"2024-10-27T20:04:05.837415Z","shell.execute_reply":"2024-10-27T20:04:05.871956Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### Step 12: Visualize Confusion Matrix with Enhanced Contrast\n\nThe confusion matrix below provides a detailed view of the model’s performance across each digit (0-9). It highlights:\n- **Correct Predictions**: Found along the diagonal.\n- **Misclassifications**: Off-diagonal elements, showing where the model confuses one digit for another.\n  \nUsing a high-contrast color map improves readability, helping us identify patterns in the model's predictions.\n","metadata":{}},{"cell_type":"code","source":"# Plot confusion matrix with improved contrast\nplt.figure(figsize=(10, 8))\nsns.heatmap(conf_matrix, annot=True, fmt='d', cmap=\"YlGnBu\", \n            xticklabels=range(10), yticklabels=range(10), \n            cbar_kws={'label': 'Count'})\nplt.xlabel(\"Predicted Label\")\nplt.ylabel(\"True Label\")\nplt.title(\"Confusion Matrix - MNIST Test Set\")\nplt.show()\n","metadata":{"execution":{"iopub.status.busy":"2024-10-27T20:04:05.873912Z","iopub.execute_input":"2024-10-27T20:04:05.874270Z","iopub.status.idle":"2024-10-27T20:04:06.447917Z","shell.execute_reply.started":"2024-10-27T20:04:05.874235Z","shell.execute_reply":"2024-10-27T20:04:06.446896Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"if torch.cuda.is_available():\n    mem_used = torch.cuda.memory_allocated(device) / 1024**2  # Convert bytes to MB\n    print(f\"Memory Usage: {mem_used:.2f} MB\")","metadata":{"execution":{"iopub.status.busy":"2024-10-27T20:04:06.449461Z","iopub.execute_input":"2024-10-27T20:04:06.450154Z","iopub.status.idle":"2024-10-27T20:04:06.456161Z","shell.execute_reply.started":"2024-10-27T20:04:06.450105Z","shell.execute_reply":"2024-10-27T20:04:06.455058Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### Step 13: Download an Example Digit Image\n\nTo test our model on a real-world digit image, we’ll first download a sample handwritten digit image from the web. Using `wget`, we’ll save the image in our working directory.\n\n- **Command**: We use `wget` with the `-O` option to specify the output file name (`digit_sample.jpg`).\n- **URL**: The image URL is wrapped in quotes to handle any special characters.\n- **`--no-check-certificate`**: This option skips SSL certificate verification, which can help avoid issues with some URLs.\n\nThe downloaded image will be used in the next step for preprocessing and model prediction, allowing us to test our model on data outside of the MNIST dataset.\n","metadata":{}},{"cell_type":"code","source":"!wget -O \"/kaggle/working/speed_limit.jpg\" \"https://driving-tests.org/wp-content/uploads/2020/03/shutterstock_294221969.jpg\" --no-check-certificate\n!wget -O \"/kaggle/working/house_number.jpg\" \"https://www.emtek.com/media/original_images/2Emtek_Modern_House_Number_Exterior_Install_US19_1872x1929px_300dpi_RGB_Hi.jpg\" --no-check-certificate\n!wget -O \"/kaggle/working/barcode_digits.jpg\" \"https://tritonstore.com.au/wp-content/uploads/EAN-13.jpg\" --no-check-certificate","metadata":{"execution":{"iopub.status.busy":"2024-10-27T20:24:20.533638Z","iopub.execute_input":"2024-10-27T20:24:20.534069Z","iopub.status.idle":"2024-10-27T20:24:24.060188Z","shell.execute_reply.started":"2024-10-27T20:24:20.534032Z","shell.execute_reply":"2024-10-27T20:24:24.059189Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### Step 14: Preprocess the Image\n\nTo prepare the downloaded image for prediction, we’ll apply the following transformations:\n1. **Crop the Region of Interest**: Isolate the area containing the numbers to focus on relevant parts of the image.\n2. **Convert to Grayscale**: Ensures the image has a single color channel, matching the format of MNIST images, which are black-and-white.\n3. **Resize to 28x28**: Resizes the cropped image to match the dimensions used in MNIST training.\n4. **Normalize**: Scales pixel values to the range expected by the model, improving prediction accuracy.\n\nThese steps ensure that our image is correctly formatted and focused for input into the MNIST digit recognition model, helping the model interpret the digits more effectively.\n","metadata":{}},{"cell_type":"code","source":"# List of sample images to test (replace with actual URLs or paths as needed)\nimages = [\n    \"/kaggle/working/speed_limit.jpg\",\n    \"/kaggle/working/house_number.jpg\",\n    \"/kaggle/working/barcode_digits.jpg\",\n]\nimage_path = images[0]  # Choose the first image\n\n# Load the image and convert to grayscale\ninput_image = Image.open(image_path).convert(\"L\")\n\n# Step 1: Increase contrast to make digits more distinguishable\nenhancer = ImageEnhance.Contrast(input_image)\ninput_image = enhancer.enhance(2.0)  # Adjust contrast as needed\n\n# Convert the PIL image to an OpenCV-compatible format for bounding box display\ninput_image_cv = np.array(input_image)\n\n# Step 2: Invert the colors to match MNIST (white digits on black background)\n# Invert grayscale values (If Apply)\ninput_image = Image.fromarray(255 - np.array(input_image))  \n\n# Step 3: Define the bounding box for the region of interest\n\n# Coordinates to focus on the number region (Speed Limit)\nx, y, w, h = 585, 200, 115, 115\n\n# Coordinates to focus on the number region (House Number)\n# x, y, w, h = 580, 80, 300, 300\n\n# Coordinates to focus on the number region (Barcode Digits)\n# x, y, w, h = 745, 455, 40, 60\n\n# Display the original image with bounding box\nplt.figure(figsize=(6, 4))\nplt.imshow(input_image_cv, cmap=\"gray\")\nplt.gca().add_patch(plt.Rectangle((x, y), w, h, edgecolor='red', linewidth=2, fill=False))\nplt.title(\"Original Image with Bounding Box (Region of Interest)\")\nplt.axis(\"off\")\nplt.show()\n\n# Step 4: Crop the region of interest using the bounding box coordinates\ncropped_image = input_image.crop((x, y, x + w, y + h))\n\n# Display the cropped region\nplt.figure(figsize=(4, 4))\nplt.imshow(cropped_image, cmap=\"gray\")\nplt.title(\"Cropped Region of Interest\")\nplt.axis(\"off\")\nplt.show()\n\n# Step 5: Resize and preprocess the cropped image for the CNN\n# Define transformations: Resize to 28x28, convert to tensor, and normalize\npreprocess = transforms.Compose([\n    transforms.Resize((28, 28)),\n    transforms.ToTensor(),  # Converts to tensor with 1 channel\n    transforms.Normalize((0.5,), (0.5,))  # Normalize to match MNIST model expectations\n])\n\n# Apply transformations and add batch dimension\ninput_tensor = preprocess(cropped_image).unsqueeze(0).to(device)  # Shape: (1, 1, 28, 28)\n\n# Display the final preprocessed image for the model\nplt.figure(figsize=(4, 4))\nplt.imshow(input_tensor.squeeze().cpu().numpy(), cmap=\"gray\")\nplt.title(\"Preprocessed Image (28x28) for CNN\")\nplt.axis(\"off\")\nplt.show()\n\n# Print tensor shape to verify it's ready for model input\nprint(\"Input tensor shape for CNN:\", input_tensor.shape)","metadata":{"execution":{"iopub.status.busy":"2024-10-27T20:24:27.617216Z","iopub.execute_input":"2024-10-27T20:24:27.618411Z","iopub.status.idle":"2024-10-27T20:24:28.208186Z","shell.execute_reply.started":"2024-10-27T20:24:27.618361Z","shell.execute_reply":"2024-10-27T20:24:28.206719Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### Step 15: Display Top Class Probabilities\n\nAfter making a prediction, we’ll display the model's top 3 class predictions along with their probabilities. This provides insights into the model's confidence and shows the closest alternative classes.\n\n1. **Softmax Transformation**: Converts the model's output to probabilities for easier interpretation of confidence.\n2. **Top 3 Predictions**: Extracts the top 3 classes and their probabilities.\n3. **Visualization**: Displays the predicted class on the image, with the top 3 predictions listed below.\n\nThis step helps us understand the model’s confidence and potential ambiguities in classification.\n","metadata":{}},{"cell_type":"code","source":"# Set the model to evaluation mode and make a prediction\nmodel.eval()\nwith torch.no_grad():\n    output = model(input_tensor)  # Forward pass\n    probabilities = F.softmax(output, dim=1)  # Convert logits to probabilities\n\n    # Get the top 3 predictions\n    top_probs, top_indices = torch.topk(probabilities, 3)  # Get top 3 probabilities and class indices\n\n# Convert top probabilities and indices to numpy arrays\ntop_probs = top_probs.squeeze().cpu().numpy() * 100  # Convert to percentages\ntop_indices = top_indices.squeeze().cpu().numpy()\n\n# Define class names for MNIST (digits 0-9)\nclass_names = [str(i) for i in range(10)]\n\n# Display the input image and predicted label\nplt.figure(figsize=(4, 4))\nplt.imshow(input_tensor.squeeze().cpu().numpy(), cmap=\"gray\")\nplt.title(f\"Predicted: {class_names[top_indices[0]]} ({top_probs[0]:.2f}%)\")\nplt.axis(\"off\")\nplt.show()\n\n# Print the top 3 class probabilities\nprint(\"Top 3 Predictions:\")\nfor i in range(3):\n    print(f\"{class_names[top_indices[i]]}: {top_probs[i]:.2f}%\")\n","metadata":{"execution":{"iopub.status.busy":"2024-10-27T20:23:51.604876Z","iopub.execute_input":"2024-10-27T20:23:51.605729Z","iopub.status.idle":"2024-10-27T20:23:51.760706Z","shell.execute_reply.started":"2024-10-27T20:23:51.605688Z","shell.execute_reply":"2024-10-27T20:23:51.759187Z"},"trusted":true},"execution_count":null,"outputs":[]}]}