{"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":"none","dataSources":[{"sourceId":112509,"databundleVersionId":14254895,"sourceType":"competition"}],"dockerImageVersionId":31259,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"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":"2026-01-18T15:21:40.208286Z","iopub.execute_input":"2026-01-18T15:21:40.208532Z","iopub.status.idle":"2026-01-18T15:21:40.400886Z","shell.execute_reply.started":"2026-01-18T15:21:40.208513Z","shell.execute_reply":"2026-01-18T15:21:40.399957Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\nimport numpy as np\nimport pandas as pd\nfrom PIL import Image\nimport joblib\nimport warnings\nwarnings.filterwarnings('ignore')\n\nimport torch\nfrom torch import nn\nfrom torch.utils.data import Dataset, DataLoader\nimport timm\nfrom torchvision import transforms\n\nfrom sklearn.ensemble import RandomForestRegressor\nfrom sklearn.svm import SVR\nfrom sklearn.multioutput import MultiOutputRegressor\nfrom sklearn.preprocessing import LabelEncoder, StandardScaler\nfrom sklearn.model_selection import GroupKFold\nfrom sklearn.metrics import mean_squared_error, mean_absolute_error, r2_score\n\nimport cv2\nimport matplotlib.pyplot as plt\nimport seaborn as sns\n\nDEVICE = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\nprint(f\"Using device: {DEVICE}\")\nprint(\"=\"*70)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-18T15:21:40.402079Z","iopub.execute_input":"2026-01-18T15:21:40.402288Z","iopub.status.idle":"2026-01-18T15:21:40.407920Z","shell.execute_reply.started":"2026-01-18T15:21:40.402270Z","shell.execute_reply":"2026-01-18T15:21:40.407242Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"Imports all libraries needed.\n\nChecks for GPU availability for faster training.","metadata":{}},{"cell_type":"code","source":"# ===== CELL 2: Load and Prepare Data =====\nprint(\"\\n📊 LOADING DATA...\")\nDATA_DIR = \"/kaggle/input/csiro-biomass\"\n\ntrain_df = pd.read_csv(f\"{DATA_DIR}/train.csv\")\ntest_df = pd.read_csv(f\"{DATA_DIR}/test.csv\")\n\nprint(f\"Train samples: {len(train_df)}\")\nprint(f\"Test samples: {len(test_df)}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-18T15:21:40.408761Z","iopub.execute_input":"2026-01-18T15:21:40.408978Z","iopub.status.idle":"2026-01-18T15:21:40.433027Z","shell.execute_reply.started":"2026-01-18T15:21:40.408959Z","shell.execute_reply":"2026-01-18T15:21:40.431673Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"Loads train/test CSVs.\n\nsample_submission.csv shows required submission format.","metadata":{}},{"cell_type":"code","source":"# ===== CELL 3: Encode Metadata =====\nprint(\"\\n🔧 ENCODING METADATA...\")\nle_state = LabelEncoder()\nle_species = LabelEncoder()\n\n# Train metadata\ntrain_df[\"State_enc\"] = le_state.fit_transform(train_df[\"State\"])\ntrain_df[\"Species_enc\"] = le_species.fit_transform(train_df[\"Species\"])\ntrain_df[\"Sampling_DOY\"] = pd.to_datetime(train_df[\"Sampling_Date\"]).dt.dayofyear\n\n# Test metadata with defaults\ntest_df[\"State_enc\"] = 0\ntest_df[\"Species_enc\"] = 0\ntest_df[\"Sampling_DOY\"] = 180\n\n# Fill missing metadata in test\nfor col in [\"Pre_GSHH_NDVI\", \"Height_Ave_cm\"]:\n    test_df[col] = train_df[col].mean()\n\nMETA_COLS = [\"Pre_GSHH_NDVI\", \"Height_Ave_cm\", \"State_enc\", \"Species_enc\", \"Sampling_DOY\"]\nTARGETS = [\"Dry_Green_g\", \"Dry_Dead_g\", \"Dry_Clover_g\", \"GDM_g\", \"Dry_Total_g\"]\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-18T15:21:40.435230Z","iopub.execute_input":"2026-01-18T15:21:40.435514Z","iopub.status.idle":"2026-01-18T15:21:40.446915Z","shell.execute_reply.started":"2026-01-18T15:21:40.435490Z","shell.execute_reply":"2026-01-18T15:21:40.446231Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ===== CELL 4: Convert to Image-Level Data =====\nprint(\"\\n🔄 CONVERTING TO IMAGE-LEVEL FORMAT...\")\n\n# Pivot so each image has all 5 targets\ntrain_wide = train_df.pivot_table(\n    index=[\"image_path\"],\n    columns=\"target_name\",\n    values=\"target\"\n).reset_index()\n\ntrain_wide = train_wide.merge(\n    train_df.drop_duplicates(\"image_path\")[[\"image_path\"] + META_COLS],\n    on=\"image_path\",\n    how=\"left\"\n)\n\n# Clean data\nfor col in META_COLS:\n    train_wide[col] = pd.to_numeric(train_wide[col], errors='coerce')\n    train_wide[col] = train_wide[col].fillna(train_wide[col].mean())\n\nfor col in TARGETS:\n    train_wide[col] = pd.to_numeric(train_wide[col], errors='coerce')\n    train_wide[col] = train_wide[col].fillna(0)\n\nprint(f\"Unique images: {len(train_wide)}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-18T15:21:40.447855Z","iopub.execute_input":"2026-01-18T15:21:40.448077Z","iopub.status.idle":"2026-01-18T15:21:40.476414Z","shell.execute_reply.started":"2026-01-18T15:21:40.448054Z","shell.execute_reply":"2026-01-18T15:21:40.475764Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ===== CELL 5: Train/Validation Split =====\nprint(\"\\n✂️ SPLITTING DATA...\")\n\ngkf = GroupKFold(n_splits=5)\ngroups = train_wide[\"image_path\"]\ntrain_idx, val_idx = next(gkf.split(train_wide, groups=groups))\n\ntrain_data = train_wide.iloc[train_idx].copy()\nval_data = train_wide.iloc[val_idx].copy()\n\nprint(f\"Train samples: {len(train_data)}\")\nprint(f\"Validation samples: {len(val_data)}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-18T15:21:40.477222Z","iopub.execute_input":"2026-01-18T15:21:40.478015Z","iopub.status.idle":"2026-01-18T15:21:40.485977Z","shell.execute_reply.started":"2026-01-18T15:21:40.477920Z","shell.execute_reply":"2026-01-18T15:21:40.485264Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ===== CELL 6: Feature Extraction Function for ML Models =====\nprint(\"\\n🎨 SETTING UP FEATURE EXTRACTION...\")\n\ndef extract_image_features(image_path, img_dir):\n    \"\"\"Extract hand-crafted features from image for Random Forest and SVR\"\"\"\n    try:\n        img_full_path = os.path.join(img_dir, image_path.split(\"/\")[-1])\n        img = cv2.imread(img_full_path)\n        if img is None:\n            return [0] * 13  # Return zeros if image can't be loaded\n        \n        img = cv2.resize(img, (224, 224))\n        features = []\n        \n        # Color statistics (mean, std for R,G,B)\n        for channel in range(3):\n            features.append(float(img[:, :, channel].mean()))\n            features.append(float(img[:, :, channel].std()))\n        \n        # Texture features using gradients\n        gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)\n        grad_x = cv2.Sobel(gray, cv2.CV_64F, 1, 0, ksize=3)\n        grad_y = cv2.Sobel(gray, cv2.CV_64F, 0, 1, ksize=3)\n        features.append(float(np.abs(grad_x).mean()))\n        features.append(float(np.abs(grad_y).mean()))\n        \n        # HSV color space statistics\n        hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)\n        features.append(float(hsv[:, :, 0].mean()))  # Hue\n        features.append(float(hsv[:, :, 1].mean()))  # Saturation\n        features.append(float(hsv[:, :, 2].mean()))  # Value\n        \n        # Green channel dominance (vegetation indicator)\n        features.append(float((img[:, :, 1] > img[:, :, 0]).sum()))\n        features.append(float((img[:, :, 1] > img[:, :, 2]).sum()))\n        \n        return features\n    except Exception as e:\n        print(f\"Error processing {image_path}: {e}\")\n        return [0] * 13","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-18T15:21:40.824305Z","iopub.execute_input":"2026-01-18T15:21:40.824551Z","iopub.status.idle":"2026-01-18T15:21:40.832712Z","shell.execute_reply.started":"2026-01-18T15:21:40.824532Z","shell.execute_reply":"2026-01-18T15:21:40.832079Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"","metadata":{}},{"cell_type":"code","source":"# ===== CELL 7: Dataset Class for Neural Network =====\nclass BiomassDataset(Dataset):\n    def __init__(self, df, img_dir, transform, train=True):\n        self.df = df.reset_index(drop=True).copy()\n        self.img_dir = img_dir\n        self.transform = transform\n        self.train = train\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 = os.path.join(self.img_dir, row[\"image_path\"].split(\"/\")[-1])\n        image = Image.open(img_path).convert(\"RGB\")\n        image = self.transform(image)\n\n        meta_values = row[META_COLS].values.astype(np.float32)\n        meta = torch.tensor(meta_values, dtype=torch.float32)\n\n        if self.train:\n            target_values = row[TARGETS].values.astype(np.float32)\n            target = torch.tensor(target_values, dtype=torch.float32)\n            return image, meta, target\n\n        return image, meta, row[\"image_path\"]","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-18T15:21:40.898671Z","iopub.execute_input":"2026-01-18T15:21:40.898999Z","iopub.status.idle":"2026-01-18T15:21:40.905609Z","shell.execute_reply.started":"2026-01-18T15:21:40.898971Z","shell.execute_reply":"2026-01-18T15:21:40.904864Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"","metadata":{}},{"cell_type":"markdown","source":"Returns image + metadata + 5-target vector for training.\n\nFor test, returns image + metadata + image_path for submission.","metadata":{}},{"cell_type":"code","source":"# ===== CELL 8: Image Transformations =====\nIMG_SIZE = 224\n\ntrain_tfms = transforms.Compose([\n    transforms.Resize((IMG_SIZE, IMG_SIZE)),\n    transforms.RandomHorizontalFlip(),\n    transforms.RandomVerticalFlip(),\n    transforms.ToTensor(),\n    transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])\n])\n\ntest_tfms = transforms.Compose([\n    transforms.Resize((IMG_SIZE, IMG_SIZE)),\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":"2026-01-18T15:21:40.907270Z","iopub.execute_input":"2026-01-18T15:21:40.907546Z","iopub.status.idle":"2026-01-18T15:21:40.923038Z","shell.execute_reply.started":"2026-01-18T15:21:40.907522Z","shell.execute_reply":"2026-01-18T15:21:40.921788Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"GroupKFold ensures no image leakage.\n\nMaintains validation accuracy close to true generalization.","metadata":{}},{"cell_type":"code","source":"# ===== CELL 9: Create DataLoaders for Neural Network =====\ntrain_ds = BiomassDataset(train_data, f\"{DATA_DIR}/train\", train_tfms, train=True)\nval_ds = BiomassDataset(val_data, f\"{DATA_DIR}/train\", test_tfms, train=True)\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)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-18T15:21:40.924054Z","iopub.execute_input":"2026-01-18T15:21:40.924325Z","iopub.status.idle":"2026-01-18T15:21:40.938207Z","shell.execute_reply.started":"2026-01-18T15:21:40.924306Z","shell.execute_reply":"2026-01-18T15:21:40.937111Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"\nprint(\"\\n\" + \"=\"*70)\nprint(\"🧠 ALGORITHM 1: NEURAL NETWORK (Deep Learning)\")\nprint(\"=\"*70)\n\nclass NeuralNetModel(nn.Module):\n    def __init__(self):\n        super().__init__()\n        # Image feature extractor\n        self.backbone = timm.create_model(\"efficientnet_b0\", pretrained=False, num_classes=0)\n        \n        # Metadata processor\n        self.meta_net = nn.Sequential(\n            nn.Linear(len(META_COLS), 128),\n            nn.ReLU(),\n            nn.Dropout(0.3)\n        )\n        \n        # Fusion head\n        self.head = nn.Sequential(\n            nn.Linear(self.backbone.num_features + 128, 256),\n            nn.ReLU(),\n            nn.Dropout(0.4),\n            nn.Linear(256, 5)\n        )\n\n    def forward(self, img, meta):\n        img_feat = self.backbone(img)\n        meta_feat = self.meta_net(meta)\n        x = torch.cat([img_feat, meta_feat], dim=1)\n        return self.head(x)\n\n\nnn_model = NeuralNetModel().to(DEVICE)\nprint(f\"Model parameters: {sum(p.numel() for p in nn_model.parameters()):,}\")\n\n# Loss function and optimizer\nWEIGHTS = torch.tensor([1, 1, 1, 2, 5], device=DEVICE, dtype=torch.float32)\n\ndef weighted_mse(pred, target):\n    return ((pred - target)**2 * WEIGHTS).mean()\n\noptimizer = torch.optim.AdamW(nn_model.parameters(), lr=3e-4, weight_decay=1e-4)\n\n# Train Neural Network\nEPOCHS = 10\nprint(f\"\\n🏋️ Training Neural Network for {EPOCHS} epochs...\")\n\nbest_nn_loss = float('inf')\n\nfor epoch in range(EPOCHS):\n    # Training\n    nn_model.train()\n    train_loss = 0\n    \n    for img, meta, target in train_loader:\n        img = img.to(DEVICE).float()\n        meta = meta.to(DEVICE).float()\n        target = target.to(DEVICE).float()\n        \n        optimizer.zero_grad()\n        preds = nn_model(img, meta)\n        loss = weighted_mse(preds, target)\n        loss.backward()\n        torch.nn.utils.clip_grad_norm_(nn_model.parameters(), max_norm=1.0)\n        optimizer.step()\n        \n        train_loss += loss.item()\n    \n    avg_train_loss = train_loss / len(train_loader)\n    \n    # Validation\n    nn_model.eval()\n    val_loss = 0\n    \n    with torch.no_grad():\n        for img, meta, target in val_loader:\n            img = img.to(DEVICE).float()\n            meta = meta.to(DEVICE).float()\n            target = target.to(DEVICE).float()\n            \n            preds = nn_model(img, meta)\n            loss = weighted_mse(preds, target)\n            val_loss += loss.item()\n    \n    avg_val_loss = val_loss / len(val_loader)\n    \n    print(f\"Epoch {epoch+1}/{EPOCHS} - Train Loss: {avg_train_loss:.4f} | Val Loss: {avg_val_loss:.4f}\")\n    \n    if avg_val_loss < best_nn_loss:\n        best_nn_loss = avg_val_loss\n        torch.save(nn_model.state_dict(), 'neural_network_model.pth')\n\n# Load best model and evaluate\nnn_model.load_state_dict(torch.load('neural_network_model.pth'))\nnn_model.eval()\n\nnn_preds = []\nnn_targets = []\n\nwith torch.no_grad():\n    for img, meta, target in val_loader:\n        img, meta = img.to(DEVICE), meta.to(DEVICE)\n        preds = nn_model(img, meta)\n        nn_preds.append(preds.cpu().numpy())\n        nn_targets.append(target.cpu().numpy())\n\nnn_preds = np.vstack(nn_preds)\nnn_targets = np.vstack(nn_targets)\n\nprint(\"\\n📊 Neural Network Validation Results:\")\nprint(f\"{'Target':<15} {'R²':<10} {'MAE (g)':<10} {'RMSE (g)':<10}\")\nprint(\"-\" * 50)\n\nnn_r2_scores = []\nfor i, target_name in enumerate(TARGETS):\n    r2 = r2_score(nn_targets[:, i], nn_preds[:, i])\n    mae = mean_absolute_error(nn_targets[:, i], nn_preds[:, i])\n    rmse = np.sqrt(mean_squared_error(nn_targets[:, i], nn_preds[:, i]))\n    nn_r2_scores.append(r2)\n    print(f\"{target_name:<15} {r2:<10.3f} {mae:<10.2f} {rmse:<10.2f}\")\n\noverall_r2_nn = r2_score(nn_targets.flatten(), nn_preds.flatten())\noverall_mae_nn = mean_absolute_error(nn_targets.flatten(), nn_preds.flatten())\nprint(\"-\" * 50)\nprint(f\"{'OVERALL':<15} {overall_r2_nn:<10.3f} {overall_mae_nn:<10.2f}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-18T15:21:40.955305Z","iopub.execute_input":"2026-01-18T15:21:40.955555Z","iopub.status.idle":"2026-01-18T15:28:52.219694Z","shell.execute_reply.started":"2026-01-18T15:21:40.955534Z","shell.execute_reply":"2026-01-18T15:28:52.218895Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================================\n# ALGORITHM 2: RANDOM FOREST (Ensemble Method)\n# ============================================================================\n\nprint(\"\\n\" + \"=\"*70)\nprint(\"🌲 ALGORITHM 2: RANDOM FOREST (Ensemble Method)\")\nprint(\"=\"*70)\n\n# Extract features for train and validation\nprint(\"Extracting image features for training set...\")\ntrain_img_features = []\nfor i, img_path in enumerate(train_data[\"image_path\"]):\n    if i % 50 == 0:\n        print(f\"  Progress: {i}/{len(train_data)}\", end='\\r')\n    features = extract_image_features(img_path, f\"{DATA_DIR}/train\")\n    train_img_features.append(features)\nprint(f\"  Completed: {len(train_data)}/{len(train_data)}\")\n\nprint(\"Extracting image features for validation set...\")\nval_img_features = []\nfor i, img_path in enumerate(val_data[\"image_path\"]):\n    if i % 20 == 0:\n        print(f\"  Progress: {i}/{len(val_data)}\", end='\\r')\n    features = extract_image_features(img_path, f\"{DATA_DIR}/train\")\n    val_img_features.append(features)\nprint(f\"  Completed: {len(val_data)}/{len(val_data)}\")\n\n# Combine image features with metadata\nX_train_rf = np.hstack([\n    np.array(train_img_features),\n    train_data[META_COLS].values\n])\n\nX_val_rf = np.hstack([\n    np.array(val_img_features),\n    val_data[META_COLS].values\n])\n\ny_train_rf = train_data[TARGETS].values\ny_val_rf = val_data[TARGETS].values\n\n# Scale features\nscaler_rf = StandardScaler()\nX_train_rf = scaler_rf.fit_transform(X_train_rf)\nX_val_rf = scaler_rf.transform(X_val_rf)\n\nprint(f\"\\nFeature shape: {X_train_rf.shape}\")\n\n# Train Random Forest\nprint(\"\\n🏋️ Training Random Forest...\")\nrf_model = MultiOutputRegressor(\n    RandomForestRegressor(\n        n_estimators=100,\n        max_depth=15,\n        min_samples_split=5,\n        min_samples_leaf=2,\n        random_state=42,\n        n_jobs=-1,\n        verbose=0\n    )\n)\n\nrf_model.fit(X_train_rf, y_train_rf)\nrf_preds = rf_model.predict(X_val_rf)\n\nprint(\"\\n📊 Random Forest Validation Results:\")\nprint(f\"{'Target':<15} {'R²':<10} {'MAE (g)':<10} {'RMSE (g)':<10}\")\nprint(\"-\" * 50)\n\nrf_r2_scores = []\nfor i, target_name in enumerate(TARGETS):\n    r2 = r2_score(y_val_rf[:, i], rf_preds[:, i])\n    mae = mean_absolute_error(y_val_rf[:, i], rf_preds[:, i])\n    rmse = np.sqrt(mean_squared_error(y_val_rf[:, i], rf_preds[:, i]))\n    rf_r2_scores.append(r2)\n    print(f\"{target_name:<15} {r2:<10.3f} {mae:<10.2f} {rmse:<10.2f}\")\n\noverall_r2_rf = r2_score(y_val_rf.flatten(), rf_preds.flatten())\noverall_mae_rf = mean_absolute_error(y_val_rf.flatten(), rf_preds.flatten())\nprint(\"-\" * 50)\nprint(f\"{'OVERALL':<15} {overall_r2_rf:<10.3f} {overall_mae_rf:<10.2f}\")\n\n# Save Random Forest model\njoblib.dump(rf_model, 'random_forest_model.pkl')\njoblib.dump(scaler_rf, 'feature_scaler.pkl')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-18T15:28:52.221330Z","iopub.execute_input":"2026-01-18T15:28:52.221536Z","iopub.status.idle":"2026-01-18T15:29:05.949164Z","shell.execute_reply.started":"2026-01-18T15:28:52.221515Z","shell.execute_reply":"2026-01-18T15:29:05.948407Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"\n# ============================================================================\n# ALGORITHM 3: SUPPORT VECTOR REGRESSION (SVR)\n# ============================================================================\n\nprint(\"\\n\" + \"=\"*70)\nprint(\"📈 ALGORITHM 3: SUPPORT VECTOR REGRESSION\")\nprint(\"=\"*70)\n\n# Use same features as Random Forest\nX_train_svr = X_train_rf\nX_val_svr = X_val_rf\ny_train_svr = y_train_rf\ny_val_svr = y_val_rf\n\n# Train SVR\nprint(\"\\n🏋️ Training Support Vector Regression...\")\nsvr_model = MultiOutputRegressor(\n    SVR(\n        kernel='rbf',\n        C=10.0,\n        epsilon=0.1,\n        gamma='scale'\n    )\n)\n\nsvr_model.fit(X_train_svr, y_train_svr)\nsvr_preds = svr_model.predict(X_val_svr)\n\nprint(\"\\n📊 SVR Validation Results:\")\nprint(f\"{'Target':<15} {'R²':<10} {'MAE (g)':<10} {'RMSE (g)':<10}\")\nprint(\"-\" * 50)\n\nsvr_r2_scores = []\nfor i, target_name in enumerate(TARGETS):\n    r2 = r2_score(y_val_svr[:, i], svr_preds[:, i])\n    mae = mean_absolute_error(y_val_svr[:, i], svr_preds[:, i])\n    rmse = np.sqrt(mean_squared_error(y_val_svr[:, i], svr_preds[:, i]))\n    svr_r2_scores.append(r2)\n    print(f\"{target_name:<15} {r2:<10.3f} {mae:<10.2f} {rmse:<10.2f}\")\n\noverall_r2_svr = r2_score(y_val_svr.flatten(), svr_preds.flatten())\noverall_mae_svr = mean_absolute_error(y_val_svr.flatten(), svr_preds.flatten())\nprint(\"-\" * 50)\nprint(f\"{'OVERALL':<15} {overall_r2_svr:<10.3f} {overall_mae_svr:<10.2f}\")\n\n# Save SVR model\njoblib.dump(svr_model, 'svr_model.pkl')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-18T15:29:05.950175Z","iopub.execute_input":"2026-01-18T15:29:05.950414Z","iopub.status.idle":"2026-01-18T15:29:05.999114Z","shell.execute_reply.started":"2026-01-18T15:29:05.950395Z","shell.execute_reply":"2026-01-18T15:29:05.998250Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================================\n# COMPARISON & ENSEMBLE\n# ============================================================================\n\nprint(\"\\n\" + \"=\"*70)\nprint(\"📊 MODEL COMPARISON\")\nprint(\"=\"*70)\n\ncomparison_df = pd.DataFrame({\n    'Algorithm': ['Neural Network', 'Random Forest', 'Support Vector Regression'],\n    'Overall R²': [overall_r2_nn, overall_r2_rf, overall_r2_svr],\n    'Overall MAE (g)': [overall_mae_nn, overall_mae_rf, overall_mae_svr],\n    'Technique': ['Neural Networks', 'Ensemble Method', 'Regression + SVM']\n})\n\nprint(\"\\n\" + comparison_df.to_string(index=False))\n\n# Create ensemble predictions (simple average)\nprint(\"\\n🔮 Creating Ensemble Predictions...\")\nensemble_preds = (nn_preds + rf_preds + svr_preds) / 3\noverall_r2_ensemble = r2_score(nn_targets.flatten(), ensemble_preds.flatten())\noverall_mae_ensemble = mean_absolute_error(nn_targets.flatten(), ensemble_preds.flatten())\n\nprint(f\"\\nEnsemble (Average of 3 models):\")\nprint(f\"  Overall R²: {overall_r2_ensemble:.3f}\")\nprint(f\"  Overall MAE: {overall_mae_ensemble:.2f}g\")\n\n# Determine best model\nbest_idx = comparison_df['Overall R²'].idxmax()\nbest_model_name = comparison_df.loc[best_idx, 'Algorithm']\nbest_r2 = comparison_df.loc[best_idx, 'Overall R²']\n\nprint(f\"\\n🏆 Best Individual Model: {best_model_name} (R² = {best_r2:.3f})\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-18T15:29:06.000954Z","iopub.execute_input":"2026-01-18T15:29:06.001182Z","iopub.status.idle":"2026-01-18T15:29:06.012315Z","shell.execute_reply.started":"2026-01-18T15:29:06.001162Z","shell.execute_reply":"2026-01-18T15:29:06.011741Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================================\n# VISUALIZATION\n# ============================================================================\n\nprint(\"\\n📊 Creating visualizations...\")\n\n# Figure 1: Per-target R² comparison\nfig, ax = plt.subplots(figsize=(12, 6))\n\nx = np.arange(len(TARGETS))\nwidth = 0.25\n\nax.bar(x - width, nn_r2_scores, width, label='Neural Network', alpha=0.8)\nax.bar(x, rf_r2_scores, width, label='Random Forest', alpha=0.8)\nax.bar(x + width, svr_r2_scores, width, label='SVR', alpha=0.8)\n\nax.set_xlabel('Target', fontsize=12)\nax.set_ylabel('R² Score', fontsize=12)\nax.set_title('Per-Target R² Score Comparison', fontsize=14, fontweight='bold')\nax.set_xticks(x)\nax.set_xticklabels(TARGETS, rotation=45, ha='right')\nax.legend()\nax.grid(True, alpha=0.3, axis='y')\nax.axhline(y=0, color='k', linestyle='-', linewidth=0.5)\n\nplt.tight_layout()\nplt.savefig('r2_comparison.png', dpi=150, bbox_inches='tight')\nplt.show()\n\n# Figure 2: Scatter plots for each model\nfig, axes = plt.subplots(2, 2, figsize=(14, 12))\naxes = axes.flatten()\n\nmodels = [\n    ('Neural Network', nn_preds, nn_targets),\n    ('Random Forest', rf_preds, y_val_rf),\n    ('SVR', svr_preds, y_val_svr),\n    ('Ensemble', ensemble_preds, nn_targets)\n]\n\nfor idx, (name, preds, targets) in enumerate(models):\n    ax = axes[idx]\n    \n    ax.scatter(targets.flatten(), preds.flatten(), alpha=0.3, s=10, edgecolors='none')\n    \n    max_val = max(targets.max(), preds.max())\n    min_val = min(targets.min(), preds.min())\n    ax.plot([min_val, max_val], [min_val, max_val], 'r--', lw=2, label='Perfect Prediction')\n    \n    r2 = r2_score(targets.flatten(), preds.flatten())\n    mae = mean_absolute_error(targets.flatten(), preds.flatten())\n    \n    ax.set_xlabel('Actual Biomass (g)', fontsize=11)\n    ax.set_ylabel('Predicted Biomass (g)', fontsize=11)\n    ax.set_title(f'{name}\\nR² = {r2:.3f}, MAE = {mae:.2f}g', fontsize=12, fontweight='bold')\n    ax.legend(loc='upper left')\n    ax.grid(True, alpha=0.3)\n\nplt.tight_layout()\nplt.savefig('prediction_scatter_all.png', dpi=150, bbox_inches='tight')\nplt.show()\nplt.close()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-18T15:29:06.013170Z","iopub.execute_input":"2026-01-18T15:29:06.013408Z","iopub.status.idle":"2026-01-18T15:29:07.489571Z","shell.execute_reply.started":"2026-01-18T15:29:06.013392Z","shell.execute_reply":"2026-01-18T15:29:07.489044Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"\"\"\"\n=============================================================================\nADVANCED VISUALIZATIONS AND METRICS\n=============================================================================\nAdd this code after your model training to get comprehensive visualizations\n=============================================================================\n\"\"\"","metadata":{}},{"cell_type":"code","source":"# ============================================================================\n# VISUALIZATION 1: Comprehensive Model Comparison Dashboard\n# ============================================================================\n\nfig = plt.figure(figsize=(20, 12))\ngs = fig.add_gridspec(3, 3, hspace=0.3, wspace=0.3)\n\n# 1.1 Overall R² Comparison (Bar Chart)\nax1 = fig.add_subplot(gs[0, 0])\nmodels_list = ['Neural\\nNetwork', 'Random\\nForest', 'SVR', 'Ensemble']\nr2_values = [overall_r2_nn, overall_r2_rf, overall_r2_svr, overall_r2_ensemble]\ncolors = ['#FF6B6B', '#4ECDC4', '#45B7D1', '#FFA07A']\n\nbars = ax1.bar(models_list, r2_values, color=colors, alpha=0.8, edgecolor='black')\nax1.axhline(y=0.7, color='green', linestyle='--', linewidth=2, label='Target (0.7)')\nax1.axhline(y=0.5, color='orange', linestyle='--', linewidth=1.5, label='Baseline (0.5)')\nax1.set_ylabel('R² Score', fontsize=12, fontweight='bold')\nax1.set_title('Overall R² Score Comparison', fontsize=14, fontweight='bold')\nax1.set_ylim([0, 1])\nax1.legend()\nax1.grid(axis='y', alpha=0.3)\n\n# Add value labels on bars\nfor bar in bars:\n    height = bar.get_height()\n    ax1.text(bar.get_x() + bar.get_width()/2., height,\n             f'{height:.3f}', ha='center', va='bottom', fontweight='bold')\n\n# 1.2 MAE Comparison (Bar Chart)\nax2 = fig.add_subplot(gs[0, 1])\nmae_values = [overall_mae_nn, overall_mae_rf, overall_mae_svr, overall_mae_ensemble]\nbars = ax2.bar(models_list, mae_values, color=colors, alpha=0.8, edgecolor='black')\nax2.set_ylabel('MAE (grams)', fontsize=12, fontweight='bold')\nax2.set_title('Mean Absolute Error Comparison', fontsize=14, fontweight='bold')\nax2.grid(axis='y', alpha=0.3)\n\nfor bar in bars:\n    height = bar.get_height()\n    ax2.text(bar.get_x() + bar.get_width()/2., height,\n             f'{height:.2f}g', ha='center', va='bottom', fontweight='bold')\n\n# 1.3 Per-Target R² Heatmap\nax3 = fig.add_subplot(gs[0, 2])\nr2_matrix = np.array([nn_r2_scores, rf_r2_scores, svr_r2_scores]).T\nsns.heatmap(r2_matrix, annot=True, fmt='.3f', cmap='RdYlGn', center=0.5,\n            xticklabels=['NN', 'RF', 'SVR'],\n            yticklabels=TARGETS, ax=ax3, cbar_kws={'label': 'R² Score'})\nax3.set_title('Per-Target R² Heatmap', fontsize=14, fontweight='bold')\n\n# 1.4 Training Loss Curves (if you saved history)\nax4 = fig.add_subplot(gs[1, 0])\n# This assumes you saved training history - if not, skip this\nax4.text(0.5, 0.5, 'Training Loss Curve\\n(Add history tracking)', \n         ha='center', va='center', fontsize=12)\nax4.set_title('Training Progress', fontsize=14, fontweight='bold')\n\n# 1.5 Residuals Distribution\nax5 = fig.add_subplot(gs[1, 1])\nnn_residuals = (nn_targets - nn_preds).flatten()\nrf_residuals = (y_val_rf - rf_preds).flatten()\nsvr_residuals = (y_val_svr - svr_preds).flatten()\n\nax5.hist(nn_residuals, bins=50, alpha=0.5, label='NN', color=colors[0])\nax5.hist(rf_residuals, bins=50, alpha=0.5, label='RF', color=colors[1])\nax5.hist(svr_residuals, bins=50, alpha=0.5, label='SVR', color=colors[2])\nax5.axvline(x=0, color='black', linestyle='--', linewidth=2)\nax5.set_xlabel('Residual (Actual - Predicted)', fontsize=11)\nax5.set_ylabel('Frequency', fontsize=11)\nax5.set_title('Residuals Distribution', fontsize=14, fontweight='bold')\nax5.legend()\nax5.grid(alpha=0.3)\n\n# 1.6 Model Performance Summary Table\nax6 = fig.add_subplot(gs[1, 2])\nax6.axis('off')\n\nsummary_data = []\nfor i, model_name in enumerate(['Neural Network', 'Random Forest', 'SVR', 'Ensemble']):\n    if i < 3:\n        r2_val = r2_values[i]\n        mae_val = mae_values[i]\n    else:\n        r2_val = overall_r2_ensemble\n        mae_val = overall_mae_ensemble\n    \n    summary_data.append([model_name, f'{r2_val:.3f}', f'{mae_val:.2f}'])\n\ntable = ax6.table(cellText=summary_data,\n                  colLabels=['Model', 'R²', 'MAE (g)'],\n                  cellLoc='center',\n                  loc='center',\n                  bbox=[0, 0, 1, 1])\ntable.auto_set_font_size(False)\ntable.set_fontsize(10)\ntable.scale(1, 2)\n\n# Style header\nfor i in range(3):\n    table[(0, i)].set_facecolor('#40466e')\n    table[(0, i)].set_text_props(weight='bold', color='white')\n\nax6.set_title('Performance Summary', fontsize=14, fontweight='bold', pad=20)\n\n# 1.7 Prediction vs Actual for Best Model\nax7 = fig.add_subplot(gs[2, 0])\nbest_preds = nn_preds if overall_r2_nn == max(r2_values[:3]) else (rf_preds if overall_r2_rf == max(r2_values[:3]) else svr_preds)\nbest_targets = nn_targets if overall_r2_nn == max(r2_values[:3]) else (y_val_rf if overall_r2_rf == max(r2_values[:3]) else y_val_svr)\n\nax7.scatter(best_targets.flatten(), best_preds.flatten(), alpha=0.4, s=20)\nmax_val = max(best_targets.max(), best_preds.max())\nmin_val = min(best_targets.min(), best_preds.min())\nax7.plot([min_val, max_val], [min_val, max_val], 'r--', lw=2, label='Perfect Prediction')\nax7.set_xlabel('Actual Biomass (g)', fontsize=11)\nax7.set_ylabel('Predicted Biomass (g)', fontsize=11)\nax7.set_title('Best Model: Predictions vs Actual', fontsize=14, fontweight='bold')\nax7.legend()\nax7.grid(alpha=0.3)\n\n# 1.8 Error Distribution by Target\nax8 = fig.add_subplot(gs[2, 1])\nmae_by_target = []\nfor i in range(len(TARGETS)):\n    mae_by_target.append([\n        mean_absolute_error(nn_targets[:, i], nn_preds[:, i]),\n        mean_absolute_error(y_val_rf[:, i], rf_preds[:, i]),\n        mean_absolute_error(y_val_svr[:, i], svr_preds[:, i])\n    ])\n\nx = np.arange(len(TARGETS))\nwidth = 0.25\nax8.bar(x - width, [m[0] for m in mae_by_target], width, label='NN', alpha=0.8)\nax8.bar(x, [m[1] for m in mae_by_target], width, label='RF', alpha=0.8)\nax8.bar(x + width, [m[2] for m in mae_by_target], width, label='SVR', alpha=0.8)\nax8.set_xlabel('Target', fontsize=11)\nax8.set_ylabel('MAE (g)', fontsize=11)\nax8.set_title('MAE by Target Variable', fontsize=14, fontweight='bold')\nax8.set_xticks(x)\nax8.set_xticklabels(TARGETS, rotation=45, ha='right')\nax8.legend()\nax8.grid(axis='y', alpha=0.3)\n\n# 1.9 Percentage Error Distribution\nax9 = fig.add_subplot(gs[2, 2])\n# Calculate percentage errors (avoiding division by zero)\nnn_pct_errors = np.abs((nn_targets - nn_preds) / (nn_targets + 1e-6)) * 100\nnn_pct_errors = nn_pct_errors[nn_pct_errors < 200]  # Remove outliers\n\nax9.hist(nn_pct_errors.flatten(), bins=50, alpha=0.7, color=colors[0], edgecolor='black')\nax9.axvline(x=np.median(nn_pct_errors), color='red', linestyle='--', \n            linewidth=2, label=f'Median: {np.median(nn_pct_errors):.1f}%')\nax9.set_xlabel('Percentage Error (%)', fontsize=11)\nax9.set_ylabel('Frequency', fontsize=11)\nax9.set_title('Percentage Error Distribution (NN)', fontsize=14, fontweight='bold')\nax9.legend()\nax9.grid(alpha=0.3)\n\nplt.suptitle('Comprehensive Model Performance Dashboard', \n             fontsize=18, fontweight='bold', y=0.995)\nplt.savefig('comprehensive_dashboard.png', dpi=300, bbox_inches='tight')\nplt.show()\nplt.close()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-18T15:29:07.490437Z","iopub.execute_input":"2026-01-18T15:29:07.490665Z","iopub.status.idle":"2026-01-18T15:29:10.449549Z","shell.execute_reply.started":"2026-01-18T15:29:07.490641Z","shell.execute_reply":"2026-01-18T15:29:10.448859Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================================\n# VISUALIZATION 2: Detailed Per-Target Analysis\n# ============================================================================\n\nfig, axes = plt.subplots(5, 3, figsize=(18, 20))\n\nfor i, target_name in enumerate(TARGETS):\n    # Column 1: Scatter plot for each target\n    ax1 = axes[i, 0]\n    ax1.scatter(nn_targets[:, i], nn_preds[:, i], alpha=0.5, s=30, label='NN', color=colors[0])\n    ax1.scatter(y_val_rf[:, i], rf_preds[:, i], alpha=0.5, s=30, label='RF', color=colors[1])\n    ax1.scatter(y_val_svr[:, i], svr_preds[:, i], alpha=0.5, s=30, label='SVR', color=colors[2])\n    \n    max_val = max(nn_targets[:, i].max(), nn_preds[:, i].max())\n    min_val = min(nn_targets[:, i].min(), nn_preds[:, i].min())\n    ax1.plot([min_val, max_val], [min_val, max_val], 'r--', lw=2)\n    \n    ax1.set_xlabel('Actual (g)', fontsize=10)\n    ax1.set_ylabel('Predicted (g)', fontsize=10)\n    ax1.set_title(f'{target_name}: Predictions', fontsize=12, fontweight='bold')\n    ax1.legend(loc='upper left', fontsize=8)\n    ax1.grid(alpha=0.3)\n    \n    # Column 2: Residual plot\n    ax2 = axes[i, 1]\n    nn_res = nn_targets[:, i] - nn_preds[:, i]\n    rf_res = y_val_rf[:, i] - rf_preds[:, i]\n    svr_res = y_val_svr[:, i] - svr_preds[:, i]\n    \n    ax2.scatter(nn_targets[:, i], nn_res, alpha=0.5, s=20, label='NN', color=colors[0])\n    ax2.scatter(y_val_rf[:, i], rf_res, alpha=0.5, s=20, label='RF', color=colors[1])\n    ax2.scatter(y_val_svr[:, i], svr_res, alpha=0.5, s=20, label='SVR', color=colors[2])\n    ax2.axhline(y=0, color='black', linestyle='--', linewidth=2)\n    \n    ax2.set_xlabel('Actual (g)', fontsize=10)\n    ax2.set_ylabel('Residual (g)', fontsize=10)\n    ax2.set_title(f'{target_name}: Residuals', fontsize=12, fontweight='bold')\n    ax2.legend(loc='upper left', fontsize=8)\n    ax2.grid(alpha=0.3)\n    \n    # Column 3: Box plot of errors\n    ax3 = axes[i, 2]\n    error_data = [\n        np.abs(nn_res),\n        np.abs(rf_res),\n        np.abs(svr_res)\n    ]\n    \n    bp = ax3.boxplot(error_data, labels=['NN', 'RF', 'SVR'], patch_artist=True)\n    for patch, color in zip(bp['boxes'], colors[:3]):\n        patch.set_facecolor(color)\n        patch.set_alpha(0.7)\n    \n    ax3.set_ylabel('Absolute Error (g)', fontsize=10)\n    ax3.set_title(f'{target_name}: Error Distribution', fontsize=12, fontweight='bold')\n    ax3.grid(alpha=0.3, axis='y')\n\nplt.suptitle('Detailed Per-Target Performance Analysis', \n             fontsize=16, fontweight='bold', y=0.995)\nplt.tight_layout()\nplt.savefig('per_target_analysis.png', dpi=300, bbox_inches='tight')\nplt.show()\nplt.close()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-18T15:29:10.450468Z","iopub.execute_input":"2026-01-18T15:29:10.451316Z","iopub.status.idle":"2026-01-18T15:29:16.555378Z","shell.execute_reply.started":"2026-01-18T15:29:10.451296Z","shell.execute_reply":"2026-01-18T15:29:16.553791Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================================\n# VISUALIZATION 3: Confusion Matrix Style Heatmap\n# ============================================================================\n\nfig, axes = plt.subplots(1, 3, figsize=(18, 5))\n\nmodels_data = [\n    ('Neural Network', nn_preds, nn_targets),\n    ('Random Forest', rf_preds, y_val_rf),\n    ('SVR', svr_preds, y_val_svr)\n]\n\nfor idx, (name, preds, targets) in enumerate(models_data):\n    ax = axes[idx]\n    \n    # Create bins for actual vs predicted\n    bins = 5\n    actual_binned = np.digitize(targets.flatten(), bins=np.percentile(targets.flatten(), np.linspace(0, 100, bins+1)))\n    pred_binned = np.digitize(preds.flatten(), bins=np.percentile(preds.flatten(), np.linspace(0, 100, bins+1)))\n    \n    # Create confusion-style matrix\n    conf_matrix = np.zeros((bins, bins))\n    for a, p in zip(actual_binned, pred_binned):\n        if 1 <= a <= bins and 1 <= p <= bins:\n            conf_matrix[a-1, p-1] += 1\n    \n    # Normalize\n    conf_matrix = conf_matrix / conf_matrix.sum() * 100\n    \n    sns.heatmap(conf_matrix, annot=True, fmt='.1f', cmap='YlOrRd', ax=ax,\n                xticklabels=['Very Low', 'Low', 'Med', 'High', 'Very High'],\n                yticklabels=['Very Low', 'Low', 'Med', 'High', 'Very High'],\n                cbar_kws={'label': 'Percentage (%)'})\n    ax.set_xlabel('Predicted Bin', fontsize=11)\n    ax.set_ylabel('Actual Bin', fontsize=11)\n    ax.set_title(f'{name}\\nPrediction Distribution Matrix', fontsize=12, fontweight='bold')\n\nplt.tight_layout()\nplt.savefig('prediction_distribution_matrix.png', dpi=300, bbox_inches='tight')\nplt.show()\nplt.close()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-18T15:29:16.556749Z","iopub.execute_input":"2026-01-18T15:29:16.556993Z","iopub.status.idle":"2026-01-18T15:29:17.930383Z","shell.execute_reply.started":"2026-01-18T15:29:16.556972Z","shell.execute_reply":"2026-01-18T15:29:17.929343Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================================\n# VISUALIZATION 3: Error Analysis\n# ============================================================================\n\nprint(\"\\nCreating Visualization 3: Error Analysis...\")\n\nfig, axes = plt.subplots(2, 3, figsize=(14, 8))\naxes = axes.flatten()\n\n# MAE by Target\nax = axes[0]\nmae_by_target = []\nfor i in range(len(TARGETS)):\n    mae_by_target.append([\n        mean_absolute_error(nn_targets[:, i], nn_preds[:, i]),\n        mean_absolute_error(y_val_rf[:, i], rf_preds[:, i]),\n        mean_absolute_error(y_val_svr[:, i], svr_preds[:, i])\n    ])\n\nx = np.arange(len(TARGETS))\nwidth = 0.25\nax.bar(x - width, [m[0] for m in mae_by_target], width, label='NN', alpha=0.8, color=colors[0])\nax.bar(x, [m[1] for m in mae_by_target], width, label='RF', alpha=0.8, color=colors[1])\nax.bar(x + width, [m[2] for m in mae_by_target], width, label='SVR', alpha=0.8, color=colors[2])\nax.set_ylabel('MAE (g)', fontsize=10)\nax.set_title('MAE by Target', fontsize=11, fontweight='bold')\nax.set_xticks(x)\nax.set_xticklabels(TARGETS, rotation=45, ha='right', fontsize=8)\nax.legend(fontsize=8)\nax.grid(axis='y', alpha=0.3)\n\n# RMSE by Target\nax = axes[1]\nrmse_by_target = []\nfor i in range(len(TARGETS)):\n    rmse_by_target.append([\n        np.sqrt(mean_squared_error(nn_targets[:, i], nn_preds[:, i])),\n        np.sqrt(mean_squared_error(y_val_rf[:, i], rf_preds[:, i])),\n        np.sqrt(mean_squared_error(y_val_svr[:, i], svr_preds[:, i]))\n    ])\n\nax.bar(x - width, [m[0] for m in rmse_by_target], width, label='NN', alpha=0.8, color=colors[0])\nax.bar(x, [m[1] for m in rmse_by_target], width, label='RF', alpha=0.8, color=colors[1])\nax.bar(x + width, [m[2] for m in rmse_by_target], width, label='SVR', alpha=0.8, color=colors[2])\nax.set_ylabel('RMSE (g)', fontsize=10)\nax.set_title('RMSE by Target', fontsize=11, fontweight='bold')\nax.set_xticks(x)\nax.set_xticklabels(TARGETS, rotation=45, ha='right', fontsize=8)\nax.legend(fontsize=8)\nax.grid(axis='y', alpha=0.3)\n\n# Error Box Plot for Neural Network\nax = axes[2]\nnn_errors_by_target = [np.abs(nn_targets[:, i] - nn_preds[:, i]) for i in range(len(TARGETS))]\nbp = ax.boxplot(nn_errors_by_target, labels=TARGETS, patch_artist=True)\nfor patch in bp['boxes']:\n    patch.set_facecolor(colors[0])\n    patch.set_alpha(0.7)\nax.set_ylabel('Absolute Error (g)', fontsize=10)\nax.set_title('NN Error Distribution', fontsize=11, fontweight='bold')\nax.set_xticklabels(TARGETS, rotation=45, ha='right', fontsize=8)\nax.grid(axis='y', alpha=0.3)\n\n# Error Box Plot for Random Forest\nax = axes[3]\nrf_errors_by_target = [np.abs(y_val_rf[:, i] - rf_preds[:, i]) for i in range(len(TARGETS))]\nbp = ax.boxplot(rf_errors_by_target, labels=TARGETS, patch_artist=True)\nfor patch in bp['boxes']:\n    patch.set_facecolor(colors[1])\n    patch.set_alpha(0.7)\nax.set_ylabel('Absolute Error (g)', fontsize=10)\nax.set_title('RF Error Distribution', fontsize=11, fontweight='bold')\nax.set_xticklabels(TARGETS, rotation=45, ha='right', fontsize=8)\nax.grid(axis='y', alpha=0.3)\n\n# Error Box Plot for SVR\nax = axes[4]\nsvr_errors_by_target = [np.abs(y_val_svr[:, i] - svr_preds[:, i]) for i in range(len(TARGETS))]\nbp = ax.boxplot(svr_errors_by_target, labels=TARGETS, patch_artist=True)\nfor patch in bp['boxes']:\n    patch.set_facecolor(colors[2])\n    patch.set_alpha(0.7)\nax.set_ylabel('Absolute Error (g)', fontsize=10)\nax.set_title('SVR Error Distribution', fontsize=11, fontweight='bold')\nax.set_xticklabels(TARGETS, rotation=45, ha='right', fontsize=8)\nax.grid(axis='y', alpha=0.3)\n\n# Model Performance Summary Table\nax = axes[5]\nax.axis('off')\nsummary_data = [\n    ['Neural Network', f'{overall_r2_nn:.3f}', f'{overall_mae_nn:.2f}'],\n    ['Random Forest', f'{overall_r2_rf:.3f}', f'{overall_mae_rf:.2f}'],\n    ['SVR', f'{overall_r2_svr:.3f}', f'{overall_mae_svr:.2f}'],\n    ['Ensemble', f'{overall_r2_ensemble:.3f}', f'{overall_mae_ensemble:.2f}']\n]\n\ntable = ax.table(cellText=summary_data,\n                 colLabels=['Model', 'R²', 'MAE (g)'],\n                 cellLoc='center',\n                 loc='center',\n                 bbox=[0, 0.2, 1, 0.6])\ntable.auto_set_font_size(False)\ntable.set_fontsize(9)\ntable.scale(1, 1.8)\n\nfor i in range(3):\n    table[(0, i)].set_facecolor('#40466e')\n    table[(0, i)].set_text_props(weight='bold', color='white')\n\nax.set_title('Summary Table', fontsize=11, fontweight='bold')\n\nplt.suptitle('Error Analysis Dashboard', fontsize=14, fontweight='bold')\nplt.tight_layout()\nplt.savefig('error_analysis.png', dpi=120, bbox_inches='tight')\nplt.show()\nplt.close()\nprint(\"✅ Saved: error_analysis.png\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-18T15:29:17.931425Z","iopub.execute_input":"2026-01-18T15:29:17.931659Z","iopub.status.idle":"2026-01-18T15:29:19.216785Z","shell.execute_reply.started":"2026-01-18T15:29:17.931641Z","shell.execute_reply":"2026-01-18T15:29:19.216020Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================================\n# VISUALIZATION 4: Feature Importance (Random Forest)\n# ============================================================================\n\nprint(\"\\nCreating Visualization 4: Feature Importance...\")\n\nfig, ax = plt.subplots(figsize=(10, 7))\n\nfeature_names = ['Color_R_mean', 'Color_R_std', 'Color_G_mean', 'Color_G_std', \n                 'Color_B_mean', 'Color_B_std', 'Texture_X', 'Texture_Y',\n                 'HSV_H', 'HSV_S', 'HSV_V', 'Green_Dom_1', 'Green_Dom_2'] + META_COLS\n\n# Average importance across all targets\navg_importance = np.zeros(len(feature_names))\nfor estimator in rf_model.estimators_:\n    avg_importance += estimator.feature_importances_\navg_importance /= len(rf_model.estimators_)\n\n# Sort by importance\nsorted_idx = np.argsort(avg_importance)\npos = np.arange(sorted_idx.shape[0])\n\nax.barh(pos, avg_importance[sorted_idx], align='center', \n        color='skyblue', alpha=0.8, edgecolor='navy')\nax.set_yticks(pos)\nax.set_yticklabels([feature_names[i] for i in sorted_idx], fontsize=9)\nax.set_xlabel('Feature Importance', fontsize=11, fontweight='bold')\nax.set_title('Random Forest Feature Importance\\n(Averaged across all targets)', \n             fontsize=13, fontweight='bold')\nax.grid(axis='x', alpha=0.3)\n\nplt.tight_layout()\nplt.savefig('feature_importance.png', dpi=120, bbox_inches='tight')\nplt.show()\nplt.close()\nprint(\"✅ Saved: feature_importance.png\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-18T15:29:19.218656Z","iopub.execute_input":"2026-01-18T15:29:19.218852Z","iopub.status.idle":"2026-01-18T15:29:19.725271Z","shell.execute_reply.started":"2026-01-18T15:29:19.218835Z","shell.execute_reply":"2026-01-18T15:29:19.724506Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":" #============================================================================\n# VISUALIZATION 5: Ensemble Analysis\n# ============================================================================\n\nprint(\"\\nCreating Visualization 5: Ensemble Analysis...\")\n\nfig, axes = plt.subplots(2, 2, figsize=(12, 10))\n\n# Best model vs Ensemble\nax = axes[0, 0]\nensemble_preds = (nn_preds + rf_preds + svr_preds) / 3\n\nax.scatter(nn_targets.flatten(), nn_preds.flatten(), alpha=0.3, s=10, label='NN', color=colors[0])\nax.scatter(nn_targets.flatten(), ensemble_preds.flatten(), alpha=0.3, s=10, label='Ensemble', color=colors[3])\n\nmax_val = max(nn_targets.max(), nn_preds.max(), ensemble_preds.max())\nmin_val = min(nn_targets.min(), nn_preds.min(), ensemble_preds.min())\nax.plot([min_val, max_val], [min_val, max_val], 'r--', lw=2)\n\nax.set_xlabel('Actual (g)', fontsize=10)\nax.set_ylabel('Predicted (g)', fontsize=10)\nax.set_title('Best Model vs Ensemble', fontsize=11, fontweight='bold')\nax.legend()\nax.grid(alpha=0.3)\n\n# Model Agreement\nax = axes[0, 1]\nfor i in range(min(30, len(nn_targets))):\n    predictions = [nn_preds.flatten()[i], rf_preds.flatten()[i], svr_preds.flatten()[i]]\n    min_pred = min(predictions)\n    max_pred = max(predictions)\n    ax.plot([i, i], [min_pred, max_pred], 'gray', alpha=0.5, linewidth=2)\n    ax.scatter([i]*3, predictions, c=colors[:3], s=30, alpha=0.7)\n\nax.set_xlabel('Sample Index', fontsize=10)\nax.set_ylabel('Predicted Biomass (g)', fontsize=10)\nax.set_title('Model Agreement (First 30 Samples)', fontsize=11, fontweight='bold')\nax.grid(alpha=0.3)\n\n# Ensemble Improvement\nax = axes[1, 0]\nmodels = ['NN', 'RF', 'SVR', 'Ensemble']\nr2_scores = [overall_r2_nn, overall_r2_rf, overall_r2_svr, overall_r2_ensemble]\nmae_scores = [overall_mae_nn, overall_mae_rf, overall_mae_svr, overall_mae_ensemble]\n\nx = np.arange(len(models))\nwidth = 0.35\n\nax2 = ax.twinx()\nbars1 = ax.bar(x - width/2, r2_scores, width, label='R²', alpha=0.8, color='green')\nbars2 = ax2.bar(x + width/2, mae_scores, width, label='MAE', alpha=0.8, color='red')\n\nax.set_ylabel('R² Score', fontsize=10, color='green')\nax2.set_ylabel('MAE (g)', fontsize=10, color='red')\nax.set_xlabel('Model', fontsize=10)\nax.set_xticks(x)\nax.set_xticklabels(models)\nax.set_title('Ensemble Performance Comparison', fontsize=11, fontweight='bold')\nax.tick_params(axis='y', labelcolor='green')\nax2.tick_params(axis='y', labelcolor='red')\nax.grid(alpha=0.3)\n\n# Add values on bars\nfor bar in bars1:\n    height = bar.get_height()\n    ax.text(bar.get_x() + bar.get_width()/2., height,\n            f'{height:.3f}', ha='center', va='bottom', fontsize=8)\n\n# Correlation between models\nax = axes[1, 1]\ncorr_matrix = np.corrcoef([nn_preds.flatten(), rf_preds.flatten(), svr_preds.flatten()])\nsns.heatmap(corr_matrix, annot=True, fmt='.3f', cmap='coolwarm', \n            xticklabels=['NN', 'RF', 'SVR'],\n            yticklabels=['NN', 'RF', 'SVR'], ax=ax, cbar_kws={'label': 'Correlation'})\nax.set_title('Model Prediction Correlation', fontsize=11, fontweight='bold')\n\nplt.suptitle('Ensemble Analysis', fontsize=14, fontweight='bold')\nplt.tight_layout()\nplt.savefig('ensemble_analysis.png', dpi=120, bbox_inches='tight')\nplt.show()\nplt.close()\nprint(\"✅ Saved: ensemble_analysis.png\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-18T15:29:19.726048Z","iopub.execute_input":"2026-01-18T15:29:19.726246Z","iopub.status.idle":"2026-01-18T15:29:21.052593Z","shell.execute_reply.started":"2026-01-18T15:29:19.726221Z","shell.execute_reply":"2026-01-18T15:29:21.051937Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================================\n# VISUALIZATION 6: Model Agreement Analysis\n# ============================================================================\n\nfig, axes = plt.subplots(2, 3, figsize=(18, 10))\n\nfor i, target_name in enumerate(TARGETS):\n    row = i // 3\n    col = i % 3\n    ax = axes[row, col]\n    \n    # Calculate agreement between models\n    nn_pred_target = nn_preds[:, i]\n    rf_pred_target = rf_preds[:, i]\n    svr_pred_target = svr_preds[:, i]\n    actual = nn_targets[:, i]\n    \n    # Plot prediction ranges\n    for j in range(min(50, len(actual))):  # Show first 50 samples\n        predictions = [nn_pred_target[j], rf_pred_target[j], svr_pred_target[j]]\n        min_pred = min(predictions)\n        max_pred = max(predictions)\n        \n        # Draw range line\n        ax.plot([j, j], [min_pred, max_pred], 'gray', alpha=0.3, linewidth=1)\n        \n        # Plot individual predictions\n        ax.scatter(j, nn_pred_target[j], color=colors[0], s=20, alpha=0.6)\n        ax.scatter(j, rf_pred_target[j], color=colors[1], s=20, alpha=0.6)\n        ax.scatter(j, svr_pred_target[j], color=colors[2], s=20, alpha=0.6)\n        \n        # Plot actual value\n        ax.scatter(j, actual[j], color='black', marker='x', s=50, linewidths=2)\n    \n    ax.set_xlabel('Sample Index', fontsize=10)\n    ax.set_ylabel('Biomass (g)', fontsize=10)\n    ax.set_title(f'{target_name}\\nModel Agreement', fontsize=12, fontweight='bold')\n    ax.grid(alpha=0.3)\n    \n    # Add legend only to first subplot\n    if i == 0:\n        ax.scatter([], [], color=colors[0], s=40, label='NN')\n        ax.scatter([], [], color=colors[1], s=40, label='RF')\n        ax.scatter([], [], color=colors[2], s=40, label='SVR')\n        ax.scatter([], [], color='black', marker='x', s=50, label='Actual')\n        ax.legend(loc='upper right', fontsize=8)\n\n# Remove extra subplot\naxes[1, 2].remove()\n\nplt.suptitle('Model Agreement Analysis (First 50 Samples)', \n             fontsize=16, fontweight='bold', y=0.995)\nplt.tight_layout()\nplt.savefig('model_agreement.png', dpi=300, bbox_inches='tight')\nplt.show()\nplt.close()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-18T15:29:21.053500Z","iopub.execute_input":"2026-01-18T15:29:21.053747Z","iopub.status.idle":"2026-01-18T15:29:33.457608Z","shell.execute_reply.started":"2026-01-18T15:29:21.053725Z","shell.execute_reply":"2026-01-18T15:29:33.456742Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================================\n# SUMMARY: Print All Generated Files\n# ============================================================================\n\nprint(\"\\n\" + \"=\"*70)\nprint(\"📊 VISUALIZATION FILES GENERATED\")\nprint(\"=\"*70)\nprint(\"✅ comprehensive_dashboard.png - Overall performance dashboard\")\nprint(\"✅ per_target_analysis.png - Detailed analysis for each target\")\nprint(\"✅ prediction_distribution_matrix.png - Confusion-style matrices\")\nprint(\"✅ error_analysis.png - Error metrics and distributions\")\nprint(\"✅ feature_importance.png - Random Forest feature importance\")\nprint(\"✅ ensemble_analysis.png - Ensemble method analysis\")\nprint(\"✅ model_agreement.png - Agreement between models\")\nprint(\"\\n🎉 All visualizations completed!\")\nprint(\"=\"*70)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-18T15:29:33.459127Z","iopub.execute_input":"2026-01-18T15:29:33.459965Z","iopub.status.idle":"2026-01-18T15:29:33.465129Z","shell.execute_reply.started":"2026-01-18T15:29:33.459930Z","shell.execute_reply":"2026-01-18T15:29:33.464426Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"\n# ============================================================================\n# GENERATE TEST PREDICTIONS\n# ============================================================================\n\nprint(\"\\n\" + \"=\"*70)\nprint(\"🎯 GENERATING TEST PREDICTIONS\")\nprint(\"=\"*70)\n\n# Choose which model to use for submission\nuse_ensemble = True  # Set to False to use best individual model\n\nif use_ensemble:\n    print(\"Using Ensemble predictions (average of all 3 models)\")\n    \n    # Neural Network predictions\n    test_ds = BiomassDataset(test_df, f\"{DATA_DIR}/test\", test_tfms, train=False)\n    test_loader = DataLoader(test_ds, batch_size=32, shuffle=False, num_workers=2)\n    \n    nn_model.eval()\n    nn_test_preds = []\n    test_paths = []\n    \n    with torch.no_grad():\n        for img, meta, image_paths in test_loader:\n            img, meta = img.to(DEVICE), meta.to(DEVICE)\n            outputs = nn_model(img, meta).cpu().numpy()\n            nn_test_preds.append(outputs)\n            test_paths.extend(image_paths)\n    \n    nn_test_preds = np.vstack(nn_test_preds)\n    \n    # Random Forest and SVR predictions\n    print(\"Extracting test features...\")\n    test_img_features = []\n    for i, img_path in enumerate(test_df[\"image_path\"]):\n        if i % 50 == 0:\n            print(f\"  Progress: {i}/{len(test_df)}\", end='\\r')\n        features = extract_image_features(img_path, f\"{DATA_DIR}/test\")\n        test_img_features.append(features)\n    print(f\"  Completed: {len(test_df)}/{len(test_df)}\")\n    \n    X_test = np.hstack([np.array(test_img_features), test_df[META_COLS].values])\n    X_test = scaler_rf.transform(X_test)\n    \n    rf_test_preds = rf_model.predict(X_test)\n    svr_test_preds = svr_model.predict(X_test)\n    \n    # Ensemble\n    final_preds = (nn_test_preds + rf_test_preds + svr_test_preds) / 3\n    \nelse:\n    print(f\"Using best individual model: {best_model_name}\")\n    \n    if best_model_name == 'Neural Network':\n        test_ds = BiomassDataset(test_df, f\"{DATA_DIR}/test\", test_tfms, train=False)\n        test_loader = DataLoader(test_ds, batch_size=32, shuffle=False, num_workers=2)\n        \n        nn_model.eval()\n        final_preds = []\n        test_paths = []\n        \n        with torch.no_grad():\n            for img, meta, image_paths in test_loader:\n                img, meta = img.to(DEVICE), meta.to(DEVICE)\n                outputs = nn_model(img, meta).cpu().numpy()\n                final_preds.append(outputs)\n                test_paths.extend(image_paths)\n        \n        final_preds = np.vstack(final_preds)\n    \n    else:\n        print(\"Extracting test features...\")\n        test_img_features = []\n        for i, img_path in enumerate(test_df[\"image_path\"]):\n            features = extract_image_features(img_path, f\"{DATA_DIR}/test\")\n            test_img_features.append(features)\n        \n        X_test = np.hstack([np.array(test_img_features), test_df[META_COLS].values])\n        X_test = scaler_rf.transform(X_test)\n        \n        if best_model_name == 'Random Forest':\n            final_preds = rf_model.predict(X_test)\n        else:\n            final_preds = svr_model.predict(X_test)\n        \n        test_paths = test_df[\"image_path\"].tolist()\n\n\n# Create submission\nrows = []\nfor i, img_path in enumerate(test_paths):\n    image_id = img_path.split(\"/\")[-1].replace(\".jpg\", \"\")\n    for j, tgt in enumerate(TARGETS):\n        rows.append((f\"{image_id}__{tgt}\", max(final_preds[i, j], 0)))\n\nsubmission = pd.DataFrame(rows, columns=[\"sample_id\", \"target\"])\n\nprint(f\"\\n✅ Generated {len(submission)} predictions\")\nprint(f\"Sample submission preview:\")\nprint(submission.head(10))\n\nsubmission.to_csv(\"submission.csv\", index=False)\nprint(\"\\n✅ Submission saved to 'submission.csv'\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-18T15:29:33.466094Z","iopub.execute_input":"2026-01-18T15:29:33.466286Z","iopub.status.idle":"2026-01-18T15:29:34.528226Z","shell.execute_reply.started":"2026-01-18T15:29:33.466268Z","shell.execute_reply":"2026-01-18T15:29:34.527394Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Remove duplicates\nsubmission = submission.drop_duplicates(subset=['sample_id'])\nprint(f\"Final submission shape: {submission.shape}\")\nsubmission.to_csv(\"submission.csv\", index=False)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-18T15:29:34.529165Z","iopub.execute_input":"2026-01-18T15:29:34.529384Z","iopub.status.idle":"2026-01-18T15:29:34.535661Z","shell.execute_reply.started":"2026-01-18T15:29:34.529364Z","shell.execute_reply":"2026-01-18T15:29:34.535029Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================================\n# SUMMARY\n# ============================================================================\n\nprint(\"\\n\" + \"=\"*70)\nprint(\"📋 SUMMARY\")\nprint(\"=\"*70)\n\nprint(\"\\n✅ Three Algorithms Implemented:\")\nprint(\"   1. Neural Network (CNN + MLP) - Deep Learning\")\nprint(\"   2. Random Forest - Ensemble Method\")\nprint(\"   3. Support Vector Regression - Regression + SVM\")\n\nprint(f\"\\n📊 Performance Summary:\")\nprint(comparison_df.to_string(index=False))\n\nprint(f\"\\n🎯 Final Submission:\")\nif use_ensemble:\n    print(f\"   Method: Ensemble (average of 3 models)\")\n    print(f\"   Expected R²: {overall_r2_ensemble:.3f}\")\nelse:\n    print(f\"   Method: {best_model_name}\")\n    print(f\"   Expected R²: {best_r2:.3f}\")\n\nprint(\"\\n💾 Saved Files:\")\nprint(\"   - submission.csv\")\nprint(\"   - neural_network_model.pth\")\nprint(\"   - random_forest_model.pkl\")\nprint(\"   - svr_model.pkl\")\nprint(\"   - feature_scaler.pkl\")\nprint(\"   - r2_comparison.png\")\nprint(\"   - prediction_scatter_all.png\")\n\nprint(\"\\n\" + \"=\"*70)\nprint(\"🎉 ALL DONE! Your submission is ready.\")\nprint(\"=\"*70)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-18T15:29:34.536578Z","iopub.execute_input":"2026-01-18T15:29:34.536777Z","iopub.status.idle":"2026-01-18T15:29:34.553180Z","shell.execute_reply.started":"2026-01-18T15:29:34.536758Z","shell.execute_reply":"2026-01-18T15:29:34.552589Z"}},"outputs":[],"execution_count":null}]}