{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.11.13","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"none","dataSources":[{"sourceId":3004,"databundleVersionId":861823,"sourceType":"competition"},{"sourceId":13686743,"sourceType":"datasetVersion","datasetId":8704720}],"dockerImageVersionId":31192,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"# Build, Train, and Evaluate a CNN on MNIST\n\nThese are the contents of this notebook:\n\n1. Load a dataset you uploaded to Kaggle.\n2. Build a Convolutional Neural Network (CNN) using Keras and TensorFlow.\n3. Train and evaluate the model on MNIST handwritten digits.\n4. Visualize predictions and understand model performance.","metadata":{}},{"cell_type":"markdown","source":"# Environment setup note\n\nKaggle images occasionally ship a protobuf version that conflicts with TensorFlow and triggers:\n- `AttributeError: 'MessageFactory' object has no attribute 'GetPrototype'`\n\nWe’ll pin protobuf to a compatible version and (optionally) force CPU execution if GPU initialization fails.","metadata":{}},{"cell_type":"code","source":"# 1) Pin protobuf to a TF-compatible version to avoid:\n#    AttributeError: 'MessageFactory' object has no attribute 'GetPrototype'\n!pip install -q --no-warn-conflicts \"protobuf<5\"  # commonly 3.20.x or 4.25.x work with TF\n\n# 2) OPTIONAL: force CPU if you don't need GPU (avoids CUDA init errors/noise)\nimport os\nos.environ[\"TF_CPP_MIN_LOG_LEVEL\"] = \"2\"   # reduce TF logs\n# Uncomment the next line to force CPU:\n# os.environ[\"CUDA_VISIBLE_DEVICES\"] = \"-1\"\n\n# 3) IMPORTANT: Restart the Python process so the new protobuf is used.\nimport IPython, sys\nIPython.display.clear_output(wait=True)\nprint(\"Protobuf pinned. Please now do: Kernel -> Restart & Run All (or Runtime -> Restart).\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-20T23:22:18.291421Z","iopub.execute_input":"2025-11-20T23:22:18.292565Z","iopub.status.idle":"2025-11-20T23:22:20.459166Z","shell.execute_reply.started":"2025-11-20T23:22:18.292486Z","shell.execute_reply":"2025-11-20T23:22:20.453943Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\n\n# List files under /kaggle/input to confirm dataset presence\n!ls /kaggle/input/","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-20T23:22:24.390301Z","iopub.execute_input":"2025-11-20T23:22:24.390788Z","iopub.status.idle":"2025-11-20T23:22:24.563740Z","shell.execute_reply.started":"2025-11-20T23:22:24.390746Z","shell.execute_reply":"2025-11-20T23:22:24.562184Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Step 1: Load the MNIST Dataset","metadata":{}},{"cell_type":"markdown","source":"Begin with imports","metadata":{}},{"cell_type":"code","source":"import numpy as np\nimport pandas as pd\nimport matplotlib.pyplot as plt\nimport seaborn as sns\n\nimport tensorflow as tf\nfrom tensorflow.keras import layers, models\nfrom tensorflow.keras.losses import CategoricalCrossentropy\nfrom tensorflow.keras.callbacks import EarlyStopping, ReduceLROnPlateau\nfrom tensorflow.keras.utils import to_categorical\nfrom tensorflow.keras.preprocessing.image import ImageDataGenerator\nfrom sklearn.model_selection import train_test_split","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Load training and test data from Kaggle\ntrain_df = pd.read_csv(\"/kaggle/input/digit-recognizer/train.csv\")\ntest_df = pd.read_csv(\"/kaggle/input/digit-recognizer/test.csv\")\n\n# Separate features and labels\nx_train = train_df.drop(\"label\", axis=1).values\ny_train = train_df[\"label\"].values\nx_test = test_df.values\n\nprint(\"Before reshape:\")\nprint(\"x_train shape:\", x_train.shape)  # (42000, 784)\nprint(\"x_test shape:\", x_test.shape)    # (28000, 784)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-20T23:22:26.302018Z","iopub.execute_input":"2025-11-20T23:22:26.302445Z","iopub.status.idle":"2025-11-20T23:22:31.568632Z","shell.execute_reply.started":"2025-11-20T23:22:26.302410Z","shell.execute_reply":"2025-11-20T23:22:31.567336Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Step 1B: Explore and Visualize the Dataset\n\nBefore building a model, it’s important to understand what the data looks like. So, we follow these steps:\n- Inspect basic shapes and distributions\n- Visualize a few sample images\n- Use `pandas` and `seaborn` to explore label frequencies","metadata":{}},{"cell_type":"code","source":"import pandas as pd\nimport seaborn as sns\nimport matplotlib.pyplot as plt\n\n# Show data dimensions\nprint(\"x_train shape:\", x_train.shape)\nprint(\"y_train shape:\", y_train.shape)\nprint(\"x_test shape:\", x_test.shape)\n\n# Convert labels to pandas DataFrame for easy plotting\ntrain_labels_df = pd.DataFrame({'label': y_train})\n\n# Basic statistics\nprint(\"\\nLabel distribution in training set:\")\nprint(train_labels_df['label'].value_counts().sort_index())\n\n# Plot label distribution\nplt.figure(figsize=(8,4))\nsns.countplot(x='label', data=train_labels_df)\nplt.title(\"Distribution of digits in training set\")\nplt.xlabel(\"Digit\")\nplt.ylabel(\"Count\")\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-20T23:22:34.643856Z","iopub.execute_input":"2025-11-20T23:22:34.644280Z","iopub.status.idle":"2025-11-20T23:22:34.872019Z","shell.execute_reply.started":"2025-11-20T23:22:34.644246Z","shell.execute_reply":"2025-11-20T23:22:34.870600Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### Visualize Label Distribution\n\nI plotted how many examples exist for each digit (0–9) in both training and test sets to help confirm the dataset is balanced.","metadata":{}},{"cell_type":"code","source":"plt.figure(figsize=(10,4))\nsns.countplot(x='label', data=train_labels_df, palette='viridis')\nplt.title(\"Training Data Label Distribution\")\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-20T23:22:42.174139Z","iopub.execute_input":"2025-11-20T23:22:42.175223Z","iopub.status.idle":"2025-11-20T23:22:42.389047Z","shell.execute_reply.started":"2025-11-20T23:22:42.175172Z","shell.execute_reply":"2025-11-20T23:22:42.387780Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Step 2: Preprocess the Data\n\nBefore feeding data into the CNN, I:\n- Normalize pixel values to the range `[0, 1]`\n- Add a channel dimension since CNNs expect 3D input (height, width, channels)","metadata":{}},{"cell_type":"code","source":"# Reshape and normalize\nx_train = x_train.reshape(-1, 28, 28, 1).astype(\"float32\") / 255.0\nx_test = x_test.reshape(-1, 28, 28, 1).astype(\"float32\") / 255.0\n\nprint(\"\\nAfter reshape and normalization:\")\nprint(\"x_train shape:\", x_train.shape)  # (42000, 28, 28, 1)\nprint(\"x_test shape:\", x_test.shape)    # (28000, 28, 28, 1)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-20T23:22:48.307030Z","iopub.execute_input":"2025-11-20T23:22:48.307426Z","iopub.status.idle":"2025-11-20T23:22:48.711666Z","shell.execute_reply.started":"2025-11-20T23:22:48.307397Z","shell.execute_reply":"2025-11-20T23:22:48.710616Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### Visualize Example Images\n\nI displayed a few samples from the training set to get an intuitive sense of the data. \nEach image is 28×28 pixels, grayscale, representing handwritten digits.","metadata":{}},{"cell_type":"code","source":"# visualize a few images\nplt.figure(figsize=(10, 4))\nfor i in range(10):\n    plt.subplot(2, 5, i + 1)\n    plt.imshow(x_train[i].squeeze(), cmap='gray')\n    plt.title(f\"Label: {y_train[i]}\")\n    plt.axis('off')\nplt.tight_layout()\nplt.show()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-20T23:22:51.998662Z","iopub.execute_input":"2025-11-20T23:22:51.999105Z","iopub.status.idle":"2025-11-20T23:22:52.600855Z","shell.execute_reply.started":"2025-11-20T23:22:51.999078Z","shell.execute_reply":"2025-11-20T23:22:52.599638Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### Average Image Visualization\n\nHere, I visualize the **average** image per digit class to observe common shape patterns across digits.","metadata":{}},{"cell_type":"code","source":"fig, axes = plt.subplots(2, 5, figsize=(10, 5))\nfor digit in range(10):\n    mean_image = x_train[y_train == digit].mean(axis=0)\n    ax = axes[digit // 5, digit % 5]\n    ax.imshow(mean_image, cmap='gray')\n    ax.set_title(f\"Digit {digit}\")\n    ax.axis('off')\nplt.suptitle(\"Average Image for Each Digit\")\nplt.tight_layout()\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-20T23:22:59.825692Z","iopub.execute_input":"2025-11-20T23:22:59.826069Z","iopub.status.idle":"2025-11-20T23:23:01.418319Z","shell.execute_reply.started":"2025-11-20T23:22:59.826044Z","shell.execute_reply":"2025-11-20T23:23:01.417231Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Understanding Image Data and CNN Inputs\n\nEach MNIST digit is a 28×28 grayscale image.  \nIn computer vision tasks, we treat each image as a grid of pixel values.\n\nA Convolutional Neural Network (CNN) processes these images by:\n- Looking for small **patterns** (like edges or corners) in early layers using kernels.\n- Combining these patterns into **higher-level features** (like shapes or digits) in deeper layers.\n\nUnlike traditional neural networks, CNNs **preserve spatial relationships** between pixels — which is essential for recognizing visual structures.","metadata":{}},{"cell_type":"markdown","source":"## Step 3: Build the CNN Model\n\nI created a simple CNN using TensorFlow Keras.  \nThis architecture includes:\n- Four convolutional layers\n- Five batch normalization layers\n- Two max pooling layers\n- Two dense hidden layer\n- An output layer for 10 classes (digits 0–9)","metadata":{}},{"cell_type":"markdown","source":"## How CNNs Work\n\nCNNs are designed to automatically learn useful features from images.  \nThey consist of several types of layers:\n\n1. **Convolutional Layers**  \n   - Learn filters (small 3×3 or 5×5 grids) that slide over the image.  \n   - Each filter detects a feature like an edge or curve.  \n   - Output: A **feature map**, which highlights where that feature appears.\n\n2. **Activation Function (ReLU)**  \n   - Adds nonlinearity to the model.  \n   - Keeps only positive values, allowing the network to learn complex patterns.\n\n3. **Pooling Layers (MaxPooling)**  \n   - Reduce spatial size (downsampling).  \n   - Keeps the most important information and makes computation faster.  \n   - Also provides some **invariance** to small shifts or rotations.\n\n4. **Fully Connected Layers (Dense Layers)**  \n   - Combine all extracted features to make final predictions.  \n   - Each neuron receives input from all features in the previous layer.\n\n5. **Softmax Output**  \n   - Converts final scores into probabilities for each class (0–9 for MNIST).","metadata":{}},{"cell_type":"code","source":"import tensorflow as tf\nfrom tensorflow.keras import layers, models\n\nmodel = models.Sequential([\n    layers.Input(shape=(28, 28, 1)),\n\n    layers.Conv2D(64, (3, 3), activation='relu', padding='same'),\n    layers.BatchNormalization(),\n    layers.Conv2D(64, (3, 3), activation='relu', padding='same'),\n    layers.BatchNormalization(),\n    layers.MaxPooling2D(),\n    layers.Dropout(0.25),\n\n    layers.Conv2D(128, (3, 3), activation='relu', padding='same'),\n    layers.BatchNormalization(),\n    layers.Conv2D(128, (3, 3), activation='relu', padding='same'),\n    layers.BatchNormalization(),\n    layers.MaxPooling2D(),\n    layers.Dropout(0.25),\n\n    layers.Flatten(),\n    layers.Dense(256, activation='relu'),\n    layers.BatchNormalization(),\n    layers.Dropout(0.5),\n    layers.Dense(10, activation='softmax')\n])\n\nmodel.summary()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-20T23:28:08.364169Z","iopub.execute_input":"2025-11-20T23:28:08.365018Z","iopub.status.idle":"2025-11-20T23:28:08.595779Z","shell.execute_reply.started":"2025-11-20T23:28:08.364971Z","shell.execute_reply":"2025-11-20T23:28:08.594622Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Step 4: Compile and Train the Model\n\nThis uses:\n- **Adam optimizer** for adaptive learning.\n- **Sparse Categorical Crossentropy** loss because labels are integers.\n- Accuracy as our performance metric.\n\nWe’ll train for a few epochs and hold out 10% of training data for validation.","metadata":{}},{"cell_type":"markdown","source":"## Loss Function, Optimizer, and Metrics\n\nOnce a CNN architecture is defined, it needs to **learn** from data.  \nThis happens through three main components:\n\n- **Loss Function**: Measures how far predictions are from true labels.  \n    - For classification, we use **Sparse Categorical Crossentropy** because labels are integers (0–9).\n- **Optimizer**: Updates network weights to minimize the loss.  \n  - **Adam** is commonly used for its adaptive learning rate.\n- **Metrics**: Used to monitor performance.\n    - **Accuracy** tells us what fraction of predictions are correct.\n","metadata":{}},{"cell_type":"code","source":"# One-hot encode labels to use with label_smoothing\nfrom tensorflow.keras.utils import to_categorical\n\ny_train_split = to_categorical(y_train_split, num_classes=10)\ny_val = to_categorical(y_val, num_classes=10)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-20T23:28:17.892934Z","iopub.execute_input":"2025-11-20T23:28:17.893325Z","iopub.status.idle":"2025-11-20T23:28:17.957651Z","shell.execute_reply.started":"2025-11-20T23:28:17.893295Z","shell.execute_reply":"2025-11-20T23:28:17.956657Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from tensorflow.keras.losses import CategoricalCrossentropy\n\nmodel.compile(\n    optimizer='adam',\n    loss=CategoricalCrossentropy(label_smoothing=0.1),\n    metrics=['accuracy']\n)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-20T23:28:19.492212Z","iopub.execute_input":"2025-11-20T23:28:19.492646Z","iopub.status.idle":"2025-11-20T23:28:19.507090Z","shell.execute_reply.started":"2025-11-20T23:28:19.492616Z","shell.execute_reply":"2025-11-20T23:28:19.505919Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Step 5: Evaluate the Model\n\nNow we’ll test the CNN on unseen data (the test set) to measure generalization.","metadata":{}},{"cell_type":"code","source":"from sklearn.model_selection import train_test_split\n\nx_train_split, x_val, y_train_split, y_val = train_test_split(\n    x_train, y_train, test_size=0.1, stratify=y_train, random_state=42\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-20T23:28:23.258320Z","iopub.execute_input":"2025-11-20T23:28:23.260162Z","iopub.status.idle":"2025-11-20T23:28:24.045903Z","shell.execute_reply.started":"2025-11-20T23:28:23.260088Z","shell.execute_reply":"2025-11-20T23:28:24.044258Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"I added data augmentation to make my model more robust to small variations (rotation, shift, zoom, etc.)","metadata":{}},{"cell_type":"code","source":"from tensorflow.keras.preprocessing.image import ImageDataGenerator\n\n# Create augmented data generator\ndatagen = ImageDataGenerator(\n    rotation_range=10,\n    width_shift_range=0.1,\n    height_shift_range=0.1,\n    zoom_range=0.1\n)\n\ndatagen.fit(x_train_split)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-20T23:28:26.272630Z","iopub.execute_input":"2025-11-20T23:28:26.273022Z","iopub.status.idle":"2025-11-20T23:28:26.356912Z","shell.execute_reply.started":"2025-11-20T23:28:26.272999Z","shell.execute_reply":"2025-11-20T23:28:26.355466Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"Here, I added the EarlyStopping mechanism to ensure it doesn't overfit.","metadata":{}},{"cell_type":"code","source":"from tensorflow.keras.callbacks import EarlyStopping, ReduceLROnPlateau\n\nearly_stop = EarlyStopping(\n    monitor='val_accuracy',\n    patience=5,\n    restore_best_weights=True\n)\n\nreduce_lr = ReduceLROnPlateau(\n    monitor='val_accuracy',\n    factor=0.5,\n    patience=3,\n    min_lr=1e-6\n)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-20T23:28:32.680819Z","iopub.execute_input":"2025-11-20T23:28:32.681229Z","iopub.status.idle":"2025-11-20T23:28:32.688516Z","shell.execute_reply.started":"2025-11-20T23:28:32.681200Z","shell.execute_reply":"2025-11-20T23:28:32.687025Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from tensorflow.keras.utils import to_categorical\n\n# One-hot encode labels (shape: [samples, 10])\ny_train_split = to_categorical(y_train_split, num_classes=10)\ny_val = to_categorical(y_val, num_classes=10)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-20T23:30:46.319693Z","iopub.execute_input":"2025-11-20T23:30:46.321210Z","iopub.status.idle":"2025-11-20T23:30:46.328748Z","shell.execute_reply.started":"2025-11-20T23:30:46.321160Z","shell.execute_reply":"2025-11-20T23:30:46.327736Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"history = model.fit(\n    x_train_split, y_train_split,\n    validation_data=(x_val, y_val),\n    epochs=40,\n    batch_size=128,\n    callbacks=[early_stop, reduce_lr],\n    verbose=1\n)\n\nval_loss, val_acc = model.evaluate(x_val, y_val)\nprint(f\"Validation Accuracy: {val_acc:.4f}\")\nprint(f\"Validation Loss: {val_loss:.4f}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-20T23:30:48.311515Z","iopub.execute_input":"2025-11-20T23:30:48.311948Z","iopub.status.idle":"2025-11-20T23:30:51.148008Z","shell.execute_reply.started":"2025-11-20T23:30:48.311921Z","shell.execute_reply":"2025-11-20T23:30:51.146608Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Interpreting Model Training Results\n\nDuring training, the model minimizes loss on the training set and tracks validation loss/accuracy.\n\n- **Training Loss** should decrease over epochs.\n- **Validation Loss** shows how well the model generalizes to unseen data.\n\nIf validation loss stops improving or starts increasing, the model may be **overfitting**, meaning it is memorizing the training data rather than learning general patterns.\n\nTo mitigate overfitting:\n- Use **Dropout layers**.\n- Add **Data Augmentation** (rotations, translations, zooms).\n- Reduce model complexity or train for fewer epochs.","metadata":{}},{"cell_type":"markdown","source":"## Understanding CNN Strengths and Limitations\n\n**Strengths:**\n- Automatically extracts meaningful visual features without manual engineering.\n- Performs well for image-related tasks such as classification, detection, and segmentation.\n- Learns hierarchical representations — from simple edges to complex shapes.\n\n**Limitations:**\n- Requires large labeled datasets to generalize well.\n- Computationally expensive for large input sizes.\n- Not ideal for non-spatial data (e.g., text sequences, tabular data).","metadata":{}},{"cell_type":"markdown","source":"## Step 6: Visualize Model Predictions\n\nHere, I plot a few test samples and compare predicted vs. true labels.","metadata":{}},{"cell_type":"code","source":"import matplotlib.pyplot as plt\n\n# Make predictions on the test set\npredictions = model.predict(x_test)\npred_labels = np.argmax(predictions, axis=1)\n\n# Show a few test images with predicted labels\nplt.figure(figsize=(12, 3))\nfor i in range(10):\n    plt.subplot(2, 5, i + 1)\n    plt.imshow(x_test[i].squeeze(), cmap='gray')\n    plt.title(f\"Pred: {pred_labels[i]}\")\n    plt.axis('off')\nplt.tight_layout()\nplt.show()\n","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"Here, the data is saved in a csv file, following competition format.","metadata":{}},{"cell_type":"code","source":"from tensorflow.keras.preprocessing.image import ImageDataGenerator\nimport numpy as np\n\n# Define test-time augmentation generator\ntta_gen = ImageDataGenerator(\n    rotation_range=10,\n    zoom_range=0.1,\n    width_shift_range=0.1,\n    height_shift_range=0.1\n)\n\n# Predict with TTA (e.g., 5 times)\ntta_steps = 5\ntta_preds = []\n\nfor _ in range(tta_steps):\n    preds = model.predict(tta_gen.flow(x_test, batch_size=128, shuffle=False))\n    tta_preds.append(preds)\n\n# Average predictions\nfinal_predictions = np.mean(tta_preds, axis=0)\npredicted_labels = np.argmax(final_predictions, axis=1)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-20T22:58:48.891171Z","iopub.execute_input":"2025-11-20T22:58:48.892369Z","iopub.status.idle":"2025-11-20T22:59:02.239641Z","shell.execute_reply.started":"2025-11-20T22:58:48.892318Z","shell.execute_reply":"2025-11-20T22:59:02.238265Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Create submission file\nimport pandas as pd\nsubmission = pd.DataFrame({\n    \"ImageId\": np.arange(1, len(predicted_labels)+1),\n    \"Label\": predicted_labels\n})\nsubmission.to_csv(\"submission.csv\", index=False)\nprint(\"submission.csv created with TTA predictions\")","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}