{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.11.11","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"nvidiaTeslaT4","dataSources":[{"sourceId":106372,"databundleVersionId":12864984,"sourceType":"competition"}],"dockerImageVersionId":31042,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"import os\nimport random\nimport numpy as np\nimport pandas as pd\nimport torch\nimport torch.nn as nn\nfrom torch.utils.data import Dataset, DataLoader\nfrom torchvision import transforms\nfrom PIL import Image\nfrom sklearn.preprocessing import LabelEncoder\nfrom sklearn.model_selection import StratifiedKFold\nfrom tqdm.notebook import tqdm\nimport timm\nimport joblib","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-07T11:16:56.690683Z","iopub.execute_input":"2025-07-07T11:16:56.691103Z","iopub.status.idle":"2025-07-07T11:16:56.695949Z","shell.execute_reply.started":"2025-07-07T11:16:56.691077Z","shell.execute_reply":"2025-07-07T11:16:56.695189Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# --- 1. Main Configuration ---\nBASE_PATH = '/kaggle/input/2025-bamboo-summer-competiton-dl-pr/'\nclass CFG:\n    # Paths\n    TRAIN_CSV_PATH = BASE_PATH + 'train.csv'\n    TEST_CSV_PATH = BASE_PATH + 'test.csv'\n    SAMPLE_CSV_PATH = BASE_PATH + 'sample_submission'\n    IMAGE_DIR_TRAIN = BASE_PATH + 'train'\n    IMAGE_DIR_TEST = BASE_PATH + 'test'\n    \n    # Model & Training settings\n    MODEL_NAME = 'tf_efficientnet_b5_ns' # A very powerful Noisy-Student model\n    IMG_SIZE = 456 # Native image size for B5\n    BATCH_SIZE = 16 # Reduce batch size for the larger model\n    EPOCHS = 15\n    LEARNING_RATE = 1e-4\n    LABEL_SMOOTHING = 0.1\n    N_SPLITS = 5\n    \n    # Other settings\n    SEED = 42\n    DEVICE = torch.device('cuda' if torch.cuda.is_available() else 'cpu')\n    NUM_WORKERS = 2","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-07T11:14:23.867686Z","iopub.execute_input":"2025-07-07T11:14:23.868142Z","iopub.status.idle":"2025-07-07T11:14:23.968749Z","shell.execute_reply.started":"2025-07-07T11:14:23.868123Z","shell.execute_reply":"2025-07-07T11:14:23.967890Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# --- 2. Function for reproducibility ---\ndef seed_everything(seed):\n    random.seed(seed)\n    os.environ['PYTHONHASHSEED'] = str(seed)\n    np.random.seed(seed)\n    torch.manual_seed(seed)\n    torch.cuda.manual_seed(seed)\n    torch.backends.cudnn.deterministic = True\n    torch.backends.cudnn.benchmark = False\n\nseed_everything(CFG.SEED)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-07T11:14:23.969567Z","iopub.execute_input":"2025-07-07T11:14:23.969779Z","iopub.status.idle":"2025-07-07T11:14:24.007770Z","shell.execute_reply.started":"2025-07-07T11:14:23.969762Z","shell.execute_reply":"2025-07-07T11:14:24.007041Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# --- 3. Load data and encode labels ---\ntrain_df = pd.read_csv(CFG.TRAIN_CSV_PATH)\ntest_df = pd.read_csv(CFG.TEST_CSV_PATH)\n\nle = LabelEncoder()\ntrain_df['label_enc'] = le.fit_transform(train_df['label'])\njoblib.dump(le, 'label_encoder.pkl')\nCFG.NUM_CLASSES = len(le.classes_)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-07T11:14:24.008562Z","iopub.execute_input":"2025-07-07T11:14:24.008829Z","iopub.status.idle":"2025-07-07T11:14:24.060859Z","shell.execute_reply.started":"2025-07-07T11:14:24.008807Z","shell.execute_reply":"2025-07-07T11:14:24.060319Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# --- 4. Define the Dataset class ---\nclass ButterflyDataset(Dataset):\n    def __init__(self, df, img_dir, transform=None, is_test=False):\n        self.df = df.reset_index(drop=True)\n        self.img_dir = img_dir\n        self.transform = transform\n        self.is_test = is_test\n\n    def __len__(self):\n        return len(self.df)\n\n    def __getitem__(self, index):\n        img_path = os.path.join(self.img_dir, self.df.loc[index, 'filename'])\n        image = Image.open(img_path).convert('RGB')\n        if self.transform:\n            image = self.transform(image)\n        if self.is_test:\n            return image\n        label = self.df.loc[index, 'label_enc']\n        return image, torch.tensor(label, dtype=torch.long)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-07T11:14:24.062584Z","iopub.execute_input":"2025-07-07T11:14:24.062786Z","iopub.status.idle":"2025-07-07T11:14:24.068166Z","shell.execute_reply.started":"2025-07-07T11:14:24.062770Z","shell.execute_reply":"2025-07-07T11:14:24.067540Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# --- 5. Define Image Transforms ---\ntrain_transform = transforms.Compose([\n    transforms.Resize((CFG.IMG_SIZE, CFG.IMG_SIZE)),\n    transforms.RandomHorizontalFlip(),\n    transforms.RandomRotation(15),\n    transforms.ColorJitter(brightness=0.1, contrast=0.1, saturation=0.1),\n    transforms.ToTensor(),\n    transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]),\n])\n\nval_transform = transforms.Compose([\n    transforms.Resize((CFG.IMG_SIZE, CFG.IMG_SIZE)),\n    transforms.ToTensor(),\n    transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]),\n])","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-07T11:14:24.068778Z","iopub.execute_input":"2025-07-07T11:14:24.069247Z","iopub.status.idle":"2025-07-07T11:14:24.091891Z","shell.execute_reply.started":"2025-07-07T11:14:24.069220Z","shell.execute_reply":"2025-07-07T11:14:24.091372Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# --- 6. K-Fold Cross-Validation Training ---\nskf = StratifiedKFold(n_splits=CFG.N_SPLITS, shuffle=True, random_state=CFG.SEED)\nmodel_paths = []\n\nfor fold, (train_idx, val_idx) in enumerate(skf.split(train_df, train_df['label_enc'])):\n    print(f\"========== Fold {fold+1}/{CFG.N_SPLITS} ==========\")\n    \n    # Create fold-specific data\n    train_fold_df = train_df.iloc[train_idx]\n    val_fold_df = train_df.iloc[val_idx]\n\n    train_dataset = ButterflyDataset(train_fold_df, CFG.IMAGE_DIR_TRAIN, train_transform)\n    val_dataset = ButterflyDataset(val_fold_df, CFG.IMAGE_DIR_TRAIN, val_transform)\n    train_loader = DataLoader(train_dataset, batch_size=CFG.BATCH_SIZE, shuffle=True, num_workers=CFG.NUM_WORKERS)\n    val_loader = DataLoader(val_dataset, batch_size=CFG.BATCH_SIZE, shuffle=False, num_workers=CFG.NUM_WORKERS)\n\n    # Create a new model for each fold\n    model = timm.create_model(CFG.MODEL_NAME, pretrained=True, num_classes=CFG.NUM_CLASSES)\n    model.to(CFG.DEVICE)\n\n    criterion = nn.CrossEntropyLoss(label_smoothing=CFG.LABEL_SMOOTHING)\n    optimizer = torch.optim.AdamW(model.parameters(), lr=CFG.LEARNING_RATE)\n    scheduler = torch.optim.lr_scheduler.CosineAnnealingWarmRestarts(optimizer, T_0=CFG.EPOCHS, eta_min=1e-6)\n    scaler = torch.cuda.amp.GradScaler()\n\n    best_acc = 0.0\n    model_path = f'best_model_fold_{fold+1}.pth'\n\n    for epoch in range(CFG.EPOCHS):\n        model.train()\n        loop = tqdm(train_loader, desc=f\"Epoch {epoch+1}/{CFG.EPOCHS}\")\n        for imgs, labels in loop:\n            imgs, labels = imgs.to(CFG.DEVICE), labels.to(CFG.DEVICE)\n            \n            with torch.cuda.amp.autocast():\n                outputs = model(imgs)\n                loss = criterion(outputs, labels)\n            \n            scaler.scale(loss).backward()\n            scaler.step(optimizer)\n            scaler.update()\n            optimizer.zero_grad()\n            loop.set_postfix(loss=loss.item())\n        \n        scheduler.step()\n\n        # Validation\n        model.eval()\n        val_correct = 0\n        with torch.no_grad():\n            for imgs, labels in val_loader:\n                imgs, labels = imgs.to(CFG.DEVICE), labels.to(CFG.DEVICE)\n                outputs = model(imgs)\n                val_correct += (outputs.argmax(1) == labels).sum().item()\n        \n        val_acc = val_correct / len(val_dataset)\n        print(f\"Fold {fold+1} | Epoch {epoch+1} | Val Acc: {val_acc:.5f}\")\n\n        if val_acc > best_acc:\n            best_acc = val_acc\n            torch.save(model.state_dict(), model_path)\n            print(f\"Best model for fold {fold+1} saved with accuracy: {best_acc:.5f}\")\n    \n    model_paths.append(model_path)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-07T11:17:02.601334Z","iopub.execute_input":"2025-07-07T11:17:02.601944Z","iopub.status.idle":"2025-07-07T11:35:08.063163Z","shell.execute_reply.started":"2025-07-07T11:17:02.601921Z","shell.execute_reply":"2025-07-07T11:35:08.062154Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# --- 7. Ensemble Prediction ---\nprint(\"\\nStarting ensemble prediction on test data...\")\ntest_dataset = ButterflyDataset(test_df, CFG.IMAGE_DIR_TEST, transform=val_transform, is_test=True)\ntest_loader = DataLoader(test_dataset, batch_size=CFG.BATCH_SIZE, shuffle=False, num_workers=CFG.NUM_WORKERS)\n\nall_fold_preds = []\nfor path in model_paths:\n    print(f\"Loading model: {path}\")\n    model = timm.create_model(CFG.MODEL_NAME, pretrained=False, num_classes=CFG.NUM_CLASSES)\n    model.load_state_dict(torch.load(path))\n    model.to(CFG.DEVICE)\n    model.eval()\n\n    fold_probs = []\n    with torch.no_grad():\n        for imgs in tqdm(test_loader, desc=f\"Predicting with {path}\"):\n            imgs = imgs.to(CFG.DEVICE)\n            \n            with torch.cuda.amp.autocast():\n                # TTA\n                outputs1 = model(imgs)\n                outputs2 = model(torch.flip(imgs, [3]))\n            \n            # Average probabilities from TTA\n            avg_outputs = (torch.softmax(outputs1, 1) + torch.softmax(outputs2, 1)) / 2\n            fold_probs.append(avg_outputs.cpu().numpy())\n    \n    all_fold_preds.append(np.vstack(fold_probs))\n\n# Average the probabilities across all folds\nfinal_avg_probs = np.mean(all_fold_preds, axis=0)\nfinal_preds = np.argmax(final_avg_probs, axis=1)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-07T11:14:24.145646Z","iopub.status.idle":"2025-07-07T11:14:24.145918Z","shell.execute_reply.started":"2025-07-07T11:14:24.145777Z","shell.execute_reply":"2025-07-07T11:14:24.145788Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# --- 8. Create Submission File ---\ndecoded_labels = le.inverse_transform(final_preds)\nsubmission = pd.DataFrame({'filename': test_df['filename'], 'label': decoded_labels})\nsubmission.to_csv('submission.csv', index=False)\n\nprint(\"\\nsubmission.csv has been created successfully!\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-07T11:14:24.147081Z","iopub.status.idle":"2025-07-07T11:14:24.147322Z","shell.execute_reply.started":"2025-07-07T11:14:24.147203Z","shell.execute_reply":"2025-07-07T11:14:24.147214Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}