{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.12.12","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"none","dataSources":[{"sourceType":"competition","sourceId":3004,"databundleVersionId":861823},{"sourceType":"datasetVersion","sourceId":242592,"datasetId":102285,"databundleVersionId":254413}],"dockerImageVersionId":31328,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"# Handwritten Digit Classification using CNN (PyTorch + MNIST)","metadata":{}},{"cell_type":"markdown","source":"## Introduction\n\nIn this project, we build a Convolutional Neural Network (CNN) to classify handwritten digits from the MNIST dataset.\n\nThe goal is to develop a simple yet effective deep learning model while understanding the fundamentals of:\n- Image classification\n- Convolutional Neural Networks (CNNs)\n- Model training and evaluation in PyTorch\n\nThis notebook is designed to be **clear, reproducible, and beginner-friendly**, making it a solid starting point for anyone entering the field of Deep Learning.","metadata":{}},{"cell_type":"markdown","source":"## Problem Statement\n\nThe objective of this project is to build a supervised machine learning model capable of correctly classifying handwritten digits (0–9).\n\nGiven:\n- A grayscale image of size 28x28 pixels\n\nThe model must predict:\n- The corresponding digit label (0 to 9)\n\nThis is a **multi-class classification problem** with 10 possible output classes.\n\n**Evaluation Metric:**\n- Accuracy","metadata":{}},{"cell_type":"markdown","source":"## Dataset Description\n\nThe MNIST dataset is one of the most widely used datasets in computer vision and deep learning.\n\n**Key characteristics:**\n- Total samples: 70,000 images\n  - Training set: 60,000\n  - Test set: 10,000\n- Image size: 28x28 pixels\n- Color: Grayscale (1 channel)\n- Classes: 10 (digits from 0 to 9)\n\n**Source:**\n- Yann LeCun et al.\n- Available via `torchvision.datasets.MNIST`\n\n**Preprocessing steps:**\n- Conversion to tensors\n- Normalization (mean = 0.5, std = 0.5)","metadata":{}},{"cell_type":"markdown","source":"## Importing Libraries","metadata":{}},{"cell_type":"code","source":"# =========================================================\n# 1. IMPORT LIBRARIES\n# =========================================================\nimport random\nimport numpy as np\nimport pandas as pd\nimport matplotlib.pyplot as plt\nimport os\nimport torch\nimport torch.nn as nn\nimport torch.optim as optim\nimport torchvision\nimport torchvision.transforms as transforms\nfrom torch.utils.data import Dataset, DataLoader\nfrom sklearn.metrics import confusion_matrix, classification_report\nfrom tqdm import tqdm\nimport seaborn as sns","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-08T14:48:37.325992Z","iopub.execute_input":"2026-04-08T14:48:37.326349Z","iopub.status.idle":"2026-04-08T14:48:52.645298Z","shell.execute_reply.started":"2026-04-08T14:48:37.326316Z","shell.execute_reply":"2026-04-08T14:48:52.644491Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# =========================================================\n# 2. REPRODUCIBILITY\n# =========================================================\ndef set_seed(seed=42):\n    random.seed(seed)\n    np.random.seed(seed)\n    torch.manual_seed(seed)\n    torch.cuda.manual_seed_all(seed)\n\nset_seed(42)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-08T14:49:09.140071Z","iopub.execute_input":"2026-04-08T14:49:09.140884Z","iopub.status.idle":"2026-04-08T14:49:09.156209Z","shell.execute_reply.started":"2026-04-08T14:49:09.140848Z","shell.execute_reply":"2026-04-08T14:49:09.155588Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# =========================================================\n# 3. DEVICE CONFIGURATION\n# =========================================================\ndevice = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\nprint(f\"Using device: {device}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-08T14:49:12.087009Z","iopub.execute_input":"2026-04-08T14:49:12.087728Z","iopub.status.idle":"2026-04-08T14:49:12.350227Z","shell.execute_reply.started":"2026-04-08T14:49:12.087696Z","shell.execute_reply":"2026-04-08T14:49:12.349279Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Data Loading and Preprocessing","metadata":{}},{"cell_type":"code","source":"# =========================================================\n# 4. DATA LOADING AND PREPROCESSING\n# =========================================================\nclass MNISTDataset(Dataset):\n    def __init__(self, images_path, labels_path):\n        with open(labels_path, \"rb\") as lbpath:\n            self.labels = np.frombuffer(lbpath.read(), dtype=np.uint8, offset=8)\n\n        with open(images_path, \"rb\") as imgpath:\n            self.images = np.frombuffer(imgpath.read(), dtype=np.uint8, offset=16)\n            self.images = self.images.reshape(len(self.labels), 28, 28)\n\n    def __len__(self):\n        return len(self.labels)\n\n    def __getitem__(self, idx):\n        image = self.images[idx].astype(np.float32) / 255.0\n        image = torch.tensor(image).unsqueeze(0)  # Shape: [1, 28, 28]\n        label = int(self.labels[idx])\n        return image, label\n\n# Dataset path\nbase_path = \"/kaggle/input/datasets/hojjatk/mnist-dataset\"\n\n# Load training dataset\ntrain_dataset = MNISTDataset(\n    os.path.join(base_path, \"train-images.idx3-ubyte\"),\n    os.path.join(base_path, \"train-labels.idx1-ubyte\")\n)\n\n# Load test dataset\ntest_dataset = MNISTDataset(\n    os.path.join(base_path, \"t10k-images.idx3-ubyte\"),\n    os.path.join(base_path, \"t10k-labels.idx1-ubyte\")\n)\n\n# Create data loaders\ntrain_loader = DataLoader(train_dataset, batch_size=64, shuffle=True)\ntest_loader = DataLoader(test_dataset, batch_size=64, shuffle=False)\n\n# Display dataset information\nprint(f\"Training samples: {len(train_dataset)}\")\nprint(f\"Test samples: {len(test_dataset)}\")\nprint(f\"Training batches: {len(train_loader)}\")\nprint(f\"Test batches: {len(test_loader)}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-08T14:49:26.296978Z","iopub.execute_input":"2026-04-08T14:49:26.297363Z","iopub.status.idle":"2026-04-08T14:49:26.956808Z","shell.execute_reply.started":"2026-04-08T14:49:26.297332Z","shell.execute_reply":"2026-04-08T14:49:26.956066Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Data Exploration\n\nBefore training the model, let's visualize some sample images from the dataset.","metadata":{}},{"cell_type":"code","source":"# =========================================================\n# 5. DATA EXPLORATION\n# =========================================================\nclass_names = [str(i) for i in range(10)]\n\nimages, labels = next(iter(train_loader))\n\nfig, axes = plt.subplots(2, 5, figsize=(12, 5))\naxes = axes.flatten()\n\nfor i in range(10):\n    img = images[i].squeeze().numpy()\n    img = img * 0.5 + 0.5  # unnormalize\n    axes[i].imshow(img, cmap=\"gray\")\n    axes[i].set_title(f\"Label: {labels[i].item()}\")\n    axes[i].axis(\"off\")\n\nplt.tight_layout()\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-08T14:49:38.914828Z","iopub.execute_input":"2026-04-08T14:49:38.915123Z","iopub.status.idle":"2026-04-08T14:49:39.631457Z","shell.execute_reply.started":"2026-04-08T14:49:38.915097Z","shell.execute_reply":"2026-04-08T14:49:39.630589Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Model Architecture\n\nWe implement a Convolutional Neural Network (CNN) to extract spatial features from the images and perform classification.\n\n**Architecture overview:**\n- Convolutional layers for feature extraction\n- Activation functions (ReLU)\n- Pooling layers for dimensionality reduction\n- Fully connected layers for classification\n\nThis architecture is simple yet powerful enough to achieve high accuracy on MNIST.","metadata":{}},{"cell_type":"code","source":"class SimpleCNN(nn.Module):\n    def __init__(self):\n        super(SimpleCNN, self).__init__()\n        \n        self.features = nn.Sequential(\n            nn.Conv2d(in_channels=1, out_channels=16, kernel_size=3, stride=1, padding=1),\n            nn.ReLU(),\n            nn.MaxPool2d(kernel_size=2, stride=2),\n            \n            nn.Conv2d(in_channels=16, out_channels=32, kernel_size=3, stride=1, padding=1),\n            nn.ReLU(),\n            nn.MaxPool2d(kernel_size=2, stride=2)\n        )\n        \n        self.classifier = nn.Sequential(\n            nn.Linear(32 * 7 * 7, 128),\n            nn.ReLU(),\n            nn.Linear(128, 10)\n        )\n\n    def forward(self, x):\n        x = self.features(x)\n        x = x.view(x.size(0), -1)\n        x = self.classifier(x)\n        return x\n\nmodel = SimpleCNN().to(device)\nprint(model)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-08T14:49:44.800552Z","iopub.execute_input":"2026-04-08T14:49:44.801355Z","iopub.status.idle":"2026-04-08T14:49:45.141700Z","shell.execute_reply.started":"2026-04-08T14:49:44.801319Z","shell.execute_reply":"2026-04-08T14:49:45.140979Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Training Setup\n\nWe define:\n- Loss function: CrossEntropyLoss\n- Optimizer: SGD (with momentum)\n- Number of epochs\n- Device (CPU/GPU)\n\nWe also ensure reproducibility by setting random seeds.","metadata":{}},{"cell_type":"code","source":"criterion = nn.CrossEntropyLoss()\noptimizer = optim.SGD(model.parameters(), lr=0.001, momentum=0.9)\nnum_epochs = 10","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-08T14:49:52.589736Z","iopub.execute_input":"2026-04-08T14:49:52.590443Z","iopub.status.idle":"2026-04-08T14:49:52.595124Z","shell.execute_reply.started":"2026-04-08T14:49:52.590406Z","shell.execute_reply":"2026-04-08T14:49:52.594271Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Model Training\n\nThe model is trained over multiple epochs using the training dataset.\n\nDuring training, we track:\n- Training loss\n- Training accuracy","metadata":{}},{"cell_type":"code","source":"# =========================================================\n# 8. TRAINING AND EVALUATION FUNCTIONS\n# =========================================================\ndef train_one_epoch(model, loader, criterion, optimizer, device):\n    model.train()\n    \n    running_loss = 0.0\n    correct = 0\n    total = 0\n    \n    for images, labels in loader:\n        images, labels = images.to(device), labels.to(device)\n        \n        optimizer.zero_grad()\n        outputs = model(images)\n        loss = criterion(outputs, labels)\n        loss.backward()\n        optimizer.step()\n        \n        running_loss += loss.item()\n        \n        _, predicted = torch.max(outputs, 1)\n        correct += (predicted == labels).sum().item()\n        total += labels.size(0)\n    \n    epoch_loss = running_loss / len(loader)\n    epoch_acc = 100 * correct / total\n    \n    return epoch_loss, epoch_acc\n\n\ndef evaluate(model, loader, criterion, device):\n    model.eval()\n    \n    running_loss = 0.0\n    correct = 0\n    total = 0\n    \n    all_preds = []\n    all_labels = []\n    \n    with torch.no_grad():\n        for images, labels in loader:\n            images, labels = images.to(device), labels.to(device)\n            \n            outputs = model(images)\n            loss = criterion(outputs, labels)\n            \n            running_loss += loss.item()\n            \n            _, predicted = torch.max(outputs, 1)\n            correct += (predicted == labels).sum().item()\n            total += labels.size(0)\n            \n            all_preds.extend(predicted.cpu().numpy())\n            all_labels.extend(labels.cpu().numpy())\n    \n    epoch_loss = running_loss / len(loader)\n    epoch_acc = 100 * correct / total\n    \n    return epoch_loss, epoch_acc, np.array(all_preds), np.array(all_labels)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-08T14:49:54.444526Z","iopub.execute_input":"2026-04-08T14:49:54.445189Z","iopub.status.idle":"2026-04-08T14:49:54.453864Z","shell.execute_reply.started":"2026-04-08T14:49:54.445156Z","shell.execute_reply":"2026-04-08T14:49:54.453106Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# =========================================================\n# 9. MODEL TRAINING\n# =========================================================\ntrain_losses = []\ntrain_accuracies = []\ntest_losses = []\ntest_accuracies = []\n\nbest_test_acc = 0.0\nbest_model_state = None\n\nfor epoch in range(num_epochs):\n    train_loss, train_acc = train_one_epoch(model, train_loader, criterion, optimizer, device)\n    test_loss, test_acc, _, _ = evaluate(model, test_loader, criterion, device)\n    \n    train_losses.append(train_loss)\n    train_accuracies.append(train_acc)\n    test_losses.append(test_loss)\n    test_accuracies.append(test_acc)\n    \n    if test_acc > best_test_acc:\n        best_test_acc = test_acc\n        best_model_state = model.state_dict()\n    \n    print(f\"Epoch [{epoch+1}/{num_epochs}]\")\n    print(f\"Train Loss: {train_loss:.4f} | Train Accuracy: {train_acc:.2f}%\")\n    print(f\"Test Loss:  {test_loss:.4f} | Test Accuracy:  {test_acc:.2f}%\")\n    print(\"-\" * 50)\n\nprint(f\"Best Test Accuracy: {best_test_acc:.2f}%\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-08T14:49:58.937016Z","iopub.execute_input":"2026-04-08T14:49:58.937642Z","iopub.status.idle":"2026-04-08T14:50:41.280066Z","shell.execute_reply.started":"2026-04-08T14:49:58.937614Z","shell.execute_reply":"2026-04-08T14:50:41.279277Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# =========================================================\n# 10. LOAD BEST MODEL\n# =========================================================\nmodel.load_state_dict(best_model_state)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-08T14:50:48.177438Z","iopub.execute_input":"2026-04-08T14:50:48.178014Z","iopub.status.idle":"2026-04-08T14:50:48.183529Z","shell.execute_reply.started":"2026-04-08T14:50:48.177984Z","shell.execute_reply":"2026-04-08T14:50:48.182790Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# =========================================================\n# 11. TRAINING CURVES\n# =========================================================\nplt.figure(figsize=(8, 5))\nplt.plot(train_losses, label=\"Train Loss\")\nplt.plot(test_losses, label=\"Test Loss\")\nplt.xlabel(\"Epoch\")\nplt.ylabel(\"Loss\")\nplt.title(\"Training and Test Loss\")\nplt.legend()\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-08T14:50:49.369855Z","iopub.execute_input":"2026-04-08T14:50:49.370641Z","iopub.status.idle":"2026-04-08T14:50:49.747825Z","shell.execute_reply.started":"2026-04-08T14:50:49.370610Z","shell.execute_reply":"2026-04-08T14:50:49.746694Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# =========================================================\n# 12. ACCURACY CURVES\n# =========================================================\nplt.figure(figsize=(8, 5))\nplt.plot(train_accuracies, label=\"Train Accuracy\")\nplt.plot(test_accuracies, label=\"Test Accuracy\")\nplt.xlabel(\"Epoch\")\nplt.ylabel(\"Accuracy (%)\")\nplt.title(\"Training and Test Accuracy\")\nplt.legend()\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-08T14:50:54.370892Z","iopub.execute_input":"2026-04-08T14:50:54.371407Z","iopub.status.idle":"2026-04-08T14:50:54.520923Z","shell.execute_reply.started":"2026-04-08T14:50:54.371377Z","shell.execute_reply":"2026-04-08T14:50:54.520221Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Model Evaluation\n\nAfter training, we evaluate the model on the test dataset to measure its generalization performance.\n\nWe compute:\n- Test accuracy","metadata":{}},{"cell_type":"code","source":"# =========================================================\n# 13. FINAL EVALUATION\n# =========================================================\ntest_loss, test_acc, y_pred, y_true = evaluate(model, test_loader, criterion, device)\n\nprint(f\"Final Test Accuracy: {test_acc:.2f}%\")\nprint(\"\\nClassification Report:\\n\")\nprint(classification_report(y_true, y_pred))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-08T14:50:59.827555Z","iopub.execute_input":"2026-04-08T14:50:59.828226Z","iopub.status.idle":"2026-04-08T14:51:00.227002Z","shell.execute_reply.started":"2026-04-08T14:50:59.828180Z","shell.execute_reply":"2026-04-08T14:51:00.226282Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# =========================================================\n# 14. CONFUSION MATRIX\n# =========================================================\ncm = confusion_matrix(y_true, y_pred)\n\nplt.figure(figsize=(8, 6))\nsns.heatmap(cm, annot=True, fmt=\"d\", cmap=\"Blues\",\n            xticklabels=class_names, yticklabels=class_names)\nplt.xlabel(\"Predicted Label\")\nplt.ylabel(\"True Label\")\nplt.title(\"Confusion Matrix\")\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-08T14:51:03.953369Z","iopub.execute_input":"2026-04-08T14:51:03.953647Z","iopub.status.idle":"2026-04-08T14:51:04.277636Z","shell.execute_reply.started":"2026-04-08T14:51:03.953623Z","shell.execute_reply":"2026-04-08T14:51:04.276975Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# =========================================================\n# 15. SAMPLE PREDICTIONS\n# =========================================================\nmodel.eval()\n\nimages, labels = next(iter(test_loader))\nimages, labels = images.to(device), labels.to(device)\n\nwith torch.no_grad():\n    outputs = model(images)\n    _, predicted = torch.max(outputs, 1)\n\nimages = images.cpu()\nlabels = labels.cpu()\npredicted = predicted.cpu()\n\nfig, axes = plt.subplots(2, 5, figsize=(12, 5))\naxes = axes.flatten()\n\nfor i in range(10):\n    img = images[i].squeeze().numpy()\n    img = img * 0.5 + 0.5  # unnormalize\n    \n    axes[i].imshow(img, cmap=\"gray\")\n    axes[i].set_title(f\"True: {labels[i].item()} | Pred: {predicted[i].item()}\")\n    axes[i].axis(\"off\")\n\nplt.tight_layout()\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-08T14:51:12.540396Z","iopub.execute_input":"2026-04-08T14:51:12.540727Z","iopub.status.idle":"2026-04-08T14:51:13.036625Z","shell.execute_reply.started":"2026-04-08T14:51:12.540697Z","shell.execute_reply":"2026-04-08T14:51:13.035936Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# =========================================================\n# 16. SHOW MISCLASSIFIED EXAMPLES\n# =========================================================\nmisclassified_images = []\nmisclassified_labels = []\nmisclassified_preds = []\n\nmodel.eval()\nwith torch.no_grad():\n    for images, labels in test_loader:\n        images, labels = images.to(device), labels.to(device)\n        outputs = model(images)\n        _, preds = torch.max(outputs, 1)\n        \n        mask = preds != labels\n        if mask.any():\n            misclassified_images.extend(images[mask].cpu())\n            misclassified_labels.extend(labels[mask].cpu())\n            misclassified_preds.extend(preds[mask].cpu())\n\n        if len(misclassified_images) >= 10:\n            break\n\nfig, axes = plt.subplots(2, 5, figsize=(12, 5))\naxes = axes.flatten()\n\nfor i in range(10):\n    img = misclassified_images[i].squeeze().numpy()\n    img = img * 0.5 + 0.5\n    \n    axes[i].imshow(img, cmap=\"gray\")\n    axes[i].set_title(f\"True: {misclassified_labels[i].item()} | Pred: {misclassified_preds[i].item()}\")\n    axes[i].axis(\"off\")\n\nplt.tight_layout()\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-08T14:51:36.970539Z","iopub.execute_input":"2026-04-08T14:51:36.971252Z","iopub.status.idle":"2026-04-08T14:51:37.642633Z","shell.execute_reply.started":"2026-04-08T14:51:36.971216Z","shell.execute_reply":"2026-04-08T14:51:37.641740Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# =========================================================\n# 17. SAVE MODEL\n# =========================================================\ntorch.save(model.state_dict(), \"mnist_cnn_model.pth\")\nprint(\"Model saved as mnist_cnn_model.pth\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-08T14:52:02.530025Z","iopub.execute_input":"2026-04-08T14:52:02.531073Z","iopub.status.idle":"2026-04-08T14:52:02.548784Z","shell.execute_reply.started":"2026-04-08T14:52:02.531034Z","shell.execute_reply":"2026-04-08T14:52:02.548056Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# =========================================================\n# 18. LOAD MODEL\n# =========================================================\nloaded_model = SimpleCNN().to(device)\nloaded_model.load_state_dict(torch.load(\"mnist_cnn_model.pth\", map_location=device))\nloaded_model.eval()\n\nprint(\"Model loaded successfully.\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-08T14:52:04.170724Z","iopub.execute_input":"2026-04-08T14:52:04.171057Z","iopub.status.idle":"2026-04-08T14:52:04.185760Z","shell.execute_reply.started":"2026-04-08T14:52:04.171028Z","shell.execute_reply":"2026-04-08T14:52:04.185089Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# =========================================================\n# 19. SINGLE IMAGE INFERENCE FROM TEST SET\n# =========================================================\ndef predict_single_image(model, image_tensor, device):\n    model.eval()\n    with torch.no_grad():\n        image_tensor = image_tensor.unsqueeze(0).to(device)\n        output = model(image_tensor)\n        probabilities = torch.softmax(output, dim=1)\n        predicted_class = torch.argmax(probabilities, dim=1).item()\n    return predicted_class, probabilities.cpu().numpy()\n\nsample_image, sample_label = test_dataset[0]\npred_class, probs = predict_single_image(loaded_model, sample_image, device)\n\nimg = sample_image.squeeze().numpy()\nimg = img * 0.5 + 0.5\n\nplt.figure(figsize=(4, 4))\nplt.imshow(img, cmap=\"gray\")\nplt.title(f\"True Label: {sample_label} | Predicted: {pred_class}\")\nplt.axis(\"off\")\nplt.show()\n\nprint(\"Predicted probabilities:\")\nprint(probs)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-08T14:52:07.865981Z","iopub.execute_input":"2026-04-08T14:52:07.866338Z","iopub.status.idle":"2026-04-08T14:52:08.050599Z","shell.execute_reply.started":"2026-04-08T14:52:07.866309Z","shell.execute_reply":"2026-04-08T14:52:08.049790Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Results and Analysis\n\nWe analyze the model's performance and discuss:\n- Accuracy achieved\n- Strengths of the model\n- Potential improvements","metadata":{}},{"cell_type":"markdown","source":"## Visualization\n\nTo better understand model performance, we can visualize:\n- Sample predictions\n- Correct vs incorrect classifications\n- Confusion matrix (optional)","metadata":{}},{"cell_type":"markdown","source":"## Future Improvements\n\nPossible ways to improve this model:\n- Use more advanced architectures\n- Apply data augmentation\n- Tune hyperparameters\n- Try different optimizers (Adam, RMSprop)\n- Add regularization techniques (Dropout, BatchNorm)","metadata":{}},{"cell_type":"markdown","source":"## Conclusion\n\nIn this notebook, we successfully built and trained a CNN to classify handwritten digits using the MNIST dataset.\n\nThis project demonstrates the fundamental workflow of a deep learning pipeline:\n- Data preprocessing\n- Model design\n- Training\n- Evaluation\n\nIt serves as a strong baseline for more advanced computer vision tasks.","metadata":{}},{"cell_type":"markdown","source":"## References\n\n- MNIST Dataset: http://yann.lecun.com/exdb/mnist/\n- PyTorch Documentation: https://pytorch.org/docs/","metadata":{}},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}