{"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\nimport torch\nimport torch.nn as nn\nimport torch.optim as optim\nfrom torch.utils.data import Dataset, DataLoader\nfrom torchvision import transforms\nfrom sklearn.model_selection import train_test_split\nfrom sklearn.preprocessing import StandardScaler\nfrom tqdm import tqdm\n\nwarnings.filterwarnings(\"ignore\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-26T22:17:18.221221Z","iopub.execute_input":"2025-12-26T22:17:18.221927Z","iopub.status.idle":"2025-12-26T22:17:24.254693Z","shell.execute_reply.started":"2025-12-26T22:17:18.221871Z","shell.execute_reply":"2025-12-26T22:17:24.254123Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Config & 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  # ✅ Added this\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-26T22:17:24.256084Z","iopub.execute_input":"2025-12-26T22:17:24.256529Z","iopub.status.idle":"2025-12-26T22:17:24.322754Z","shell.execute_reply.started":"2025-12-26T22:17:24.256487Z","shell.execute_reply":"2025-12-26T22:17:24.322133Z"}},"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-26T22:17:24.323734Z","iopub.execute_input":"2025-12-26T22:17:24.324010Z","iopub.status.idle":"2025-12-26T22:17:24.395153Z","shell.execute_reply.started":"2025-12-26T22:17:24.323990Z","shell.execute_reply":"2025-12-26T22:17:24.394477Z"}},"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-26T22:17:24.396627Z","iopub.execute_input":"2025-12-26T22:17:24.396855Z","iopub.status.idle":"2025-12-26T22:17:24.410173Z","shell.execute_reply.started":"2025-12-26T22:17:24.396834Z","shell.execute_reply":"2025-12-26T22:17:24.409511Z"}},"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        image = Image.open(os.path.join(self.image_dir, row.image_path)).convert(\"RGB\")\n        if self.transform:\n            image = self.transform(image)\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 image, torch.tensor(target)\n\ntransform = 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\ntrain_dataset = BiomassDataset(train_df, os.path.join(BASE_PATH,\"train\"), transform, scaler)\nval_dataset = BiomassDataset(val_df, os.path.join(BASE_PATH,\"train\"), transform, scaler)\n\ntrain_loader = DataLoader(train_dataset, batch_size=BATCH_SIZE, shuffle=True, num_workers=2)\nval_loader = DataLoader(val_dataset, batch_size=BATCH_SIZE, shuffle=False, num_workers=2)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-26T22:17:24.410958Z","iopub.execute_input":"2025-12-26T22:17:24.411158Z","iopub.status.idle":"2025-12-26T22:17:24.428286Z","shell.execute_reply.started":"2025-12-26T22:17:24.411139Z","shell.execute_reply":"2025-12-26T22:17:24.427694Z"}},"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-26T22:17:24.429060Z","iopub.execute_input":"2025-12-26T22:17:24.429271Z","iopub.status.idle":"2025-12-26T22:17:24.601915Z","shell.execute_reply.started":"2025-12-26T22:17:24.429251Z","shell.execute_reply":"2025-12-26T22:17:24.601091Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Flexible CNN Model for Experimenting with Blocks","metadata":{}},{"cell_type":"code","source":"class FlexibleCNN(nn.Module):\n    def __init__(self, num_blocks=3, base_channels=32, num_outputs=5):\n        super().__init__()\n        layers = []\n        in_channels = 3\n        out_channels = base_channels\n        for i in range(num_blocks):\n            layers.append(nn.Conv2d(in_channels, out_channels, 3, padding=1))\n            layers.append(nn.BatchNorm2d(out_channels))\n            layers.append(nn.ReLU(inplace=True))\n            layers.append(nn.MaxPool2d(2))\n            in_channels = out_channels\n            out_channels *= 2\n        layers.append(nn.AdaptiveAvgPool2d(1))\n        self.features = nn.Sequential(*layers)\n        self.regressor = nn.Linear(in_channels, num_outputs)\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-26T22:17:24.602945Z","iopub.execute_input":"2025-12-26T22:17:24.603238Z","iopub.status.idle":"2025-12-26T22:17:24.609349Z","shell.execute_reply.started":"2025-12-26T22:17:24.603212Z","shell.execute_reply":"2025-12-26T22:17:24.608691Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Epoch 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    total_loss, total_r2, n = 0.0, 0.0, 0\n    with torch.set_grad_enabled(training):\n        for images, targets in loader:\n            images, targets = images.to(DEVICE), targets.to(DEVICE)\n            preds = model(images)\n            loss = criterion(preds, targets)\n            if training:\n                optimizer.zero_grad()\n                loss.backward()\n                optimizer.step()\n            b = images.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-26T22:17:24.610166Z","iopub.execute_input":"2025-12-26T22:17:24.610458Z","iopub.status.idle":"2025-12-26T22:17:24.624105Z","shell.execute_reply.started":"2025-12-26T22:17:24.610439Z","shell.execute_reply":"2025-12-26T22:17:24.623436Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Training & Plotting for Multiple Block Configurations","metadata":{}},{"cell_type":"code","source":"block_options = [2, 3, 4, 5]\nresults = {}\n\nfor num_blocks in block_options:\n    print(f\"\\nTraining CNN with {num_blocks} blocks...\")\n    \n    model = FlexibleCNN(\n        num_blocks=num_blocks,\n        base_channels=32,\n        num_outputs=len(TARGET_COLUMNS)\n    ).to(DEVICE)\n\n    optimizer = optim.Adam(\n        model.parameters(),\n        lr=LEARNING_RATE,\n        weight_decay=WEIGHT_DECAY\n    )\n\n    train_losses, val_losses = [], []\n    train_r2s, val_r2s = [], []\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        print(\n            f\"Epoch {epoch+1} | \"\n            f\"Train Loss: {tr_l:.4f}, Train R²: {tr_r2:.4f} | \"\n            f\"Val Loss: {va_l:.4f}, Val R²: {va_r2:.4f}\"\n        )\n\n    results[num_blocks] = {\n        \"avg_train_loss\": np.mean(train_losses),\n        \"avg_val_loss\": np.mean(val_losses),\n        \"avg_train_r2\": np.mean(train_r2s),\n        \"avg_val_r2\": np.mean(val_r2s),\n        \"train_losses\": train_losses,\n        \"val_losses\": val_losses,\n        \"train_r2s\": train_r2s,\n        \"val_r2s\": val_r2s\n    }\n\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-26T22:17:24.624915Z","iopub.execute_input":"2025-12-26T22:17:24.625214Z","iopub.status.idle":"2025-12-26T22:31:59.546766Z","shell.execute_reply.started":"2025-12-26T22:17:24.625194Z","shell.execute_reply":"2025-12-26T22:31:59.545955Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"summary_blocks = pd.DataFrame.from_dict(results, orient=\"index\")\nsummary_blocks.index.name = \"Num_Blocks\"\nsummary_blocks.reset_index(inplace=True)\nsummary_blocks\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-26T22:31:59.549396Z","iopub.execute_input":"2025-12-26T22:31:59.549661Z","iopub.status.idle":"2025-12-26T22:31:59.577624Z","shell.execute_reply.started":"2025-12-26T22:31:59.549634Z","shell.execute_reply":"2025-12-26T22:31:59.577097Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ----------------------------\n# Plot curves for all block options\n# ----------------------------\nplt.figure(figsize=(16,6))\nfor num_blocks in block_options:\n    plt.plot(results[num_blocks][\"val_r2s\"], label=f\"Val R² - {num_blocks} blocks\")\nplt.title(\"Validation Weighted R² per Epoch for Different Block Configs\")\nplt.xlabel(\"Epoch\")\nplt.ylabel(\"Weighted R²\")\nplt.legend()\nplt.show()\n\nplt.figure(figsize=(16,6))\nfor num_blocks in block_options:\n    plt.plot(results[num_blocks][\"val_losses\"], label=f\"Val Loss - {num_blocks} blocks\")\nplt.title(\"Validation Loss per Epoch for Different Block Configs\")\nplt.xlabel(\"Epoch\")\nplt.ylabel(\"Loss\")\nplt.legend()\nplt.show()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-26T22:31:59.578376Z","iopub.execute_input":"2025-12-26T22:31:59.578595Z","iopub.status.idle":"2025-12-26T22:32:00.016526Z","shell.execute_reply.started":"2025-12-26T22:31:59.578576Z","shell.execute_reply":"2025-12-26T22:32:00.015685Z"}},"outputs":[],"execution_count":null}]}