{"metadata":{"kernelspec":{"display_name":"Python 3","language":"python","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":"none","dataSources":[{"sourceType":"competition","sourceId":112509,"databundleVersionId":14254895},{"sourceType":"datasetVersion","sourceId":15761382,"datasetId":10100749,"databundleVersionId":16705500}],"dockerImageVersionId":31328,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"import os\n\nimport pandas as pd\nimport numpy as np\nimport matplotlib.pyplot as plt\n%matplotlib inline\n\nfrom sklearn.model_selection import KFold, GroupKFold, StratifiedGroupKFold\n\nfrom PIL import Image\nfrom tqdm.auto import tqdm\ntqdm.pandas()\nfrom pathlib import Path\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-16T04:26:56.613225Z","iopub.execute_input":"2026-04-16T04:26:56.613618Z","iopub.status.idle":"2026-04-16T04:26:58.224058Z","shell.execute_reply.started":"2026-04-16T04:26:56.613521Z","shell.execute_reply":"2026-04-16T04:26:58.223141Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"DATA_ROOT = '/kaggle/input/competitions/csiro-biomass/'","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-16T04:27:00.299778Z","iopub.execute_input":"2026-04-16T04:27:00.300276Z","iopub.status.idle":"2026-04-16T04:27:00.305112Z","shell.execute_reply.started":"2026-04-16T04:27:00.300243Z","shell.execute_reply":"2026-04-16T04:27:00.303996Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_df = pd.read_csv(f'{DATA_ROOT}train.csv')\ntrain_df.head()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-16T04:27:01.000494Z","iopub.execute_input":"2026-04-16T04:27:01.000896Z","iopub.status.idle":"2026-04-16T04:27:01.074064Z","shell.execute_reply.started":"2026-04-16T04:27:01.000862Z","shell.execute_reply":"2026-04-16T04:27:01.073191Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_df[['sample_id_prefix', 'sample_id_suffix']] = train_df.sample_id.str.split('__', expand=True)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-16T04:27:01.251497Z","iopub.execute_input":"2026-04-16T04:27:01.251860Z","iopub.status.idle":"2026-04-16T04:27:01.261050Z","shell.execute_reply.started":"2026-04-16T04:27:01.251831Z","shell.execute_reply":"2026-04-16T04:27:01.260035Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"cols = ['sample_id_prefix', 'image_path', 'Sampling_Date', 'State', 'Species', 'Pre_GSHH_NDVI', 'Height_Ave_cm']\n\nagg_train_df = (\n    train_df\n    .pivot_table(index=cols, columns='target_name', values='target', aggfunc='first')\n    .reset_index()\n)\nagg_train_df.columns.name = None\n\nagg_train_df['image'] = agg_train_df.image_path.progress_apply(\n    lambda path: Image.open(DATA_ROOT + path).convert('RGB')\n)\nagg_train_df.head()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-16T04:27:01.388394Z","iopub.execute_input":"2026-04-16T04:27:01.388771Z","iopub.status.idle":"2026-04-16T04:27:30.786687Z","shell.execute_reply.started":"2026-04-16T04:27:01.388720Z","shell.execute_reply":"2026-04-16T04:27:30.785794Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"agg_train_df[['Dry_Green_g', 'Dry_Clover_g', 'GDM_g', 'Dry_Dead_g', 'Dry_Total_g']].dtypes\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-16T04:27:30.788204Z","iopub.execute_input":"2026-04-16T04:27:30.788485Z","iopub.status.idle":"2026-04-16T04:27:30.796645Z","shell.execute_reply.started":"2026-04-16T04:27:30.788457Z","shell.execute_reply":"2026-04-16T04:27:30.795817Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"target_cols = ['Dry_Green_g', 'Dry_Clover_g', 'GDM_g', 'Dry_Dead_g', 'Dry_Total_g']\nagg_train_df[target_cols] = agg_train_df[target_cols].apply(pd.to_numeric, errors='coerce')\nagg_train_df[target_cols].dtypes","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-16T04:27:30.797796Z","iopub.execute_input":"2026-04-16T04:27:30.798170Z","iopub.status.idle":"2026-04-16T04:27:30.819164Z","shell.execute_reply.started":"2026-04-16T04:27:30.798129Z","shell.execute_reply":"2026-04-16T04:27:30.818166Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"agg_train_df['image_size'] = agg_train_df.image.apply(lambda x: x.size)\nagg_train_df['image_size'].value_counts()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-16T04:27:30.821857Z","iopub.execute_input":"2026-04-16T04:27:30.822139Z","iopub.status.idle":"2026-04-16T04:27:30.841289Z","shell.execute_reply.started":"2026-04-16T04:27:30.822112Z","shell.execute_reply":"2026-04-16T04:27:30.840415Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"These are data integrity checks — verifying that certain columns are derived from others (i.e., the math adds up).\n\nCheck 1:\n\n\n```Dry_Green_g + Dry_Clover_g ≈ GDM_g```\nChecks what fraction of rows satisfy this. If the result is 1.0, then GDM_g (Green Dry Matter) is always the sum of its green and clover components.\n\nCheck 2:\n\n\n```GDM_g + Dry_Dead_g ≈ Dry_Total_g```\nChecks what fraction of rows satisfy this. Verifies that total dry matter = green dry matter + dead dry matter.\n\natol=1e-04 allows a tiny tolerance (±0.0001g) to account for floating point rounding. .mean() converts the boolean array to a proportion — 1.0 means 100% of rows pass.\n\nThe implied data hierarchy:\n```\nDry_Total_g\n├── GDM_g  (Green Dry Matter)\n│   ├── Dry_Green_g\n│   └── Dry_Clover_g\n└── Dry_Dead_g\n```\nIf both checks return 1.0, those relationships hold perfectly across the dataset. If not (e.g. 0.97), ~3% of rows have inconsistencies worth investigating.","metadata":{}},{"cell_type":"code","source":"GDM_g_average = np.isclose(\n    agg_train_df[['Dry_Green_g', 'Dry_Clover_g']].sum(axis=1),\n    agg_train_df['GDM_g'],\n    atol=1e-04\n).mean()\n\nDry_Total_g_average = np.isclose(\n    agg_train_df[['GDM_g', 'Dry_Dead_g']].sum(axis=1),\n    agg_train_df['Dry_Total_g'],\n    atol=1e-04\n).mean()\n\nprint(f'GDM_g is approximately equal to Dry_Green_g + Dry_Clover_g in {GDM_g_average:.2%} of the samples.')\nprint(f'Dry_Total_g is approximately equal to GDM_g + Dry_Dead_g in {Dry_Total_g_average:.2%} of the samples.')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-16T04:27:30.842329Z","iopub.execute_input":"2026-04-16T04:27:30.842586Z","iopub.status.idle":"2026-04-16T04:27:30.864622Z","shell.execute_reply.started":"2026-04-16T04:27:30.842549Z","shell.execute_reply":"2026-04-16T04:27:30.863763Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"plt.figure(figsize=(16, 4))\nplt.subplot(1, 3, 1)\nagg_train_df.Dry_Green_g.plot(kind='hist')\n_ = plt.title('Dry_Green_g')\n\nplt.subplot(1, 3, 2)\nagg_train_df.Dry_Clover_g.plot(kind='hist')\n_ = plt.title('Dry_Clover_g')\n\nplt.subplot(1, 3, 3)\nagg_train_df.Dry_Dead_g.plot(kind='hist')\n_ = plt.title('Dry_Dead_g')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-16T04:27:30.865960Z","iopub.execute_input":"2026-04-16T04:27:30.866336Z","iopub.status.idle":"2026-04-16T04:27:31.556821Z","shell.execute_reply.started":"2026-04-16T04:27:30.866296Z","shell.execute_reply":"2026-04-16T04:27:31.555624Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import matplotlib.pyplot as plt\n\nn = 6\nsamples = agg_train_df.sample(n)\n\nfig, axes = plt.subplots(2, 3, figsize=(14, 10))\nfor ax, (_, row) in zip(axes.flat, samples.iterrows()):\n    ax.imshow(row['image'])\n    ax.axis('off')\n\n    title = (\n        f\"{row['Species']} — {row['Sampling_Date']}\\n\"\n        f\"Dry_Total:  {row['Dry_Total_g']:.1f}g\\n\"\n        f\"  GDM:      {row['GDM_g']:.1f}g\\n\"\n        f\"    Green:  {row['Dry_Green_g']:.1f}g\\n\"\n        f\"    Clover: {row['Dry_Clover_g']:.1f}g\\n\"\n        f\"  Dead:     {row['Dry_Dead_g']:.1f}g\"\n    )\n    ax.set_title(title, fontsize=8, family='monospace', loc='left')\n\nplt.tight_layout()\nplt.show()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-16T04:27:31.557962Z","iopub.execute_input":"2026-04-16T04:27:31.558293Z","iopub.status.idle":"2026-04-16T04:27:33.090819Z","shell.execute_reply.started":"2026-04-16T04:27:31.558259Z","shell.execute_reply":"2026-04-16T04:27:33.089867Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Dataloader","metadata":{}},{"cell_type":"code","source":"from torch.utils.data import Dataset, DataLoader\nfrom torchvision import transforms\n\nclass RegressionDataset(Dataset):\n    def __init__(self, data, transform=None):\n        self.data = data\n        self.transform = transform\n\n    def __len__(self):\n        return self.data.shape[0]\n\n    def __getitem__(self, idx):\n        item = self.data.iloc[idx]\n        image = item.image\n\n        if self.transform:\n            image = self.transform(image)\n\n        targets = [item['Dry_Green_g'], item['Dry_Clover_g'], item['Dry_Dead_g']]\n        return image, targets\n\n\ndef create_dataloader(data, target_image_size=(256, 256), batch_size=32, shuffle=True, aug=True):    \n    if aug:\n        transform = transforms.Compose([\n            transforms.Resize(target_image_size),\n            transforms.RandomHorizontalFlip(p=0.5),\n            transforms.RandomVerticalFlip(p=0.5),\n            transforms.ColorJitter(brightness=0.1, contrast=0.1, saturation=0.1, hue=0.05),\n            transforms.ToTensor(),\n            transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])\n        ])\n    \n    else:\n        transform = transforms.Compose([\n            transforms.Resize(target_image_size),\n\n            transforms.ToTensor(),\n            transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])\n        ])\n\n    dataset = RegressionDataset(data, transform=transform)\n    print('dataset size:', len(dataset))\n    dataloader = DataLoader(dataset, batch_size=batch_size, shuffle=shuffle, num_workers=0)\n    return dataloader","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-16T04:27:33.092304Z","iopub.execute_input":"2026-04-16T04:27:33.092702Z","iopub.status.idle":"2026-04-16T04:27:41.199699Z","shell.execute_reply.started":"2026-04-16T04:27:33.092649Z","shell.execute_reply":"2026-04-16T04:27:41.198728Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Model","metadata":{}},{"cell_type":"code","source":"import timm, torch\n\nMODEL_NAME= 'efficientnet_b2'\nmodel = timm.create_model('efficientnet_b2', pretrained=False, num_classes=3)\nstate_dict = torch.load('/kaggle/input/datasets/sansethisak/efficientnet-b2-pretrained/best_mode_fold0.pth')\nmodel.load_state_dict(state_dict, strict=False)  # strict=False since num_classes differs\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-16T04:29:12.325030Z","iopub.execute_input":"2026-04-16T04:29:12.325883Z","iopub.status.idle":"2026-04-16T04:29:12.561186Z","shell.execute_reply.started":"2026-04-16T04:29:12.325842Z","shell.execute_reply":"2026-04-16T04:29:12.560228Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"TARGET_IMAGE_SIZE = model.pretrained_cfg['input_size'][1:]","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-16T04:29:12.562893Z","iopub.execute_input":"2026-04-16T04:29:12.563313Z","iopub.status.idle":"2026-04-16T04:29:12.567643Z","shell.execute_reply.started":"2026-04-16T04:29:12.563280Z","shell.execute_reply":"2026-04-16T04:29:12.566796Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import torch\nimport torch.nn as nn\nimport torch.optim as optim\nfrom torch.optim import AdamW\n\n# ======== Weighted R² ========\ndef weighted_r2_score(y_true: np.ndarray, y_pred: np.ndarray):\n    \"\"\"\n    y_true, y_pred: shape (N, 5): Green/Clover/Dead/GDM/Total\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\n\ndef calc_metric(outputs, targets):\n    '''\n        outputs/targets: shape (N, 3): Green/Clover/Dead\n    '''\n    y_true = np.column_stack((\n        targets,\n        targets[:, :2].sum(axis=1),\n        targets.sum(axis=1),\n    ))\n    \n    y_pred = np.column_stack((\n        outputs,\n        outputs[:, :2].sum(axis=1),\n        outputs.sum(axis=1),\n    ))\n    \n    weighted_r2, r2_scores = weighted_r2_score(y_true, y_pred)\n    return weighted_r2, r2_scores","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-16T04:29:12.632429Z","iopub.execute_input":"2026-04-16T04:29:12.632803Z","iopub.status.idle":"2026-04-16T04:29:12.642889Z","shell.execute_reply.started":"2026-04-16T04:29:12.632761Z","shell.execute_reply":"2026-04-16T04:29:12.641485Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def train_epoch(model, dataloader, criterion, optimizer, device):\n    model.train()\n    total_loss = 0\n\n    for images, targets in dataloader:\n        images = images.to(device)\n        targets = torch.stack(targets).T.float().to(device)\n\n        optimizer.zero_grad()\n        outputs = model(images)\n        loss = criterion(outputs, targets)\n        loss.backward()\n        optimizer.step()\n\n        total_loss += loss.item()\n\n    return total_loss / len(dataloader)\n\n\ndef validate(model, dataloader, criterion, device):\n    model.eval()\n    total_loss = 0\n    all_outputs = []\n    all_targets = []\n\n    with torch.no_grad():\n        for images, targets in dataloader:\n            images = images.to(device)\n            targets = torch.stack(targets).T.float().to(device)\n\n            outputs = model(images)\n            loss = criterion(outputs, targets)\n            total_loss += loss.item()\n\n            all_outputs.append(outputs.detach().cpu())\n            all_targets.append(targets.detach().cpu())\n\n    outputs = torch.cat(all_outputs).numpy()\n    targets = torch.cat(all_targets).numpy()\n    \n    weighted_r2, r2_scores = calc_metric(outputs, targets)\n\n    return total_loss / len(dataloader), weighted_r2, r2_scores","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-16T04:29:12.775423Z","iopub.execute_input":"2026-04-16T04:29:12.775807Z","iopub.status.idle":"2026-04-16T04:29:12.784588Z","shell.execute_reply.started":"2026-04-16T04:29:12.775774Z","shell.execute_reply":"2026-04-16T04:29:12.783875Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"NFOLD = 5\nkfold = KFold(n_splits=NFOLD, shuffle=True, random_state=42)\n\nagg_train_df['fold'] = None\nfor i, (trn_idx, val_idx) in enumerate(kfold.split(agg_train_df.index)):\n    agg_train_df.loc[val_idx, 'fold'] = i","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-16T04:29:12.913244Z","iopub.execute_input":"2026-04-16T04:29:12.914141Z","iopub.status.idle":"2026-04-16T04:29:12.923260Z","shell.execute_reply.started":"2026-04-16T04:29:12.914096Z","shell.execute_reply":"2026-04-16T04:29:12.922348Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def train_fold(data, fold):\n    device = torch.device('cuda:0' if torch.cuda.is_available() else 'cpu')\n    \n    # Hyperparameters\n    batch_size = 32\n    lr = 1e-3\n    patience = 10\n    num_epochs = 100\n\n    # model, loss, optimizer\n    PRETRAINED_WEIGHTS = '/kaggle/input/datasets/sansethisak/efficientnet-b2-pretrained/best_mode_fold0.pth'\n    \n    model = timm.create_model(MODEL_NAME, pretrained=False, num_classes=3)\n    # Load backbone weights, ignore classifier head mismatch\n    state_dict = torch.load(PRETRAINED_WEIGHTS, map_location=device)\n    model.load_state_dict(state_dict, strict=False)\n\n    model.to(device)\n\n    criterion = nn.SmoothL1Loss()  # nn.MSELoss()\n    optimizer = AdamW(model.parameters(), lr=lr)\n    \n    scheduler = optim.lr_scheduler.ReduceLROnPlateau(\n        optimizer, mode='max', factor=0.5, patience=patience//2\n    )\n    \n    # data\n    train_loader = create_dataloader(data[data.fold != fold], TARGET_IMAGE_SIZE, batch_size, shuffle=True, aug=True)\n    val_loader = create_dataloader(data[data.fold == fold], TARGET_IMAGE_SIZE, batch_size, shuffle=False, aug=False)\n\n    # Training loop\n    history = []\n    best_score = -float('inf')\n    epochs_without_improvement = 0\n    \n    for epoch in range(num_epochs):\n        train_loss = train_epoch(model, train_loader, criterion, optimizer, device)\n        val_loss, weighted_r2, r2_scores = validate(model, val_loader, criterion, device)\n        \n        scheduler.step(weighted_r2)\n\n        if epoch % 10 == 0:\n            print(f\"Epoch [{epoch}/{num_epochs}]: Train Loss: {train_loss:.4f}, Val Loss: {val_loss:.4f}, weighted_r2: {weighted_r2:.4f}, lr: {optimizer.param_groups[0]['lr']}\")\n        \n        history.append({\n            'train_loss': train_loss,\n            'val_loss': val_loss,\n            'weighted_r2': weighted_r2,\n            'r2_scores': r2_scores,\n        })\n    \n        if weighted_r2 > best_score:\n            best_score = weighted_r2\n            epochs_without_improvement = 0\n            torch.save(model.state_dict(), f'{OUTPUT_DIR}/best_mode_fold{fold}.pth')\n        \n        else:\n            epochs_without_improvement += 1\n        \n        if epochs_without_improvement >= patience:\n            print(f\"epoch={epoch}, {patience} 个 epoch\")\n            break\n\n    print(f\"\\nTraining completed. Best weighted_r2: {best_score:.4f}\")\n    \n    return history, best_score","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-16T04:33:02.485240Z","iopub.execute_input":"2026-04-16T04:33:02.485583Z","iopub.status.idle":"2026-04-16T04:33:02.497097Z","shell.execute_reply.started":"2026-04-16T04:33:02.485549Z","shell.execute_reply":"2026-04-16T04:33:02.496037Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"OUTPUT_DIR = 'trained_models/'\nif not os.path.exists(OUTPUT_DIR):\n    os.mkdir(OUTPUT_DIR)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-16T04:33:02.710144Z","iopub.execute_input":"2026-04-16T04:33:02.710898Z","iopub.status.idle":"2026-04-16T04:33:02.715625Z","shell.execute_reply.started":"2026-04-16T04:33:02.710852Z","shell.execute_reply":"2026-04-16T04:33:02.714474Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"NFOLD = 5\nall_best_score = []\n\nfor i in range(NFOLD):\n    print(f'### fold={i}')\n    history, best_score = train_fold(agg_train_df, fold=i)\n    all_best_score.append(best_score)\n    history = pd.DataFrame(history)\n\n    history.to_json(\n        f'{OUTPUT_DIR}/history_fold{i}.jsonl',\n        orient='records',\n        lines=True,\n        force_ascii=False,\n    )\n\n    # plot\n    plt.figure(figsize=(12, 4))\n\n    plt.subplot(1, 2, 1)\n    plt.title('Loss')\n    plt.plot(history.train_loss, label='train')\n    plt.plot(history.val_loss, label='val')\n    plt.legend()\n\n    plt.subplot(1, 2, 2)\n    plt.title('weighted_r2')\n    plt.plot(history.weighted_r2)\n    plt.show()\n\nprint('Avg CV:', np.mean(all_best_score))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-16T04:33:02.904604Z","iopub.execute_input":"2026-04-16T04:33:02.905328Z","iopub.status.idle":"2026-04-16T04:46:24.837354Z","shell.execute_reply.started":"2026-04-16T04:33:02.905293Z","shell.execute_reply":"2026-04-16T04:46:24.836312Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Inference","metadata":{}},{"cell_type":"code","source":"SAVED_MODELS = './trained_models/'\ndef predict(model, dataloader, device):\n    model.to(device)\n    model.eval()\n\n    all_outputs = []\n    with torch.no_grad():\n        for images, targets in dataloader:\n            images = images.to(device)\n    \n            outputs = model(images)\n            all_outputs.append(outputs.detach().cpu())\n    \n    outputs = torch.cat(all_outputs).numpy()\n    return outputs\n\ndef kfold_predict(dataloader):\n    device = torch.device('cuda:0' if torch.cuda.is_available() else 'cpu')\n    \n    all_preds = []\n    for model_file in Path(SAVED_MODELS).glob('*.pth'):\n        model = timm.create_model(MODEL_NAME, pretrained=False, num_classes=3)\n        model.load_state_dict(torch.load(model_file))\n\n        preds = predict(model, dataloader, device)\n        all_preds.append(preds)\n\n    avg_preds = np.mean(all_preds, axis=0)\n    return avg_preds","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-16T05:20:11.776528Z","iopub.execute_input":"2026-04-16T05:20:11.778248Z","iopub.status.idle":"2026-04-16T05:20:11.795361Z","shell.execute_reply.started":"2026-04-16T05:20:11.778191Z","shell.execute_reply":"2026-04-16T05:20:11.794280Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"test_df = pd.read_csv(DATA_ROOT + 'test.csv')\n\ntest_df['target'] = 0.0\ntest_df[['sample_id_prefix', 'sample_id_suffix']] = test_df.sample_id.str.split('__', expand=True)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-16T05:20:13.347784Z","iopub.execute_input":"2026-04-16T05:20:13.348248Z","iopub.status.idle":"2026-04-16T05:20:13.393835Z","shell.execute_reply.started":"2026-04-16T05:20:13.348209Z","shell.execute_reply":"2026-04-16T05:20:13.392988Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"cols = ['sample_id_prefix', 'image_path']\nagg_test_df = test_df.groupby(cols).apply(lambda df: df.set_index('target_name').target)\nagg_test_df.reset_index(inplace=True)\nagg_test_df.columns.name = None\n\nagg_test_df['image'] = agg_test_df.image_path.progress_apply(\n    lambda path: Image.open(DATA_ROOT + path).convert('RGB')\n)\n\nagg_test_df.head()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-16T05:20:14.280958Z","iopub.execute_input":"2026-04-16T05:20:14.281936Z","iopub.status.idle":"2026-04-16T05:20:14.532116Z","shell.execute_reply.started":"2026-04-16T05:20:14.281898Z","shell.execute_reply":"2026-04-16T05:20:14.531347Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"test_loader = create_dataloader(agg_test_df, TARGET_IMAGE_SIZE, 32, shuffle=False, aug=False)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-16T05:20:15.879773Z","iopub.execute_input":"2026-04-16T05:20:15.880355Z","iopub.status.idle":"2026-04-16T05:20:15.889447Z","shell.execute_reply.started":"2026-04-16T05:20:15.880320Z","shell.execute_reply":"2026-04-16T05:20:15.888523Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"preds = kfold_predict(test_loader)\nagg_test_df[['Dry_Green_g', 'Dry_Clover_g', 'Dry_Dead_g']] = preds\nagg_test_df['GDM_g'] = agg_test_df.Dry_Green_g + agg_test_df.Dry_Clover_g\nagg_test_df['Dry_Total_g'] = agg_test_df.GDM_g + agg_test_df.Dry_Dead_g\n\nagg_test_df.head()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-16T05:20:16.923567Z","iopub.execute_input":"2026-04-16T05:20:16.924347Z","iopub.status.idle":"2026-04-16T05:20:17.834231Z","shell.execute_reply.started":"2026-04-16T05:20:16.924311Z","shell.execute_reply":"2026-04-16T05:20:17.833311Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"cols = ['Dry_Clover_g', 'Dry_Dead_g', 'Dry_Green_g', 'Dry_Total_g', 'GDM_g']\nsub_df = agg_test_df.set_index('sample_id_prefix')[cols].stack()\nsub_df = sub_df.reset_index()\nsub_df.columns = ['sample_id_prefix', 'target_name', 'target']\n\nsub_df['sample_id'] = sub_df.sample_id_prefix + '__' + sub_df.target_name\n\ncols = ['sample_id', 'target']\nsub_df[cols].to_csv('submission.csv', index=False)  ","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-16T05:20:19.582316Z","iopub.execute_input":"2026-04-16T05:20:19.582670Z","iopub.status.idle":"2026-04-16T05:20:19.609085Z","shell.execute_reply.started":"2026-04-16T05:20:19.582629Z","shell.execute_reply":"2026-04-16T05:20:19.608078Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"print(len(agg_test_df))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-16T05:20:21.240297Z","iopub.execute_input":"2026-04-16T05:20:21.241017Z","iopub.status.idle":"2026-04-16T05:20:21.245928Z","shell.execute_reply.started":"2026-04-16T05:20:21.240983Z","shell.execute_reply":"2026-04-16T05:20:21.245040Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"!head submission.csv","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-16T05:20:22.146832Z","iopub.execute_input":"2026-04-16T05:20:22.147168Z","iopub.status.idle":"2026-04-16T05:20:22.562841Z","shell.execute_reply.started":"2026-04-16T05:20:22.147137Z","shell.execute_reply":"2026-04-16T05:20:22.561701Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}