654. BirdCLEF+ 2025 | birdclef-2025
我们要感谢乌克兰武装部队、乌克兰国家安全局、乌克兰国防情报局和乌克兰国家紧急服务局,他们提供了安全和保障,使我们能够参与这场伟大的竞赛,完成这项工作,并帮助科学、技术和商业不停滞而是向前发展。
随着竞赛接近尾声,俄罗斯再次对和平的乌克兰城市发动了大规模导弹和无人机袭击。这一恐怖行为针对平民住宅和关键基础设施,造成广泛破坏和人员伤亡。此外,使用“双重打击”战术——两次袭击同一地点以杀害正在援助被困在受损和毁坏建筑物中人员的救援人员——进一步突显了袭击的非人道性。这一行动再次证实了俄罗斯作为一个恐怖主义国家的地位。
如果我们分解整个 BirdCLEF 竞赛的历程,可以说直到 2023 年(含)都是“额外数据就是一切”的时代,但自 2024 年以来,范式已转变为“伪标签的兔子洞之旅”。所以让我们跳进去,探索半监督学习的仙境。
我们下载了额外的 Xeno-Canto 数据,考虑到 2023 年竞赛 中提到的 bug,结果大约有:
| 来源 | 样本数量 |
|---|---|
| Xeno Canto | 7,376 |
| 往届竞赛 | 90 |
这些样本不在当年的数据集中,但具有适当的 primary_label 标签。
有趣的是,使用解析后的 XC 和 iNat 数据并没有改善我的模型,但给 @vialactea 带来了轻微的性能提升。
关于主要训练数据就是这样了。
我们在 5 秒和随机选择的片段上训练模型。根据去年的经验,我们最初尝试了三种选择 5 秒片段的方法:从整个音频中随机选取 5 秒,从前 7 秒中随机选取 5 秒,以及从前 7 秒或后 7 秒中随机选取 5 秒。后两种方法的理由是,录音师通常会在动物发声时开始录音,在动物停止发声时停止录音。这将有助于模型避免误报。7 秒只是为了增加多样性。
在初步测试中,最后一种方法提供了更好的结果。然而,有些物种只有很少的音频——有些情况下只有 2 个——我们担心过拟合。因此,我们决定检查这些物种的音频,以手动识别发声部分。以下图表说明了我们发现的三种情况:



不幸的是,这是所有 BirdCLEF 竞赛的真实故事。我们探索了一系列验证策略。每个策略的核心在于按 primary_label 分层并按 author 分组。之后,我们必须弄清楚如何处理采样不足的物种。所以,我们尝试了:
我们主要采用了第一种和第三种策略。
当然,最有趣的问题是:验证分数与我们的公共排行榜分数相关吗?经过一些重大改进后,我们看到两个指标都有显著的积极变化,但当在 ~1% AUC 范围内调整事物时,相关性几乎不存在。

正如你在图中看到的,当我们突破 0.9 公共里程碑时,相关性决定休息了。
我们遵循了以往鸟类和音频竞赛的良好传统,使用了 Spec → 2D CNN 方法。但特别是今年,骨干网络确实起了作用!我们尝试了 ConvNeXt 系列,它在 2023 年 showed 不错的结果,但这次失败了——同时也引入了非常大的延迟。ResNeXt 系列表现也不好。接下来的编码器成为了我们的最爱:
在某些设置中,EfficientNet 显示了更好的结果,而在其他设置中,nfnet_l0 占据了上风。此外,两者都非常适合集成。
关于分类头,事情很标准:我使用了 SED 头,而 @vialactea 使用了多层感知机。
两个骨干网络的共同部分:
优化器选择在骨干网络之间有所不同:
我认为这里最重要的部分是平衡策略。我们探索了几种,并在最终集成中使用了不同的策略:
sample_weights = (
all_primary_labels.value_counts() /
all_primary_labels.value_counts().sum()
) ** (-0.5)
我们最终使用了 Balanced 策略和 Balanced + Upsampling 的组合。
现在我们来到secret sauce 的第一个成分——预训练!
从最早的实验开始,@vialactea 在去年预训练的骨干网络上进行微调取得了非常好的结果。一般概念很简单:
挑选正确的 checkpoint 是一门单独的艺术。我们尝试使用最后一个、最好的以及前 3 个最好的平均值。后两种方法效果最好。
仅使用预训练的骨干网络并丢弃分类头。
最初,我尝试仅在过去的竞赛数据上进行预训练——失败了。那个初始化使事情变得更糟。然后我采用了 @vialactea 的方法并确认它是一个杀手级功能:分数从 0.83–0.84 跳到了 0.86–0.87。
在去年预训练模型成功之后,我们决定在新鲜的巨大 Xeno-Canto 快照上训练新的模型。不幸的是,进展不如预期:它们的表现与 2024 预训练模型相当或略差。尽管如此,一个 eca_nfnet_l0 checkpoint 显示出希望并被选入最终集成。
预训练后,我们在主要数据集上进行了微调,没有使用诸如骨干网络和头部的差异学习率等复杂技术。
这里我完全参考 2023 年的 write-up,因为我们使用了完全相同的设置。我们尝试关闭 RandomFiltering 和/或 SpecAug,这导致了稍好的 CV 分数但更差的 LB。所以决定已定:不要让模型的生活太轻松——机器人应该工作!
我们使用了以下算法:
train_soundscapes。伪代码看起来像这样:
…
# primary_label_prob 包含 5 秒块中的最大概率
soundscape_df = soundscape_df[soundscape_df["primary_label_prob"] > 0.5]
soundscape_df[scored_species < 0.1] = 0
…
下一步是正确采样。为了避免破坏(进一步)已经偏斜的数据分布并更好地控制采样伪标签的数量,我们使用了以下策略:
soundscape_df 中。如果是,则以某种概率(在我们的例子中为 0.4)选择它代替当前样本,并将硬标签替换为伪软标签向量。第一次伪迭代将我们的分数从 0.86–0.87 提高到了 0.89–0.895。但如前所述——只有第一次...
下一个明显的步骤?堆叠更多的伪迭代!所以,我们使用在第一次伪迭代样本上训练的模型进行预测,这次使用未通过最大概率阈值的 soundscapes。这将分数提升到了 0.90–0.91 范围。
不幸的是,第三次迭代没有带来明显的收益,所以我们稍微改变了策略。主要问题是我们无法重新预测前一次迭代的 soundscapes,因为存在数据泄露。但是如果我们把 soundscapes 分成折呢?现在我们可以用“伪训练”模型以 OOF(折外)模式预测它们。遗憾的是,重复这种方法两次并没有将分数提升到第二次迭代以上。然而,它给了我们以略有不同方式生成的伪标签——然后我们将它们与之前的伪标签结合,形成疯狂的伪混合。基于 tf_efficientnetv2_s 的模型在这个混合上训练成为了我们的顶级独立模型,公共得分 0.917,私有得分 0.91。
| 伪迭代 | 选定文件数量 |
|---|---|
| 1 | 4430 |
| 2 | 1483 |
| 3 | 1437 |
与我们 2023 年的解决方案相反,今年我们决定严重过拟合。当然不是真的——但我们确实基于最佳验证 ROC AUC 选择了 checkpoints。我们尝试使用最后一个以及前 3 个最佳 checkpoints 的平均值,但这些通常在排行榜上表现较差。幸运的是,由于需要预测的文件较少,我们不太担心, simply 提交了每个实验的所有 5 折模型。
我们使用了一个简单的后处理步骤:对于每个音频文件,我们将所有块级预测乘以该文件中每个鸟类类别的最高概率。这会提升 consistently 强的预测并抑制较弱的预测,而不影响文件内排名(因为所有块都同等缩放)。然而,它改变了文件间的全局排名——例如,强文件(均值 = 0.9)中的 0.2 块变为 0.18,而弱文件(均值 = 0.4)中的 0.2 块变为 0.08。这 consistently 改善了结果,通常对于较弱的模型提高 0.01,对于较强的模型提高 0.005 左右。后处理函数的代码:
def postprocessing(input_df, top=1):
only_probs = input_df.iloc[:, 1:].values
N, F = only_probs.shape
only_probs = only_probs.reshape((N//12, 12, F))
mean_ = np.mean(np.sort(only_probs, axis=1)[:, -top:], axis=1, keepdims=True)
only_probs *= mean_
input_df.iloc[:, 1:] = only_probs.reshape((N, F))
return input_df
我们在推理期间独立预测每个 5 秒块。我们也尝试了 previous TOP 4 团队 使用的重叠窗口方法(让我们遵循他们的命名称之为 TTA)。它在公共 LB 上表现良好,将最佳实验的分数从 0.917 提升到公共 0.922,从私有 0.91 提升到 0.918。不幸的是,它在公共排行榜上与后处理配合不佳。
我们的最终提交包含 3 个模型:
所有这些都由后处理支持,并且没有 TTA 预测。
强者将应付我们的长读,但对于其他人——一个简短的消融表。
| 改进 | 公共分数 |
|---|---|
| Baseline | 0.83-0.84 |
| + 预训练 | 0.86-0.87 |
| + 伪迭代 1 | 0.89-0.895 |
| + 伪迭代 2-3 | 0.9-0.91 |
| + TTA | 0.922 |
| 后处理 | +0.005-0.01 |
我希望你在阅读时没有睡着。首先,我要祝贺 @vialactea 达到 Grandmaster 排名!很荣幸能与你一起参加这次竞赛!
我要感谢整个 Kaggle 社区,祝贺所有参与者和获胜者。特别感谢康奈尔鸟类学实验室、LifeCLEF、Google Research、Xeno-canto、@stefankahl、@tomdenton、@holgerklinck 和 @avocadomastermind。你们都在讨论中非常活跃,分享了数据集和有趣的材料,回答了所有问题,当然,还准备了这么酷的竞赛!