{"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":"gpu","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":"code","source":"# This Python 3 environment comes with many helpful analytics libraries installed\n# It is defined by the kaggle/python Docker image: https://github.com/kaggle/docker-python\n# For example, here's several helpful packages to load\n\nimport numpy as np # linear algebra\nimport pandas as pd # data processing, CSV file I/O (e.g. pd.read_csv)\n\n# Input data files are available in the read-only \"../input/\" directory\n# For example, running this (by clicking run or pressing Shift+Enter) will list all files under the input directory\n\nimport os\nfor dirname, _, filenames in os.walk('/kaggle/input'):\n    for filename in filenames:\n        print(os.path.join(dirname, filename))\n\n# You can write up to 20GB to the current directory (/kaggle/working/) that gets preserved as output when you create a version using \"Save & Run All\" \n# You can also write temporary files to /kaggle/temp/, but they won't be saved outside of the current session","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2025-11-02T05:04:38.452312Z","iopub.execute_input":"2025-11-02T05:04:38.452706Z","iopub.status.idle":"2025-11-02T05:04:38.469299Z","shell.execute_reply.started":"2025-11-02T05:04:38.452663Z","shell.execute_reply":"2025-11-02T05:04:38.468120Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# 🌾 CSIRO - Image2Biomass Prediction | Rishi Bro Edition ⚡\n# Kaggle-safe version (no internet access required)\n# ============================================================\n\nimport os\nimport torch\nimport torch.nn as nn\nimport pandas as pd\nimport numpy as np\nfrom PIL import Image\nfrom torch.utils.data import Dataset, DataLoader\nfrom torchvision import transforms, models\nfrom sklearn.preprocessing import LabelEncoder, StandardScaler\nfrom sklearn.model_selection import train_test_split\n\n# ============================================================\n# ⚙️ CONFIG\n# ============================================================\nTRAIN_CSV = \"/kaggle/input/csiro-biomass/train.csv\"\nTEST_CSV = \"/kaggle/input/csiro-biomass/test.csv\"\nSUBMISSION_PATH = \"/kaggle/working/submission.csv\"\n\nDEVICE = \"cuda\" if torch.cuda.is_available() else \"cpu\"\nEPOCHS = 5          # increase later if GPU is fast\nBATCH_SIZE = 16\nLR = 1e-4\n\nprint(f\"🚀 Device in use: {DEVICE.upper()}\")\n\n# ============================================================\n# 📂 LOAD DATA\n# ============================================================\ntrain_df = pd.read_csv(TRAIN_CSV)\ntest_df = pd.read_csv(TEST_CSV)\n\nprint(\"✅ Train shape:\", train_df.shape)\nprint(\"✅ Test shape:\", test_df.shape)\nprint(\"📄 Train columns:\", list(train_df.columns))\n\n# ============================================================\n# 🧩 PREPROCESS\n# ============================================================\n# Encode categorical columns\nfor col in [\"State\", \"Species\"]:\n    if col in train_df.columns:\n        le = LabelEncoder()\n        train_df[col] = le.fit_transform(train_df[col].astype(str))\n        if col in test_df.columns:\n            test_df[col] = le.transform(test_df[col].astype(str))\n        else:\n            test_df[col] = 0\n\n# Feature & Target columns\nfeature_cols = [c for c in [\"State\", \"Species\", \"Pre_GSHH_NDVI\", \"Height_Ave_cm\"] if c in train_df.columns]\ntarget_col = \"target\"\n\n# Add missing columns to test\nfor col in feature_cols:\n    if col not in test_df.columns:\n        test_df[col] = 0\n\n# Fill NaNs\ntrain_df[feature_cols] = train_df[feature_cols].fillna(0)\ntest_df[feature_cols] = test_df[feature_cols].fillna(0)\n\n# Scale features\nscaler = StandardScaler()\ntrain_df[feature_cols] = scaler.fit_transform(train_df[feature_cols])\ntest_df[feature_cols] = scaler.transform(test_df[feature_cols])\n\n# ============================================================\n# 🧠 DATASET CLASS\n# ============================================================\nclass PastureDataset(Dataset):\n    def __init__(self, df, feature_cols, target_col=None, is_test=False):\n        self.df = df\n        self.feature_cols = feature_cols\n        self.target_col = target_col\n        self.is_test = is_test\n        self.transform = transforms.Compose([\n            transforms.Resize((128, 128)),   # Smaller = faster\n            transforms.ToTensor(),\n            transforms.Normalize([0.485, 0.456, 0.406],\n                                 [0.229, 0.224, 0.225])\n        ])\n\n    def __len__(self):\n        return len(self.df)\n\n    def __getitem__(self, idx):\n        row = self.df.iloc[idx]\n        img_path = row[\"image_path\"]\n\n        # Build path correctly\n        if not os.path.isabs(img_path):\n            img_path = os.path.join(\"/kaggle/input/csiro-biomass\", img_path)\n        if not os.path.exists(img_path):\n            raise FileNotFoundError(f\"❌ Image not found: {img_path}\")\n\n        image = Image.open(img_path).convert(\"RGB\")\n        image = self.transform(image)\n\n        feats = row[self.feature_cols].astype(float).values\n        tabular = torch.tensor(feats, dtype=torch.float32)\n\n        if self.is_test:\n            return image, tabular, row[\"sample_id\"]\n        else:\n            target = torch.tensor(row[self.target_col], dtype=torch.float32)\n            return image, tabular, target\n\n# ============================================================\n# 🧩 MODEL (Offline Safe)\n# ============================================================\nclass BiomassPredictor(nn.Module):\n    def __init__(self, tabular_input_dim):\n        super().__init__()\n        # ✅ No internet download — offline mode\n        self.cnn = models.resnet18(weights=None)\n        self.cnn.fc = nn.Identity()\n\n        self.tabular = nn.Sequential(\n            nn.Linear(tabular_input_dim, 64),\n            nn.ReLU(),\n            nn.Linear(64, 32),\n            nn.ReLU()\n        )\n\n        self.head = nn.Sequential(\n            nn.Linear(512 + 32, 64),\n            nn.ReLU(),\n            nn.Linear(64, 1)\n        )\n\n    def forward(self, image, tabular):\n        img_feat = self.cnn(image)\n        tab_feat = self.tabular(tabular)\n        fused = torch.cat([img_feat, tab_feat], dim=1)\n        return self.head(fused)\n\n# ============================================================\n# 🏋️ TRAINING FUNCTION\n# ============================================================\ndef train_model(train_df, feature_cols, target_col):\n    train_df, val_df = train_test_split(train_df, test_size=0.1, random_state=42)\n    train_ds = PastureDataset(train_df, feature_cols, target_col)\n    val_ds = PastureDataset(val_df, feature_cols, target_col)\n    train_loader = DataLoader(train_ds, batch_size=BATCH_SIZE, shuffle=True)\n    val_loader = DataLoader(val_ds, batch_size=BATCH_SIZE, shuffle=False)\n\n    model = BiomassPredictor(tabular_input_dim=len(feature_cols)).to(DEVICE)\n    optimizer = torch.optim.Adam(model.parameters(), lr=LR)\n    loss_fn = nn.MSELoss()\n\n    print(f\"🚀 Training on {DEVICE.upper()} for {EPOCHS} epochs...\")\n\n    for epoch in range(EPOCHS):\n        model.train()\n        total_loss = 0\n        for image, tabular, target in train_loader:\n            image, tabular, target = image.to(DEVICE), tabular.to(DEVICE), target.to(DEVICE).unsqueeze(1)\n            optimizer.zero_grad()\n            output = model(image, tabular)\n            loss = loss_fn(output, target)\n            loss.backward()\n            optimizer.step()\n            total_loss += loss.item()\n\n        # Validation\n        val_loss = 0\n        model.eval()\n        with torch.no_grad():\n            for image, tabular, target in val_loader:\n                image, tabular, target = image.to(DEVICE), tabular.to(DEVICE), target.to(DEVICE).unsqueeze(1)\n                val_loss += loss_fn(model(image, tabular), target).item()\n\n        print(f\"📆 Epoch [{epoch+1}/{EPOCHS}] | Train Loss: {total_loss/len(train_loader):.4f} | Val Loss: {val_loss/len(val_loader):.4f}\")\n\n    torch.save(model.state_dict(), \"biomass_model.pt\")\n    print(\"✅ Model saved as biomass_model.pt\")\n    return model\n\n# ============================================================\n# 🔮 PREDICTION\n# ============================================================\ndef predict(model, test_df, feature_cols):\n    test_ds = PastureDataset(test_df, feature_cols, is_test=True)\n    loader = DataLoader(test_ds, batch_size=1, shuffle=False)\n    preds = {}\n    model.eval()\n    with torch.no_grad():\n        for image, tabular, sample_id in loader:\n            image, tabular = image.to(DEVICE), tabular.to(DEVICE)\n            pred = model(image, tabular).cpu().numpy().squeeze()\n            preds[sample_id[0]] = float(pred)\n    return preds\n\n# ============================================================\n# 🏁 RUN EVERYTHING\n# ============================================================\nmodel = train_model(train_df, feature_cols, target_col)\npreds = predict(model, test_df, feature_cols)\n\nsubmission = pd.DataFrame(list(preds.items()), columns=[\"sample_id\", \"target\"])\nsubmission.to_csv(SUBMISSION_PATH, index=False)\n\nprint(f\"🏁 Submission file saved to: {SUBMISSION_PATH}\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-02T05:04:38.656602Z","iopub.execute_input":"2025-11-02T05:04:38.657971Z"}},"outputs":[],"execution_count":null}]}