{"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":"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},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"**SECTION 1 – SETUP**","metadata":{}},{"cell_type":"code","source":"# ====================================================\n# 1. SETUP & LIBRARIES\n# ====================================================\nimport os\nimport numpy as np\nimport pandas as pd\nfrom PIL import Image\nfrom tqdm import tqdm\n\nimport torch\nfrom torchvision import models, transforms\n\nfrom sklearn.model_selection import train_test_split\nfrom sklearn.metrics import r2_score\nfrom xgboost import XGBRegressor\nfrom sklearn.multioutput import MultiOutputRegressor\n\ndevice = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\nprint(\"Using device:\", device)\n","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":" **SECTION 2 – LOAD & CLEAN DATA**","metadata":{}},{"cell_type":"code","source":"# ====================================================\n# 2. LOAD & PREPARE DATA\n# ====================================================\ntrain = pd.read_csv(\"/kaggle/input/csiro-biomass/train.csv\")\nprint(\"Original rows:\", len(train))\ndisplay(train.head())\n\n# Pivot long → wide (one row per image)\ntrain_wide = train.pivot_table(\n    index=[\"image_path\"],\n    columns=\"target_name\",\n    values=\"target\"\n).reset_index()\ntrain_wide.columns.name = None\n\ntarget_cols = ['Dry_Green_g','Dry_Dead_g','Dry_Clover_g','GDM_g','Dry_Total_g']\n\n# Convert to floats safely\nfor col in target_cols:\n    train_wide[col] = pd.to_numeric(train_wide[col], errors=\"coerce\").fillna(0).astype(\"float32\")\n\nprint(\"Unique images:\", len(train_wide))\ndisplay(train_wide.head())\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-04T12:04:08.700072Z","iopub.execute_input":"2025-11-04T12:04:08.700719Z","iopub.status.idle":"2025-11-04T12:04:08.770160Z","shell.execute_reply.started":"2025-11-04T12:04:08.700697Z","shell.execute_reply":"2025-11-04T12:04:08.769306Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"**SECTION 3 – IMAGE TRANSFORMS & FROZEN MODEL**","metadata":{}},{"cell_type":"code","source":"# ====================================================\n# 3. DEFINE IMAGE TRANSFORMS & FROZEN RESNET\n# ====================================================\nval_tfms = transforms.Compose([\n    transforms.Resize((224,224)),\n    transforms.ToTensor(),\n    transforms.Normalize([0.485,0.456,0.406],[0.229,0.224,0.225])\n])\n\n# Pretrained ResNet50 up to penultimate layer (no fc)\n# =============================================================\n# 3. DEFINE IMAGE TRANSFORMS & FROZEN RESNET (OFFLINE SAFE)\n# =============================================================\nval_tfms = transforms.Compose([\n    transforms.Resize((224,224)),\n    transforms.ToTensor(),\n    transforms.Normalize([0.485,0.456,0.406],[0.229,0.224,0.225])\n])\n\nfrom torchvision import models\nimport torch, os\n\n# Ensure torch uses cache instead of internet\ntorch.hub.set_dir(\"/root/.cache/torch/hub\")\nos.environ[\"TORCH_HOME\"] = \"/root/.cache/torch\"\n\ntry:\n    resnet = models.resnet50(weights='IMAGENET1K_V1')\n    print(\"✅ Loaded pretrained ResNet50 weights (from cache)\")\nexcept Exception as e:\n    print(\"⚠️ Could not download weights. Using untrained ResNet50 instead.\")\n    resnet = models.resnet50(weights=None)\n\n# Remove the classification head (keep feature extractor)\nresnet = torch.nn.Sequential(*list(resnet.children())[:-1])\nresnet.eval().to(device)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-04T12:05:01.364557Z","iopub.execute_input":"2025-11-04T12:05:01.364843Z","iopub.status.idle":"2025-11-04T12:05:01.897744Z","shell.execute_reply.started":"2025-11-04T12:05:01.364823Z","shell.execute_reply":"2025-11-04T12:05:01.897032Z"},"_kg_hide-output":true,"scrolled":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"**SECTION 4 – EXTRACT IMAGE EMBEDDINGS**","metadata":{}},{"cell_type":"code","source":"# ====================================================\n# 4. EXTRACT IMAGE EMBEDDINGS\n# ====================================================\ndef get_embedding(img_path):\n    full_path = os.path.join(\"/kaggle/input/csiro-biomass\", img_path)\n    img = Image.open(full_path).convert(\"RGB\")\n    img = val_tfms(img).unsqueeze(0).to(device)\n    with torch.no_grad():\n        emb = resnet(img).squeeze().cpu().numpy()  # 2048-d vector\n    return emb\n\nembeddings = []\nfor path in tqdm(train_wide[\"image_path\"], desc=\"Extracting train embeddings\"):\n    embeddings.append(get_embedding(path))\n\nX_emb = np.stack(embeddings)\ny = train_wide[target_cols].values\nprint(\"Embeddings shape:\", X_emb.shape, \" | Targets shape:\", y.shape)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-04T12:06:07.238715Z","iopub.execute_input":"2025-11-04T12:06:07.239310Z","iopub.status.idle":"2025-11-04T12:06:39.630560Z","shell.execute_reply.started":"2025-11-04T12:06:07.239285Z","shell.execute_reply":"2025-11-04T12:06:39.629666Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"**SECTION 5 – TRAIN REGRESSOR ON EMBEDDINGS**","metadata":{}},{"cell_type":"code","source":"# ====================================================\n# 5. TRAIN REGRESSOR ON EMBEDDINGS\n# ====================================================\nX_train, X_val, y_train, y_val = train_test_split(X_emb, y, test_size=0.15, random_state=42)\n\nreg = MultiOutputRegressor(\n    XGBRegressor(\n        n_estimators=800,\n        learning_rate=0.05,\n        max_depth=5,\n        subsample=0.9,\n        colsample_bytree=0.9,\n        random_state=42,\n        n_jobs=-1\n    )\n)\n\nprint(\"Training XGBoost on frozen embeddings...\")\nreg.fit(X_train, y_train)\n\ny_pred = reg.predict(X_val)\nr2s = r2_score(y_val, y_pred, multioutput='raw_values')\nweights = np.array([0.1,0.1,0.1,0.2,0.5])\nweighted_r2 = np.sum(weights * r2s)\n\nfor t,s in zip(target_cols, r2s):\n    print(f\"{t:15s}: R² = {s:.3f}\")\nprint(f\"Weighted R² = {weighted_r2:.3f}\")\n","metadata":{"trusted":true,"_kg_hide-output":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"**SECTION 6 – PREDICT TEST & BUILD SUBMISSION**","metadata":{}},{"cell_type":"code","source":"# ====================================================\n# 6. PREDICT TEST & SAVE SUBMISSION\n# ====================================================\ntest = pd.read_csv(\"/kaggle/input/csiro-biomass/test.csv\")\nunique_imgs = test[\"image_path\"].unique()\n\n# Extract embeddings for test set\ntest_emb = []\nfor path in tqdm(unique_imgs, desc=\"Extracting test embeddings\"):\n    test_emb.append(get_embedding(path))\nX_test_emb = np.stack(test_emb)\n\n# Predict\ny_test_pred = reg.predict(X_test_emb)\n\n# Convert to submission format\nrows = []\nfor img, preds in zip(unique_imgs, y_test_pred):\n    img_id = img.split(\"/\")[-1].replace(\".jpg\",\"\")\n    for t,val in zip(target_cols, preds):\n        rows.append({\"sample_id\": f\"{img_id}__{t}\", \"target\": float(val)})\n\nsub = pd.DataFrame(rows)\nsub.to_csv(\"/kaggle/working/submission.csv\", index=False)\nprint(\"✅ submission.csv saved!\")\nsub.head()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-04T12:13:58.247949Z","iopub.execute_input":"2025-11-04T12:13:58.248249Z","iopub.status.idle":"2025-11-04T12:13:58.519436Z","shell.execute_reply.started":"2025-11-04T12:13:58.248230Z","shell.execute_reply":"2025-11-04T12:13:58.518910Z"}},"outputs":[],"execution_count":null}]}