{"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":"competition","sourceId":13836,"databundleVersionId":1718836,"isSourceIdPinned":false}],"dockerImageVersionId":31328,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"!pip install -q timm","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-13T05:49:18.222064Z","iopub.execute_input":"2026-04-13T05:49:18.222342Z","iopub.status.idle":"2026-04-13T05:49:23.795635Z","shell.execute_reply.started":"2026-04-13T05:49:18.222313Z","shell.execute_reply":"2026-04-13T05:49:23.794896Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# =========================\n# 2. IMPORT LIBRARIES\n# =========================\nimport os\nimport random\nimport numpy as np\nimport pandas as pd\n\nimport torch\nimport torch.nn as nn\nfrom torch.utils.data import Dataset, DataLoader, WeightedRandomSampler\n\nfrom sklearn.model_selection import train_test_split\nfrom sklearn.utils.class_weight import compute_class_weight\n\nimport torchvision.transforms as transforms\nfrom PIL import Image\n\nimport timm\nfrom tqdm import tqdm","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-13T05:49:33.283675Z","iopub.execute_input":"2026-04-13T05:49:33.284298Z","iopub.status.idle":"2026-04-13T05:49:51.55837Z","shell.execute_reply.started":"2026-04-13T05:49:33.284262Z","shell.execute_reply":"2026-04-13T05:49:51.557745Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# =========================\n# 3. CONFIG\n# =========================\nRANDOM_SEED = 42\nBATCH_SIZE = 32\nIMG_SIZE = 224\nEPOCHS = 8\nLR = 3e-4\n\nDATA_DIR = \"/kaggle/input/competitions/cassava-leaf-disease-classification\"\nTRAIN_IMAGES = os.path.join(DATA_DIR, \"train_images\")\nCSV_PATH = os.path.join(DATA_DIR, \"train.csv\")\n\nDEVICE = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-13T05:50:00.203257Z","iopub.execute_input":"2026-04-13T05:50:00.203835Z","iopub.status.idle":"2026-04-13T05:50:00.475369Z","shell.execute_reply.started":"2026-04-13T05:50:00.203791Z","shell.execute_reply":"2026-04-13T05:50:00.474692Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# =========================\n# 4. REPRODUCIBILITY\n# =========================\ndef set_seed(seed):\n    random.seed(seed)\n    np.random.seed(seed)\n    torch.manual_seed(seed)\n    torch.cuda.manual_seed_all(seed)\n\nset_seed(RANDOM_SEED)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-13T05:50:09.108124Z","iopub.execute_input":"2026-04-13T05:50:09.108413Z","iopub.status.idle":"2026-04-13T05:50:09.122231Z","shell.execute_reply.started":"2026-04-13T05:50:09.108385Z","shell.execute_reply":"2026-04-13T05:50:09.121499Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# 5. LOAD DATA\n# =========================\ndf = pd.read_csv(CSV_PATH)\nprint(df.head())\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-13T05:50:14.373292Z","iopub.execute_input":"2026-04-13T05:50:14.373906Z","iopub.status.idle":"2026-04-13T05:50:14.430454Z","shell.execute_reply.started":"2026-04-13T05:50:14.37387Z","shell.execute_reply":"2026-04-13T05:50:14.429819Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"#train\ntrain_df, temp_df = train_test_split(\n    df,\n    test_size=0.30,\n    random_state=RANDOM_SEED,\n    stratify=df[\"label\"]\n)\n\nval_df, test_df = train_test_split(\n    temp_df,\n    test_size=0.5,\n    random_state=RANDOM_SEED,\n    stratify=temp_df[\"label\"]\n)\n\nprint(len(train_df), len(val_df), len(test_df))\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-13T05:50:19.073352Z","iopub.execute_input":"2026-04-13T05:50:19.073898Z","iopub.status.idle":"2026-04-13T05:50:19.106805Z","shell.execute_reply.started":"2026-04-13T05:50:19.073866Z","shell.execute_reply":"2026-04-13T05:50:19.106051Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# =========================\n# 7. DATASET CLASS\n# =========================\nclass CassavaDataset(Dataset):\n    def __init__(self, dataframe, img_dir, transform=None):\n        self.df = dataframe.reset_index(drop=True)\n        self.img_dir = img_dir\n        self.transform = transform\n\n    def __len__(self):\n        return len(self.df)\n\n    def __getitem__(self, idx):\n        img_name = self.df.loc[idx, \"image_id\"]\n        label = self.df.loc[idx, \"label\"]\n\n        img_path = os.path.join(self.img_dir, img_name)\n        image = Image.open(img_path).convert(\"RGB\")\n\n        if self.transform:\n            image = self.transform(image)\n\n        return image, label","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-13T05:50:30.098335Z","iopub.execute_input":"2026-04-13T05:50:30.098663Z","iopub.status.idle":"2026-04-13T05:50:30.103685Z","shell.execute_reply.started":"2026-04-13T05:50:30.098632Z","shell.execute_reply":"2026-04-13T05:50:30.103044Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# =========================\n# 8. TRANSFORMS\n# =========================\ntrain_tfms = transforms.Compose([\n    transforms.Resize((IMG_SIZE, IMG_SIZE)),\n    transforms.RandomHorizontalFlip(),\n    transforms.RandomRotation(15),\n    transforms.ColorJitter(brightness=0.2, contrast=0.2),\n    transforms.ToTensor(),\n    transforms.Normalize([0.485, 0.456, 0.406],\n                         [0.229, 0.224, 0.225])\n])\n\nval_tfms = transforms.Compose([\n    transforms.Resize((IMG_SIZE, IMG_SIZE)),\n    transforms.ToTensor(),\n    transforms.Normalize([0.485, 0.456, 0.406],\n                         [0.229, 0.224, 0.225])\n])","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-13T05:50:35.353297Z","iopub.execute_input":"2026-04-13T05:50:35.353883Z","iopub.status.idle":"2026-04-13T05:50:35.359564Z","shell.execute_reply.started":"2026-04-13T05:50:35.353847Z","shell.execute_reply":"2026-04-13T05:50:35.358613Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# =========================\n# 9. DATASETS\n# =========================\ntrain_dataset = CassavaDataset(train_df, TRAIN_IMAGES, train_tfms)\nval_dataset = CassavaDataset(val_df, TRAIN_IMAGES, val_tfms)\ntest_dataset = CassavaDataset(test_df, TRAIN_IMAGES, val_tfms)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-13T05:50:42.573895Z","iopub.execute_input":"2026-04-13T05:50:42.574444Z","iopub.status.idle":"2026-04-13T05:50:42.57936Z","shell.execute_reply.started":"2026-04-13T05:50:42.574411Z","shell.execute_reply":"2026-04-13T05:50:42.578465Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# =========================\n# 10. CLASS IMBALANCE HANDLING\n# =========================\nclass_counts = train_df[\"label\"].value_counts().sort_index()\nclass_weights = 1.0 / class_counts.values\nsample_weights = train_df[\"label\"].map(lambda x: class_weights[x]).values\n\nsampler = WeightedRandomSampler(\n    weights=sample_weights,\n    num_samples=len(sample_weights),\n    replacement=True\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-13T05:50:48.128177Z","iopub.execute_input":"2026-04-13T05:50:48.128907Z","iopub.status.idle":"2026-04-13T05:50:48.164013Z","shell.execute_reply.started":"2026-04-13T05:50:48.128874Z","shell.execute_reply":"2026-04-13T05:50:48.163505Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# =========================\n# 11. DATALOADERS\n# =========================\ntrain_loader = DataLoader(train_dataset, batch_size=BATCH_SIZE, sampler=sampler)\nval_loader = DataLoader(val_dataset, batch_size=BATCH_SIZE, shuffle=False)\ntest_loader = DataLoader(test_dataset, batch_size=BATCH_SIZE, shuffle=False)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-13T05:50:52.268184Z","iopub.execute_input":"2026-04-13T05:50:52.268513Z","iopub.status.idle":"2026-04-13T05:50:52.272935Z","shell.execute_reply.started":"2026-04-13T05:50:52.268462Z","shell.execute_reply":"2026-04-13T05:50:52.272211Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from torchvision.models import efficientnet_b0\n\nmodel = efficientnet_b0(weights=\"DEFAULT\")\nmodel.classifier[1] = torch.nn.Linear(model.classifier[1].in_features, 5)\nmodel = model.to(DEVICE)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-13T05:50:55.347177Z","iopub.execute_input":"2026-04-13T05:50:55.347785Z","iopub.status.idle":"2026-04-13T05:50:56.174258Z","shell.execute_reply.started":"2026-04-13T05:50:55.347753Z","shell.execute_reply":"2026-04-13T05:50:56.173627Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# =========================\n# 13. LOSS + OPTIMIZER\n# =========================\ncriterion = nn.CrossEntropyLoss()\noptimizer = torch.optim.AdamW(model.parameters(), lr=LR)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-13T05:51:01.337145Z","iopub.execute_input":"2026-04-13T05:51:01.337679Z","iopub.status.idle":"2026-04-13T05:51:01.342458Z","shell.execute_reply.started":"2026-04-13T05:51:01.337648Z","shell.execute_reply":"2026-04-13T05:51:01.341868Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# =========================\n# 14. TRAINING LOOP\n# =========================\ndef train_one_epoch(model, loader):\n    model.train()\n    total_loss, correct = 0, 0\n\n    for images, labels in tqdm(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\n        loss.backward()\n        optimizer.step()\n\n        total_loss += loss.item()\n        preds = torch.argmax(outputs, 1)\n        correct += (preds == labels).sum().item()\n\n    return total_loss / len(loader), correct / len(loader.dataset)\n\n\ndef evaluate(model, loader):\n    model.eval()\n    total_loss, correct = 0, 0\n\n    with torch.no_grad():\n        for images, labels in loader:\n            images, labels = images.to(DEVICE), labels.to(DEVICE)\n\n            outputs = model(images)\n            loss = criterion(outputs, labels)\n\n            total_loss += loss.item()\n            preds = torch.argmax(outputs, 1)\n            correct += (preds == labels).sum().item()\n\n    return total_loss / len(loader), correct / len(loader.dataset)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-13T05:51:17.012243Z","iopub.execute_input":"2026-04-13T05:51:17.012616Z","iopub.status.idle":"2026-04-13T05:51:17.019622Z","shell.execute_reply.started":"2026-04-13T05:51:17.012584Z","shell.execute_reply":"2026-04-13T05:51:17.018944Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# =========================\n# 15. TRAIN MODEL\n# =========================\nbest_acc = 0\n\nfor epoch in range(EPOCHS):\n    train_loss, train_acc = train_one_epoch(model, train_loader)\n    val_loss, val_acc = evaluate(model, val_loader)\n\n    print(f\"\\nEpoch {epoch+1}/{EPOCHS}\")\n    print(f\"Train Loss: {train_loss:.4f}, Train Acc: {train_acc:.4f}\")\n    print(f\"Val Loss: {val_loss:.4f}, Val Acc: {val_acc:.4f}\")\n\n    if val_acc > best_acc:\n        best_acc = val_acc\n        torch.save(model.state_dict(), \"best_model.pth\")\n        print(\"💾 Saved Best Model!\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-13T05:51:27.137399Z","iopub.execute_input":"2026-04-13T05:51:27.137725Z","iopub.status.idle":"2026-04-13T06:25:48.681573Z","shell.execute_reply.started":"2026-04-13T05:51:27.137695Z","shell.execute_reply":"2026-04-13T06:25:48.680768Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# =========================\n# 16. TEST EVALUATION\n# =========================\nmodel.load_state_dict(torch.load(\"best_model.pth\"))\ntest_loss, test_acc = evaluate(model, test_loader)\n\nprint(\"\\nFINAL TEST RESULTS\")\nprint(f\"Test Loss: {test_loss:.4f}, Test Acc: {test_acc:.4f}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-13T06:28:17.317751Z","iopub.execute_input":"2026-04-13T06:28:17.318069Z","iopub.status.idle":"2026-04-13T06:29:06.562975Z","shell.execute_reply.started":"2026-04-13T06:28:17.318038Z","shell.execute_reply":"2026-04-13T06:29:06.562313Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# =========================\n# 17. KAGGLE SUBMISSION\n# =========================\n\nimport pandas as pd\nimport torch\n\nmodel.eval()\n\n# Load test images from competition test set\nTEST_DIR = os.path.join(DATA_DIR, \"test_images\")\n\ntest_images = os.listdir(TEST_DIR)\n\npredictions = []\n\nwith torch.no_grad():\n    for img_name in test_images:\n        img_path = os.path.join(TEST_DIR, img_name)\n\n        image = Image.open(img_path).convert(\"RGB\")\n        image = val_tfms(image).unsqueeze(0).to(DEVICE)\n\n        outputs = model(image)\n        pred = torch.argmax(outputs, dim=1).item()\n\n        predictions.append(pred)\n\n# Create submission dataframe\nsubmission = pd.DataFrame({\n    \"image_id\": test_images,\n    \"label\": predictions\n})\n\n# Save file\nsubmission.to_csv(\"submission.csv\", index=False)\n\nprint(\"✅ Submission file created: submission.csv\")\nprint(submission.head())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-13T06:30:08.808961Z","iopub.execute_input":"2026-04-13T06:30:08.809257Z","iopub.status.idle":"2026-04-13T06:30:08.860626Z","shell.execute_reply.started":"2026-04-13T06:30:08.809228Z","shell.execute_reply":"2026-04-13T06:30:08.860002Z"}},"outputs":[],"execution_count":null}]}