{"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:12:13.425072Z","iopub.execute_input":"2025-12-27T02:12:13.425280Z","iopub.status.idle":"2025-12-27T02:12:22.294540Z","shell.execute_reply.started":"2025-12-27T02:12:13.425258Z","shell.execute_reply":"2025-12-27T02:12:22.293948Z"}},"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\nWEIGHT_DECAY = 1e-4\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:12:22.298417Z","iopub.execute_input":"2025-12-27T02:12:22.298614Z","iopub.status.idle":"2025-12-27T02:12:22.357709Z","shell.execute_reply.started":"2025-12-27T02:12:22.298595Z","shell.execute_reply":"2025-12-27T02:12:22.356950Z"}},"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\"))\n\ndf = df.pivot_table(\n    index=\"image_path\",\n    columns=\"target_name\",\n    values=\"target\"\n).reset_index()\n\ndf.fillna(0, inplace=True)\n\nTARGET_COLUMNS = [\n    'Dry_Green_g',\n    'Dry_Dead_g',\n    'Dry_Clover_g',\n    'GDM_g',\n    'Dry_Total_g'\n]\n\nprint(\"Dataset shape:\", df.shape)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-27T02:12:31.634131Z","iopub.execute_input":"2025-12-27T02:12:31.634864Z","iopub.status.idle":"2025-12-27T02:12:31.706496Z","shell.execute_reply.started":"2025-12-27T02:12:31.634834Z","shell.execute_reply":"2025-12-27T02:12:31.705720Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Train / Validation Split & Target Scaling","metadata":{}},{"cell_type":"code","source":"train_df, val_df = train_test_split(\n    df,\n    test_size=0.2,\n    random_state=SEED,\n    shuffle=True\n)\n\nscaler = StandardScaler()\nscaler.fit(train_df[TARGET_COLUMNS].values)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-27T02:12:47.366227Z","iopub.execute_input":"2025-12-27T02:12:47.366979Z","iopub.status.idle":"2025-12-27T02:12:47.382389Z","shell.execute_reply.started":"2025-12-27T02:12:47.366947Z","shell.execute_reply":"2025-12-27T02:12:47.381704Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Dataset Class","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(\n            os.path.join(self.image_dir, row.image_path)\n        ).convert(\"RGB\")\n\n        if self.transform:\n            img = self.transform(img)\n\n        target = np.array(\n            [getattr(row, t) for t in TARGET_COLUMNS],\n            dtype=np.float32\n        )\n\n        if self.scaler:\n            target = self.scaler.transform(target.reshape(1, -1))[0]\n\n        return img, torch.tensor(target)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-27T02:13:02.820844Z","iopub.execute_input":"2025-12-27T02:13:02.821164Z","iopub.status.idle":"2025-12-27T02:13:02.829293Z","shell.execute_reply.started":"2025-12-27T02:13:02.821137Z","shell.execute_reply":"2025-12-27T02:13:02.828455Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Augmentation Strategies ","metadata":{}},{"cell_type":"code","source":"augmentation_configs = {\n    \"no_aug\": 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\n    \"light_aug\": transforms.Compose([\n        transforms.Resize((IMG_SIZE, IMG_SIZE)),\n        transforms.RandomHorizontalFlip(0.5),\n        transforms.ToTensor(),\n        transforms.Normalize([0.485,0.456,0.406],[0.229,0.224,0.225])\n    ]),\n\n    \"medium_aug\": transforms.Compose([\n        transforms.Resize((IMG_SIZE, IMG_SIZE)),\n        transforms.RandomHorizontalFlip(0.5),\n        transforms.RandomRotation(15),\n        transforms.ColorJitter(0.2,0.2,0.2),\n        transforms.ToTensor(),\n        transforms.Normalize([0.485,0.456,0.406],[0.229,0.224,0.225])\n    ]),\n\n    \"strong_aug\": transforms.Compose([\n        transforms.Resize((IMG_SIZE, IMG_SIZE)),\n        transforms.RandomHorizontalFlip(0.5),\n        transforms.RandomVerticalFlip(0.3),\n        transforms.RandomRotation(25),\n        transforms.ColorJitter(0.3,0.3,0.3,0.05),\n        transforms.RandomAffine(0, translate=(0.1,0.1), scale=(0.9,1.1)),\n        transforms.ToTensor(),\n        transforms.Normalize([0.485,0.456,0.406],[0.229,0.224,0.225])\n    ])\n}\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-27T02:13:24.083005Z","iopub.execute_input":"2025-12-27T02:13:24.083308Z","iopub.status.idle":"2025-12-27T02:13:24.090232Z","shell.execute_reply.started":"2025-12-27T02:13:24.083282Z","shell.execute_reply":"2025-12-27T02:13:24.089561Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Fixed Validation Transform","metadata":{}},{"cell_type":"code","source":"val_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:13:47.165848Z","iopub.execute_input":"2025-12-27T02:13:47.166376Z","iopub.status.idle":"2025-12-27T02:13:47.170322Z","shell.execute_reply.started":"2025-12-27T02:13:47.166351Z","shell.execute_reply":"2025-12-27T02:13:47.169690Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Weighted Loss ","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\n    def forward(self, preds, targets):\n        return ((preds - targets) ** 2 * self.weights).mean()\n\ncriterion = WeightedMSELoss(TARGET_WEIGHTS)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-27T02:14:28.298271Z","iopub.execute_input":"2025-12-27T02:14:28.298560Z","iopub.status.idle":"2025-12-27T02:14:28.462482Z","shell.execute_reply.started":"2025-12-27T02:14:28.298534Z","shell.execute_reply":"2025-12-27T02:14:28.461683Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Weighted R² Metric","metadata":{}},{"cell_type":"code","source":"def 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() /\n            ((weights*ss_tot).sum() + 1e-8)).item()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-27T02:14:37.415207Z","iopub.execute_input":"2025-12-27T02:14:37.416014Z","iopub.status.idle":"2025-12-27T02:14:37.420486Z","shell.execute_reply.started":"2025-12-27T02:14:37.415976Z","shell.execute_reply":"2025-12-27T02:14:37.419800Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# CNN Architecture (Fixed)","metadata":{}},{"cell_type":"code","source":"class CNN(nn.Module):\n    def __init__(self, num_filters=256, num_blocks=3, 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            ]\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:14:55.261189Z","iopub.execute_input":"2025-12-27T02:14:55.261478Z","iopub.status.idle":"2025-12-27T02:14:55.267089Z","shell.execute_reply.started":"2025-12-27T02:14:55.261454Z","shell.execute_reply":"2025-12-27T02:14:55.266322Z"}},"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\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\n            if training:\n                optimizer.zero_grad()\n                loss.backward()\n                optimizer.step()\n\n            b = imgs.size(0)\n            total_loss += loss.item() * b\n            total_r2 += weighted_r2_score(\n                targets, preds, TARGET_WEIGHTS\n            ) * b\n            n += b\n\n    return total_loss/n, total_r2/n\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-27T02:15:12.495498Z","iopub.execute_input":"2025-12-27T02:15:12.496192Z","iopub.status.idle":"2025-12-27T02:15:12.501437Z","shell.execute_reply.started":"2025-12-27T02:15:12.496161Z","shell.execute_reply":"2025-12-27T02:15:12.500851Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Augmentation Experiment Training Loop","metadata":{}},{"cell_type":"code","source":"results = {}\n\nfor aug_name, train_tf in augmentation_configs.items():\n    print(f\"\\n==============================\")\n    print(f\" Augmentation: {aug_name}\")\n    print(f\"==============================\")\n\n    train_dataset = BiomassDataset(\n        train_df, os.path.join(BASE_PATH,\"train\"),\n        transform=train_tf, scaler=scaler\n    )\n\n    val_dataset = BiomassDataset(\n        val_df, os.path.join(BASE_PATH,\"train\"),\n        transform=val_transform, scaler=scaler\n    )\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 = CNN().to(DEVICE)\n    optimizer = optim.Adam(model.parameters(), lr=LEARNING_RATE, weight_decay=WEIGHT_DECAY)\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(\n            f\"Epoch {epoch+1} | \"\n            f\"Train R²: {tr_r2:.4f} | \"\n            f\"Val R²: {va_r2:.4f}\"\n        )\n\n    results[aug_name] = {\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:15:31.067788Z","iopub.execute_input":"2025-12-27T02:15:31.068396Z","iopub.status.idle":"2025-12-27T02:17:52.303454Z","shell.execute_reply.started":"2025-12-27T02:15:31.068370Z","shell.execute_reply":"2025-12-27T02:17:52.302617Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Summary Table","metadata":{}},{"cell_type":"code","source":"summary_df = pd.DataFrame([\n    {\n        \"Augmentation\": 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:17:52.305085Z","iopub.execute_input":"2025-12-27T02:17:52.305362Z","iopub.status.idle":"2025-12-27T02:17:52.330800Z","shell.execute_reply.started":"2025-12-27T02:17:52.305334Z","shell.execute_reply":"2025-12-27T02:17:52.330046Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Validation R² Plot","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² – Augmentation Strategies\")\nplt.xlabel(\"Epoch\")\nplt.ylabel(\"Weighted R²\")\nplt.legend()\nplt.show()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-27T02:17:52.331813Z","iopub.execute_input":"2025-12-27T02:17:52.332177Z","iopub.status.idle":"2025-12-27T02:17:52.545877Z","shell.execute_reply.started":"2025-12-27T02:17:52.332149Z","shell.execute_reply":"2025-12-27T02:17:52.545141Z"}},"outputs":[],"execution_count":null}]}