573. Kaggle - LLM Science Exam | kaggle-llm-science-exam
首先感谢竞赛主办方组织了如此精彩的比赛!同时也要感谢@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。
我尝试更新了@MB的方法,结合了两种不同的上下文并设置了出现频率的阈值等,但最有效的方法是对TF-IDF频率应用对数缩放并更新停用词。我将原始停用词与sklearn.feature_extraction.text中的ENGLISH_STOP_WORDS进行了结合。
以下是学习时考虑暂停标记的做法。(根据论文所述,就像人类仔细思考时表现更好一样,在训练期间添加暂停标记可以提高语言模型的准确性,因此希望CV能有所提升...我尝试了以下方法,但如果方法更有创意,CV可能会进一步提高。)
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()