{"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":"nvidiaTeslaT4","dataSources":[{"sourceId":3004,"databundleVersionId":861823,"sourceType":"competition"}],"dockerImageVersionId":31090,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"# This Python 3 environment comes with many helpful analytics libraries installed\n# It is defined by the kaggle/python Docker image: https://github.com/kaggle/docker-python\n# For example, here's several helpful packages to load\n\nimport numpy as np # linear algebra\nimport pandas as pd # data processing, CSV file I/O (e.g. pd.read_csv)\n\n# Input data files are available in the read-only \"../input/\" directory\n# For example, running this (by clicking run or pressing Shift+Enter) will list all files under the input directory\n\nimport os\nfor dirname, _, filenames in os.walk('/kaggle/input'):\n    for filename in filenames:\n        print(os.path.join(dirname, filename))\n\n# You can write up to 20GB to the current directory (/kaggle/working/) that gets preserved as output when you create a version using \"Save & Run All\" \n# You can also write temporary files to /kaggle/temp/, but they won't be saved outside of the current session","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2025-07-23T11:19:12.993651Z","iopub.execute_input":"2025-07-23T11:19:12.994140Z","iopub.status.idle":"2025-07-23T11:19:13.247934Z","shell.execute_reply.started":"2025-07-23T11:19:12.994116Z","shell.execute_reply":"2025-07-23T11:19:13.247204Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Loading Dataset","metadata":{}},{"cell_type":"code","source":"import torch \nfrom torch.utils.data import Dataset\ntrain_df=pd.read_csv(\"/kaggle/input/digit-recognizer/train.csv\")\ntest_df=pd.read_csv(\"/kaggle/input/digit-recognizer/test.csv\")\nprint(train_df.describe())\nprint(test_df.describe())\n        ","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-23T11:19:13.249275Z","iopub.execute_input":"2025-07-23T11:19:13.249633Z","iopub.status.idle":"2025-07-23T11:19:24.375197Z","shell.execute_reply.started":"2025-07-23T11:19:13.249614Z","shell.execute_reply":"2025-07-23T11:19:24.374558Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"X=train_df.iloc[:,1:].to_numpy()/255\ny=train_df.iloc[:,0].to_numpy()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-23T11:19:24.375863Z","iopub.execute_input":"2025-07-23T11:19:24.376102Z","iopub.status.idle":"2025-07-23T11:19:24.481265Z","shell.execute_reply.started":"2025-07-23T11:19:24.376084Z","shell.execute_reply":"2025-07-23T11:19:24.480688Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from sklearn.model_selection import train_test_split\nX_train,X_val,y_train,y_val=train_test_split(X,y,test_size=0.1,random_state=42)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-23T11:19:24.481946Z","iopub.execute_input":"2025-07-23T11:19:24.482205Z","iopub.status.idle":"2025-07-23T11:19:25.450954Z","shell.execute_reply.started":"2025-07-23T11:19:24.482181Z","shell.execute_reply":"2025-07-23T11:19:25.450374Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"X_train[0].shape","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-23T11:19:25.452816Z","iopub.execute_input":"2025-07-23T11:19:25.453148Z","iopub.status.idle":"2025-07-23T11:19:25.458539Z","shell.execute_reply.started":"2025-07-23T11:19:25.453130Z","shell.execute_reply":"2025-07-23T11:19:25.457854Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class MNISTDataset(Dataset):\n    def __init__(self,features,labels):\n        self.features=torch.tensor(features,dtype=torch.float32).reshape(-1,1,28,28) # Convert features to a 4D tensor (N, C, H, W) and store in self.data N is the batch_size\n        self.labels=torch.tensor(labels)\n    def __len__(self):\n        return len(self.features)\n    def __getitem__(self,idx):\n        return self.features[idx],self.labels[idx]\n        \n\n        \n        ","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-23T11:19:25.459160Z","iopub.execute_input":"2025-07-23T11:19:25.459356Z","iopub.status.idle":"2025-07-23T11:19:25.471491Z","shell.execute_reply.started":"2025-07-23T11:19:25.459341Z","shell.execute_reply":"2025-07-23T11:19:25.470862Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_dataset=MNISTDataset(X_train,y_train)\nval_dataset=MNISTDataset(X_val,y_val)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-23T11:19:25.472066Z","iopub.execute_input":"2025-07-23T11:19:25.472380Z","iopub.status.idle":"2025-07-23T11:19:25.587852Z","shell.execute_reply.started":"2025-07-23T11:19:25.472356Z","shell.execute_reply":"2025-07-23T11:19:25.587282Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from torch.utils.data import DataLoader\ntrain_dataloader=DataLoader(train_dataset,batch_size=32,shuffle=True)\nval_dataloader=DataLoader(val_dataset,batch_size=32,shuffle=False)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-23T11:19:25.588525Z","iopub.execute_input":"2025-07-23T11:19:25.588791Z","iopub.status.idle":"2025-07-23T11:19:25.593211Z","shell.execute_reply.started":"2025-07-23T11:19:25.588762Z","shell.execute_reply":"2025-07-23T11:19:25.592516Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Building the model","metadata":{}},{"cell_type":"code","source":"import torch.nn as nn\nclass Model(nn.Module):\n    def __init__(self):\n        super().__init__()\n        self.feature_extractor = nn.Sequential(\n            # Input: (N, 1, 28, 28)\n            nn.Conv2d(1, 112, kernel_size=3, padding=1),  # (N, 112, 28, 28)\n            nn.BatchNorm2d(112),\n            nn.ELU(inplace=True),\n            nn.AvgPool2d(kernel_size=2),  # Halves dimensions -> (N, 112, 14, 14)\n\n            nn.Conv2d(112, 224, kernel_size=3, padding=1), #  (N, 224, 14, 14)\n            nn.BatchNorm2d(224),\n            nn.ELU(inplace=True),\n            nn.AvgPool2d(kernel_size=2),  # Halves dimensions -> (N, 224, 7, 7)\n\n            nn.Conv2d(224, 224, kernel_size=3, padding=1), # Stays (N, 224, 7, 7)\n            nn.BatchNorm2d(224),\n            nn.ELU(inplace=True),\n            \n            # This layer is key: it forces the 7x7 maps to 1x1, regardless of input size\n            nn.AdaptiveAvgPool2d((1, 1)), # Output: (N, 224, 1, 1)\n            nn.Flatten()                   # Output: (N, 224)\n        )\n        \n        self.classifier = nn.Sequential(\n            # Input from feature_extractor is 224 features\n            nn.Dropout(p=0.5),\n            nn.Linear(224, 448),\n            nn.ELU(inplace=True),\n            nn.Dropout(p=0.5),\n            #Output must be 10 for the 10 MNIST classes (0-9)\n            nn.Linear(448, 10) \n        )\n\n    def forward(self, x):\n        x = self.feature_extractor(x)\n        x = self.classifier(x)\n        return x\ndevice=\"cuda\" if torch.cuda.is_available() else'cpu'","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-23T11:19:25.594057Z","iopub.execute_input":"2025-07-23T11:19:25.594384Z","iopub.status.idle":"2025-07-23T11:19:25.691058Z","shell.execute_reply.started":"2025-07-23T11:19:25.594360Z","shell.execute_reply":"2025-07-23T11:19:25.690385Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"net=Model().to(device)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-23T11:19:25.691787Z","iopub.execute_input":"2025-07-23T11:19:25.692025Z","iopub.status.idle":"2025-07-23T11:19:25.908323Z","shell.execute_reply.started":"2025-07-23T11:19:25.692001Z","shell.execute_reply":"2025-07-23T11:19:25.907549Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from torchsummary import summary\nsummary(net,input_size=(1,28,28))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-23T11:19:25.909105Z","iopub.execute_input":"2025-07-23T11:19:25.909421Z","iopub.status.idle":"2025-07-23T11:19:26.612025Z","shell.execute_reply.started":"2025-07-23T11:19:25.909400Z","shell.execute_reply":"2025-07-23T11:19:26.611276Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Training and Evaluation","metadata":{}},{"cell_type":"code","source":"from tqdm.auto import tqdm\nimport torch.optim as optim\n# --- Helper function for one training epoch ---\ndef train_step(model: torch.nn.Module,\n               dataloader: torch.utils.data.DataLoader,\n               loss_fn: torch.nn.Module,\n               optimizer: torch.optim.Optimizer,\n               device: torch.device):\n    model.train()\n    train_loss, train_acc = 0, 0\n\n    # Use tqdm for a progress bar\n    for X, y in tqdm(dataloader, desc=\"Training\", leave=False):\n        # Move data to the target device\n        X, y = X.to(device), y.to(device)\n\n        # 1. Forward pass\n        y_pred = model(X)\n\n        # 2. Calculate loss\n        loss = loss_fn(y_pred, y)\n        train_loss += loss.item()\n\n        # 3. Optimizer zero grad, backward pass, and optimizer step\n        optimizer.zero_grad()\n        loss.backward()\n        optimizer.step()\n\n        # 4. Calculate accuracy\n        y_pred_class = torch.argmax(y_pred, dim=1)\n        train_acc += (y_pred_class == y).sum().item()\n\n    # Calculate average loss and accuracy for the epoch\n    avg_loss = train_loss / len(dataloader)\n    avg_acc = train_acc / len(dataloader.dataset)\n    return avg_loss, avg_acc\n\n# --- Helper function for one validation epoch ---\ndef val_step(model: torch.nn.Module,\n             dataloader: torch.utils.data.DataLoader,\n             loss_fn: torch.nn.Module,\n             device: torch.device):\n    model.eval()\n    val_loss, val_acc = 0, 0\n\n    with torch.no_grad():\n        for X, y in tqdm(dataloader, desc=\"Validation\", leave=False):\n            X, y = X.to(device), y.to(device)\n\n            # 1. Forward pass\n            val_pred = model(X)\n\n            # 2. Calculate loss\n            loss = loss_fn(val_pred, y)\n            val_loss += loss.item()\n\n            # 3. Calculate accuracy\n            val_pred_class = torch.argmax(val_pred, dim=1)\n            val_acc += (val_pred_class == y).sum().item()\n\n    # Calculate average loss and accuracy for the epoch\n    avg_loss = val_loss / len(dataloader)\n    avg_acc = val_acc / len(dataloader.dataset)\n    return avg_loss, avg_acc\n\n\n# --- Main Training Loop ---\nepochs = 10\ntrain_losses, train_accuracies = [], []\nval_losses, val_accuracies = [], []\ncriterion=nn.CrossEntropyLoss()\noptimizer = optim.Adam(net.parameters(), lr=0.001)\nfor epoch in tqdm(range(epochs), desc=\"Epochs\"):\n    # Call the train step function\n    train_loss, train_acc = train_step(model=net,\n                                       dataloader=train_dataloader,\n                                       loss_fn=criterion,\n                                       optimizer=optimizer,\n                                       device=device)\n\n    # Call the validation step function\n    val_loss, val_acc = val_step(model=net,\n                                 dataloader=val_dataloader,\n                                 loss_fn=criterion,\n                                 device=device)\n\n    # Print results for the epoch\n    print(\n        f\"Epoch: {epoch+1} | \"\n        f\"Train Loss: {train_loss:.4f} | \"\n        f\"Train Acc: {train_acc*100:.2f}% | \"\n        f\"Val Loss: {val_loss:.4f} | \"\n        f\"Val Acc: {val_acc*100:.2f}%\"\n    )\n\n    # Store results\n    train_losses.append(train_loss)\n    train_accuracies.append(train_acc)\n    val_losses.append(val_loss)\n    val_accuracies.append(val_acc)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-23T11:19:26.612873Z","iopub.execute_input":"2025-07-23T11:19:26.613557Z","iopub.status.idle":"2025-07-23T11:20:36.236019Z","shell.execute_reply.started":"2025-07-23T11:19:26.613529Z","shell.execute_reply":"2025-07-23T11:20:36.235283Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import matplotlib.pyplot as plt\nepochs_range = range(1, epochs + 1)\nplt.figure(figsize=(12, 5))\nplt.plot(epochs_range, train_losses, 'r-', label='Training Loss')\nplt.plot(epochs_range, val_accuracies, 'b-', label='Validation Accuracy (%)')\nplt.xlabel('Epoch')\nplt.ylabel('Value')\nplt.title('Training Loss and Validation Accuracy Over Epochs')\nplt.legend()\nplt.grid(True)\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-23T11:20:52.798679Z","iopub.execute_input":"2025-07-23T11:20:52.799283Z","iopub.status.idle":"2025-07-23T11:20:53.031368Z","shell.execute_reply.started":"2025-07-23T11:20:52.799225Z","shell.execute_reply":"2025-07-23T11:20:53.030696Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"X_test=test_df.values/255.0\nX_test = torch.tensor(X_test, dtype=torch.float32).reshape(-1, 1, 28, 28).to(device)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-23T11:28:44.958412Z","iopub.execute_input":"2025-07-23T11:28:44.958859Z","iopub.status.idle":"2025-07-23T11:28:45.102559Z","shell.execute_reply.started":"2025-07-23T11:28:44.958839Z","shell.execute_reply":"2025-07-23T11:28:45.101988Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from torch.utils.data import TensorDataset\nnet.eval()\ntest_dataset=TensorDataset(X_test)\ntest_dataloader=DataLoader(test_dataset,batch_size=32)\nall_preds = [] # List to store predictions from each batch\n\n\nwith torch.no_grad():\n    for X_batch_tuple in tqdm(test_dataloader, desc=\"Predicting\"):\n        # The dataloader wraps our tensor in a tuple, so we extract it\n        X_batch = X_batch_tuple[0].to(device)\n        outputs = net(X_batch)\n\n        # Get predictions by finding the class with the highest score\n        _, preds = torch.max(outputs, 1)\n\n        # Move predictions to CPU and append to our list\n        all_preds.append(preds.cpu())\n\n# Concatenate the list of prediction tensors into one single tensor\nfinal_predictions = torch.cat(all_preds)\n\n# --- Create and save the submission file ---\n\nprint(f\"Generated {len(final_predictions)} predictions. Creating submission file...\")\n\nsubmission = pd.DataFrame({\n    \"ImageId\": range(1, len(final_predictions) + 1),\n    \"Label\": final_predictions.numpy() # Convert tensor to numpy array for pandas\n})\n\n# Save to CSV\nsubmission.to_csv('submission.csv', index=False)\n\nprint(\"Submission file 'submission.csv' created successfully!\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-23T11:34:06.756553Z","iopub.execute_input":"2025-07-23T11:34:06.757089Z","iopub.status.idle":"2025-07-23T11:34:08.374080Z","shell.execute_reply.started":"2025-07-23T11:34:06.757066Z","shell.execute_reply":"2025-07-23T11:34:08.373531Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}