{"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":"code","source":"import os\nimport pandas as pd\nimport numpy as np\nimport torch\nimport torch.nn as nn\nimport torch.optim as optim\nfrom torch.utils.data import Dataset, DataLoader\nfrom sklearn.model_selection import train_test_split\nfrom sklearn.preprocessing import StandardScaler\nfrom PIL import Image\nfrom torchvision import transforms\nfrom tqdm.auto import tqdm\nfrom transformers import AutoImageProcessor, AutoModel\nimport matplotlib.pyplot as plt\n\n# Configuration\nBASE_PATH = \"/kaggle/input/csiro-biomass\"\nOFFLINE_PATH = \"./dinov2-offline-tools\"\nDEVICE = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\nTARGET_COLUMNS = ['Dry_Green_g', 'Dry_Dead_g', 'Dry_Clover_g', 'GDM_g', 'Dry_Total_g']\n\n# Pre-download and save DINOv2 locally (Run with Internet ON)\nif not os.path.exists(OFFLINE_PATH):\n    print(\"Downloading DINOv2 for offline use...\")\n    tmp_proc = AutoImageProcessor.from_pretrained('facebook/dinov2-small')\n    tmp_model = AutoModel.from_pretrained('facebook/dinov2-small')\n    tmp_proc.save_pretrained(OFFLINE_PATH)\n    tmp_model.save_pretrained(OFFLINE_PATH)\n    print(\"Model saved locally!\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-30T14:20:20.587022Z","iopub.execute_input":"2025-12-30T14:20:20.587292Z","iopub.status.idle":"2025-12-30T14:20:49.048575Z","shell.execute_reply.started":"2025-12-30T14:20:20.587262Z","shell.execute_reply":"2025-12-30T14:20:49.047873Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# 1. Load and Pivot Data\ndf = 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\n# 2. Split into Train/Val\ntrain_df, val_df = train_test_split(df, test_size=0.2, random_state=42)\n\n# 3. Scaling Targets\ntarget_scaler = StandardScaler()\ny_train_scaled = target_scaler.fit_transform(train_df[TARGET_COLUMNS].values)\ny_val_scaled = target_scaler.transform(val_df[TARGET_COLUMNS].values)\n\n# Convert to Tensors\ny_train_tensor = torch.tensor(y_train_scaled, dtype=torch.float32)\ny_val_tensor = torch.tensor(y_val_scaled, dtype=torch.float32)\n\nprint(f\"Data ready. Training samples: {len(train_df)}, Validation samples: {len(val_df)}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-30T14:20:49.050257Z","iopub.execute_input":"2025-12-30T14:20:49.050809Z","iopub.status.idle":"2025-12-30T14:20:49.104237Z","shell.execute_reply.started":"2025-12-30T14:20:49.050783Z","shell.execute_reply":"2025-12-30T14:20:49.103717Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Load local model\nprocessor = AutoImageProcessor.from_pretrained(OFFLINE_PATH)\nbackbone = AutoModel.from_pretrained(OFFLINE_PATH).to(DEVICE)\nbackbone.eval()\n\ndef extract_features(image_paths):\n    feats = []\n    transform = transforms.Compose([\n        transforms.Resize((224, 224)),\n        transforms.ToTensor(),\n        transforms.Normalize(mean=processor.image_mean, std=processor.image_std)\n    ])\n    \n    with torch.no_grad():\n        for path in tqdm(image_paths, desc=\"Extracting Features\"):\n            full_path = os.path.join(BASE_PATH, path)\n            img = Image.open(full_path).convert(\"RGB\")\n            img_t = transform(img).unsqueeze(0).to(DEVICE)\n            output = backbone(img_t)\n            # Use CLS token\n            feats.append(output.last_hidden_state[:, 0, :].cpu().numpy())\n            \n    return torch.tensor(np.vstack(feats), dtype=torch.float32)\n\n# Run Extraction\nprint(\"Extracting training features...\")\nX_train = extract_features(train_df['image_path'])\nprint(\"Extracting validation features...\")\nX_val = extract_features(val_df['image_path'])","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-30T14:20:49.104948Z","iopub.execute_input":"2025-12-30T14:20:49.105197Z","iopub.status.idle":"2025-12-30T14:21:15.669677Z","shell.execute_reply.started":"2025-12-30T14:20:49.105168Z","shell.execute_reply":"2025-12-30T14:21:15.668937Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class BiomassMLP(nn.Module):\n    def __init__(self, input_dim=384, output_dim=5):\n        super().__init__()\n        self.net = nn.Sequential(\n            nn.Linear(input_dim, 512),\n            nn.BatchNorm1d(512),\n            nn.ReLU(),\n            nn.Dropout(0.4),\n            nn.Linear(512, 128),\n            nn.ReLU(),\n            nn.Dropout(0.2),\n            nn.Linear(128, output_dim)\n        )\n    def forward(self, x): return self.net(x)\n\ndef weighted_mse_loss(preds, targets, weights):\n    loss = (preds - targets) ** 2\n    weighted_loss = loss * weights.expand_as(loss)\n    return weighted_loss.mean()\n\ndef weighted_r2_score(y_true, y_pred, weights):\n    ss_res = ((y_true - y_pred) ** 2).sum(0)\n    ss_tot = ((y_true - y_true.mean(0)) ** 2).sum(0)\n    return (1 - (weights * ss_res).sum() / ((weights * ss_tot).sum() + 1e-8)).item()\n\n# Weights from competition rules\nTARGET_WEIGHTS = torch.tensor([0.1, 0.1, 0.1, 0.2, 0.5], device=DEVICE)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-30T14:21:15.670594Z","iopub.execute_input":"2025-12-30T14:21:15.670803Z","iopub.status.idle":"2025-12-30T14:21:15.677492Z","shell.execute_reply.started":"2025-12-30T14:21:15.670782Z","shell.execute_reply":"2025-12-30T14:21:15.676919Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"model = BiomassMLP().to(DEVICE)\noptimizer = optim.Adam(model.parameters(), lr=1e-4)\n\nhistory = {'train_loss': [], 'val_loss': [], 'val_r2': []}\nbest_r2 = -np.inf\n\nfor epoch in range(200):\n    model.train()\n    optimizer.zero_grad()\n    \n    preds = model(X_train.to(DEVICE))\n    loss = weighted_mse_loss(preds, y_train_tensor.to(DEVICE), TARGET_WEIGHTS)\n    \n    loss.backward()\n    optimizer.step()\n    \n    model.eval()\n    with torch.no_grad():\n        v_preds = model(X_val.to(DEVICE))\n        v_loss = weighted_mse_loss(v_preds, y_val_tensor.to(DEVICE), TARGET_WEIGHTS)\n        v_r2 = weighted_r2_score(y_val_tensor.to(DEVICE), v_preds, TARGET_WEIGHTS)\n        \n        history['train_loss'].append(loss.item())\n        history['val_loss'].append(v_loss.item())\n        history['val_r2'].append(v_r2)\n        \n        if v_r2 > best_r2:\n            best_r2 = v_r2\n            torch.save(model.state_dict(), \"best_biomass_model.pth\")\n\n    if (epoch+1) % 20 == 0:\n        print(f\"Epoch {epoch+1} | Val R2: {v_r2:.4f}\")\n\nprint(f\"Training finished. Best Val R2: {best_r2:.4f}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-30T14:21:15.678344Z","iopub.execute_input":"2025-12-30T14:21:15.678662Z","iopub.status.idle":"2025-12-30T14:21:16.952517Z","shell.execute_reply.started":"2025-12-30T14:21:15.678630Z","shell.execute_reply":"2025-12-30T14:21:16.951780Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"plt.figure(figsize=(12, 5))\nplt.subplot(1, 2, 1)\nplt.plot(history['train_loss'], label='Train Weighted MSE')\nplt.plot(history['val_loss'], label='Val Weighted MSE')\nplt.title('Loss History')\nplt.legend()\n\nplt.subplot(1, 2, 2)\nplt.plot(history['val_r2'], color='orange', label='Val Weighted R2')\nplt.title('R2 History')\nplt.legend()\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-30T14:21:16.953501Z","iopub.execute_input":"2025-12-30T14:21:16.953892Z","iopub.status.idle":"2025-12-30T14:21:17.217419Z","shell.execute_reply.started":"2025-12-30T14:21:16.953857Z","shell.execute_reply":"2025-12-30T14:21:17.216666Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# 1. Prep Test Data\ntest_meta = pd.read_csv(os.path.join(BASE_PATH, \"test.csv\"))\n\n# Get unique image paths for efficient feature extraction\nunique_test_images = test_meta['image_path'].unique()\n\n# 2. Extract and Predict\nX_test = extract_features(unique_test_images)\n\n# Load the best weights from the training session\nmodel.load_state_dict(torch.load(\"best_biomass_model.pth\"))\nmodel.eval()\n\nwith torch.no_grad():\n    # Model outputs scaled values\n    test_preds_scaled = model(X_test.to(DEVICE)).cpu().numpy()\n    # Convert back to actual grams (g) using the scaler from Block 2\n    test_preds = target_scaler.inverse_transform(test_preds_scaled)\n\n# 3. Format Mapping into Long Format\n# We map the predicted vector of 5 values back to their specific names\npred_dict = {img: p for img, p in zip(unique_test_images, test_preds)}\nsubmission_rows = []\n\n# The competition requires exactly 5 rows per image\nfor img_path in unique_test_images:\n    # Extract the base Image ID from the path (e.g., 'ID1001187975')\n    # This assumes the image_path in your CSV looks like 'images/ID1001187975.jpg'\n    img_id = os.path.basename(img_path).split('.')[0]\n    \n    # Iterate through the 5 specific targets in the required order\n    for idx, target_name in enumerate(TARGET_COLUMNS):\n        # Create the specific sample_id required: ImageID__TargetName\n        sample_id = f\"{img_id}__{target_name}\"\n        \n        # Retrieve the specific prediction for this target\n        predicted_value = pred_dict[img_path][idx]\n        \n        # Ensure target is a float and not negative\n        val = max(0.0, float(predicted_value))\n        \n        submission_rows.append({\n            'sample_id': sample_id,\n            'target': val\n        })\n\n# 4. Save to CSV\nsubmission_df = pd.DataFrame(submission_rows)\n\n# Final check: Ensure we have only the two required columns\nsubmission_df = submission_df[['sample_id', 'target']]\n\nsubmission_df.to_csv(\"submission.csv\", index=False)\n\nprint(f\"Done! Created submission.csv with {len(submission_df)} rows.\")\n# Displaying a sample to verify formatting\nprint(submission_df.head(10))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-30T14:21:17.219234Z","iopub.execute_input":"2025-12-30T14:21:17.219550Z","iopub.status.idle":"2025-12-30T14:21:17.341551Z","shell.execute_reply.started":"2025-12-30T14:21:17.219526Z","shell.execute_reply":"2025-12-30T14:21:17.340813Z"}},"outputs":[],"execution_count":null}]}