{"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":112509,"databundleVersionId":14254895,"sourceType":"competition"}],"dockerImageVersionId":31153,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"import timm\nimport torch\nimport torch.nn as nn\nfrom torch.utils.data import Dataset, DataLoader\nfrom PIL import Image\nimport torchvision.transforms as T\nimport pytorch_lightning as pl\nfrom pytorch_lightning.callbacks import ModelCheckpoint\nimport random\nimport os\nimport numpy as np\nimport pandas as pd\nfrom sklearn.model_selection import KFold","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2025-10-29T06:30:34.775344Z","iopub.execute_input":"2025-10-29T06:30:34.775512Z","iopub.status.idle":"2025-10-29T06:30:52.295690Z","shell.execute_reply.started":"2025-10-29T06:30:34.775497Z","shell.execute_reply":"2025-10-29T06:30:52.295112Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ======== Seed ========\ndef seed_everything(seed: int = 42):\n    pl.seed_everything(seed, workers=True)\n    random.seed(seed)\n    np.random.seed(seed)\n    torch.manual_seed(seed)\n    torch.cuda.manual_seed_all(seed)\n    os.environ[\"PYTHONHASHSEED\"] = str(seed)\n    # \n    torch.backends.cudnn.deterministic = True\n    torch.backends.cudnn.benchmark = False\nseed_everything(42)","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ======== Weighted R² ========\ndef weighted_r2_score(y_true: np.ndarray, y_pred: np.ndarray):\n    \"\"\"\n    y_true, y_pred: shape (N, 5)\n    \"\"\"\n    weights = np.array([0.1, 0.1, 0.1, 0.2, 0.5])\n    r2_scores = []\n    for i in range(5):\n        y_t = y_true[:, i]\n        y_p = y_pred[:, i]\n        ss_res = np.sum((y_t - y_p) ** 2)\n        ss_tot = np.sum((y_t - np.mean(y_t)) ** 2)\n        r2 = 1 - ss_res / ss_tot if ss_tot > 0 else 0.0\n        r2_scores.append(r2)\n    r2_scores = np.array(r2_scores)\n    weighted_r2 = np.sum(r2_scores * weights) / np.sum(weights)\n    return weighted_r2, r2_scores","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-29T06:30:52.297251Z","iopub.execute_input":"2025-10-29T06:30:52.297635Z","iopub.status.idle":"2025-10-29T06:30:52.303149Z","shell.execute_reply.started":"2025-10-29T06:30:52.297616Z","shell.execute_reply":"2025-10-29T06:30:52.302377Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ======== Dataset ========\nclass ImageRegressionDataset(Dataset):\n    def __init__(self, df, transform=None):\n        self.df = df\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        image = Image.open('/kaggle/input/csiro-biomass/' + row[\"image_path\"]).convert(\"RGB\")\n        if self.transform:\n            image = self.transform(image)\n        targets = torch.tensor([\n            row[\"Dry_Green_g\"],\n            row[\"Dry_Dead_g\"],\n            row[\"Dry_Clover_g\"],\n            row[\"GDM_g\"],\n            row[\"Dry_Total_g\"]\n        ], dtype=torch.float32)\n        return image, targets","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-29T06:30:52.303958Z","iopub.execute_input":"2025-10-29T06:30:52.304215Z","iopub.status.idle":"2025-10-29T06:30:52.324896Z","shell.execute_reply.started":"2025-10-29T06:30:52.304193Z","shell.execute_reply":"2025-10-29T06:30:52.324269Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ======== Model ========\nclass MultiRegressionModel(pl.LightningModule):\n    def __init__(self, model_name=\"efficientnet_b0\", pretrained=False, lr=1e-4, output_dim=5):\n        super().__init__()\n        self.save_hyperparameters()\n        self.model = timm.create_model(model_name, pretrained=pretrained, num_classes=output_dim)\n        self.criterion = nn.SmoothL1Loss() # nn.MSELoss()\n        self.val_outputs = []\n\n    def forward(self, x):\n        return self.model(x)  # shape: (B, 5)\n\n    def training_step(self, batch, batch_idx):\n        x, y = batch\n        y_hat = self(x)\n        loss = self.criterion(y_hat, y)\n        self.log(\"train_loss\", loss, on_step=False, on_epoch=True)\n        return loss\n\n    def validation_step(self, batch, batch_idx):\n        x, y = batch\n        y_hat = self(x)\n        loss = self.criterion(y_hat, y)\n        self.val_outputs.append((y_hat.detach().cpu(), y.detach().cpu()))\n        self.log(\"val_loss\", loss, on_step=False, on_epoch=True)\n        return loss\n\n    def on_validation_epoch_end(self):\n        if len(self.val_outputs) == 0:\n            self.log(\"val_weighted_r2\", 0.0, prog_bar=True, on_epoch=True)\n            for name in [\"Dry_Green_g\", \"Dry_Dead_g\", \"Dry_Clover_g\", \"GDM_g\", \"Dry_Total_g\"]:\n                self.log(f\"val_r2_{name}\", 0.0, on_epoch=True)\n            self.val_outputs.clear()\n            return\n\n        preds, trues = zip(*self.val_outputs)\n        preds = torch.cat(preds).numpy()\n        trues = torch.cat(trues).numpy()\n        weighted_r2, r2s = weighted_r2_score(trues, preds)\n        self.log(\"val_weighted_r2\", weighted_r2, prog_bar=True, on_epoch=True)\n        for i, name in enumerate([\"Dry_Green_g\", \"Dry_Dead_g\", \"Dry_Clover_g\", \"GDM_g\", \"Dry_Total_g\"]):\n            self.log(f\"val_r2_{name}\", r2s[i], on_epoch=True)\n        self.val_outputs.clear()\n        return\n\n    def configure_optimizers(self):\n        optimizer = torch.optim.AdamW(self.parameters(), lr=self.hparams.lr)\n        scheduler = torch.optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max=10)\n        return {\"optimizer\": optimizer, \"lr_scheduler\": scheduler}","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-29T06:30:52.325507Z","iopub.execute_input":"2025-10-29T06:30:52.325683Z","iopub.status.idle":"2025-10-29T06:30:52.343132Z","shell.execute_reply.started":"2025-10-29T06:30:52.325662Z","shell.execute_reply":"2025-10-29T06:30:52.342564Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ======== DataModule ========\nclass ImageRegressionDataModule(pl.LightningDataModule):\n    def __init__(self, train_df, valid_df, batch_size=8, num_workers=4, img_size=1000):\n        super().__init__()\n        self.train_df = train_df\n        self.valid_df = valid_df\n        self.batch_size = batch_size\n        self.num_workers = num_workers\n        self.img_size = img_size\n\n    def setup(self, stage=None):\n        train_transform = T.Compose([\n            T.Resize((self.img_size, self.img_size)),\n            T.RandomHorizontalFlip(p=0.5),\n            T.RandomVerticalFlip(p=0.5),\n            T.ColorJitter(brightness=0.1, contrast=0.1, saturation=0.1, hue=0.05),\n            T.ToTensor(),\n            T.Normalize([0.485, 0.456, 0.406],\n                        [0.229, 0.224, 0.225])\n        ])\n        val_transform = T.Compose([\n            T.Resize((self.img_size, self.img_size)),\n            T.ToTensor(),\n            T.Normalize([0.485, 0.456, 0.406],\n                        [0.229, 0.224, 0.225])\n        ])\n        self.train_dataset = ImageRegressionDataset(self.train_df, train_transform)\n        self.val_dataset = ImageRegressionDataset(self.valid_df, val_transform)\n\n    def train_dataloader(self):\n        return DataLoader(self.train_dataset, batch_size=self.batch_size,\n                          shuffle=True, num_workers=self.num_workers)\n\n    def val_dataloader(self):\n        return DataLoader(self.val_dataset, batch_size=self.batch_size,\n                          shuffle=False, num_workers=self.num_workers)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-29T06:30:52.343873Z","iopub.execute_input":"2025-10-29T06:30:52.344126Z","iopub.status.idle":"2025-10-29T06:30:52.361382Z","shell.execute_reply.started":"2025-10-29T06:30:52.344104Z","shell.execute_reply":"2025-10-29T06:30:52.360653Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_df = pd.read_csv('/kaggle/input/csiro-biomass/train.csv')\ntrain_df = pd.pivot_table(train_df, index='image_path', columns=['target_name'], values='target').reset_index()\nkf = KFold(n_splits=5, random_state=0, shuffle=True)\nfor fold, (train_index, valid_index) in enumerate(kf.split(train_df)):\n    datamodule = ImageRegressionDataModule(train_df.iloc[train_index], train_df.iloc[valid_index])\n    model = MultiRegressionModel(model_name=\"efficientnet_b2\", pretrained=True, lr=1e-4)\n    \n    checkpoint_callback = ModelCheckpoint(\n        monitor=\"val_weighted_r2\",\n        save_top_k=1,\n        mode=\"max\",\n        filename=f\"best_model_fold{fold}\"\n    )\n    \n    trainer = pl.Trainer(\n        max_epochs=10,\n        accelerator=\"gpu\" if torch.cuda.is_available() else \"cpu\",\n        callbacks=[checkpoint_callback],\n        precision=\"16-mixed\",  # optional for faster training\n    )\n    trainer.fit(model, datamodule=datamodule)\n    torch.save(model.state_dict(), f\"model_fold{fold}.pth\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-29T06:30:52.362061Z","iopub.execute_input":"2025-10-29T06:30:52.362316Z","execution_failed":"2025-10-29T06:36:16.264Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}