687. CSIRO - Image2Biomass Prediction | csiro-biomass
感谢主办方举办这次比赛。这是一个直接的图像任务,我非常享受参与的过程。
DINOv3 的密集特征(dense features)被证明是非常强大的。我的方法侧重于有效地利用这些特征,同时尽可能保留预训练权重。
使用全局特征(CLS)会丢弃 DINOv3 强大的局部分离能力,因此我将模型设计得尽可能接近分割任务。
| 模型 | 分辨率 | CV (OOF R²) | 公开榜 | 私有榜 |
|---|---|---|---|---|
| DinoV3 Huge+ (child-exp012) | 672 | 0.809 | 0.75 | 0.65 |
| DinoV3 Huge+ (child-exp017) | 768 | 0.816 | 0.75 | 0.65 |
| DinoV3 Huge+ (child-exp026) | 672 + PatchDropout | 0.811 | 0.75 | 0.65 |
| DinoV3 Huge+ (child-exp032) | 864 | 0.814 | 0.75 | 0.65 |
| DinoV3 Large (child-exp020) | 864 | 0.818 | 0.73 | 0.66 |
| DinoV3 Large (child-exp037) | 960 | 0.819 | 0.74 | 0.65 |
当使用 PCA 可视化 DINOv3 特征时,你可以看到即使是零样本(zero-shot),DINOv3 在局部分离方面也表现出色。它可以清晰地区分 Green(绿草)、Clover(三叶草)、Dead(枯草,部分)和 Soil(土壤)。
关注 Clover:

关注 Dead:

注意:我特意选择了视觉上分离清晰的样本。并非所有样本都能如此清晰地分离。
尽管具有出色的局部分离能力,但在 CLS token 上附加一个头会丢弃空间信息,这感觉很浪费。相反,我采用了密度估计(Density Estimation)方法:从每个 patch token 预测局部生物量密度,并在整个图像上求和(积分)。
输入图像 (1000x1000, 左右分割的一半)
| 调整大小
输入 (672x672x3)
|
DINOv3 ViT-Huge+ 骨干网络 (冻结 -> 渐进式解冻)
|
Patch tokens [B, 1764, 1280] (42x42 patches, 每个 1280 维)
| 重塑
[B, 1280, 42, 42]
|
1x1 Conv (1280 -> 5) <- 这里只有 6,405 个可学习参数
|
Softplus (非负约束:生物量密度在物理上是非负的)
|
密度图 [B, 5, 42, 42] (每个 patch 的 5 个目标的局部密度)
|
Sum (聚合所有 patches)
|
预测 [B, 5] = [Green, Dead, Clover, GDM, Total]
关键点:
让训练收敛具有挑战性。我尝试过 Conv3x3 来学习相邻 patches 之间的关系,但无论怎么调整学习率都无法解决 NaN loss 问题。只有使用更简单的 Conv1x1 训练才稳定下来。我还必须监控前 5-8 个 epoch 的训练,检查验证分数是否稳定,手动停止那些没有收敛的运行。
分辨率至关重要。更高的分辨率 consistently 提高了 CV 和 LB 分数。我从 224 开始,最终达到了 960。
更高的分辨率需要更多的训练时间和显存,所以我从 256 开始实验,逐渐增加到 512,然后在比赛的最后 3 周增加到 768。最终模型在 Large 上使用 960px,在 Huge+ 上使用 864px。在 864px 上训练 Huge+ 需要 20 小时(5 折),使用 76GB 显存,batch size 为 8 (Colab A100 80GB)。
渐进式解冻(Gradual Unfreeze)是关键。对于像 DINOv3 这样具有强大预训练权重的模型,从一开始就进行全量微调会破坏宝贵的预训练表示。
例如,DINOv3 Large 总共有 24 个 Transformer 块。设置 max_unfreeze_ratio=50%,到第 40 个 epoch 时最多解冻 12 个块。
一个重要的设计选择是不使用学习率调度器。大多数调度器应用 warmup 然后逐渐衰减,但使用这种策略时,后期解冻的层将无法获得足够的学习。相反,我线性增加了骨干网络的学习率:
许多公共 notebook 将 2000x1000 的图像分为左右两半,然后在输入到头之前连接最终层的输出。
我的方法不同:分为左右两半并将标签也减半,有效地使训练数据翻倍。这种方法在 DINOv3、EVA02-CLIP 和 SigLIP 上始终优于 2-stream 连接方法。
基础损失是 SmoothL1Loss,预测所有 5 个目标。
关键补充是ConsistencyLoss(一致性损失),它强制执行物理关系:
我最初认为只预测 3 个独立目标(Green, Dead, Clover)并推导其余目标会更高效。然而,由于 Clover 和 Green 之间似乎存在一些标签噪声,预测所有 5 个目标并加上一致性约束可以作为有效的正则化。
采用来自此讨论帖的基于组的方法稳定了 CV-LB 相关性。
由于 Clover 和 Dead 难以预测,我使用分层(stratification)将它们均匀分布到各折中:
df_wide['clover_dead_presence'] = (
(df_wide['Dry_Clover_g'] > 0).astype(int).astype(str) + '_' +
(df_wide['Dry_Dead_g'] > 0).astype(int).astype(str)
)
这被用作 StratifiedGroupKFold 的分层变量,确保 Clover 或 Dead 值为零的样本均匀分布在各折中。由于许多样本的这些目标值为零,简单的随机分割可能会导致某些折出现偏差。
| 值 | Clover | Dead | 含义 |
|---|---|---|---|
| 0_0 | 无 (=0) | 无 (=0) | Clover 和 Dead 均为零 |
| 0_1 | 无 (=0) | 存在 (>0) | 仅 Dead |
| 1_0 | 存在 (>0) | 无 (=0) | 仅 Clover |
| 1_1 | 存在 (>0) | 存在 (>0) | 两者都存在 |
这使得 CV-LB 相关性非常稳定。
使用的增强方法:
HorizontalFlip (p=0.5)
VerticalFlip (p=0.5)
RandomRotate90 (p=0.5)
Rotate (limit=10, p=0.3)
RandomResizedCrop (scale=0.85-1.0, ratio=0.95-1.05, p=0.5)
ColorJitter (brightness=0.3, contrast=0.3, saturation=0.3, hue=0.15, p=0.7)
RandomGamma (gamma_limit=80-120, p=0.3)
RandomBrightnessContrast (brightness=0.25, contrast=0.25, p=0.5)
GaussianBlur (blur_limit=3-5, p=0.2)
RandomShadow (p=0.1)
RandomToneCurve (p=0.2)
关于 RandomResizedCrop:我最初认为不应该使用它,因为它会改变图像中可见的草量。然而,它实际上提高了准确率。这可能是因为不同摄影师的相机到地面的距离不同,这种增强提高了对此类变化的鲁棒性。
过于激进的增强会降低性能:
鉴于数据集较小且是回归任务,预计会有显著的榜单波动。受Feedback Prize 竞赛中 Psi 的讨论启发,我使用 2 个种子进行训练和评估,以验证模型是否实现了真正的泛化并提高鲁棒性。
我从比赛早期就开始参与,逐步升级模型:SigLIP -> EVA02-CLIP -> DINOv3。
我一开始并不知道 DINOv3 会这么强。我通常更喜欢基于 CLIP 的模型,但在这次比赛中 DINOv3 具有压倒性的优势。
以下所有模型都使用相同的训练管道(增强、学习率、渐进式解冻)。仅调整了 batch size 以适应显存。
| 模型 | CV | 公开榜 | 私有榜 |
|---|---|---|---|
| SigLIP | 0.75 | 0.66 | 0.55 |
| EVA02-CLIP | 0.75 | 0.69 | 0.584 |
| DINOv3 [全局特征] | 0.79 | 0.73 | 0.64 |
| DINOv3 [密集特征] | 0.81 | 0.75 | 0.66 |