{"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":"nvidiaTeslaT4","dataSources":[{"sourceType":"datasetVersion","sourceId":4749004,"datasetId":2733586,"databundleVersionId":4812102}],"dockerImageVersionId":31329,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"# =============================\n# 1. IMPORTS\n# =============================\nimport torch\nimport torch.nn as nn\nimport torch.optim as optim\nfrom torchvision import datasets, transforms, models\nfrom torch.utils.data import DataLoader, random_split\nfrom PIL import Image\nimport pandas as pd\nimport os\nimport matplotlib.pyplot as plt\n\ndevice = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\n\n# =============================\n# 2. PATHS\n# =============================\nBASE_DIR = \"/kaggle/input/datasets/samuelcortinhas/muffin-vs-chihuahua-image-classification\"\nTRAIN_DIR = os.path.join(BASE_DIR, \"train\")\nTEST_DIR  = os.path.join(BASE_DIR, \"test\")\n\n# =============================\n# 3. TRANSFORMS (ANTI-OVERFITTING)\n# =============================\ntrain_transform = transforms.Compose([\n    transforms.Resize((224, 224)),\n    transforms.RandomHorizontalFlip(),\n    transforms.RandomRotation(15),\n    transforms.RandomResizedCrop(224, scale=(0.8, 1.0)),\n    transforms.ColorJitter(0.2,0.2,0.2),\n    transforms.ToTensor(),\n    transforms.Normalize([0.485,0.456,0.406],[0.229,0.224,0.225])\n])\n\nval_transform = transforms.Compose([\n    transforms.Resize((224, 224)),\n    transforms.ToTensor(),\n    transforms.Normalize([0.485,0.456,0.406],[0.229,0.224,0.225])\n])\n\n# =============================\n# 4. DATA\n# =============================\nfull_dataset = datasets.ImageFolder(TRAIN_DIR, transform=train_transform)\n\ntrain_size = int(0.8 * len(full_dataset))\nval_size = len(full_dataset) - train_size\n\ntrain_data, val_data = random_split(full_dataset, [train_size, val_size])\nval_data.dataset.transform = val_transform\n\ntrain_loader = DataLoader(train_data, batch_size=32, shuffle=True)\nval_loader = DataLoader(val_data, batch_size=32)\n\nclass_names = full_dataset.classes\nprint(\"Classes:\", class_names)\n\n# =============================\n# 5. MODEL (EfficientNet + Dropout)\n# =============================\nmodel = models.efficientnet_b0(pretrained=True)\n\n# Freeze base\nfor param in model.features.parameters():\n    param.requires_grad = False\n\nmodel.classifier = nn.Sequential(\n    nn.Dropout(0.4),\n    nn.Linear(model.classifier[1].in_features, 2)\n)\n\nmodel = model.to(device)\n\n# =============================\n# 6. TRAINING\n# =============================\ncriterion = nn.CrossEntropyLoss()\noptimizer = optim.Adam(model.parameters(), lr=1e-4, weight_decay=1e-4)\n\nepochs = 8\nbest_acc = 0\npatience = 3\ncounter = 0\n\ntrain_acc_list = []\nval_acc_list = []\n\nfor epoch in range(epochs):\n    # ---- TRAIN ----\n    model.train()\n    correct, total = 0, 0\n\n    for images, labels in train_loader:\n        images, labels = images.to(device), labels.to(device)\n\n        optimizer.zero_grad()\n        outputs = model(images)\n        loss = criterion(outputs, labels)\n        loss.backward()\n        optimizer.step()\n\n        _, pred = torch.max(outputs, 1)\n        total += labels.size(0)\n        correct += (pred == labels).sum().item()\n\n    train_acc = 100 * correct / total\n    train_acc_list.append(train_acc)\n\n    # ---- VALIDATION ----\n    model.eval()\n    correct, total = 0, 0\n\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\n            _, pred = torch.max(outputs, 1)\n            total += labels.size(0)\n            correct += (pred == labels).sum().item()\n\n    val_acc = 100 * correct / total\n    val_acc_list.append(val_acc)\n\n    print(f\"Epoch {epoch+1} | Train: {train_acc:.2f}% | Val: {val_acc:.2f}%\")\n\n    # Early stopping\n    if val_acc > best_acc:\n        best_acc = val_acc\n        counter = 0\n        torch.save(model.state_dict(), \"/kaggle/working/best_model.pth\")\n    else:\n        counter += 1\n\n    if counter >= patience:\n        print(\"⛔ Early stopping\")\n        break\n\n# =============================\n# 7. PLOT GRAPH\n# =============================\nplt.figure()\nplt.plot(train_acc_list, label=\"Train Accuracy\")\nplt.plot(val_acc_list, label=\"Validation Accuracy\")\nplt.xlabel(\"Epochs\")\nplt.ylabel(\"Accuracy\")\nplt.title(\"Training vs Validation\")\nplt.legend()\nplt.show()\n\n# =============================\n# 8. LOAD BEST MODEL\n# =============================\nmodel.load_state_dict(torch.load(\"/kaggle/working/best_model.pth\"))\nmodel.eval()\n\n# =============================\n# 9. TEST PREDICTION\n# =============================\ntest_files = sorted([f for f in os.listdir(TEST_DIR) \n                     if f.lower().endswith(('.jpg','.jpeg','.png'))])\n\nresults = []\n\nfor img_name in test_files:\n    img_path = os.path.join(TEST_DIR, img_name)\n\n    img = Image.open(img_path).convert(\"RGB\")\n    img = val_transform(img).unsqueeze(0).to(device)\n\n    with torch.no_grad():\n        output = model(img)\n        _, pred = torch.max(output, 1)\n\n    label = class_names[pred.item()]\n    results.append([img_name, label])\n\n# =============================\n# 10. SUBMISSION\n# =============================\nsubmission = pd.DataFrame(results, columns=[\"ID\", \"Predict\"])\nsubmission.to_csv(\"/kaggle/working/submission.csv\", index=False)\n\nprint(\"✅ submission.csv created!\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-19T18:18:57.125410Z","iopub.execute_input":"2026-04-19T18:18:57.125701Z","iopub.status.idle":"2026-04-19T18:26:30.768194Z","shell.execute_reply.started":"2026-04-19T18:18:57.125676Z","shell.execute_reply":"2026-04-19T18:26:30.767635Z"}},"outputs":[],"execution_count":null}]}