{"metadata":{"kernelspec":{"display_name":"Python 3","language":"python","name":"python3"},"language_info":{"codemirror_mode":{"name":"ipython","version":3},"file_extension":".py","mimetype":"text/x-python","name":"python","nbconvert_exporter":"python","pygments_lexer":"ipython3","version":"3.11.13"},"kaggle":{"accelerator":"gpu","dataSources":[{"sourceId":112509,"databundleVersionId":14254895,"sourceType":"competition"},{"sourceId":271932618,"sourceType":"kernelVersion"}],"dockerImageVersionId":31154,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":true},"papermill":{"default_parameters":{},"duration":675.316664,"end_time":"2025-10-29T22:37:02.179399","environment_variables":{},"exception":null,"input_path":"__notebook__.ipynb","output_path":"__notebook__.ipynb","parameters":{},"start_time":"2025-10-29T22:25:46.862735","version":"2.6.0"}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"## Setup and Configuration\n","metadata":{}},{"cell_type":"code","source":"# =========================\n# 0) Imports & config\n# =========================\nimport os, gc, math, random, time, json\nfrom pathlib import Path\n\nimport numpy as np\nimport pandas as pd\n\nfrom sklearn.model_selection import GroupKFold\nfrom sklearn.metrics import r2_score\nfrom sklearn.preprocessing import OneHotEncoder\n\nimport torch\nimport torchvision\nimport torch.nn as nn\nimport torch.optim as optim\nfrom torch.utils.data import DataLoader\nimport torchvision.transforms as transforms\n\nimport albumentations as A\n\nSEED = 42\nIMG_SIZE = 448\nBATCH_SIZE = 16\nEPOCHS = 10\nLR = 2e-4\nBACKBONE = \"tf_efficientnetv2_s\"   # try \"convnext_base\" if you have more GPU\nUSE_META = True                     # toggle meta features on/off\nMETA_OHE_TOP_N = 30                 # top N categories kept per column, rest grouped as \"Other\"\nDEVICE = \"cuda\" if torch.cuda.is_available() else \"cpu\"\n\nTARGETS = [\"Dry_Green_g\", \"Dry_Dead_g\", \"Dry_Clover_g\", \"GDM_g\", \"Dry_Total_g\"]\nTARGET_WEIGHTS = np.array([1,1,1,1,1], dtype=np.float32)  # equal weights unless competition states otherwise\nTARGET_WEIGHTS = TARGET_WEIGHTS / TARGET_WEIGHTS.sum()\n\nrandom.seed(SEED); np.random.seed(SEED); torch.manual_seed(SEED); torch.cuda.manual_seed_all(SEED)\n\nINPUT_DIR = Path(\"/kaggle/input/csiro-biomass\")\nWORK_DIR = Path(\"/kaggle/working\")\n\nsubdirs = [p for p in INPUT_DIR.iterdir() if p.is_dir()]\nCOMP_DIR = subdirs[0] if len(subdirs)==1 else INPUT_DIR  # fallback\n\nprint(\"Using data from:\", COMP_DIR)","metadata":{"_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","execution":{"iopub.status.busy":"2025-11-21T08:00:24.664627Z","iopub.execute_input":"2025-11-21T08:00:24.664907Z","iopub.status.idle":"2025-11-21T08:00:24.675077Z","shell.execute_reply.started":"2025-11-21T08:00:24.664887Z","shell.execute_reply":"2025-11-21T08:00:24.674328Z"},"papermill":{"duration":47.957057,"end_time":"2025-10-29T22:26:38.28091","exception":false,"start_time":"2025-10-29T22:25:50.323853","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Load and Prepare the Data","metadata":{}},{"cell_type":"code","source":"# =========================\n# 1) Load data & pivot wide\n# =========================\ntrain = pd.read_csv(COMP_DIR / \"train.csv\")\ntest  = pd.read_csv(COMP_DIR / \"test.csv\")\nprint(train.shape, test.shape)\n\n# Extract image_id (basename without extension) for grouping and for sample_id construction\ndef path_to_id(p):\n    return Path(p).stem\n\ntrain[\"image_id\"] = train[\"image_path\"].apply(path_to_id)\ntest[\"image_id\"]  = test[\"image_path\"].apply(path_to_id)\n\n# Wide targets per image\npivot_targets = train.pivot_table(index=\"image_id\", columns=\"target_name\", values=\"target\", aggfunc=\"first\").reset_index()\n# Grab representative per-image meta (same across 5 target rows)\nmeta_cols = [\"image_path\",\"Sampling_Date\",\"State\",\"Species\",\"Pre_GSHH_NDVI\",\"Height_Ave_cm\"]\nmeta_per_img = train.drop_duplicates(subset=[\"image_id\"])[[\"image_id\"] + meta_cols]\n\ndf = pivot_targets.merge(meta_per_img, on=\"image_id\", how=\"left\")\nassert all(t in df.columns for t in TARGETS)\n\nprint(\"Wide train shape:\", df.shape)\ndf.head()\n","metadata":{"papermill":{"duration":0.341261,"end_time":"2025-10-29T22:26:38.625072","exception":false,"start_time":"2025-10-29T22:26:38.283811","status":"completed"},"tags":[],"trusted":true,"execution":{"iopub.status.busy":"2025-11-21T08:00:28.112788Z","iopub.execute_input":"2025-11-21T08:00:28.113358Z","iopub.status.idle":"2025-11-21T08:00:28.204414Z","shell.execute_reply.started":"2025-11-21T08:00:28.113326Z","shell.execute_reply":"2025-11-21T08:00:28.203684Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Create Meta Features","metadata":{}},{"cell_type":"code","source":"# =========================\n# 2) Meta preprocessing\n# =========================\n# We’ll one-hot 'State' and the Top-N frequent categories of 'Species' (rest -> 'Other').\ndef topn_ohe_fit(series, top_n):\n    vc = series.value_counts()\n    cats = list(vc.head(top_n).index)\n    return cats\n\ndef topn_ohe_transform(series, cats, prefix):\n    out = pd.DataFrame({f\"{prefix}_{c}\": (series == c).astype(int) for c in cats})\n    if not out.shape[1]:\n        return pd.DataFrame(index=series.index)\n    # 'Other'\n    out[f\"{prefix}_Other\"] = (~series.isin(cats)).astype(int)\n    return out\n\nstate_cats = topn_ohe_fit(df[\"State\"].fillna(\"Unknown\"), top_n=10)\nspecies_cats = topn_ohe_fit(df[\"Species\"].fillna(\"Unknown\"), top_n=META_OHE_TOP_N)\n\nstate_ohe   = topn_ohe_transform(df[\"State\"].fillna(\"Unknown\"), state_cats, \"State\")\nspecies_ohe = topn_ohe_transform(df[\"Species\"].fillna(\"Unknown\"), species_cats, \"Species\")\n\nmeta_num = df[[\"Pre_GSHH_NDVI\",\"Height_Ave_cm\"]].copy()\nmeta_num = meta_num.fillna(meta_num.median())\n\nMETA_FEATS = pd.concat([meta_num, state_ohe, species_ohe], axis=1).astype(np.float32)\nMETA_DIM = META_FEATS.shape[1] if USE_META else 0\nprint(\"META_DIM:\", META_DIM)\n\n# Save mapping to apply same OHE on test\nmeta_map = {\n    \"state_cats\": state_cats,\n    \"species_cats\": species_cats,\n    \"meta_num_cols\": [\"Pre_GSHH_NDVI\",\"Height_Ave_cm\"]\n}","metadata":{"execution":{"iopub.status.busy":"2025-11-21T08:00:30.929377Z","iopub.execute_input":"2025-11-21T08:00:30.930092Z","iopub.status.idle":"2025-11-21T08:00:30.950207Z","shell.execute_reply.started":"2025-11-21T08:00:30.930067Z","shell.execute_reply":"2025-11-21T08:00:30.949466Z"},"papermill":{"duration":0.023835,"end_time":"2025-10-29T22:26:38.651936","exception":false,"start_time":"2025-10-29T22:26:38.628101","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Dataset and Augmentations\nalbumentations need internet access a pre-dl lib should resolve","metadata":{}},{"cell_type":"code","source":"# =========================\n# 3) Albumentations & Dataset\n# =========================\ntrain_tfms = A.Compose([\n    A.LongestMaxSize(max_size=IMG_SIZE),\n    A.PadIfNeeded(IMG_SIZE, IMG_SIZE, border_mode=cv2.BORDER_REFLECT_101),\n    A.HorizontalFlip(p=0.5),\n    A.VerticalFlip(p=0.2),\n    A.ColorJitter(p=0.2),\n    A.Normalize(),\n    ToTensorV2(),\n])\n\nvalid_tfms = A.Compose([\n    A.LongestMaxSize(max_size=IMG_SIZE),\n    A.PadIfNeeded(IMG_SIZE, IMG_SIZE, border_mode=cv2.BORDER_REFLECT_101),\n    A.CenterCrop(IMG_SIZE, IMG_SIZE, p=1.0),\n    A.Normalize(),\n    ToTensorV2(),\n])\n\nclass PastureDataset(Dataset):\n    def __init__(self, df_img, targets, img_root, tfms, meta=None):\n        self.df = df_img.reset_index(drop=True)\n        self.targets = targets  # np array [N, 5] or None (for test)\n        self.img_root = Path(img_root)\n        self.tfms = tfms\n        self.meta = meta.astype(np.float32) if meta is not None else None\n        self.image_paths = self.df[\"image_path\"].values\n\n    def __len__(self):\n        return len(self.df)\n\n    def __getitem__(self, idx):\n        img_path_rel = self.image_paths[idx]\n        img_fp = self.img_root / img_path_rel\n        img = cv2.imread(str(img_fp))\n        if img is None:\n            img_fp_alt = Path(str(self.img_root)) / \"train\" / Path(img_path_rel).name\n            img = cv2.imread(str(img_fp_alt))\n        img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)\n        if self.tfms:\n            img = self.tfms(image=img)[\"image\"]\n\n        x_meta = None\n        if self.meta is not None:\n            x_meta = torch.from_numpy(self.meta[idx])\n\n        y = None\n        if self.targets is not None:\n            y = torch.tensor(self.targets[idx], dtype=torch.float32)\n\n        return img, x_meta, y","metadata":{"execution":{"iopub.status.busy":"2025-11-21T08:00:33.713907Z","iopub.execute_input":"2025-11-21T08:00:33.714519Z","iopub.status.idle":"2025-11-21T08:00:33.741191Z","shell.execute_reply.started":"2025-11-21T08:00:33.714495Z","shell.execute_reply":"2025-11-21T08:00:33.740106Z"},"papermill":{"duration":0.01892,"end_time":"2025-10-29T22:26:38.673686","exception":false,"start_time":"2025-10-29T22:26:38.654766","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Model Architecture\n- **timm** backbone (EfficientNetV2-S by default) for image feature extraction.\n- small **MLP head** that combines image and meta features.\n- The final layer outputs 5 regression values (one per biomass component).\n\nhttps://medium.com/@alphaalimamykamara/implementing-vgg16-with-pytorch-a-comprehensive-guide-to-data-preparation-and-model-training-1abcaa20cf51","metadata":{}},{"cell_type":"code","source":"# =========================\n# 4) Model (Enhanced Meta + Multi-Head)\n# =========================\nclass VGG16(nn.Module):\n    def __init__(self, num_classes=1000):\n        super(VGG16, self).__init__()\n        # Define convolutional blocks (features)\n        self.features = nn.Sequential(\n            # Block 1\n            nn.Conv2d(3, 64, kernel_size=3, padding=1),\n            nn.ReLU(inplace=True),\n            nn.Conv2d(64, 64, kernel_size=3, padding=1),\n            nn.ReLU(inplace=True),\n            nn.MaxPool2d(kernel_size=2, stride=2),\n\n            # Block 2\n            nn.Conv2d(64, 128, kernel_size=3, padding=1),\n            nn.ReLU(inplace=True),\n            nn.Conv2d(128, 128, kernel_size=3, padding=1),\n            nn.ReLU(inplace=True),\n            nn.MaxPool2d(kernel_size=2, stride=2),\n\n            # Block 3\n            nn.Conv2d(128, 256, kernel_size=3, padding=1),\n            nn.ReLU(inplace=True),\n            nn.Conv2d(256, 256, kernel_size=3, padding=1),\n            nn.ReLU(inplace=True),\n            nn.Conv2d(256, 256, kernel_size=3, padding=1),\n            nn.ReLU(inplace=True),\n            nn.MaxPool2d(kernel_size=2, stride=2),\n\n            # Block 4\n            nn.Conv2d(256, 512, kernel_size=3, padding=1),\n            nn.ReLU(inplace=True),\n            nn.Conv2d(512, 512, kernel_size=3, padding=1),\n            nn.ReLU(inplace=True),\n            nn.Conv2d(512, 512, kernel_size=3, padding=1),\n            nn.ReLU(inplace=True),\n            nn.MaxPool2d(kernel_size=2, stride=2),\n\n            # Block 5\n            nn.Conv2d(512, 512, kernel_size=3, padding=1),\n            nn.ReLU(inplace=True),\n            nn.Conv2d(512, 512, kernel_size=3, padding=1),\n            nn.ReLU(inplace=True),\n            nn.Conv2d(512, 512, kernel_size=3, padding=1),\n            nn.ReLU(inplace=True),\n            nn.MaxPool2d(kernel_size=2, stride=2)\n        )\n\n        # Fully connected layers (classifier)\n        self.classifier = nn.Sequential(\n            nn.Flatten(),\n            nn.Linear(512 * 7 * 7, 4096),\n            nn.ReLU(inplace=True),\n            nn.Dropout(0.5),\n            nn.Linear(4096, 4096),\n            nn.ReLU(inplace=True),\n            nn.Dropout(0.5),\n            nn.Linear(4096, num_classes),\n            nn.Softmax(dim=1)\n        )\n\n    def forward(self, x):\n        x = self.features(x)\n        x = self.classifier(x)\n        return x\n\nif __name__ == \"__main__\":\n  vgg_16 = VGG16(num_classes=1000)\n\n  # Create a random input tensor with shape (batch_size, channels, height, width)\n  # For example, batch size = 1, channels = 3 (RGB), height = 224, width = 224\n  input_tensor = torch.rand(1, 3, 224, 224) # random imput tensor\n  output = vgg_16(input_tensor)\n  print(\"Output shape:\", output.shape) # print the shape","metadata":{"execution":{"iopub.status.busy":"2025-11-21T08:01:12.650523Z","iopub.execute_input":"2025-11-21T08:01:12.651085Z","iopub.status.idle":"2025-11-21T08:01:14.111566Z","shell.execute_reply.started":"2025-11-21T08:01:12.651065Z","shell.execute_reply":"2025-11-21T08:01:14.110987Z"},"papermill":{"duration":0.011621,"end_time":"2025-10-29T22:26:38.688248","exception":false,"start_time":"2025-10-29T22:26:38.676627","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def load_cifar10_dataset(batch_size=32):\n    # Define data transformations\n    transform = transforms.Compose([\n        transforms.Resize((224, 224)),  # Resize images to 224x224\n        transforms.ToTensor(),  # Convert images to tensors\n        transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])  # Normalize\n    ])\n\n    # Load CIFAR-10 dataset\n    train_dataset = torchvision.datasets.CIFAR10(root='./data', train=True, transform=transform, download=True)\n    test_dataset = torchvision.datasets.CIFAR10(root='./data', train=False, transform=transform, download=True)\n\n    train_loader = DataLoader(dataset=train_dataset, batch_size=batch_size, shuffle=True)\n    test_loader = DataLoader(dataset=test_dataset, batch_size=batch_size, shuffle=False)\n\n    return train_loader, test_loader","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-21T08:01:24.985649Z","iopub.execute_input":"2025-11-21T08:01:24.985944Z","iopub.status.idle":"2025-11-21T08:01:24.991563Z","shell.execute_reply.started":"2025-11-21T08:01:24.985923Z","shell.execute_reply":"2025-11-21T08:01:24.990662Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"vgg_16 = VGG16(num_classes=10)  # num_classes = 10 for CIFAR-10\n\ntrain_loader, test_loader = load_cifar10_dataset(batch_size=32)\n\nlearning_rate = 0.001\n\n# Define loss function and optimizer\ncriterion = nn.CrossEntropyLoss()\noptimizer = optim.Adam(vgg_16.parameters(), lr=learning_rate)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-21T08:01:28.206981Z","iopub.execute_input":"2025-11-21T08:01:28.207641Z","iopub.status.idle":"2025-11-21T08:02:01.338753Z","shell.execute_reply.started":"2025-11-21T08:01:28.207618Z","shell.execute_reply":"2025-11-21T08:02:01.337633Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Training the model\nfor epoch in range(EPOCHS):\n    for images, labels in train_loader:\n        # Forward pass\n        outputs = vgg_16(images)\n        loss = criterion(outputs, labels)\n\n        # Backward pass and optimization\n        optimizer.zero_grad()  # Zero the gradients\n        loss.backward()  # Backpropagate\n        optimizer.step()  # Update weights\n\n    print(f'Epoch [{epoch + 1}/{EPOCHS}], Loss: {loss.item():.4f}')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-21T08:11:32.348955Z","iopub.execute_input":"2025-11-21T08:11:32.349538Z","iopub.status.idle":"2025-11-21T08:11:32.363006Z","shell.execute_reply.started":"2025-11-21T08:11:32.349515Z","shell.execute_reply":"2025-11-21T08:11:32.362086Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Training Functions and Evaluation Metric","metadata":{}},{"cell_type":"code","source":"# =========================\n# 5) Metric & training loop\n# =========================\ndef weighted_r2(y_true, y_pred):\n    # y_true, y_pred: numpy arrays [N, 5]\n    r2s = []\n    for i in range(len(TARGETS)):\n        r2s.append(r2_score(y_true[:, i], y_pred[:, i]))\n    r2s = np.array(r2s)\n    return float((r2s * TARGET_WEIGHTS).sum()), r2s\n\ndef train_one_epoch(model, loader, optimizer, scaler, criterion, lam_total_consistency=0.0):\n    model.train()\n    total_loss = 0.0\n    for imgs, metas, ys in loader:\n        imgs = imgs.to(DEVICE)\n        ys = ys.to(DEVICE)\n        metas = metas.to(DEVICE) if metas is not None else None\n\n        optimizer.zero_grad()\n        with torch.cuda.amp.autocast(enabled=True):\n            preds = model(imgs, metas)\n            loss = criterion(preds, ys)\n            # Optional soft constraint: Dry_Total ≈ Dry_Green + Dry_Dead + Dry_Clover\n            if lam_total_consistency > 0:\n                total_est = preds[:, 0] + preds[:, 1] + preds[:, 2]  # order must match TARGETS!\n                loss_cons = nn.functional.l1_loss(total_est, preds[:, 4])\n                loss = loss + lam_total_consistency * loss_cons\n\n        scaler.scale(loss).backward()\n        scaler.step(optimizer)\n        scaler.update()\n        total_loss += loss.item() * imgs.size(0)\n    return total_loss / len(loader.dataset)\n\n@torch.no_grad()\ndef validate(model, loader, criterion):\n    model.eval()\n    total_loss = 0.0\n    all_y, all_p = [], []\n    for imgs, metas, ys in loader:\n        imgs = imgs.to(DEVICE)\n        metas = metas.to(DEVICE) if metas is not None else None\n        ys = ys.to(DEVICE)\n        preds = model(imgs, metas)\n        loss = criterion(preds, ys)\n        total_loss += loss.item() * imgs.size(0)\n        all_y.append(ys.cpu().numpy())\n        all_p.append(preds.cpu().numpy())\n    all_y = np.concatenate(all_y)\n    all_p = np.concatenate(all_p)\n    wr2, r2s = weighted_r2(all_y, all_p)\n    return total_loss / len(loader.dataset), wr2, r2s, all_p, all_y","metadata":{"execution":{"iopub.status.busy":"2025-11-21T00:04:24.233545Z","iopub.execute_input":"2025-11-21T00:04:24.234034Z","iopub.status.idle":"2025-11-21T00:04:24.243092Z","shell.execute_reply.started":"2025-11-21T00:04:24.233999Z","shell.execute_reply":"2025-11-21T00:04:24.242421Z"},"papermill":{"duration":0.012655,"end_time":"2025-10-29T22:26:38.716178","exception":false,"start_time":"2025-10-29T22:26:38.703523","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Cross-Validation Strategy\n\n**GroupKFold (5 folds)**:\n- Groups are based on image_id to prevent data leakage between multiple target rows from the same image.\n- Each fold is trained and validated independently.\n","metadata":{}},{"cell_type":"code","source":"# =========================\n# 6) Build CV folds\n# =========================\n# Group by image to avoid leakage across the 5 target rows originally\ngroups = df[\"image_id\"].values\ngkf = GroupKFold(n_splits=5)\n\n# Targets matrix Y [N, 5]\nY = df[TARGETS].values.astype(np.float32)\n\n# Compose meta features matrix aligned with df rows\nMETA_MAT = META_FEATS.values if USE_META else None\n\n# Where are the actual image files?\n# train images usually under COMP_DIR/\"train\" or per paths in df[\"image_path\"].\nIMG_ROOT = COMP_DIR  # we will resolve using relative paths like \"train/IDxxx.jpg\"","metadata":{"execution":{"iopub.status.busy":"2025-11-21T00:04:40.336885Z","iopub.execute_input":"2025-11-21T00:04:40.337512Z","iopub.status.idle":"2025-11-21T00:04:40.342888Z","shell.execute_reply.started":"2025-11-21T00:04:40.337491Z","shell.execute_reply":"2025-11-21T00:04:40.342229Z"},"papermill":{"duration":0.009587,"end_time":"2025-10-29T22:26:38.728639","exception":false,"start_time":"2025-10-29T22:26:38.719052","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Model Training Across Folds","metadata":{}},{"cell_type":"code","source":"# =========================\n# 7) Train CV and out-of-fold predictions\n# =========================\noof_preds = np.zeros_like(Y, dtype=np.float32)\nfold_scores = []\nfor fold, (tr_idx, va_idx) in enumerate(gkf.split(df, groups=groups)):\n    print(f\"\\n===== FOLD {fold} =====\")\n\n    tr_df, va_df = df.iloc[tr_idx], df.iloc[va_idx]\n    tr_Y, va_Y = Y[tr_idx], Y[va_idx]\n    tr_meta = META_MAT[tr_idx] if USE_META else None\n    va_meta = META_MAT[va_idx] if USE_META else None\n\n    tr_ds = PastureDataset(tr_df, tr_Y, IMG_ROOT, train_tfms, tr_meta)\n    va_ds = PastureDataset(va_df, va_Y, IMG_ROOT, valid_tfms, va_meta)\n\n    tr_loader = DataLoader(tr_ds, batch_size=BATCH_SIZE, shuffle=True, num_workers=2, pin_memory=True, drop_last=True)\n    va_loader = DataLoader(va_ds, batch_size=BATCH_SIZE, shuffle=False, num_workers=2, pin_memory=True)\n\n    #model = TimmRegressor(backbone=BACKBONE, out_dim=len(TARGETS), meta_dim=(META_DIM if USE_META else 0)).to(DEVICE)\n    #model = TimmRegressor(backbone=BACKBONE,out_dim=len(TARGETS),meta_dim=(META_DIM if USE_META else 0),pretrained=True, pretrained_path=\"/kaggle/input/timm-weights/timm_weights/tf_efficientnetv2_s_21ft1k-d7dafa41.safetensors\").to(DEVICE)\n    \n    model = torch.hub.load('pytorch/vision:v0.10.0', 'vgg19', pretrained=True)\n\n    best_wr2 = -1e9\n    best_state = None\n\n    for epoch in range(1, EPOCHS+1):\n        tr_loss = train_one_epoch(model, tr_loader, optimizer, scaler, criterion, lam_total_consistency=0.02)\n        va_loss, va_wr2, va_r2s, va_p, va_y = validate(model, va_loader, criterion)\n        scheduler.step()\n\n        print(f\"Epoch {epoch:02d} | tr_loss {tr_loss:.4f} | va_loss {va_loss:.4f} | WR2 {va_wr2:.4f} | per-target R2 {np.round(va_r2s,3)}\")\n        if va_wr2 > best_wr2:\n            best_wr2 = va_wr2\n            best_state = {k:v.cpu() for k,v in model.state_dict().items()}\n\n    print(f\"Fold {fold} best WR2: {best_wr2:.4f}\")\n    fold_scores.append(best_wr2)\n\n    # Save OOF predictions with the best model\n    model.load_state_dict({k:v.to(DEVICE) for k,v in best_state.items()})\n    va_loss, va_wr2, va_r2s, va_p, va_y = validate(model, va_loader, criterion)\n    oof_preds[va_idx] = va_p\n\n    # Save fold weights for inference ensembling later (optional)\n    torch.save(best_state, WORK_DIR / f\"model_fold{fold}.pt\")\n    del model; gc.collect(); torch.cuda.empty_cache()\n\ncv_wr2, cv_r2s = weighted_r2(Y, oof_preds)\nprint(\"\\nCV weighted R2:\", cv_wr2, \" | per-target:\", np.round(cv_r2s, 4))\nnp.save(WORK_DIR / \"oof_preds.npy\", oof_preds)","metadata":{"execution":{"iopub.status.busy":"2025-11-21T00:04:45.077507Z","iopub.execute_input":"2025-11-21T00:04:45.078231Z","iopub.status.idle":"2025-11-21T00:05:17.157049Z","shell.execute_reply.started":"2025-11-21T00:04:45.078207Z","shell.execute_reply":"2025-11-21T00:05:17.155859Z"},"papermill":{"duration":614.954787,"end_time":"2025-10-29T22:36:53.686133","exception":false,"start_time":"2025-10-29T22:26:38.731346","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import torch\nfrom torch import nn\nimport torch.nn.functional as F\n\nclass VGG(nn.Module):\n    def __init__(\n        self,\n        architecture,\n        in_channels=3, \n        in_height=224, \n        in_width=224, \n        num_hidden=4096,\n        num_classes=1000\n    ):\n        super(VGG, self).__init__()\n        self.in_channels = in_channels\n        self.in_width = in_width\n        self.in_height = in_height\n        self.num_hidden = num_hidden\n        self.num_classes = num_classes\n        self.convs = self.init_convs(architecture)\n        self.fcs = self.init_fcs(architecture)\n        \n    def forward(self, x):\n        x = self.convs(x)\n        x = x.reshape(x.size(0), -1)\n        x = self.fcs(x)\n        return x\n    \n    def init_fcs(self, architecture):\n        pool_count = architecture.count(\"M\")\n        factor = (2 ** pool_count)\n        if (self.in_height % factor) + (self.in_width % factor) != 0:\n            raise ValueError(\n                f\"`in_height` and `in_width` must be multiples of {factor}\"\n            )\n        out_height = self.in_height // factor\n        out_width = self.in_width // factor\n        last_out_channels = next(\n            x for x in architecture[::-1] if type(x) == int\n        )\n        return nn.Sequential(\n            nn.Linear(\n                last_out_channels * out_height * out_width, \n                self.num_hidden),\n            nn.ReLU(),\n            nn.Dropout(p=0.5),\n            nn.Linear(self.num_hidden, self.num_hidden),\n            nn.ReLU(),\n            nn.Dropout(p=0.5),\n            nn.Linear(self.num_hidden, self.num_classes)\n        )\n    \n    def init_convs(self, architecture):\n        layers = []\n        in_channels = self.in_channels\n        \n        for x in architecture:\n            if type(x) == int:\n                out_channels = x\n                layers.extend(\n                    [\n                        nn.Conv2d(\n                            in_channels=in_channels,\n                            out_channels=out_channels,\n                            kernel_size=(3, 3),\n                            stride=(1, 1),\n                            padding=(1, 1),\n                        ),\n                        nn.BatchNorm2d(out_channels),\n                        nn.ReLU(),\n                    ]\n                )\n                in_channels = x\n            else:\n                layers.append(\n                    nn.MaxPool2d(kernel_size=(2, 2), stride=(2, 2))\n                )\n\n        return nn.Sequential(*layers)\n        ","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-21T00:07:45.364864Z","iopub.execute_input":"2025-11-21T00:07:45.365419Z","iopub.status.idle":"2025-11-21T00:08:17.400902Z","shell.execute_reply.started":"2025-11-21T00:07:45.365399Z","shell.execute_reply":"2025-11-21T00:08:17.399682Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Inference and Submission File","metadata":{}},{"cell_type":"code","source":"# =========================\n# 8) Inference on test & submission\n# =========================\n# Build test meta with the same OHE mapping\ndef build_test_meta(test_df, comp_dir):\n    # test.csv has image_path, target_name per row; we need 1 row per image to run the model once per image\n    test_imgs = test_df.drop_duplicates(subset=[\"image_id\"])[[\"image_id\",\"image_path\"]].copy()\n    # We don't have Sampling_Date/State/Species in test.csv according to the prompt; if they exist, keep them\n    # Here we’ll set missing meta to neutral values:\n    meta_num = pd.DataFrame({\n        \"Pre_GSHH_NDVI\": np.full(len(test_imgs), df[\"Pre_GSHH_NDVI\"].median()),\n        \"Height_Ave_cm\": np.full(len(test_imgs), df[\"Height_Ave_cm\"].median()),\n    })\n    state_series = pd.Series([\"Unknown\"] * len(test_imgs))\n    species_series = pd.Series([\"Unknown\"] * len(test_imgs))\n    state_ohe = topn_ohe_transform(state_series, state_cats, \"State\")\n    species_ohe = topn_ohe_transform(species_series, species_cats, \"Species\")\n    meta_mat = pd.concat([meta_num, state_ohe, species_ohe], axis=1).astype(np.float32)\n    return test_imgs, meta_mat\n\ntest_imgs, test_meta = build_test_meta(test, COMP_DIR)\n\n# Dataloader for test images\nclass TestImageDataset(Dataset):\n    def __init__(self, df_img, img_root, tfms, meta=None):\n        self.df = df_img.reset_index(drop=True)\n        self.img_root = Path(img_root)\n        self.tfms = tfms\n        self.meta = meta.astype(np.float32) if meta is not None else None\n        self.image_paths = self.df[\"image_path\"].values\n\n    def __len__(self):\n        return len(self.df)\n\n    def __getitem__(self, idx):\n        img_fp = self.img_root / self.image_paths[idx]\n        img = cv2.imread(str(img_fp))\n        if img is None:\n            img_fp_alt = Path(str(self.img_root)) / \"test\" / Path(self.image_paths[idx]).name\n            img = cv2.imread(str(img_fp_alt))\n        img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)\n        img = self.tfms(image=img)[\"image\"]\n        x_meta = torch.from_numpy(self.meta[idx]) if self.meta is not None else None\n        return img, x_meta\n\ntest_ds = TestImageDataset(test_imgs, COMP_DIR, valid_tfms, test_meta.values if USE_META else None)\ntest_loader = DataLoader(test_ds, batch_size=BATCH_SIZE, shuffle=False, num_workers=2, pin_memory=True)\n\n# Load fold models and ensemble (mean)\nfold_preds = []\nfor fold in range(5):\n    model = MultiHeadRegressor(backbone=BACKBONE, meta_dim=(META_DIM if USE_META else 0)).to(DEVICE)\n    state_dict = torch.load(WORK_DIR / f\"model_fold{fold}.pt\", map_location=DEVICE)\n    model.load_state_dict(state_dict)\n    model.eval()\n    preds_all = []\n    with torch.no_grad():\n        for imgs, metas in test_loader:\n            imgs = imgs.to(DEVICE)\n            metas = metas.to(DEVICE) if USE_META else None\n            p = model(imgs, metas).cpu().numpy()\n            preds_all.append(p)\n    preds_all = np.vstack(preds_all)  # [num_test_images, 5]\n    fold_preds.append(preds_all)\n    del model; gc.collect(); torch.cuda.empty_cache()\n\npreds_mean = np.mean(fold_preds, axis=0)  # [N_test_images, 5]\npreds_mean = np.clip(preds_mean, a_min=0.0, a_max=None)  # biomass can’t be negative\n\n# Map back to long format using test.csv order:\n# test has multiple rows per image (one per target_name). We need sample_id,target accordingly.\nimgid_to_row = {iid:i for i, iid in enumerate(test_imgs[\"image_id\"].values)}\n\npred_rows = []\nfor _, r in test.iterrows():\n    iid = r[\"image_id\"]\n    tname = r[\"target_name\"]\n    col_idx = TARGETS.index(tname)\n    pred = preds_mean[imgid_to_row[iid], col_idx]\n    pred_rows.append((r[\"sample_id\"], float(pred)))\n\nsub_df = pd.DataFrame(pred_rows, columns=[\"sample_id\",\"target\"])\nsub_df.to_csv(\"submission.csv\", index=False)\nsub_df.head(10)\n","metadata":{"execution":{"iopub.status.busy":"2025-11-14T09:47:38.386254Z","iopub.execute_input":"2025-11-14T09:47:38.386538Z","iopub.status.idle":"2025-11-14T09:47:44.109336Z","shell.execute_reply.started":"2025-11-14T09:47:38.386517Z","shell.execute_reply":"2025-11-14T09:47:44.108596Z"},"papermill":{"duration":5.758244,"end_time":"2025-10-29T22:36:59.452009","exception":false,"start_time":"2025-10-29T22:36:53.693765","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null}]}