{"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":"gpu","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":"markdown","source":"<div style=\"width: 100%; height: \"><h1 style=\"text-align: center; font-family: 'Roboto', sans-serif; background-color: rgba(117, 197, 186, 0.2); padding: 30px; border: 5px solid rgb(245, 235, 208); border-style: solid; border-radius: 10px;\"> CSIRO: Image2BioMass</h1></div>","metadata":{}},{"cell_type":"markdown","source":"<div class=\"alert alert-block alert-info\" style=\"font-family: 'Roboto', sans-serif;\n            background-color: rgba(218, 185, 114, 0.2);\n            border: 5px solid rgb(22, 122, 114);\n            border-style: solid;\n            border-radius: 10px;\n            padding: 30px;\">\n\n- This notebook explores a baseline solution with efficientnet for the CSIRO Image2BioMass Challenge aiming to predict the coverage of vegetation for a patch of land from images.\n\n- The Train Dataset provides 357 images, each of which record 5 targets - `'Dry_Clover_g' 'Dry_Dead_g' 'Dry_Green_g' 'Dry_Total_g' 'GDM_g'` \n\n- The Competition metric is individual-r2 score for each of the 5 targets","metadata":{}},{"cell_type":"code","source":"SUBMISSION = False\nimport numpy as np\nimport pandas as pd\nimport matplotlib.pyplot as plt\nimport seaborn as sns\n\n\nimport os\nimport sys\nfrom tqdm import tqdm \nimport sys\n\n#------------\n#--- data ---\n#------------\nDATA_DIR = r\"/kaggle/input/csiro-biomass\"\nTRAIN_DIR = os.path.join(DATA_DIR, 'train')\nTEST_DIR = os.path.join(DATA_DIR, 'test')\ndf = pd.read_csv(os.path.join(DATA_DIR, \"train.csv\"))\ndf_test = pd.read_csv(os.path.join(DATA_DIR, \"test.csv\"))\ndf_sample = pd.read_csv(os.path.join(DATA_DIR, \"sample_submission.csv\"))\nTARGET = \"target\"\n\n#-----------------\n#--- cosmetics ---\n#-----------------\nSNS_CMAP = 'BrBG'\nfrom rich.console import Console\nrc = Console(force_jupyter=False, color_system=\"truecolor\")\n\nimport warnings\nwarnings.filterwarnings('ignore')\n%load_ext autoreload\n%autoreload 2\n%matplotlib inline\n\nsns.set()\nsns.set_palette(SNS_CMAP)\n# --- dark mode---\n# plt.style.use(\"dark_background\")\n# plt.rcParams['grid.color'] = '#444444'\ncolors = sns.palettes.color_palette(SNS_CMAP)\npd.options.mode.chained_assignment = None\n\ndef rprint(text:str, rule:bool = False)->None:\n    rc.print(text)\n    if rule:\n        rc.rule(\"***\")\n\ndef df_head_regression(df, target_col=None, palette=\"viridis\", n=5, alpha=0.5):\n    \"\"\"\n    Highlights rows based on a continuous target column using gradient background colors.\n\n    Parameters:\n    - df: DataFrame to style\n    - target_col: column name to base row color on (default: last column)\n    - palette: Seaborn/Matplotlib palette name or list of colors\n    - n: number of rows to display\n    - alpha: transparency level (0 = fully transparent, 1 = opaque)\n    \"\"\"\n    if target_col is None:\n        target_col = df.columns[-1]\n\n    df_show = df.head(n)\n\n    # Normalize target values between 0 and 1\n    target_vals = df_show[target_col].astype(float)\n    norm = (target_vals - target_vals.min()) / (target_vals.max() - target_vals.min() + 1e-9)\n\n    # Get color palette\n    cmap = plt.get_cmap(palette)\n\n    def row_style(row):\n        val = row[target_col]\n        # Normalize this row’s target value\n        val_norm = (val - target_vals.min()) / (target_vals.max() - target_vals.min() + 1e-9)\n        # Get RGBA\n        r, g, b, _ = cmap(val_norm)\n        rgba = (int(r * 255), int(g * 255), int(b * 255), alpha)\n        return [f'background-color: rgba{rgba}'] * len(row)\n\n    return df_show.style.apply(row_style, axis=1)\n\ndf_head_regression(df, TARGET, SNS_CMAP, n=8)","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2025-11-30T08:11:47.852790Z","iopub.execute_input":"2025-11-30T08:11:47.852950Z","iopub.status.idle":"2025-11-30T08:11:50.866911Z","shell.execute_reply.started":"2025-11-30T08:11:47.852934Z","shell.execute_reply":"2025-11-30T08:11:50.866025Z"},"_kg_hide-input":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"agg_df = df.agg([\"nunique\", \"unique\", lambda x:x.isna().sum(), \"dtypes\"]).T\nagg_df['unique'] = agg_df['unique'].apply(lambda x: x if len(x)<10 else x[:10]) \nagg_df.style.apply(lambda s: [f'background-color: rgba({colors[0][0]*255}, {colors[0][1]*255}, {colors[0][2]*255}, 0.3)' if i % 2 == 0 else f'background-color: rgba({colors[5][0]*255}, {colors[5][1]*255}, {colors[5][2]*255}, 0.3)' for i in range(len(s))])","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-05T16:23:26.239982Z","iopub.execute_input":"2025-11-05T16:23:26.241129Z","iopub.status.idle":"2025-11-05T16:23:26.305209Z","shell.execute_reply.started":"2025-11-05T16:23:26.241081Z","shell.execute_reply":"2025-11-05T16:23:26.304088Z"},"jupyter":{"source_hidden":true},"_kg_hide-input":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# <div style=\"width: 100%\"><h1 style=\"text-align: center; font-family: 'Roboto', sans-serif; background-color: rgba(117, 197, 186, 0.2); padding: 30px; border: 5px solid rgb(245, 235, 208);; border-style: solid; border-radius: 10px;\"> 1. EDA</h1></div>","metadata":{"_kg_hide-input":true}},{"cell_type":"code","source":"if not SUBMISSION:\n    df_grouped = df.groupby('image_path')[[TARGET]].sum()\n    df_img = df_grouped.reset_index().merge(\n        df[['image_path', 'Species']], how='left', on='image_path')\n    \n    def visualize_multiclass_from_df(df, cols:int = 2):\n        species = df['Species'].unique()\n        rprint(f\"found {len(species)} unique species\")\n        rows = (len(species)+cols-1)//cols\n        fig, ax = plt.subplots(rows, cols, figsize=(15, 4*rows))\n        for idx, species_name in enumerate(species):\n            img_path = os.path.join(DATA_DIR, df[df['Species']==species_name].iloc[0][\"image_path\"])\n            img = cv2.imread(img_path)\n            img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)\n            ax.flatten()[idx].imshow(img)\n            # ax.flatten()[idx].axis('off')\n            ax.flatten()[idx].set_title(species_name)\n    \n        for axes in ax.flat:\n            axes.grid(False)\n            axes.set_xticks([])\n            axes.set_yticks([])\n            axes.set_xticklabels([])\n            axes.set_yticklabels([])\n            axes.set_frame_on(False)\n    \n        plt.tight_layout()\n        plt.show()\n    \n    visualize_multiclass_from_df(df_img)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-30T19:58:56.025171Z","iopub.execute_input":"2025-10-30T19:58:56.025519Z","iopub.status.idle":"2025-10-30T19:59:02.777189Z","shell.execute_reply.started":"2025-10-30T19:58:56.025493Z","shell.execute_reply":"2025-10-30T19:59:02.775552Z"},"jupyter":{"source_hidden":true},"_kg_hide-input":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"if not SUBMISSION:\n    fig, ax = plt.subplots(1, 2, figsize = (15, 7))\n    sns.histplot(\n        data=df,\n        x=TARGET,\n        bins=20,\n        hue=\"target_name\",\n        multiple=\"stack\",\n        palette=SNS_CMAP,\n        ax=ax[0]\n    )\n    sns.boxplot(\n        df,\n        y=TARGET,\n        x='target_name',\n        ax=ax[1],\n        palette = SNS_CMAP,\n    )\n    plt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-05T07:02:53.745816Z","iopub.execute_input":"2025-11-05T07:02:53.746191Z","iopub.status.idle":"2025-11-05T07:02:54.411203Z","shell.execute_reply.started":"2025-11-05T07:02:53.746168Z","shell.execute_reply":"2025-11-05T07:02:54.410307Z"},"jupyter":{"source_hidden":true},"_kg_hide-input":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import cv2\nfrom skimage.filters import sobel\nfrom skimage.measure import shannon_entropy\nimport matplotlib.gridspec as gridspec\n\ndef extract_feat(df: pd.DataFrame, data_dir: str) -> pd.DataFrame:\n    # Make a copy to avoid modifying the original DataFrame\n    df = df.copy()\n\n    # Prepare new columns\n    df[\"green_ratio\"] = np.nan\n    df[\"luminance\"] = np.nan\n    df[\"edge_density\"] = np.nan\n    df[\"color_entropy\"] = np.nan\n\n    # Iterate over rows with tqdm for progress\n    for i, df_row in tqdm(df.iterrows(), total=len(df), desc=\"Extracting features\"):\n        img_path = os.path.join(data_dir, df_row[\"image_path\"])\n        img = cv2.imread(str(img_path))\n        if img is None:\n            continue\n        img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)\n        img = img.astype(np.float32) / 255.0\n\n        R, G, B = img[..., 0], img[..., 1], img[..., 2]\n\n        # 1. Green ratio\n        mask_green = (G > B) & (G > R) & (G > 0.2)\n        df.at[i, \"green_ratio\"] = mask_green.mean()\n\n        # 2. Luminance\n        df.at[i, \"luminance\"] = np.mean(0.299 * R + 0.587 * G + 0.114 * B)\n\n        # 3. Edge density\n        gray = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY)\n        edge_map = sobel(gray / 255.0)\n        df.at[i, \"edge_density\"] = edge_map.mean()\n\n        # 4. Shannon entropy\n        df.at[i, \"color_entropy\"] = shannon_entropy(gray)\n\n    return df\n\ndef custom_plot(df):\n    fig = plt.figure(figsize=(15, 14))\n    outer = gridspec.GridSpec(2, 2, figure=fig, wspace=0.3, hspace=0.35)\n    \n    # --- Helper to create a joint-like set of axes inside an outer cell\n    def make_joint_axes(subplot_spec, main_height_ratio=4, main_width_ratio=4):\n        \"\"\"\n        Given an outer subplot_spec, returns (ax_main, ax_marg_x, ax_marg_y)\n        where:\n          - ax_main is the central joint plot,\n          - ax_marg_x is the marginal on top (shares x with main),\n          - ax_marg_y is the marginal on the right (shares y with main).\n        \"\"\"\n        inner = gridspec.GridSpecFromSubplotSpec(\n            2, 2,\n            subplot_spec=subplot_spec,\n            height_ratios=[1, main_height_ratio],\n            width_ratios=[main_width_ratio, 1],\n            hspace=0.05,\n            wspace=0.05\n        )\n        # create main first so top/right can share axes\n        ax_main = fig.add_subplot(inner[2])  # row=1,col=0 index=2\n        ax_marg_x = fig.add_subplot(inner[0], sharex=ax_main)  # row=0,col=0 index=0\n        ax_marg_y = fig.add_subplot(inner[3], sharey=ax_main)  # row=1,col=1 index=3\n    \n        # clean up marginal ticks so they don't overlap clutter\n        plt.setp(ax_marg_x.get_xticklabels(), visible=False)\n        ax_marg_x.tick_params(axis=\"x\", which=\"both\", bottom=False, top=False)\n        plt.setp(ax_marg_y.get_yticklabels(), visible=False)\n        ax_marg_y.tick_params(axis=\"y\", which=\"both\", left=False, right=False)\n    \n        return ax_main, ax_marg_x, ax_marg_y\n    \n    # -------------------------\n    # Top-left joint: green_ratio vs TARGET\n    ax_main_1, ax_top_1, ax_right_1 = make_joint_axes(outer[0])\n    \n    sns.kdeplot(\n        data=df, x=\"green_ratio\", y=\"Dry_Green_g\",\n        fill=True, cmap=\"Greens\", thresh=0.05, ax=ax_main_1, levels=6\n    )\n    sns.kdeplot(data=df, x=\"green_ratio\", fill=True, ax=ax_top_1, linewidth=1)\n    sns.kdeplot(data=df, y=\"Dry_Green_g\", fill=True, ax=ax_right_1, linewidth=1)\n    \n    ax_main_1.set_xlabel(\"green_ratio\")\n    ax_main_1.set_ylabel(\"Dry_Green_g\")\n    ax_top_1.set_title(\"Green ratio vs \" + str(\"Dry_Green_g\"))\n    \n    # -------------------------\n    # Bottom-left joint: luminance vs TARGET\n    ax_main_2, ax_top_2, ax_right_2 = make_joint_axes(outer[2])\n    \n    sns.kdeplot(\n        data=df, x=\"luminance\", y=\"Dry_Green_g\",\n        fill=True, cmap=\"Greens\", thresh=0.05, ax=ax_main_2, levels=6\n    )\n    sns.kdeplot(data=df, x=\"luminance\", fill=True, ax=ax_top_2, linewidth=1)\n    sns.kdeplot(data=df, y=\"Dry_Green_g\", fill=True, ax=ax_right_2, linewidth=1)\n    \n    ax_main_2.set_xlabel(\"luminance\")\n    ax_main_2.set_ylabel(\"Dry_Green_g\")\n    ax_top_2.set_title(\"Luminance vs \" + str(\"Dry_Green_g\"))\n    \n    # -------------------------\n    # Top-right: scatter edge_density vs TARGET (placed in outer[1])\n    ax_scatter1 = fig.add_subplot(outer[1])\n    sns.scatterplot(data=df, x=\"edge_density\", y=\"Dry_Clover_g\", hue=\"Dry_Green_g\", ax=ax_scatter1, s=20, alpha=0.6, palette=SNS_CMAP)\n    ax_scatter1.set_title(\"Edge density vs \" + str(\"Dry_Clover_g\"))\n    \n    # Bottom-right: scatter color_entropy vs TARGET (placed in outer[3])\n    ax_scatter2 = fig.add_subplot(outer[3])\n    sns.scatterplot(data=df[df[\"color_entropy\"]>14], x=\"color_entropy\", y=\"GDM_g\", hue=\"Dry_Green_g\", ax=ax_scatter2, s=20, alpha=0.6, palette=SNS_CMAP)\n    ax_scatter2.set_title(\"Color entropy vs \" + str(\"GDM_g\"))\n    \n    plt.tight_layout()\n    plt.show()\n\ndf =  df[[\"image_path\", \"target_name\", \"target\"]]\ndf = df.pivot_table(\n    index=[\"image_path\"],\n    columns='target_name',\n    values=\"target\").reset_index()\n\ndf = extract_feat(df, DATA_DIR)\ncustom_plot(df)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-26T15:50:59.822612Z","iopub.execute_input":"2025-11-26T15:50:59.823183Z","iopub.status.idle":"2025-11-26T15:52:11.175461Z","shell.execute_reply.started":"2025-11-26T15:50:59.823156Z","shell.execute_reply":"2025-11-26T15:52:11.174694Z"},"jupyter":{"source_hidden":true},"_kg_hide-input":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# <div style=\"width: 100%\"><h1 style=\"text-align: center; font-family: 'Roboto', sans-serif; background-color: rgba(117, 197, 186, 0.2); padding: 30px; border: 5px solid rgb(245, 235, 208);; border-style: solid; border-radius: 10px;\"> 2. EfficientNet Baseline</h1></div>","metadata":{}},{"cell_type":"code","source":"!pip install -q lightning\nimport torch\nfrom torchvision import datasets, transforms\nfrom torch.utils.data import DataLoader, random_split, Dataset\nfrom typing import Union, Optional, Callable\nfrom sklearn.preprocessing import StandardScaler\nfrom sklearn.base import BaseEstimator\nfrom sklearn.model_selection import train_test_split\nfrom PIL import Image\n\nimport lightning as L","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-30T08:11:50.868342Z","iopub.execute_input":"2025-11-30T08:11:50.868723Z","iopub.status.idle":"2025-11-30T08:13:14.874140Z","shell.execute_reply.started":"2025-11-30T08:11:50.868705Z","shell.execute_reply":"2025-11-30T08:13:14.873316Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class CSIRODataModule(L.LightningDataModule):\n\n    IMG_SIZE:int = 384\n    VAL_SPLIT:float = 0.1\n    NUM_WORKERS = 4\n\n    TRAIN_TRANSFORMS = transforms.Compose([\n        transforms.Resize((IMG_SIZE, IMG_SIZE)),\n        transforms.RandomAffine(\n            degrees=5,                     # very slight rotation\n            translate=(0.05, 0.05),        # ±5% translation\n            scale=None,\n            shear=None,\n        ),\n        transforms.RandomHorizontalFlip(p=0.5),\n    \n        #slight color transforms (since color is one of our imp signals)\n        transforms.ColorJitter(\n            brightness=0.10,\n            contrast=0.10,\n            saturation=0.05,\n            hue=0.02,\n        ),\n    \n        # light sharpness + noise\n        transforms.RandomAdjustSharpness(sharpness_factor=1.1, p=0.3),\n        transforms.GaussianBlur(kernel_size=3, sigma=(0.1, 1.0)),\n    \n        transforms.ToTensor(),\n        transforms.Normalize(mean=(0.485,0.456,0.406),\n                             std=(0.229,0.224,0.225)),\n    ])\n    \n    VAL_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    ])\n\n    TARGETS:list[str] = ['Dry_Clover_g', 'Dry_Dead_g', 'Dry_Green_g', 'Dry_Total_g', 'GDM_g']\n\n    def __init__(\n        self,\n        data_dir: str,\n        df: Union[pd.DataFrame, str],\n        batch_size:int = 128\n    )->None:\n        super().__init__()\n        self.data_dir = data_dir\n        self.df = df\n        self.batch_size = batch_size\n\n    # --dataclass\n    class ImageDataset(Dataset):\n        def __init__(self, image_dir, df, transform, targets, scaler, test=False):\n            self.samples = list(df.itertuples(index=False))\n            self.image_dir = image_dir\n            self.transform = transform\n            self.targets = targets\n            self.scaler = scaler\n            self.test = test\n\n        def __len__(self):\n            return len(self.samples)\n\n        def __getitem__(self, idx):\n            row = self.samples[idx]\n\n            # image\n            img_path = os.path.join(self.image_dir, row.image_path)\n            image = Image.open(img_path).convert(\"RGB\")\n            image = self.transform(image)\n\n            # targets\n            if not self.test:\n                t = torch.tensor([getattr(row, t) for t in self.targets], dtype=torch.float32)\n    \n                if self.scaler:\n                    t = torch.tensor(self.scaler.transform([t.numpy()])[0], dtype=torch.float32)\n            else:\n                t = torch.tensor([0, 0, 0, 0, 0])\n                \n            return image, t\n    #---\n\n    def setup(self, stage=None):\n\n        if stage == \"fit\" or stage is None:\n            df_train, df_val = train_test_split(\n                self.df, test_size=self.VAL_SPLIT, random_state=42, shuffle=True)\n            self.target_scaler = StandardScaler()\n            self.target_scaler.fit(df_train[CSIRODataModule.TARGETS])\n            \n            self.train_dataset = self.ImageDataset(\n                self.data_dir, df_train, self.TRAIN_TRANSFORMS, CSIRODataModule.TARGETS, self.target_scaler\n            )\n            self.val_dataset = self.ImageDataset(\n                self.data_dir, df_val, self.VAL_TRANSFORMS, CSIRODataModule.TARGETS, self.target_scaler\n            )\n            rprint(f\"FIT {self.__class__} with \\n --num_workers--: {self.NUM_WORKERS} \\n --batch_size--: {self.batch_size}, \\n [purple]train_images[/]: {len(df_train)} \\n [purple]val_images[/]: {len(df_val)}\\n image_size: {self.IMG_SIZE}\")\n\n    def train_dataloader(self):\n        return DataLoader(self.train_dataset, \n                          batch_size=self.batch_size, \n                          shuffle=True,\n                          num_workers=self.NUM_WORKERS)\n\n    def val_dataloader(self):\n        return DataLoader(self.val_dataset, \n                          batch_size=self.batch_size,\n                          num_workers=self.NUM_WORKERS)\n\ndf = pd.read_csv(os.path.join(DATA_DIR, \"train.csv\"))\ndf = df[[\"image_path\", \"target_name\", \"target\"]]\ndf = df.pivot_table(\n    index=[\"image_path\"],\n    columns='target_name',\n    values=\"target\").reset_index()\n\ndf_test = pd.read_csv(os.path.join(DATA_DIR, \"test.csv\"))\ndf_test = df_test[[\"image_path\"]].drop_duplicates().reset_index(drop=True)\n\nfor t in CSIRODataModule.TARGETS:\n    df_test[t] = 0","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-30T08:13:14.875098Z","iopub.execute_input":"2025-11-30T08:13:14.875656Z","iopub.status.idle":"2025-11-30T08:13:14.968116Z","shell.execute_reply.started":"2025-11-30T08:13:14.875614Z","shell.execute_reply":"2025-11-30T08:13:14.967497Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"#==================================\n#==       augmentations         ===\n#==================================\nif not SUBMISSION:\n    from copy import deepcopy\n    \n    img_path = os.path.join(DATA_DIR, df.iloc[0][\"image_path\"])\n    img = Image.open(img_path).convert(\"RGB\")\n    vis_transforms = deepcopy(CSIRODataModule.TRAIN_TRANSFORMS.transforms[1:-2])\n    \n    ncols = 2\n    nrows = (len(vis_transforms)+ncols-1)//ncols\n    fig, ax = plt.subplots(nrows, ncols, figsize=(15, 6*nrows))\n    ax = ax.flatten()\n    ax[0].axis(\"off\")\n    ax[0].set_title(\"Original\")\n    ax[0].imshow(img)\n    for i, transform in enumerate(vis_transforms):\n        try:\n            transform.p = 1.0\n        except:\n            pass\n        aug_img = transform(img)\n        ax[i+1].imshow(aug_img)\n        ax[i+1].set_title(type(transform).__name__)\n        ax[i+1].axis(\"off\")\n    \n    plt.tight_layout()\n    plt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-29T12:30:46.344974Z","iopub.execute_input":"2025-11-29T12:30:46.346046Z","iopub.status.idle":"2025-11-29T12:30:49.416948Z","shell.execute_reply.started":"2025-11-29T12:30:46.346019Z","shell.execute_reply":"2025-11-29T12:30:49.414939Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":".import torch.nn as nn\n\nclass EfficientNetModule(L.LightningModule):\n\n    TARGET_WEIGHTS = torch.tensor(\n        [0.1, 0.1, 0.1, 0.5, 0.2],\n        dtype=torch.float32\n    )\n\n    #========================\n    class EfficientNetCore(nn.Module):\n        def __init__(self):\n            super().__init__()\n            backbone = efficientnet_b0(weights=EfficientNet_B0_Weights.IMAGENET1K_V1)\n            in_feat = backbone.classifier[1].in_features\n            backbone.classifier = nn.Identity()\n    \n            self.backbone = backbone\n            self.head = nn.Linear(in_feat, 5)\n    \n        def forward(self, x):\n            x = self.backbone(x)\n            x = self.head(x)\n            return x\n    #========================\n            \n    def __init__(self,\n                 lr:float = 1e-2,\n                 target_scaler = None,\n                ):\n        super().__init__()\n        self.model = self.EfficientNetCore()\n        \n        self.lr:float = lr\n        self.loss_fn:nn.Module = nn.MSELoss()\n        self.target_scaler = target_scaler\n        self.register_buffer(\"target_weights_device\", self.TARGET_WEIGHTS)\n\n    def forward(self, x):\n        x = self.model(x)\n        return x\n\n    # ===== Custom Weighted Global R2 =====\n    def weighted_r2(self, y_true, y_pred):\n        # unscale before calculating metric\n        if self.target_scaler:\n            # y_true = torch.tensor(self.target_scaler.inverse_transform(y_true.cpu()), device=y_true.device)\n            # y_pred = torch.tensor(self.target_scaler.inverse_transform(y_pred.cpu()), device=y_pred.device)\n            y_true = torch.from_numpy(\n                self.target_scaler.inverse_transform(y_true.cpu())\n            ).float().to(y_true.device)\n\n            y_pred = torch.from_numpy(\n                self.target_scaler.inverse_transform(y_pred.cpu())\n            ).float().to(y_true.device)\n        \n        w = self.target_weights_device\n        w = w.unsqueeze(0)\n\n        ss_res = torch.sum(w * (y_true - y_pred) ** 2)\n        ss_tot = torch.sum(w * (y_true - torch.mean(y_true, dim=0, keepdim=True)) ** 2)\n\n        return 1 - ss_res / (ss_tot + 1e-8)\n\n    def configure_optimizers(self):\n        optimizer = torch.optim.Adam(self.parameters(), lr=self.lr)\n        scheduler = torch.optim.lr_scheduler.CosineAnnealingLR(optimizer, 20, 0.05*self.lr)\n        return [optimizer], [scheduler]\n\n    def training_step(self, batch, batch_idx):\n        x, y = batch\n        preds = self(x)\n        loss = self.loss_fn(preds, y)\n        self.log(f\"train_loss\", loss, prog_bar=True)\n        return loss\n    \n    def validation_step(self, batch, batch_idx):\n        x, y = batch\n        preds = self(x)\n        val_loss = self.loss_fn(preds, y)\n        r2_metric = self.weighted_r2(y, preds)\n        self.log_dict({\n            \"val_loss\": val_loss,\n            \"r2\": r2_metric,\n        })\n\n    def offload_wts(self, path=\"model.pt\"):\n        torch.save(self.model.state_dict(), path)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-30T08:13:39.283493Z","iopub.execute_input":"2025-11-30T08:13:39.283835Z","iopub.status.idle":"2025-11-30T08:13:39.338020Z","shell.execute_reply.started":"2025-11-30T08:13:39.283793Z","shell.execute_reply":"2025-11-30T08:13:39.337293Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"lt_data_module = CSIRODataModule(DATA_DIR, df, batch_size=64)\nlt_data_module.setup()\nmodel = EfficientNetModule(target_scaler = lt_data_module.target_scaler)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-30T08:13:45.583253Z","iopub.execute_input":"2025-11-30T08:13:45.583880Z","iopub.status.idle":"2025-11-30T08:13:45.988920Z","shell.execute_reply.started":"2025-11-30T08:13:45.583856Z","shell.execute_reply":"2025-11-30T08:13:45.988203Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"if not SUBMISSION:\n    from lightning.pytorch.loggers import WandbLogger\n    from kaggle_secrets import UserSecretsClient\n    import wandb\n    \n    wandb_key = UserSecretsClient().get_secret(\"WANDB\")\n    wandb.login(key=wandb_key)\n    \n    wandb_logger = WandbLogger(project=\"kaggle-CSIRO\")\n\nelse:\n    wandb_logger = None","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-30T08:13:46.060715Z","iopub.execute_input":"2025-11-30T08:13:46.060976Z","iopub.status.idle":"2025-11-30T08:13:58.535053Z","shell.execute_reply.started":"2025-11-30T08:13:46.060957Z","shell.execute_reply":"2025-11-30T08:13:58.534281Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from lightning.pytorch.callbacks import DeviceStatsMonitor\n\ntrainer = L.Trainer(\n    logger=wandb_logger,\n    # profiler=\"simple\",\n    # fast_dev_run=2,\n    devices=1,\n    precision=\"16-mixed\",\n    max_epochs=20,\n\n    callbacks=[DeviceStatsMonitor()]\n)\ntrainer.fit(\n    model,\n    lt_data_module\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-30T08:13:58.536119Z","iopub.execute_input":"2025-11-30T08:13:58.536556Z","iopub.status.idle":"2025-11-30T08:20:28.102601Z","shell.execute_reply.started":"2025-11-30T08:13:58.536538Z","shell.execute_reply":"2025-11-30T08:20:28.101968Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"model.offload_wts()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-30T08:20:28.103506Z","iopub.execute_input":"2025-11-30T08:20:28.103808Z","iopub.status.idle":"2025-11-30T08:20:28.202474Z","shell.execute_reply.started":"2025-11-30T08:20:28.103779Z","shell.execute_reply":"2025-11-30T08:20:28.201924Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"test_dataset = lt_data_module.ImageDataset(DATA_DIR,\n                                            df_test,\n                                            lt_data_module.VAL_TRANSFORMS,None,None,test=True)\n\ntest_dl = DataLoader(\n    test_dataset,\n    batch_size=32,\n    shuffle=False,\n    num_workers=4,\n)\n\nmodel.eval()\nall_preds = []\nwith torch.no_grad():\n    for imgs, _ in tqdm(test_dl):\n        preds = model(imgs)\n        if model.target_scaler is not None:\n            preds = model.target_scaler.inverse_transform(preds.cpu().numpy())\n        else:\n            preds = preds.cpu().numpy()\n\n        all_preds.append(preds)\n\nall_preds = np.vstack(all_preds)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-30T08:20:28.204048Z","iopub.execute_input":"2025-11-30T08:20:28.204306Z","iopub.status.idle":"2025-11-30T08:20:28.776438Z","shell.execute_reply.started":"2025-11-30T08:20:28.204289Z","shell.execute_reply":"2025-11-30T08:20:28.775630Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"%%capture\nstacked_preds = np.vstack(all_preds)\ndf_test[CSIRODataModule.TARGETS] = stacked_preds\n# wandb.finish()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-30T08:20:28.777579Z","iopub.execute_input":"2025-11-30T08:20:28.777925Z","iopub.status.idle":"2025-11-30T08:20:28.846329Z","shell.execute_reply.started":"2025-11-30T08:20:28.777902Z","shell.execute_reply":"2025-11-30T08:20:28.845680Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# <div style=\"width: 100%\"><h1 style=\"text-align: center; font-family: 'Roboto', sans-serif; background-color: rgba(117, 197, 186, 0.2); padding: 30px; border: 5px solid rgb(245, 235, 208);; border-style: solid; border-radius: 10px;\"> 3. Submission</h1></div>","metadata":{}},{"cell_type":"code","source":"df_res = df_test.melt(\n    id_vars=['image_path'],\n    value_vars=['Dry_Clover_g', 'Dry_Dead_g', 'Dry_Green_g', 'Dry_Total_g', 'GDM_g'],\n    var_name='target_name',\n    value_name='target'\n)\n\ndf_res[\"sample_id\"] = df_res.apply(lambda x: f\"{x['image_path'][5:-4]}__{x['target_name']}\", axis=1)\ndf_res = df_res[['sample_id', 'target']]\n\ndf_sub = pd.read_csv(r\"/kaggle/input/csiro-biomass/sample_submission.csv\")\ndf_sub = df_sub.drop('target', axis=1)\ndf_sub = df_sub.merge(right=df_res)\n\ndf_sub.to_csv(\"submission.csv\", index=False)\ndf_sub","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-30T08:20:28.847151Z","iopub.execute_input":"2025-11-30T08:20:28.847403Z","iopub.status.idle":"2025-11-30T08:20:28.937907Z","shell.execute_reply.started":"2025-11-30T08:20:28.847386Z","shell.execute_reply":"2025-11-30T08:20:28.937246Z"}},"outputs":[],"execution_count":null}]}