{"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}],"dockerImageVersionId":31286,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"#  Single Layer Perceptron #  ","metadata":{}},{"cell_type":"markdown","source":"","metadata":{}},{"cell_type":"code","source":"# =========================\n# Perceptron Notebook (Digit Recognizer)\n# =========================\n\nimport os\nimport time\nimport numpy as np\nimport pandas as pd\nimport matplotlib.pyplot as plt\n\nimport torch\nimport torch.nn as nn\nimport torch.optim as optim\nfrom torch.utils.data import Dataset, DataLoader, random_split\n\nfrom sklearn.metrics import confusion_matrix, ConfusionMatrixDisplay, f1_score\n\n# -----------------------------\n# Reproducibility\n# -----------------------------\nSEED = 42\ntorch.manual_seed(SEED)\nnp.random.seed(SEED)\n\ndevice = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\nprint(\"Device:\", device)\n\n# -----------------------------\n# Find dataset automatically\n# -----------------------------\ndef find_digit_recognizer_paths():\n    base = \"/kaggle/input\"\n    for root, _, files in os.walk(base):\n        if \"train.csv\" in files and \"test.csv\" in files:\n            return os.path.join(root, \"train.csv\"), os.path.join(root, \"test.csv\")\n    raise FileNotFoundError(\"Digit Recognizer dataset not found\")\n\nTRAIN_PATH, TEST_PATH = find_digit_recognizer_paths()\n\nprint(\"TRAIN_PATH:\", TRAIN_PATH)\nprint(\"TEST_PATH :\", TEST_PATH)\n\n# -----------------------------\n# Load dataset\n# -----------------------------\ntrain_df = pd.read_csv(TRAIN_PATH)\ntest_df = pd.read_csv(TEST_PATH)\n\ny_all = train_df[\"label\"].values.astype(np.int64)\nX_all = train_df.drop(columns=[\"label\"]).values.astype(np.float32) / 255.0\nX_test = test_df.values.astype(np.float32) / 255.0\n\nprint(\"Train shape:\", train_df.shape)\nprint(\"Test shape :\", test_df.shape)\n\n# -----------------------------\n# Dataset class\n# -----------------------------\nclass MNISTDataset(Dataset):\n    def __init__(self, X, y=None):\n        self.X = X\n        self.y = y\n\n    def __len__(self):\n        return len(self.X)\n\n    def __getitem__(self, idx):\n        x = torch.tensor(self.X[idx], dtype=torch.float32)\n        if self.y is None:\n            return x\n        y = torch.tensor(self.y[idx], dtype=torch.long)\n        return x, y\n\n# -----------------------------\n# Train / Validation split\n# -----------------------------\nfull_ds = MNISTDataset(X_all, y_all)\n\nval_size = int(0.1 * len(full_ds))\ntrain_size = len(full_ds) - val_size\n\ngenerator = torch.Generator().manual_seed(SEED)\ntrain_ds, val_ds = random_split(full_ds, [train_size, val_size], generator=generator)\n\nBATCH_SIZE = 128\n\ntrain_loader = DataLoader(train_ds, batch_size=BATCH_SIZE, shuffle=True)\nval_loader = DataLoader(val_ds, batch_size=BATCH_SIZE, shuffle=False)\n\n# -----------------------------\n# Perceptron model\n# -----------------------------\nclass Perceptron(nn.Module):\n    def __init__(self):\n        super().__init__()\n        self.fc = nn.Linear(784, 10)\n\n    def forward(self, x):\n        return self.fc(x)\n\nmodel = Perceptron().to(device)\nprint(model)\n\n# -----------------------------\n# Perceptron Architecture Diagram\n# -----------------------------\nfrom matplotlib.patches import FancyBboxPatch\n\nfig, ax = plt.subplots(figsize=(12, 3))\n\ndef draw_box(text, x, color):\n    box = FancyBboxPatch(\n        (x, 0.4), 0.22, 0.25,\n        boxstyle=\"round,pad=0.02\",\n        linewidth=1.5,\n        edgecolor=\"black\",\n        facecolor=color\n    )\n    ax.add_patch(box)\n    ax.text(\n        x + 0.11, 0.525, text,\n        ha=\"center\", va=\"center\",\n        fontsize=11, weight=\"bold\"\n    )\n\ndraw_box(\"Input\\n(784 pixels)\", 0.05, \"#D6EAF8\")\ndraw_box(\"Linear Layer\\n(784 → 10)\", 0.39, \"#D5F5E3\")\ndraw_box(\"Output\\n(10 classes)\", 0.73, \"#FADBD8\")\n\nax.annotate(\"\", xy=(0.39, 0.525), xytext=(0.27, 0.525),\n            arrowprops=dict(arrowstyle=\"->\", linewidth=2))\nax.annotate(\"\", xy=(0.73, 0.525), xytext=(0.61, 0.525),\n            arrowprops=dict(arrowstyle=\"->\", linewidth=2))\n\nax.set_xlim(0, 1)\nax.set_ylim(0, 1)\nax.set_title(\"Perceptron Architecture (MNIST Digit Classification)\", fontsize=14)\nax.axis(\"off\")\nplt.show()\n\n# -----------------------------\n# Parameter count\n# -----------------------------\ndef count_params(model):\n    return sum(p.numel() for p in model.parameters() if p.requires_grad)\n\nprint(\"Trainable parameters:\", count_params(model))\n\nprint(\"\"\"\nPerceptron Architecture\n\nInput Layer: 784 features (28x28 image flattened)\n↓\nLinear Layer (784 → 10)\n↓\nSoftmax via CrossEntropyLoss\n↓\nOutput: Digit class (0-9)\n\"\"\")\n\n# -----------------------------\n# Training Setup\n# -----------------------------\ncriterion = nn.CrossEntropyLoss()\noptimizer = optim.Adam(model.parameters(), lr=1e-3)\n\nEPOCHS = 8\n\n# -----------------------------\n# Evaluation function\n# -----------------------------\ndef eval_acc_f1(model, loader):\n    model.eval()\n\n    all_preds = []\n    all_true = []\n\n    with torch.no_grad():\n        for x, y in loader:\n            x, y = x.to(device), y.to(device)\n\n            logits = model(x)\n            preds = torch.argmax(logits, dim=1)\n\n            all_preds.append(preds.cpu().numpy())\n            all_true.append(y.cpu().numpy())\n\n    all_preds = np.concatenate(all_preds)\n    all_true = np.concatenate(all_true)\n\n    acc = (all_preds == all_true).mean()\n    f1 = f1_score(all_true, all_preds, average=\"macro\")\n\n    return acc, f1, all_true, all_preds\n\n# -----------------------------\n# Training Loop\n# -----------------------------\ntrain_losses = []\nval_losses = []\n\ntrain_accs = []\nval_accs = []\n\nstart = time.time()\n\nfor epoch in range(1, EPOCHS + 1):\n    model.train()\n\n    running_loss = 0.0\n    correct = 0\n    total = 0\n\n    for x, y in train_loader:\n        x, y = x.to(device), y.to(device)\n\n        optimizer.zero_grad()\n\n        logits = model(x)\n        loss = criterion(logits, y)\n\n        loss.backward()\n        optimizer.step()\n\n        running_loss += loss.item()\n\n        preds = torch.argmax(logits, dim=1)\n        correct += (preds == y).sum().item()\n        total += y.size(0)\n\n    train_loss = running_loss / len(train_loader)\n    train_acc = correct / total\n\n    # Validation\n    model.eval()\n\n    val_loss = 0.0\n    vcorrect = 0\n    vtotal = 0\n\n    with torch.no_grad():\n        for x, y in val_loader:\n            x, y = x.to(device), y.to(device)\n\n            logits = model(x)\n            loss = criterion(logits, y)\n\n            val_loss += loss.item()\n\n            preds = torch.argmax(logits, dim=1)\n            vcorrect += (preds == y).sum().item()\n            vtotal += y.size(0)\n\n    val_loss /= len(val_loader)\n    val_acc = vcorrect / vtotal\n\n    train_losses.append(train_loss)\n    val_losses.append(val_loss)\n    train_accs.append(train_acc)\n    val_accs.append(val_acc)\n\n    print(\n        f\"Epoch {epoch}/{EPOCHS} | \"\n        f\"Train Loss {train_loss:.4f} Acc {train_acc:.4f} | \"\n        f\"Val Loss {val_loss:.4f} Acc {val_acc:.4f}\"\n    )\n\ntrain_time = time.time() - start\n\n# -----------------------------\n# Final Evaluation\n# -----------------------------\nfinal_acc, final_f1, y_true, y_pred = eval_acc_f1(model, val_loader)\n\nprint(\"\\nFinal Validation Accuracy:\", final_acc)\nprint(\"Final Validation F1 Score:\", final_f1)\nprint(\"Training Time:\", train_time)\n\n# -----------------------------\n# Training Curves\n# -----------------------------\nepochs = range(1, EPOCHS + 1)\n\nplt.figure(figsize=(12, 5))\n\nplt.subplot(1, 2, 1)\nplt.plot(epochs, train_losses, label=\"Train Loss\")\nplt.plot(epochs, val_losses, label=\"Val Loss\")\nplt.legend()\nplt.title(\"Loss Curves\")\nplt.xlabel(\"Epoch\")\nplt.ylabel(\"Loss\")\n\nplt.subplot(1, 2, 2)\nplt.plot(epochs, train_accs, label=\"Train Accuracy\")\nplt.plot(epochs, val_accs, label=\"Val Accuracy\")\nplt.legend()\nplt.title(\"Accuracy Curves\")\nplt.xlabel(\"Epoch\")\nplt.ylabel(\"Accuracy\")\n\nplt.tight_layout()\nplt.show()\n\n# -----------------------------\n# Confusion Matrix\n# -----------------------------\ncm = confusion_matrix(y_true, y_pred)\ndisp = ConfusionMatrixDisplay(cm)\ndisp.plot(values_format=\"d\")\nplt.title(\"Perceptron Confusion Matrix\")\nplt.show()\n\n# -----------------------------\n# Visualize learned weights\n# -----------------------------\nW = model.fc.weight.detach().cpu().numpy().reshape(10, 28, 28)\n\nplt.figure(figsize=(10, 4))\n\nfor i in range(10):\n    plt.subplot(2, 5, i + 1)\n    plt.imshow(W[i], cmap=\"gray\")\n    plt.title(str(i))\n    plt.axis(\"off\")\n\nplt.suptitle(\"Perceptron Learned Templates\")\nplt.tight_layout()\nplt.show()\n\n# -----------------------------\n# Train on full dataset\n# -----------------------------\nfull_loader = DataLoader(MNISTDataset(X_all, y_all), batch_size=BATCH_SIZE, shuffle=True)\n\n# Reinitialize model for full training\nmodel = Perceptron().to(device)\noptimizer = optim.Adam(model.parameters(), lr=1e-3)\n\nfor epoch in range(1, EPOCHS + 1):\n    model.train()\n    running = 0.0\n\n    for x, y in full_loader:\n        x, y = x.to(device), y.to(device)\n\n        optimizer.zero_grad()\n\n        logits = model(x)\n        loss = criterion(logits, y)\n\n        loss.backward()\n        optimizer.step()\n\n        running += loss.item()\n\n    print(f\"[Full Train] Epoch {epoch}/{EPOCHS} Loss {running / len(full_loader):.4f}\")\n\n# -----------------------------\n# Kaggle Submission\n# -----------------------------\ntest_loader = DataLoader(MNISTDataset(X_test), batch_size=BATCH_SIZE, shuffle=False)\n\nmodel.eval()\npreds_all = []\n\nwith torch.no_grad():\n    for x in test_loader:\n        x = x.to(device)\n        preds = torch.argmax(model(x), dim=1)\n        preds_all.append(preds.cpu().numpy())\n\npreds_all = np.concatenate(preds_all)\n\nsubmission = pd.DataFrame({\n    \"ImageId\": np.arange(1, len(preds_all) + 1),\n    \"Label\": preds_all\n})\n\n# SAVE TO WORKING DIRECTORY\nsubmission.to_csv(\"/kaggle/working/submission_perceptron.csv\", index=False)\n\nprint(\"Saved file to /kaggle/working/submission_perceptron.csv\")\n\nprint(\"Files in working directory:\")\nprint(os.listdir('/kaggle/working'))\n\n\n# -----------------------------\n# Save results for comparison\n# -----------------------------\nperceptron_results = {\n    \"Model\": \"Perceptron\",\n    \"Val Accuracy\": final_acc,\n    \"Val F1\": final_f1,\n    \"Train Time (s)\": train_time,\n    \"Params\": count_params(model),\n    \"Kaggle Score\": 0.0\n}\n\nprint(\"\\nPerceptron Results Summary\")\nprint(perceptron_results)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-12T17:49:54.875127Z","iopub.execute_input":"2026-03-12T17:49:54.876542Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Multi Layer Perceptron # ","metadata":{}},{"cell_type":"code","source":"# =========================\n# MLP Notebook (Digit Recognizer)\n# =========================\n\nimport os, time\nimport numpy as np\nimport pandas as pd\nimport matplotlib.pyplot as plt\n\nimport torch\nimport torch.nn as nn\nimport torch.optim as optim\nfrom torch.utils.data import Dataset, DataLoader, random_split\n\nfrom sklearn.metrics import confusion_matrix, ConfusionMatrixDisplay, f1_score\n\n# -----------------------------\n# Reproducibility\n# -----------------------------\nSEED = 42\ntorch.manual_seed(SEED)\nnp.random.seed(SEED)\n\ndevice = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\nprint(\"Device:\", device)\n\n# -----------------------------\n# Find dataset automatically\n# -----------------------------\ndef find_digit_recognizer_paths():\n    base=\"/kaggle/input\"\n    for root,_,files in os.walk(base):\n        if \"train.csv\" in files and \"test.csv\" in files:\n            return os.path.join(root,\"train.csv\"),os.path.join(root,\"test.csv\")\n    raise FileNotFoundError(\"Digit Recognizer dataset not found\")\n\nTRAIN_PATH,TEST_PATH=find_digit_recognizer_paths()\n\ntrain_df=pd.read_csv(TRAIN_PATH)\ntest_df=pd.read_csv(TEST_PATH)\n\ny_all=train_df[\"label\"].values.astype(np.int64)\nX_all=train_df.drop(columns=[\"label\"]).values.astype(np.float32)/255.0\nX_test=test_df.values.astype(np.float32)/255.0\n\n# -----------------------------\n# Dataset\n# -----------------------------\nclass MNISTDataset(Dataset):\n    def __init__(self,X,y=None):\n        self.X=X\n        self.y=y\n\n    def __len__(self):\n        return len(self.X)\n\n    def __getitem__(self,idx):\n\n        x=torch.tensor(self.X[idx],dtype=torch.float32)\n\n        if self.y is None:\n            return x\n\n        y=torch.tensor(self.y[idx],dtype=torch.long)\n\n        return x,y\n\n\nfull_ds=MNISTDataset(X_all,y_all)\n\nval_size=int(0.1*len(full_ds))\ntrain_size=len(full_ds)-val_size\n\ntrain_ds,val_ds=random_split(full_ds,[train_size,val_size])\n\nBATCH_SIZE=128\n\ntrain_loader=DataLoader(train_ds,batch_size=BATCH_SIZE,shuffle=True)\nval_loader=DataLoader(val_ds,batch_size=BATCH_SIZE,shuffle=False)\n\n# -----------------------------\n# MLP Architecture Diagram\n# -----------------------------\nfrom matplotlib.patches import FancyBboxPatch\n\nfig,ax=plt.subplots(figsize=(12,3))\n\ndef draw_box(text,x,color):\n\n    box=FancyBboxPatch((x,0.4),0.22,0.25,\n                       boxstyle=\"round,pad=0.02\",\n                       linewidth=1.5,\n                       edgecolor=\"black\",\n                       facecolor=color)\n\n    ax.add_patch(box)\n\n    ax.text(x+0.11,0.525,text,\n            ha=\"center\",\n            va=\"center\",\n            fontsize=11,\n            weight=\"bold\")\n\ndraw_box(\"Input\\n(784)\",0.02,\"#D6EAF8\")\ndraw_box(\"Hidden Layer 1\\n(256)\",0.26,\"#D5F5E3\")\ndraw_box(\"Hidden Layer 2\\n(128)\",0.50,\"#D5F5E3\")\ndraw_box(\"Output\\n(10)\",0.74,\"#FADBD8\")\n\nax.annotate(\"\",xy=(0.26,0.525),xytext=(0.18,0.525),arrowprops=dict(arrowstyle=\"->\",linewidth=2))\nax.annotate(\"\",xy=(0.50,0.525),xytext=(0.42,0.525),arrowprops=dict(arrowstyle=\"->\",linewidth=2))\nax.annotate(\"\",xy=(0.74,0.525),xytext=(0.66,0.525),arrowprops=dict(arrowstyle=\"->\",linewidth=2))\n\nax.axis(\"off\")\nax.set_title(\"MLP Architecture\")\n\nplt.show()\n\n# -----------------------------\n# Model\n# -----------------------------\nclass MLP(nn.Module):\n\n    def __init__(self):\n\n        super().__init__()\n\n        self.fc1=nn.Linear(784,256)\n        self.fc2=nn.Linear(256,128)\n        self.fc3=nn.Linear(128,10)\n\n        self.relu=nn.ReLU()\n\n    def forward(self,x):\n\n        x=self.relu(self.fc1(x))\n        x=self.relu(self.fc2(x))\n        x=self.fc3(x)\n\n        return x\n\n\nmodel=MLP().to(device)\n\nprint(model)\n\ndef count_params(m):\n    return sum(p.numel() for p in m.parameters() if p.requires_grad)\n\nprint(\"Trainable parameters:\",count_params(model))\n\n# -----------------------------\n# Training Setup\n# -----------------------------\ncriterion=nn.CrossEntropyLoss()\noptimizer=optim.Adam(model.parameters(),lr=1e-3)\n\nEPOCHS=8\n\ndef eval_acc_f1(model,loader):\n\n    model.eval()\n\n    all_preds=[]\n    all_true=[]\n\n    with torch.no_grad():\n\n        for x,y in loader:\n\n            x,y=x.to(device),y.to(device)\n\n            logits=model(x)\n\n            preds=torch.argmax(logits,dim=1)\n\n            all_preds.append(preds.cpu().numpy())\n            all_true.append(y.cpu().numpy())\n\n    all_preds=np.concatenate(all_preds)\n    all_true=np.concatenate(all_true)\n\n    acc=(all_preds==all_true).mean()\n    f1=f1_score(all_true,all_preds,average=\"macro\")\n\n    return acc,f1,all_true,all_preds\n\n\n# -----------------------------\n# Training Loop\n# -----------------------------\ntrain_losses=[]\nval_losses=[]\ntrain_accs=[]\nval_accs=[]\n\nstart=time.time()\n\nfor epoch in range(1,EPOCHS+1):\n\n    model.train()\n\n    running_loss=0\n    correct=0\n    total=0\n\n    for x,y in train_loader:\n\n        x,y=x.to(device),y.to(device)\n\n        optimizer.zero_grad()\n\n        logits=model(x)\n\n        loss=criterion(logits,y)\n\n        loss.backward()\n\n        optimizer.step()\n\n        running_loss+=loss.item()\n\n        preds=torch.argmax(logits,dim=1)\n\n        correct+=(preds==y).sum().item()\n        total+=y.size(0)\n\n    tr_loss=running_loss/len(train_loader)\n    tr_acc=correct/total\n\n    model.eval()\n\n    vloss=0\n    vcorrect=0\n    vtotal=0\n\n    with torch.no_grad():\n\n        for x,y in val_loader:\n\n            x,y=x.to(device),y.to(device)\n\n            logits=model(x)\n\n            loss=criterion(logits,y)\n\n            vloss+=loss.item()\n\n            preds=torch.argmax(logits,dim=1)\n\n            vcorrect+=(preds==y).sum().item()\n            vtotal+=y.size(0)\n\n    va_loss=vloss/len(val_loader)\n    va_acc=vcorrect/vtotal\n\n    train_losses.append(tr_loss)\n    val_losses.append(va_loss)\n\n    train_accs.append(tr_acc)\n    val_accs.append(va_acc)\n\n    print(f\"Epoch {epoch}/{EPOCHS} | Train Loss {tr_loss:.4f} Acc {tr_acc:.4f} | Val Loss {va_loss:.4f} Acc {va_acc:.4f}\")\n\ntrain_time=time.time()-start\n\nfinal_acc,final_f1,y_true,y_pred=eval_acc_f1(model,val_loader)\n\nprint(\"Final Val Accuracy:\",final_acc)\nprint(\"Final Val F1:\",final_f1)\n\n# -----------------------------\n# Curves\n# -----------------------------\nepochs=range(1,EPOCHS+1)\n\nplt.figure(figsize=(12,5))\n\nplt.subplot(1,2,1)\nplt.plot(epochs,train_losses,label=\"Train Loss\")\nplt.plot(epochs,val_losses,label=\"Val Loss\")\nplt.legend()\nplt.title(\"MLP Loss Curves\")\n\nplt.subplot(1,2,2)\nplt.plot(epochs,train_accs,label=\"Train Acc\")\nplt.plot(epochs,val_accs,label=\"Val Acc\")\nplt.legend()\nplt.title(\"MLP Accuracy Curves\")\n\nplt.show()\n\n# -----------------------------\n# Confusion Matrix\n# -----------------------------\ncm=confusion_matrix(y_true,y_pred)\n\ndisp=ConfusionMatrixDisplay(cm)\ndisp.plot(values_format=\"d\")\n\nplt.title(\"MLP Confusion Matrix\")\nplt.show()\n\n\n# -----------------------------\n# Learned Features (MLP)\n# -----------------------------\nW1 = model.fc1.weight.detach().cpu().numpy()  # shape (256,784)\n\n# pick 10 random neurons\nidx = np.random.choice(W1.shape[0],10,replace=False)\n\nplt.figure(figsize=(10,4))\n\nfor i,k in enumerate(idx):\n    \n    plt.subplot(2,5,i+1)\n    plt.imshow(W1[k].reshape(28,28), cmap=\"gray\")\n    plt.title(f\"Neuron {k}\")\n    plt.axis(\"off\")\n\nplt.suptitle(\"MLP Learned Features (First Layer)\")\nplt.show()\n\n# -----------------------------\n# Train on full dataset (MLP)\n# -----------------------------\nfull_loader = DataLoader(MNISTDataset(X_all, y_all), batch_size=BATCH_SIZE, shuffle=True)\n\noptimizer = optim.Adam(model.parameters(), lr=1e-3)\n\nmodel.train()\n\nfor epoch in range(1, EPOCHS + 1):\n\n    running = 0\n\n    for x, y in full_loader:\n\n        x, y = x.to(device), y.to(device)\n\n        optimizer.zero_grad()\n\n        loss = criterion(model(x), y)\n\n        loss.backward()\n\n        optimizer.step()\n\n        running += loss.item()\n\n    print(f\"[Full Train] Epoch {epoch}/{EPOCHS} Loss {running/len(full_loader):.4f}\")\n\n# -----------------------------\n# Submission\n# -----------------------------\ntest_loader=DataLoader(MNISTDataset(X_test),batch_size=BATCH_SIZE)\n\nmodel.eval()\n\npreds_all=[]\n\nwith torch.no_grad():\n\n    for x in test_loader:\n\n        x=x.to(device)\n\n        preds=torch.argmax(model(x),dim=1)\n\n        preds_all.append(preds.cpu().numpy())\n\npreds_all=np.concatenate(preds_all)\n\nsubmission=pd.DataFrame({\n\"ImageId\":np.arange(1,len(preds_all)+1),\n\"Label\":preds_all\n})\n\nsubmission.to_csv(\"submission_mlp.csv\",index=False)\n\nprint(\"Saved submission_mlp.csv\")\n\n# -----------------------------\n# Save Results\n# -----------------------------\nmlp_results={\n\"Model\":\"MLP\",\n\"Val Accuracy\":final_acc,\n\"Val F1\":final_f1,\n\"Train Time (s)\":train_time,\n\"Params\":count_params(model),\n\"Kaggle Score\": 0.0\n}\n\nprint(mlp_results)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-11T11:45:21.524598Z","iopub.execute_input":"2026-03-11T11:45:21.525097Z","iopub.status.idle":"2026-03-11T11:46:13.664080Z","shell.execute_reply.started":"2026-03-11T11:45:21.525058Z","shell.execute_reply":"2026-03-11T11:46:13.662536Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":" # Convolutional Neural Network # ","metadata":{}},{"cell_type":"code","source":"# =========================\n# CNN (LeNet) Notebook\n# =========================\n\nimport os,time\nimport numpy as np\nimport pandas as pd\nimport matplotlib.pyplot as plt\n\nimport torch\nimport torch.nn as nn\nimport torch.optim as optim\nfrom torch.utils.data import Dataset,DataLoader,random_split\n\nfrom sklearn.metrics import confusion_matrix,ConfusionMatrixDisplay,f1_score\n\nSEED=42\ntorch.manual_seed(SEED)\nnp.random.seed(SEED)\n\ndevice=torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\n\n# -----------------------------\n# Load dataset\n# -----------------------------\ndef find_digit_recognizer_paths():\n    base=\"/kaggle/input\"\n    for root,_,files in os.walk(base):\n        if \"train.csv\" in files and \"test.csv\" in files:\n            return os.path.join(root,\"train.csv\"),os.path.join(root,\"test.csv\")\n\nTRAIN_PATH,TEST_PATH=find_digit_recognizer_paths()\n\ntrain_df=pd.read_csv(TRAIN_PATH)\ntest_df=pd.read_csv(TEST_PATH)\n\ny_all=train_df[\"label\"].values.astype(np.int64)\nX_all=train_df.drop(columns=[\"label\"]).values.astype(np.float32)/255.0\nX_test=test_df.values.astype(np.float32)/255.0\n\n# -----------------------------\n# Dataset\n# -----------------------------\nclass MNISTDatasetCNN(Dataset):\n\n    def __init__(self,X,y=None):\n\n        self.X=X\n        self.y=y\n\n    def __len__(self):\n        return len(self.X)\n\n    def __getitem__(self,idx):\n\n        x=self.X[idx].reshape(1,28,28)\n\n        x=torch.tensor(x,dtype=torch.float32)\n\n        if self.y is None:\n            return x\n\n        y=torch.tensor(self.y[idx],dtype=torch.long)\n\n        return x,y\n\n\nfull_ds=MNISTDatasetCNN(X_all,y_all)\n\nval_size=int(0.1*len(full_ds))\ntrain_size=len(full_ds)-val_size\n\ntrain_ds,val_ds=random_split(full_ds,[train_size,val_size])\n\nBATCH_SIZE=128\n\ntrain_loader=DataLoader(train_ds,batch_size=BATCH_SIZE,shuffle=True)\nval_loader=DataLoader(val_ds,batch_size=BATCH_SIZE)\n\n# -----------------------------\n# CNN Architecture Diagram\n# -----------------------------\nfrom matplotlib.patches import FancyBboxPatch\n\nfig,ax=plt.subplots(figsize=(14,3))\n\ndef draw_box(text,x,color):\n\n    box=FancyBboxPatch((x,0.4),0.16,0.25,\n                       boxstyle=\"round,pad=0.02\",\n                       linewidth=1.5,\n                       edgecolor=\"black\",\n                       facecolor=color)\n\n    ax.add_patch(box)\n\n    ax.text(x+0.08,0.525,text,\n            ha=\"center\",\n            va=\"center\",\n            fontsize=10,\n            weight=\"bold\")\n\ndraw_box(\"Input\\n28x28\",0.02,\"#D6EAF8\")\ndraw_box(\"Conv1\\n6 filters\",0.18,\"#D5F5E3\")\ndraw_box(\"Pool\",0.34,\"#FCF3CF\")\ndraw_box(\"Conv2\\n16 filters\",0.50,\"#D5F5E3\")\ndraw_box(\"Pool\",0.66,\"#FCF3CF\")\ndraw_box(\"Dense\",0.82,\"#E8DAEF\")\n\nax.axis(\"off\")\nax.set_title(\"CNN (LeNet) Architecture\")\n\nplt.show()\n\n# -----------------------------\n# Model\n# -----------------------------\nclass LeNet(nn.Module):\n\n    def __init__(self):\n\n        super().__init__()\n\n        self.conv1=nn.Conv2d(1,6,5)\n        self.pool=nn.MaxPool2d(2,2)\n        self.conv2=nn.Conv2d(6,16,5)\n\n        self.fc1=nn.Linear(256,120)\n        self.fc2=nn.Linear(120,84)\n        self.fc3=nn.Linear(84,10)\n\n    def forward(self,x):\n\n        x=self.pool(torch.relu(self.conv1(x)))\n        x=self.pool(torch.relu(self.conv2(x)))\n\n        x=x.view(x.size(0),-1)\n\n        x=torch.relu(self.fc1(x))\n        x=torch.relu(self.fc2(x))\n\n        return self.fc3(x)\n\n\nmodel=LeNet().to(device)\n\nprint(model)\n\ndef count_params(m):\n    return sum(p.numel() for p in m.parameters() if p.requires_grad)\n\nprint(\"Trainable parameters:\",count_params(model))\n\ncriterion=nn.CrossEntropyLoss()\noptimizer=optim.Adam(model.parameters(),lr=1e-3)\n\nEPOCHS=8\n\n# -----------------------------\n# Training Loop\n# -----------------------------\ntrain_losses=[]\nval_losses=[]\ntrain_accs=[]\nval_accs=[]\n\nstart=time.time()\n\nfor epoch in range(1,EPOCHS+1):\n\n    model.train()\n\n    running_loss=0\n    correct=0\n    total=0\n\n    for x,y in train_loader:\n\n        x,y=x.to(device),y.to(device)\n\n        optimizer.zero_grad()\n\n        logits=model(x)\n\n        loss=criterion(logits,y)\n\n        loss.backward()\n\n        optimizer.step()\n\n        running_loss+=loss.item()\n\n        preds=torch.argmax(logits,dim=1)\n\n        correct+=(preds==y).sum().item()\n        total+=y.size(0)\n\n    tr_loss=running_loss/len(train_loader)\n    tr_acc=correct/total\n\n    model.eval()\n\n    vloss=0\n    vcorrect=0\n    vtotal=0\n\n    with torch.no_grad():\n\n        for x,y in val_loader:\n\n            x,y=x.to(device),y.to(device)\n\n            logits=model(x)\n\n            loss=criterion(logits,y)\n\n            vloss+=loss.item()\n\n            preds=torch.argmax(logits,dim=1)\n\n            vcorrect+=(preds==y).sum().item()\n            vtotal+=y.size(0)\n\n    va_loss=vloss/len(val_loader)\n    va_acc=vcorrect/vtotal\n\n    train_losses.append(tr_loss)\n    val_losses.append(va_loss)\n\n    train_accs.append(tr_acc)\n    val_accs.append(va_acc)\n\n\nprint(f\"Epoch {epoch}/{EPOCHS} | Train Loss {tr_loss:.4f} Acc {tr_acc:.4f} | Val Loss {va_loss:.4f} Acc {va_acc:.4f}\")\n\n\n\ntrain_time=time.time()-start\n\n# -----------------------------\n# Evaluation\n# -----------------------------\nall_preds=[]\nall_true=[]\n\nmodel.eval()\n\nwith torch.no_grad():\n\n    for x,y in val_loader:\n\n        x,y=x.to(device),y.to(device)\n\n        preds=torch.argmax(model(x),dim=1)\n\n        all_preds.append(preds.cpu().numpy())\n        all_true.append(y.cpu().numpy())\n\nall_preds=np.concatenate(all_preds)\nall_true=np.concatenate(all_true)\n\nfinal_acc=(all_preds==all_true).mean()\nfinal_f1=f1_score(all_true,all_preds,average=\"macro\")\n\nprint(\"Accuracy:\",final_acc)\n\n\n# -----------------------------\n# Training Curves\n# -----------------------------\nepochs = range(1, EPOCHS + 1)\n\nplt.figure(figsize=(12,5))\n\nplt.subplot(1,2,1)\nplt.plot(epochs, train_losses, label=\"Train Loss\")\nplt.plot(epochs, val_losses, label=\"Val Loss\")\nplt.legend()\nplt.title(\"CNN Loss Curves\")\nplt.xlabel(\"Epoch\")\nplt.ylabel(\"Loss\")\n\nplt.subplot(1,2,2)\nplt.plot(epochs, train_accs, label=\"Train Accuracy\")\nplt.plot(epochs, val_accs, label=\"Val Accuracy\")\nplt.legend()\nplt.title(\"CNN Accuracy Curves\")\nplt.xlabel(\"Epoch\")\nplt.ylabel(\"Accuracy\")\n\nplt.show()\n\n\n\n# -----------------------------\n# Confusion Matrix\n# -----------------------------\ncm=confusion_matrix(all_true,all_preds)\n\ndisp=ConfusionMatrixDisplay(cm)\ndisp.plot(values_format=\"d\")\n\nplt.title(\"CNN Confusion Matrix\")\nplt.show()\n\n# -----------------------------\n# CNN Learned Filters\n# -----------------------------\nfilters = model.conv1.weight.detach().cpu().numpy()  # (6,1,5,5)\n\nplt.figure(figsize=(10,2))\n\nfor i in range(filters.shape[0]):\n\n    plt.subplot(1, filters.shape[0], i+1)\n    plt.imshow(filters[i,0], cmap=\"gray\")\n    plt.title(f\"F{i}\")\n    plt.axis(\"off\")\n\nplt.suptitle(\"CNN Learned Filters (Conv1)\")\nplt.show()\n\n# -----------------------------\n# Train on full dataset (CNN)\n# -----------------------------\nfull_loader = DataLoader(MNISTDatasetCNN(X_all, y_all), batch_size=BATCH_SIZE, shuffle=True)\n\noptimizer = optim.Adam(model.parameters(), lr=1e-3)\n\nmodel.train()\n\nfor epoch in range(1, EPOCHS + 1):\n\n    running = 0\n\n    for x, y in full_loader:\n\n        x, y = x.to(device), y.to(device)\n\n        optimizer.zero_grad()\n\n        loss = criterion(model(x), y)\n\n        loss.backward()\n\n        optimizer.step()\n\n        running += loss.item()\n\n    print(f\"[Full Train] Epoch {epoch}/{EPOCHS} Loss {running/len(full_loader):.4f}\")\n\n# -----------------------------\n# Submission\n# -----------------------------\ntest_loader=DataLoader(MNISTDatasetCNN(X_test),batch_size=BATCH_SIZE)\n\nmodel.eval()\n\npreds_all=[]\n\nwith torch.no_grad():\n\n    for x in test_loader:\n\n        x=x.to(device)\n\n        preds=torch.argmax(model(x),dim=1)\n\n        preds_all.append(preds.cpu().numpy())\n\npreds_all=np.concatenate(preds_all)\n\nsubmission=pd.DataFrame({\n\"ImageId\":np.arange(1,len(preds_all)+1),\n\"Label\":preds_all\n})\n\nsubmission.to_csv(\"submission_cnn.csv\",index=False)\n\nprint(\"Saved submission_cnn.csv\")\n\n# -----------------------------\n# Save Results\n# -----------------------------\ncnn_results={\n\"Model\":\"CNN (LeNet)\",\n\"Val Accuracy\":final_acc,\n\"Val F1\":final_f1,\n\"Train Time (s)\":train_time,\n\"Params\":count_params(model),\n\"Kaggle Score\": 0.0\n}\n\nprint(cnn_results)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-12T12:01:29.634048Z","iopub.execute_input":"2026-03-12T12:01:29.634331Z","iopub.status.idle":"2026-03-12T12:02:55.421256Z","shell.execute_reply.started":"2026-03-12T12:01:29.634308Z","shell.execute_reply":"2026-03-12T12:02:55.419920Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Comparison Table # ","metadata":{}},{"cell_type":"code","source":"# =========================\n# Model Comparison Table\n# =========================\n\nimport pandas as pd\n\nresults = pd.DataFrame([\n    perceptron_results,\n    mlp_results,\n    cnn_results\n])\n\n# nicer formatting\nresults = results[[\"Model\",\"Val Accuracy\",\"Val F1\",\"Train Time (s)\",\"Params\",\"Kaggle Score\"]]\n\nresults.style \\\n    .format({\n        \"Val Accuracy\": \"{:.4f}\",\n        \"Val F1\": \"{:.4f}\",\n        \"Train Time (s)\": \"{:.2f}\",\n        \"Params\": \"{:,}\",\n        \"Kaggle Score\": \"{:.5f}\"\n    }) \\\n    .background_gradient(subset=[\"Val Accuracy\",\"Val F1\"], cmap=\"Greens\") \\\n    .set_caption(\"Comparison of Perceptron, MLP, and CNN Models on MNIST\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-11T11:52:43.260365Z","iopub.execute_input":"2026-03-11T11:52:43.262058Z","iopub.status.idle":"2026-03-11T11:52:43.285068Z","shell.execute_reply.started":"2026-03-11T11:52:43.261992Z","shell.execute_reply":"2026-03-11T11:52:43.283991Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import matplotlib.pyplot as plt\nfrom tensorflow.keras.datasets import mnist\n\n(x_train, y_train), _ = mnist.load_data()\n\nplt.figure(figsize=(6,6))\n\nfor i in range(9):\n    plt.subplot(3,3,i+1)\n    plt.imshow(x_train[i], cmap='gray')\n    plt.axis('off')\n\nplt.tight_layout()\nplt.savefig(\"mnist_samples.png\", dpi=300)\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-12T12:03:24.459408Z","iopub.execute_input":"2026-03-12T12:03:24.459751Z","iopub.status.idle":"2026-03-12T12:03:25.097072Z","shell.execute_reply.started":"2026-03-12T12:03:24.459724Z","shell.execute_reply":"2026-03-12T12:03:25.095601Z"}},"outputs":[],"execution_count":null}]}