返回列表

3rd Place Solution

419. Rainforest Connection Species Audio Detection | rfcx-species-audio-detection

开始: 2020-11-17 结束: 2021-02-17 环境监测 数据算法赛
第三名方案

第三名方案

太长不看版 (TLDR)

我们的方案是8个模型的平均融合,这些模型基于 train_tp.csv 中所有录音 ID 的真正例标签(给定的 + 手工标注的标签)以及 train_fp.csv 中部分录音 ID(手工标注的标签)进行训练,并使用了大量的数据增强。此外,部分模型还使用了伪标签和一个手工标注的外部数据集进行训练。我们还通过对物种 3 进行阈值处理对融合结果进行了后处理。

从 308 次提交中可以看出,我们测试了很多技术,下面我将按类别分享更详细的信息。

数据准备

在尝试了许多技术后,我仍无法建立一个合适的验证框架,于是决定开始深入挖掘数据,发现在 train_tp.csv 给出的 t_min 和 t_max 标签范围内和范围外都有许多未标记的样本。在讨论区提到允许使用手工标注的物种后,我开始手动标注数据,在标注了 100 个录音 ID 后,我已经可以获得 > 0.9 的公共 LB 分数,并且本地 CV 分数相当一致,与公共 LB 有一定相关性。很自然地,我继续标注整个 train_tp.csv,因为它只有大约 1.3k 个录音 ID。进一步标注 train_fp.csv 对分数有帮助,但帮助很小,所以我在某个时候停止了。随着我对数据越来越熟悉,我一天可以标注 300 个录音 ID :),参考伪标签也帮了很大忙。我又把 train_tp.csv 过了几遍,以确保数据质量。我使用频谱图和听音策略来分析和标注数据,有些物种在频谱图上很容易发现,有些则更容易通过听音发现,在某些情况下,听音和频谱图的视觉检查可以作为一种多重验证技术来获得更高质量的标签,特别是当鸟类/青蛙距离录音机很远或者有像瀑布声这样的强噪音时。通过标注和分析数据,我还弄清了会出现的声音/噪音种类,这启发我尝试了一些数据增强方法,我将在下面分享。除了真正例标签,我还根据我对特定录音 ID 中标签完整性的信心,添加了“有噪声”和“无噪声”标签。我并不是一个完美的标注员,所以我想以不同的方式处理标签完整和不完整的录音 ID,这也将在下面分享。

我还从 train_tp.csv 中移除了一些标签,因为我发现有些真正例很可疑,我之前没有测试不移除标签的效果,所以不确定这有多大帮助。

此外,在找到组织者的论文后,我搜索了具有适当许可的包含这些物种的数据集,并找到了一个包含本次比赛物种且许可合适的数据集。但是没有任何标签,所以我也用与 train_tp.csv 相同的格式手动标注了它。https://datadryad.org/stash/dataset/doi:10.5061/dryad.c0g2t 。我重新上传了数据集 这里 并附带了我的手动标签。

我将额外的标签作为数据集上传到了 https://www.kaggle.com/dicksonchin93/extra-labels-for-rcfx-competition-data,随意使用,看看你是否能获得更好的单模型分数!我的单模型在公共 LB 上是 0.970。

建模 / 数据预处理

我使用了梅尔频谱图,参数如下:32kHz 采样率,跳长 716,窗口大小 1366,梅尔频带数 224 或 128。尝试了一堆方法,但简单地使用 3 层标准化的梅尔频谱图效果最好。图像尺寸为 (num_mel_bins, 750)。

使用 train_tp.csv 创建折叠可能会将一些训练数据泄漏到验证数据中,所以我将问题视为多标签目标,并使用 iterative-stratification 使用唯一的录音 ID 及其多标签目标将数据分层为 5 个分区。我有两个不同的 5 折分区,使用了不同版本的多标签,并在最终提交中混合使用了两者。

我在比赛期间的不同阶段使用了多种不同的音频时长,最佳时长在我的实现中有所不同,但最后,我使用了 5 秒的音频进行训练和预测,因为 LWLWRAP 分数在公共 LB 和本地验证上都更好。

在训练期间随机采样 5 秒音频,在预测时使用带有重叠的 5 秒滑动窗口,并取预测的最大值。我认为 5 秒音频的随机采样方式是一种数据增强方法,所以我将在下面的大量数据增强类别中解释它。

数据增强
  • 随机 5 秒音频样本:
    起始点是从参考 t_mins 和 t_maxes 之间随机选择的值,获取方式如下:
def get_ref_tmin_tmax_and_species_ids(
    self, all_tp_events, label_column_key="species_id"
):
        all_tp_events["t_min_ref"] = all_tp_events["t_min"].apply(
           lambda x: max(x - (self.period / 2.0), 0)
        )
        def get_tmax_ref(row, period=self.period):
            tmin_x = row["t_min"]
            tmax_x = row["t_max"]
            tmax_ref = tmax_x - (period / 4.0)
            if tmax_ref < tmin_x:
                tmax_ref = (tmax_x - tmin_x) / 2.0 + tmin_x
            return tmax_ref
        all_tp_events["t_max_ref"] = all_tp_events[
            ["t_max", "t_min"]
        ].apply(get_tmax_ref, axis=1)