673. MAP - Charting Student Math Misunderstandings | map-charting-student-math-misunderstandings
简单而有效
🎯 比赛目标: 根据解释预测学生的数学误解
📊 最终得分: 0.947 MAP@3 (私有排行榜)
🏅 排名: 第 45 名 / 银牌
🤖 方法: 6 模型 LLM 集成与智能投票
⚡ 关键创新: 基于家族的过滤 + 多因素评分
如果没有 Kaggle 社区的慷慨贡献,这次银牌成就将不可能实现。我深表感激:
比赛与平台:
社区贡献者:
Kaggle 社区:
这是我的第一枚银牌,它证明了站在巨人的肩膀上可以取得多大的成就。谢谢大家!🚀
MAP (Misconception Annotation Project) 比赛挑战参与者开发 NLP 模型,从学生的开放式解释中预测他们的数学误解。目标是帮助教师有效地识别和解决阻碍学生学习的错误思维模式。
评估指标:Mean Average Precision @ 3 (MAP@3)
我的解决方案利用了 6 个不同 LLM 的集成,每个模型都为最终预测贡献了独特的优势。关键见解是不同的模型架构和训练配置捕捉到了数学误解模式的不同方面。
| # | 模型 | 公开 LB | 训练方法 | GPU 设置 | LoRA 配置 | 学习率 | Epochs | 训练时间 |
|---|---|---|---|---|---|---|---|---|
| 1 | Hunyuan 7B Instruct | 0.945 | LoRA | 2×L4 | R:64, α:128 | 2e-4 | 3 | 4h 34m |
| 2 | Qwen 3 4B | 0.945 | LoRA | 2×L4 | R:512, WD:0.3 | 2e-5 | 3 | 2h |
| 3 | Qwen 2 8B | 0.945 | QLoRA 4-bit | 2×L4 | R:64, α:32 | 2e-4 | 2 | 4h 23m |
| 4 | Qwen 3 14B | 0.944 | QLoRA 4-bit | 4×L4 | R:16, α:32 | 2e-4 | 3 | 11h 34m |
| 5 | Qwen 3 8B | 0.943 | 全量微调 | 4×L4 | N/A | 2e-5 | 3 | 3h |
| 6 | DeepseekMath 7B | 0.944 | 全量微调 | 4×L4 | N/A | 2e-5 | 3 | 3h 24m |
所有模型的预处理保持一致以确保公平比较:
# 创建目标标签
train['target'] = train.Category + ":" + train.Misconception
train['label'] = le.fit_transform(train['target'])
# 识别正确答案
idx = train.apply(lambda row: row.Category.split('_')[0], axis=1) == 'True'
correct = train.loc[idx].copy()
correct['c'] = correct.groupby(['QuestionId', 'MC_Answer']).MC_Answer.transform('count')
correct = correct.sort_values('c', ascending=False)
correct = correct.drop_duplicates(['QuestionId'])
correct = correct[['QuestionId', 'MC_Answer']]
correct['is_correct'] = 1 # 标记这些为正确答案
# 将 'is_correct' 标志合并到主训练 DataFrame 中
train = train.merge(correct, on=['QuestionId', 'MC_Answer'], how='left')
train.is_correct = train.is_correct.fillna(0)
每个训练示例的格式如下:
Question: {QuestionText}
Answer: {MC_Answer}
Is Correct Answer: {Yes/No}
Student Explanation: {StudentExplanation}
这种结构为模型提供了完整的上下文:问题、学生的答案选择、是否正确以及他们的推理。
bnb_config = BitsAndBytesConfig(
load_in_4bit=True,
bnb_4bit_use_double_quant=True,
bnb_4bit_quant_type="nf4",
bnb_4bit_compute_dtype=torch.bfloat16
)
# 所有模型的通用设置
num_train_epochs = 3
learning_rate = 2e-4 to 2e-5 # 因模型而异
lr_scheduler_type = "cosine"
warmup_ratio = 0.1
gradient_accumulation_steps = 16
fp16/bf16 = True
概率提取
对于每个模型,我生成了所有类别的完整概率分布(不仅仅是前 3 名),这对于有效的集成至关重要:
# 生成概率
logits = trainer.predict(test_dataset).predictions
probs = softmax(logits, axis=1)
# 排序并保存前 K 个类别及其概率
top_indices = np.argsort(-probs, axis=1)
for i in range(num_classes):
prob_dict[f"prob_{i}"] = probs[i, top_indices[i]]
# 保存用于集成
prob_df.to_csv(f"submission_{model_name}_prob.csv", index=False)
集成是将性能从单独的 0.943-0.945 分提升到公开排行榜 0.949 分的关键组件。
一个关键见解是认识到预测必须尊重类别家族(True 与 False):
# 从正确答案构建家族映射
fam_map = test_df.merge(correct_answers, on=['QuestionId','MC_Answer'])
fam_map['family'] = fam_map['is_correct'].map({1: 'True_', 0: 'False_'})
集成结合了三个评分组件:
for class_name in all_classes:
base_score = class_total_prob[class_name]
agreement_bonus = class_votes[class_name] / n_models
confidence_bonus = class_max_prob[class_name]
final_scores[class_name] = (
base_score * 0.34 +
agreement_bonus * 0.33 +
confidence_bonus * 0.33
)
# 按家族过滤
final_scores = {k: v for k, v in final_scores.items()
if k.startswith(family_prefix)}
# 如果预测少于 3 个则回填
fillers = [f"{family}_Neither:NA"]
if family == "True_":
fillers.append(f"{family}_Correct:NA")
所有模型权重相等 (weight=1),因为它们表现相似:
weights = [1, 1, 1, 1, 1, 1] # 等权重
这次比赛表明,集成多样性、高效微调技术和领域特定约束是在专用 NLP 任务中实现顶级性能的关键。6 个精心训练的 LLM 与智能集成的结合实现了 0.947 MAP@3 分数 和 银牌 的成绩。
感谢阅读!欢迎提问或讨论该方法。 🚀
```