{"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},{"sourceType":"modelInstanceVersion","sourceId":829122,"databundleVersionId":16637790,"modelInstanceId":630554,"modelId":642465,"isSourceIdPinned":false}],"dockerImageVersionId":31329,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"# Imports","metadata":{}},{"cell_type":"code","source":"import os\nimport gc\nimport json\nimport random\nimport numpy as np\nimport pandas as pd\nfrom PIL import Image\nfrom tqdm.auto import tqdm\n\nfrom sklearn.model_selection import train_test_split\nfrom sklearn.metrics import accuracy_score\n\nimport torch\nimport torch.nn as nn\nimport torch.optim as optim\nfrom torch.utils.data import Dataset, DataLoader\n\nimport torchvision\nfrom torchvision import transforms\nfrom torchvision.models import resnet50, ResNet50_Weights","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2026-04-12T20:39:19.109414Z","iopub.execute_input":"2026-04-12T20:39:19.110015Z","iopub.status.idle":"2026-04-12T20:39:19.114522Z","shell.execute_reply.started":"2026-04-12T20:39:19.109985Z","shell.execute_reply":"2026-04-12T20:39:19.113808Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Config","metadata":{}},{"cell_type":"code","source":"class CFG:\n    # Paths\n    COMP_DIR = \"/kaggle/input/competitions/cassava-leaf-disease-classification\"\n    TRAIN_CSV = os.path.join(COMP_DIR, \"train.csv\")\n    SAMPLE_SUB_CSV = os.path.join(COMP_DIR, \"sample_submission.csv\")\n    TRAIN_DIR = os.path.join(COMP_DIR, \"train_images\")\n    TEST_DIR = os.path.join(COMP_DIR, \"test_images\")\n    LABEL_MAP_JSON = os.path.join(COMP_DIR, \"label_num_to_disease_map.json\")\n\n    # General\n    SEED = 42\n    NUM_CLASSES = 5\n    IMG_SIZE = 224\n    BATCH_SIZE = 32\n    NUM_WORKERS = 4\n    EPOCHS = 10\n\n    # Optimization\n    LR = 1e-4\n    WEIGHT_DECAY = 1e-4\n    LABEL_SMOOTHING = 0.1\n\n    # Device\n    DEVICE = \"cuda\" if torch.cuda.is_available() else \"cpu\"\n\n    # Saving\n    MODEL_PATH = \"/kaggle/working/best_resnet50_cassava.pth\"\n    SUB_PATH = \"/kaggle/working/submission.csv\"\n\nprint(\"Device:\", CFG.DEVICE)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-12T20:39:19.115959Z","iopub.execute_input":"2026-04-12T20:39:19.116297Z","iopub.status.idle":"2026-04-12T20:39:19.129968Z","shell.execute_reply.started":"2026-04-12T20:39:19.116272Z","shell.execute_reply":"2026-04-12T20:39:19.129278Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Seed everything","metadata":{}},{"cell_type":"code","source":"def set_seed(seed=42):\n    random.seed(seed)\n    np.random.seed(seed)\n    torch.manual_seed(seed)\n    torch.cuda.manual_seed_all(seed)\n\n    os.environ[\"PYTHONHASHSEED\"] = str(seed)\n\n    torch.backends.cudnn.deterministic = True\n    torch.backends.cudnn.benchmark = False\n\nset_seed(CFG.SEED)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-12T20:39:19.130888Z","iopub.execute_input":"2026-04-12T20:39:19.131144Z","iopub.status.idle":"2026-04-12T20:39:19.144989Z","shell.execute_reply.started":"2026-04-12T20:39:19.131122Z","shell.execute_reply":"2026-04-12T20:39:19.144256Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Load data","metadata":{}},{"cell_type":"code","source":"train_df = pd.read_csv(CFG.TRAIN_CSV)\nsample_sub = pd.read_csv(CFG.SAMPLE_SUB_CSV)\n\nprint(\"Train shape:\", train_df.shape)\nprint(train_df.head())\n\nwith open(CFG.LABEL_MAP_JSON, \"r\") as f:\n    label_map = json.load(f)\n\nprint(\"Label map:\", label_map)\nprint(\"\\nClass counts:\")\nprint(train_df[\"label\"].value_counts().sort_index())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-12T20:39:19.145935Z","iopub.execute_input":"2026-04-12T20:39:19.146563Z","iopub.status.idle":"2026-04-12T20:39:19.177974Z","shell.execute_reply.started":"2026-04-12T20:39:19.146537Z","shell.execute_reply":"2026-04-12T20:39:19.177284Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Train/Validation split","metadata":{}},{"cell_type":"code","source":"train_df[\"image_path\"] = train_df[\"image_id\"].apply(lambda x: os.path.join(CFG.TRAIN_DIR, x))\n\ntrain_fold, valid_fold = train_test_split(\n    train_df,\n    test_size=0.2,\n    stratify=train_df[\"label\"],\n    random_state=CFG.SEED\n)\n\ntrain_fold = train_fold.reset_index(drop=True)\nvalid_fold = valid_fold.reset_index(drop=True)\n\nprint(\"Train split:\", train_fold.shape)\nprint(\"Valid split:\", valid_fold.shape)\n\nprint(\"\\nTrain label distribution:\")\nprint(train_fold[\"label\"].value_counts(normalize=True).sort_index())\n\nprint(\"\\nValid label distribution:\")\nprint(valid_fold[\"label\"].value_counts(normalize=True).sort_index())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-12T20:39:19.179491Z","iopub.execute_input":"2026-04-12T20:39:19.179709Z","iopub.status.idle":"2026-04-12T20:39:19.223052Z","shell.execute_reply.started":"2026-04-12T20:39:19.179687Z","shell.execute_reply":"2026-04-12T20:39:19.222471Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Transforms","metadata":{}},{"cell_type":"code","source":"train_transforms = transforms.Compose([\n    transforms.Resize((CFG.IMG_SIZE + 32, CFG.IMG_SIZE + 32)),\n    transforms.RandomResizedCrop(CFG.IMG_SIZE, scale=(0.8, 1.0)),\n    transforms.RandomHorizontalFlip(p=0.5),\n    transforms.RandomVerticalFlip(p=0.3),\n    transforms.RandomRotation(degrees=20),\n    transforms.ColorJitter(brightness=0.2, contrast=0.2, saturation=0.2, hue=0.08),\n    transforms.ToTensor(),\n    transforms.Normalize(\n        mean=[0.485, 0.456, 0.406],\n        std=[0.229, 0.224, 0.225]\n    ),\n    transforms.RandomErasing(p=0.25, scale=(0.02, 0.15), ratio=(0.3, 3.3), value='random')\n])\n\nvalid_transforms = transforms.Compose([\n    transforms.Resize((CFG.IMG_SIZE, CFG.IMG_SIZE)),\n    transforms.ToTensor(),\n    transforms.Normalize(\n        mean=[0.485, 0.456, 0.406],\n        std=[0.229, 0.224, 0.225]\n    )\n])","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-12T20:39:19.223892Z","iopub.execute_input":"2026-04-12T20:39:19.224120Z","iopub.status.idle":"2026-04-12T20:39:19.230129Z","shell.execute_reply.started":"2026-04-12T20:39:19.224099Z","shell.execute_reply":"2026-04-12T20:39:19.229369Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Dataset class","metadata":{}},{"cell_type":"code","source":"class CassavaDataset(Dataset):\n    def __init__(self, df=None, img_dir=None, transforms=None, is_test=False):\n        self.df = df\n        self.img_dir = img_dir\n        self.transforms = transforms\n        self.is_test = is_test\n\n        if self.is_test:\n            self.image_ids = sorted(os.listdir(img_dir))\n        else:\n            self.image_paths = df[\"image_path\"].values\n            self.labels = df[\"label\"].values\n\n    def __len__(self):\n        if self.is_test:\n            return len(self.image_ids)\n        return len(self.df)\n\n    def __getitem__(self, idx):\n        if self.is_test:\n            image_id = self.image_ids[idx]\n            image_path = os.path.join(self.img_dir, image_id)\n            image = Image.open(image_path).convert(\"RGB\")\n\n            if self.transforms:\n                image = self.transforms(image)\n\n            return image, image_id\n\n        image_path = self.image_paths[idx]\n        label = self.labels[idx]\n\n        image = Image.open(image_path).convert(\"RGB\")\n\n        if self.transforms:\n            image = self.transforms(image)\n\n        return image, torch.tensor(label, dtype=torch.long)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-12T20:39:19.231086Z","iopub.execute_input":"2026-04-12T20:39:19.231457Z","iopub.status.idle":"2026-04-12T20:39:19.241214Z","shell.execute_reply.started":"2026-04-12T20:39:19.231423Z","shell.execute_reply":"2026-04-12T20:39:19.240629Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# DataLoaders","metadata":{}},{"cell_type":"code","source":"train_dataset = CassavaDataset(\n    df=train_fold,\n    transforms=train_transforms,\n    is_test=False\n)\n\nvalid_dataset = CassavaDataset(\n    df=valid_fold,\n    transforms=valid_transforms,\n    is_test=False\n)\n\ntest_dataset = CassavaDataset(\n    img_dir=CFG.TEST_DIR,\n    transforms=valid_transforms,\n    is_test=True\n)\n\ntrain_loader = DataLoader(\n    train_dataset,\n    batch_size=CFG.BATCH_SIZE,\n    shuffle=True,\n    num_workers=CFG.NUM_WORKERS,\n    pin_memory=True\n)\n\nvalid_loader = DataLoader(\n    valid_dataset,\n    batch_size=CFG.BATCH_SIZE,\n    shuffle=False,\n    num_workers=CFG.NUM_WORKERS,\n    pin_memory=True\n)\n\ntest_loader = DataLoader(\n    test_dataset,\n    batch_size=CFG.BATCH_SIZE,\n    shuffle=False,\n    num_workers=CFG.NUM_WORKERS,\n    pin_memory=True\n)\n\nprint(\"Train batches:\", len(train_loader))\nprint(\"Valid batches:\", len(valid_loader))\nprint(\"Test batches:\", len(test_loader))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-12T20:39:19.242007Z","iopub.execute_input":"2026-04-12T20:39:19.242294Z","iopub.status.idle":"2026-04-12T20:39:19.260341Z","shell.execute_reply.started":"2026-04-12T20:39:19.242260Z","shell.execute_reply":"2026-04-12T20:39:19.259745Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Build ResNet50 model","metadata":{}},{"cell_type":"code","source":"def build_model(num_classes=5):\n    model = resnet50(weights=None)  # ✅ no internet\n    in_features = model.fc.in_features\n    model.fc = nn.Linear(in_features, num_classes)\n    return model\n\nmodel = build_model(CFG.NUM_CLASSES).to(CFG.DEVICE)\n\n# ✅ THIS is where you use your uploaded path\nckpt = torch.load(\"/kaggle/input/models/mizeroluckygall/resnet50-cassava/pytorch/default/1/best_resnet50_cassava.pth\", map_location=CFG.DEVICE)\nmodel.load_state_dict(ckpt)\n\nprint(\"Model loaded from checkpoint.\")\nprint(model.fc)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-12T20:39:19.261953Z","iopub.execute_input":"2026-04-12T20:39:19.262222Z","iopub.status.idle":"2026-04-12T20:39:21.778208Z","shell.execute_reply.started":"2026-04-12T20:39:19.262199Z","shell.execute_reply":"2026-04-12T20:39:21.777491Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Loss, optimizer, scheduler","metadata":{}},{"cell_type":"code","source":"criterion = nn.CrossEntropyLoss(label_smoothing=CFG.LABEL_SMOOTHING)\n\noptimizer = optim.AdamW(\n    model.parameters(),\n    lr=CFG.LR,\n    weight_decay=CFG.WEIGHT_DECAY\n)\n\nscheduler = optim.lr_scheduler.CosineAnnealingLR(\n    optimizer,\n    T_max=CFG.EPOCHS\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-12T20:39:21.779036Z","iopub.execute_input":"2026-04-12T20:39:21.779346Z","iopub.status.idle":"2026-04-12T20:39:21.784049Z","shell.execute_reply.started":"2026-04-12T20:39:21.779310Z","shell.execute_reply":"2026-04-12T20:39:21.783450Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Training and validation functions","metadata":{}},{"cell_type":"code","source":"def train_one_epoch(model, loader, optimizer, criterion, device):\n    model.train()\n\n    running_loss = 0.0\n    all_preds = []\n    all_labels = []\n\n    pbar = tqdm(loader, total=len(loader), desc=\"Training\", leave=False)\n\n    for images, labels in pbar:\n        images = images.to(device, non_blocking=True)\n        labels = labels.to(device, non_blocking=True)\n\n        optimizer.zero_grad()\n\n        outputs = model(images)\n        loss = criterion(outputs, labels)\n\n        loss.backward()\n        optimizer.step()\n\n        running_loss += loss.item() * images.size(0)\n\n        preds = outputs.argmax(dim=1)\n        all_preds.extend(preds.detach().cpu().numpy())\n        all_labels.extend(labels.detach().cpu().numpy())\n\n        pbar.set_postfix(loss=loss.item())\n\n    epoch_loss = running_loss / len(loader.dataset)\n    epoch_acc = accuracy_score(all_labels, all_preds)\n\n    return epoch_loss, epoch_acc\n\n\n@torch.no_grad()\ndef valid_one_epoch(model, loader, criterion, device):\n    model.eval()\n\n    running_loss = 0.0\n    all_preds = []\n    all_labels = []\n\n    pbar = tqdm(loader, total=len(loader), desc=\"Validation\", leave=False)\n\n    for images, labels in pbar:\n        images = images.to(device, non_blocking=True)\n        labels = labels.to(device, non_blocking=True)\n\n        outputs = model(images)\n        loss = criterion(outputs, labels)\n\n        running_loss += loss.item() * images.size(0)\n\n        preds = outputs.argmax(dim=1)\n        all_preds.extend(preds.detach().cpu().numpy())\n        all_labels.extend(labels.detach().cpu().numpy())\n\n        pbar.set_postfix(loss=loss.item())\n\n    epoch_loss = running_loss / len(loader.dataset)\n    epoch_acc = accuracy_score(all_labels, all_preds)\n\n    return epoch_loss, epoch_acc","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-12T20:39:21.785026Z","iopub.execute_input":"2026-04-12T20:39:21.785558Z","iopub.status.idle":"2026-04-12T20:39:21.798965Z","shell.execute_reply.started":"2026-04-12T20:39:21.785534Z","shell.execute_reply":"2026-04-12T20:39:21.798249Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Train loop","metadata":{}},{"cell_type":"code","source":"best_val_acc = 0.0\nhistory = []\n\nfor epoch in range(CFG.EPOCHS):\n    print(f\"\\nEpoch [{epoch + 1}/{CFG.EPOCHS}]\")\n\n    train_loss, train_acc = train_one_epoch(\n        model, train_loader, optimizer, criterion, CFG.DEVICE\n    )\n\n    val_loss, val_acc = valid_one_epoch(\n        model, valid_loader, criterion, CFG.DEVICE\n    )\n\n    scheduler.step()\n\n    history.append({\n        \"epoch\": epoch + 1,\n        \"train_loss\": train_loss,\n        \"train_acc\": train_acc,\n        \"val_loss\": val_loss,\n        \"val_acc\": val_acc,\n        \"lr\": optimizer.param_groups[0][\"lr\"]\n    })\n\n    print(f\"Train Loss: {train_loss:.4f} | Train Acc: {train_acc:.4f}\")\n    print(f\"Valid Loss: {val_loss:.4f} | Valid Acc: {val_acc:.4f}\")\n    print(f\"LR: {optimizer.param_groups[0]['lr']:.6f}\")\n\n    if val_acc > best_val_acc:\n        best_val_acc = val_acc\n        torch.save(model.state_dict(), CFG.MODEL_PATH)\n        print(f\"Best model saved to: {CFG.MODEL_PATH}\")\n\nprint(f\"\\nBest Validation Accuracy: {best_val_acc:.4f}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-12T20:39:21.800362Z","iopub.execute_input":"2026-04-12T20:39:21.800773Z","execution_failed":"2026-04-12T20:39:40.896Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Training history","metadata":{}},{"cell_type":"code","source":"history_df = pd.DataFrame(history)\nhistory_df","metadata":{"trusted":true,"execution":{"execution_failed":"2026-04-12T20:39:40.897Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Load best model","metadata":{}},{"cell_type":"code","source":"best_model = build_model(CFG.NUM_CLASSES).to(CFG.DEVICE)\nbest_model.load_state_dict(torch.load(CFG.MODEL_PATH, map_location=CFG.DEVICE))\nbest_model.eval()\n\nprint(\"Best model loaded.\")","metadata":{"trusted":true,"execution":{"execution_failed":"2026-04-12T20:39:40.897Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Test prediction","metadata":{}},{"cell_type":"code","source":"@torch.no_grad()\ndef predict_test(model, loader, device):\n    model.eval()\n\n    image_ids = []\n    preds_all = []\n\n    pbar = tqdm(loader, total=len(loader), desc=\"Predicting Test\")\n\n    for images, batch_image_ids in pbar:\n        images = images.to(device, non_blocking=True)\n\n        outputs = model(images)\n        preds = outputs.argmax(dim=1).detach().cpu().numpy()\n\n        image_ids.extend(batch_image_ids)\n        preds_all.extend(preds)\n\n    return image_ids, preds_all\n\ntest_image_ids, test_preds = predict_test(best_model, test_loader, CFG.DEVICE)","metadata":{"trusted":true,"execution":{"execution_failed":"2026-04-12T20:39:40.897Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Create submission","metadata":{}},{"cell_type":"code","source":"submission = pd.DataFrame({\n    \"image_id\": test_image_ids,\n    \"label\": test_preds\n})\n\nsubmission = submission.sort_values(\"image_id\").reset_index(drop=True)\nsubmission.to_csv(CFG.SUB_PATH, index=False)\n\nprint(submission.head())\nprint(f\"\\nSubmission saved to: {CFG.SUB_PATH}\")","metadata":{"trusted":true,"execution":{"execution_failed":"2026-04-12T20:39:40.897Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Quick check","metadata":{}},{"cell_type":"code","source":"sub_check = pd.read_csv(CFG.SUB_PATH)\nprint(sub_check.shape)\nprint(sub_check.head())","metadata":{"trusted":true,"execution":{"execution_failed":"2026-04-12T20:39:40.897Z"}},"outputs":[],"execution_count":null}]}