{"cells":[{"cell_type":"markdown","metadata":{},"source":"# CSIRO Biomass Prediction - Baseline Model V2\n**Iteration 1: EfficientNet-B0 + Metadata Features**\n\n## Fixes\n- Changed to use weights=None (train from scratch) to avoid internet dependency\n- This baseline establishes performance without pretrained weights\n- Next iteration will add pretrained weights via dataset\n\n## Estimated Time\n- ~25 minutes for 20 epochs"},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":"import numpy as np\nimport pandas as pd\nimport torch\nimport torch.nn as nn\nfrom torch.utils.data import Dataset, DataLoader\nfrom torchvision import transforms, models\nfrom PIL import Image\nimport time\nfrom sklearn.preprocessing import LabelEncoder, StandardScaler\nfrom sklearn.model_selection import train_test_split\nimport os\nprint(f\"PyTorch version: {torch.__version__}\")\nprint(f\"CUDA available: {torch.cuda.is_available()}\")"},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":"# Configuration\nclass Config:\n    IMG_SIZE = 224\n    BATCH_SIZE = 16\n    EPOCHS = 15  # Reduced slightly for faster baseline\n    LEARNING_RATE = 1e-3\n    DEVICE = 'cuda' if torch.cuda.is_available() else 'cpu'\n    TARGETS = ['Dry_Clover_g', 'Dry_Dead_g', 'Dry_Green_g', 'Dry_Total_g', 'GDM_g']\n    SEED = 42\n\ncfg = Config()\nprint(f\"Using device: {cfg.DEVICE}\")\n\n# Set seeds\nnp.random.seed(cfg.SEED)\ntorch.manual_seed(cfg.SEED)\nif torch.cuda.is_available():\n    torch.cuda.manual_seed(cfg.SEED)"},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":"# Load data\ntrain_df = pd.read_csv('/kaggle/input/csiro-biomass/train.csv')\ntest_df = pd.read_csv('/kaggle/input/csiro-biomass/test.csv')\nsample_sub = pd.read_csv('/kaggle/input/csiro-biomass/sample_submission.csv')\n\nprint(f\"Train shape: {train_df.shape}\")\nprint(f\"Unique train images: {train_df['image_path'].nunique()}\")\nprint(f\"Test shape: {test_df.shape}\")\nprint(f\"\\nTarget statistics:\")\nprint(train_df.groupby('target_name')['target'].describe())"},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":"class BiomassDataset(Dataset):\n    def __init__(self, df, transform=None, is_test=False, species_encoder=None, state_encoder=None, scaler=None):\n        self.df = df.copy()\n        self.image_groups = df.groupby('image_path')\n        self.image_paths = list(self.image_groups.groups.keys())\n        self.transform = transform\n        self.is_test = is_test\n        \n        if is_test:\n            self.species_encoder = species_encoder\n            self.state_encoder = state_encoder\n            self.scaler = scaler\n        else:\n            self.species_encoder = LabelEncoder()\n            self.state_encoder = LabelEncoder()\n            self.scaler = StandardScaler()\n            \n            self.species_encoder.fit(df['Species'].fillna('Unknown'))\n            self.state_encoder.fit(df['State'].fillna('Unknown'))\n            numeric_features = df.groupby('image_path')[['Pre_GSHH_NDVI', 'Height_Ave_cm']].first()\n            self.scaler.fit(numeric_features)\n    \n    def __len__(self):\n        return len(self.image_paths)\n    \n    def __getitem__(self, idx):\n        img_path = self.image_paths[idx]\n        full_path = os.path.join('/kaggle/input/csiro-biomass', img_path)\n        \n        try:\n            image = Image.open(full_path).convert('RGB')\n            if self.transform:\n                image = self.transform(image)\n        except Exception as e:\n            print(f\"Error loading {full_path}: {e}\")\n            image = torch.zeros(3, cfg.IMG_SIZE, cfg.IMG_SIZE)\n        \n        group_data = self.image_groups.get_group(img_path).iloc[0]\n        species = self.species_encoder.transform([group_data['Species']])[0]\n        state = self.state_encoder.transform([group_data['State']])[0]\n        ndvi = group_data['Pre_GSHH_NDVI']\n        height = group_data['Height_Ave_cm']\n        \n        numeric = np.array([[ndvi, height]])\n        numeric_scaled = self.scaler.transform(numeric)[0]\n        metadata = torch.tensor([species, state] + list(numeric_scaled), dtype=torch.float32)\n        \n        if self.is_test:\n            return image, metadata, img_path\n        else:\n            targets = []\n            for target_name in cfg.TARGETS:\n                target_row = self.image_groups.get_group(img_path)\n                target_row = target_row[target_row['target_name'] == target_name]\n                if len(target_row) > 0:\n                    targets.append(target_row['target'].values[0])\n                else:\n                    targets.append(0.0)\n            targets = torch.tensor(targets, dtype=torch.float32)\n            return image, metadata, targets"},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":"class BiomassModel(nn.Module):\n    \"\"\"Simplified model without pretrained weights\"\"\"\n    def __init__(self, num_metadata_features=4, num_targets=5):\n        super().__init__()\n        # EfficientNet-B0 from scratch (no pretrained weights)\n        efficientnet = models.efficientnet_b0(weights=None)  # No pretrained weights\n        self.image_features = efficientnet.features\n        self.image_pool = nn.AdaptiveAvgPool2d(1)\n        img_feature_size = 1280\n        \n        self.metadata_processor = nn.Sequential(\n            nn.Linear(num_metadata_features, 32),\n            nn.ReLU(),\n            nn.Dropout(0.2),\n            nn.Linear(32, 64),\n            nn.ReLU()\n        )\n        \n        combined_size = img_feature_size + 64\n        self.regressor = nn.Sequential(\n            nn.Linear(combined_size, 512),\n            nn.ReLU(),\n            nn.Dropout(0.3),\n            nn.Linear(512, 256),\n            nn.ReLU(),\n            nn.Dropout(0.2),\n            nn.Linear(256, num_targets)\n        )\n    \n    def forward(self, image, metadata):\n        img_feat = self.image_features(image)\n        img_feat = self.image_pool(img_feat)\n        img_feat = img_feat.view(img_feat.size(0), -1)\n        meta_feat = self.metadata_processor(metadata)\n        combined = torch.cat([img_feat, meta_feat], dim=1)\n        outputs = self.regressor(combined)\n        return outputs"},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":"# Training transforms with augmentation\ntrain_transform = transforms.Compose([\n    transforms.Resize((cfg.IMG_SIZE, cfg.IMG_SIZE)),\n    transforms.RandomHorizontalFlip(p=0.5),\n    transforms.RandomVerticalFlip(p=0.5),\n    transforms.RandomRotation(30),\n    transforms.ColorJitter(brightness=0.2, contrast=0.2, saturation=0.2, hue=0.1),\n    transforms.ToTensor(),\n    transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])\n])\n\nval_transform = transforms.Compose([\n    transforms.Resize((cfg.IMG_SIZE, cfg.IMG_SIZE)),\n    transforms.ToTensor(),\n    transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])\n])\n\n# Split train/val\nunique_images = train_df['image_path'].unique()\ntrain_images, val_images = train_test_split(unique_images, test_size=0.2, random_state=cfg.SEED)\ntrain_subset = train_df[train_df['image_path'].isin(train_images)]\nval_subset = train_df[train_df['image_path'].isin(val_images)]\n\nprint(f\"Train images: {len(train_images)}, Val images: {len(val_images)}\")\n\n# Create datasets\ntrain_dataset = BiomassDataset(train_subset, transform=train_transform, is_test=False)\nval_dataset = BiomassDataset(val_subset, transform=val_transform, is_test=False, \n                              species_encoder=train_dataset.species_encoder,\n                              state_encoder=train_dataset.state_encoder,\n                              scaler=train_dataset.scaler)\n\ntrain_loader = DataLoader(train_dataset, batch_size=cfg.BATCH_SIZE, shuffle=True, num_workers=2)\nval_loader = DataLoader(val_dataset, batch_size=cfg.BATCH_SIZE, shuffle=False, num_workers=2)"},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":"# Initialize model\nmodel = BiomassModel().to(cfg.DEVICE)\ncriterion = nn.MSELoss()\noptimizer = torch.optim.AdamW(model.parameters(), lr=cfg.LEARNING_RATE, weight_decay=1e-4)\nscheduler = torch.optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max=cfg.EPOCHS)\n\nprint(f\"Model parameters: {sum(p.numel() for p in model.parameters()):,}\")\nprint(f\"Trainable parameters: {sum(p.numel() for p in model.parameters() if p.requires_grad):,}\")"},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":"# Training loop\nbest_val_loss = float('inf')\ntrain_losses = []\nval_losses = []\nstart_time = time.time()\n\nfor epoch in range(cfg.EPOCHS):\n    # Train\n    model.train()\n    train_loss = 0\n    for images, metadata, targets in train_loader:\n        images = images.to(cfg.DEVICE)\n        metadata = metadata.to(cfg.DEVICE)\n        targets = targets.to(cfg.DEVICE)\n        \n        optimizer.zero_grad()\n        outputs = model(images, metadata)\n        loss = criterion(outputs, targets)\n        loss.backward()\n        optimizer.step()\n        train_loss += loss.item()\n    \n    train_loss /= len(train_loader)\n    train_losses.append(train_loss)\n    \n    # Validate\n    model.eval()\n    val_loss = 0\n    with torch.no_grad():\n        for images, metadata, targets in val_loader:\n            images = images.to(cfg.DEVICE)\n            metadata = metadata.to(cfg.DEVICE)\n            targets = targets.to(cfg.DEVICE)\n            outputs = model(images, metadata)\n            loss = criterion(outputs, targets)\n            val_loss += loss.item()\n    \n    val_loss /= len(val_loader)\n    val_losses.append(val_loss)\n    scheduler.step()\n    \n    # Save best model\n    if val_loss < best_val_loss:\n        best_val_loss = val_loss\n        torch.save(model.state_dict(), 'best_model.pth')\n    \n    if (epoch + 1) % 3 == 0:\n        print(f\"Epoch {epoch+1}/{cfg.EPOCHS} - Train Loss: {train_loss:.4f}, Val Loss: {val_loss:.4f}\")\n\ntraining_time = (time.time() - start_time) / 60\nprint(f\"\\nTraining completed in {training_time:.2f} minutes\")\nprint(f\"Best validation loss: {best_val_loss:.4f}\")"},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":"# Load best model for inference\nmodel.load_state_dict(torch.load('best_model.pth'))\nmodel.eval()\n\n# Create test dataset\ntest_dataset = BiomassDataset(test_df, transform=val_transform, is_test=True,\n                               species_encoder=train_dataset.species_encoder,\n                               state_encoder=train_dataset.state_encoder,\n                               scaler=train_dataset.scaler)\ntest_loader = DataLoader(test_dataset, batch_size=1, shuffle=False)\n\n# Make predictions\npredictions = []\nimage_paths = []\n\nwith torch.no_grad():\n    for images, metadata, paths in test_loader:\n        images = images.to(cfg.DEVICE)\n        metadata = metadata.to(cfg.DEVICE)\n        outputs = model(images, metadata)\n        predictions.append(outputs.cpu().numpy()[0])\n        image_paths.append(paths[0])\n\nprint(f\"Made predictions for {len(predictions)} images\")"},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":"# Create submission file\nsubmission_rows = []\n\nfor img_path, preds in zip(image_paths, predictions):\n    img_id = img_path.split('/')[-1].replace('.jpg', '')\n    for target_name, pred_value in zip(cfg.TARGETS, preds):\n        sample_id = f\"{img_id}__{target_name}\"\n        submission_rows.append({'sample_id': sample_id, 'target': max(0, float(pred_value))})  # Ensure non-negative\n\nsubmission_df = pd.DataFrame(submission_rows)\nsubmission_df.to_csv('submission.csv', index=False)\n\nprint(\"Submission file created!\")\nprint(f\"\\nSample of predictions:\")\nprint(submission_df.head(10))\nprint(f\"\\nPrediction statistics:\")\nprint(submission_df['target'].describe())"}],"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.0"}},"nbformat":4,"nbformat_minor":4}