{"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"},{"sourceId":4958,"sourceType":"datasetVersion","datasetId":2847}],"dockerImageVersionId":31234,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"# ============================================================\n# CSIRO - Image2Biomass Prediction\n# ============================================================\n\n# -----------------------------\n# 1. Imports & Setup\n# -----------------------------\nimport os\nimport random\nimport numpy as np\nimport pandas as pd\nfrom PIL import Image\nfrom tqdm import tqdm\n\nimport torch\nimport torch.nn as nn\nfrom torch.utils.data import Dataset, DataLoader\nfrom torchvision import transforms, models\n\n# -----------------------------\n# 2. Reproducibility\n# -----------------------------\ndef seed_everything(seed=42):\n    random.seed(seed)\n    np.random.seed(seed)\n    torch.manual_seed(seed)\n    torch.cuda.manual_seed_all(seed)\n\nseed_everything(42)\n\nDEVICE = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\nprint(\"Device:\", DEVICE)\n\n# -----------------------------\n# 3. Paths\n# -----------------------------\nTRAIN_CSV = \"/kaggle/input/csiro-biomass/train.csv\"\nTEST_CSV  = \"/kaggle/input/csiro-biomass/test.csv\"\nTRAIN_DIR = \"/kaggle/input/csiro-biomass/train\"\nTEST_DIR  = \"/kaggle/input/csiro-biomass/test\"\n\nPRETRAINED_PATH = \"/kaggle/input/pretrained-pytorch-models/resnet18-5c106cde.pth\"\n\n# -----------------------------\n# 4. Load Data\n# -----------------------------\ntrain_df = pd.read_csv(TRAIN_CSV)\ntest_df  = pd.read_csv(TEST_CSV)\n\ntrain_df[\"image_id\"] = train_df[\"sample_id\"].str.split(\"__\").str[0]\ntest_df[\"image_id\"]  = test_df[\"sample_id\"].str.split(\"__\").str[0]\n\nprint(\"Train shape:\", train_df.shape)\nprint(\"Test shape :\", test_df.shape)\n\n# -----------------------------\n# 5. Targets\n# -----------------------------\nTARGETS = [\n    \"Dry_Green_g\",\n    \"Dry_Dead_g\",\n    \"Dry_Clover_g\",\n    \"GDM_g\",\n    \"Dry_Total_g\",\n]\n\ntarget_to_idx = {t: i for i, t in enumerate(TARGETS)}\nTARGET_WEIGHTS = torch.tensor([0.1, 0.1, 0.1, 0.2, 0.5], device=DEVICE)\n\n# -----------------------------\n# 6. Target normalization\n# -----------------------------\nlog_targets = train_df.copy()\nlog_targets[\"log_target\"] = np.log1p(log_targets[\"target\"])\n\ntarget_stats = {}\nfor t in TARGETS:\n    vals = log_targets.loc[log_targets[\"target_name\"] == t, \"log_target\"]\n    target_stats[t] = (vals.mean(), vals.std() + 1e-6)\n\n# -----------------------------\n# 7. Transforms\n# -----------------------------\nIMG_SIZE = 224\n\ntrain_tfms = transforms.Compose([\n    transforms.Resize((IMG_SIZE, IMG_SIZE)),\n    transforms.RandomHorizontalFlip(),\n    transforms.RandomVerticalFlip(),\n    transforms.ColorJitter(0.1, 0.1, 0.1, 0.05),\n    transforms.ToTensor(),\n    transforms.Normalize(\n        mean=[0.485, 0.456, 0.406],\n        std =[0.229, 0.224, 0.225],\n    ),\n])\n\ntest_tfms = transforms.Compose([\n    transforms.Resize((IMG_SIZE, 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])\n\n# -----------------------------\n# 8. Dataset\n# -----------------------------\nclass BiomassDataset(Dataset):\n    def __init__(self, df, image_dir, train=True):\n        self.df = df\n        self.image_dir = image_dir\n        self.train = train\n        self.groups = df.groupby(\"image_id\")\n        self.image_ids = list(self.groups.groups.keys())\n\n    def __len__(self):\n        return len(self.image_ids)\n\n    def __getitem__(self, idx):\n        image_id = self.image_ids[idx]\n        rows = self.groups.get_group(image_id)\n\n        img_path = rows.iloc[0][\"image_path\"]\n        img = Image.open(\n            os.path.join(self.image_dir, os.path.basename(img_path))\n        ).convert(\"RGB\")\n\n        img = train_tfms(img) if self.train else test_tfms(img)\n\n        if not self.train:\n            return img, image_id\n\n        y = torch.zeros(len(TARGETS))\n        for _, r in rows.iterrows():\n            mu, sd = target_stats[r[\"target_name\"]]\n            y[target_to_idx[r[\"target_name\"]]] = (\n                np.log1p(r[\"target\"]) - mu\n            ) / sd\n\n        return img, y\n\n# -----------------------------\n# 9. DataLoaders\n# -----------------------------\nBATCH_SIZE = 32\n\ntrain_loader = DataLoader(\n    BiomassDataset(train_df, TRAIN_DIR, train=True),\n    batch_size=BATCH_SIZE,\n    shuffle=True,\n    num_workers=2,\n    pin_memory=True\n)\n\ntest_loader = DataLoader(\n    BiomassDataset(test_df, TEST_DIR, train=False),\n    batch_size=BATCH_SIZE,\n    shuffle=False\n)\n\n# -----------------------------\n# 10. Model\n# -----------------------------\nclass BiomassModel(nn.Module):\n    def __init__(self):\n        super().__init__()\n        base = models.resnet18(weights=None)\n\n        state = torch.load(PRETRAINED_PATH, map_location=\"cpu\", weights_only=False)\n        base.load_state_dict(state)\n\n        # Unfreeze layer3 + layer4\n        for p in base.parameters():\n            p.requires_grad = False\n        for p in base.layer3.parameters():\n            p.requires_grad = True\n        for p in base.layer4.parameters():\n            p.requires_grad = True\n\n        self.backbone = nn.Sequential(*list(base.children())[:-1])\n\n        self.head = nn.Sequential(\n            nn.Linear(512, 512),\n            nn.BatchNorm1d(512),\n            nn.ReLU(),\n            nn.Dropout(0.4),\n            nn.Linear(512, 256),\n            nn.BatchNorm1d(256),\n            nn.ReLU(),\n            nn.Dropout(0.3),\n            nn.Linear(256, len(TARGETS))\n        )\n\n    def forward(self, x):\n        x = self.backbone(x)\n        x = x.flatten(1)\n        return self.head(x)\n\nmodel = BiomassModel().to(DEVICE)\n\n# -----------------------------\n# 11. Loss & Optimizer\n# -----------------------------\ncriterion = nn.MSELoss(reduction=\"none\")\n\noptimizer = torch.optim.AdamW(\n    filter(lambda p: p.requires_grad, model.parameters()),\n    lr=3e-4,\n    weight_decay=1e-4\n)\n\nscheduler = torch.optim.lr_scheduler.CosineAnnealingLR(\n    optimizer, T_max=10\n)\n\n# -----------------------------\n# 12. Training\n# -----------------------------\nEPOCHS = 10\n\nfor epoch in range(EPOCHS):\n    model.train()\n    epoch_loss = 0.0\n\n    for imgs, targets in tqdm(train_loader, desc=f\"Epoch {epoch+1}/{EPOCHS}\"):\n        imgs = imgs.to(DEVICE)\n        targets = targets.to(DEVICE)\n\n        preds = model(imgs)\n        loss = criterion(preds, targets)\n        loss = (loss * TARGET_WEIGHTS).mean()\n\n        optimizer.zero_grad()\n        loss.backward()\n        optimizer.step()\n\n        epoch_loss += loss.item()\n\n    scheduler.step()\n    print(f\"Epoch {epoch+1} loss: {epoch_loss / len(train_loader):.4f}\")\n\n# -----------------------------\n# 13. Inference\n# -----------------------------\nmodel.eval()\nimage_preds = {}\n\nwith torch.no_grad():\n    for imgs, image_ids in tqdm(test_loader, desc=\"Inference\"):\n        imgs = imgs.to(DEVICE)\n        preds = model(imgs).cpu().numpy()\n\n        for i, img_id in enumerate(image_ids):\n            out = []\n            for j, t in enumerate(TARGETS):\n                mu, sd = target_stats[t]\n                out.append(np.expm1(preds[i, j] * sd + mu))\n            image_preds[img_id] = np.array(out)\n\n# -----------------------------\n# 14. Submission\n# -----------------------------\nsubmission_targets = [\n    image_preds[row[\"image_id\"]][target_to_idx[row[\"target_name\"]]]\n    for _, row in test_df.iterrows()\n]\n\nsubmission = pd.DataFrame({\n    \"sample_id\": test_df[\"sample_id\"],\n    \"target\": submission_targets\n})\n\nsubmission.to_csv(\"submission.csv\", index=False)\nprint(\"\\nSubmission saved as submission.csv\")\nsubmission.head()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-14T17:15:07.662180Z","iopub.execute_input":"2026-01-14T17:15:07.663012Z","iopub.status.idle":"2026-01-14T17:17:06.735611Z","shell.execute_reply.started":"2026-01-14T17:15:07.662961Z","shell.execute_reply":"2026-01-14T17:17:06.734640Z"}},"outputs":[],"execution_count":null}]}