654. BirdCLEF+ 2025 | birdclef-2025
首先,我要感谢 BirdCLEF 2025 的组织者举办了这场精彩的竞赛。这是一次充满挑战却又收获颇丰的经历。
祝贺所有参赛者的辛勤工作和出色的解决方案。我很兴奋能分享导致我们取得这一结果的方法。
我还要特别感谢我的队友 rihanpiggy。我们一起攻克了许多 Kaggle 竞赛——有时是对手,有时是队友——在这个过程中我从他身上学到了很多。如果没有他的努力和支持,我不可能达到 Grandmaster 的水平。
我们的解决方案由两种模型类型的集成组成:SED 风格 CNN 和 2021 年第 2 名风格 CNN。与前几年一样,结合不同 pipeline 训练的模型被证明能有效提高排行榜分数。对于 SED 模型,构建高质量的伪标签对于实现强劲性能至关重要。一些小技巧,如后处理和测试时增强(TTA),在今年也被证明 consistently 有效。
混合专家模型:
受 BirdClef2024 第 3 名方案的启发,在训练期间,我们以 50% 的概率从训练声景中随机采样音频片段并对应伪标签。
结果是,每个训练批次包含 50% 的训练声景和 50% 的训练音频。
这种方法对所有物种模型和 70 种主要物种模型效果很好,但对 146 种鸟类模型和其他罕见的 136 种物种模型效果不佳。
迭代运行训练和伪标签循环可以不断提高 LB。我们运行了这个循环 4 次。
对于 70 种主要鸟类模型,伪标签必须用 labels = labels - np.min(labels) 进行归一化才能使该方法生效。
使用权重 [0.1, 0.8, 0.1] 对 2 个邻居进行平滑处理可以提高每个模型的公共和私有 LB。
对于 146 种鸟类模型和 70 种主要鸟类模型,添加使用 BirdNet 从训练声景中提取的音频片段效果很好。
BirdNet 覆盖 145 种鸟类,我们对训练声景进行 BirdNet 推理,并提取置信度 > 0.1 的音频片段。
这种方法对 146 种鸟类模型和 70 种主要鸟类模型效果很好,但对所有物种模型和其他罕见的 136 种物种模型效果不佳。
这种方法显著提高了公共 LB,但仅略微提高了私有 LB。
模型合并提高了 tf_efficientnetv2_s_in21k(所有物种,70 种主要鸟类)模型的 LB。合并太多模型会损害 LB,3 个模型就足够了。
然而,对于 hgnetv2 模型,模型合并会破坏模型。
原始信号模型、简单 CNN 模型提高了集成 LB。
| 编号 | 实验 | 架构 | 训练时长 | n_mels | n_fft | fmin | fmax | 图像尺寸 | 公共 LB | 私有 LB |
|---|---|---|---|---|---|---|---|---|---|---|
| 1 | 所有物种模型基线 | SED v2s | 10s | 256 | 2048 | 0 | 16000 | 384 | 0.866 | 0.873 |
| 2 | 所有物种模型伪标签 iter1 | SED v2s | 10s | 256 | 2048 | 0 | 16000 | 384 | 0.894 | 0.893 |
| 3 | 所有物种模型伪标签 iter2 | SED v2s | 10s | 256 | 2048 | 0 | 16000 | 384 | 0.898 | 0.900 |
| 4 | 所有物种模型伪标签合并 iter2 iter1 基线 | SED v2s | 10s | 256 | 2048 | 0 | 16000 | 384 | 0.891 | 0.908 |
| 5 | 所有物种模型伪标签合并 iter3 iter2 iter1 | SED v2s | 10s | 256 | 2048 | 0 | 16000 | 384 | 0.886 | 0.903 |
| 6 | 所有物种模型伪标签合并 iter4 iter3 iter2 | SED v2s | 10s | 256 | 2048 | 0 | 16000 | 384 | 0.894 | 0.908 |
| 编号 | 实验 | 架构 | 训练时长 | n_mels | n_fft | fmin | fmax | 图像尺寸 | 公共 LB | 私有 LB |
|---|---|---|---|---|---|---|---|---|---|---|
| 1 | 70 种主要鸟类模型基线 | SED v2s | 10s | 256 | 2048 | 0 | 16000 | 384 | 0.692 | 0.695 |
| 2 | 70 种主要鸟类模型伪标签 iter1 | SED v2s | 10s | 256 | 2048 | 0 | 16000 | 384 | 0.696 | 0.707 |
| 3 | 70 种主要鸟类模型伪标签 iter2 | SED v2s | 10s | 256 | 2048 | 0 | 16000 | 384 | 0.698 | 0.708 |
| 4 | 70 种主要鸟类模型伪标签合并 iter2 iter1 基线 | SED v2s | 10s | 256 | 2048 | 0 | 16000 | 384 | 0.699 | 0.710 |
| 5 | 70 种主要鸟类模型伪标签合并 iter3 iter2 iter1 | SED v2s | 10s | 256 | 2048 | 0 | 16000 | 384 | 0.699 | 0.710 |
| 6 | 70 种主要鸟类模型伪标签合并 iter4 iter3 iter2 | SED v2s | 10s | 256 | 2048 | 0 | 16000 | 384 | 0.700 | 0.705 |
| 编号 | 实验 | 架构 | 训练时长 | n_mels | n_fft | fmin | fmax | 图像尺寸 | 公共 LB | 私有 LB |
|---|---|---|---|---|---|---|---|---|---|---|
| 1 | 70 种主要鸟类模型基线 | CNN hgnetv2_b3 | 15s | 192 | 2048 | 50 | 14000 | 288 | 0.666 | 0.667 |
| 2 | 70 种主要鸟类模型伪标签 iter1 | CNN hgnetv2_b3 | 15s | 192 | 2048 | 50 | 14000 | 288 | 0.687 | 0.687 |
| 3 | 70 种主要鸟类模型伪标签 iter2 | CNN hgnetv2_b3 | 15s | 192 | 2048 | 50 | 14000 | 288 | 0.689 | 0.685 |
| 4 | 70 种主要鸟类模型伪标签 iter3 | CNN hgnetv2_b3 | 15s | 192 | 2048 | 50 | 14000 | 288 | 0.694 | 0.692 |
| 5 | 70 种主要鸟类模型伪标签 iter4 | CNN hgnetv2_b3 | 15s | 192 | 2048 | 50 | 14000 | 288 | 0.688 | 0.689 |
| 编号 | 实验 | 架构 | 训练时长 | n_mels | n_fft | fmin | fmax | 图像尺寸 | 公共 LB | 私有 LB |
|---|---|---|---|---|---|---|---|---|---|---|
| 1 | 146 种鸟类模型 | SED hgnetv2_b3 | 10s | 256 | 2048 | 50 | 14000 | 288 | 0.787 | 0.797 |
| 2 | 其他罕见的 136 种物种模型 | SED hgnetv2_b3 | 10s | 256 | 2048 | 50 | 14000 | 288 | 0.684 | 0.662 |
| 3 | RihanPiggy 模型集成 | - | - | - | - | - | - | - | 0.916 | 0.906 |
我使用了两个基于 2021 年第 2 名解决方案风格的 CNN 模型。两个模型都在相同的数据集上使用相同的 mel 频谱图设置和骨干网络进行训练。唯一的区别在于训练和推理期间使用的音频片段长度:一个模型使用 5 秒片段,另一个使用 8 秒片段。
基线配置如下:
hgnetv2_b3.ssld_stage1_in22k_in1kgem with learnable plinearbceadamwlinearAddBackgroundNoise, Gain, NoiseInjection, GaussianNoiseSNR, PinkNoiseSNR, SpecAugment, SpecMixup一个简单的测试时增强是有效的:我们将音频归一化为固定的峰值音量 (0.1),并平均原始和归一化 mel 频谱图的模型预测。这提高了对音量变化的鲁棒性。
for audio in test_loader:
audio_norm = 0.1 * audio / audio.abs().amax(dim=1, keepdim=True) # BxL, peak_volume = 0.1
spec = melspec_transform(audio)
spec_norm = melspec_transform(audio_norm)
preds_8s = 0.5 * (model_8s(spec) + model_8s(spec_norm))
[0.1, 0.3, 1.2, 2.4, 1.2, 0.3, 0.1] 进行平滑| 实验 | 私有 LB | 公共 LB |
|---|---|---|
| 5s CNN | 0.858 | 0.855 |
| 8s CNN | 0.851 | 0.854 |
| 5s + 8s + TTA | 0.860 | 0.861 |
vit_small_patch14_reg4_dinov2 甚至在早期实验中就取得了高 0.88 的私有 LB 分数,超过了我们的 hgnet。然而,由于推理时间限制,它未被包含在最终提交中。对于最终集成,我们对每个模型的 logits 应用了最小 - 最大缩放,遵循 2024 年第 11 名团队 使用的方法。缩放后的 logits 然后使用加权平均进行组合。
我们还采用了一种受 2024 年第 3 名团队 启发的声景级后处理技术,这使得排行榜分数提高了约 0.01。该方法使用其最大 logits 增强每个声景片段。简化版本如下:
def postprocess(df, bird_cols, p=0.5):
# 应用每个声景的调整
preds = df[bird_cols].values
for i in range(0,len(preds),12):
preds_soundscape = preds[i:i+12]
max_preds_soundscape = preds_soundscape.max(0, keepdims=True)
max_preds_soundscape = max_preds_soundscape + (preds_soundscape.mean() - max_preds_soundscape.mean())
preds_soundscape = preds_soundscape + 0.5 * max_preds_soundscape
preds[i:i+12] = preds_soundscape
df[bird_cols] = preds
# 最小 - 最大归一化和幂缩放
for bird_col in bird_cols:
df[bird_col] = df[bird_col].values - df[bird_col].min()
df[bird_col] = df[bird_col].values / df[bird_col].max()
df[bird_cols] = df[bird_cols].values ** p
return df
最终集成取得了 0.924 的公共分数和 0.922 的私有分数。