{"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":"gpu","dataSources":[{"sourceId":13836,"databundleVersionId":1718836,"sourceType":"competition"}],"dockerImageVersionId":31193,"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},"outputs":[],"execution_count":null},{"cell_type":"code","source":"DATA_DIR = \"../input/cassava-leaf-disease-classification/train_images/\"  # path to images\nCSV_PATH = \"../input/cassava-leaf-disease-classification/train.csv\"\nOUTPUT_DIR = \"./prepared_data/\"","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-28T08:06:49.182628Z","iopub.execute_input":"2025-11-28T08:06:49.182847Z","iopub.status.idle":"2025-11-28T08:06:49.188876Z","shell.execute_reply.started":"2025-11-28T08:06:49.182823Z","shell.execute_reply":"2025-11-28T08:06:49.188099Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import pandas as pd\ndf = pd.read_csv(CSV_PATH)\ndf['file_path'] = df['image_id'].apply(lambda x: DATA_DIR + x)\nprint(df.shape)\nprint(df['label'].value_counts())\ndf.head()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-28T08:09:39.194569Z","iopub.execute_input":"2025-11-28T08:09:39.195190Z","iopub.status.idle":"2025-11-28T08:09:39.509118Z","shell.execute_reply.started":"2025-11-28T08:09:39.195165Z","shell.execute_reply":"2025-11-28T08:09:39.508442Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from PIL import Image\nimport os\n\nmissing = []\ncorrupt = []\nfor fp in df['file_path'].tolist():\n    if not os.path.exists(fp):\n        missing.append(fp)\n    else:\n        try:\n            with Image.open(fp) as im:\n                im.verify()   # PIL verify for corruption\n        except Exception as e:\n            corrupt.append((fp, str(e)))\n\nprint(\"missing:\", len(missing))\nprint(\"corrupt:\", len(corrupt))\n# Optionally print examples\ncorrupt[:5]\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-28T08:10:19.023146Z","iopub.execute_input":"2025-11-28T08:10:19.023877Z","iopub.status.idle":"2025-11-28T08:13:35.106266Z","shell.execute_reply.started":"2025-11-28T08:10:19.023851Z","shell.execute_reply":"2025-11-28T08:13:35.105679Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import matplotlib.pyplot as plt\ncounts = df['label'].value_counts().sort_index()\nplt.figure(figsize=(6,4))\nplt.bar(counts.index.astype(str), counts.values)\nplt.xlabel(\"label\")\nplt.ylabel(\"count\")\nplt.title(\"Class distribution\")\nplt.show()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-28T08:13:51.461467Z","iopub.execute_input":"2025-11-28T08:13:51.461736Z","iopub.status.idle":"2025-11-28T08:13:51.617105Z","shell.execute_reply.started":"2025-11-28T08:13:51.461715Z","shell.execute_reply":"2025-11-28T08:13:51.616478Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import matplotlib.pyplot as plt\nfrom PIL import Image\nimport random\n\ndef show_samples(df, n=5, size=(3,3)):\n    labels = sorted(df['label'].unique())\n    plt.figure(figsize=(n*2, len(labels)*2))\n    row = 0\n    for lbl in labels:\n        samples = df[df['label']==lbl]['file_path'].sample(n, random_state=42).tolist()\n        for i, p in enumerate(samples):\n            im = Image.open(p).convert('RGB')\n            plt.subplot(len(labels), n, row*n + i + 1)\n            plt.imshow(im); plt.axis('off')\n            if i == 0:\n                plt.ylabel(f\"label {lbl}\", rotation=0, labelpad=40)\n        row += 1\n    plt.tight_layout()\nshow_samples(df, n=5)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-28T08:14:16.860345Z","iopub.execute_input":"2025-11-28T08:14:16.861064Z","iopub.status.idle":"2025-11-28T08:14:19.465534Z","shell.execute_reply.started":"2025-11-28T08:14:16.861039Z","shell.execute_reply":"2025-11-28T08:14:19.464605Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from PIL import Image\nimport numpy as np\nws, hs = [], []\nfor fp in df['file_path'].sample(1000, random_state=1):  # sample for speed\n    with Image.open(fp) as im:\n        w,h = im.size\n    ws.append(w); hs.append(h)\n\nimport seaborn as sns\nplt.figure(figsize=(10,4))\nplt.subplot(1,2,1)\nsns.histplot(ws, bins=30); plt.title(\"width\")\nplt.subplot(1,2,2)\nsns.histplot(hs, bins=30); plt.title(\"height\")\nplt.show()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-28T08:16:01.581792Z","iopub.execute_input":"2025-11-28T08:16:01.582105Z","iopub.status.idle":"2025-11-28T08:16:03.451414Z","shell.execute_reply.started":"2025-11-28T08:16:01.582082Z","shell.execute_reply":"2025-11-28T08:16:03.450703Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# pip install albumentations timm\nimport os\nimport torch\nfrom torch.utils.data import Dataset, DataLoader\nimport albumentations as A\nfrom albumentations.pytorch import ToTensorV2\nimport cv2\nimport pandas as pd\n\n# Example: ensure df exists and has 'file_path' and 'label' columns from earlier steps\n# df = pd.read_csv(\"...\")  # make sure this is defined\n\ntrain_transform = A.Compose([\n    # pass size as a tuple (height, width) OR use named args height=..., width=...\n    A.RandomResizedCrop((384, 384), scale=(0.8, 1.0)),\n    A.HorizontalFlip(p=0.5),\n    A.RandomBrightnessContrast(p=0.5),\n    A.Normalize(mean=(0.485,0.456,0.406), std=(0.229,0.224,0.225)),\n    ToTensorV2()\n])\n# If 'fold' is missing, create stratified folds\nif 'fold' not in df.columns:\n    print(\"Creating stratified folds...\")\n    from sklearn.model_selection import StratifiedKFold\n    import numpy as np\n    n_splits = 5\n    skf = StratifiedKFold(n_splits=n_splits, shuffle=True, random_state=42)\n    df['fold'] = -1\n    for fold, (_, val_idx) in enumerate(skf.split(df, df['label'])):\n        df.loc[val_idx, 'fold'] = fold\n    print(\"Fold counts:\\n\", df['fold'].value_counts())\nelse:\n    print(\"'fold' already present. Fold counts:\\n\", df['fold'].value_counts())\n\nclass CassavaDataset(Dataset):\n    def __init__(self, df, transform=None):\n        self.df = df.reset_index(drop=True)\n        self.transform = transform\n\n    def __len__(self):\n        return len(self.df)\n\n    def __getitem__(self, idx):\n        row = self.df.iloc[idx]\n        fp = row['file_path']\n        # safety: check file exists\n        if not os.path.exists(fp):\n            raise FileNotFoundError(f\"Image path not found: {fp}\")\n        img_bgr = cv2.imread(fp)\n        if img_bgr is None:\n            raise RuntimeError(f\"Failed to read image (cv2.imread returned None): {fp}\")\n        img = cv2.cvtColor(img_bgr, cv2.COLOR_BGR2RGB)\n        # Ensure dtype is uint8 for albumentations\n        if img.dtype != 'uint8':\n            img = (img * 255).astype('uint8')\n\n        if self.transform:\n            img = self.transform(image=img)['image']\n        label = int(row['label'])\n        return img, label\n\n# debug dataloader\n# make sure df and df['fold'] exist; here we assume it's prepared earlier\ntrain_df = df[df['fold'] != 0].reset_index(drop=True)\nds = CassavaDataset(train_df, transform=train_transform)\ndl = DataLoader(ds, batch_size=8, num_workers=4, shuffle=True, pin_memory=True)\n\n# quick sanity check\nimages, labels = next(iter(dl))\nprint(\"images:\", images.shape, images.dtype)   # expect (B, C, H, W), float32\nprint(\"labels:\", labels.shape, labels.dtype)   # expect (B,), torch.long\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-28T08:37:31.594024Z","iopub.execute_input":"2025-11-28T08:37:31.594477Z","iopub.status.idle":"2025-11-28T08:37:32.507660Z","shell.execute_reply.started":"2025-11-28T08:37:31.594449Z","shell.execute_reply":"2025-11-28T08:37:32.506835Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### CUTMIX AND SOFT LOSS HELPERS","metadata":{"execution":{"iopub.status.busy":"2025-11-28T08:41:33.193765Z","iopub.execute_input":"2025-11-28T08:41:33.194278Z","iopub.status.idle":"2025-11-28T08:41:33.197726Z","shell.execute_reply.started":"2025-11-28T08:41:33.194238Z","shell.execute_reply":"2025-11-28T08:41:33.196945Z"}}},{"cell_type":"code","source":"import numpy as np\nimport torch\nimport torch.nn.functional as F\n\n# one-hot helper\ndef one_hot(labels, num_classes, device):\n    return torch.zeros(labels.size(0), num_classes, device=device).scatter_(1, labels.view(-1,1), 1)\n\n# sample lambda from Beta\ndef rand_beta(alpha):\n    return np.random.beta(alpha, alpha) if alpha > 0 else 1.0\n\n# CutMix batch function\ndef cutmix_batch(images, labels, alpha=1.0, prob=0.5, num_classes=5):\n    \"\"\"\n    images: Tensor (B, C, H, W)\n    labels: Tensor (B,) int\n    returns: mixed_images, mixed_labels (soft), lam\n    \"\"\"\n    if np.random.rand() > prob:\n        device = images.device\n        return images, one_hot(labels, num_classes, device), 1.0\n\n    lam = rand_beta(alpha)\n    batch_size, _, H, W = images.size()\n    index = torch.randperm(batch_size, device=images.device)\n    labels2 = labels[index]\n\n    # box\n    cut_rat = np.sqrt(1. - lam)\n    cut_w = int(W * cut_rat)\n    cut_h = int(H * cut_rat)\n\n    # uniform center\n    cx = np.random.randint(W)\n    cy = np.random.randint(H)\n\n    bbx1 = np.clip(cx - cut_w // 2, 0, W)\n    bby1 = np.clip(cy - cut_h // 2, 0, H)\n    bbx2 = np.clip(cx + cut_w // 2, 0, W)\n    bby2 = np.clip(cy + cut_h // 2, 0, H)\n\n    new_images = images.clone()\n    new_images[:, :, bby1:bby2, bbx1:bbx2] = images[index, :, bby1:bby2, bbx1:bbx2]\n\n    # adjust lambda based on actual area\n    area = (bbx2 - bbx1) * (bby2 - bby1)\n    lam_adjusted = 1 - area / (H * W)\n\n    device = images.device\n    y1_hot = one_hot(labels, num_classes, device)\n    y2_hot = one_hot(labels2, num_classes, device)\n    y_mixed = lam_adjusted * y1_hot + (1 - lam_adjusted) * y2_hot\n\n    return new_images, y_mixed, lam_adjusted\n\n# soft cross entropy\ndef soft_cross_entropy(pred_logits, soft_targets):\n    log_probs = F.log_softmax(pred_logits, dim=1)\n    loss = - (soft_targets * log_probs).sum(dim=1).mean()\n    return loss\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-28T08:40:16.331620Z","iopub.execute_input":"2025-11-28T08:40:16.332647Z","iopub.status.idle":"2025-11-28T08:40:16.342162Z","shell.execute_reply.started":"2025-11-28T08:40:16.332611Z","shell.execute_reply":"2025-11-28T08:40:16.341435Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Minimal model, optimizer, scheduler setup","metadata":{"execution":{"iopub.status.busy":"2025-11-28T08:42:34.819191Z","iopub.execute_input":"2025-11-28T08:42:34.819524Z","iopub.status.idle":"2025-11-28T08:42:34.823018Z","shell.execute_reply.started":"2025-11-28T08:42:34.819503Z","shell.execute_reply":"2025-11-28T08:42:34.822310Z"}}},{"cell_type":"code","source":"import timm\ndevice = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\n\nnum_classes = 5\nmodel = timm.create_model('efficientnet_b4', pretrained=True, num_classes=num_classes)\nmodel = model.to(device)\n\nimport torch.optim as optim\nfrom torch.optim.lr_scheduler import CosineAnnealingLR\n\noptimizer = optim.AdamW(model.parameters(), lr=1e-4, weight_decay=1e-2)\nscheduler = CosineAnnealingLR(optimizer, T_max=10)  # tune T_max per epochs\nscaler = torch.cuda.amp.GradScaler()  # for AMP\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-28T08:43:15.148763Z","iopub.execute_input":"2025-11-28T08:43:15.149408Z","iopub.status.idle":"2025-11-28T08:43:24.462957Z","shell.execute_reply.started":"2025-11-28T08:43:15.149376Z","shell.execute_reply":"2025-11-28T08:43:24.462097Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## vALIDATION (NO CUTMIX)","metadata":{}},{"cell_type":"code","source":"from sklearn.metrics import accuracy_score, f1_score\nimport numpy as np\n\ndef validate(model, val_loader):\n    model.eval()\n    preds = []\n    truths = []\n    running_loss = 0.0\n    n = 0\n    with torch.no_grad():\n        for images, labels in val_loader:\n            images = images.to(device, non_blocking=True)\n            labels = labels.to(device, non_blocking=True)\n            # forward\n            logits = model(images)\n            loss = F.cross_entropy(logits, labels)  # integer labels for val\n            running_loss += loss.item() * images.size(0)\n            n += images.size(0)\n            ps = torch.argmax(logits, dim=1).cpu().numpy()\n            preds.append(ps)\n            truths.append(labels.cpu().numpy())\n    preds = np.concatenate(preds)\n    truths = np.concatenate(truths)\n    acc = accuracy_score(truths, preds)\n    f1 = f1_score(truths, preds, average='macro')\n    return running_loss / n, acc, f1\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-28T08:44:16.839397Z","iopub.execute_input":"2025-11-28T08:44:16.839880Z","iopub.status.idle":"2025-11-28T08:44:16.847351Z","shell.execute_reply.started":"2025-11-28T08:44:16.839849Z","shell.execute_reply":"2025-11-28T08:44:16.846556Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}