{"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":716775,"sourceType":"modelInstanceVersion","isSourceIdPinned":true,"modelInstanceId":544891,"modelId":557953}],"dockerImageVersionId":31236,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"# Approach\n- Use a pretrained `resnet50` that is compatible with `torchvision` and upload it\n- Combine it wth transforms\n- Use `log1p` targets to avoid skewing towards large examples\n- Augmentations (croppiing, resizing, etc.)\n- Validate using the same metric (weighted R²)\n- Inference + construct `submission.csv`\n\n---\n## Imports, setup\n- Ignore `UnsupportedFieldAttributeWarning` for cleaner output cells\n- Ignore albumentations phoning home to `check_version` info ","metadata":{}},{"cell_type":"code","source":"import warnings\nwarnings.filterwarnings(\"ignore\")\n\nimport os, random, math, time\nimport numpy as np\nimport pandas as pd\n\nfrom PIL import Image\n\nimport torch\nimport torch.nn as nn\nfrom torch.utils.data import Dataset, DataLoader\n\nimport timm\nimport albumentations as A\nfrom albumentations.pytorch import ToTensorV2\n\n!nvidia-smi\n\n# Seed\ndef init_seed(seed=52):\n    random.seed(seed)\n    np.random.seed(seed)\n    torch.manual_seed(seed)\n    torch.cuda.manual_seed_all(seed)\n    torch.backends.cudnn.deterministic = False\n    torch.backends.cudnn.benchmark = True\n\ninit_seed()\n\n_device = \"cuda\" if torch.cuda.is_available() else \"cpu\"\nprint(\"Device:\", _device)\n\n# Paths\n_data_directory = \"/kaggle/input/csiro-biomass/\"\n_train_image_directory = os.path.join(_data_directory, \"train\")\n_test_image_directory  = os.path.join(_data_directory, \"test\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-12T05:42:15.799356Z","iopub.execute_input":"2026-01-12T05:42:15.799544Z","iopub.status.idle":"2026-01-12T05:43:01.805258Z","shell.execute_reply.started":"2026-01-12T05:42:15.799524Z","shell.execute_reply":"2026-01-12T05:43:01.804311Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"- Seed with a lucky number + CUDA check (GPU T4 x2)","metadata":{}},{"cell_type":"code","source":"train_df = pd.read_csv(os.path.join(_data_directory, \"train.csv\"))\ntest_df  = pd.read_csv(os.path.join(_data_directory, \"test.csv\"))\n\n# Extract image_id\ntrain_df[\"image_id\"] = (\n    train_df[\"image_path\"]\n    .str.replace(\".jpg\", \"\", regex=False)\n    .str.replace(\"train/\", \"\", regex=False)\n)\n\n# Pivot to wide format: 1 row per image, 15 target columns\nwide = (\n    train_df.pivot(index=\"image_id\", columns=\"target_name\", values=\"target\")\n    .reset_index()\n)\n\n# Attach image_path\npaths = train_df[[\"image_id\", \"image_path\"]].drop_duplicates(\"image_id\")\nwide = wide.merge(paths, on=\"image_id\", how=\"left\")\n\nprint(\"wide shape:\", wide.shape)\nprint(\"columns:\", wide.columns.tolist())\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-12T05:43:08.209299Z","iopub.execute_input":"2026-01-12T05:43:08.209598Z","iopub.status.idle":"2026-01-12T05:43:08.288910Z","shell.execute_reply.started":"2026-01-12T05:43:08.209565Z","shell.execute_reply":"2026-01-12T05:43:08.288301Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"_targets = [\"Dry_Green_g\", \"Dry_Dead_g\", \"Dry_Clover_g\", \"GDM_g\", \"Dry_Total_g\"]\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-12T05:43:11.292811Z","iopub.execute_input":"2026-01-12T05:43:11.293118Z","iopub.status.idle":"2026-01-12T05:43:11.297061Z","shell.execute_reply.started":"2026-01-12T05:43:11.293092Z","shell.execute_reply":"2026-01-12T05:43:11.296497Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"- Load the image directories and setup dataframes\n- Define the targets and weights","metadata":{}},{"cell_type":"code","source":"ids = wide[\"image_id\"].values\nnp.random.seed(52)\nnp.random.shuffle(ids)\n\ncut = int(0.85 * len(ids))\ntrain_ids = set(ids[:cut])\neval_ids  = set(ids[cut:])\n\ntrain_wide = wide[wide[\"image_id\"].isin(train_ids)].reset_index(drop=True)\neval_wide  = wide[wide[\"image_id\"].isin(eval_ids)].reset_index(drop=True)\n\nprint(len(train_wide), len(eval_wide))\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-12T05:43:13.902496Z","iopub.execute_input":"2026-01-12T05:43:13.902803Z","iopub.status.idle":"2026-01-12T05:43:13.911909Z","shell.execute_reply.started":"2026-01-12T05:43:13.902774Z","shell.execute_reply":"2026-01-12T05:43:13.911260Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"- Define train and evail transforms\n- Include standard ImageNet mean and std","metadata":{}},{"cell_type":"code","source":"IMAGENET_MEAN = (0.485, 0.456, 0.406) \nIMAGENET_STD = (0.229, 0.224, 0.225) \n_image_size = 224 \ntrain_tfms = A.Compose([ \n    A.RandomResizedCrop( \n        size=(_image_size, _image_size), \n        scale=(0.6, 1.0), \n        ratio=(0.75, 1.33)), \n    A.HorizontalFlip(p=0.5), \n    A.VerticalFlip(p=0.2), \n    A.ShiftScaleRotate(\n        shift_limit=0.05, \n        scale_limit=0.1, \n        rotate_limit=15, p=0.5 ),\n    A.RandomBrightnessContrast(p=0.3), \n    A.HueSaturationValue(p=0.3), \n    A.GaussianBlur(p=0.2), \n    A.RandomShadow(p=0.2), \n    A.RandomFog(p=0.1), \n    A.Normalize(mean=IMAGENET_MEAN, std=IMAGENET_STD), \n    ToTensorV2(), ]) \n\neval_tfms = A.Compose([ \n    A.Resize( height=_image_size, width=_image_size ),\n    A.Normalize(mean=IMAGENET_MEAN, std=IMAGENET_STD), ToTensorV2(), ])","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-12T05:43:16.772355Z","iopub.execute_input":"2026-01-12T05:43:16.772642Z","iopub.status.idle":"2026-01-12T05:43:16.788237Z","shell.execute_reply.started":"2026-01-12T05:43:16.772618Z","shell.execute_reply":"2026-01-12T05:43:16.787619Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"- One dataset compatible with both the train and eval datasets","metadata":{}},{"cell_type":"code","source":"class BiomassDataset(Dataset):\n    def __init__(self, df, root, targets, tfms=None):\n        self.df = df\n        self.root = root\n        self.targets = targets\n        self.tfms = tfms\n\n    def __len__(self):\n        return len(self.df)\n\n    def __getitem__(self, idx):\n        row = self.df.iloc[idx]\n        path = os.path.join(self.root, row[\"image_path\"])\n        img = Image.open(path).convert(\"RGB\")\n        img = np.array(img)\n\n        if self.tfms:\n            img = self.tfms(image=img)[\"image\"]\n\n        y = row[self.targets].values.astype(np.float32)\n        y_log = np.log1p(y)\n\n        return {\n            \"image\": img,\n            \"y\": torch.tensor(y_log, dtype=torch.float32),\n            \"image_id\": row[\"image_id\"],\n        }\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-12T05:43:19.658390Z","iopub.execute_input":"2026-01-12T05:43:19.659017Z","iopub.status.idle":"2026-01-12T05:43:19.664685Z","shell.execute_reply.started":"2026-01-12T05:43:19.658988Z","shell.execute_reply":"2026-01-12T05:43:19.664050Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"- Combine the multi-target regressor with resnet50","metadata":{}},{"cell_type":"code","source":"class MultiTargetRegressor(nn.Module):\n    def __init__(self, backbone, n_targets):\n        super().__init__()\n        self.backbone = backbone\n\n        # Handle both cases:\n        if hasattr(backbone, \"fc\"):\n            if isinstance(backbone.fc, nn.Identity):\n                # fc already removed earlier\n                in_features = 2048   # ResNet50 default\n            else:\n                in_features = backbone.fc.in_features\n                backbone.fc = nn.Identity()\n        else:\n            raise ValueError(\"Backbone has no .fc attribute\")\n\n        self.head = nn.Sequential(\n            nn.LayerNorm(in_features),\n            nn.Dropout(0.2),\n            nn.Linear(in_features, n_targets),\n        )\n\n    def forward(self, x):\n        feats = self.backbone(x)\n        return self.head(feats)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-12T05:43:22.276158Z","iopub.execute_input":"2026-01-12T05:43:22.276868Z","iopub.status.idle":"2026-01-12T05:43:22.282090Z","shell.execute_reply.started":"2026-01-12T05:43:22.276844Z","shell.execute_reply":"2026-01-12T05:43:22.281503Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"- Load in our wonderful `nvidia_resnet50`","metadata":{}},{"cell_type":"code","source":"from torchvision.models import resnet50\nfrom collections import OrderedDict\n\nweights_path = \"/kaggle/input/nvidia-resnet50/pytorch/default/1/nvidia_resnet50_200821.pth\"\n\nbackbone = resnet50(weights=None)\nstate = torch.load(weights_path, map_location=\"cpu\")\nif \"state_dict\" in state:\n    state = state[\"state_dict\"]\n\nclean_state = OrderedDict(\n    (k.replace(\"module.\", \"\"), v) for k, v in state.items()\n)\nbackbone.load_state_dict(clean_state, strict=False)\n\nbackbone = backbone.to(_device)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-12T05:43:25.390933Z","iopub.execute_input":"2026-01-12T05:43:25.391652Z","iopub.status.idle":"2026-01-12T05:43:28.520645Z","shell.execute_reply.started":"2026-01-12T05:43:25.391623Z","shell.execute_reply":"2026-01-12T05:43:28.520060Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"- Load our `nvidia_resnet50`","metadata":{}},{"cell_type":"markdown","source":"- Sanity check: we aren't missing anything","metadata":{}},{"cell_type":"code","source":"import torch\nfrom torchvision.models import resnet50\nfrom collections import OrderedDict\n\n_local_backbone = resnet50(weights=None)\n\nstate = torch.load(weights_path, map_location=\"cpu\")\nif isinstance(state, dict) and \"state_dict\" in state:\n    state = state[\"state_dict\"]\n\nnew_state = OrderedDict((k[len(\"module.\"):] if k.startswith(\"module.\") else k, v) for k, v in state.items())\n_local_backbone.load_state_dict(new_state, strict=False)\n\nmissing, unexpected = _local_backbone.load_state_dict(new_state, strict=False)\nprint(\"missing:\", len(missing), \"unexpected:\", len(unexpected))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-12T05:44:15.502970Z","iopub.execute_input":"2026-01-12T05:44:15.503654Z","iopub.status.idle":"2026-01-12T05:44:15.966370Z","shell.execute_reply.started":"2026-01-12T05:44:15.503626Z","shell.execute_reply":"2026-01-12T05:44:15.965594Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"- Define the datasets and looaders. `num_workers=0` due to an annoying bug.","metadata":{}},{"cell_type":"code","source":"train_ds = BiomassDataset(train_wide, _data_directory, _targets, train_tfms)\neval_ds  = BiomassDataset(eval_wide,  _data_directory, _targets, eval_tfms)\n\ntrain_loader = DataLoader(train_ds, batch_size=64, shuffle=True, num_workers=0)\neval_loader  = DataLoader(eval_ds,  batch_size=64, shuffle=False, num_workers=0)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-12T05:44:22.197772Z","iopub.execute_input":"2026-01-12T05:44:22.198531Z","iopub.status.idle":"2026-01-12T05:44:22.203042Z","shell.execute_reply.started":"2026-01-12T05:44:22.198486Z","shell.execute_reply":"2026-01-12T05:44:22.202235Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"- Method for scoring our own R2 during iterations to keep track of the best/chosen models","metadata":{}},{"cell_type":"code","source":"# Kaggle-style weights for 5 targets \n_kaggle_weights = np.array([ \n    0.01, # Dry_Green_g \n    0.01, # Dry_Dead_g \n    0.01, # Dry_Clover_g\n    0.20, # GDM_g \n    0.50 # Dry_Total_g \n], dtype=np.float64)\n\ndef kaggle_r2_score(y_true, y_pred): \n    y_true = np.asarray(y_true, dtype=np.float64) \n    y_pred = np.asarray(y_pred, dtype=np.float64) \n    w = _kaggle_weights # Weighted mean \n    y_bar = (w * y_true).sum() / w.sum() \n    ssr = (w * (y_true - y_pred)**2).sum() \n    sst = (w * (y_true - y_bar)**2).sum() \n    return 1.0 - ssr / (sst + 1e-12)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-12T05:44:25.395326Z","iopub.execute_input":"2026-01-12T05:44:25.395618Z","iopub.status.idle":"2026-01-12T05:44:25.401058Z","shell.execute_reply.started":"2026-01-12T05:44:25.395592Z","shell.execute_reply":"2026-01-12T05:44:25.400360Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_df.head(10)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-12T05:44:28.516920Z","iopub.execute_input":"2026-01-12T05:44:28.517482Z","iopub.status.idle":"2026-01-12T05:44:28.543830Z","shell.execute_reply.started":"2026-01-12T05:44:28.517455Z","shell.execute_reply":"2026-01-12T05:44:28.543212Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"- Train + keep track of the 'best' models.\n- Update the checkpoints and keep a log of the recorded metrics\n- Added scheduling, AMP, and gradient clipping after Epoch 14 (~0.32)\n- Increased epochs after chosen_model_d.pt performance degradation\n- Refactpred completely due to a misunderstanding - best_model.pt\n\n+ Started with unfreezig layer 4, saw improvement,\n+ Then unfroze layer 3 also\n+ This combination seemed to produce the best results","metadata":{}},{"cell_type":"code","source":"model = MultiTargetRegressor(backbone, 5).to(_device)\n\n# Freeze backbone except layer3+4\nfor p in model.backbone.parameters():\n    p.requires_grad = False\nfor p in model.backbone.layer3.parameters():\n    p.requires_grad = True\nfor p in model.backbone.layer4.parameters():\n    p.requires_grad = True\n\nopt = torch.optim.AdamW([\n    {\"params\": model.head.parameters(), \"lr\": 2e-4},\n    {\"params\": model.backbone.layer4.parameters(), \"lr\": 5e-5},\n    {\"params\": model.backbone.layer3.parameters(), \"lr\": 2e-5},\n], weight_decay=1e-4)\n\nscaler = torch.amp.GradScaler(\"cuda\")\nmax_grad_norm = 1.0\nbest = -999\n\ndef mse_loss(pred, y):\n    return ((pred - y)**2).mean()\n\nfor epoch in range(1, 26):\n    model.train()\n    losses = []\n\n    for batch in train_loader:\n        x = batch[\"image\"].to(_device)\n        y = batch[\"y\"].to(_device)\n\n        opt.zero_grad(set_to_none=True)\n\n        with torch.amp.autocast(\"cuda\"):\n            pred = model(x)\n            loss = mse_loss(pred, y)\n\n        scaler.scale(loss).backward()\n        torch.nn.utils.clip_grad_norm_(model.parameters(), max_grad_norm)\n        scaler.step(opt)\n        scaler.update()\n\n        losses.append(loss.item())\n\n    # Eval\n    model.eval()\n    ys, ps = [], []\n    with torch.no_grad():\n        for batch in eval_loader:\n            x = batch[\"image\"].to(_device)\n            y_log = batch[\"y\"].cpu().numpy()\n            p_log = model(x).detach().cpu().numpy()\n\n            y = np.expm1(y_log)\n            p = np.expm1(p_log)\n            p = np.clip(p, 0, None)\n\n            ys.append(y)\n            ps.append(p)\n\n    ys = np.concatenate(ys)\n    ps = np.concatenate(ps)\n\n    score = kaggle_r2_score(ys, ps)\n    print(f\"Epoch {epoch} | loss={np.mean(losses):.4f} | R2={score:.4f}\")\n\n    if score > best:\n        best = score\n        torch.save(model.state_dict(), \"best_model.pt\")\n        print(\"  saved best_model.pt\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-12T05:45:56.617581Z","iopub.execute_input":"2026-01-12T05:45:56.617898Z","iopub.status.idle":"2026-01-12T05:54:00.584010Z","shell.execute_reply.started":"2026-01-12T05:45:56.617872Z","shell.execute_reply":"2026-01-12T05:54:00.583292Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"- Still a lot of GPU hours left with Kaggle, so batch size 16 should be okay\n- Setup the datasets, loaders, and define the model","metadata":{}},{"cell_type":"markdown","source":"- Sanity check - `best_run.pt` `Epoch 22 | loss=0.6644 | R2=0.9999\n  saved best_model.pt`\n- Make sure 'overlap ids' is zero - we didn't accidentally equate the train dataset to the eval dataset. (Because that result is *good*)","metadata":{}},{"cell_type":"code","source":"print(\"trainable params:\",\n      sum(p.numel() for p in model.parameters() if p.requires_grad))\n\ndef count_trainable(m):\n    return sum(p.numel() for p in m.parameters() if p.requires_grad)\n\nprint(\"trainable total:\", count_trainable(model))\nprint(\"trainable backbone:\", count_trainable(model.backbone))\nprint(\"trainable layer4:\", count_trainable(model.backbone.layer4))\nprint(\"trainable head:\", count_trainable(model.head))\n\nprint(\"train rows\", len(train_ds), \"eval rows\", len(eval_ds))\nprint(\"same object?\", train_ds is eval_ds)\ntrain_ids = [train_ds[i][\"image_id\"] for i in range(len(train_ds))]\neval_ids  = [eval_ds[i][\"image_id\"]  for i in range(len(eval_ds))]\n\nprint(\"overlap ids\", len(set(train_ids) & set(eval_ids)))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-12T06:02:18.458401Z","iopub.execute_input":"2026-01-12T06:02:18.458763Z","iopub.status.idle":"2026-01-12T06:02:36.642688Z","shell.execute_reply.started":"2026-01-12T06:02:18.458736Z","shell.execute_reply":"2026-01-12T06:02:36.641949Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"- Sanity check: ~~head-only training shouldn't have millions of parameters~~\n- ~~Unfreezing at layer4 makes it millions of parameters~~\n- Added unfreezing at layer 3 also with `Epoch 7 | train_loss=0.34146 | weighted_R2=0.32252`","metadata":{}},{"cell_type":"code","source":"from tqdm.auto import tqdm\n\n# Load best model\n_model = MultiTargetRegressor(\n    backbone,\n    len(_targets)\n).to(_device)\n\n_model.load_state_dict(torch.load(\"best_model.pt\", map_location=_device))\n_model.eval()\n\n# Test transforms (same normalization as eval)\n_test_tfms = eval_tfms\n\n# Build a per-image test dataframe: one row per image_id\n_test_dataframe = test_df.copy()\n_test_dataframe[\"image_id\"] = (\n    _test_dataframe[\"image_path\"]\n    .str.replace(\".jpg\", \"\", regex=False)\n    .str.replace(\"test/\", \"\", regex=False)\n)\n\n_test_images = (\n    _test_dataframe[[\"image_id\", \"image_path\"]]\n    .drop_duplicates(\"image_id\")\n    .reset_index(drop=True)\n)\n\nprint(\"unique test images:\", len(_test_images))\n\n# Dataset that does NOT require targets\nclass TestBiomassDataset(Dataset):\n    def __init__(self, df, root, tfms=None):\n        self.df = df\n        self.root = root\n        self.tfms = tfms\n\n    def __len__(self):\n        return len(self.df)\n\n    def __getitem__(self, idx):\n        row = self.df.iloc[idx]\n        path = os.path.join(self.root, row[\"image_path\"])\n        img = Image.open(path).convert(\"RGB\")\n        img = np.array(img)\n\n        if self.tfms:\n            img = self.tfms(image=img)[\"image\"]\n\n        return {\n            \"image\": img,\n            \"image_id\": row[\"image_id\"],\n            \"filename\": os.path.basename(row[\"image_path\"]),\n        }\n\n_test_dataset = TestBiomassDataset(\n    _test_images,\n    _data_directory ,\n    tfms=_test_tfms\n)\n\n_test_loader = DataLoader(\n    _test_dataset,\n    batch_size=64,\n    shuffle=False,\n    num_workers=0,\n    pin_memory=True\n)\n\nall_ids = []\nall_filenames = []\nall_predictions = []\n\nwith torch.no_grad():\n    for batch in tqdm(_test_loader, desc=\"infer\"):\n        x = batch[\"image\"].to(_device, non_blocking=True)\n        p_log = _model(x).cpu().numpy()       # (B,5) log1p\n        p = np.expm1(p_log)\n        p = np.clip(p, 0, None)\n\n        all_predictions.append(p)\n        all_ids.extend(batch[\"image_id\"])\n        all_filenames.extend(batch[\"filename\"])\n\nall_predictions = np.concatenate(all_predictions, axis=0)  # (N,5)\nprint(\"predictions shape:\", all_predictions.shape)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-12T06:20:08.648791Z","iopub.execute_input":"2026-01-12T06:20:08.649084Z","iopub.status.idle":"2026-01-12T06:20:09.189300Z","shell.execute_reply.started":"2026-01-12T06:20:08.649059Z","shell.execute_reply":"2026-01-12T06:20:09.188445Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Inference on test\n- Evaluate the test dataset using the best chosen model\n- ~~Seems that after all the tweaks/attempts at optimizing `chosen_model_b.pt` performed the best~~.\n- Complete refactor, `best_model.pt` is performing much better (low loss, ~0.999 R2)","metadata":{}},{"cell_type":"markdown","source":"# Construct submission CSV\n- Pandas dataframe -> `{image_id}_{target_name}` + append predicted biomass","metadata":{}},{"cell_type":"code","source":"rows = []\nfor image_id, preds in zip(all_ids, all_predictions):\n    for t, val in zip(_targets, preds):\n        rows.append({\n            \"sample_id\": f\"{image_id}__{t}\",\n            \"target\": float(val),\n        })\n\n_submission = pd.DataFrame(rows, columns=[\"sample_id\", \"target\"])\n_submission.to_csv(\"submission.csv\", index=False)\n\nprint(\"Saved submission.csv with shape:\", _submission.shape)\nprint(_submission.head())\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-12T06:23:19.296338Z","iopub.execute_input":"2026-01-12T06:23:19.296638Z","iopub.status.idle":"2026-01-12T06:23:19.309108Z","shell.execute_reply.started":"2026-01-12T06:23:19.296612Z","shell.execute_reply":"2026-01-12T06:23:19.308552Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"- `submission.csv` created in expected format","metadata":{}},{"cell_type":"markdown","source":"*Shoutout to adosiml.com my portfolio website - trying to find employment.*","metadata":{}}]}