{"metadata":{"kernelspec":{"display_name":".venv (3.13.3)","language":"python","name":"python3"},"language_info":{"codemirror_mode":{"name":"ipython","version":3},"file_extension":".py","mimetype":"text/x-python","name":"python","nbconvert_exporter":"python","pygments_lexer":"ipython3","version":"3.13.3"}},"nbformat_minor":4,"nbformat":4,"cells":[{"id":"275cc088","cell_type":"code","source":"import kagglehub\nimport pandas as pd\nimport matplotlib.pyplot as plt\nimport torch\nimport torch.nn as nn\nimport torch.nn.functional as F\nimport torch.optim as optim\nfrom torch.utils.data import DataLoader, TensorDataset\nfrom sklearn.model_selection import train_test_split\nfrom torchvision import transforms","metadata":{},"outputs":[],"execution_count":null},{"id":"96076565","cell_type":"code","source":"print(torch.__version__)","metadata":{},"outputs":[],"execution_count":null},{"id":"00c5a8b5","cell_type":"code","source":"device = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\nprint(f\"Training on: {device}\")","metadata":{},"outputs":[],"execution_count":null},{"id":"9d8fbf70","cell_type":"code","source":"path=\"/kaggle/input/competitions/digit-recognizer\"","metadata":{},"outputs":[],"execution_count":null},{"id":"fd47f31e","cell_type":"code","source":"with open(path + '/train.csv', 'r') as f:\n    train_data = pd.read_csv(f)\ntrain_data.head()","metadata":{},"outputs":[],"execution_count":null},{"id":"5432be41","cell_type":"code","source":"with open(path + '/test.csv', 'r') as f:\n    test_data = pd.read_csv(f)\ntest_data.head()","metadata":{},"outputs":[],"execution_count":null},{"id":"1761b7e0","cell_type":"code","source":"train_transforms = transforms.Compose([\n    transforms.RandomRotation(15),      # Rotate +/- 10 degrees\n    transforms.RandomAffine(0, translate=(0.15, 0.15)), # Shift up/down/left/right by 10%\n    transforms.RandomAffine(0, scale=(0.85, 1.15)),\n    transforms.Lambda(lambda x: x.reshape(-1)) # Flatten back to 784 for the MLP\n])\n\ndef augment_batch(batch_X):\n    # Step A: Reshape flat (784) to 2D (28x28)\n    batch_2d = batch_X.reshape(-1,1,28,28)\n    \n    # Step B: Apply the movement\n    moved_2d = train_transforms(batch_2d)\n    \n    # Step C: Flatten back to (784) for your MLP\n    return moved_2d.reshape(-1, 784)","metadata":{},"outputs":[],"execution_count":null},{"id":"931446b9","cell_type":"code","source":"X=train_data.drop(columns='label')\nX=X/255\n\nX_tensor = torch.tensor(X.values, dtype=torch.float32)\ny_labels = torch.tensor(train_data['label'].values, dtype=torch.long)\n\nX_train, X_val, y_train, y_val = train_test_split(\n    X_tensor, y_labels, test_size=0.2, random_state=42\n)\n\ntrain_dataset = TensorDataset(X_train, y_train)\nval_dataset = TensorDataset(X_val, y_val)\n\ntrain_loader = DataLoader(train_dataset, batch_size=64, shuffle=True,num_workers=2,pin_memory=True)\nval_loader = DataLoader(val_dataset, batch_size=64, shuffle=False)\n\n","metadata":{},"outputs":[],"execution_count":null},{"id":"cea26a2d","cell_type":"code","source":"class Model(nn.Module):\n    def __init__(self, input_size: int, hidden_sizes: list[int], output_size: int):\n        super().__init__()\n        self.layers = nn.ModuleList()\n        self.batch_norms = nn.ModuleList() \n        \n        current_dim = input_size\n        for h_size in hidden_sizes:\n            self.layers.append(nn.Linear(current_dim, h_size))\n            self.batch_norms.append(nn.BatchNorm1d(h_size))\n            current_dim = h_size\n            \n        self.output_layer = nn.Linear(current_dim, output_size)\n\n    def forward(self, x):\n        # We iterate through the hidden layers and their corresponding BN layers\n        for i in range(len(self.layers)):\n            x = self.layers[i](x)\n            x = self.batch_norms[i](x) # Standardizes the activations\n            x = F.relu(x)\n            x = F.dropout(x, p=0.2, training=self.training)\n        return self.output_layer(x)","metadata":{},"outputs":[],"execution_count":null},{"id":"0bbebc16","cell_type":"code","source":"model=Model(len(X.columns), [4096, 2048, 1024], 10).to(device)\n\ncriterion = nn.CrossEntropyLoss()\noptimizer = optim.Adam(model.parameters(), lr=0.001, weight_decay=1e-4)\nscheduler = optim.lr_scheduler.StepLR(optimizer, step_size=20, gamma=0.5)\n\n\n# Updated Training & Validation Loop\ntrain_losses = []\nval_losses = []\n","metadata":{},"outputs":[],"execution_count":null},{"id":"35d18022","cell_type":"code","source":"","metadata":{},"outputs":[],"execution_count":null},{"id":"3656f5fb","cell_type":"code","source":"epochs=250\nfor epoch in range(150,epochs):\n    # --- TRAINING PHASE ---\n    model.train()\n    running_train_loss = 0.0\n    for batch_X, batch_y in train_loader:\n        batch_X, batch_y = batch_X.to(device), batch_y.to(device)\n        \n        batch_X=augment_batch(batch_X)\n\n        optimizer.zero_grad()\n        outputs = model(batch_X)\n        loss = criterion(outputs, batch_y)\n        loss.backward()\n        optimizer.step()\n        running_train_loss += loss.item()\n    \n    avg_train_loss = running_train_loss / len(train_loader)\n    train_losses.append(avg_train_loss)\n\n    # --- VALIDATION PHASE ---\n    model.eval()\n    running_val_loss = 0.0\n    correct = 0\n    total = 0\n    \n    with torch.no_grad(): # Disable gradient tracking\n        for batch_X, batch_y in val_loader:\n            batch_X, batch_y = batch_X.to(device), batch_y.to(device)\n            outputs = model(batch_X)\n            loss = criterion(outputs, batch_y)\n            running_val_loss += loss.item()\n            \n            # Calculate Accuracy\n            _, predicted = torch.max(outputs.data, 1)\n            total += batch_y.size(0)\n            correct += (predicted == batch_y).sum().item()\n            \n    avg_val_loss = running_val_loss / len(val_loader)\n    val_losses.append(avg_val_loss)\n    accuracy = 100 * correct / total\n    \n    # Step scheduler after each epoch\n    scheduler.step()\n\n    if (epoch + 1) % 5 == 0:\n        print(f\"Epoch [{epoch+1}/{epochs}] | Train Loss: {avg_train_loss:.4f} | Val Loss: {avg_val_loss:.4f} | Val Acc: {accuracy:.2f}%\")\n\n","metadata":{},"outputs":[],"execution_count":null},{"id":"011c8586","cell_type":"code","source":"\n# Plotting both\nplt.plot(train_losses, label='Train Loss')\nplt.plot(val_losses, label='Val Loss')\nplt.legend()\nplt.show()","metadata":{},"outputs":[],"execution_count":null},{"id":"bbe1b180","cell_type":"code","source":"model.eval()\nX=test_data\nX=X/255\nX_tensor=torch.tensor(X.values, dtype=torch.float32).to(device)\ny=model(X_tensor)\npred=torch.argmax(y, 1)\nsubmission=pd.DataFrame({'ImageId':range(1,len(pred)+1), 'Label':pred.cpu()})","metadata":{},"outputs":[],"execution_count":null},{"id":"9b25c888","cell_type":"code","source":"# Save the 'state_dict' (the weights)\ntorch.save(model.state_dict(), 'mnist_mlp_model.pth')\ncheckpoint = {\n    'epoch': epoch,\n    'model_state_dict': model.state_dict(),\n    'optimizer_state_dict': optimizer.state_dict(),\n    'loss': loss,\n}\ntorch.save(checkpoint, 'checkpoint.pth')","metadata":{},"outputs":[],"execution_count":null},{"id":"656e8ea1","cell_type":"code","source":"submission.to_csv('submission.csv', index=False)","metadata":{},"outputs":[],"execution_count":null}]}