{"metadata":{"kernelspec":{"name":"python3","display_name":"Python 3","language":"python"},"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":31154,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":true},"papermill":{"default_parameters":{},"duration":6.756759,"end_time":"2025-02-26T02:09:27.363350","environment_variables":{},"exception":null,"input_path":"__notebook__.ipynb","output_path":"__notebook__.ipynb","parameters":{},"start_time":"2025-02-26T02:09:20.606591","version":"2.6.0"}},"nbformat_minor":4,"nbformat":4,"cells":[{"id":"f506feee-98f0-4106-a53a-ccbff09b3ce3","cell_type":"markdown","source":"# CSIRO - Image2Biomass Prediction\n- Predict biomass using the provided pasture images\n### https://www.kaggle.com/competitions/csiro-biomass","metadata":{}},{"id":"uxo5vg22olf","cell_type":"code","source":"# Import libraries\nimport numpy as np\nimport pandas as pd\nimport torch\nimport torch.nn as nn\nimport torch.optim as optim\nfrom torch.utils.data import Dataset, DataLoader\nfrom torchvision import transforms\nfrom PIL import Image\nfrom sklearn.model_selection import train_test_split\nfrom sklearn.metrics import mean_absolute_error, mean_squared_error, r2_score\nimport matplotlib.pyplot as plt\nimport os\nfrom tqdm import tqdm\n\ndevice = torch.device('cuda' if torch.cuda.is_available() else 'cpu')\nprint(f'Using device: {device}')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-02T03:25:28.328732Z","iopub.execute_input":"2025-11-02T03:25:28.329417Z","iopub.status.idle":"2025-11-02T03:25:35.989140Z","shell.execute_reply.started":"2025-11-02T03:25:28.329395Z","shell.execute_reply":"2025-11-02T03:25:35.988526Z"}},"outputs":[],"execution_count":null},{"id":"p2o67pkkom","cell_type":"code","source":"# ==================== CONFIGURATION ====================\n\n# Data Paths\nDATA_DIR = '/kaggle/input/csiro-biomass'\nTRAIN_CSV = DATA_DIR+'/train.csv'\nTEST_CSV = DATA_DIR+'/test.csv'\n\n# Image Parameters\nIMG_SIZE = (256, 256)\n\n# Training Parameters\nBATCH_SIZE = 16\nNUM_EPOCHS = 50\nLEARNING_RATE = 0.0001\nRANDOM_SEED = 21\nVAL_SPLIT = 0.2  # 20% validation split\nNUM_WORKERS = 2\n\n# Model Architecture - Simple CNN\nCNN_CHANNELS = [32, 64, 128, 256]  # Conv layer channel sizes\nCLASSIFIER_HIDDEN = [128, 64]  # Hidden layers in final classifier\nDROPOUT_RATES = [0.3, 0.1]  # Dropout rates for classifier layers\n\n# Learning Rate Scheduler\nSCHEDULER_FACTOR = 0.5  # Reduce LR by this factor\nSCHEDULER_PATIENCE = 3  # Epochs with no improvement\n\n# Competition Weights (for loss and metric calculation)\nTARGET_WEIGHTS = {\n    'Dry_Clover_g': 0.1,\n    'Dry_Dead_g': 0.1,\n    'Dry_Green_g': 0.1,\n    'Dry_Total_g': 0.5,\n    'GDM_g': 0.2\n}\nTARGET_NAMES = ['Dry_Clover_g', 'Dry_Dead_g', 'Dry_Green_g', 'Dry_Total_g', 'GDM_g']\nWEIGHTS_ARRAY = [TARGET_WEIGHTS[name] for name in TARGET_NAMES]\n\n# Display Configuration\nprint(\"=\" * 60)\nprint(\"CONFIGURATION SUMMARY\")\nprint(\"=\" * 60)\nprint(f\"Data Directory: {DATA_DIR}\")\nprint(f\"Image Size: {IMG_SIZE}\")\nprint(f\"Batch Size: {BATCH_SIZE}\")\nprint(f\"Learning Rate: {LEARNING_RATE}\")\nprint(f\"Epochs: {NUM_EPOCHS}\")\nprint(f\"Validation Split: {VAL_SPLIT * 100}%\")\nprint(f\"Random Seed: {RANDOM_SEED}\")\nprint(f\"\\nModel Architecture:\")\nprint(f\"  CNN Channels: {CNN_CHANNELS}\")\nprint(f\"  Classifier: {CNN_CHANNELS[-1]} -> {' -> '.join(map(str, CLASSIFIER_HIDDEN))} -> 5\")\nprint(f\"\\nTarget Weights:\")\nfor name, weight in TARGET_WEIGHTS.items():\n    print(f\"  {name}: {weight}\")\nprint(\"=\" * 60)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-02T03:25:35.990311Z","iopub.execute_input":"2025-11-02T03:25:35.990645Z","iopub.status.idle":"2025-11-02T03:25:35.998826Z","shell.execute_reply.started":"2025-11-02T03:25:35.990627Z","shell.execute_reply":"2025-11-02T03:25:35.997955Z"}},"outputs":[],"execution_count":null},{"id":"cobdzzzo0cj","cell_type":"code","source":"# Load and prepare data\ndf = pd.read_csv(TRAIN_CSV)\n\n# Pivot the data so each image has one row with all 5 targets\ndf_pivot = df.pivot_table(\n    index=['image_path'],\n    columns='target_name',\n    values='target'\n).reset_index()\n\n# Rename columns for clarity\ndf_pivot.columns.name = None\ntarget_cols = TARGET_NAMES\n\nprint(f\"Total images: {len(df_pivot)}\")\nprint(f\"\\nTarget columns: {target_cols}\")\nprint(f\"\\nTarget statistics:\")\nprint(df_pivot[target_cols].describe())\nprint(f\"\\nFirst few rows:\")\nprint(df_pivot.head())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-02T03:25:35.999585Z","iopub.execute_input":"2025-11-02T03:25:35.999858Z","iopub.status.idle":"2025-11-02T03:25:36.077659Z","shell.execute_reply.started":"2025-11-02T03:25:35.999836Z","shell.execute_reply":"2025-11-02T03:25:36.076965Z"}},"outputs":[],"execution_count":null},{"id":"bsuleqg36hr","cell_type":"code","source":"# Prepare data splits\nfrom sklearn.preprocessing import StandardScaler\n\n# Train-test split\ntrain_df, val_df = train_test_split(df_pivot, test_size=VAL_SPLIT, random_state=RANDOM_SEED)\n\n# Normalize target variables using training statistics\ntarget_scaler = StandardScaler()\ntrain_df[target_cols] = target_scaler.fit_transform(train_df[target_cols])\nval_df[target_cols] = target_scaler.transform(val_df[target_cols])\n\nprint(f\"Training samples: {len(train_df)}\")\nprint(f\"Validation samples: {len(val_df)}\")\nprint(f\"\\nTarget normalization applied:\")\nprint(f\"  Mean: {target_scaler.mean_}\")\nprint(f\"  Std: {target_scaler.scale_}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-02T03:25:36.078397Z","iopub.execute_input":"2025-11-02T03:25:36.078566Z","iopub.status.idle":"2025-11-02T03:25:36.091652Z","shell.execute_reply.started":"2025-11-02T03:25:36.078553Z","shell.execute_reply":"2025-11-02T03:25:36.091029Z"}},"outputs":[],"execution_count":null},{"id":"s3ozzw6u91a","cell_type":"code","source":"# Dataset class - Image only\nclass BiomassDataset(Dataset):\n    def __init__(self, df, data_dir=DATA_DIR, transform=None):\n        self.df = df.reset_index(drop=True)\n        self.data_dir = data_dir\n        self.transform = transform\n        self.target_cols = TARGET_NAMES\n        \n    def __len__(self):\n        return len(self.df)\n    \n    def __getitem__(self, idx):\n        img_path = os.path.join(self.data_dir, self.df.loc[idx, 'image_path'])\n        image = Image.open(img_path).convert('RGB')\n        \n        if self.transform:\n            image = self.transform(image)\n        \n        # Get targets (5 values)\n        targets = torch.tensor(self.df.loc[idx, self.target_cols].values.astype(np.float32), dtype=torch.float32)\n        \n        return image, targets\n\n# Simple data transforms\ntransform = transforms.Compose([\n    transforms.Resize(IMG_SIZE),\n    transforms.ToTensor(),  # Converts to [0, 1] range\n])\n\n# Create datasets and dataloaders\ntrain_dataset = BiomassDataset(train_df, transform=transform)\nval_dataset = BiomassDataset(val_df, transform=transform)\n\ntrain_loader = DataLoader(train_dataset, batch_size=BATCH_SIZE, shuffle=True, num_workers=NUM_WORKERS)\nval_loader = DataLoader(val_dataset, batch_size=BATCH_SIZE, shuffle=False, num_workers=NUM_WORKERS)\n\nprint(f\"Train batches: {len(train_loader)}\")\nprint(f\"Validation batches: {len(val_loader)}\")\nprint(f\"Number of targets: 5 ({', '.join(TARGET_NAMES)})\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-02T03:25:36.093780Z","iopub.execute_input":"2025-11-02T03:25:36.094187Z","iopub.status.idle":"2025-11-02T03:25:36.106851Z","shell.execute_reply.started":"2025-11-02T03:25:36.094169Z","shell.execute_reply":"2025-11-02T03:25:36.106254Z"}},"outputs":[],"execution_count":null},{"id":"a35qrcs5l0l","cell_type":"code","source":"# Simple CNN model - Image only\nclass SimpleCNN(nn.Module):\n    def __init__(self, num_outputs=5):\n        super(SimpleCNN, self).__init__()\n        \n        # CNN for image features\n        self.features = nn.Sequential(\n            # Conv block 1\n            nn.Conv2d(3, CNN_CHANNELS[0], kernel_size=3, padding=1),\n            nn.BatchNorm2d(CNN_CHANNELS[0]),\n            nn.ReLU(inplace=True),\n            nn.MaxPool2d(2, 2),  # 112x112\n            \n            # Conv block 2\n            nn.Conv2d(CNN_CHANNELS[0], CNN_CHANNELS[1], kernel_size=3, padding=1),\n            nn.BatchNorm2d(CNN_CHANNELS[1]),\n            nn.ReLU(inplace=True),\n            nn.MaxPool2d(2, 2),  # 56x56\n            \n            # Conv block 3\n            nn.Conv2d(CNN_CHANNELS[1], CNN_CHANNELS[2], kernel_size=3, padding=1),\n            nn.BatchNorm2d(CNN_CHANNELS[2]),\n            nn.ReLU(inplace=True),\n            nn.MaxPool2d(2, 2),  # 28x28\n            \n            # Conv block 4\n            nn.Conv2d(CNN_CHANNELS[2], CNN_CHANNELS[3], kernel_size=3, padding=1),\n            nn.BatchNorm2d(CNN_CHANNELS[3]),\n            nn.ReLU(inplace=True),\n            nn.MaxPool2d(2, 2),  # 14x14\n            \n            nn.AdaptiveAvgPool2d((1, 1)),\n            nn.Flatten()\n        )\n        \n        # Classifier\n        self.classifier = nn.Sequential(\n            nn.Linear(CNN_CHANNELS[-1], CLASSIFIER_HIDDEN[0]),\n            nn.ReLU(inplace=True),\n            nn.Dropout(DROPOUT_RATES[0]),\n            nn.Linear(CLASSIFIER_HIDDEN[0], CLASSIFIER_HIDDEN[1]),\n            nn.ReLU(inplace=True),\n            nn.Dropout(DROPOUT_RATES[1]),\n            nn.Linear(CLASSIFIER_HIDDEN[1], num_outputs)\n        )\n        \n    def forward(self, image):\n        # Extract image features\n        feat = self.features(image)\n        \n        # Final prediction\n        output = self.classifier(feat)\n        \n        return output\n\nmodel = SimpleCNN(num_outputs=5).to(device)\nprint(f\"Model parameters: {sum(p.numel() for p in model.parameters()):,}\")\nprint(f\"\\nModel outputs 5 predictions: {', '.join(TARGET_NAMES)}\")\nprint(model)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-02T03:25:36.107505Z","iopub.execute_input":"2025-11-02T03:25:36.107774Z","iopub.status.idle":"2025-11-02T03:25:36.324299Z","shell.execute_reply.started":"2025-11-02T03:25:36.107747Z","shell.execute_reply":"2025-11-02T03:25:36.323721Z"}},"outputs":[],"execution_count":null},{"id":"6beza4xjize","cell_type":"code","source":"# Competition metric: Weighted R² score\ndef competition_score(y_true, y_pred):\n    \"\"\"\n    Calculate the competition metric: weighted average of R² scores\n    Weights defined in TARGET_WEIGHTS config\n    \"\"\"\n    weights = np.array(WEIGHTS_ARRAY)\n    \n    r2_scores = []\n    for i in range(y_true.shape[1]):\n        r2 = r2_score(y_true[:, i], y_pred[:, i])\n        r2_scores.append(r2)\n    \n    r2_scores = np.array(r2_scores)\n    weighted_score = np.sum(weights * r2_scores)\n    \n    return weighted_score, r2_scores\n\n# Weighted MSE Loss\nclass WeightedMSELoss(nn.Module):\n    def __init__(self):\n        super(WeightedMSELoss, self).__init__()\n        # Weights based on competition metric importance\n        self.weights = torch.tensor(WEIGHTS_ARRAY).to(device)\n        \n    def forward(self, predictions, targets):\n        mse_per_target = torch.mean((predictions - targets) ** 2, dim=0)\n        weighted_mse = torch.sum(self.weights * mse_per_target)\n        return weighted_mse\n\n# Training setup\ncriterion = WeightedMSELoss()\noptimizer = optim.Adam(model.parameters(), lr=LEARNING_RATE)\nscheduler = optim.lr_scheduler.ReduceLROnPlateau(\n    optimizer, \n    mode='max', \n    factor=SCHEDULER_FACTOR, \n    patience=SCHEDULER_PATIENCE\n)\n\ntrain_losses = []\nval_losses = []\ntrain_scores = []  # Competition scores\nval_scores = []    # Competition scores\n\nprint(\"Using Weighted MSE Loss with competition weights:\")\nfor name, weight in TARGET_WEIGHTS.items():\n    print(f\"  {name}: {weight}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-02T03:25:36.324989Z","iopub.execute_input":"2025-11-02T03:25:36.325201Z","iopub.status.idle":"2025-11-02T03:25:36.333349Z","shell.execute_reply.started":"2025-11-02T03:25:36.325185Z","shell.execute_reply":"2025-11-02T03:25:36.332778Z"}},"outputs":[],"execution_count":null},{"id":"3nqxaqyrt6y","cell_type":"code","source":"# Training loop with competition metric tracking\nfor epoch in range(NUM_EPOCHS):\n    # Training phase\n    model.train()\n    train_loss = 0.0\n    train_preds_epoch = []\n    train_targets_epoch = []\n    \n    for images, targets in tqdm(train_loader, desc=f'Epoch {epoch+1}/{NUM_EPOCHS} [Train]'):\n        images = images.to(device)\n        targets = targets.to(device)\n        \n        optimizer.zero_grad()\n        outputs = model(images)\n        loss = criterion(outputs, targets)\n        loss.backward()\n        optimizer.step()\n        \n        train_loss += loss.item() * images.size(0)\n        train_preds_epoch.append(outputs.detach().cpu().numpy())\n        train_targets_epoch.append(targets.cpu().numpy())\n    \n    train_loss = train_loss / len(train_dataset)\n    train_losses.append(train_loss)\n    \n    # Calculate training competition score\n    train_preds_epoch = np.vstack(train_preds_epoch)\n    train_targets_epoch = np.vstack(train_targets_epoch)\n    train_comp_score, _ = competition_score(train_targets_epoch, train_preds_epoch)\n    train_scores.append(train_comp_score)\n    \n    # Validation phase\n    model.eval()\n    val_loss = 0.0\n    val_preds_epoch = []\n    val_targets_epoch = []\n    \n    with torch.no_grad():\n        for images, targets in tqdm(val_loader, desc=f'Epoch {epoch+1}/{NUM_EPOCHS} [Val]'):\n            images = images.to(device)\n            targets = targets.to(device)\n            \n            outputs = model(images)\n            loss = criterion(outputs, targets)\n            \n            val_loss += loss.item() * images.size(0)\n            val_preds_epoch.append(outputs.cpu().numpy())\n            val_targets_epoch.append(targets.cpu().numpy())\n    \n    val_loss = val_loss / len(val_dataset)\n    val_losses.append(val_loss)\n    \n    # Calculate validation competition score\n    val_preds_epoch = np.vstack(val_preds_epoch)\n    val_targets_epoch = np.vstack(val_targets_epoch)\n    val_comp_score, _ = competition_score(val_targets_epoch, val_preds_epoch)\n    val_scores.append(val_comp_score)\n    \n    print(f'Epoch {epoch+1}/{NUM_EPOCHS} - Train Loss: {train_loss:.4f}, Val Loss: {val_loss:.4f} | Train Score: {train_comp_score:.4f}, Val Score: {val_comp_score:.4f}')\n    \n    # Scheduler based on validation competition score\n    scheduler.step(val_comp_score)\n\nprint(\"\\nTraining completed!\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-02T03:25:36.333960Z","iopub.execute_input":"2025-11-02T03:25:36.334178Z","iopub.status.idle":"2025-11-02T03:34:54.639505Z","shell.execute_reply.started":"2025-11-02T03:25:36.334161Z","shell.execute_reply":"2025-11-02T03:34:54.638624Z"}},"outputs":[],"execution_count":null},{"id":"xei862g19b","cell_type":"code","source":"# Plot training curves\nfig, (ax1, ax2) = plt.subplots(1, 2, figsize=(15, 5))\n\n# Loss curves\nax1.plot(train_losses, label='Train Loss', marker='o', markersize=3)\nax1.plot(val_losses, label='Validation Loss', marker='o', markersize=3)\nax1.set_xlabel('Epoch')\nax1.set_ylabel('Weighted MSE Loss')\nax1.set_title('Training and Validation Loss')\nax1.legend()\nax1.grid(True, alpha=0.3)\n\n# Competition score curves\nax2.plot(train_scores, label='Train Score', marker='o', markersize=3)\nax2.plot(val_scores, label='Validation Score', marker='o', markersize=3)\nax2.set_xlabel('Epoch')\nax2.set_ylabel('Competition Score (Weighted R²)')\nax2.set_title('Competition Metric Over Epochs')\nax2.legend()\nax2.grid(True, alpha=0.3)\n\nplt.tight_layout()\nplt.show()\n\nprint(f\"\\nBest Validation Score: {max(val_scores):.4f} at epoch {np.argmax(val_scores)+1}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-02T03:34:54.640481Z","iopub.execute_input":"2025-11-02T03:34:54.640764Z","iopub.status.idle":"2025-11-02T03:34:55.094806Z","shell.execute_reply.started":"2025-11-02T03:34:54.640739Z","shell.execute_reply":"2025-11-02T03:34:55.094022Z"}},"outputs":[],"execution_count":null},{"id":"h2p4gih6gw","cell_type":"code","source":"# Evaluate on train and validation sets with competition metrics\ndef evaluate_model(model, dataloader, dataset_name):\n    model.eval()\n    all_preds = []\n    all_targets = []\n    \n    with torch.no_grad():\n        for images, targets in dataloader:\n            images = images.to(device)\n            outputs = model(images).cpu().numpy()\n            all_preds.append(outputs)\n            all_targets.append(targets.numpy())\n    \n    all_preds = np.vstack(all_preds)\n    all_targets = np.vstack(all_targets)\n    \n    # Denormalize predictions and targets back to original scale\n    all_preds = target_scaler.inverse_transform(all_preds)\n    all_targets = target_scaler.inverse_transform(all_targets)\n    \n    # Calculate competition score\n    comp_score, r2_per_target = competition_score(all_targets, all_preds)\n    \n    print(f\"\\n{dataset_name} Set Metrics:\")\n    print(\"=\"*80)\n    print(f\"{'COMPETITION SCORE (Weighted R²):':<40} {comp_score:>10.4f}\")\n    print(\"=\"*80)\n    \n    for i, name in enumerate(TARGET_NAMES):\n        mae = mean_absolute_error(all_targets[:, i], all_preds[:, i])\n        rmse = np.sqrt(mean_squared_error(all_targets[:, i], all_preds[:, i]))\n        r2 = r2_per_target[i]\n        weight = WEIGHTS_ARRAY[i]\n        \n        print(f\"{name:15s} (w={weight:.1f}) - MAE: {mae:8.4f}, RMSE: {rmse:8.4f}, R²: {r2:7.4f}\")\n    \n    # Overall metrics (unweighted)\n    overall_mae = mean_absolute_error(all_targets.flatten(), all_preds.flatten())\n    overall_rmse = np.sqrt(mean_squared_error(all_targets.flatten(), all_preds.flatten()))\n    overall_r2 = r2_score(all_targets.flatten(), all_preds.flatten())\n    \n    print(\"-\"*80)\n    print(f\"{'Overall (unweighted)':15s}     - MAE: {overall_mae:8.4f}, RMSE: {overall_rmse:8.4f}, R²: {overall_r2:7.4f}\")\n    print(\"=\"*80)\n    \n    return all_preds, all_targets\n\n# Evaluate on both sets\ntrain_preds, train_targets = evaluate_model(model, train_loader, \"Training\")\nval_preds, val_targets = evaluate_model(model, val_loader, \"Validation\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-02T03:34:55.095527Z","iopub.execute_input":"2025-11-02T03:34:55.095724Z","iopub.status.idle":"2025-11-02T03:35:06.047811Z","shell.execute_reply.started":"2025-11-02T03:34:55.095708Z","shell.execute_reply":"2025-11-02T03:35:06.046934Z"}},"outputs":[],"execution_count":null},{"id":"5beu2q2z8r","cell_type":"code","source":"# Visualize predictions vs actual values\nfig, axes = plt.subplots(2, 3, figsize=(18, 10))\naxes = axes.flatten()\n\nfor i, name in enumerate(TARGET_NAMES):\n    ax = axes[i]\n    \n    # Plot validation set\n    ax.scatter(val_targets[:, i], val_preds[:, i], alpha=0.5, s=20, label='Validation')\n    \n    # Plot perfect prediction line\n    min_val = min(val_targets[:, i].min(), val_preds[:, i].min())\n    max_val = max(val_targets[:, i].max(), val_preds[:, i].max())\n    ax.plot([min_val, max_val], [min_val, max_val], 'r--', lw=2, label='Perfect Prediction')\n    \n    ax.set_xlabel('Actual Value (g)')\n    ax.set_ylabel('Predicted Value (g)')\n    ax.set_title(f'{name} - Predictions vs Actual')\n    ax.legend()\n    ax.grid(True, alpha=0.3)\n\n# Hide the 6th subplot\naxes[5].set_visible(False)\n\nplt.tight_layout()\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-02T03:35:06.048951Z","iopub.execute_input":"2025-11-02T03:35:06.049561Z","iopub.status.idle":"2025-11-02T03:35:07.237468Z","shell.execute_reply.started":"2025-11-02T03:35:06.049537Z","shell.execute_reply":"2025-11-02T03:35:07.236442Z"}},"outputs":[],"execution_count":null},{"id":"j1twilk1f39","cell_type":"code","source":"# Generate submission for test set\nprint(\"=\"*80)\nprint(\"GENERATING TEST PREDICTIONS FOR SUBMISSION\")\nprint(\"=\"*80)\n\n# Load test data\ntest_df = pd.read_csv(TEST_CSV)\nprint(f\"\\nTest samples: {len(test_df)}\")\nprint(f\"Test data columns: {list(test_df.columns)}\")\nprint(f\"\\nSample test data:\")\nprint(test_df.head())\n\n# Get unique test images (since each image has 5 rows in test.csv)\ntest_images = test_df[['image_path']].drop_duplicates().reset_index(drop=True)\nprint(f\"\\nUnique test images: {len(test_images)}\")\n\n# Create test dataset (without targets, image only)\nclass TestDataset(Dataset):\n    def __init__(self, df, data_dir=DATA_DIR, transform=None):\n        self.df = df.reset_index(drop=True)\n        self.data_dir = data_dir\n        self.transform = transform\n        \n    def __len__(self):\n        return len(self.df)\n    \n    def __getitem__(self, idx):\n        img_path = os.path.join(self.data_dir, self.df.loc[idx, 'image_path'])\n        image = Image.open(img_path).convert('RGB')\n        \n        if self.transform:\n            image = self.transform(image)\n        \n        return image\n\n# Create test dataset and loader\ntest_dataset = TestDataset(test_images, transform=transform)\ntest_loader = DataLoader(test_dataset, batch_size=BATCH_SIZE, shuffle=False, num_workers=NUM_WORKERS)\n\n# Make predictions\nprint(f\"\\n\" + \"=\"*80)\nprint(\"MAKING PREDICTIONS...\")\nprint(\"=\"*80)\n\nmodel.eval()\ntest_preds = []\n\nwith torch.no_grad():\n    for images in test_loader:\n        images = images.to(device)\n        outputs = model(images).cpu().numpy()\n        test_preds.append(outputs)\n\ntest_preds = np.vstack(test_preds)\n\n# Denormalize predictions to original scale\ntest_preds = target_scaler.inverse_transform(test_preds)\n\nprint(f\"\\nPredictions shape: {test_preds.shape}\")\nprint(f\"Predictions for test images:\")\nfor i, img_path in enumerate(test_images['image_path']):\n    print(f\"\\n{img_path}:\")\n    for j, target_name in enumerate(TARGET_NAMES):\n        print(f\"  {target_name}: {test_preds[i, j]:.4f}\")\n\n# Create submission dataframe matching the required format\nprint(f\"\\n\" + \"=\"*80)\nprint(\"CREATING SUBMISSION FILE...\")\nprint(\"=\"*80)\n\nsubmission_data = []\n\nfor idx, row in test_df.iterrows():\n    sample_id = row['sample_id']\n    target_name = row['target_name']\n    \n    # Find which image this belongs to\n    img_idx = test_images[test_images['image_path'] == row['image_path']].index[0]\n    \n    # Find which target column\n    target_idx = TARGET_NAMES.index(target_name)\n    \n    # Get prediction\n    pred_value = test_preds[img_idx, target_idx]\n    \n    submission_data.append({\n        'sample_id': sample_id,\n        'target': pred_value\n    })\n\nsubmission_df = pd.DataFrame(submission_data)\n\n# Save submission\nsubmission_path = 'submission.csv'\nsubmission_df.to_csv(submission_path, index=False)\n\nprint(f\"\\nSUBMISSION SAVED: {submission_path}\")\nprint(\"=\"*80)\nprint(f\"\\nSubmission file preview:\")\nprint(submission_df.head())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-02T03:35:07.238327Z","iopub.execute_input":"2025-11-02T03:35:07.238550Z","iopub.status.idle":"2025-11-02T03:35:07.499431Z","shell.execute_reply.started":"2025-11-02T03:35:07.238532Z","shell.execute_reply":"2025-11-02T03:35:07.498516Z"}},"outputs":[],"execution_count":null}]}