返回列表

9th place solution

654. BirdCLEF+ 2025 | birdclef-2025

开始: 2025-03-10 结束: 2025-06-05 环境监测 数据算法赛
第 9 名解决方案 - BirdCLEF 2025
作者: I2nfinit3y & @yuanzhezhou
排名: 第 9 名 (Gold Medal)
发布时间: 2025-06-06

第 9 名解决方案

感谢主办方和本次竞赛的每一位参与者!还要感谢我的伟大队友 @yuanzhezhou!我从这次有趣的比赛中学到了很多,很高兴能获得我的第一枚金牌。
我们的解决方案如下。

1. 数据集

仅使用 2025 年的 train_audio 和 train_soundscapes。

2. 数据预处理

我们移除了音频中 50% 的人声。因为我们发现移除所有人声会降低模型性能。

3. 训练

第一阶段模型

采样

在我们的实验中,RMS 采样优于随机采样。

模型

我们使用了 2023 年第 2 名解决方案 中开放的 sed 模型。感谢 @honglihang 提供的出色模型,我只需要修改一些配置和代码,就得到了一个在 LB 上得分 0.850+ 的单模型。

此外,我们还使用了公开 Notebook 中的 cnn 模型作为我们的第一阶段模型。

损失函数

对于 sed 模型,我们在训练中使用 FocalBCE 损失。
对于 cnn 模型,我们使用 CE+BCE 损失。在训练早期,我们使用 ce 损失可以提高收敛速度,在训练后期使用 bce 损失。

数据增强

对于原始信号:

对于 Mel 频谱图:

  • Mixup2
  • 时间掩码 (Time masking)
  • FilterAugment,概率为 0.5
  • 频率掩码 (FrequencyMasking),概率为 0.5
  • 粉红噪声 (PinkNoise),概率为 0.5

Mel 频谱图参数

target_duration  = 5
img_size = 384
SR = 32000
n_fft = 2048
n_mels = 256
f_min = 20
f_max = 16000
hop_length = target_duration * SR // (img_size - 1)

melspec_transform = torchaudio.transforms.MelSpectrogram(
            sample_rate=SR,
            hop_length=hop_length,
            n_mels=n_mels,
            f_min=f_min,
            f_max=f_max,
            n_fft=n_fft,
            pad_mode="constant",
            norm="slaney",
            onesided=True,
            mel_scale="htk",
        )
db_transform = torchaudio.transforms.AmplitudeToDB(
            stype="power", top_db=80
        )

# 对于 cnn
SR = 32000
n_fft = 2048
n_mels = 128
f_min = 20
f_max = 14000
hop_length = 1024

EMA (指数移动平均)

在训练中应用 ema 使我的模型更加鲁棒,并缩小了不同 epoch 之间的差距。

骨干网络

对于 sed 模型,我们使用 efficientnetv2_b3, eca_nfet_l0 和 seresnext26t,并在 10 秒片段上进行训练。对于 cnn 模型,我们使用 efficientnet_b0,并在 5 秒片段上进行训练。

通过混合这些模型,我们可以得到一个在 LB 上得分为 0.888 的 sed 模型和一个在 LB 上得分为 0.840+ 的 cnn 模型。

第二阶段模型

我们使用我们的 sed、cnn 和公开 Notebook 中的模型来生成每 10 秒块伪标签。对于每个样本,我们仅使用前 10% 的类别作为软标签,其他类别设置为零。然后,我们使用相同的管道训练新的 sed 模型。在这个阶段,我们选择 efficientnetv2_b3 和 seresnext26t 作为我们最终模型的骨干网络。

通过使用伪标签,我们在 LB 上获得了 0.02+ 的提升。

4. 推理与 TTA

我们将 10 秒块放入模型中,并使用 2 秒作为窗口长度来应用 TTA。您可以在我的推理 Notebook 中查看更多细节。所有模型首先都转换为 onnx 格式。

5. 集成与后处理

我们没有太多时间调整集成和后处理策略,所以我们只是使用加权平均来集成模型。

对于后处理:

  • 使用 [0.2, 0.6, 0.2] 平滑预测 (在 LB 上提升 0.005~0.01)
  • 在推理中 fmax - 2000 (在 LB 上提升 0.002)

6. 模型得分

我们注意到随机种子对结果有相对巨大的影响。所以我们集成了具有不同种子但相同管道的模型。

骨干网络 种子 Public Private
efficientnetv2_b3 ① 42 0.903 0.913
efficientnetv2_b3 ② 3407 0.904 0.915
efficientnetv2_b3 ③ 2025 0.896 0.915
seresnext26t ④ 42 0.899 0.908

通过集成 ①、②、④,我们在 LB 上得到 0.913,在 Private 上得到 0.921,我们选择它作为最终提交。
通过集成 ①、②、③,我们在 LB 上得到 0.913 (较低),在 Private 上得到 0.922,但我们错过了这次提交。

实际上,在我们最后一次提交机会中,我们尝试集成所有 4 个模型,它在十个 60 秒的声音景观上大约花费了 1 分 20 秒。看起来不会超时,但确实超时了。

但是,当我们在比赛结束后使用单线程而不是多线程再次提交时,它运行良好,并在 Private 上获得了 0.924。看来 onnx+ 多线程会导致 bug,这使我们错过了进入前 5 名的机会 :(

7. 一些在 LB 上无效但在 Private 上有效的想法

由于 LB 的不稳定性,我们遗憾地错过了一些在 LB 上无效但在 Private 上表现良好的想法。

按概率重采样伪标签

窗口长度为 10 秒,步长为 3 秒,模型预测每个 60 秒的声音景观。然后,我们在每个声音景观上得到 17 个片段,并选择概率总和最高的前 3 个片段。
这个想法让我得到了一个在 LB 上得分为 0.891 但在 Private 上得分为 0.916 的模型。

在后处理中通过最大值和均值调整概率

这个想法来自 2024 年第 3 名解决方案

def max_mean_post(sub_df, cfg):
    print("Postprocessing submission predictions...")
    sub_df.loc[:, cfg.bird_cols] = logit(sub_df.iloc[:, 1:].values)
    sub_df["group"] = sub_df['row_id'].str.rsplit('_', n=1).str[0]

    dfg_max = sub_df[["group"] + cfg.bird_cols].groupby('group').max().reset_index()
    dfg_mean = sub_df[["group"] + cfg.bird_cols].groupby('group').mean().reset_index()

    delta = dfg_mean[cfg.bird_cols].mean(1) - dfg_max[cfg.bird_cols].mean(1)
    for c in cfg.bird_cols:
        dfg_max[c] += delta

    sub_df = sub_df.merge(dfg_max, how="left", on="group", suffixes=('', '_delta'))
    for c in cfg.bird_cols:
        sub_df[c] = expit((sub_df[c] + sub_df[c + "_delta"]) / 2)

    return sub_df.loc[:, ['row_id']+cfg.bird_cols].reset_index(drop=True)

它在 LB 上降低了 0.007,但在 Private 上提升了 0.001~0.003。

8. 无效的方法

  • 原始信号模型
  • 使用排名平均进行集成
  • 带能量权重的 RMS 采样
  • 使用常用名作为辅助目标
  • 低秩幂后处理
  • 向原始信号添加高斯噪声

感谢大家!还有很多想法我没有时间尝试。希望能在 BirdCLEF 2026 中验证它们!

同比赛其他方案