{
  "id": 671268,
  "title": "133rd Place Solution",
  "url": "/competitions/csiro-biomass/discussion/671268",
  "author_name": "hikaggler",
  "post_date": "2026-02-01T02:18:43.905000",
  "votes": 3,
  "comment_count": 0,
  "views": 0,
  "content": "<p>First of all, we would like to thank the hosts for organizing this competition.\nAlthough we are a team of beginners, thanks to the clear problem setting and the insightful discussions and code shared by other participants, we were able to complete the competition successfully.</p>\n<p>Here is a summary of our approach.</p>\n<h2>CV Strategy</h2>\n<p>Our biggest challenge throughout the competition was constructing a robust CV strategy.\nAfter much trial and error, we finally adopted <strong>Stratified Group K Fold</strong>. We grouped the data by <code>Sampling_Date</code> and stratified it by <code>State</code> to create 4 folds (Seed: 33).\nThis ensured that sampling dates did not leak between folds while maintaining a balanced representation of all states in every fold.</p>\n<h2>Data Processing &amp; Augmentation</h2>\n<h3>4-Patch Overlapping Strategy</h3>\n<p>Simply resizing the original 4000x2000 images to 512x512 resulted in the loss of fine texture details (e.g., leaf shapes). To address this, we cropped the images into 4 overlapping patches based on the following coordinates:</p>\n<pre><code>def __post_init__(self):\n    self.patch_positions = [\n        (0.00, 0.35),  # Patch 1\n        (0.20, 0.55),  # Patch 2\n        (0.45, 0.80),  # Patch 3\n        (0.65, 1.00),  # Patch 4\n    ]\n</code></pre>\n<h3>Augmentation</h3>\n<p>Since soil color and lighting conditions varied significantly by State, we applied the following augmentations to improve robustness against domain shifts:</p>\n<ul>\n<li><p>Strong ColorJitter (exp38): We significantly perturbed brightness, contrast, and saturation to encourage the model to learn features independent of specific color tones.</p></li>\n<li><p>Inpainting (Cleanup): Metadata and color bars appearing at the bottom of images were automatically masked and removed using inpainting. This prevented the model from overfitting to these artifacts.</p></li>\n</ul>\n<h2>Model Architecture</h2>\n<h3>Backbone</h3>\n<p>We used DINOv3 Large as our backbone. We used it as a Frozen Backbone because unfreezing it led to early overfitting.</p>\n<h3>Local Mamba Fusion</h3>\n<p>Feature maps extracted from the 4 patches were fed into a Local Mamba Block. We aggregated these features using Patch Attention to dynamically weight them, allowing the model to effectively capture the context across different patches.</p>\n<h3>Domain Shift Strategy</h3>\n<p>During the competition, we discovered that the inference error (Loss) for the NSW (New South Wales) state was more than double that of other states. We addressed this domain shift through a three-step approach:</p>\n<ul>\n<li><p>FiLM (exp35): We injected State information into the intermediate features as a conditioning mechanism.</p></li>\n<li><p>Multi-Scale MIL (exp51): In addition to the 4 patches, we added a global center-crop patch to perform Multiple Instance Learning across different scales.</p></li>\n<li><p>State-Guided Experts: We split the output head into experts for each of the 4 states. The final output was blended based on the predicted probability of the state.</p></li>\n</ul>\n<h3>Post-processing</h3>\n<p>During EDA, I observed that dry_dead_g was frequently zero for the state of WA. Based on this, I implemented a post-processing step using the state prediction model mentioned earlier. When the model predicted \"WA\" with the highest probability (wa_prob), I adjusted the predicted value of dry_dead_g using the following formula:</p>\n<pre><code>original_dead = pred[1]\nscaled_dead = original_dead * (1 - wa_prob)\n</code></pre>\n<p>While this approach improved the score on the Public LB, performance on the Private LB was actually better without this step.</p>\n<h2>Loss Function</h2>\n<p>Since the target variables (Green, Dead, Clover) had small variances, we adopted Huber Loss.</p>\n<h2>What didn't work</h2>\n<ul>\n<li><p>Leave-One-State-Out: We attempted to treat one state as completely unknown for validation, but the domain gap was too large, preventing the model from converging properly.</p></li>\n<li><p>Backbone Unfreezing: This caused severe overfitting, likely due to the limited number of samples.</p></li>\n<li><p>TTA: When TTA was applied, LB score dropped 0.04 in LB score. </p></li>\n<li><p>We conducted training using DINOv2; however, DINOv3 outperformed it in every method we tested. Therefore, we ultimately decided to use DINOv3 exclusively.</p></li>\n</ul>",
  "messages": [
    {
      "id": 3400137,
      "postDate": "2026-02-01T02:18:43.907Z",
      "content": "<p>First of all, we would like to thank the hosts for organizing this competition.\nAlthough we are a team of beginners, thanks to the clear problem setting and the insightful discussions and code shared by other participants, we were able to complete the competition successfully.</p>\n<p>Here is a summary of our approach.</p>\n<h2>CV Strategy</h2>\n<p>Our biggest challenge throughout the competition was constructing a robust CV strategy.\nAfter much trial and error, we finally adopted <strong>Stratified Group K Fold</strong>. We grouped the data by <code>Sampling_Date</code> and stratified it by <code>State</code> to create 4 folds (Seed: 33).\nThis ensured that sampling dates did not leak between folds while maintaining a balanced representation of all states in every fold.</p>\n<h2>Data Processing &amp; Augmentation</h2>\n<h3>4-Patch Overlapping Strategy</h3>\n<p>Simply resizing the original 4000x2000 images to 512x512 resulted in the loss of fine texture details (e.g., leaf shapes). To address this, we cropped the images into 4 overlapping patches based on the following coordinates:</p>\n<pre><code>def __post_init__(self):\n    self.patch_positions = [\n        (0.00, 0.35),  # Patch 1\n        (0.20, 0.55),  # Patch 2\n        (0.45, 0.80),  # Patch 3\n        (0.65, 1.00),  # Patch 4\n    ]\n</code></pre>\n<h3>Augmentation</h3>\n<p>Since soil color and lighting conditions varied significantly by State, we applied the following augmentations to improve robustness against domain shifts:</p>\n<ul>\n<li><p>Strong ColorJitter (exp38): We significantly perturbed brightness, contrast, and saturation to encourage the model to learn features independent of specific color tones.</p></li>\n<li><p>Inpainting (Cleanup): Metadata and color bars appearing at the bottom of images were automatically masked and removed using inpainting. This prevented the model from overfitting to these artifacts.</p></li>\n</ul>\n<h2>Model Architecture</h2>\n<h3>Backbone</h3>\n<p>We used DINOv3 Large as our backbone. We used it as a Frozen Backbone because unfreezing it led to early overfitting.</p>\n<h3>Local Mamba Fusion</h3>\n<p>Feature maps extracted from the 4 patches were fed into a Local Mamba Block. We aggregated these features using Patch Attention to dynamically weight them, allowing the model to effectively capture the context across different patches.</p>\n<h3>Domain Shift Strategy</h3>\n<p>During the competition, we discovered that the inference error (Loss) for the NSW (New South Wales) state was more than double that of other states. We addressed this domain shift through a three-step approach:</p>\n<ul>\n<li><p>FiLM (exp35): We injected State information into the intermediate features as a conditioning mechanism.</p></li>\n<li><p>Multi-Scale MIL (exp51): In addition to the 4 patches, we added a global center-crop patch to perform Multiple Instance Learning across different scales.</p></li>\n<li><p>State-Guided Experts: We split the output head into experts for each of the 4 states. The final output was blended based on the predicted probability of the state.</p></li>\n</ul>\n<h3>Post-processing</h3>\n<p>During EDA, I observed that dry_dead_g was frequently zero for the state of WA. Based on this, I implemented a post-processing step using the state prediction model mentioned earlier. When the model predicted \"WA\" with the highest probability (wa_prob), I adjusted the predicted value of dry_dead_g using the following formula:</p>\n<pre><code>original_dead = pred[1]\nscaled_dead = original_dead * (1 - wa_prob)\n</code></pre>\n<p>While this approach improved the score on the Public LB, performance on the Private LB was actually better without this step.</p>\n<h2>Loss Function</h2>\n<p>Since the target variables (Green, Dead, Clover) had small variances, we adopted Huber Loss.</p>\n<h2>What didn't work</h2>\n<ul>\n<li><p>Leave-One-State-Out: We attempted to treat one state as completely unknown for validation, but the domain gap was too large, preventing the model from converging properly.</p></li>\n<li><p>Backbone Unfreezing: This caused severe overfitting, likely due to the limited number of samples.</p></li>\n<li><p>TTA: When TTA was applied, LB score dropped 0.04 in LB score. </p></li>\n<li><p>We conducted training using DINOv2; however, DINOv3 outperformed it in every method we tested. Therefore, we ultimately decided to use DINOv3 exclusively.</p></li>\n</ul>",
      "rawMarkdown": "First of all, we would like to thank the hosts for organizing this competition.\nAlthough we are a team of beginners, thanks to the clear problem setting and the insightful discussions and code shared by other participants, we were able to complete the competition successfully.\n\nHere is a summary of our approach.\n\n## CV Strategy\nOur biggest challenge throughout the competition was constructing a robust CV strategy.\nAfter much trial and error, we finally adopted **Stratified Group K Fold**. We grouped the data by `Sampling_Date` and stratified it by `State` to create 4 folds (Seed: 33).\nThis ensured that sampling dates did not leak between folds while maintaining a balanced representation of all states in every fold.\n\n## Data Processing & Augmentation\n\n### 4-Patch Overlapping Strategy\nSimply resizing the original 4000x2000 images to 512x512 resulted in the loss of fine texture details (e.g., leaf shapes). To address this, we cropped the images into 4 overlapping patches based on the following coordinates:\n\n```python\ndef __post_init__(self):\n    self.patch_positions = [\n        (0.00, 0.35),  # Patch 1\n        (0.20, 0.55),  # Patch 2\n        (0.45, 0.80),  # Patch 3\n        (0.65, 1.00),  # Patch 4\n    ]\n```\n### Augmentation\nSince soil color and lighting conditions varied significantly by State, we applied the following augmentations to improve robustness against domain shifts:\n\n- Strong ColorJitter (exp38): We significantly perturbed brightness, contrast, and saturation to encourage the model to learn features independent of specific color tones.\n\n- Inpainting (Cleanup): Metadata and color bars appearing at the bottom of images were automatically masked and removed using inpainting. This prevented the model from overfitting to these artifacts.\n\n## Model Architecture\n### Backbone\nWe used DINOv3 Large as our backbone. We used it as a Frozen Backbone because unfreezing it led to early overfitting.\n\n### Local Mamba Fusion\nFeature maps extracted from the 4 patches were fed into a Local Mamba Block. We aggregated these features using Patch Attention to dynamically weight them, allowing the model to effectively capture the context across different patches.\n\n### Domain Shift Strategy\nDuring the competition, we discovered that the inference error (Loss) for the NSW (New South Wales) state was more than double that of other states. We addressed this domain shift through a three-step approach:\n\n- FiLM (exp35): We injected State information into the intermediate features as a conditioning mechanism.\n\n- Multi-Scale MIL (exp51): In addition to the 4 patches, we added a global center-crop patch to perform Multiple Instance Learning across different scales.\n\n- State-Guided Experts: We split the output head into experts for each of the 4 states. The final output was blended based on the predicted probability of the state.\n\n### Post-processing\n\nDuring EDA, I observed that dry_dead_g was frequently zero for the state of WA. Based on this, I implemented a post-processing step using the state prediction model mentioned earlier. When the model predicted \"WA\" with the highest probability (wa_prob), I adjusted the predicted value of dry_dead_g using the following formula:\n\n```python\noriginal_dead = pred[1]\nscaled_dead = original_dead * (1 - wa_prob)\n```\nWhile this approach improved the score on the Public LB, performance on the Private LB was actually better without this step.\n\n\n## Loss Function\nSince the target variables (Green, Dead, Clover) had small variances, we adopted Huber Loss.\n\n## What didn't work\n- Leave-One-State-Out: We attempted to treat one state as completely unknown for validation, but the domain gap was too large, preventing the model from converging properly.\n\n- Backbone Unfreezing: This caused severe overfitting, likely due to the limited number of samples.\n\n- TTA: When TTA was applied, LB score dropped 0.04 in LB score. \n\n- We conducted training using DINOv2; however, DINOv3 outperformed it in every method we tested. Therefore, we ultimately decided to use DINOv3 exclusively.",
      "votes": 3
    }
  ],
  "comments": [],
  "raw_markdown_by_id": {
    "3400137": "First of all, we would like to thank the hosts for organizing this competition.\nAlthough we are a team of beginners, thanks to the clear problem setting and the insightful discussions and code shared by other participants, we were able to complete the competition successfully.\n\nHere is a summary of our approach.\n\n## CV Strategy\nOur biggest challenge throughout the competition was constructing a robust CV strategy.\nAfter much trial and error, we finally adopted **Stratified Group K Fold**. We grouped the data by `Sampling_Date` and stratified it by `State` to create 4 folds (Seed: 33).\nThis ensured that sampling dates did not leak between folds while maintaining a balanced representation of all states in every fold.\n\n## Data Processing & Augmentation\n\n### 4-Patch Overlapping Strategy\nSimply resizing the original 4000x2000 images to 512x512 resulted in the loss of fine texture details (e.g., leaf shapes). To address this, we cropped the images into 4 overlapping patches based on the following coordinates:\n\n```python\ndef __post_init__(self):\n    self.patch_positions = [\n        (0.00, 0.35),  # Patch 1\n        (0.20, 0.55),  # Patch 2\n        (0.45, 0.80),  # Patch 3\n        (0.65, 1.00),  # Patch 4\n    ]\n```\n### Augmentation\nSince soil color and lighting conditions varied significantly by State, we applied the following augmentations to improve robustness against domain shifts:\n\n- Strong ColorJitter (exp38): We significantly perturbed brightness, contrast, and saturation to encourage the model to learn features independent of specific color tones.\n\n- Inpainting (Cleanup): Metadata and color bars appearing at the bottom of images were automatically masked and removed using inpainting. This prevented the model from overfitting to these artifacts.\n\n## Model Architecture\n### Backbone\nWe used DINOv3 Large as our backbone. We used it as a Frozen Backbone because unfreezing it led to early overfitting.\n\n### Local Mamba Fusion\nFeature maps extracted from the 4 patches were fed into a Local Mamba Block. We aggregated these features using Patch Attention to dynamically weight them, allowing the model to effectively capture the context across different patches.\n\n### Domain Shift Strategy\nDuring the competition, we discovered that the inference error (Loss) for the NSW (New South Wales) state was more than double that of other states. We addressed this domain shift through a three-step approach:\n\n- FiLM (exp35): We injected State information into the intermediate features as a conditioning mechanism.\n\n- Multi-Scale MIL (exp51): In addition to the 4 patches, we added a global center-crop patch to perform Multiple Instance Learning across different scales.\n\n- State-Guided Experts: We split the output head into experts for each of the 4 states. The final output was blended based on the predicted probability of the state.\n\n### Post-processing\n\nDuring EDA, I observed that dry_dead_g was frequently zero for the state of WA. Based on this, I implemented a post-processing step using the state prediction model mentioned earlier. When the model predicted \"WA\" with the highest probability (wa_prob), I adjusted the predicted value of dry_dead_g using the following formula:\n\n```python\noriginal_dead = pred[1]\nscaled_dead = original_dead * (1 - wa_prob)\n```\nWhile this approach improved the score on the Public LB, performance on the Private LB was actually better without this step.\n\n\n## Loss Function\nSince the target variables (Green, Dead, Clover) had small variances, we adopted Huber Loss.\n\n## What didn't work\n- Leave-One-State-Out: We attempted to treat one state as completely unknown for validation, but the domain gap was too large, preventing the model from converging properly.\n\n- Backbone Unfreezing: This caused severe overfitting, likely due to the limited number of samples.\n\n- TTA: When TTA was applied, LB score dropped 0.04 in LB score. \n\n- We conducted training using DINOv2; however, DINOv3 outperformed it in every method we tested. Therefore, we ultimately decided to use DINOv3 exclusively."
  },
  "complete": true,
  "source": "existing_discussion",
  "competition_id": 112509,
  "competition_slug": "csiro-biomass",
  "team_id": 14619250,
  "solution_url": "/competitions/csiro-biomass/writeups/133rd-place-solution"
}