返回列表

7th place solution

654. BirdCLEF+ 2025 | birdclef-2025

开始: 2025-03-10 结束: 2025-06-05 环境监测 数据算法赛
第 7 名解决方案 - BirdCLEF 2025

第 7 名解决方案

作者: yokuyama & RihanPiggy

竞赛: BirdCLEF 2025

发布时间: 2025-06-11

排名: 第 7 名

首先,我要感谢 BirdCLEF 2025 的组织者举办了这场精彩的竞赛。这是一次充满挑战却又收获颇丰的经历。
祝贺所有参赛者的辛勤工作和出色的解决方案。我很兴奋能分享导致我们取得这一结果的方法。

我还要特别感谢我的队友 rihanpiggy。我们一起攻克了许多 Kaggle 竞赛——有时是对手,有时是队友——在这个过程中我从他身上学到了很多。如果没有他的努力和支持,我不可能达到 Grandmaster 的水平。

太长不看版 (TL;DR)

我们的解决方案由两种模型类型的集成组成:SED 风格 CNN2021 年第 2 名风格 CNN。与前几年一样,结合不同 pipeline 训练的模型被证明能有效提高排行榜分数。对于 SED 模型,构建高质量的伪标签对于实现强劲性能至关重要。一些小技巧,如后处理和测试时增强(TTA),在今年也被证明 consistently 有效。

CNNs 1: RihanPiggy 部分

训练数据集

  • 竞赛数据(手动移除 CSA 音频文件中的人声)
  • 从 Xeno-canto 下载的额外音频文件

模型

混合专家模型:

  • SED 使用 tf_efficientnetv2_s_in21k(所有物种)
  • SED 使用 hgnetv2_b5.ssld_stage2_ft_in1k(146 种鸟类)
  • SED 使用 tf_efficientnetv2_s_in21k(70 种训练样本较多的鸟类)
  • CNN 使用 hgnetv2_b3.ssld_stage2_ft_in1k(70 种主要鸟类,训练样本较多)
  • SED 使用 hgnetv2_b5.ssld_stage2_ft_in1k(其他罕见的 136 种物种,训练样本相对较少)

有效的方法

迭代式伪标签训练

受 BirdClef2024 第 3 名方案的启发,在训练期间,我们以 50% 的概率从训练声景中随机采样音频片段并对应伪标签。
结果是,每个训练批次包含 50% 的训练声景和 50% 的训练音频。
这种方法对所有物种模型和 70 种主要物种模型效果很好,但对 146 种鸟类模型和其他罕见的 136 种物种模型效果不佳。
迭代运行训练和伪标签循环可以不断提高 LB。我们运行了这个循环 4 次。
对于 70 种主要鸟类模型,伪标签必须用 labels = labels - np.min(labels) 进行归一化才能使该方法生效。

平滑后处理(未使用,因为导致集成 LB 下降)

使用权重 [0.1, 0.8, 0.1] 对 2 个邻居进行平滑处理可以提高每个模型的公共和私有 LB。

使用 BirdNet 从训练声景中提取音频片段

对于 146 种鸟类模型和 70 种主要鸟类模型,添加使用 BirdNet 从训练声景中提取的音频片段效果很好。
BirdNet 覆盖 145 种鸟类,我们对训练声景进行 BirdNet 推理,并提取置信度 > 0.1 的音频片段。
这种方法对 146 种鸟类模型和 70 种主要鸟类模型效果很好,但对所有物种模型和其他罕见的 136 种物种模型效果不佳。
这种方法显著提高了公共 LB,但仅略微提高了私有 LB。

仅在其他罕见的 136 种物种模型上实施的技巧

  1. 防止昆虫纲与其他物种混淆。昆虫纲仅与其他昆虫纲物种 mixup。此技巧提高了公共 LB,但严重损害了私有 LB。
  2. 0.25 * focal loss。恢复了被技巧 1 损害的私有 LB。
  3. 除了上述迭代训练外,使用伪标签从训练声景中提取音频片段大大提高了公共 LB,但略微损害了私有 LB。
    最终,基线模型显示出最好的私有 LB。

线性模型合并

模型合并提高了 tf_efficientnetv2_s_in21k(所有物种,70 种主要鸟类)模型的 LB。合并太多模型会损害 LB,3 个模型就足够了。
然而,对于 hgnetv2 模型,模型合并会破坏模型。

模型多样性很重要

原始信号模型、简单 CNN 模型提高了集成 LB。

训练设置和 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

推理

  • 使用异步推理队列的 OpenVINO
  • 使用 NNCF 量化 SED 模型

无效的方法

  • 训练时长为 60s 的 SED 模型,旨在直接捕捉声景中的所有全局上下文。
  • 物种分类的辅助损失。
  • 两栖纲、哺乳纲、昆虫纲专家模型。为两栖纲调整更窄的 fmin 和 fmax 范围反而使模型更差。
  • 使用 BirdVocal 从训练声景中提取音频片段。
  • 对不同物种组进行 separate mixup,例如鸟类、昆虫纲、哺乳纲、两栖纲。
  • 实施 BirdClef2024 第 2 名的伪标签方法。
  • 计算每个音频的峰值并从峰值采样音频。(类似 RMS 采样)
  • 使用 BirdNet 和 BirdVocal 的 logits 进行知识蒸馏。(这在 BirdClef2024 中对我有效)

CNNs 2: yokuyama 部分

训练数据集

  • 竞赛数据(手动处理以移除 CSA 音频文件中的人声)
  • 时间序列手工标注的竞赛数据(针对某些稀有类别)

模型

我使用了两个基于 2021 年第 2 名解决方案风格的 CNN 模型。两个模型都在相同的数据集上使用相同的 mel 频谱图设置和骨干网络进行训练。唯一的区别在于训练和推理期间使用的音频片段长度:一个模型使用 5 秒片段,另一个使用 8 秒片段。

基线配置如下:

  • n_mels: 256
  • n_fft: 2048
  • fmin: 50
  • fmax: 14000
  • image_width: 288
  • 骨干网络:hgnetv2_b3.ssld_stage1_in22k_in1k
  • 池化:gem with learnable p
  • 头部:linear
  • 损失:bce
  • 优化器:adamw
  • max_lr: 1e-3
  • weight_decay: 1e-3
  • lr_scheduler linear
  • epochs: 40
  • batch_size: 64
  • 增强:AddBackgroundNoise, Gain, NoiseInjection, GaussianNoiseSNR, PinkNoiseSNR, SpecAugment, SpecMixup
  • 推理时间:~3 分钟(单模型)

TTA (测试时增强)

一个简单的测试时增强是有效的:我们将音频归一化为固定的峰值音量 (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

无效的方法

  • 伪标签:尽管付出了巨大努力,但与 SED 模型不同,它并没有带来明显的性能提升。
  • 基于分类类别和科的辅助损失。
  • 基于尺度不变 SNR、SAR、其他音频属性的辅助损失...
  • 除 mel 频谱图之外的替代前端(例如 CQT, PECN)无效。

额外实验(未完全验证)

  • 一些 DINO 预训练的 ViT 模型显示出比 CNN 更好的性能。
    • vit_small_patch14_reg4_dinov2 甚至在早期实验中就取得了高 0.88 的私有 LB 分数,超过了我们的 hgnet。然而,由于推理时间限制,它未被包含在最终提交中。
  • 更彻底的人工循环清洗和手动标签修正对公共 LB 影响有限,但在私有 LB 上带来了约 +0.02 的明显增益。

集成

对于最终集成,我们对每个模型的 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 的私有分数。

同比赛其他方案