返回列表

41st Place Solution for the Kaggle - LLM Science Exam

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

开始: 2023-07-11 结束: 2023-10-10 自然语言处理 数据算法赛
Kaggle LLM科学考试第41名解决方案

Kaggle LLM科学考试第41名解决方案

作者:mipypf | Private LB排名:第41名(银牌) | 发布日期:2023年10月11日

首先感谢竞赛主办方组织了如此精彩的比赛!同时也要感谢@cdeotte@MB以及所有分享宝贵思路和代码的参赛者们。这是一场非常激动人心且有趣的竞赛!我的最终成绩是Public LB第58名,Private LB第41名,获得了银牌。

背景

方法概述

我们的最终模型结合了七个单独的microsoft/deberta-v3-large模型(每个模型使用不同的随机种子、数据集和暂停标记)。最终模型是七个microsoft/deberta-v3-large模型的平均集成与基于TF-IDF检索的结合。最终结果为Public LB=0.916 / Private LB=0.909。

提交详情

训练使用的数据集是@cdeotte的60k数据集和99k数据集。此外,我们参考《Think before you speak: Training Language Models With Pause Tokens》创建了考虑暂停标记的训练模型。推理过程中使用了参考@MB方法的基于TF-IDF的上下文检索方法。

以下是每个模型的CV、Public LB和Private LB摘要(推理时使用了基于TF-IDF的上下文检索方法)。所有情况下,CV的验证数据都使用了来自@cdeotte的60k数据集中的200条数据。

模型 CV Public LB Private LB
microsoft/deberta-v3-large (seed=42, dataset=60k, 无暂停标记) 0.915 0.907 0.905
microsoft/deberta-v3-large (seed=52, dataset=60k, 无暂停标记) 0.893 - -
microsoft/deberta-v3-large (seed=62, dataset=60k, 无暂停标记) 0.910 - -
microsoft/deberta-v3-large (seed=72, dataset=60k, 无暂停标记) 0.895 - -
microsoft/deberta-v3-large (seed=82, dataset=60k, 无暂停标记) 0.905 - -
microsoft/deberta-v3-large (seed=42, dataset=60k, 有暂停标记) 0.906 0.902 0.902
microsoft/deberta-v3-large (seed=42, dataset=60k+99k, 无暂停标记) 0.904 - -

冻结层

由于microsoft/deberta-v3-large模型本身学习效果不佳,我们冻结了一些层(如果层未冻结,第一个epoch的MAP@3表现不佳,CV约为0.37)。

最大长度

训练时的max_length设置为256、512和1024,其中512的CV最佳,因此最终使用了512。

基于TF-IDF的上下文检索方法

我尝试更新了@MB的方法,结合了两种不同的上下文并设置了出现频率的阈值等,但最有效的方法是对TF-IDF频率应用对数缩放并更新停用词。我将原始停用词与sklearn.feature_extraction.text中的ENGLISH_STOP_WORDS进行了结合。

暂停标记

以下是学习时考虑暂停标记的做法。(根据论文所述,就像人类仔细思考时表现更好一样,在训练期间添加暂停标记可以提高语言模型的准确性,因此希望CV能有所提升...我尝试了以下方法,但如果方法更有创意,CV可能会进一步提高。)

  • 向tokenizer添加暂停标记
tokenizer.add_tokens([""])
model.resize_token_embeddings(len(tokenizer))
  • 在输入的随机位置添加暂停标记
PAUSE_TOKEN_COUNT = 2

def insert_pause_tokens(sentence, count):
    tokens = tokenizer.tokenize(sentence)
    for _ in range(count):
        position = random.randint(1, len(tokens) - 1)
        tokens.insert(position, "")
    return tokenizer.convert_tokens_to_string(tokens)

def preprocess(example):
    context_with_pause = insert_pause_tokens(example["context"], PAUSE_TOKEN_COUNT)
    first_sentence = ["[CLS] " + context_with_pause] * 5
    second_sentences = [
        " #### " + example["prompt"] + " [SEP] " + example[option] + " [SEP]"
        for option in "ABCDE"
    ]
    tokenized_example = tokenizer(
        first_sentence,
        second_sentences,
        truncation="only_first",
        max_length=MAX_INPUT,
        add_special_tokens=False,
    )
    tokenized_example["label"] = option_to_index[example["answer"]]
    return tokenized_example
  • 自定义损失设置
def custom_loss(outputs, labels, attention_mask):
    loss = F.cross_entropy(outputs, labels, reduction="none")
    # pause_id = tokenizer.convert_tokens_to_string([''])
    # pause_mask = (labels == pause_id).float()
    pause_id = tokenizer.convert_tokens_to_ids([""])[0]
    pause_mask = (labels == pause_id).type(torch.float)
    masked_loss = loss * (1 - pause_mask)
    return masked_loss.mean()
  • 设置并运行自定义训练器
class CustomTrainer(Trainer):
    def compute_loss(self, model, inputs, return_outputs=False):
        labels = inputs.pop("labels")
        outputs = model(**inputs)
        logits = outputs.logits
        loss = custom_loss(logits, labels, inputs["attention_mask"])
        return (loss, outputs) if return_outputs else loss

trainer = CustomTrainer(
    model=model,
    args=training_args,
    tokenizer=tokenizer,
    data_collator=DataCollatorForMultipleChoice(tokenizer=tokenizer),
    train_dataset=tokenized_dataset,
    eval_dataset=tokenized_dataset_valid,
    compute_metrics=compute_metrics,
    callbacks=[EarlyStoppingCallback(early_stopping_patience=1)],
)

trainer.train()
同比赛其他方案