{"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":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":"import os\nimport random\nimport json\nimport time\n\nimport numpy as np\nimport pandas as pd\nfrom PIL import Image\n\nfrom sklearn.model_selection import StratifiedKFold\nimport torch\nimport torch.nn as nn\nimport torch.optim as optim\nfrom torch.utils.data import Dataset, DataLoader\nfrom torchvision import transforms, models\n\nDEVICE = 'cuda' if torch.cuda.is_available() else 'cpu'\nSEED = 42\nN_FOLDS = 5\nIMG_SIZE = 512\nBATCH_SIZE = 16\nEPOCHS = 10\nLR = 1e-4\nNUM_CLASSES = 5\n\nDATA_DIR   = '/kaggle/input/cassava-leaf-disease-classification'\nTRAIN_DIR  = os.path.join(DATA_DIR, 'train_images')\nTEST_DIR   = os.path.join(DATA_DIR, 'test_images')\nTRAIN_CSV  = os.path.join(DATA_DIR, 'train.csv')\nSAMPLE_SUB = os.path.join(DATA_DIR, 'sample_submission.csv')\n\nprint('Using device:', DEVICE)\nprint('Data dir:', DATA_DIR)","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2025-11-20T10:58:04.930978Z","iopub.execute_input":"2025-11-20T10:58:04.931200Z","iopub.status.idle":"2025-11-20T10:58:15.598830Z","shell.execute_reply.started":"2025-11-20T10:58:04.931178Z","shell.execute_reply":"2025-11-20T10:58:15.598136Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def seed_everything(seed=SEED):\n    random.seed(seed)\n    np.random.seed(seed)\n    torch.manual_seed(seed)\n    torch.cuda.manual_seed_all(seed)\n    torch.backends.cudnn.deterministic = True\n    torch.backends.cudnn.benchmark = False\n\nseed_everything()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-20T10:58:46.362129Z","iopub.execute_input":"2025-11-20T10:58:46.362401Z","iopub.status.idle":"2025-11-20T10:58:46.372746Z","shell.execute_reply.started":"2025-11-20T10:58:46.362382Z","shell.execute_reply":"2025-11-20T10:58:46.371970Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_df = pd.read_csv(TRAIN_CSV)\nprint(train_df.head())\nprint('\\nClass distribution:')\nprint(train_df['label'].value_counts())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-20T10:59:09.347232Z","iopub.execute_input":"2025-11-20T10:59:09.347761Z","iopub.status.idle":"2025-11-20T10:59:09.371013Z","shell.execute_reply.started":"2025-11-20T10:59:09.347737Z","shell.execute_reply":"2025-11-20T10:59:09.370401Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class CassavaDataset(Dataset):\n    def __init__(self, df, img_dir, transforms=None, is_test=False):\n        self.df = df.reset_index(drop=True)\n        self.img_dir = img_dir\n        self.transforms = transforms\n        self.is_test = is_test\n\n    def __len__(self):\n        return len(self.df)\n\n    def __getitem__(self, idx):\n        row = self.df.iloc[idx]\n        img_path = os.path.join(self.img_dir, row['image_id'])\n        image = Image.open(img_path)\n\n        if image.mode != 'RGB':\n            image = image.convert('RGB')\n\n        if self.transforms:\n            image = self.transforms(image)\n\n        if self.is_test:\n            return image, row['image_id']\n\n        label = int(row['label'])\n        return image, label\n\ntrain_transforms = transforms.Compose([\n    transforms.RandomResizedCrop(IMG_SIZE, scale=(0.8, 1.0)),\n    transforms.RandomHorizontalFlip(),\n    transforms.RandomVerticalFlip(),\n    transforms.RandomRotation(20),\n    transforms.ColorJitter(brightness=0.2, contrast=0.2,\n                           saturation=0.2, hue=0.1),\n    transforms.ToTensor(),\n    transforms.Normalize(mean=[0.485, 0.456, 0.406],\n                         std=[0.229, 0.224, 0.225]),\n])\n\nval_transforms = transforms.Compose([\n    transforms.Resize((IMG_SIZE, IMG_SIZE)),\n    transforms.ToTensor(),\n    transforms.Normalize(mean=[0.485, 0.456, 0.406],\n                         std=[0.229, 0.224, 0.225]),\n])","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-20T11:00:03.695512Z","iopub.execute_input":"2025-11-20T11:00:03.695800Z","iopub.status.idle":"2025-11-20T11:00:03.703721Z","shell.execute_reply.started":"2025-11-20T11:00:03.695778Z","shell.execute_reply":"2025-11-20T11:00:03.703028Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def build_model(num_classes=NUM_CLASSES):\n    \n    model = None\n    \n    try:\n        weights_enum = getattr(models, 'ResNet50_Weights', None)\n        if weights_enum is not None:\n            weights = weights_enum.IMAGENET1K_V2\n            model = models.resnet50(weights=weights)\n            print(\"Loaded ResNet50 with IMAGENET1K_V2 weights.\")\n    except Exception as e:\n        print(\"Could not load new API weights:\", e)\n        \n    if model is None:\n        try:\n            model = models.resnet50(pretrained=True)\n            print(\"Loaded ResNet50 with pretrained=True.\")\n        except Exception as e:\n            print(\"Could not load pretrained weights, using random init:\", e)\n            model = models.resnet50(pretrained=False)\n\n    in_features = model.fc.in_features\n    model.fc = nn.Linear(in_features, num_classes)\n    return model","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-20T11:01:32.291374Z","iopub.execute_input":"2025-11-20T11:01:32.292143Z","iopub.status.idle":"2025-11-20T11:01:32.297478Z","shell.execute_reply.started":"2025-11-20T11:01:32.292116Z","shell.execute_reply":"2025-11-20T11:01:32.296605Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from torch.cuda.amp import GradScaler, autocast\n\ndef train_one_epoch(model, train_loader, criterion, optimizer,\n                    epoch, scaler, device=DEVICE):\n    model.train()\n    running_loss = 0.0\n    running_correct = 0\n    num_samples = 0\n\n    for step, (images, labels) in enumerate(train_loader):\n        images = images.to(device)\n        labels = labels.to(device)\n\n        optimizer.zero_grad()\n\n        with autocast(enabled=(device == 'cuda')):\n            outputs = model(images)\n            loss = criterion(outputs, labels)\n\n        scaler.scale(loss).backward()\n        scaler.step(optimizer)\n        scaler.update()\n\n        _, preds = outputs.max(1)\n        batch_size = labels.size(0)\n        num_samples += batch_size\n        running_loss += loss.item() * batch_size\n        running_correct += (preds == labels).sum().item()\n\n        if (step + 1) % 50 == 0 or (step + 1) == len(train_loader):\n            print(\n                f\"Epoch {epoch} Step {step+1}/{len(train_loader)} \"\n                f\"Loss {running_loss/num_samples:.4f} \"\n                f\"Acc {running_correct/num_samples:.4f}\",\n                end='\\r'\n            )\n\n    epoch_loss = running_loss / num_samples\n    epoch_acc = running_correct / num_samples\n    return epoch_loss, epoch_acc\n\n\ndef validate_one_epoch(model, valid_loader, criterion, device=DEVICE):\n    model.eval()\n    running_loss = 0.0\n    running_correct = 0\n    num_samples = 0\n\n    with torch.no_grad():\n        for images, labels in valid_loader:\n            images = images.to(device)\n            labels = labels.to(device)\n\n            with autocast(enabled=(device == 'cuda')):\n                outputs = model(images)\n                loss = criterion(outputs, labels)\n\n            _, preds = outputs.max(1)\n            batch_size = labels.size(0)\n            num_samples += batch_size\n            running_loss += loss.item() * batch_size\n            running_correct += (preds == labels).sum().item()\n\n    epoch_loss = running_loss / num_samples\n    epoch_acc = running_correct / num_samples\n    return epoch_loss, epoch_acc","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-20T11:02:09.605270Z","iopub.execute_input":"2025-11-20T11:02:09.605778Z","iopub.status.idle":"2025-11-20T11:02:09.614308Z","shell.execute_reply.started":"2025-11-20T11:02:09.605757Z","shell.execute_reply":"2025-11-20T11:02:09.613469Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"skf = StratifiedKFold(n_splits=N_FOLDS, shuffle=True, random_state=SEED)\nfolds = list(skf.split(train_df['image_id'], train_df['label']))\n\noof_predictions = np.zeros((len(train_df), NUM_CLASSES), dtype=np.float32)\noof_targets = train_df['label'].values\n\nfor fold, (train_idx, val_idx) in enumerate(folds):\n    print(f\"\\n===== Fold {fold+1}/{N_FOLDS} =====\")\n\n    train_data = train_df.iloc[train_idx].reset_index(drop=True)\n    val_data   = train_df.iloc[val_idx].reset_index(drop=True)\n\n    train_dataset = CassavaDataset(train_data, TRAIN_DIR,\n                                   transforms=train_transforms)\n    val_dataset   = CassavaDataset(val_data,   TRAIN_DIR,\n                                   transforms=val_transforms)\n\n    train_loader = DataLoader(\n        train_dataset,\n        batch_size=BATCH_SIZE,\n        shuffle=True,\n        num_workers=4,\n        pin_memory=True\n    )\n    val_loader = DataLoader(\n        val_dataset,\n        batch_size=BATCH_SIZE,\n        shuffle=False,\n        num_workers=4,\n        pin_memory=True\n    )\n\n    model = build_model().to(DEVICE)\n\n    criterion = nn.CrossEntropyLoss(label_smoothing=0.1)\n    optimizer = optim.AdamW(model.parameters(), lr=LR, weight_decay=1e-4)\n    scheduler = optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max=EPOCHS)\n    scaler    = GradScaler(enabled=(DEVICE == 'cuda'))\n\n    best_val_acc = 0.0\n    best_model_path = f'/kaggle/working/best_model_fold{fold}.pth'\n\n    for epoch in range(1, EPOCHS + 1):\n        start_time = time.time()\n\n        train_loss, train_acc = train_one_epoch(\n            model, train_loader, criterion, optimizer, epoch, scaler, device=DEVICE\n        )\n        val_loss, val_acc = validate_one_epoch(\n            model, val_loader, criterion, device=DEVICE\n        )\n\n        scheduler.step()\n\n        elapsed = time.time() - start_time\n        print(\n            f\"\\nFold {fold+1} Epoch {epoch}/{EPOCHS} \"\n            f\"Train loss: {train_loss:.4f} Acc: {train_acc:.4f} | \"\n            f\"Val loss: {val_loss:.4f} Acc: {val_acc:.4f} | \"\n            f\"Time: {elapsed:.1f}s\"\n        )\n\n        if val_acc > best_val_acc:\n            best_val_acc = val_acc\n            torch.save(model.state_dict(), best_model_path)\n            print(f\"--> Saved best model for fold {fold+1} with val_acc {best_val_acc:.4f}\")\n\n    model.load_state_dict(torch.load(best_model_path, map_location=DEVICE))\n    model.eval()\n\n    val_loader = DataLoader(\n        val_dataset,\n        batch_size=BATCH_SIZE,\n        shuffle=False,\n        num_workers=4,\n        pin_memory=True\n    )\n\n    all_outputs = []\n\n    with torch.no_grad():\n        for images, labels in val_loader:\n            images = images.to(DEVICE)\n            with autocast(enabled=(DEVICE == 'cuda')):\n                outputs = model(images)\n                probs = torch.softmax(outputs, dim=1)\n            all_outputs.append(probs.cpu().numpy())\n\n    all_outputs = np.concatenate(all_outputs, axis=0)\n    oof_predictions[val_idx] = all_outputs\n\n    fold_acc = (oof_predictions[val_idx].argmax(axis=1) == oof_targets[val_idx]).mean()\n    print(f\"Fold {fold+1} OOF accuracy: {fold_acc:.4f}\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-20T11:02:44.710913Z","iopub.execute_input":"2025-11-20T11:02:44.711201Z","iopub.status.idle":"2025-11-20T16:31:49.425676Z","shell.execute_reply.started":"2025-11-20T11:02:44.711183Z","shell.execute_reply":"2025-11-20T16:31:49.424789Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"oof_pred_labels = oof_predictions.argmax(axis=1)\noof_accuracy = (oof_pred_labels == oof_targets).mean()\nprint(f\"OOF accuracy across all folds: {oof_accuracy:.4f}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-20T16:32:01.407998Z","iopub.execute_input":"2025-11-20T16:32:01.408328Z","iopub.status.idle":"2025-11-20T16:32:01.414354Z","shell.execute_reply.started":"2025-11-20T16:32:01.408299Z","shell.execute_reply":"2025-11-20T16:32:01.413763Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"sub_df = pd.read_csv(SAMPLE_SUB)\nprint('Sample submission head:')\nprint(sub_df.head())\n\ntest_df = sub_df.copy()\n\ntest_dataset = CassavaDataset(\n    test_df, TEST_DIR, transforms=val_transforms, is_test=True\n)\ntest_loader = DataLoader(\n    test_dataset,\n    batch_size=BATCH_SIZE,\n    shuffle=False,\n    num_workers=4,\n    pin_memory=True\n)\n\nall_preds = np.zeros((len(test_df), NUM_CLASSES), dtype=np.float32)\n\nfor fold in range(N_FOLDS):\n    print(f\"Inference with fold {fold+1} model...\")\n\n    model = build_model().to(DEVICE)\n    best_model_path = f'/kaggle/working/best_model_fold{fold}.pth'\n    state_dict = torch.load(best_model_path, map_location=DEVICE)\n    model.load_state_dict(state_dict)\n    model.eval()\n\n    fold_preds = []\n\n    with torch.no_grad():\n        for images, image_ids in test_loader:\n            images = images.to(DEVICE)\n            with autocast(enabled=(DEVICE == 'cuda')):\n                outputs = model(images)\n                probs = torch.softmax(outputs, dim=1)\n            fold_preds.append(probs.cpu().numpy())\n\n    fold_preds = np.concatenate(fold_preds, axis=0)\n    all_preds += fold_preds / N_FOLDS  # average over folds\n\npred_labels = all_preds.argmax(axis=1)\nsub_df['label'] = pred_labels\n\nsub_df.to_csv('submission.csv', index=False)\nprint('submission.csv saved!')\nsub_df.head()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-20T16:32:06.265731Z","iopub.execute_input":"2025-11-20T16:32:06.266303Z","iopub.status.idle":"2025-11-20T16:32:10.470269Z","shell.execute_reply.started":"2025-11-20T16:32:06.266280Z","shell.execute_reply":"2025-11-20T16:32:10.469379Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}