返回列表

33rd Place Silver Medal Solution

687. CSIRO - Image2Biomass Prediction | csiro-biomass

开始: 2025-10-28 结束: 2026-01-28 作物智能识别 数据算法赛
第 33 名银牌解决方案 - 牧场生物量预测

第 33 名银牌解决方案

副标题:微调 DINO v3 Huge (11 亿参数) 并结合 SigLIP 进行牧场生物量预测

作者: Muhammad Ibrahim Qasmi (Grandmaster)

发布日期: 2026 年 1 月 30 日

竞赛排名: 第 33 名 (银牌)

老实说,我仍然无法相信这一切。我的第一枚银牌。在 3000 多支队伍中排名第 33。我不得不刷新了三次排行榜以确保这是真的 :)

首先,感谢 CSIRO 以及所有参与组织这次竞赛的人员。从图像中预测牧场生物量是一个非常现实世界的问题,我在工作中学到了很多。

快速致谢那些帮助我一路走来的公开 Notebook 和数据集 —— SigLIP 模型、数据划分 Notebook 以及许多我们赖以构建的基线方法。Kaggle 社区真的很棒。

我的历程

我大约在一个月前参加了这次竞赛,但老实说,我大部分 serius 的工作都是在最后 15 天完成的。在前几周,我只是阅读讨论区,运行公开 Notebook,试图理解什么有效,什么无效。

包括我在内的许多人的公共排行榜得分都卡在 0.72-0.73 左右。我不断尝试各种小改动,但没有什么真正能推动进展。然后我开始换个角度思考。

改变一切的主要想法是:与其像其他人一样使用冻结的 DINO 嵌入,如果我实际上在这个特定数据集上从头重新训练 DINO 模型会怎样?而且不仅仅是任何 DINO,而是拥有 11 亿参数的 Huge 版本。

这很有风险,因为在只有 357 张图像上训练如此庞大的模型很容易过拟合。但通过适当的正则化和早停,它奏效了。我从 0.73 提升到了 0.74,并从公共排行榜的第 106 名跃升至私有排行榜的第 33 名。

我具体做了什么

模型

我使用 vit_huge_plus_patch16_dinov3 作为我的骨干网络。这是一个拥有约 11 亿参数的视觉 Transformer (ViT),使用 DINOv3 自监督学习进行预训练。

每个牧场图像为 2000x1000 像素。我将其垂直分为左半部分和右半部分,每部分 1000x1000。两个部分都通过相同的骨干网络(共享权重),然后将特征拼接在一起。

为了融合这些特征,我使用了称为局部 Mamba 块 (Local Mamba Blocks) 的东西。这基本上是一种带有深度卷积和 gating 机制的轻量级注意力机制:

class LocalMambaBlock(nn.Module):
    def __init__(self, dim, kernel_size=5, dropout=0.1):
        super().__init__()
        self.norm = nn.LayerNorm(dim)
        self.dwconv = nn.Conv1d(dim, dim, kernel_size, 
                                 padding=kernel_size//2, groups=dim)
        self.gate = nn.Linear(dim, dim)
        self.proj = nn.Linear(dim, dim)
        self.drop = nn.Dropout(dropout)

    def forward(self, x):
        shortcut = x
        x = self.norm(x)
        g = torch.sigmoid(self.gate(x))
        x = x * g
        x = self.dwconv(x.transpose(1, 2)).transpose(1, 2)
        x = self.proj(x)
        return shortcut + self.drop(x)

想法很简单:归一化,控制信息流,应用局部卷积,投影回去,并添加残差连接。堆叠两个这样的块给了我很好的特征融合效果。

融合之后,我有三个独立的预测头分别用于 Green (绿草)、Dead (枯草) 和 Clover (三叶草)。然后从中计算 GDM 和 Total (GDM = Green + Clover, Total = GDM + Dead)。这尊重了目标之间的实际物理关系。

训练细节

以下是对我有效的配置:

  • 图像大小:512x512
  • Batch size: 6
  • 4 折交叉验证,使用 StratifiedGroupKFold
  • 学习率:骨干网络 1e-5,头部 5e-4
  • 权重衰减:1e-2
  • Dropout: 0.2
  • 早停耐心值:15 个 epoch
  • 最大 epoch: 210 (但早停很早就触发了)
  • 带有 2 个 epoch 预热 (warmup) 的余弦退火调度器
  • 混合精度训练 (fp16)

对于增强,我保持简单:

  • 水平翻转
  • 垂直翻转
  • 随机旋转 90 度
  • 平移缩放旋转
  • 颜色抖动 (轻微)

损失函数经过加权以匹配竞赛指标。我使用 log 空间中的 Huber 损失来处理广泛的生物量值和异常值:

weights = [0.1, 0.1, 0.1, 0.2, 0.5]  # Green, Dead, Clover, GDM, Total
loss = weighted_mean(HuberLoss(log1p(pred), log1p(target)))

集成模型

单独的 DINO 让我达到了 0.72-0.73 左右。为了进一步推进,我将其与 SigLIP 结合。

SigLIP 是一个视觉 - 语言模型。我从图像中提取嵌入,并计算与农业文本概念(如"bare soil"裸露土壤、"dense pasture"茂密牧场、"dead grass"枯草、"white clover"白三叶草等)的相似性得分。然后将这些语义特征输入到 GBDT 集成中(HistGradientBoosting, GradientBoosting, CatBoost, LightGBM 平均在一起)。

最终预测对于大多数目标是 75% DINO 和 25% SigLIP。对于 Clover specifically,我使用 100% DINO,因为 SigLIP 在预测它方面表现不佳。

后处理

一些有帮助的小校准:

# Clover 倾向于预测过高
Dry_Clover_g = Dry_Clover_g * 0.8

# 根据预测范围调整枯草材料
if Dry_Dead_g > 20:
    Dry_Dead_g = Dry_Dead_g * 1.1
elif Dry_Dead_g < 10:
    Dry_Dead_g = Dry_Dead_g * 0.9

# 强制质量平衡
GDM_g = Dry_Green_g + Dry_Clover_g
Dry_Total_g = GDM_g + Dry_Dead_g

结果

交叉验证得分:

折数 (Fold) R2 得分
0 0.82
1 0.85
2 0.81
3 0.84
平均 0.83

排行榜:

阶段 得分 排名
公共排行榜 (Public LB) 0.73 第 105 名
私有排行榜 (Private LB) 0.74 第 33 名

名次震荡是真实的。相信交叉验证胜过公共排行榜得到了回报。

我的收获

  1. 重新训练优于冻结特征 - 在这个特定领域微调 DINO 是最大的增益
  2. 尊重目标之间的关系 - GDM 和 Total 是衍生量,预测应保持这种关系
  3. 简单的增强即可生效 - 在这个小数据集上,重度增强弊大于利
  4. 正则化至关重要 - Dropout、权重衰减和早停防止了在 357 张图像上的过拟合
  5. 相信你的交叉验证 - 公共排行榜具有误导性,私有排行榜与我的交叉验证更匹配

代码与资源

我正在分享我的完整解决方案:

训练代码:一个干净的单细胞 Notebook,包含完整的 DINO 训练管道。我私下运行了这个,所以 Notebook 没有显示我实际的训练输出,但代码正是产生我 0.74 提交的内容。

推理代码:结合 DINO 和 SigLIP 的集成推理。这是生成最终 submission.csv 文件的内容。

模型检查点:训练好的模型权重(4 折),您可以直接用于推理。

GitHub 仓库:包含训练和推理脚本的完整代码仓库。

感谢阅读。这是我的第一枚银牌,对我来说意义重大。如果您对解决方案有任何问题,欢迎在评论中提问。

PS: 我现在是 Kaggle 竞赛专家 (Competitions Expert) :)
Muhammad Ibrahim Qasmi

同比赛其他方案