{"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":"gpu","dataSources":[{"sourceId":112509,"databundleVersionId":14254895,"sourceType":"competition"}],"dockerImageVersionId":31236,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"# Imports","metadata":{}},{"cell_type":"code","source":"import os\nimport warnings\nimport numpy as np\nimport pandas as pd\nimport matplotlib.pyplot as plt\nfrom PIL import Image\n\nimport torch\nimport torch.nn as nn\nimport torch.optim as optim\nfrom torch.utils.data import Dataset, DataLoader\nfrom torchvision import transforms\n\nfrom sklearn.model_selection import train_test_split\nfrom sklearn.preprocessing import StandardScaler\n\nwarnings.filterwarnings(\"ignore\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-27T02:21:49.441902Z","iopub.execute_input":"2025-12-27T02:21:49.442197Z","iopub.status.idle":"2025-12-27T02:21:58.196111Z","shell.execute_reply.started":"2025-12-27T02:21:49.442166Z","shell.execute_reply":"2025-12-27T02:21:58.195291Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Configuration & Reproducibility","metadata":{}},{"cell_type":"code","source":"SEED = 42\nBASE_PATH = \"/kaggle/input/csiro-biomass\"\nIMG_SIZE = 224\nBATCH_SIZE = 16\nNUM_EPOCHS = 20\nLEARNING_RATE = 1e-3\n\nDEVICE = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\n\nnp.random.seed(SEED)\ntorch.manual_seed(SEED)\ntorch.cuda.manual_seed_all(SEED)\ntorch.backends.cudnn.deterministic = True\ntorch.backends.cudnn.benchmark = False\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-27T02:22:00.957086Z","iopub.execute_input":"2025-12-27T02:22:00.957372Z","iopub.status.idle":"2025-12-27T02:22:01.017614Z","shell.execute_reply.started":"2025-12-27T02:22:00.957349Z","shell.execute_reply":"2025-12-27T02:22:01.017028Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Load & Pivot Dataset","metadata":{}},{"cell_type":"code","source":"df = pd.read_csv(os.path.join(BASE_PATH, \"train.csv\"))\ndf = df.pivot_table(index=\"image_path\", columns=\"target_name\", values=\"target\").reset_index()\ndf.fillna(0, inplace=True)\n\nTARGET_COLUMNS = ['Dry_Green_g','Dry_Dead_g','Dry_Clover_g','GDM_g','Dry_Total_g']\nprint(\"Dataset shape:\", df.shape)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-27T02:22:15.508581Z","iopub.execute_input":"2025-12-27T02:22:15.509293Z","iopub.status.idle":"2025-12-27T02:22:15.578348Z","shell.execute_reply.started":"2025-12-27T02:22:15.509266Z","shell.execute_reply":"2025-12-27T02:22:15.577618Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"#  Train / Validation Split & Scaling","metadata":{}},{"cell_type":"code","source":"train_df, val_df = train_test_split(df, test_size=0.2, random_state=SEED, shuffle=True)\nscaler = StandardScaler()\nscaler.fit(train_df[TARGET_COLUMNS].values)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-27T02:22:41.384721Z","iopub.execute_input":"2025-12-27T02:22:41.385399Z","iopub.status.idle":"2025-12-27T02:22:41.399427Z","shell.execute_reply.started":"2025-12-27T02:22:41.385370Z","shell.execute_reply":"2025-12-27T02:22:41.398855Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Dataset Class & DataLoaders","metadata":{}},{"cell_type":"code","source":"class BiomassDataset(Dataset):\n    def __init__(self, df, image_dir, transform=None, scaler=None):\n        self.df = df.copy()\n        self.df[\"image_path\"] = self.df[\"image_path\"].apply(os.path.basename)\n        self.samples = list(self.df.itertuples(index=False))\n        self.image_dir = image_dir\n        self.transform = transform\n        self.scaler = scaler\n\n    def __len__(self):\n        return len(self.samples)\n\n    def __getitem__(self, idx):\n        row = self.samples[idx]\n        img = Image.open(os.path.join(self.image_dir, row.image_path)).convert(\"RGB\")\n        if self.transform:\n            img = self.transform(img)\n        target = np.array([getattr(row, t) for t in TARGET_COLUMNS], dtype=np.float32)\n        if self.scaler:\n            target = self.scaler.transform(target.reshape(1,-1))[0]\n        return img, torch.tensor(target)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-27T02:22:58.147441Z","iopub.execute_input":"2025-12-27T02:22:58.148054Z","iopub.status.idle":"2025-12-27T02:22:58.153860Z","shell.execute_reply.started":"2025-12-27T02:22:58.148026Z","shell.execute_reply":"2025-12-27T02:22:58.153245Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"transform = transforms.Compose([\n    transforms.Resize((IMG_SIZE, IMG_SIZE)),\n    transforms.ToTensor(),\n    transforms.Normalize([0.485,0.456,0.406],[0.229,0.224,0.225])\n])\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-27T02:23:05.582408Z","iopub.execute_input":"2025-12-27T02:23:05.582688Z","iopub.status.idle":"2025-12-27T02:23:05.587034Z","shell.execute_reply.started":"2025-12-27T02:23:05.582663Z","shell.execute_reply":"2025-12-27T02:23:05.586170Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Weighted Loss & Metric","metadata":{}},{"cell_type":"code","source":"TARGET_WEIGHTS = torch.tensor([0.1,0.1,0.1,0.2,0.5], device=DEVICE)\n\nclass WeightedMSELoss(nn.Module):\n    def __init__(self, weights):\n        super().__init__()\n        self.register_buffer(\"weights\", weights)\n    def forward(self, preds, targets):\n        return ((preds-targets)**2 * self.weights).mean()\n\ncriterion = WeightedMSELoss(TARGET_WEIGHTS)\n\ndef weighted_r2_score(y_true, y_pred, weights):\n    mean_y = y_true.mean(0, keepdim=True)\n    ss_res = ((y_true - y_pred)**2).sum(0)\n    ss_tot = ((y_true - mean_y)**2).sum(0)\n    return (1 - (weights*ss_res).sum() / ((weights*ss_tot).sum()+1e-8)).item()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-27T02:23:21.250730Z","iopub.execute_input":"2025-12-27T02:23:21.251511Z","iopub.status.idle":"2025-12-27T02:23:21.401733Z","shell.execute_reply.started":"2025-12-27T02:23:21.251482Z","shell.execute_reply":"2025-12-27T02:23:21.401132Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# CNN Architecture With Dropout","metadata":{}},{"cell_type":"code","source":"class CNNWithDropout(nn.Module):\n    def __init__(self, num_filters=256, num_blocks=3, dropout=0.0, num_outputs=5):\n        super().__init__()\n        layers = []\n        in_channels = 3\n\n        for _ in range(num_blocks):\n            layers += [\n                nn.Conv2d(in_channels, num_filters, 3, padding=1),\n                nn.BatchNorm2d(num_filters),\n                nn.ReLU(inplace=True),\n                nn.MaxPool2d(2),\n                nn.Dropout(dropout)\n            ]\n            in_channels = num_filters\n\n        layers.append(nn.AdaptiveAvgPool2d(1))\n        self.features = nn.Sequential(*layers)\n        self.regressor = nn.Linear(num_filters, num_outputs)\n\n    def forward(self, x):\n        x = self.features(x)\n        return self.regressor(x.flatten(1))\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-27T02:23:39.458140Z","iopub.execute_input":"2025-12-27T02:23:39.458645Z","iopub.status.idle":"2025-12-27T02:23:39.464322Z","shell.execute_reply.started":"2025-12-27T02:23:39.458620Z","shell.execute_reply":"2025-12-27T02:23:39.463456Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# One-Epoch Train / Eval Function","metadata":{}},{"cell_type":"code","source":"def run_epoch(model, loader, optimizer=None):\n    training = optimizer is not None\n    model.train() if training else model.eval()\n\n    total_loss, total_r2, n = 0, 0, 0\n    with torch.set_grad_enabled(training):\n        for imgs, targets in loader:\n            imgs, targets = imgs.to(DEVICE), targets.to(DEVICE)\n            preds = model(imgs)\n            loss = criterion(preds, targets)\n            if training:\n                optimizer.zero_grad()\n                loss.backward()\n                optimizer.step()\n            b = imgs.size(0)\n            total_loss += loss.item()*b\n            total_r2 += weighted_r2_score(targets, preds, TARGET_WEIGHTS)*b\n            n += b\n    return total_loss/n, total_r2/n\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-27T02:23:56.084198Z","iopub.execute_input":"2025-12-27T02:23:56.084498Z","iopub.status.idle":"2025-12-27T02:23:56.089860Z","shell.execute_reply.started":"2025-12-27T02:23:56.084476Z","shell.execute_reply":"2025-12-27T02:23:56.089181Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Experiment Setup – Dropout & Weight Decay Grid","metadata":{}},{"cell_type":"code","source":"dropout_options = [0.0, 0.2, 0.4, 0.6]\nweight_decay_options = [0.0, 1e-4, 1e-3]\n\nresults = {}\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-27T02:24:26.873292Z","iopub.execute_input":"2025-12-27T02:24:26.873769Z","iopub.status.idle":"2025-12-27T02:24:26.877449Z","shell.execute_reply.started":"2025-12-27T02:24:26.873744Z","shell.execute_reply":"2025-12-27T02:24:26.876727Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Training Loop Across Grid","metadata":{}},{"cell_type":"code","source":"for dropout in dropout_options:\n    for wd in weight_decay_options:\n        key = f\"dropout_{dropout}_wd_{wd}\"\n        print(f\"\\n==============================\")\n        print(f\" Training: {key}\")\n        print(f\"==============================\")\n\n        train_dataset = BiomassDataset(train_df, os.path.join(BASE_PATH,\"train\"), transform, scaler)\n        val_dataset = BiomassDataset(val_df, os.path.join(BASE_PATH,\"train\"), transform, scaler)\n\n        train_loader = DataLoader(train_dataset, BATCH_SIZE, shuffle=True, num_workers=2)\n        val_loader = DataLoader(val_dataset, BATCH_SIZE, shuffle=False, num_workers=2)\n\n        model = CNNWithDropout(dropout=dropout).to(DEVICE)\n        optimizer = optim.Adam(model.parameters(), lr=LEARNING_RATE, weight_decay=wd)\n\n        train_losses, val_losses = [], []\n        train_r2s, val_r2s = [], []\n        best_val_r2 = -1e9\n\n        for epoch in range(NUM_EPOCHS):\n            tr_l, tr_r2 = run_epoch(model, train_loader, optimizer)\n            va_l, va_r2 = run_epoch(model, val_loader)\n\n            train_losses.append(tr_l)\n            val_losses.append(va_l)\n            train_r2s.append(tr_r2)\n            val_r2s.append(va_r2)\n\n            best_val_r2 = max(best_val_r2, va_r2)\n\n            print(f\"Epoch {epoch+1} | Train R²: {tr_r2:.4f} | Val R²: {va_r2:.4f}\")\n\n        results[key] = {\n            \"avg_train_r2\": np.mean(train_r2s),\n            \"avg_val_r2\": np.mean(val_r2s),\n            \"best_val_r2\": best_val_r2,\n            \"avg_val_loss\": np.mean(val_losses),\n            \"val_r2s\": val_r2s\n        }\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-27T02:24:41.530207Z","iopub.execute_input":"2025-12-27T02:24:41.530484Z","iopub.status.idle":"2025-12-27T02:29:56.899004Z","shell.execute_reply.started":"2025-12-27T02:24:41.530462Z","shell.execute_reply":"2025-12-27T02:29:56.897772Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Summary Table","metadata":{}},{"cell_type":"code","source":"summary_df = pd.DataFrame([\n    {\n        \"Config\": k,\n        \"Avg Val R²\": results[k][\"avg_val_r2\"],\n        \"Best Val R²\": results[k][\"best_val_r2\"],\n        \"Avg Val Loss\": results[k][\"avg_val_loss\"]\n    }\n    for k in results\n]).sort_values(\"Avg Val R²\", ascending=False)\n\ndisplay(summary_df)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-27T02:29:56.899749Z","iopub.status.idle":"2025-12-27T02:29:56.900029Z","shell.execute_reply.started":"2025-12-27T02:29:56.899879Z","shell.execute_reply":"2025-12-27T02:29:56.899893Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Validation R² Curves","metadata":{}},{"cell_type":"code","source":"plt.figure(figsize=(16,6))\nfor k in results:\n    plt.plot(results[k][\"val_r2s\"], label=k)\nplt.title(\"Validation Weighted R² – Dropout & Weight Decay\")\nplt.xlabel(\"Epoch\")\nplt.ylabel(\"Weighted R²\")\nplt.legend()\nplt.show()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-27T02:29:56.901778Z","iopub.status.idle":"2025-12-27T02:29:56.902223Z","shell.execute_reply.started":"2025-12-27T02:29:56.902043Z","shell.execute_reply":"2025-12-27T02:29:56.902064Z"}},"outputs":[],"execution_count":null}]}