返回列表

1st Place Solution

573. Kaggle - LLM Science Exam | kaggle-llm-science-exam

开始: 2023-07-11 结束: 2023-10-10 自然语言处理 数据算法赛
第一名解决方案

第一名解决方案

作者:Pascal Pfeiffer(以及队友)
发布日期:2023年10月11日

感谢这个执行良好的比赛,吸引了社区的大量关注。同时非常感谢我出色的队友@ybabakhin@philippsinger

查看我们的TLDR摘要这里

上下文检索

我们很早就开始参加这个比赛,很快就发现使用简单的分类方法对deberta和LLM模型都存在性能上限。

因此,下一步是探索从维基百科文章中检索上下文。我们的第一次提交使用了all-MiniLM-L6-v2模型。令我们惊讶的是,这个第一次尝试比我们之前所有非上下文模型都要好得多。从那时起,我们主要专注于改进上下文检索部分,而保持模型部分几乎不变。我们尝试了MTEB排行榜前20名中的大多数模型:https://huggingface.co/spaces/mteb/leaderboard。最后,我们本地选择了大约300种检索+LLM模型+不同维基百科转储的组合。我们的最终提交包含了来自e5-base-v2e5-large-v2gte-basegte-largebge-large模型的上下文。

除了探索各种模型外,我们还尝试了不同的方式来编码维基百科块和比赛数据的提示+答案。对于维基百科,我们主要编码文章标题+文章块,对于问题,最终模型有2种变体:

  1. 简单连接:“{prompt} {A}, {B}, {C}, {D}, {E}”
  2. 分别为每个“{prompt} {A}”、“{prompt} {B}”等选项搜索上下文,最后按相似度排序。

我们的维基百科转储包含3000万到6000万个文本块。然而,我们实现了一个相当快速且可扩展的GPU相似度搜索。首先,我们将整个6000万数据库拆分为较小的部分,然后逐个加载。然后我们计算每个部分与整个测试数据之间的相似度矩阵(在GPU上使用torch张量的简单矩阵乘法)。现在我们要做的就是在所有部分中存储全局Top-k列表。为了使速度加倍,我们在2个GPU上并行执行。

不同的维基百科转储

和大多数团队一样,我们开始时使用公共维基作为我们的上下文和嵌入源。我们投入了大量时间来创建我们自己的维基语料库。首先,我们通过从更接近比赛开始日期的快照重新创建了上述维基。我们添加了一些额外的后处理步骤,例如将非常小的段落(通常是副标题)与后续段落连接起来。在调查上下文匹配错误时,我们发现所有公共维基百科转储解析器都不能正确展开lua代码,这种代码在维基百科上经常用于在科学文章中渲染值。为了将这些内容包含到我们的上下文中,我们决定使用cirrussearch转储,这是一个包含所有(几乎)完全渲染的维基百科页面的转储。不幸的是,这个转储不包含换行符,因此我们将句子合并到256、512或1024个字符的不同目标长度,同时不打破任何句子。最后,我们考虑了所有这些维基(以及每种维基的多种嵌入)用于混合,这有助于增加多样性。作为一个独立的维基百科语料库,目标长度为512个字符的cirrussearch维基对我们来说是最好的。

我们也尝试过滤维基以获取科学文章,但从未在使用完整维基语料库的公共排行榜上取得显著改进。显然,嵌入模型足够强大,不会被无关文章分散注意力,我们决定稳妥起见,在最终提交中包含完整维基。

更长的上下文

我们开始时在训练和推理中都使用单个最相似的维基块。然而,我们很快注意到在推理过程中增加上下文长度可能会改善结果。对我们来说,最优策略是使用3个块上下文训练模型,并使用5个块进行推理。进一步增加训练上下文太慢,而进一步增加推理上下文可能会向上下文中添加太多噪声。我们进行的一个有趣的测试是在推理过程中反转上下文的顺序,这导致分数大幅下降。因此,这可能意味着模型学习了“最佳”上下文的位置。

数据和验证策略

对于训练,我们主要使用公开共享的数据集。添加更多数据对我们的模型没有帮助。对于验证,我们从一开始就使用提供的200个样本作为验证数据。然而,在引入上下文检索部分后,我们很快在那里达到了0.99的性能。在比赛的其余时间里,我们使用了来自一个公开共享数据集的6000个STEM问题。

模型

我们的最终集成是微调7B和13B LLM的组合。所有模型都以二进制方式训练,即每个五个选项的单个样本,最终预测概率用于排序。这使得模型独立于答案的顺序。对于训练数据,我们发现以下步骤很有用:

  • 选择合成生成的多选样本。Kaggle上分享了很多这样的样本。有趣的是,数据量对提高性能并不太重要。
  • 对于每个样本,我们使用我们的RAG嵌入方法生成上下文。这允许我们生成像推理过程中那样的多个上下文,在此处添加噪声对训练有帮助。在问题生成的真实上下文上训练比在生成的上下文上训练稍差。

我们使用LORA在所有线性层上训练所有模型。我们在最终下一个token logits之后使用一个二进制分类头。我们还对头部与LORA层使用了较低的学习率。我们对LORA设置、学习率和其他小细节进行了调整,但它们对某些其他深度学习用例的影响不大。所有模型都在单个epoch上训练,使用典型的余弦lr衰减和BCE损失,选择最后一个检查点。

我们在以下LLM模型上取得了最大的成功:

  • Llama-2-7b
  • Mistral-7B-v0.1
  • xgen-7b-8k-base
  • Llama-2-13b

下图从高层次解释了我们的架构和推理设置:

  1. 首先,我们将上下文和问题输入到骨干网络中,并保存past_key_values。对于每个问题,我们只需要执行一次,因为每个答案都有相同的上下文和问题。这对于仅解码器模型来说是一个巨大的优势,可以节省大量运行时间。
  2. 然后,我们在五个答案中的每一个上再次执行骨干网络,每个答案都使用上一次执行的past_key_values作为缓存输入。现在我们可以以五个为一组进行批处理。
  3. 我们取每个输出的最终下一个token预测,即整个词汇表上的logits,并将其输入到从头开始训练的最终分类头中。这个头部现在学习将LLM在整个词汇表上的输出映射到单个二进制预测,即答案是否正确。
  4. 然后通过按预测概率排序来确定最终顺序。

这种二进制方法的一个缺点是,每个样本都缺乏来自所有其他选项的交叉信息,这些信息可能包含独立的信号。我们尝试了很多多类模型或简单地将其他答案作为上下文添加到二进制模型中,但这会引入巨大的位置偏差。因此,我们尝试使用TTA方法来解决这个问题,使用答案的各种有序组合,这肯定有帮助,但更加不稳定,无法胜过二进制模型。

我们还想出了一个我们认为相当聪明的想法,另一个解决方案,以帮助模型学习样本之间的交叉信息而不添加位置偏差。在这里,想法是将所有其他选项的平均下一个token logits作为额外输入添加到最终分类头中。因此,最终头部获得手头答案的所有可能token的logits,以及其他四个答案的所有可能token的logits的平均值。这不会添加位置偏差,并提供额外的信息,提升了CV和LB,在与更简单的方法混合时效果很好。

正如您可能已经从我们的团队名称猜到的那样,我们使用了一个分叉的H2O LLM Studio来进行实验和训练我们的模型。我们添加了训练因果分类模型的完全灵活的方式。合并的PR允许使用我们解决方案中使用的核心技术来训练二进制和多类分类LLM模型。您只需添加一个带有二进制目标和输入文本的csv文件,然后训练模型。

推理解决方案

我们的最终集成是五个7B模型和一个13B模型的混合。每个模型使用不同的上下文方法,使用不同的维基、嵌入和topk。管道精确适应9小时运行时间,并利用2.5TB的输入数据。

我们现在分享了我们的推理代码:

一个获胜内核,0.933私有分数:https://www.kaggle.com/code/ybabakhin/1st-place-team-h2o-llm-studio
一个单模型,0.932私有分数:https://www.kaggle.com/code/ybabakhin/1st-place-single-model-inference

同比赛其他方案