{"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-08-21T09:03:59.359767Z","iopub.execute_input":"2025-08-21T09:03:59.360213Z","iopub.status.idle":"2025-08-21T09:03:59.368377Z","shell.execute_reply.started":"2025-08-21T09:03:59.360189Z","shell.execute_reply":"2025-08-21T09:03:59.367744Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import pandas as pd\nimport numpy as np\nimport torch\nimport torch.nn as nn\nimport torch.optim as optim\nfrom torch.utils.data import Dataset, DataLoader, random_split\nfrom sklearn.metrics import accuracy_score\nimport matplotlib.pyplot as plt\nfrom torchvision import transforms\nfrom torch.optim.lr_scheduler import ReduceLROnPlateau","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-21T09:04:02.095472Z","iopub.execute_input":"2025-08-21T09:04:02.096114Z","iopub.status.idle":"2025-08-21T09:04:02.100321Z","shell.execute_reply.started":"2025-08-21T09:04:02.096091Z","shell.execute_reply":"2025-08-21T09:04:02.099572Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"BATCH_SIZE = 128\nEPOCHS = 30 # Increased epochs for better convergence with augmentation\nLEARNING_RATE = 0.001\nVALIDATION_SPLIT = 0.2","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-21T09:04:36.299354Z","iopub.execute_input":"2025-08-21T09:04:36.300100Z","iopub.status.idle":"2025-08-21T09:04:36.303465Z","shell.execute_reply.started":"2025-08-21T09:04:36.300045Z","shell.execute_reply":"2025-08-21T09:04:36.302804Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"DEVICE = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\nprint(f\"Using device: {DEVICE}\")\nif torch.cuda.is_available():\n    print(f\"Found {torch.cuda.device_count()} GPUs!\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-21T08:38:51.330982Z","iopub.execute_input":"2025-08-21T08:38:51.331565Z","iopub.status.idle":"2025-08-21T08:38:51.460717Z","shell.execute_reply.started":"2025-08-21T08:38:51.331538Z","shell.execute_reply":"2025-08-21T08:38:51.460123Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_transforms = transforms.Compose([\n    transforms.ToPILImage(), # Convert numpy array to PIL Image\n    transforms.RandomRotation(degrees=10),\n    transforms.RandomAffine(degrees=0, translate=(0.1, 0.1), scale=(0.9, 1.1)),\n    transforms.ToTensor(), # Convert back to tensor and normalize to [0, 1]\n])\n\n# --- 3. Modified Custom Dataset ---\nclass DigitDataset(Dataset):\n    def __init__(self, data, labels=None, transform=None):\n        self.data = data\n        self.labels = labels\n        self.transform = transform\n\n    def __len__(self):\n        return len(self.data)\n\n    def __getitem__(self, idx):\n        # Reshape to 28x28, keep as uint8 for PIL transformations\n        image = self.data[idx].reshape(28, 28).astype(np.uint8)\n\n        if self.transform:\n            # Apply augmentations (which includes ToTensor and normalization)\n            image = self.transform(image)\n        else:\n            # For validation/test set: just normalize and convert to tensor\n            image = torch.tensor(image / 255.0, dtype=torch.float32).unsqueeze(0)\n\n        if self.labels is not None:\n            label = torch.tensor(self.labels[idx], dtype=torch.long)\n            return image, label\n        else:\n            return image","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-21T09:05:45.318315Z","iopub.execute_input":"2025-08-21T09:05:45.318588Z","iopub.status.idle":"2025-08-21T09:05:45.325091Z","shell.execute_reply.started":"2025-08-21T09:05:45.318564Z","shell.execute_reply":"2025-08-21T09:05:45.324381Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class DigitRecognizerCNN_V2(nn.Module):\n    def __init__(self):\n        super(DigitRecognizerCNN_V2, self).__init__()\n        self.conv_stack = nn.Sequential(\n            nn.Conv2d(1, 32, kernel_size=3, padding=1), nn.ReLU(), nn.BatchNorm2d(32),\n            nn.Conv2d(32, 32, kernel_size=3, padding=1), nn.ReLU(), nn.BatchNorm2d(32),\n            nn.MaxPool2d(2), nn.Dropout(0.25),\n            nn.Conv2d(32, 64, kernel_size=3, padding=1), nn.ReLU(), nn.BatchNorm2d(64),\n            nn.Conv2d(64, 64, kernel_size=3, padding=1), nn.ReLU(), nn.BatchNorm2d(64),\n            nn.MaxPool2d(2), nn.Dropout(0.25)\n        )\n        self.fc_stack = nn.Sequential(\n            nn.Linear(64 * 7 * 7, 256), nn.ReLU(), nn.BatchNorm1d(256), nn.Dropout(0.5),\n            nn.Linear(256, 10)\n        )\n\n    def forward(self, x):\n        x = self.conv_stack(x)\n        x = x.view(x.size(0), -1)\n        x = self.fc_stack(x)\n        return x","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-21T09:06:04.492679Z","iopub.execute_input":"2025-08-21T09:06:04.493454Z","iopub.status.idle":"2025-08-21T09:06:04.499796Z","shell.execute_reply.started":"2025-08-21T09:06:04.493427Z","shell.execute_reply":"2025-08-21T09:06:04.498998Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from sklearn.model_selection import train_test_split\nif __name__ == \"__main__\":\n    train_df = pd.read_csv(\"/kaggle/input/digit-recognizer/train.csv\")\n    test_df = pd.read_csv(\"/kaggle/input/digit-recognizer/test.csv\")\n\n    X_full = train_df.drop(\"label\", axis=1).values\n    y_full = train_df[\"label\"].values\n    X_test = test_df.values\n\n    # Split data *before* creating Datasets\n    X_train, X_val, y_train, y_val = train_test_split(\n        X_full, y_full, test_size=VALIDATION_SPLIT, random_state=42\n    )\n\n    # Create datasets: apply transforms ONLY to the training set\n    train_dataset = DigitDataset(X_train, y_train, transform=train_transforms)\n    val_dataset = DigitDataset(X_val, y_val, transform=None) # No augmentation for validation\n\n    train_loader = DataLoader(train_dataset, batch_size=BATCH_SIZE, shuffle=True)\n    val_loader = DataLoader(val_dataset, batch_size=BATCH_SIZE, shuffle=False)\n    \n    # Instantiate the new, deeper model\n    model = DigitRecognizerCNN_V2()\n\n    if torch.cuda.device_count() > 1:\n        print(f\"Using {torch.cuda.device_count()} GPUs for Data Parallelism.\")\n        model = nn.DataParallel(model)\n    model.to(DEVICE)\n    \n    criterion = nn.CrossEntropyLoss()\n    optimizer = optim.Adam(model.parameters(), lr=LEARNING_RATE)\n    \n    # --- NEW: Initialize the Learning Rate Scheduler ---\n    scheduler = ReduceLROnPlateau(optimizer, mode='min', factor=0.2, patience=2, verbose=True)\n\n    print(\"\\nStarting training with enhanced model and augmentations...\")\n    for epoch in range(EPOCHS):\n        model.train()\n        running_loss = 0.0\n        for images, labels in train_loader:\n            images, labels = images.to(DEVICE), labels.to(DEVICE)\n            optimizer.zero_grad()\n            outputs = model(images)\n            loss = criterion(outputs, labels)\n            loss.backward()\n            optimizer.step()\n            running_loss += loss.item()\n\n        model.eval()\n        val_loss = 0.0\n        correct, total = 0, 0\n        with torch.no_grad():\n            for images, labels in val_loader:\n                images, labels = images.to(DEVICE), labels.to(DEVICE)\n                outputs = model(images)\n                loss = criterion(outputs, labels)\n                val_loss += loss.item()\n                _, predicted = torch.max(outputs.data, 1)\n                total += labels.size(0)\n                correct += (predicted == labels).sum().item()\n        \n        avg_val_loss = val_loss / len(val_loader)\n        accuracy = 100 * correct / total\n        \n        print(f\"Epoch [{epoch+1}/{EPOCHS}] | Train Loss: {running_loss/len(train_loader):.4f} | \"\n              f\"Val Loss: {avg_val_loss:.4f} | Val Accuracy: {accuracy:.2f}%\")\n\n        # --- NEW: Step the scheduler based on validation loss ---\n        scheduler.step(avg_val_loss)\n\n    print(\"Finished training.\\n\")\n    print(\"Generating submission file...\")\n    \n    test_dataset = DigitDataset(X_test, transform=None)\n    test_loader = DataLoader(test_dataset, batch_size=BATCH_SIZE, shuffle=False)\n    \n    predictions = []\n    model.eval()\n    with torch.no_grad():\n        for images in test_loader:\n            images = images.to(DEVICE)\n            outputs = model(images)\n            _, predicted = torch.max(outputs.data, 1)\n            predictions.extend(predicted.cpu().numpy())\n\n    submission_df = pd.DataFrame({\"ImageId\": range(1, len(predictions) + 1), \"Label\": predictions})\n    submission_df.to_csv(\"submission.csv\", index=False)\n    print(\"submission.csv created successfully!\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-21T09:07:24.973741Z","iopub.execute_input":"2025-08-21T09:07:24.974243Z","iopub.status.idle":"2025-08-21T09:14:21.323562Z","shell.execute_reply.started":"2025-08-21T09:07:24.974219Z","shell.execute_reply":"2025-08-21T09:14:21.322941Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}