{"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"},{"sourceId":657579,"sourceType":"modelInstanceVersion","isSourceIdPinned":true,"modelInstanceId":497106,"modelId":512461},{"sourceId":657724,"sourceType":"modelInstanceVersion","isSourceIdPinned":true,"modelInstanceId":497227,"modelId":512574},{"sourceId":657732,"sourceType":"modelInstanceVersion","isSourceIdPinned":true,"modelInstanceId":497234,"modelId":512580},{"sourceId":666704,"sourceType":"modelInstanceVersion","isSourceIdPinned":true,"modelInstanceId":504672,"modelId":512580}],"dockerImageVersionId":31193,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"import re\nfrom pathlib import Path\nfrom copy import deepcopy\n\nimport numpy as np\nimport pandas as pd\nfrom PIL import Image\nfrom tqdm import tqdm\nimport matplotlib.pyplot as plt\nimport seaborn as sns\nfrom sklearn.model_selection import train_test_split, KFold, GroupKFold\n\nimport torch\nimport torch.nn as nn\nimport torch.nn.functional as F\nfrom torch.utils.data import Dataset, DataLoader, Subset\nfrom torch.amp import autocast, GradScaler\n\nimport torchvision\nimport torchvision.transforms as T\nfrom torchvision import models","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-01T08:44:50.579841Z","iopub.execute_input":"2025-12-01T08:44:50.580049Z","iopub.status.idle":"2025-12-01T08:45:00.153491Z","shell.execute_reply.started":"2025-12-01T08:44:50.580028Z","shell.execute_reply":"2025-12-01T08:45:00.152881Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"torch.backends.cudnn.benchmark = True\ndevice = \"cuda\" if torch.cuda.is_available() else \"cpu\"","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-01T08:45:00.155138Z","iopub.execute_input":"2025-12-01T08:45:00.155556Z","iopub.status.idle":"2025-12-01T08:45:00.213565Z","shell.execute_reply.started":"2025-12-01T08:45:00.155536Z","shell.execute_reply":"2025-12-01T08:45:00.212460Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"n_folds = 5\nn_epochs = 50\nbatch_size = 16\nlearning_rate = 1e-3\n\nweights = torch.tensor([0.1, 0.1, 0.1, 0.5, 0.2], device=device)\n\npretrained_path = \"/kaggle/input/efficientnetv2real/pytorch/m/1/efficientnet_v2_m-dc08266a.pth\"","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-01T08:45:00.214666Z","iopub.execute_input":"2025-12-01T08:45:00.214964Z","iopub.status.idle":"2025-12-01T08:45:00.380155Z","shell.execute_reply.started":"2025-12-01T08:45:00.214940Z","shell.execute_reply":"2025-12-01T08:45:00.379323Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"BASE_PATH = Path(\"/kaggle/input/csiro-biomass\")\n\ntrain_df = pd.read_csv(BASE_PATH / \"train.csv\")\ntest_df = pd.read_csv(BASE_PATH / \"test.csv\")\n\ntrain_df.info()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-01T08:45:00.380978Z","iopub.execute_input":"2025-12-01T08:45:00.381633Z","iopub.status.idle":"2025-12-01T08:45:00.424339Z","shell.execute_reply.started":"2025-12-01T08:45:00.381609Z","shell.execute_reply":"2025-12-01T08:45:00.423690Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_df.head()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-01T08:45:00.425071Z","iopub.execute_input":"2025-12-01T08:45:00.425336Z","iopub.status.idle":"2025-12-01T08:45:00.445402Z","shell.execute_reply.started":"2025-12-01T08:45:00.425308Z","shell.execute_reply":"2025-12-01T08:45:00.444666Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"test_df.head()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-01T08:45:00.446240Z","iopub.execute_input":"2025-12-01T08:45:00.446607Z","iopub.status.idle":"2025-12-01T08:45:00.453761Z","shell.execute_reply.started":"2025-12-01T08:45:00.446567Z","shell.execute_reply":"2025-12-01T08:45:00.453041Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_df.describe()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-01T08:45:00.456077Z","iopub.execute_input":"2025-12-01T08:45:00.456487Z","iopub.status.idle":"2025-12-01T08:45:00.478690Z","shell.execute_reply.started":"2025-12-01T08:45:00.456470Z","shell.execute_reply":"2025-12-01T08:45:00.477957Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_pivotted = train_df.pivot(\n    index=[\"image_path\", \"Sampling_Date\"],\n    # index=[\"image_path\", \"Sampling_Date\", \"State\"],\n    columns=\"target_name\",\n    values=\"target\",\n).reset_index()\n\ntrain_pivotted.columns","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-01T08:45:00.480191Z","iopub.execute_input":"2025-12-01T08:45:00.480647Z","iopub.status.idle":"2025-12-01T08:45:00.501373Z","shell.execute_reply.started":"2025-12-01T08:45:00.480628Z","shell.execute_reply":"2025-12-01T08:45:00.500813Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"samples = train_df.sample(3, random_state=42)\n\nfig, axes = plt.subplots(1, 3, figsize=(12, 4))\n\nfor ax, (_, row) in zip(axes, samples.iterrows()):\n    img = Image.open(BASE_PATH / row[\"image_path\"])\n    ax.imshow(img)\n    ax.set_title(f\"Species: {row['Species']}\", fontsize=10)\n    ax.axis(\"off\")\n\nplt.tight_layout()\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-01T08:45:00.502045Z","iopub.execute_input":"2025-12-01T08:45:00.502300Z","iopub.status.idle":"2025-12-01T08:45:01.736123Z","shell.execute_reply.started":"2025-12-01T08:45:00.502278Z","shell.execute_reply":"2025-12-01T08:45:01.735398Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"target_cols = [\"Dry_Clover_g\", \"Dry_Dead_g\", \"Dry_Green_g\", \"Dry_Total_g\", \"GDM_g\"]\ntrain_pivotted[target_cols].hist(bins=30, figsize=(12,6))\nplt.tight_layout()\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-01T08:45:01.736902Z","iopub.execute_input":"2025-12-01T08:45:01.737132Z","iopub.status.idle":"2025-12-01T08:45:02.615792Z","shell.execute_reply.started":"2025-12-01T08:45:01.737108Z","shell.execute_reply":"2025-12-01T08:45:02.615187Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"plt.figure(figsize=(8,6))\nsns.heatmap(train_pivotted[target_cols].corr(), annot=True, cmap=\"coolwarm\")\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-01T08:45:02.616511Z","iopub.execute_input":"2025-12-01T08:45:02.616826Z","iopub.status.idle":"2025-12-01T08:45:02.832276Z","shell.execute_reply.started":"2025-12-01T08:45:02.616808Z","shell.execute_reply":"2025-12-01T08:45:02.831640Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"test_unique = test_df[\"image_path\"].drop_duplicates().reset_index(drop=True)\n\ntest_unique.head()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-01T08:45:02.833162Z","iopub.execute_input":"2025-12-01T08:45:02.833472Z","iopub.status.idle":"2025-12-01T08:45:02.839683Z","shell.execute_reply.started":"2025-12-01T08:45:02.833446Z","shell.execute_reply":"2025-12-01T08:45:02.839156Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class TrainDataset(Dataset):\n    def __init__(self, df, transform = None):\n        self.df = df\n        self.transform = transform\n\n        self.image_col = \"image_path\"\n        self.target_cols = [\n            \"Dry_Clover_g\",\n            \"Dry_Dead_g\",\n            \"Dry_Green_g\",\n            \"Dry_Total_g\",\n            \"GDM_g\",\n        ]\n\n    def __len__(self):\n        return len(self.df)\n\n    def __getitem__(self, idx):\n        row = self.df.iloc[idx]\n        \n        image = Image.open(BASE_PATH / row[self.image_col]).convert(\"RGB\")\n        if self.transform:\n            image = self.transform(image)\n        \n        targets_array = np.array(row[self.target_cols].values, dtype=np.float32)\n        targets = torch.tensor(targets_array, dtype=torch.float32)\n        \n        return image, targets\n\nclass InferenceDataset(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        image_path = Path(self.df.iloc[idx])\n        image_id = image_path.stem\n        \n        image = Image.open(BASE_PATH / image_path).convert(\"RGB\")\n        if self.transform:\n            image = self.transform(image)\n        \n        return image, image_id","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-01T08:45:02.840556Z","iopub.execute_input":"2025-12-01T08:45:02.840845Z","iopub.status.idle":"2025-12-01T08:45:02.855676Z","shell.execute_reply.started":"2025-12-01T08:45:02.840828Z","shell.execute_reply":"2025-12-01T08:45:02.854801Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"T_train = T.Compose([\n    T.RandomResizedCrop(384),\n    T.RandomHorizontalFlip(p=0.75),\n    T.RandomVerticalFlip(p=0.75),\n    T.RandomApply([\n        # T.RandomRotation(degrees=30),\n        # T.ColorJitter(brightness=0.2, contrast=0.2, saturation=0.1, hue=0.05),\n        T.ColorJitter(brightness=0.2, contrast=0.2),\n    ], p=0.25),\n    T.ToTensor(),\n    T.Normalize(\n        mean=[0.485, 0.456, 0.406],\n        std=[0.229, 0.224, 0.225],\n    ),\n])\n\nT_val = T.Compose([\n    T.Resize((384, 384)),\n    T.ToTensor(),\n    T.Normalize(\n        mean=[0.485, 0.456, 0.406],\n        std=[0.229, 0.224, 0.225],\n    ),\n])","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-01T08:45:02.856426Z","iopub.execute_input":"2025-12-01T08:45:02.856619Z","iopub.status.idle":"2025-12-01T08:45:02.870662Z","shell.execute_reply.started":"2025-12-01T08:45:02.856604Z","shell.execute_reply":"2025-12-01T08:45:02.869880Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import matplotlib.pyplot as plt\nfrom torch.utils.data import DataLoader, Subset\nimport torchvision.transforms as T\nimport random\n\ntrain_dataset = TrainDataset(train_pivotted, transform=T_train)\nloader = DataLoader(train_dataset, batch_size=1, shuffle=False)\n\nfig, axs = plt.subplots(1, 4, figsize=(18, 8))\n\nfor i, (image, target) in enumerate(loader):\n    axs[i].imshow(image[0].permute(1, 2, 0).numpy())\n    axs[i].set_title(\"Augmented\")\n    axs[i].axis(\"off\")\n\n    if i == 3: break\n\nplt.tight_layout()\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-01T08:45:02.871363Z","iopub.execute_input":"2025-12-01T08:45:02.871687Z","iopub.status.idle":"2025-12-01T08:45:03.999790Z","shell.execute_reply.started":"2025-12-01T08:45:02.871665Z","shell.execute_reply":"2025-12-01T08:45:03.998875Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class BiomassRegressor(nn.Module):\n    def __init__(self, pretrained_path = None, device = device):\n        super().__init__()\n\n        self.backbone = models.efficientnet_v2_m(weights=None)\n        if pretrained_path:\n            state_dict = torch.load(pretrained_path, map_location=device)\n            self.backbone.load_state_dict(state_dict)\n\n        self.backbone.classifier = nn.Identity()\n\n        with torch.no_grad():\n            dummy = torch.randn(1, 3, 384, 384)\n            dummy_feat = self.backbone(dummy)\n        feature_dim = dummy_feat.shape[1]\n\n        self.dry_head = nn.Sequential(\n            nn.Linear(2 * feature_dim, feature_dim),\n            nn.ReLU(),\n            nn.Dropout(0.2),\n            nn.Linear(feature_dim, 2),\n        )\n        self.green_head = nn.Sequential(\n            nn.Linear(2 * feature_dim, feature_dim),\n            nn.ReLU(),\n            nn.Dropout(0.2),\n            nn.Linear(feature_dim, 1),\n        )\n        self.total_head = nn.Sequential(\n            nn.Linear(2 * feature_dim, feature_dim),\n            nn.ReLU(),\n            nn.Dropout(0.2),\n            nn.Linear(feature_dim, 2),\n        )\n\n    def forward(self, x):\n        _, _, H, W = x.shape\n        left = x[:, :, :, :W // 2]\n        right = x[:, :, :, W // 2:]\n\n        feat_left = self.backbone(left)\n        feat_right = self.backbone(right)\n\n        features = torch.cat([feat_left, feat_right], dim=1)\n\n        dry = self.dry_head(features)\n        green = self.green_head(features)\n        total = self.total_head(features)\n        \n        return torch.cat([dry, green, total], dim=1)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-01T08:45:04.000615Z","iopub.execute_input":"2025-12-01T08:45:04.000832Z","iopub.status.idle":"2025-12-01T08:45:04.009482Z","shell.execute_reply.started":"2025-12-01T08:45:04.000816Z","shell.execute_reply":"2025-12-01T08:45:04.008622Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def weighted_mse(preds, targets, weights):\n    mse = (preds - targets) ** 2\n    mse_weighted = (mse * weights).mean()\n    \n    return mse_weighted\n\ndef weighted_r2(preds, targets, weights):\n    weights_norm = weights / weights.sum()\n    \n    ss_res = ((targets - preds) ** 2).sum(dim=0)\n    ss_tot = ((targets - targets.mean(dim=0)) ** 2).sum(dim=0)\n    \n    r2_per_target = 1 - ss_res / ss_tot\n    r2_weighted = (r2_per_target * weights_norm).sum()\n    \n    return r2_weighted","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-01T08:45:04.010216Z","iopub.execute_input":"2025-12-01T08:45:04.010473Z","iopub.status.idle":"2025-12-01T08:45:04.025487Z","shell.execute_reply.started":"2025-12-01T08:45:04.010455Z","shell.execute_reply":"2025-12-01T08:45:04.024861Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class EarlyStopping:\n    \"\"\"\n    Early Stopping that uses EMA for patience-based checks,\n    but checks the hard threshold against the raw, unlagged metric.\n    \"\"\"\n    def __init__(self, patience=20, mode=\"max\", min_delta=1e-4, hard_thresh=0.6, alpha=0.9):\n        # Configuration\n        self.patience = patience\n        self.mode = mode\n        self.min_delta = min_delta\n        self.hard_thresh = hard_thresh\n        \n        # EMA parameters\n        self.alpha = alpha\n        self.ema_metric = None # Stores the current smoothed metric value\n\n        # Standard ES state variables\n        self.best = None       # Stores the best *smoothed* metric achieved\n        self.counter = 0\n        self.stop = False\n\n    def step(self, metric):\n        # 1. IMMEDIATE STOP CHECK (Uses RAW metric)\n        # If the raw R2 immediately hits the target, stop training immediately.\n        if metric >= self.hard_thresh:\n            self.stop = True\n            return self.stop\n            \n        # 2. Calculate the Exponential Moving Average (EMA)\n        if self.ema_metric is None:\n            self.ema_metric = metric\n        else:\n            # EMA calculation\n            self.ema_metric = (self.alpha * self.ema_metric) + ((1 - self.alpha) * metric)\n        \n        current_smoothed_metric = self.ema_metric \n\n        # 3. PATIENCE STOP LOGIC (Uses SMOOTHED metric)\n        if self.best is None:\n            # Initialize the best score with the first smoothed metric\n            self.best = current_smoothed_metric\n        else:\n            # Check for improvement using the smoothed metric\n            improve = (\n                current_smoothed_metric > self.best + self.min_delta\n                if self.mode == \"max\"\n                else current_smoothed_metric < self.best - self.min_delta\n            )\n    \n            if improve:\n                # Update best score with the new smoothed metric\n                self.best = current_smoothed_metric\n                self.counter = 0\n            else:\n                self.counter += 1\n    \n            if self.counter >= self.patience:\n                self.stop = True\n\n        return self.stop","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-01T08:45:04.026367Z","iopub.execute_input":"2025-12-01T08:45:04.026604Z","iopub.status.idle":"2025-12-01T08:45:04.043922Z","shell.execute_reply.started":"2025-12-01T08:45:04.026588Z","shell.execute_reply":"2025-12-01T08:45:04.043316Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_pivotted[\"Group_Key\"] = (\n    # train_pivotted[\"State\"].astype(str) + \"_\" +\n    train_pivotted[\"Sampling_Date\"].astype(str)\n)\ngroups = train_pivotted[\"Group_Key\"].values\nkf = GroupKFold(n_splits=n_folds)\nscaler = GradScaler(device)\n\nfor fold, (train_idx, val_idx) in enumerate(kf.split(train_pivotted, groups=groups)):\n    print(f\"\\n{'Fold ' + str(fold + 1) + '/' + str(n_folds):^40}\\n{'=' * 40}\")\n\n    train_sub = Subset(TrainDataset(train_pivotted, transform=T_train), train_idx)\n    val_sub = Subset(TrainDataset(train_pivotted, transform=T_val), val_idx)\n\n    train_loader = DataLoader(train_sub, batch_size=batch_size, shuffle=True, num_workers=2, pin_memory=True)\n    val_loader = DataLoader(val_sub, batch_size=batch_size, shuffle=False, num_workers=2, pin_memory=True)\n\n    # model = BiomassRegressor().to(device)\n    model = BiomassRegressor(pretrained_path=pretrained_path).to(device)\n    model = model.to(memory_format=torch.channels_last)\n    optimizer = torch.optim.AdamW(model.parameters(), lr=learning_rate)\n    criterion = lambda preds, targets: weighted_mse(preds, targets, weights)\n    # scheduler = torch.optim.lr_scheduler.ReduceLROnPlateau(\n    #     optimizer, mode=\"max\", factor=0.5, patience=3,\n    # )\n    scheduler = torch.optim.lr_scheduler.OneCycleLR(\n        optimizer,\n        max_lr=learning_rate,\n        steps_per_epoch=len(train_loader),\n        epochs=n_epochs,\n        pct_start=0.1,\n        anneal_strategy=\"cos\",\n        div_factor=25.0,\n        final_div_factor=1e4,\n    )\n    early_stopper = EarlyStopping(patience=10, mode=\"max\", hard_thresh=0.6, alpha=0.9)\n\n    train_losses = []\n    val_losses = []\n\n    for epoch in range(1, n_epochs + 1):\n        model.train()\n        train_preds = []\n        train_targets = []\n        for images, targets in tqdm(train_loader):\n            images, targets = images.to(device), targets.to(device)\n            images = images.to(device, memory_format=torch.channels_last)\n            # targets = torch.log1p(targets)\n            optimizer.zero_grad()\n            with autocast(device, dtype=torch.float16):\n                outputs = model(images)\n                # outputs = model(images).to(torch.float32)\n                loss = criterion(outputs, targets)\n            scaler.scale(loss).backward()\n            scaler.unscale_(optimizer)\n            nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0)\n            scaler.step(optimizer)\n            scaler.update()\n            train_preds.append(outputs.detach())\n            train_targets.append(targets)\n\n        train_preds_concat = torch.cat(train_preds)\n        train_targets_concat = torch.cat(train_targets)\n\n        # train_preds_concat = torch.expm1(torch.cat(train_preds))\n        # train_targets_concat = torch.expm1(torch.cat(train_targets))\n        \n        train_r2 = weighted_r2(train_preds_concat, train_targets_concat, weights)\n        train_mse = weighted_mse(train_preds_concat, train_targets_concat, weights)\n        \n        model.eval()\n        val_preds = []\n        val_targets = []\n        with torch.no_grad(), autocast(device, dtype=torch.float16):\n            for images, targets in val_loader:\n                images, targets = images.to(device), targets.to(device)\n                images = images.to(device, memory_format=torch.channels_last)\n                outputs = model(images)\n                outputs = model(images).to(torch.float32)\n                val_preds.append(outputs)\n                val_targets.append(targets)\n        \n        val_preds_concat = torch.cat(val_preds)\n        # val_preds_concat = torch.expm1(torch.cat(val_preds))\n        val_targets_concat = torch.cat(val_targets)\n        \n        val_r2 = weighted_r2(val_preds_concat, val_targets_concat, weights)\n        val_mse = weighted_mse(val_preds_concat, val_targets_concat, weights)\n        \n        # scheduler.step(val_r2)\n        scheduler.step()\n        \n        print(\n            f\"Epoch {epoch}/{n_epochs}:\",\n            f\"\\n * Train MSE = {train_mse:.4f} | Train R2 = {train_r2:.4f}\",\n            f\"\\n * Val MSE = {val_mse:.4f} | Val R2 = {val_r2:.4f}\",\n        )\n\n        train_losses.append(train_mse.cpu().numpy())\n        val_losses.append(val_mse.cpu().numpy())\n\n        if early_stopper.step(val_r2):\n            print(\"=============== [ STOP ] ===============\")\n            break\n\n    plt.plot(train_losses, label=\"Train\")\n    plt.plot(val_losses, label=\"Val\")\n\n    plt.legend()\n\n    plt.show()\n    \n    fold_path = f\"fold_{fold + 1}_model.pth\"\n    torch.save(model.state_dict(), fold_path)\n    print(f\"Saved fold {fold + 1} model to {fold_path}!\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-01T08:46:17.951828Z","iopub.execute_input":"2025-12-01T08:46:17.952717Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"fold_models = []\nfor fold in range(n_folds):\n    model = BiomassRegressor()\n    model.load_state_dict(torch.load(f\"fold_{fold + 1}_model.pth\"))\n    model.to(device)\n    fold_models.append(model)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-01T08:45:04.100521Z","iopub.status.idle":"2025-12-01T08:45:04.100722Z","shell.execute_reply.started":"2025-12-01T08:45:04.100623Z","shell.execute_reply":"2025-12-01T08:45:04.100632Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"test_ds = InferenceDataset(test_unique, transform=T_val)\ntest_loader = DataLoader(test_ds, batch_size=batch_size, shuffle=False)\n\ntarget_names = [\"Dry_Clover_g\", \"Dry_Dead_g\", \"Dry_Green_g\", \"Dry_Total_g\", \"GDM_g\"]\n\nresults = []\nwith torch.no_grad():\n    for images, image_ids in tqdm(test_loader):\n        images = images.to(device)\n        fold_preds = []\n        for model in fold_models:\n            model.eval()\n            outputs = model(images)\n            # outputs = model(images).to(torch.float32)\n            fold_preds.append(outputs)\n        ensemble_preds = torch.stack(fold_preds).mean(dim=0).cpu()\n        # ensemble_preds = torch.expm1(torch.stack(fold_preds).mean(dim=0).cpu())\n        for img_id, pred in zip(image_ids, ensemble_preds):\n            for t_idx, t_name in enumerate(target_names):\n                row_id = f\"{img_id}__{t_name}\"\n                value = pred[t_idx].item()\n                results.append((row_id, value))\n\nsubmission = pd.DataFrame(results, columns=[\"sample_id\", \"target\"])\nsubmission.to_csv(\"submission.csv\", index=False)\n\nsubmission","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-01T08:45:04.101739Z","iopub.status.idle":"2025-12-01T08:45:04.101974Z","shell.execute_reply.started":"2025-12-01T08:45:04.101869Z","shell.execute_reply":"2025-12-01T08:45:04.101879Z"}},"outputs":[],"execution_count":null}]}