返回列表

3rd Place Solution

666. Make Data Count - Finding Data References | make-data-count-finding-data-references

开始: 2025-06-11 结束: 2025-09-09 文献与知识发现 数据算法赛
第三名解决方案 - Deberta 集成与元数据提取

比赛排名: 第 3 名

作者: matheus (tomirol)

合作者: Eduardo Rocha de Andrade (arc144)

发布日期: 2025-09-10

第三名解决方案

Deberta 集成与元数据提取

感谢 Kaggle 和主办方举办此次比赛,事实证明这比我们最初预期的要具有挑战性得多。

我们的解决方案主要由两个步骤组成:

  • 通过正则表达式 + 后处理去除假阳性(FP)进行引用检索
  • 通过 6 折 Deberta-v3 集成 + 启发式规则进行类型分类

引用检索

与其他团队类似,我们的检索过程在这里非常直接。我们使用简单的正则表达式和规则来提取 DOI 和 accession IDs 的候选引用,然后与 2024 年 DataCite 年度转储以及 EUPMC 挖掘术语语料库中的一些额外条目进行比较。此外,我们还从 Crossref 2025 转储中提取了论文元数据信息。

这个简单的方法足以获得接近 100% 的 DOI 召回率,同时保持 minimal 的假阳性。虽然我们尝试了不同的方法,但这最终被证明是最可靠的。除此之外,我们还根据我们在训练集和 LB 探测中观察到的情况应用了一些小的启发式规则。

后处理与启发式规则

如果我们在 PDF 解析文本中找不到引用,我们也会在 XML 中搜索 accessions 和 DOIs。否则,我们简单地忽略 XML 而仅依赖 PDF。

我们还不得不忽略一些总是假阳性的 accession 和 DOIs,例如:

  • figshare DOIs
  • GCA_ accessions,这是假阳性的主要来源
  • HGNC:*, rs* 以及其他几种类型

最后,如果在同一篇文章中发现多个 accessions 和 DOIs,我们发现忽略 DOI 会在 LB 上带来提升。

数据集类型分类

一开始我们就知道类型分类是本次比赛中最重要的部分之一。原因是,由于我们使用 F1 分数作为指标,检索部分的 FN 或 FP 仅计为 1 个错误。然而,如果我们获得 TP 样本但错误分类了其类型,我们将得到 2 个错误:1 个 FN 用于缺失的正确类型,1 个 FP 用于错误的预测类型。

我们这里的解决方案主要包括两个步骤:6 折 Deberta-v3 集成和一些启发式规则。

启发式规则

与其他团队类似,我们首先使用了一些在 LB 和 CV 上都证明有效的规则:

  • 所有 dryad DOIs 和 SAMN accessions 都是 primary(主要)
  • 如果 DOI 标题或作者列表与论文相似(通过字符串编辑距离),则数据集是 primary
  • 如果 accession 在 EUPMC 中被引用超过 5 次,则是 secondary(次要)(简单概率检查)
  • 如果 DOI 有 5 次或更多引用,则是 secondary(简单概率检查)

剩余的引用随后使用 Deberta-v3 Large 集成进行分类。

训练细节

我们创建了一个 6 折 StratifiedGroupKFold(按 type 分层并按 article_id 分组),并在每个折上使用二元分类设置(顶部有分类头)训练了一个 deberta-v3 large。以下特征用于生成训练提示:

  • 论文中引用周围的上下文(1k 字符)
  • 文章文本的前 500 个字符
  • 论文和数据集标题(如有)

我们还尝试微调许多 LLM,如带有分类头的 Qwen2.5 7B,但事实证明这在训练过程中非常不稳定。Debertas 不仅更快,而且取得了更好的结果。

我们发现了两个非常重要的技巧来稳定训练:添加梯度裁剪和模型/权重 EMA。后者是一种非常简单的技术,包括通过 SGD/Adam 训练可训练模型,并在每个训练步骤后通过直接平均(加权)两个模型的权重来更新冻结的对应模型。使用 transformers 库,可以通过继承 Trainer 类轻松实现,如下所示:

from typing import Dict
import torch
from ema_pytorch import EMA
import copy

class EMATrainer(Trainer):
    def __init__(self, ema_decay=0.9995, ema_update_every=1, *args, **kwargs):
        super().__init__(*args, **kwargs)
        
        # 模型设置后初始化 EMA
        self.ema_decay = ema_decay
        self.ema_update_every = ema_update_every
        self.ema = None
        
    def _setup_ema(self):
        if self.ema is None:
            self.ema = EMA(
                self.model,
                beta=self.ema_decay,
                update_every=self.ema_update_every,
                update_after_step=50  # 50 步后开始 EMA
            )
    
    def training_step(self, model, inputs, num_items_in_batch = None):
        """重写训练步骤以包含 EMA 更新"""
        if self.ema is None:
            self._setup_ema()
            
        # 执行正常训练步骤
        loss = super().training_step(model, inputs, num_items_in_batch=num_items_in_batch)
        
        # 每一步后更新 EMA
        self.ema.update()
        
        return loss
    
    def evaluate(self, eval_dataset=None, ignore_keys=None, metric_key_prefix="eval"):
        """使用 EMA 模型进行评估"""
        if self.ema is not None:
            # 暂时使用 EMA 模型进行评估
            original_model = self.model
            self.model = self.ema.ema_model
            
            results = super().evaluate(eval_dataset, ignore_keys, metric_key_prefix)
            
            # 恢复原始模型
            self.model = original_model
            return results
        else:
            return super().evaluate(eval_dataset, ignore_keys, metric_key_prefix)

    def save_model(self, output_dir=None, _internal_call=False):
        """保存 EMA 模型"""
        # 保存 EMA 模型
        ema_output_dir = f"{output_dir}/ema_model"
        if self.ema is not None and output_dir is not None:
            self.ema.ema_model.save_pretrained(ema_output_dir)
        else:
            self.model.save_pretrained(ema_output_dir)            

这两种技术虽然简单,但尽管训练数据集很小且 noisy,它们使训练曲线更加平滑。

在推理时,我们一次运行 2 个 DeBERTa 模型(每个 T4 GPU 上一个),并通过简单平均集成 6 个预测。
在我们的测试中,这个 6 折集成明显优于使用 0 样本模型。拥有分类头的另一个优势是能够随意调整阈值(直接或通过分位数)。

特别鸣谢

最后,我们要向以下人员致以谢意:

  • @mccocoful 从比赛开始就分享了这么多内容,即使在 LB 顶部孤立了这么久
  • @rdmpage 不辞辛劳地标注并分享了(更好的)训练标签版本
  • @keakohv 分享了数据引用语料库的想法,使比赛对所有之前未使用它的团队更加公平

在我看来,像你们这样的人是 Kaggle 如此伟大的原因,也是我多年来能够学到这么多东西的原因。我不知道还有哪个地方的人们愿意在如此竞争激烈的环境中如此乐意分享和帮助彼此。

同比赛其他方案