{"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,"isSourceIdPinned":false,"sourceType":"competition"},{"sourceId":13747888,"sourceType":"datasetVersion","datasetId":8748054},{"sourceId":14302290,"sourceType":"datasetVersion","datasetId":9129968}],"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"# Install FastAI\n\nInstall the FastAI library and its dependencies using pip.","metadata":{}},{"cell_type":"code","source":"# Environment flag\nimport os\nkaggle = os.path.exists('/kaggle/input/csiro-biomass/')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-05T02:54:22.942415Z","iopub.execute_input":"2026-01-05T02:54:22.943144Z","iopub.status.idle":"2026-01-05T02:54:22.992344Z","shell.execute_reply.started":"2026-01-05T02:54:22.943114Z","shell.execute_reply":"2026-01-05T02:54:22.991460Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"#!pip install fastai","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-05T02:54:22.993667Z","iopub.execute_input":"2026-01-05T02:54:22.994029Z","iopub.status.idle":"2026-01-05T02:54:23.041625Z","shell.execute_reply.started":"2026-01-05T02:54:22.994002Z","shell.execute_reply":"2026-01-05T02:54:23.040859Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# === Fastai 2.8.5 Kaggle-Safe Offline Install (One Cell) ===\nimport sys\n\nif os.path.exists(\"/kaggle\"):\n    # Copy fastai source to a writable directory\n    !cp -r /kaggle/input/fastai-2-8-5/fastai-2.8.5 /kaggle/working/fastai_src\n\n    # Use the local source directly (editable)\n    sys.path.insert(0, \"/kaggle/working/fastai_src\")\n\n    # Auto-reload edits\n    %load_ext autoreload\n    %autoreload 2\n\n    print(\"Fastai 2.8.5 ready for editable development on Kaggle.\")\nelse:\n    print(\"Not on Kaggle, skipping FastAI setup.\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-05T02:54:23.042491Z","iopub.execute_input":"2026-01-05T02:54:23.042995Z","iopub.status.idle":"2026-01-05T02:54:25.049495Z","shell.execute_reply.started":"2026-01-05T02:54:23.042971Z","shell.execute_reply":"2026-01-05T02:54:25.048610Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from fastai.vision.all import *\nfrom fastai.metrics import R2Score\nfrom fastai.tabular.all import *\nimport pandas as pd\n\nimport warnings\nfrom torchvision.models import ConvNeXt_Tiny_Weights, ConvNeXt_Base_Weights, ConvNeXt_Large_Weights\nfrom torchvision.models import convnext_tiny, convnext_large\n\n# Suppress torchvision warnings\nwarnings.filterwarnings(\"ignore\", category=UserWarning, module=\"torchvision\")\n\n# Set PyTorch CUDA memory management\nos.environ['PYTORCH_CUDA_ALLOC_CONF'] = 'expandable_segments:True'\n\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-05T02:54:25.051524Z","iopub.execute_input":"2026-01-05T02:54:25.051751Z","iopub.status.idle":"2026-01-05T02:54:25.110017Z","shell.execute_reply.started":"2026-01-05T02:54:25.051730Z","shell.execute_reply":"2026-01-05T02:54:25.109101Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"\nimport fastai\nprint(fastai.__version__)\n\n# Example: check torch and GPU\nimport torch\nprint(torch.__version__, torch.cuda.is_available())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-05T02:54:25.110933Z","iopub.execute_input":"2026-01-05T02:54:25.111173Z","iopub.status.idle":"2026-01-05T02:54:25.159179Z","shell.execute_reply.started":"2026-01-05T02:54:25.111153Z","shell.execute_reply":"2026-01-05T02:54:25.158427Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Import Libraries\n\nImport necessary libraries including FastAI's vision module.","metadata":{}},{"cell_type":"code","source":"from fastai.vision.all import *\nfrom fastai.metrics import R2Score\nfrom fastai.tabular.all import *\nimport pandas as pd\nimport os\nimport warnings\nfrom torchvision.models import ConvNeXt_Tiny_Weights, ConvNeXt_Base_Weights, ConvNeXt_Large_Weights\nfrom torchvision.models import convnext_tiny, convnext_large\n\n# Suppress torchvision warnings\nwarnings.filterwarnings(\"ignore\", category=UserWarning, module=\"torchvision\")\n\n# Set PyTorch CUDA memory management\nos.environ['PYTORCH_CUDA_ALLOC_CONF'] = 'expandable_segments:True'\n\n# Environment flag\nkaggle = os.path.exists('/kaggle/input/csiro-biomass/')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-05T02:54:25.159948Z","iopub.execute_input":"2026-01-05T02:54:25.160153Z","iopub.status.idle":"2026-01-05T02:54:25.206456Z","shell.execute_reply.started":"2026-01-05T02:54:25.160138Z","shell.execute_reply":"2026-01-05T02:54:25.205703Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Load Dataset\n\nLoad the biomass dataset from the csiro-biomass folder.","metadata":{}},{"cell_type":"code","source":"if kaggle:\n    data_dir = \"/kaggle/input/csiro-biomass/\"\nelse:\n    data_dir = \"./csiro-biomass/\"\n\npath = Path(data_dir)\ndf = pd.read_csv(path/'train.csv')\n\n# Convert Sampling_Date to day of year for better use as feature\ndf['Sampling_Date'] = pd.to_datetime(df['Sampling_Date'])\ndf['DayOfYear'] = df['Sampling_Date'].dt.dayofyear\n\n# Pivot to have one row per image with all targets\ndf_pivot = df.pivot(index='image_path', columns='target_name', values='target').reset_index()\n\n# Define target columns\ntarget_cols = ['Dry_Green_g', 'Dry_Dead_g', 'Dry_Clover_g', 'GDM_g', 'Dry_Total_g']\n\n# Get metadata per image\nmetadata_cols = ['State', 'Species', 'Pre_GSHH_NDVI', 'Height_Ave_cm', 'DayOfYear']\nmetadata_df = df.groupby('image_path')[metadata_cols].first().reset_index()\n\n# Merge metadata with pivoted df\ndf_pivot = df_pivot.merge(metadata_df, on='image_path')\n\n# Make image_path full path\ndf_pivot['image_path'] = df_pivot['image_path'].apply(lambda x: str(path / x))\n\n# Preprocess tabular data\nprocs = [Categorify, Normalize]\ncat_names = ['State', 'Species']\ncont_names = ['Pre_GSHH_NDVI', 'Height_Ave_cm', 'DayOfYear']\nto = TabularPandas(df_pivot, procs, cat_names=cat_names, cont_names=cont_names)\ndf_pivot = to.items\n\n# Drop rows with missing targets\ndf_pivot = df_pivot.dropna(subset=target_cols)\n\n# Normalize each target\ntarget_means = df_pivot[target_cols].mean()\ntarget_stds = df_pivot[target_cols].std()\ndf_pivot[target_cols] = (df_pivot[target_cols] - target_means) / target_stds\n\nprint(df_pivot.head())\nprint(f\"Dataset size after dropping NaN: {len(df_pivot)}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-05T02:54:25.207311Z","iopub.execute_input":"2026-01-05T02:54:25.207583Z","iopub.status.idle":"2026-01-05T02:54:25.302652Z","shell.execute_reply.started":"2026-01-05T02:54:25.207561Z","shell.execute_reply":"2026-01-05T02:54:25.301889Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"df.head()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-05T02:54:25.304208Z","iopub.execute_input":"2026-01-05T02:54:25.304411Z","iopub.status.idle":"2026-01-05T02:54:25.361490Z","shell.execute_reply.started":"2026-01-05T02:54:25.304396Z","shell.execute_reply":"2026-01-05T02:54:25.360561Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Create DataLoaders\n\nCreate DataLoaders for training and validation using FastAI's data loading utilities.","metadata":{}},{"cell_type":"code","source":"def encode_tabular(r):\n    state = r['State']\n    species = r['Species']\n    state_code = to.classes['State'].o2i.get(state, 0)\n    species_code = to.classes['Species'].o2i.get(species, 0)\n    return torch.tensor([state_code, species_code, r['Pre_GSHH_NDVI'], r['Height_Ave_cm'], r['DayOfYear']], dtype=torch.float)\n\ndef get_targets(x): return x[target_cols].values.astype(float)\n\ndblock = DataBlock(blocks=(ImageBlock, TransformBlock(), TransformBlock(type_tfms=[lambda x: tensor(x).float()])),\n                   get_x=[ColReader('image_path'), encode_tabular], get_y=get_targets, item_tfms=Resize(192), splitter=RandomSplitter())\n\nbs = 4\ndls = dblock.dataloaders(df_pivot, bs=bs)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-05T02:54:25.362494Z","iopub.execute_input":"2026-01-05T02:54:25.362747Z","iopub.status.idle":"2026-01-05T02:54:25.681585Z","shell.execute_reply.started":"2026-01-05T02:54:25.362726Z","shell.execute_reply":"2026-01-05T02:54:25.680708Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Show labeled random examples from each target\nimport random\nimport torch\nimport matplotlib.pyplot as plt\nimport numpy as np\n\nnum_examples = 5\nindices = random.sample(range(len(dls.train_ds)), num_examples)\n\nfor idx in indices:\n    img, tab, targ = dls.train_ds[idx]\n    targ_orig = targ * torch.tensor(target_stds.values) + torch.tensor(target_means.values)\n    \n    # Get additional metadata\n    img_path = dls.train_ds.items.iloc[idx]['image_path']\n    img_path_rel = Path(img_path).relative_to(path).as_posix()\n    row = df[df['image_path'] == img_path_rel].iloc[0]\n    species = row['Species']\n    state = row['State']\n    height = row['Height_Ave_cm']\n    \n    # Display image with label below\n    fig, ax = plt.subplots()\n    img_np = np.array(img)\n    ax.imshow(img_np)\n    ax.axis('off')\n    label = f\"Example {idx}: Species: {species}, State: {state}, Height: {height:.1f} cm\\nTargets: {dict(zip(target_cols, targ_orig.tolist()))}\"\n    plt.figtext(0.5, 0.01, label, ha='center', fontsize=8, wrap=True)\n    plt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-05T02:54:25.684518Z","iopub.execute_input":"2026-01-05T02:54:25.684758Z","iopub.status.idle":"2026-01-05T02:54:28.378800Z","shell.execute_reply.started":"2026-01-05T02:54:25.684738Z","shell.execute_reply":"2026-01-05T02:54:28.378046Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Define custom multi-modal model\nclass MultiModalModel(Module):\n    def __init__(self, cnn_body, tabular_model, emb_szs, n_cont):\n        self.cnn_body = cnn_body\n        self.pool = AdaptiveConcatPool2d()\n        self.tabular_model = tabular_model\n        # Get output sizes\n        with torch.no_grad():\n            dummy_img = torch.randn(1, 3, 224, 224)\n            cnn_out = self.pool(self.cnn_body(dummy_img)).flatten(1).shape[1]\n            dummy_cat = torch.zeros(1, len(emb_szs)).long()\n            dummy_cont = torch.randn(1, n_cont)\n            self.tabular_model.eval()\n            tab_out = self.tabular_model(dummy_cat, dummy_cont).shape[1]\n            self.tabular_model.train()\n        self.head = nn.Linear(cnn_out + tab_out, 5)\n    \n    def forward(self, img, tab):\n        if not isinstance(tab, torch.Tensor):\n            tab = torch.tensor(tab)\n        # Split tab into cat and cont\n        n_cat = len(cat_names)\n        cat = tab[:, :n_cat].long()\n        cont = tab[:, n_cat:]\n        img_feat = self.pool(self.cnn_body(img)).flatten(1)\n        tab_feat = self.tabular_model(cat, cont)\n        return self.head(torch.cat([img_feat, tab_feat], dim=1))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-05T02:54:28.379652Z","iopub.execute_input":"2026-01-05T02:54:28.380404Z","iopub.status.idle":"2026-01-05T02:54:28.432937Z","shell.execute_reply.started":"2026-01-05T02:54:28.380382Z","shell.execute_reply":"2026-01-05T02:54:28.432158Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Train Model\n\nInitialize a learner with a pre-trained model and fine-tune it on the dataset.","metadata":{}},{"cell_type":"code","source":"if kaggle:\n    # Load custom pretrained weights from CSIRO ConvNeXt Base\n    model = convnext_base(weights=None)\n    weights_path = \"/kaggle/input/convnext-base-weights/convnext_base-6075fbad.pth\"\n    model.load_state_dict(torch.load(weights_path))\n    print(\"Using ConvNeXt base with CSIRO pretrained weights on Kaggle\")\nelse:\n    model = convnext_base(weights=None)\n    weights_path = \"weights/convnext_base-6075fbad.pth\"\n    if os.path.exists(weights_path):\n        model.load_state_dict(torch.load(weights_path))\n        print(\"Using ConvNeXt base with local pretrained weights\")\n    else:\n        print(\"Using ConvNeXt base with random initialization locally\")\n\n# Create CNN body\ncnn_body = create_body(model, pretrained=False)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-05T02:54:28.433831Z","iopub.execute_input":"2026-01-05T02:54:28.434111Z","iopub.status.idle":"2026-01-05T02:54:36.239052Z","shell.execute_reply.started":"2026-01-05T02:54:28.434086Z","shell.execute_reply":"2026-01-05T02:54:36.238239Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Find optimal learning rate\n# learn.lr_find()\n# lr_min = learn.recorder.lr_find.lr_min\n# lr_steep = learn.recorder.lr_find.lr_steep\n# print(f\"Minimum/10: {lr_min:.2e}, Steepest point: {lr_steep:.2e}\")\nlr_min = 1e-4\nprint(f\"Using fixed lr: {lr_min:.2e}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-05T02:54:36.239735Z","iopub.execute_input":"2026-01-05T02:54:36.239950Z","iopub.status.idle":"2026-01-05T02:54:36.291210Z","shell.execute_reply.started":"2026-01-05T02:54:36.239933Z","shell.execute_reply":"2026-01-05T02:54:36.290362Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Create tabular model\nemb_szs = get_emb_sz(to)\nn_cont = len(cont_names)\ntabular_model = TabularModel(emb_szs, n_cont, layers=[200,100], out_sz=50)\n\n# Create multi-modal model\nmodel = MultiModalModel(cnn_body, tabular_model, emb_szs, n_cont)\n\n# Create learner\nlearn = Learner(dls, model, loss_func=MSELossFlat(), metrics=[R2Score()])","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-05T02:54:36.292050Z","iopub.execute_input":"2026-01-05T02:54:36.292291Z","iopub.status.idle":"2026-01-05T02:54:36.826442Z","shell.execute_reply.started":"2026-01-05T02:54:36.292273Z","shell.execute_reply":"2026-01-05T02:54:36.825595Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Fine-tune with more epochs and optimal learning rate\ntorch.cuda.empty_cache()\nlearn = learn.to_fp16()\nlearn.fine_tune(50, base_lr=lr_min)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-05T02:54:36.827244Z","iopub.execute_input":"2026-01-05T02:54:36.827450Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Plot training loss\nlearn.recorder.plot_loss()","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Detailed Training Loss Plot\nplt.figure(figsize=(10, 6))\nplt.plot(learn.recorder.lrs, learn.recorder.losses, label='Training Loss')\nif hasattr(learn.recorder, 'val_losses') and learn.recorder.val_losses:\n    plt.plot(learn.recorder.lrs, learn.recorder.val_losses, label='Validation Loss')\nplt.xlabel('Learning Rate')\nplt.ylabel('Loss')\nplt.title('Training and Validation Loss vs Learning Rate')\nplt.legend()\nplt.show()","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Evaluate Model\n\nEvaluate the model's performance using metrics like R2.","metadata":{}},{"cell_type":"code","source":"# For regression, plot_top_losses is not implemented. Instead, let's validate the model.\nlearn.validate()","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Visualize Predictions\n\nSince this is a regression task, we'll create a scatter plot of predicted vs. actual biomass values and show examples of good and bad predictions.","metadata":{}},{"cell_type":"code","source":"import matplotlib.pyplot as plt\nimport torch\n\n# Get predictions on validation set\npreds, targs = learn.get_preds()\n\n# Denormalize predictions and targets\ntargs_orig = targs * torch.tensor(target_stds.values) + torch.tensor(target_means.values)\npreds_orig = preds * torch.tensor(target_stds.values) + torch.tensor(target_means.values)\n\n# Plot for each target\nfig, axes = plt.subplots(1, 5, figsize=(25, 5))\nfor i, target in enumerate(target_cols):\n    axes[i].scatter(targs_orig[:, i], preds_orig[:, i], alpha=0.5)\n    axes[i].plot([targs_orig[:, i].min(), targs_orig[:, i].max()], [targs_orig[:, i].min(), targs_orig[:, i].max()], 'r--')\n    axes[i].set_xlabel(f'Actual {target} (g)')\n    axes[i].set_ylabel(f'Predicted {target} (g)')\n    axes[i].set_title(f'Predicted vs Actual {target}')\nplt.tight_layout()\nplt.show()\n\n# Calculate errors and show best/worst for each target\nfor i, target in enumerate(target_cols):\n    errors = (preds_orig[:, i] - targs_orig[:, i]).abs()\n    \n    # Get indices of best and worst predictions\n    best_idx = errors.argsort()[:3]  # Top 3 best\n    worst_idx = errors.argsort(descending=True)[:3]  # Top 3 worst\n    \n    print(f\"\\nBest Predictions for {target}:\")\n    for j in best_idx:\n        idx = j.item()\n        img_path = dls.valid_ds.items.iloc[idx]['image_path']\n        actual = targs_orig[idx, i].item()\n        pred = preds_orig[idx, i].item()\n        print(f\"Image: {img_path}, Actual: {actual:.2f}, Predicted: {pred:.2f}, Error: {abs(pred - actual):.2f}\")\n    \n    print(f\"\\nWorst Predictions for {target}:\")\n    for j in worst_idx:\n        idx = j.item()\n        img_path = dls.valid_ds.items.iloc[idx]['image_path']\n        actual = targs_orig[idx, i].item()\n        pred = preds_orig[idx, i].item()\n        print(f\"Image: {img_path}, Actual: {actual:.2f}, Predicted: {pred:.2f}, Error: {abs(pred - actual):.2f}\")","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Confusion Matrix equivalent for regression: Correlation Matrix\nimport seaborn as sns\n\n# Compute correlation matrix between actual and predicted\ncorr_matrix = np.corrcoef(targs_orig.T, preds_orig.T)[:5, 5:]\ncorr_df = pd.DataFrame(corr_matrix, index=target_cols, columns=[f'Pred_{col}' for col in target_cols])\n\nplt.figure(figsize=(8, 6))\nsns.heatmap(corr_df, annot=True, cmap='coolwarm', vmin=-1, vmax=1)\nplt.title('Correlation between Actual and Predicted Targets')\nplt.show()\n\nprint(\"Correlation Matrix:\")\nprint(corr_df)","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"print(f\"preds.shape: {preds.shape}\")\nprint(f\"targs.shape: {targs.shape}\")\nprint(f\"target_means: {target_means}\")\nprint(f\"target_stds: {target_stds}\")","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Calculate estimated leaderboard score (weighted R² on validation set)\nimport torch\n\n# Denormalize\ntargs_orig = targs * torch.tensor(target_stds.values) + torch.tensor(target_means.values)\npreds_orig = preds * torch.tensor(target_stds.values) + torch.tensor(target_means.values)\n\n# Weights for each target\nweights_dict = {'Dry_Green_g': 0.1, 'Dry_Dead_g': 0.1, 'Dry_Clover_g': 0.1, 'GDM_g': 0.2, 'Dry_Total_g': 0.5}\n\n# Compute R² for each target and weight them\nweighted_r2 = 0.0\nfor i, target in enumerate(target_cols):\n    targs_i = targs_orig[:, i]\n    preds_i = preds_orig[:, i]\n    mean_i = targs_i.mean()\n    SS_res = ((targs_i - preds_i)**2).sum()\n    SS_tot = ((targs_i - mean_i)**2).sum()\n    r2_i = 1 - SS_res / SS_tot\n    weight = weights_dict[target]\n    weighted_r2 += weight * r2_i\n    print(f\"{target}: R² = {r2_i:.4f}, weight = {weight}\")\n\nprint(f\"\\nEstimated Leaderboard Score (Weighted R²): {weighted_r2:.4f}\")","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import pathlib\ntest_df = pd.read_csv(path/'test.csv')\ntest_df['image_path'] = test_df['image_path'].apply(lambda x: pathlib.Path(path) / x)\n\n# Create dummy metadata for test set using training means, unique image_paths\nunique_images = test_df['image_path'].unique()\nmetadata_df_test = pd.DataFrame({\n    'image_path': unique_images,\n    'Sampling_Date': pd.to_datetime('2020-01-01'),  # dummy\n    'State': list(to.classes['State'])[0],  # use first known category\n    'Species': list(to.classes['Species'])[0],  # use first known category\n    'Pre_GSHH_NDVI': df['Pre_GSHH_NDVI'].mean(),  # use training mean\n    'Height_Ave_cm': df['Height_Ave_cm'].mean(),  # use training mean\n    'DayOfYear': pd.to_datetime('2020-01-01').dayofyear  # dummy\n})\n\ntest_pivot = test_df[['image_path']].copy()\ntest_pivot = test_pivot.merge(metadata_df_test, on='image_path')\n\nprint(f\"Test DF size: {test_df.shape}\")\nprint(f\"Test pivot size: {test_pivot.shape}\")\n\ntest_dl = dls.test_dl(test_pivot)\ntest_preds, _ = learn.get_preds(dl=test_dl)\n\n# Denormalize predictions\ntest_preds_orig = test_preds * torch.tensor(target_stds.values) + torch.tensor(target_means.values)\n\n# Ensure non-negative predictions (biomass can't be negative)\ntest_preds_orig = torch.clamp(test_preds_orig, min=0)\n\n# Create submission\nsubmission = test_df[['sample_id']].copy()\nsubmission.columns = ['sample_id']\nsubmission['target'] = 0.0\nfor idx, row in test_df.iterrows():\n    j = target_cols.index(row['target_name'])\n    submission.loc[idx, 'target'] = test_preds_orig[idx, j].item()\n\nsubmission.to_csv('submission.csv', index=False)\n\nprint(\"Submission saved to submission.csv\")","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"submission.head(10)","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"submission.to_csv('submission.csv', index=False)\n\nprint(\"Submission saved to submission.csv\")","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Calculate estimated leaderboard score (weighted R² on validation set)\nimport torch\n\n# Denormalize\ntargs_orig = targs * torch.tensor(target_stds.values) + torch.tensor(target_means.values)\npreds_orig = preds * torch.tensor(target_stds.values) + torch.tensor(target_means.values)\n\n# Flatten all predictions and targets\nall_targs = targs_orig.flatten()\nall_preds = preds_orig.flatten()\n\n# Weights\nn_samples = targs.shape[0]\nweights_dict = {'Dry_Green_g': 0.1, 'Dry_Dead_g': 0.1, 'Dry_Clover_g': 0.1, 'GDM_g': 0.2, 'Dry_Total_g': 0.5}\nweights = []\nfor target in target_cols:\n    weights.extend([weights_dict[target]] * n_samples)\nweights = torch.tensor(weights)\n\n# Global weighted mean\nglobal_mean = (weights * all_targs).sum() / weights.sum()\n\n# SS_res and SS_tot\nSS_res = (weights * (all_targs - all_preds)**2).sum()\nSS_tot = (weights * (all_targs - global_mean)**2).sum()\n\nr2 = 1 - SS_res / SS_tot\nprint(f\"Estimated Leaderboard Score (Weighted R²): {r2:.4f}\")","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}