{"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":[{"sourceId":112509,"databundleVersionId":14254895,"sourceType":"competition"}],"dockerImageVersionId":31236,"isInternetEnabled":false,"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,"execution":{"iopub.status.busy":"2026-01-09T10:34:03.533544Z","iopub.execute_input":"2026-01-09T10:34:03.533807Z","iopub.status.idle":"2026-01-09T10:34:06.790588Z","shell.execute_reply.started":"2026-01-09T10:34:03.533782Z","shell.execute_reply":"2026-01-09T10:34:06.789924Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import sys\nimport os\nimport gc\nimport pandas as pd\nimport numpy as np\nimport torch\nimport torch.nn as nn\nimport torch.optim as optim\nfrom torch.utils.data import Dataset, DataLoader\nfrom torchvision import transforms, models\nfrom PIL import Image\n\n# ==========================================\n# 1. Global Config & Path Discovery\n# ==========================================\nprint(\"🔍 Starting robust path discovery...\")\n\nSEARCH_ROOT = '/kaggle/input'\nTEST_IMG_DIR = None\nTRAIN_CSV = None\n\n# Find directories\nfor root, dirs, files in os.walk(SEARCH_ROOT):\n    if 'test_images' in dirs:\n        TEST_IMG_DIR = os.path.join(root, 'test_images')\n    if 'train.csv' in files:\n        TRAIN_CSV = os.path.join(root, 'train.csv')\n\n# Fallback for Test Dir\nif TEST_IMG_DIR is None:\n    for root, dirs, files in os.walk(SEARCH_ROOT):\n        if len(files) > 0 and files[0].lower().endswith(('.png', '.jpg', '.jpeg')):\n            if 'train' not in root.lower():\n                TEST_IMG_DIR = root\n                break\n\nprint(f\"📂 Train CSV: {TRAIN_CSV}\")\nprint(f\"📂 Test Dir: {TEST_IMG_DIR}\")\n\n# Constants\nTARGET_COLS = ['Dry_Green_g', 'Dry_Dead_g', 'Dry_Clover_g', 'GDM_g', 'Dry_Total_g']\nDEVICE = torch.device('cuda' if torch.cuda.is_available() else 'cpu')\nprint(f\"🚀 Device: {DEVICE}\")\n\n# ==========================================\n# 2. Dataset Definition\n# ==========================================\nclass BiomassDataset(Dataset):\n    def __init__(self, dataframe, root_dir, transform=None, is_test=False, image_ids=None):\n        self.df = dataframe\n        self.root_dir = root_dir\n        self.transform = transform\n        self.is_test = is_test\n        self.image_ids = image_ids\n        \n        if not is_test:\n            self.img_paths = dataframe['image_path'].tolist()\n            self.targets = dataframe[TARGET_COLS].values.astype('float32')\n\n    def __len__(self):\n        return len(self.image_ids) if self.is_test else len(self.df)\n\n    def __getitem__(self, idx):\n        image = None\n        if self.is_test:\n            img_id = self.image_ids[idx]\n            for ext in ['.png', '.jpg', '.jpeg', '']:\n                path = os.path.join(self.root_dir, f\"{img_id}{ext}\")\n                if os.path.exists(path):\n                    try: image = Image.open(path).convert('RGB'); break\n                    except: pass\n        else:\n            path = os.path.join(self.root_dir, self.df.iloc[idx]['image_path'])\n            if os.path.exists(path):\n                image = Image.open(path).convert('RGB')\n\n        if image is None: image = Image.new('RGB', (224, 224), (0, 0, 0))\n        if self.transform: image = self.transform(image)\n        \n        if self.is_test: return image, self.image_ids[idx]\n        return image, torch.tensor(self.targets[idx])\n\n# ==========================================\n# 3. Model Initialization\n# ==========================================\ntry:\n    print(\"🛠️ Initializing EfficientNet-B4 (Bigger Brain)...\")\n    model = models.efficientnet_b4(weights=None)\n    model.classifier[1] = nn.Linear(model.classifier[1].in_features, 5)\n    model = model.to(DEVICE)\nexcept Exception as e:\n    print(f\"❌ Model Init Error: {e}\")\n\n# Stronger Augmentation\ntrain_transform = transforms.Compose([\n    transforms.Resize((224, 224)),\n    transforms.RandomHorizontalFlip(),\n    transforms.RandomVerticalFlip(),\n    transforms.RandomRotation(15), # New: Rotate slightly\n    transforms.ColorJitter(brightness=0.2, contrast=0.2, saturation=0.2), # Stronger jitter\n    transforms.ToTensor(),\n    transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])\n])\n\ninference_transform = transforms.Compose([\n    transforms.Resize((224, 224)),\n    transforms.ToTensor(),\n    transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])\n])\n\n# ==========================================\n# 4. Training Phase (All-in Strategy)\n# ==========================================\nif TRAIN_CSV and os.path.exists(TRAIN_CSV):\n    try:\n        print(\"🚀 Starting ALL-IN Training (No Validation Split)...\")\n        df_raw = pd.read_csv(TRAIN_CSV)\n        df = df_raw.pivot_table(index='image_path', columns='target_name', values='target', aggfunc='first').reset_index()\n        df = df.fillna(0)\n        \n        train_root = os.path.dirname(TRAIN_CSV)\n        if os.path.exists(os.path.join(train_root, 'train_images')):\n            train_root = os.path.join(train_root, 'train_images')\n            \n        # Use 100% of data for training\n        train_ds = BiomassDataset(df, train_root, transform=train_transform)\n        train_loader = DataLoader(train_ds, batch_size=16, shuffle=True, num_workers=2)\n        \n        optimizer = optim.AdamW(model.parameters(), lr=1e-3, weight_decay=1e-4) # AdamW is better for generalization\n        \n        # Huber Loss (SmoothL1) is less sensitive to outliers than MSE\n        criterion = nn.SmoothL1Loss()\n        \n        # Cosine Annealing Scheduler (Smooth curve from 1e-3 to 0)\n        NUM_EPOCHS = 60\n        scheduler = optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max=NUM_EPOCHS)\n        \n        print(f\"   Running {NUM_EPOCHS} epochs on full dataset...\")\n        \n        model.train()\n        for epoch in range(NUM_EPOCHS):\n            running_loss = 0.0\n            for imgs, targets in train_loader:\n                imgs, targets = imgs.to(DEVICE), targets.to(DEVICE)\n                \n                optimizer.zero_grad()\n                outputs = model(imgs)\n                loss = criterion(outputs, targets)\n                loss.backward()\n                optimizer.step()\n                \n                running_loss += loss.item()\n            \n            scheduler.step()\n            current_lr = scheduler.get_last_lr()[0]\n            \n            # Only print every 5 epochs to keep log clean\n            if (epoch + 1) % 5 == 0:\n                avg_loss = running_loss / len(train_loader)\n                print(f\"   Epoch {epoch+1}/{NUM_EPOCHS} | Train Loss: {avg_loss:.4f} | LR: {current_lr:.6f}\")\n            \n        print(\"✅ Full Training Done.\")\n        \n        del optimizer, criterion, train_loader, train_ds, imgs, targets, scheduler\n        gc.collect()\n        torch.cuda.empty_cache()\n    except Exception as e:\n        print(f\"⚠️ Training Failed: {e}\")\n\n# ==========================================\n# 5. Inference & File Generation\n# ==========================================\nprint(\"🚀 Starting Inference...\")\nfinal_data = []\n\ntry:\n    if TEST_IMG_DIR and os.path.exists(TEST_IMG_DIR):\n        test_files = [f for f in os.listdir(TEST_IMG_DIR) if f.lower().endswith(('.png', '.jpg', '.jpeg'))]\n        test_ids = list(set([os.path.splitext(f)[0] for f in test_files]))\n        \n        if len(test_ids) > 0:\n            test_ds = BiomassDataset(None, TEST_IMG_DIR, transform=inference_transform, is_test=True, image_ids=test_ids)\n            test_loader = DataLoader(test_ds, batch_size=32, shuffle=False)\n            \n            model.eval()\n            with torch.no_grad():\n                for imgs, batch_ids in test_loader:\n                    imgs = imgs.to(DEVICE)\n                    outputs = model(imgs).cpu().numpy()\n                    \n                    for i, img_id in enumerate(batch_ids):\n                        preds = np.maximum(outputs[i], 0)\n                        for t_idx, t_name in enumerate(TARGET_COLS):\n                            final_data.append({\n                                'sample_id': f\"{img_id}__{t_name}\",\n                                'target': float(preds[t_idx])\n                            })\n    \n    # Fallback / Dummy\n    if len(final_data) == 0:\n        final_data.append({'sample_id': 'ID_000__Dry_Green_g', 'target': 0.0})\n\n    submission_df = pd.DataFrame(final_data)\n    submission_df = submission_df[['sample_id', 'target']]\n    submission_df.to_csv('/kaggle/working/submission.csv', index=False)\n    print(f\"✅ FINAL SUCCESS: Saved to /kaggle/working/submission.csv\")\n    print(submission_df.head())\n\nexcept Exception as e:\n    print(f\"❌ Inference Error: {e}\")","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}