第五名解决方案 - Eedi 数学误解挖掘
第五名解决方案
前言
首先,我要感谢组织者举办了如此有趣的比赛,充满了创新空间且没有重大问题。我还要感谢我的队友,他们为我第一次组队经历创造了顺畅的沟通环境。
解决方案摘要
- 合成数据生成 (Synthetic data generation)
- 我们使用 LLM 通过 few-shot prompting 为训练数据集中未包含的每个 MisconceptionId 创建问题。
- 对于这些 few-shot 样本,我们插入具有与正在生成的 MisconceptionNames 相似的问题和错误答案。
- 知识蒸馏 (Knowledge Distillation)
- 我们将问题文本 (questiontext)、正确答案和错误答案输入到 LLM (Qwen 2.5 32B Instruct) 中,生成错误答案背后的推理。此内容用作所有后续模型(Biencoder, Listwise reranker)的输入。
- 候选生成 (Candidate generation)
- 我们使用 biencoder 模型缩小每个问题和错误答案的 Misconception 候选范围。
- 列表式重排序 (Listwise reranking)
- 我们将由 Biencoder 排序的 Misconception 候选每次输入 52 个(对应于用作选项的大写和小写字母的数量)到微调后的 LLM (Qwen 2.5 32B Instruct) 中,以获得每个选项的概率。然后我们根据这些概率对 Misconceptions 进行排序。
- 对于此过程,我们使用来自 biencoder 的前 104 个候选。换句话说,我们执行两次推理,每次 52 个候选,提取概率,然后合并结果。
- 集成 (Ensemble)
- 由于 Listwise reranker 使用 GPTQ + vLLM 推理,单次推理仅需约 120 分钟,因此可以在三个折 (folds) 上对结果进行集成。(但由于 此处 讨论的随机延迟,我们的最佳提交未能包含第三折的所有结果。希望 Kaggle 工作人员将来能解决这些问题。)
详细解决方案
- 交叉验证 (Cross validation)
- 最初,我们使用基于 QuestionId 的 GroupKFold(k=5),但由于 lb 显著低于本地 cv,我们怀疑测试数据中可能有许多仅出现在测试数据中的 MisconceptionIds。基于此,我们切换到使用 SubjectId 的 GroupKFold(k=5)。此变化导致仅出现在验证数据中的 MisconceptionIds 比例更高,这倾向于使 cv 更接近 lb。
- 例如:本地 cv 0.626, lb: 0.633
- 合成数据生成 (Synthetic data generation)
- 合成数据生成对于将没有现有问题的 MisconceptionIds 包含在预测结果中至关重要。对于每个没有问题的 MisconceptionName,我们使用 LLM (gemini-1.5-pro) 通过 few-shot prompting (4 shots) 生成一组现有数据集信息(QuestionName, SubjectName, ConstructName, Correct Answer, Incorrect Answer)。
- 对于 few-shot prompting 样本,我们使用模型 (dunzhang/stella_en_1.5B_v5) 嵌入所有 MisconceptionNames,并包含对应于相似 MisconceptionNames 的问题。与使用随机样本相比,这使 cv 提高了约 +0.01。
- 知识蒸馏 (Knowledge Distillation, KD)
- 我们将 QuestionText、正确答案和错误答案输入到 LLM (Qwen 2.5 32B Instruct) 中以估计 Misconception。使用此信息可使 biencoder 的 cv 提高 +0.04。虽然后面提到的 Listwise reranker 使用与 KD 相同的模型,但与不包含它时相比,cv 增加了约 +0.01。
PROMPT_FORMAT = """<|im_start|>system
You will be given a math problem and its correct and incorrect answer.
First explain why the correct answer is correct, and finally explain reasons and misconceptions for incorrect answer.
Please briefly explain in 200 words or less.<|im_end|>
<|im_start|>user
Problem: {QuestionText}\nCorrect Answer: {Correct}\nIncorrect Answer: {Answer}.<|im_end|>
<|im_start|>assistant
"""
- 候选生成 (Candidate generation)
- 我们使用 Biencoder 提取每个问题和错误答案对的 MisconceptionName 候选。虽然我们执行负样本挖掘,但硬负样本 (hard negatives) 效果不佳,所以我们包含稍微宽松的样本。
- 训练参数
- library: SentenceTransfomer
- model: dunzhang/stella_en_1.5B_v5
- 负样本挖掘参数
- range_min: 512
- num_negatives: 2
- lora config
- loss: CachedMultipleNegativesRankingLoss
- epoch: 1
- cv: 0.41, lb: 0.39
- 列表式重排序器 (Listwise reranker)
- 概述
- 提示词 (prompt)
- 经过各种试错,我们最终使用以下提示词进行常规微调,以使 LLM 生成合适的选择(必须是单个 token 以便通过 logits(概率) 进行排序)。
NA_PROMPT_FORMAT = """<|im_start|>system
You will be given math problem, overview of ther problem, correct answer, incorrect answer, and incorrect reason.
Please return the most appropriate option from the list of misconceptions. Do not output anything other than options. If there are no suitable
options, return NA.<|im_end|>
<|im_start|>user
# Math Problem
Problem: {QuestionText}\nOverview: ({SubjectName}){ConstructName}\nCorrectAnswer: {Correct}\nIncorrectAnswer:
{Answer}\nIncorrectReason: {kd}
# Misconception List (rank: {rank})
{misconception_list}<|im_end|>
<|im_start|>assistant
{label_choice}"""
- 训练 (Training)
- 我们将 biencoder 的前 208 个输出分为非重叠的组,每组 52 个(1-52, 53-104, 105-156, 157-208)并进行微调。
- 训练参数
- loss
- 这是常规微调中使用的标准交叉熵损失,跨整个词汇表计算。我们选择这种更简单的实现方法,因为仅限制为候选 choices + "NA" token 计算交叉熵显示结果差异很小。
- model: Qwen/Qwen2.5-32B-Instruct
- lora_config
- lr: 5e-5
- scheduler: cosine
- warmup_steps: 10
- epoch : 1.0
- global batch size: 8
- library: unsloth
- 推理 (Inference)
- 从 biencoder 输出中,我们取前 104 项并将其分为两组(1-52 和 53-104)。每组输入模型,仅生成单个 token 以获得每个选项的概率。然后将两组的结果合并并排序。
- 虽然增加到前 156 个(52x3 推理)显示本地 CV 略有改善,但这并未反映在 leaderboard 分数中,所以我们没有采用这种方法。
- 使用 GPTQ 量化为 4-bit,并使用 vLLM 进行推理
cv(公共)[私有] 分数
|
52x1 |
52x2 |
52x3 |
| fold 4 |
0.623(0.627)[0572] |
0.626(0.633)[0.581] |
0.630(0.631)[0.581] |
| fold 3 |
|
(0.644)[0.581] |
|
| fold (3 + 4) |
|
(0.650)[0.590] |
|
| fold (2(60%sample) + 3 + 4) |
|
(0.653)[0.597] |
|
计算成本
我的本地机器是 1 x RTX 4090。
我租用了一台 A100(来自 vast.ai)用于训练 32B 模型,包括实验,总成本约为 350 美元...
在 A100 上训练一个 32B Listwise 模型大约需要 7 小时。
无效尝试 (What didn't work)
- Listwise reranker 中使用的选项
- 作为单个 token 选择,我们尝试了两个字母的组合、字母和数字 0-9 的组合,以及日语平假名,但所有这些方法的表现都不如仅使用 52 个字母。
- 利用 QwQ-32B-preview
- 多步重排序 (multi step reraking) (Qwen 2.5 7B(104 候选) → Qwen 2.5 32B(52 候选))
- 在重排序提示词中添加问题和误解的示例。