{
  "id": 670746,
  "title": "A Dual-Stream DINOv3 + Gated Depthwise Convolutions + Auxiliary Metadata",
  "url": "/competitions/csiro-biomass/writeups/30th-place-solution",
  "author_name": "",
  "post_date": "2026-01-29T13:54:50.187Z",
  "votes": 10,
  "comment_count": 3,
  "views": 0,
  "content": "<p>First off, a huge thank you to the organizers and CSIRO for hosting this challenging competition. It was a great opportunity to work on this problem. Below is the summary of our approach, and again thanks to everyone.</p>\n<h2>1. Overview</h2>\n<p>Our final solution is an ensemble of three models based on the <strong>DualGatedEncoder</strong> and <strong>TwinStreamMetaNet</strong> architectures. The core philosophy was to treat the input images not as single static frames but as wide panoramic data that benefited from being split and processed via a \"Twin Stream\" approach.</p>\n<p><strong>Backbone:</strong> <code>vit_huge_plus_patch16_dinov3.lvd1689m</code></p>\n<p><strong>Architecture:</strong> Dual-stream processing with Gated Depthwise Convolutional fusion.</p>\n<p><strong>Meta-Learning:</strong> An auxiliary head to predict metadata (NDVI, Height) from image features during inference.</p>\n<p><strong>Physics Constraints:</strong> A learnable Consistency Gate and hard post-processing constraints to ensure mass balance. This helped us surpass 0.73 on Public LB.</p>\n<p><strong>Loss:</strong> Weighted Huber Loss with class balancing.</p>\n<h2>2. Data Processing &amp; Input</h2>\n<p>The dataset images had a wide aspect ratio (2:1). Resizing them to a square would distort the vegetation features.</p>\n<p><strong>Dual-Stream Splitting:</strong> We split each image into two  halves (Left and Right). These are fed into the backbone as a pair.</p>\n<p><strong>Metadata Integration:</strong> We also injected normalized metadata (NDVI, Height) and cyclic date encodings (Sin/Cos of Day of Year) into the model based on a <code>meta_dropout</code> parameter.</p>\n<p><strong>Augmentations:</strong> We used a rather heavy augmentations pipeline including:</p>\n<ul>\n<li><strong>Geometric:</strong> Horizontal/Vertical flips, Affine transforms (scale, rotate, shear).</li>\n<li><strong>Robust augmentations:</strong> CoarseDropout (to force global context learning), ISO noise, and Motion Blur.</li>\n</ul>\n<p><strong>Metadata Dropout:</strong> During training, we randomly zeroed out the metadata vector to force the model to learn purely visual features and not over-rely on NDVI and Height.</p>\n<h2>3. Our Model Architecture</h2>\n<h3>From Cross-Attention to Gated Depthwise Convolutions</h3>\n<p>We started with a heavy attention-based mechanism but moved to a lightweight, gated convolutional mixer for efficiency and better convergence.</p>\n<p>Initially, we treated the Left and Right image halves as distinct sequences that needed to \"attend\" to each other globally. We implemented a CrossAttention module (Standard O(N2) Multi-Head Attention) where the Left tokens acted as queries for the Right keys/values, and vice versa. We also used a SpatialContextAttention module for pooling.</p>\n<p>We pivoted to a unified \"Twin Stream\" approach. Instead of complex attention maps, we simply concatenated the tokens from both streams and fused them using a custom Gated Depthwise Convolution Block which uses Depthwise Convolutions (kernel size 5) to mix information locally and a Gating mechanism (Sigmoid) to control the flow of information.</p>\n<h3>Final Architecture</h3>\n<p><strong>DualGatedEncoder</strong> -  The Left and Right features are concatenated (B,2N,C) and passed through two stacked GatedDWConvBlock layers, and a dedicated sub-network predicts metadata (NDVI, Height) from the visual features.</p>\n<p><strong>TwinStreamMetaNet</strong> - It is trained in such a way to handle the inference effectively by relying heavily on the Auxiliary Head to predict environmental conditions (NDVI, Height) purely from the image texture.</p>\n<p>PS: Both architectures use a Consistency Gate. We observed that the Dead components was the hardest to predict directly due to high variance. However, the physical relationship Total=GDM+Dead holds true. The Solution: Consistency Gate outputs a gate_val (via Sigmoid).</p>\n<pre><code>If gate_val is high: Trust the Derived logic (Total−GDM).\nIf gate_val is low: Trust the Raw learned prediction.\n</code></pre>\n<h2>4. Inference &amp; Ensemble</h2>\n<h3>4-Way Test Time Augmentation (TTA)</h3>\n<p>Since our models were trained with heavy geometric augmentations, we applied a comprehensive 4-way TTA during inference.</p>\n<h3>Target-Specific Weight Optimization</h3>\n<p>Instead of using a simple mean or finding a single weight for the whole model, we optimized the ensemble weights per each target variable.</p>\n<p>We used Nelder-Mead Scalar Minimization on our hold-out validation set to find the optimal blending ratio α for each biomass component. To maximize R2 score for the specific target:</p>\n<ul>\n<li>Method: For two models A and B, and for each target t:\n$$P_{\\text{final}}^{t} = \\alpha_t \\cdot P_A^{t} + (1 - \\alpha_t) \\cdot P_B^{t}$$</li>\n</ul>\n<h3>Final Ensemble Submission</h3>\n<table>\n<thead>\n<tr>\n<th>Model Architecture</th>\n<th>Weight</th>\n<th>Role</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><strong>DualGatedEncoder</strong></td>\n<td><strong>0.50</strong></td>\n<td>Best single model (LB 0.75). Uses strict consistency gates.</td>\n</tr>\n<tr>\n<td><strong>TwinStreamMetaNet</strong></td>\n<td><strong>0.30</strong></td>\n<td>different architecture trained with <code>meta_dropout=1</code> (LB 0.74).</td>\n</tr>\n<tr>\n<td><strong>TwinStreamMetaNet (Var)</strong></td>\n<td><strong>0.20</strong></td>\n<td>different architecture (LB 0.74).</td>\n</tr>\n</tbody>\n</table>\n<h2>Acknowledgements</h2>\n<p>Since this was our first kaggle comp, special thanks to the kaggle community for the informative discussions. Also thanks for all the teammates for the hardwork</p>\n<hr>\n<h4>Final Notebook: <a href=\"https://www.kaggle.com/code/ayshthkr/csiro-image2biomass-final-draft-0-75-0-63-lb\" target=\"_blank\">https://www.kaggle.com/code/ayshthkr/csiro-image2biomass-final-draft-0-75-0-63-lb</a></h4>\n<h4>Pls share if you found this informative.</h4>",
  "messages": [
    {
      "id": "3398661",
      "postDate": "01/29/2026 13:54:31",
      "content": "<p>First off, a huge thank you to the organizers and CSIRO for hosting this challenging competition. It was a great opportunity to work on this problem. Below is the summary of our approach, and again thanks to everyone.</p>\n<h2>1. Overview</h2>\n<p>Our final solution is an ensemble of three models based on the <strong>DualGatedEncoder</strong> and <strong>TwinStreamMetaNet</strong> architectures. The core philosophy was to treat the input images not as single static frames but as wide panoramic data that benefited from being split and processed via a \"Twin Stream\" approach.</p>\n<p><strong>Backbone:</strong> <code>vit_huge_plus_patch16_dinov3.lvd1689m</code></p>\n<p><strong>Architecture:</strong> Dual-stream processing with Gated Depthwise Convolutional fusion.</p>\n<p><strong>Meta-Learning:</strong> An auxiliary head to predict metadata (NDVI, Height) from image features during inference.</p>\n<p><strong>Physics Constraints:</strong> A learnable Consistency Gate and hard post-processing constraints to ensure mass balance. This helped us surpass 0.73 on Public LB.</p>\n<p><strong>Loss:</strong> Weighted Huber Loss with class balancing.</p>\n<h2>2. Data Processing &amp; Input</h2>\n<p>The dataset images had a wide aspect ratio (2:1). Resizing them to a square would distort the vegetation features.</p>\n<p><strong>Dual-Stream Splitting:</strong> We split each image into two  halves (Left and Right). These are fed into the backbone as a pair.</p>\n<p><strong>Metadata Integration:</strong> We also injected normalized metadata (NDVI, Height) and cyclic date encodings (Sin/Cos of Day of Year) into the model based on a <code>meta_dropout</code> parameter.</p>\n<p><strong>Augmentations:</strong> We used a rather heavy augmentations pipeline including:</p>\n<ul>\n<li><strong>Geometric:</strong> Horizontal/Vertical flips, Affine transforms (scale, rotate, shear).</li>\n<li><strong>Robust augmentations:</strong> CoarseDropout (to force global context learning), ISO noise, and Motion Blur.</li>\n</ul>\n<p><strong>Metadata Dropout:</strong> During training, we randomly zeroed out the metadata vector to force the model to learn purely visual features and not over-rely on NDVI and Height.</p>\n<h2>3. Our Model Architecture</h2>\n<h3>From Cross-Attention to Gated Depthwise Convolutions</h3>\n<p>We started with a heavy attention-based mechanism but moved to a lightweight, gated convolutional mixer for efficiency and better convergence.</p>\n<p>Initially, we treated the Left and Right image halves as distinct sequences that needed to \"attend\" to each other globally. We implemented a CrossAttention module (Standard O(N2) Multi-Head Attention) where the Left tokens acted as queries for the Right keys/values, and vice versa. We also used a SpatialContextAttention module for pooling.</p>\n<p>We pivoted to a unified \"Twin Stream\" approach. Instead of complex attention maps, we simply concatenated the tokens from both streams and fused them using a custom Gated Depthwise Convolution Block which uses Depthwise Convolutions (kernel size 5) to mix information locally and a Gating mechanism (Sigmoid) to control the flow of information.</p>\n<h3>Final Architecture</h3>\n<p><strong>DualGatedEncoder</strong> -  The Left and Right features are concatenated (B,2N,C) and passed through two stacked GatedDWConvBlock layers, and a dedicated sub-network predicts metadata (NDVI, Height) from the visual features.</p>\n<p><strong>TwinStreamMetaNet</strong> - It is trained in such a way to handle the inference effectively by relying heavily on the Auxiliary Head to predict environmental conditions (NDVI, Height) purely from the image texture.</p>\n<p>PS: Both architectures use a Consistency Gate. We observed that the Dead components was the hardest to predict directly due to high variance. However, the physical relationship Total=GDM+Dead holds true. The Solution: Consistency Gate outputs a gate_val (via Sigmoid).</p>\n<pre><code>If gate_val is high: Trust the Derived logic (Total−GDM).\nIf gate_val is low: Trust the Raw learned prediction.\n</code></pre>\n<h2>4. Inference &amp; Ensemble</h2>\n<h3>4-Way Test Time Augmentation (TTA)</h3>\n<p>Since our models were trained with heavy geometric augmentations, we applied a comprehensive 4-way TTA during inference.</p>\n<h3>Target-Specific Weight Optimization</h3>\n<p>Instead of using a simple mean or finding a single weight for the whole model, we optimized the ensemble weights per each target variable.</p>\n<p>We used Nelder-Mead Scalar Minimization on our hold-out validation set to find the optimal blending ratio α for each biomass component. To maximize R2 score for the specific target:</p>\n<ul>\n<li>Method: For two models A and B, and for each target t:\n$$P_{\\text{final}}^{t} = \\alpha_t \\cdot P_A^{t} + (1 - \\alpha_t) \\cdot P_B^{t}$$</li>\n</ul>\n<h3>Final Ensemble Submission</h3>\n<table>\n<thead>\n<tr>\n<th>Model Architecture</th>\n<th>Weight</th>\n<th>Role</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><strong>DualGatedEncoder</strong></td>\n<td><strong>0.50</strong></td>\n<td>Best single model (LB 0.75). Uses strict consistency gates.</td>\n</tr>\n<tr>\n<td><strong>TwinStreamMetaNet</strong></td>\n<td><strong>0.30</strong></td>\n<td>different architecture trained with <code>meta_dropout=1</code> (LB 0.74).</td>\n</tr>\n<tr>\n<td><strong>TwinStreamMetaNet (Var)</strong></td>\n<td><strong>0.20</strong></td>\n<td>different architecture (LB 0.74).</td>\n</tr>\n</tbody>\n</table>\n<h2>Acknowledgements</h2>\n<p>Since this was our first kaggle comp, special thanks to the kaggle community for the informative discussions. Also thanks for all the teammates for the hardwork</p>\n<hr>\n<h4>Final Notebook: <a href=\"https://www.kaggle.com/code/ayshthkr/csiro-image2biomass-final-draft-0-75-0-63-lb\" target=\"_blank\">https://www.kaggle.com/code/ayshthkr/csiro-image2biomass-final-draft-0-75-0-63-lb</a></h4>\n<h4>Pls share if you found this informative.</h4>",
      "rawMarkdown": "First off, a huge thank you to the organizers and CSIRO for hosting this challenging competition. It was a great opportunity to work on this problem. Below is the summary of our approach, and again thanks to everyone.\n\n## 1. Overview\n\nOur final solution is an ensemble of three models based on the **DualGatedEncoder** and **TwinStreamMetaNet** architectures. The core philosophy was to treat the input images not as single static frames but as wide panoramic data that benefited from being split and processed via a \"Twin Stream\" approach.\n\n**Backbone:** `vit_huge_plus_patch16_dinov3.lvd1689m`\n\n**Architecture:** Dual-stream processing with Gated Depthwise Convolutional fusion.\n\n**Meta-Learning:** An auxiliary head to predict metadata (NDVI, Height) from image features during inference.\n\n**Physics Constraints:** A learnable Consistency Gate and hard post-processing constraints to ensure mass balance. This helped us surpass 0.73 on Public LB.\n\n**Loss:** Weighted Huber Loss with class balancing.\n\n\n## 2. Data Processing & Input\n\nThe dataset images had a wide aspect ratio (2:1). Resizing them to a square would distort the vegetation features.\n\n**Dual-Stream Splitting:** We split each image into two  halves (Left and Right). These are fed into the backbone as a pair.\n\n**Metadata Integration:** We also injected normalized metadata (NDVI, Height) and cyclic date encodings (Sin/Cos of Day of Year) into the model based on a `meta_dropout` parameter.\n\n**Augmentations:** We used a rather heavy augmentations pipeline including:\n\n- **Geometric:** Horizontal/Vertical flips, Affine transforms (scale, rotate, shear).\n- **Robust augmentations:** CoarseDropout (to force global context learning), ISO noise, and Motion Blur.\n\n**Metadata Dropout:** During training, we randomly zeroed out the metadata vector to force the model to learn purely visual features and not over-rely on NDVI and Height.\n\n\n\n## 3. Our Model Architecture \n\n### From Cross-Attention to Gated Depthwise Convolutions\n\nWe started with a heavy attention-based mechanism but moved to a lightweight, gated convolutional mixer for efficiency and better convergence.\n\nInitially, we treated the Left and Right image halves as distinct sequences that needed to \"attend\" to each other globally. We implemented a CrossAttention module (Standard O(N2) Multi-Head Attention) where the Left tokens acted as queries for the Right keys/values, and vice versa. We also used a SpatialContextAttention module for pooling.\n\nWe pivoted to a unified \"Twin Stream\" approach. Instead of complex attention maps, we simply concatenated the tokens from both streams and fused them using a custom Gated Depthwise Convolution Block which uses Depthwise Convolutions (kernel size 5) to mix information locally and a Gating mechanism (Sigmoid) to control the flow of information.\n\n### Final Architecture\n\n**DualGatedEncoder** -  The Left and Right features are concatenated (B,2N,C) and passed through two stacked GatedDWConvBlock layers, and a dedicated sub-network predicts metadata (NDVI, Height) from the visual features.\n\n**TwinStreamMetaNet** - It is trained in such a way to handle the inference effectively by relying heavily on the Auxiliary Head to predict environmental conditions (NDVI, Height) purely from the image texture.\n\nPS: Both architectures use a Consistency Gate. We observed that the Dead components was the hardest to predict directly due to high variance. However, the physical relationship Total=GDM+Dead holds true. The Solution: Consistency Gate outputs a gate_val (via Sigmoid).\n\n    If gate_val is high: Trust the Derived logic (Total−GDM).\n    If gate_val is low: Trust the Raw learned prediction.\n\n\n## 4. Inference & Ensemble\n\n### 4-Way Test Time Augmentation (TTA)\n\nSince our models were trained with heavy geometric augmentations, we applied a comprehensive 4-way TTA during inference.\n\n### Target-Specific Weight Optimization\n\nInstead of using a simple mean or finding a single weight for the whole model, we optimized the ensemble weights per each target variable.\n\nWe used Nelder-Mead Scalar Minimization on our hold-out validation set to find the optimal blending ratio α for each biomass component. To maximize R2 score for the specific target:\n- Method: For two models A and B, and for each target t:\n$$P_{\\text{final}}^{t} = \\alpha_t \\cdot P_A^{t} + (1 - \\alpha_t) \\cdot P_B^{t}$$\n\n### Final Ensemble Submission\n\n| Model Architecture | Weight | Role |\n| --- | --- | --- |\n| **DualGatedEncoder** | **0.50** | Best single model (LB 0.75). Uses strict consistency gates. |\n| **TwinStreamMetaNet** | **0.30** | different architecture trained with `meta_dropout=1` (LB 0.74). |\n| **TwinStreamMetaNet (Var)** | **0.20** | different architecture (LB 0.74). |\n\n## Acknowledgements\n\nSince this was our first kaggle comp, special thanks to the kaggle community for the informative discussions. Also thanks for all the teammates for the hardwork\n\n---\n#### Final Notebook: https://www.kaggle.com/code/ayshthkr/csiro-image2biomass-final-draft-0-75-0-63-lb\n#### Pls share if you found this informative.",
      "votes": null
    },
    {
      "id": "3399610",
      "postDate": "01/31/2026 03:55:19",
      "content": "<p>Hey everyone, congrats on your first Kaggle medal, that’s awesome progress!\nI was going through your write-up and code and had a couple of questions to better understand your approach. And sorry in advance if I’m missing something obvious, just trying to learn from your work.</p>\n<ol>\n<li>In your write-up, you have mentioned</li>\n</ol>\n<blockquote>\n  <p>Gated Depthwise Convolution Block which uses Depthwise Convolutions (kernel size 5) to mix information locally and a Gating mechanism (Sigmoid) to control the flow of information.</p>\n</blockquote>\n<p>But, your <code>GatedDWConvBlock</code>'s forward function looks like this:</p>\n<pre><code>shortcut = x\nx = self.norm(x)\ng = torch.sigmoid(self.gate(x))\nx = x * g\nx = x.transpose(1, 2)\nx = self.dwconv(x)\nx = x.transpose(1, 2)\nx = self.proj(x)\nx = self.drop(x)\nreturn shortcut + x\n</code></pre>\n<p>And here, the sigmoid gate is applied to the input before the depthwise convolution. So this confuses me. I wanted to know why you guys are gating the input instead of the DWConv output? As this seems to be completely defeating the purpose of a \"gating mechanism to control flow of information\" from the convolution. Or am I missing something here? </p>\n<ol>\n<li>I had one more question. I was trying to know more about the data augmentation you have done, and as you have mentioned in your write-up:</li>\n</ol>\n<blockquote>\n  <p>Augmentations: We used a rather heavy augmentations pipeline including:</p>\n  <ul>\n  <li>Geometric: Horizontal/Vertical flips, Affine transforms (scale, rotate, shear).</li>\n  <li>Robust augmentations: CoarseDropout (to force global context learning), ISO noise, and Motion Blur.</li>\n  </ul>\n  <p>Since our models were trained with heavy geometric augmentations, we applied a comprehensive 4-way TTA during inference.</p>\n</blockquote>\n<p>I tried to trace this through the inference code, but the test-time transforms I found only include <code>A.Resize + A.Normalize</code> but no flips or rotations. So, I wanted to know what the 4 TTA configurations are. (Horizontal flip? Vertical flip? Rotations?) Additionally, if possible, I’d love to know what your actual training-time augmentation pipeline looked like.</p>\n<p>Thanks a lot, and congrats again on the great result! Way to go!</p>",
      "rawMarkdown": "Hey everyone, congrats on your first Kaggle medal, that’s awesome progress!\nI was going through your write-up and code and had a couple of questions to better understand your approach. And sorry in advance if I’m missing something obvious, just trying to learn from your work.\n\n1. In your write-up, you have mentioned\n> Gated Depthwise Convolution Block which uses Depthwise Convolutions (kernel size 5) to mix information locally and a Gating mechanism (Sigmoid) to control the flow of information.\n\nBut, your `GatedDWConvBlock`'s forward function looks like this:\n```python\nshortcut = x\nx = self.norm(x)\ng = torch.sigmoid(self.gate(x))\nx = x * g\nx = x.transpose(1, 2)\nx = self.dwconv(x)\nx = x.transpose(1, 2)\nx = self.proj(x)\nx = self.drop(x)\nreturn shortcut + x\n```\nAnd here, the sigmoid gate is applied to the input before the depthwise convolution. So this confuses me. I wanted to know why you guys are gating the input instead of the DWConv output? As this seems to be completely defeating the purpose of a \"gating mechanism to control flow of information\" from the convolution. Or am I missing something here? \n\n2. I had one more question. I was trying to know more about the data augmentation you have done, and as you have mentioned in your write-up:\n> Augmentations: We used a rather heavy augmentations pipeline including:\n> \n> - Geometric: Horizontal/Vertical flips, Affine transforms (scale, rotate, shear).\n> - Robust augmentations: CoarseDropout (to force global context learning), ISO noise, and Motion Blur.\n\n\n> Since our models were trained with heavy geometric augmentations, we applied a comprehensive 4-way TTA during inference.\n\nI tried to trace this through the inference code, but the test-time transforms I found only include `A.Resize + A.Normalize` but no flips or rotations. So, I wanted to know what the 4 TTA configurations are. (Horizontal flip? Vertical flip? Rotations?) Additionally, if possible, I’d love to know what your actual training-time augmentation pipeline looked like.\n\nThanks a lot, and congrats again on the great result! Way to go!",
      "votes": null
    },
    {
      "id": "3399655",
      "postDate": "01/31/2026 05:46:15",
      "content": "<p>Hey Rudr, thanks for the comment.</p>\n<p>You are right that this order looks off, but that was an intentional choice.\nWe started our experiments using Spatial attention, but with more experiments and certain design choices inspired from Mamba, we eventually moved on to a Pre-convolution gating structure, here we apply the gate before DWConv, to effectively mask out the irrelevant pixels first. This was computationally more efficient and gave a push of around +0.03 on LB.</p>\n<p>As for TTA, we have a Standard view, Horizontal Flip, Vertical Flip, and 180deg Flip during inference which are averaged out to give final prediction.</p>\n<p>As for our training time augmentations, here is our training code snippet</p>\n<pre><code>            self.tfms = A.Compose([\n                A.Resize(height=target_h, width=target_w),\n\n                A.HorizontalFlip(p=0.5),\n                A.VerticalFlip(p=0.5),\n\n                A.Affine(\n                    translate_percent={'x': (-0.1, 0.1), 'y': (-0.1, 0.1)}, \n                    scale=(0.8, 1.2),\n                    rotate=(-45, 45),    \n                    shear=(-10, 10),     \n                    border_mode=cv2.BORDER_REFLECT_101, \n                    fill=0,                             \n                    p=0.7\n                ),\n\n                A.OneOf([\n                    A.RandomBrightnessContrast(brightness_limit=0.3, contrast_limit=0.3, p=1.0),\n                    A.HueSaturationValue(hue_shift_limit=20, sat_shift_limit=30, val_shift_limit=20, p=1.0),\n                    A.RGBShift(r_shift_limit=20, g_shift_limit=20, b_shift_limit=20, p=1.0),\n                    A.CLAHE(clip_limit=4.0, tile_grid_size=(8, 8), p=1.0),\n                    A.RandomGamma(gamma_limit=(80, 120), p=1.0),\n                ], p=0.8),\n\n                A.OneOf([\n                    A.GaussNoise(std_range=(0.02, 0.1), p=1.0),\n                    A.ISONoise(color_shift=(0.01, 0.05), intensity=(0.1, 0.5), p=1.0),\n                    A.MotionBlur(blur_limit=7, p=1.0),\n                    A.ImageCompression(quality_range=(75, 100), p=1.0),\n                ], p=0.4),\n\n                A.CoarseDropout(\n                    num_holes_range=(4, 12),           \n                    hole_height_range=(1, target_h//8),\n                    hole_width_range=(1, target_h//8),\n                    fill=0,        \n                    p=0.3\n                ),\n\n                A.Normalize(**norm_stats),\n                ToTensorV2()\n            ])\n</code></pre>\n<p>Having discussions with fellow kagglers helped us figure out early that heavy augmentations were the way to go, but looking at it now, I couldn't find other medalists using it, so maybe we just got lucky.</p>",
      "rawMarkdown": "Hey Rudr, thanks for the comment.\n\nYou are right that this order looks off, but that was an intentional choice.\nWe started our experiments using Spatial attention, but with more experiments and certain design choices inspired from Mamba, we eventually moved on to a Pre-convolution gating structure, here we apply the gate before DWConv, to effectively mask out the irrelevant pixels first. This was computationally more efficient and gave a push of around +0.03 on LB.\n\nAs for TTA, we have a Standard view, Horizontal Flip, Vertical Flip, and 180deg Flip during inference which are averaged out to give final prediction.\n\nAs for our training time augmentations, here is our training code snippet\n```\n            self.tfms = A.Compose([\n                A.Resize(height=target_h, width=target_w),\n                \n                A.HorizontalFlip(p=0.5),\n                A.VerticalFlip(p=0.5),\n                \n                A.Affine(\n                    translate_percent={'x': (-0.1, 0.1), 'y': (-0.1, 0.1)}, \n                    scale=(0.8, 1.2),\n                    rotate=(-45, 45),    \n                    shear=(-10, 10),     \n                    border_mode=cv2.BORDER_REFLECT_101, \n                    fill=0,                             \n                    p=0.7\n                ),\n                \n                A.OneOf([\n                    A.RandomBrightnessContrast(brightness_limit=0.3, contrast_limit=0.3, p=1.0),\n                    A.HueSaturationValue(hue_shift_limit=20, sat_shift_limit=30, val_shift_limit=20, p=1.0),\n                    A.RGBShift(r_shift_limit=20, g_shift_limit=20, b_shift_limit=20, p=1.0),\n                    A.CLAHE(clip_limit=4.0, tile_grid_size=(8, 8), p=1.0),\n                    A.RandomGamma(gamma_limit=(80, 120), p=1.0),\n                ], p=0.8),\n                \n                A.OneOf([\n                    A.GaussNoise(std_range=(0.02, 0.1), p=1.0),\n                    A.ISONoise(color_shift=(0.01, 0.05), intensity=(0.1, 0.5), p=1.0),\n                    A.MotionBlur(blur_limit=7, p=1.0),\n                    A.ImageCompression(quality_range=(75, 100), p=1.0),\n                ], p=0.4),\n                \n                A.CoarseDropout(\n                    num_holes_range=(4, 12),           \n                    hole_height_range=(1, target_h//8),\n                    hole_width_range=(1, target_h//8),\n                    fill=0,        \n                    p=0.3\n                ),\n        \n                A.Normalize(**norm_stats),\n                ToTensorV2()\n            ])\n```\nHaving discussions with fellow kagglers helped us figure out early that heavy augmentations were the way to go, but looking at it now, I couldn't find other medalists using it, so maybe we just got lucky.",
      "votes": null
    },
    {
      "id": "3399881",
      "postDate": "01/31/2026 12:51:45",
      "content": "<p>Hey Ayush, thanks for clarifying! I'm curious about the Mamba connection you mentioned. From what I understand, Mamba's selectivity mechanism gates the state transition (B and C matrices), not the input. Could you elaborate on which specific aspect of Mamba inspired gating the input before DWConv? </p>\n<p>Also, I'm trying to understand the \"mask irrelevant pixels\" logic - since the gate <code>g = sigmoid(self.gate(x))</code> is computed from the same input x that it masks, how does it know which pixels are irrelevant before seeing the DWConv features? It's not making sense to me. Also, did you compare this to gating the DWConv output?</p>\n<hr>\n<p>For the TTA part, thanks for listing it! Could you point me to which cell in your notebook implements this TTA, as I couldn't find it? </p>\n<p>Also, quick note: 180° rotation = horizontal flip + vertical flip, so you'd only get 3 unique views, not 4. Did you mean 90° rotation instead?</p>\n<p>Lastly, I notice you use A.Affine with rotate=(-45, 45). For pasture images, wouldn't 45° rotations create unrealistic orientations? What was the motivation or need for such heavy rotation augmentation, or was it hit-and-trial based?</p>\n<hr>\n<p>Lastly, if you're comfortable, please do share your training script. If you aren't, again, no issues.</p>",
      "rawMarkdown": "Hey Ayush, thanks for clarifying! I'm curious about the Mamba connection you mentioned. From what I understand, Mamba's selectivity mechanism gates the state transition (B and C matrices), not the input. Could you elaborate on which specific aspect of Mamba inspired gating the input before DWConv? \n\nAlso, I'm trying to understand the \"mask irrelevant pixels\" logic - since the gate `g = sigmoid(self.gate(x))` is computed from the same input x that it masks, how does it know which pixels are irrelevant before seeing the DWConv features? It's not making sense to me. Also, did you compare this to gating the DWConv output?\n\n---\n\nFor the TTA part, thanks for listing it! Could you point me to which cell in your notebook implements this TTA, as I couldn't find it? \n\nAlso, quick note: 180° rotation = horizontal flip + vertical flip, so you'd only get 3 unique views, not 4. Did you mean 90° rotation instead?\n\nLastly, I notice you use A.Affine with rotate=(-45, 45). For pasture images, wouldn't 45° rotations create unrealistic orientations? What was the motivation or need for such heavy rotation augmentation, or was it hit-and-trial based?\n\n---\n\nLastly, if you're comfortable, please do share your training script. If you aren't, again, no issues.",
      "votes": null
    }
  ],
  "comments": [
    {
      "id": 3399610,
      "author_name": "rudrpratapsingh",
      "author_url": "",
      "post_date": "01/31/2026 03:55:19",
      "content": "<p>Hey everyone, congrats on your first Kaggle medal, that’s awesome progress!\nI was going through your write-up and code and had a couple of questions to better understand your approach. And sorry in advance if I’m missing something obvious, just trying to learn from your work.</p>\n<ol>\n<li>In your write-up, you have mentioned</li>\n</ol>\n<blockquote>\n  <p>Gated Depthwise Convolution Block which uses Depthwise Convolutions (kernel size 5) to mix information locally and a Gating mechanism (Sigmoid) to control the flow of information.</p>\n</blockquote>\n<p>But, your <code>GatedDWConvBlock</code>'s forward function looks like this:</p>\n<pre><code>shortcut = x\nx = self.norm(x)\ng = torch.sigmoid(self.gate(x))\nx = x * g\nx = x.transpose(1, 2)\nx = self.dwconv(x)\nx = x.transpose(1, 2)\nx = self.proj(x)\nx = self.drop(x)\nreturn shortcut + x\n</code></pre>\n<p>And here, the sigmoid gate is applied to the input before the depthwise convolution. So this confuses me. I wanted to know why you guys are gating the input instead of the DWConv output? As this seems to be completely defeating the purpose of a \"gating mechanism to control flow of information\" from the convolution. Or am I missing something here? </p>\n<ol>\n<li>I had one more question. I was trying to know more about the data augmentation you have done, and as you have mentioned in your write-up:</li>\n</ol>\n<blockquote>\n  <p>Augmentations: We used a rather heavy augmentations pipeline including:</p>\n  <ul>\n  <li>Geometric: Horizontal/Vertical flips, Affine transforms (scale, rotate, shear).</li>\n  <li>Robust augmentations: CoarseDropout (to force global context learning), ISO noise, and Motion Blur.</li>\n  </ul>\n  <p>Since our models were trained with heavy geometric augmentations, we applied a comprehensive 4-way TTA during inference.</p>\n</blockquote>\n<p>I tried to trace this through the inference code, but the test-time transforms I found only include <code>A.Resize + A.Normalize</code> but no flips or rotations. So, I wanted to know what the 4 TTA configurations are. (Horizontal flip? Vertical flip? Rotations?) Additionally, if possible, I’d love to know what your actual training-time augmentation pipeline looked like.</p>\n<p>Thanks a lot, and congrats again on the great result! Way to go!</p>",
      "votes": null,
      "replies": [
        {
          "id": 3399655,
          "author_name": "ayshthkr",
          "author_url": "",
          "post_date": "01/31/2026 05:46:15",
          "content": "<p>Hey Rudr, thanks for the comment.</p>\n<p>You are right that this order looks off, but that was an intentional choice.\nWe started our experiments using Spatial attention, but with more experiments and certain design choices inspired from Mamba, we eventually moved on to a Pre-convolution gating structure, here we apply the gate before DWConv, to effectively mask out the irrelevant pixels first. This was computationally more efficient and gave a push of around +0.03 on LB.</p>\n<p>As for TTA, we have a Standard view, Horizontal Flip, Vertical Flip, and 180deg Flip during inference which are averaged out to give final prediction.</p>\n<p>As for our training time augmentations, here is our training code snippet</p>\n<pre><code>            self.tfms = A.Compose([\n                A.Resize(height=target_h, width=target_w),\n\n                A.HorizontalFlip(p=0.5),\n                A.VerticalFlip(p=0.5),\n\n                A.Affine(\n                    translate_percent={'x': (-0.1, 0.1), 'y': (-0.1, 0.1)}, \n                    scale=(0.8, 1.2),\n                    rotate=(-45, 45),    \n                    shear=(-10, 10),     \n                    border_mode=cv2.BORDER_REFLECT_101, \n                    fill=0,                             \n                    p=0.7\n                ),\n\n                A.OneOf([\n                    A.RandomBrightnessContrast(brightness_limit=0.3, contrast_limit=0.3, p=1.0),\n                    A.HueSaturationValue(hue_shift_limit=20, sat_shift_limit=30, val_shift_limit=20, p=1.0),\n                    A.RGBShift(r_shift_limit=20, g_shift_limit=20, b_shift_limit=20, p=1.0),\n                    A.CLAHE(clip_limit=4.0, tile_grid_size=(8, 8), p=1.0),\n                    A.RandomGamma(gamma_limit=(80, 120), p=1.0),\n                ], p=0.8),\n\n                A.OneOf([\n                    A.GaussNoise(std_range=(0.02, 0.1), p=1.0),\n                    A.ISONoise(color_shift=(0.01, 0.05), intensity=(0.1, 0.5), p=1.0),\n                    A.MotionBlur(blur_limit=7, p=1.0),\n                    A.ImageCompression(quality_range=(75, 100), p=1.0),\n                ], p=0.4),\n\n                A.CoarseDropout(\n                    num_holes_range=(4, 12),           \n                    hole_height_range=(1, target_h//8),\n                    hole_width_range=(1, target_h//8),\n                    fill=0,        \n                    p=0.3\n                ),\n\n                A.Normalize(**norm_stats),\n                ToTensorV2()\n            ])\n</code></pre>\n<p>Having discussions with fellow kagglers helped us figure out early that heavy augmentations were the way to go, but looking at it now, I couldn't find other medalists using it, so maybe we just got lucky.</p>",
          "votes": null,
          "replies": [
            {
              "id": 3399881,
              "author_name": "rudrpratapsingh",
              "author_url": "",
              "post_date": "01/31/2026 12:51:45",
              "content": "<p>Hey Ayush, thanks for clarifying! I'm curious about the Mamba connection you mentioned. From what I understand, Mamba's selectivity mechanism gates the state transition (B and C matrices), not the input. Could you elaborate on which specific aspect of Mamba inspired gating the input before DWConv? </p>\n<p>Also, I'm trying to understand the \"mask irrelevant pixels\" logic - since the gate <code>g = sigmoid(self.gate(x))</code> is computed from the same input x that it masks, how does it know which pixels are irrelevant before seeing the DWConv features? It's not making sense to me. Also, did you compare this to gating the DWConv output?</p>\n<hr>\n<p>For the TTA part, thanks for listing it! Could you point me to which cell in your notebook implements this TTA, as I couldn't find it? </p>\n<p>Also, quick note: 180° rotation = horizontal flip + vertical flip, so you'd only get 3 unique views, not 4. Did you mean 90° rotation instead?</p>\n<p>Lastly, I notice you use A.Affine with rotate=(-45, 45). For pasture images, wouldn't 45° rotations create unrealistic orientations? What was the motivation or need for such heavy rotation augmentation, or was it hit-and-trial based?</p>\n<hr>\n<p>Lastly, if you're comfortable, please do share your training script. If you aren't, again, no issues.</p>",
              "votes": null,
              "replies": []
            }
          ]
        }
      ]
    }
  ],
  "raw_markdown_by_id": {
    "3398661": "First off, a huge thank you to the organizers and CSIRO for hosting this challenging competition. It was a great opportunity to work on this problem. Below is the summary of our approach, and again thanks to everyone.\n\n## 1. Overview\n\nOur final solution is an ensemble of three models based on the **DualGatedEncoder** and **TwinStreamMetaNet** architectures. The core philosophy was to treat the input images not as single static frames but as wide panoramic data that benefited from being split and processed via a \"Twin Stream\" approach.\n\n**Backbone:** `vit_huge_plus_patch16_dinov3.lvd1689m`\n\n**Architecture:** Dual-stream processing with Gated Depthwise Convolutional fusion.\n\n**Meta-Learning:** An auxiliary head to predict metadata (NDVI, Height) from image features during inference.\n\n**Physics Constraints:** A learnable Consistency Gate and hard post-processing constraints to ensure mass balance. This helped us surpass 0.73 on Public LB.\n\n**Loss:** Weighted Huber Loss with class balancing.\n\n\n## 2. Data Processing & Input\n\nThe dataset images had a wide aspect ratio (2:1). Resizing them to a square would distort the vegetation features.\n\n**Dual-Stream Splitting:** We split each image into two  halves (Left and Right). These are fed into the backbone as a pair.\n\n**Metadata Integration:** We also injected normalized metadata (NDVI, Height) and cyclic date encodings (Sin/Cos of Day of Year) into the model based on a `meta_dropout` parameter.\n\n**Augmentations:** We used a rather heavy augmentations pipeline including:\n\n- **Geometric:** Horizontal/Vertical flips, Affine transforms (scale, rotate, shear).\n- **Robust augmentations:** CoarseDropout (to force global context learning), ISO noise, and Motion Blur.\n\n**Metadata Dropout:** During training, we randomly zeroed out the metadata vector to force the model to learn purely visual features and not over-rely on NDVI and Height.\n\n\n\n## 3. Our Model Architecture \n\n### From Cross-Attention to Gated Depthwise Convolutions\n\nWe started with a heavy attention-based mechanism but moved to a lightweight, gated convolutional mixer for efficiency and better convergence.\n\nInitially, we treated the Left and Right image halves as distinct sequences that needed to \"attend\" to each other globally. We implemented a CrossAttention module (Standard O(N2) Multi-Head Attention) where the Left tokens acted as queries for the Right keys/values, and vice versa. We also used a SpatialContextAttention module for pooling.\n\nWe pivoted to a unified \"Twin Stream\" approach. Instead of complex attention maps, we simply concatenated the tokens from both streams and fused them using a custom Gated Depthwise Convolution Block which uses Depthwise Convolutions (kernel size 5) to mix information locally and a Gating mechanism (Sigmoid) to control the flow of information.\n\n### Final Architecture\n\n**DualGatedEncoder** -  The Left and Right features are concatenated (B,2N,C) and passed through two stacked GatedDWConvBlock layers, and a dedicated sub-network predicts metadata (NDVI, Height) from the visual features.\n\n**TwinStreamMetaNet** - It is trained in such a way to handle the inference effectively by relying heavily on the Auxiliary Head to predict environmental conditions (NDVI, Height) purely from the image texture.\n\nPS: Both architectures use a Consistency Gate. We observed that the Dead components was the hardest to predict directly due to high variance. However, the physical relationship Total=GDM+Dead holds true. The Solution: Consistency Gate outputs a gate_val (via Sigmoid).\n\n    If gate_val is high: Trust the Derived logic (Total−GDM).\n    If gate_val is low: Trust the Raw learned prediction.\n\n\n## 4. Inference & Ensemble\n\n### 4-Way Test Time Augmentation (TTA)\n\nSince our models were trained with heavy geometric augmentations, we applied a comprehensive 4-way TTA during inference.\n\n### Target-Specific Weight Optimization\n\nInstead of using a simple mean or finding a single weight for the whole model, we optimized the ensemble weights per each target variable.\n\nWe used Nelder-Mead Scalar Minimization on our hold-out validation set to find the optimal blending ratio α for each biomass component. To maximize R2 score for the specific target:\n- Method: For two models A and B, and for each target t:\n$$P_{\\text{final}}^{t} = \\alpha_t \\cdot P_A^{t} + (1 - \\alpha_t) \\cdot P_B^{t}$$\n\n### Final Ensemble Submission\n\n| Model Architecture | Weight | Role |\n| --- | --- | --- |\n| **DualGatedEncoder** | **0.50** | Best single model (LB 0.75). Uses strict consistency gates. |\n| **TwinStreamMetaNet** | **0.30** | different architecture trained with `meta_dropout=1` (LB 0.74). |\n| **TwinStreamMetaNet (Var)** | **0.20** | different architecture (LB 0.74). |\n\n## Acknowledgements\n\nSince this was our first kaggle comp, special thanks to the kaggle community for the informative discussions. Also thanks for all the teammates for the hardwork\n\n---\n#### Final Notebook: https://www.kaggle.com/code/ayshthkr/csiro-image2biomass-final-draft-0-75-0-63-lb\n#### Pls share if you found this informative.",
    "3399610": "Hey everyone, congrats on your first Kaggle medal, that’s awesome progress!\nI was going through your write-up and code and had a couple of questions to better understand your approach. And sorry in advance if I’m missing something obvious, just trying to learn from your work.\n\n1. In your write-up, you have mentioned\n> Gated Depthwise Convolution Block which uses Depthwise Convolutions (kernel size 5) to mix information locally and a Gating mechanism (Sigmoid) to control the flow of information.\n\nBut, your `GatedDWConvBlock`'s forward function looks like this:\n```python\nshortcut = x\nx = self.norm(x)\ng = torch.sigmoid(self.gate(x))\nx = x * g\nx = x.transpose(1, 2)\nx = self.dwconv(x)\nx = x.transpose(1, 2)\nx = self.proj(x)\nx = self.drop(x)\nreturn shortcut + x\n```\nAnd here, the sigmoid gate is applied to the input before the depthwise convolution. So this confuses me. I wanted to know why you guys are gating the input instead of the DWConv output? As this seems to be completely defeating the purpose of a \"gating mechanism to control flow of information\" from the convolution. Or am I missing something here? \n\n2. I had one more question. I was trying to know more about the data augmentation you have done, and as you have mentioned in your write-up:\n> Augmentations: We used a rather heavy augmentations pipeline including:\n> \n> - Geometric: Horizontal/Vertical flips, Affine transforms (scale, rotate, shear).\n> - Robust augmentations: CoarseDropout (to force global context learning), ISO noise, and Motion Blur.\n\n\n> Since our models were trained with heavy geometric augmentations, we applied a comprehensive 4-way TTA during inference.\n\nI tried to trace this through the inference code, but the test-time transforms I found only include `A.Resize + A.Normalize` but no flips or rotations. So, I wanted to know what the 4 TTA configurations are. (Horizontal flip? Vertical flip? Rotations?) Additionally, if possible, I’d love to know what your actual training-time augmentation pipeline looked like.\n\nThanks a lot, and congrats again on the great result! Way to go!",
    "3399655": "Hey Rudr, thanks for the comment.\n\nYou are right that this order looks off, but that was an intentional choice.\nWe started our experiments using Spatial attention, but with more experiments and certain design choices inspired from Mamba, we eventually moved on to a Pre-convolution gating structure, here we apply the gate before DWConv, to effectively mask out the irrelevant pixels first. This was computationally more efficient and gave a push of around +0.03 on LB.\n\nAs for TTA, we have a Standard view, Horizontal Flip, Vertical Flip, and 180deg Flip during inference which are averaged out to give final prediction.\n\nAs for our training time augmentations, here is our training code snippet\n```\n            self.tfms = A.Compose([\n                A.Resize(height=target_h, width=target_w),\n                \n                A.HorizontalFlip(p=0.5),\n                A.VerticalFlip(p=0.5),\n                \n                A.Affine(\n                    translate_percent={'x': (-0.1, 0.1), 'y': (-0.1, 0.1)}, \n                    scale=(0.8, 1.2),\n                    rotate=(-45, 45),    \n                    shear=(-10, 10),     \n                    border_mode=cv2.BORDER_REFLECT_101, \n                    fill=0,                             \n                    p=0.7\n                ),\n                \n                A.OneOf([\n                    A.RandomBrightnessContrast(brightness_limit=0.3, contrast_limit=0.3, p=1.0),\n                    A.HueSaturationValue(hue_shift_limit=20, sat_shift_limit=30, val_shift_limit=20, p=1.0),\n                    A.RGBShift(r_shift_limit=20, g_shift_limit=20, b_shift_limit=20, p=1.0),\n                    A.CLAHE(clip_limit=4.0, tile_grid_size=(8, 8), p=1.0),\n                    A.RandomGamma(gamma_limit=(80, 120), p=1.0),\n                ], p=0.8),\n                \n                A.OneOf([\n                    A.GaussNoise(std_range=(0.02, 0.1), p=1.0),\n                    A.ISONoise(color_shift=(0.01, 0.05), intensity=(0.1, 0.5), p=1.0),\n                    A.MotionBlur(blur_limit=7, p=1.0),\n                    A.ImageCompression(quality_range=(75, 100), p=1.0),\n                ], p=0.4),\n                \n                A.CoarseDropout(\n                    num_holes_range=(4, 12),           \n                    hole_height_range=(1, target_h//8),\n                    hole_width_range=(1, target_h//8),\n                    fill=0,        \n                    p=0.3\n                ),\n        \n                A.Normalize(**norm_stats),\n                ToTensorV2()\n            ])\n```\nHaving discussions with fellow kagglers helped us figure out early that heavy augmentations were the way to go, but looking at it now, I couldn't find other medalists using it, so maybe we just got lucky.",
    "3399881": "Hey Ayush, thanks for clarifying! I'm curious about the Mamba connection you mentioned. From what I understand, Mamba's selectivity mechanism gates the state transition (B and C matrices), not the input. Could you elaborate on which specific aspect of Mamba inspired gating the input before DWConv? \n\nAlso, I'm trying to understand the \"mask irrelevant pixels\" logic - since the gate `g = sigmoid(self.gate(x))` is computed from the same input x that it masks, how does it know which pixels are irrelevant before seeing the DWConv features? It's not making sense to me. Also, did you compare this to gating the DWConv output?\n\n---\n\nFor the TTA part, thanks for listing it! Could you point me to which cell in your notebook implements this TTA, as I couldn't find it? \n\nAlso, quick note: 180° rotation = horizontal flip + vertical flip, so you'd only get 3 unique views, not 4. Did you mean 90° rotation instead?\n\nLastly, I notice you use A.Affine with rotate=(-45, 45). For pasture images, wouldn't 45° rotations create unrealistic orientations? What was the motivation or need for such heavy rotation augmentation, or was it hit-and-trial based?\n\n---\n\nLastly, if you're comfortable, please do share your training script. If you aren't, again, no issues."
  },
  "source": "meta"
}