{"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"}],"dockerImageVersionId":31192,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"# ============================================================\n# Digit Recognizer — CNN in PyTorch vs TensorFlow\n# ============================================================\n\n# -------------------------\n# Imports & Config\n# -------------------------\nimport os\nimport random\nimport numpy as np\nimport pandas as pd\n\n# PyTorch\nimport torch\nfrom torch import nn\nfrom torch.utils.data import Dataset, DataLoader\n\n# TensorFlow / Keras\nimport tensorflow as tf\nfrom tensorflow import keras\nfrom tensorflow.keras import layers\n\nfrom sklearn.model_selection import train_test_split\n\n# -------------------------\n# Reproducibility\n# -------------------------\nSEED = 42\nrandom.seed(SEED)\nnp.random.seed(SEED)\ntorch.manual_seed(SEED)\ntf.random.set_seed(SEED)\n\n# -------------------------\n# Device Config (PyTorch)\n# -------------------------\ndevice = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\nprint(\"Using PyTorch device:\", device)\n\n# ============================================================\n# 1. Load Data\n# ============================================================\n\ntrain_df = pd.read_csv(\"/kaggle/input/digit-recognizer/train.csv\")\ntest_df  = pd.read_csv(\"/kaggle/input/digit-recognizer/test.csv\")\n\nprint(\"Train shape:\", train_df.shape)\nprint(\"Test shape:\", test_df.shape)\n\n# Split features and labels\nX = train_df.drop(\"label\", axis=1).values  # (n_samples, 784)\ny = train_df[\"label\"].values              # (n_samples,)\n\n# Normalize to [0,1]\nX = X.astype(\"float32\") / 255.0\ntest_data = test_df.values.astype(\"float32\") / 255.0\n\n# Train/Validation split\nX_train, X_val, y_train, y_val = train_test_split(\n    X, y, test_size=0.1, random_state=SEED, stratify=y\n)\n\nprint(\"Train:\", X_train.shape, \"Val:\", X_val.shape)\n\n# Reshape for CNNs: (N, 28, 28, 1)\nX_train_img = X_train.reshape(-1, 28, 28, 1)\nX_val_img   = X_val.reshape(-1, 28, 28, 1)\ntest_img    = test_data.reshape(-1, 28, 28, 1)\n\n# ============================================================\n# 2. PyTorch Dataset & Dataloader\n# ============================================================\n\nclass MNISTTorchDataset(Dataset):\n    def __init__(self, images, labels=None):\n        \"\"\"\n        images: numpy array (N, 28, 28, 1), [0,1] float32\n        labels: optional, numpy array (N,)\n        \"\"\"\n        self.images = images\n        self.labels = labels\n\n    def __len__(self):\n        return len(self.images)\n\n    def __getitem__(self, idx):\n        # Convert to (1, 28, 28) for Conv2d\n        img = self.images[idx].transpose(2, 0, 1)  # (1,28,28)\n        img_tensor = torch.tensor(img, dtype=torch.float32)\n\n        if self.labels is not None:\n            label_tensor = torch.tensor(self.labels[idx], dtype=torch.long)\n            return img_tensor, label_tensor\n        else:\n            return img_tensor\n\nBATCH_SIZE = 128\n\ntrain_dataset_torch = MNISTTorchDataset(X_train_img, y_train)\nval_dataset_torch   = MNISTTorchDataset(X_val_img, y_val)\ntest_dataset_torch  = MNISTTorchDataset(test_img, labels=None)\n\ntrain_loader = DataLoader(train_dataset_torch, batch_size=BATCH_SIZE, shuffle=True)\nval_loader   = DataLoader(val_dataset_torch, batch_size=BATCH_SIZE, shuffle=False)\ntest_loader  = DataLoader(test_dataset_torch, batch_size=BATCH_SIZE, shuffle=False)\n\n# ============================================================\n# 3. PyTorch CNN Model\n# ============================================================\n\n# As given (input: 1x28x28)\n# Conv2d(1,32,3) -> (32, 26, 26)\n# MaxPool2d(2)   -> (32, 13, 13) => 32*13*13 = 5408\n\nclass TorchCNN(nn.Module):\n    def __init__(self):\n        super().__init__()\n        self.model = nn.Sequential(\n            nn.Conv2d(1, 32, 3),\n            nn.ReLU(),\n            nn.MaxPool2d(2),\n            nn.Flatten(),\n            nn.Linear(5408, 128),\n            nn.ReLU(),\n            nn.Linear(128, 10)\n        )\n\n    def forward(self, x):\n        return self.model(x)\n\ntorch_model = TorchCNN().to(device)\ncriterion = nn.CrossEntropyLoss()\noptimizer = torch.optim.Adam(torch_model.parameters(), lr=1e-3)\n\n# -------------------------\n# Training & Evaluation Loops\n# -------------------------\ndef train_one_epoch(model, loader, optimizer, criterion, device):\n    model.train()\n    running_loss = 0.0\n    correct = 0\n    total = 0\n\n    for images, labels in loader:\n        images = images.to(device)\n        labels = labels.to(device)\n\n        optimizer.zero_grad()\n        outputs = model(images)            # (batch, 10)\n        loss = criterion(outputs, labels)\n        loss.backward()\n        optimizer.step()\n\n        running_loss += loss.item() * labels.size(0)\n\n        preds = outputs.argmax(dim=1)\n        correct += (preds == labels).sum().item()\n        total += labels.size(0)\n\n    epoch_loss = running_loss / total\n    epoch_acc  = correct / total\n    return epoch_loss, epoch_acc\n\n\ndef evaluate(model, loader, criterion, device):\n    model.eval()\n    running_loss = 0.0\n    correct = 0\n    total = 0\n\n    with torch.no_grad():\n        for images, labels in loader:\n            images = images.to(device)\n            labels = labels.to(device)\n\n            outputs = model(images)\n            loss = criterion(outputs, labels)\n\n            running_loss += loss.item() * labels.size(0)\n            preds = outputs.argmax(dim=1)\n            correct += (preds == labels).sum().item()\n            total += labels.size(0)\n\n    epoch_loss = running_loss / total\n    epoch_acc  = correct / total\n    return epoch_loss, epoch_acc\n\n# -------------------------\n# Train PyTorch Model\n# -------------------------\nEPOCHS_TORCH = 10\n\nbest_val_acc_torch = 0.0\nfor epoch in range(1, EPOCHS_TORCH + 1):\n    train_loss, train_acc = train_one_epoch(torch_model, train_loader, optimizer, criterion, device)\n    val_loss, val_acc = evaluate(torch_model, val_loader, criterion, device)\n\n    if val_acc > best_val_acc_torch:\n        best_val_acc_torch = val_acc\n        torch.save(torch_model.state_dict(), \"best_torch_cnn.pth\")\n\n    print(f\"[PyTorch] Epoch {epoch}/{EPOCHS_TORCH} \"\n          f\"- Train loss: {train_loss:.4f}, acc: {train_acc:.4f} \"\n          f\"- Val loss: {val_loss:.4f}, acc: {val_acc:.4f}\")\n\nprint(\"Best PyTorch Val Accuracy:\", best_val_acc_torch)\n\n# Load best weights\ntorch_model.load_state_dict(torch.load(\"best_torch_cnn.pth\"))\n\n# ============================================================\n# 4. TensorFlow / Keras CNN Model\n# ============================================================\n\n# Build tf.data datasets from numpy arrays\nBATCH_SIZE_TF = 128\n\ntrain_ds_tf = tf.data.Dataset.from_tensor_slices((X_train_img, y_train))\ntrain_ds_tf = train_ds_tf.shuffle(buffer_size=1024, seed=SEED).batch(BATCH_SIZE_TF)\n\nval_ds_tf = tf.data.Dataset.from_tensor_slices((X_val_img, y_val))\nval_ds_tf = val_ds_tf.batch(BATCH_SIZE_TF)\n\ntest_ds_tf = tf.data.Dataset.from_tensor_slices(test_img)\ntest_ds_tf = test_ds_tf.batch(BATCH_SIZE_TF)\n\n# -------------------------\n# Define TF Model (similar architecture)\n# -------------------------\ndef build_tf_model():\n    model = keras.Sequential(\n        [\n            layers.Conv2D(32, (3, 3), activation=\"relu\", input_shape=(28, 28, 1)),\n            layers.MaxPooling2D(pool_size=(2, 2)),\n            layers.Flatten(),\n            layers.Dense(128, activation=\"relu\"),\n            layers.Dense(10, activation=\"softmax\"),\n        ]\n    )\n    model.compile(\n        optimizer=keras.optimizers.Adam(learning_rate=1e-3),\n        loss=\"sparse_categorical_crossentropy\",\n        metrics=[\"accuracy\"],\n    )\n    return model\n\ntf_model = build_tf_model()\ntf_model.summary()\n\n# -------------------------\n# Train TF Model\n# -------------------------\nEPOCHS_TF = 10\n\nhistory = tf_model.fit(\n    train_ds_tf,\n    validation_data=val_ds_tf,\n    epochs=EPOCHS_TF,\n    verbose=2\n)\n\n# Final validation accuracy from TF\ntf_val_acc = history.history[\"val_accuracy\"][-1]\nprint(\"TensorFlow Val Accuracy:\", tf_val_acc)\n\n# ============================================================\n# 5. Compare Metrics: PyTorch vs TensorFlow\n# ============================================================\nprint(\"===================================\")\nprint(\"Validation Accuracy Comparison\")\nprint(\"PyTorch CNN   :\", best_val_acc_torch)\nprint(\"TensorFlow CNN:\", tf_val_acc)\nprint(\"Better model  :\", \"PyTorch\" if best_val_acc_torch >= tf_val_acc else \"TensorFlow\")\nprint(\"===================================\")\n\n# ============================================================\n# 6. Create Submission\n# ============================================================\n\n\ntorch_model.eval()\nall_preds_torch = []\n\nwith torch.no_grad():\n    for images in test_loader:\n        images = images.to(device)\n        outputs = torch_model(images)\n        preds = outputs.argmax(dim=1).cpu().numpy()\n        all_preds_torch.extend(preds)\n\nsubmission_torch = pd.DataFrame({\n    \"ImageId\": np.arange(1, len(all_preds_torch) + 1),\n    \"Label\": all_preds_torch\n})\n\nsubmission_torch.to_csv(\"submission_torch.csv\", index=False)\nprint(\"Saved PyTorch submission as submission_torch.csv\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-02T05:56:50.814287Z","iopub.execute_input":"2025-12-02T05:56:50.814564Z","iopub.status.idle":"2025-12-02T06:00:54.687217Z","shell.execute_reply.started":"2025-12-02T05:56:50.814543Z","shell.execute_reply":"2025-12-02T06:00:54.686124Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 4. Results — PyTorch vs TensorFlow CNN on MNIST\n\n### 4.1 Training Logs (Summary)\n\n**PyTorch CNN**\n\n- Device: `cpu`\n- Train shape: `(42000, 785)` → `(37800, 784)` train, `(4200, 784)` validation\n- Architecture:\n  - `Conv2d(1 → 32, kernel_size=3)` → `ReLU` → `MaxPool2d(2)`\n  - `Flatten` → `Linear(5408 → 128)` → `ReLU`\n  - `Linear(128 → 10)`\n\n**Training progress (PyTorch)**  \n- Epoch 1: **Train acc = 0.8908**, Val acc = 0.9552  \n- Epoch 2: **Train acc = 0.9667**, Val acc = 0.9733  \n- Epoch 3: **Train acc = 0.9787**, Val acc = 0.9798  \n- Epoch 6: Train acc = 0.9906, **Val acc = 0.9838**  \n- Epoch 10: Train acc = 0.9961, Val acc = 0.9831  \n\n**Best PyTorch validation accuracy**:  \n> **0.9840 (98.40%)**\n\n---\n\n**TensorFlow / Keras CNN**\n\n- Same idea: `Conv2D(32, 3×3)` → `MaxPooling2D(2×2)` → `Flatten` → `Dense(128)` → `Dense(10)`\n- Model parameters: **693,962 trainable parameters**\n- Trained for 10 epochs with `Adam(lr=1e-3)` and `sparse_categorical_crossentropy`.\n\n**Training progress (TensorFlow)**  \n- Epoch 1: **Train acc = 0.9196**, Val acc = 0.9662  \n- Epoch 2: **Train acc = 0.9747**, Val acc = 0.9795  \n- Epoch 3: Train acc = 0.9832, Val acc = 0.9807  \n- Epoch 6: Train acc = 0.9928, **Val acc = 0.9829**  \n- Epoch 10: Train acc = 0.9983, Val acc = 0.9821  \n\n**Final TensorFlow validation accuracy**:  \n> **0.9821 (98.21%)**\n\n---\n\n### 4.2 Side-by-Side Comparison\n\n| Framework   | Best Val Accuracy |\n|------------|-------------------|\n| **PyTorch**   | **0.9840 (98.40%)** |\n| **TensorFlow** | **0.9821 (98.21%)** |\n\nBoth models use almost the **same CNN architecture**, same input processing (28×28 grayscale normalized to `[0, 1]`), and are trained on the same train/validation split.\n\n- The **PyTorch model** slightly edges out TensorFlow on this split by about **0.2%** in validation accuracy.\n- The difference is **very small** and can easily be due to:\n  - Random weight initialization\n  - Mini-batch ordering\n  - Small variations in optimization dynamics\n\nSo the main takeaway is:\n\n> **Both PyTorch and TensorFlow can reach ~98–99% accuracy on MNIST with a very simple CNN.**  \n> The choice of framework is more about developer experience, ecosystem, and personal preference than raw performance for this task.\n\n---\n\n### 4.3 Submission\n\nFor this notebook, I used the **PyTorch model** (best validation accuracy: **98.40%**) to generate predictions on the test set and saved them as:\n\n```text\nsubmission_torch.csv","metadata":{}},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}