{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.11.13","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"}],"dockerImageVersionId":31153,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"# 🌿 CSIRO Biomass Prediction — Deep Learning Baseline (ResNet34)\nIn this notebook, we build a deep learning model using a ResNet34 backbone to predict various biomass targets from RGB images.  \nThe pipeline covers:\n1. Exploratory Data Analysis (EDA)\n2. Custom PyTorch Dataset\n3. Model Architecture (StableResNet)\n4. Training & Validation Loops\n5. Model Evaluation & Visualization\n6. Test Prediction Generation\n","metadata":{}},{"cell_type":"markdown","source":"#  Imports","metadata":{}},{"cell_type":"code","source":"import os\nimport torch\nimport numpy as np\nimport pandas as pd\nimport seaborn as sns\nimport torch.nn as nn\nimport matplotlib.pyplot as plt\nimport torchvision.transforms as transforms\nfrom PIL import Image\nfrom collections import defaultdict\nfrom torchvision.models import resnet34\nfrom torch.utils.data import Dataset, DataLoader\nfrom sklearn.model_selection import train_test_split\nfrom sklearn.metrics import mean_squared_error, r2_score\nimport warnings\nwarnings.filterwarnings('ignore')\n\n# --- Reproducibility ---\ndef set_seed(seed=42):\n    torch.manual_seed(seed)\n    torch.cuda.manual_seed(seed)\n    np.random.seed(seed)\n    torch.backends.cudnn.deterministic = True\nset_seed(42)","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# 1️Data Loading and Exploration","metadata":{}},{"cell_type":"code","source":"print(\"📥 Loading dataset...\")\ntrain_df = pd.read_csv('/kaggle/input/csiro-biomass/train.csv')\nprint(f\"Training data shape: {train_df.shape}\")\nprint(train_df.head())\n\n# --- Target Statistics ---\nprint(\"\\n📊 Target Value Statistics:\")\nprint(train_df['target'].describe())\n\n# --- EDA: Distributions ---\nplt.figure(figsize=(15, 5))\n\nplt.subplot(1, 3, 1)\ntrain_df['target_name'].value_counts().plot(kind='bar', color='teal')\nplt.title('Target Name Distribution')\nplt.xticks(rotation=45)\n\nplt.subplot(1, 3, 2)\nsns.histplot(train_df['target'], bins=50, kde=True, color='green')\nplt.title('Target Value Distribution')\n\nplt.subplot(1, 3, 3)\nsns.boxplot(x='target_name', y='target', data=train_df, palette='Set2')\nplt.title('Target Values by Type')\nplt.xticks(rotation=45)\n\nplt.tight_layout()\nplt.show()\n\n#  Outlier Analysis \noutlier_threshold = train_df['target'].quantile(0.99)\noutliers = train_df[train_df['target'] > outlier_threshold]\nprint(f\"99th percentile threshold: {outlier_threshold:.2f}\")\nprint(f\"Number of outliers: {len(outliers)}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-29T08:05:17.497439Z","iopub.status.idle":"2025-10-29T08:05:17.497838Z","shell.execute_reply.started":"2025-10-29T08:05:17.497617Z","shell.execute_reply":"2025-10-29T08:05:17.497637Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"#  Custom Dataset Class","metadata":{}},{"cell_type":"code","source":"class BiomassDataset(Dataset):\n    def __init__(self, df, images_dir, transform=None, is_test=False):\n        self.df = df.reset_index(drop=True)\n        self.images_dir = images_dir\n        self.transform = transform\n        self.is_test = is_test\n        self.image_cache = {}\n        if not is_test:\n            self.target_mapping = {\n                'Dry_Green_g': 0, 'Dry_Dead_g': 1, 'Dry_Clover_g': 2,\n                'GDM_g': 3, 'Dry_Total_g': 4\n            }\n\n    def __len__(self):\n        return len(self.df)\n    \n    def _load_image(self, image_path):\n        if image_path in self.image_cache:\n            return self.image_cache[image_path]\n        try:\n            image = Image.open(image_path).convert('RGB')\n            self.image_cache[image_path] = image\n            return image\n        except Exception as e:\n            print(f\"Error loading {image_path}: {e}\")\n            return Image.new('RGB', (2000, 1000), color='white')\n\n    def __getitem__(self, idx):\n        row = self.df.iloc[idx]\n        image_path = os.path.join(self.images_dir, row['image_path'])\n        image = self._load_image(image_path)\n        if self.transform:\n            image = self.transform(image)\n        if self.is_test:\n            return image, row['sample_id']\n        target_value = row['target']\n        target_type = self.target_mapping[row['target_name']]\n        return image, torch.tensor(target_value, dtype=torch.float32), target_type\n\n#  Data Augmentation \ntrain_transform = transforms.Compose([\n    transforms.Resize((400, 200)),\n    transforms.RandomHorizontalFlip(p=0.3),\n    transforms.ColorJitter(brightness=0.1, contrast=0.1),\n    transforms.ToTensor(),\n    transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])\n])\nval_transform = transforms.Compose([\n    transforms.Resize((400, 200)),\n    transforms.ToTensor(),\n    transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])\n])","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-29T07:57:42.014384Z","iopub.execute_input":"2025-10-29T07:57:42.014781Z","iopub.status.idle":"2025-10-29T08:05:17.412667Z","shell.execute_reply.started":"2025-10-29T07:57:42.014756Z","shell.execute_reply":"2025-10-29T08:05:17.409888Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"\n#  Model Definition — StableResNet","metadata":{}},{"cell_type":"code","source":"\nclass StableResNet(nn.Module):\n    def __init__(self, num_targets=5, pretrained=False):\n        super().__init__()\n        self.backbone = resnet34(weights=None if not pretrained else 'IMAGENET1K_V1')\n        in_features = self.backbone.fc.in_features\n        self.backbone.fc = nn.Sequential(\n            nn.Linear(in_features, 256),\n            nn.BatchNorm1d(256),\n            nn.ReLU(inplace=True),\n            nn.Dropout(0.2),\n            nn.Linear(256, 128),\n            nn.ReLU(inplace=True),\n            nn.Linear(128, num_targets)\n        )\n        self._initialize_weights()\n        \n    def _initialize_weights(self):\n        for m in self.modules():\n            if isinstance(m, nn.Conv2d):\n                nn.init.kaiming_normal_(m.weight, nonlinearity='relu')\n            elif isinstance(m, nn.Linear):\n                nn.init.normal_(m.weight, 0, 0.01)\n                nn.init.constant_(m.bias, 0)\n    \n    def forward(self, x, target_type=None):\n        return self.backbone(x)","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"\n#  Train | Validate Functions\n","metadata":{}},{"cell_type":"code","source":"def train_epoch(model, dataloader, criterion, optimizer, device):\n    model.train()\n    total_loss = 0\n    for batch_idx, (images, targets, types) in enumerate(dataloader):\n        images, targets, types = images.to(device), targets.to(device), types.to(device)\n        optimizer.zero_grad()\n        outputs = model(images)\n        preds = outputs[torch.arange(len(types)), types]\n        loss = criterion(preds, targets)\n        loss.backward()\n        torch.nn.utils.clip_grad_norm_(model.parameters(), 1.0)\n        optimizer.step()\n        total_loss += loss.item()\n    return total_loss / len(dataloader)\n\ndef validate_model(model, dataloader, criterion, device):\n    model.eval()\n    total_loss, preds_all, targs_all = 0, [], []\n    with torch.no_grad():\n        for images, targets, types in dataloader:\n            images, targets, types = images.to(device), targets.to(device), types.to(device)\n            outputs = model(images)\n            preds = outputs[torch.arange(len(types)), types]\n            loss = criterion(preds, targets)\n            total_loss += loss.item()\n            preds_all.extend(preds.cpu().numpy())\n            targs_all.extend(targets.cpu().numpy())\n    mse = mean_squared_error(targs_all, preds_all)\n    r2 = r2_score(targs_all, preds_all)\n    return total_loss / len(dataloader), mse, r2, preds_all, targs_all\n    \ndef train_model(model, train_loader, val_loader, criterion, optimizer, scheduler, device, num_epochs=25):\n    best_val_loss, patience, counter = float('inf'), 7, 0\n    train_hist, val_hist = [], []\n    for epoch in range(num_epochs):\n        print(f\"\\nEpoch {epoch+1}/{num_epochs}\")\n        train_loss = train_epoch(model, train_loader, criterion, optimizer, device)\n        val_loss, mse, r2, preds, targs = validate_model(model, val_loader, criterion, device)\n        scheduler.step(val_loss)\n        train_hist.append(train_loss)\n        val_hist.append(val_loss)\n        print(f\"Train Loss: {train_loss:.4f} | Val Loss: {val_loss:.4f} | MSE: {mse:.4f} | R²: {r2:.4f}\")\n        if val_loss < best_val_loss:\n            best_val_loss = val_loss\n            torch.save(model.state_dict(), \"best_model.pth\")\n            counter = 0\n            print(\"✅ New best model saved!\")\n        else:\n            counter += 1\n            if counter >= patience:\n                print(\"⏹ Early stopping triggered.\")\n                break\n    model.load_state_dict(torch.load(\"best_model.pth\"))\n    return train_hist, val_hist","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"\n#  Data Split & Loader Setup","metadata":{}},{"cell_type":"code","source":"train_idx, val_idx = train_test_split(\n    range(len(train_df)),\n    test_size=0.2,\n    stratify=train_df['target_name'],\n    random_state=42\n)\ntrain_data = train_df.iloc[train_idx].reset_index(drop=True)\nval_data = train_df.iloc[val_idx].reset_index(drop=True)\n\ntrain_ds = BiomassDataset(train_data, '/kaggle/input/csiro-biomass', train_transform)\nval_ds = BiomassDataset(val_data, '/kaggle/input/csiro-biomass', val_transform)\n\ntrain_loader = DataLoader(train_ds, batch_size=32, shuffle=True, num_workers=2)\nval_loader = DataLoader(val_ds, batch_size=32, shuffle=False, num_workers=2)\n\ndevice = torch.device('cuda' if torch.cuda.is_available() else 'cpu')\nprint(f\"Using device: {device}\")\n","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"#  Model Training","metadata":{}},{"cell_type":"code","source":"\nmodel = StableResNet(num_targets=5).to(device)\ncriterion = nn.SmoothL1Loss()\noptimizer = torch.optim.AdamW(model.parameters(), lr=0.001, weight_decay=0.01)\nscheduler = torch.optim.lr_scheduler.ReduceLROnPlateau(optimizer, patience=3, factor=0.5, verbose=True)\n\nprint(\"🚀 Training started...\")\ntrain_losses, val_losses = train_model(model, train_loader, val_loader, criterion, optimizer, scheduler, device)\n\n","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Evaluation","metadata":{}},{"cell_type":"code","source":"model.eval()\nval_loss, mse, r2, preds, targs = validate_model(model, val_loader, criterion, device)\nprint(f\"\\nFinal Validation Results → Loss: {val_loss:.4f} | MSE: {mse:.4f} | R²: {r2:.4f}\")\n\nplt.figure(figsize=(15,5))\nplt.subplot(1,3,1)\nplt.plot(train_losses, label='Train')\nplt.plot(val_losses, label='Validation')\nplt.legend(); plt.title(\"Loss Curve\")\n\nplt.subplot(1,3,2)\nsns.scatterplot(x=targs, y=preds, alpha=0.6)\nplt.plot([0, max(targs)], [0, max(targs)], 'r--')\nplt.xlabel(\"Actual\"); plt.ylabel(\"Predicted\"); plt.title(\"Predicted vs Actual\")\n\nplt.subplot(1,3,3)\nsns.histplot(np.array(preds)-np.array(targs), bins=30, color='purple')\nplt.title(\"Error Distribution\")\n\nplt.tight_layout()\nplt.show()\n\n","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Test Predictions & Submission\n","metadata":{}},{"cell_type":"code","source":"print(\"📤 Generating test predictions...\")\ntest_df = pd.read_csv('/kaggle/input/csiro-biomass/test.csv')\ntest_ds = BiomassDataset(test_df, '/kaggle/input/csiro-biomass', val_transform, is_test=True)\ntest_loader = DataLoader(test_ds, batch_size=32, shuffle=False, num_workers=2)\n\nmodel.eval()\npredictions, sample_ids = [], []\ntarget_mapping = {'Dry_Green_g': 0, 'Dry_Dead_g': 1, 'Dry_Clover_g': 2, 'GDM_g': 3, 'Dry_Total_g': 4}\n\nwith torch.no_grad():\n    for images, batch_ids in test_loader:\n        images = images.to(device)\n        outputs = model(images)\n        for i, sid in enumerate(batch_ids):\n            row = test_df[test_df['sample_id'] == sid].iloc[0]\n            idx = target_mapping[row['target_name']]\n            pred = max(0, outputs[i, idx].item())\n            predictions.append(pred)\n            sample_ids.append(sid)\n\nsubmission = pd.DataFrame({'sample_id': sample_ids, 'target': predictions})\nsubmission.to_csv('submission.csv', index=False)\nprint(\"✅ Submission saved as 'submission.csv'\")\nprint(submission.head())\n","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}