返回列表

public 6th / private 31st solution - multi-head structure

673. MAP - Charting Student Math Misunderstandings | map-charting-student-math-misunderstandings

开始: 2025-07-10 结束: 2025-10-15 个性化学习 数据算法赛
公开榜第 6 / 私有榜第 31 名方案 - 多头结构

公开榜第 6 / 私有榜第 31 名方案 - 多头结构

作者: Yannan Chen
发布时间: 2025-10-20
竞赛排名: 第 31 名 (私有榜)

感谢 Kaggle 和社区举办这场有趣的竞赛。

我花费了大量时间训练和测试各种方法,但最终跌出了金牌区。不过,我早就预料到这种情况会发生。随着我提交更多版本,不稳定的 LB 分数和 CV-LB 的不一致性揭示了这场竞赛固有的随机性。

我的主要收获:控制实验随机性,并系统地找出什么有效、什么无效。


1. 模型结构:双头混合架构 (Dual-Head Hybrid Architecture)

主要决策

  • 仅使用分类方法(不使用 causalML)
  • 使用 Qwen-32B 作为骨干网络。测试表明:Qwen 的表现优于其他模型;Qwen-32B 优于较小版本(14B, 7B)
  • 多头结构(1 个全局头 + 15 个局部头),结合全局知识与局部专家
  • 模型仅对误解进行分类(37 个标签);正确或错误通过硬编码在之后标记。
模型结构图

全局头 (Global Head, 37 维)

  • 学习跨问题的通用误解模式
  • 正常的梯度流向 LoRA 骨干网络

局部头 (Local Heads, 15 个头,可变维度)

  • 每个 QuestionId 对应一个头
  • 每个头仅输出该问题的候选误解
  • 例如:问题 31772 有 3 个候选 [0,1,5],问题 32835 有 4 个候选 [17,18,19,20]

梯度阻断机制 (Gradient Blocking Mechanism)

防止局部头过拟合损害骨干网络的泛化能力:

if eta_local2lora == 0:
    feat_local = feat.detach()  # 硬阻断
else:
    feat_local = feat.detach() + eta * (feat - feat.detach())  # 软阻断

最优设置:eta_local2lora = 0.2 (20% 梯度流)

推理融合 (Inference Fusion)

final_score = lambda * p_local37 + (1 - lambda) * p_global

最优 lambda 范围:0.0~1.0(在验证期间测试多个值)


2. 训练策略

训练策略图

交叉验证 (Cross-Validation)

基于 QuestionId + MisconceptionId 组合的 5 折分层 KFold。

输入格式 (Input Format)

使用不同的查询格式训练多个模型,以增加集成的多样性:

  • 查询 1: 问题 + 学生答案 + 是否正确 + 学生解释
  • 查询 2: 问题 + 所有选项 + 学生答案 + 是否正确 + 学生解释
  • 查询 3: 问题 + 正确答案 + 学生答案 + 是否正确 + 学生解释
  • ...

多阶段学习率调度 (Multi-Phase LR Scheduling)

学习率在这场竞赛中非常关键。以下设置产生了最佳结果:

  • LoRA: lr=2e-4, wd=0.01
  • 全局头:lr=5e-5, wd=0.05
  • 局部头:lr=5e-5, wd=0.05
  • 损失权重:alpha=0.6 (局部), beta=1.0 (全局)

最佳 CV 分数通常在第二个 epoch 结束时(epoch 1)达到。保留 Epoch 2 但很少能带来更好的 CV 分数。

  • 阶段 1 (Epoch 0-1): cosine 100%→5%
  • 阶段 2 (Epoch 2): cosine 5%→0%

3. 提交策略

选择来自不同 fold 并使用不同输入格式训练的模型以获得更好的泛化能力。根据 CV 表现将不同的模型分配给不同的 QuestionId,这进一步略微提高了 LB 分数。

cands = { # 候选列表
    "model1":[lora_path_1, lambda_1, query_format_1],
    "model2":[lora_path_2, lambda_2, query_format_2],
    "model3":[lora_path_3, lambda_3, query_format_3],
    "model4":[lora_path_4, lambda_4, query_format_4],
    "model5":[lora_path_5, lambda_5, query_format_5],
    ...
}

cands_sub = { # 权重
    "model1": {31772: 0.4, 31774: 0.0, 31777: 0.4, 31778: 0.4, 32829: 0.4, 32833: 0.4, 32835: 0.4, 33471: 0.4, 33472: 0.4, 33474: 0.0, 76870: 0.4, 89443: 0.4, 91695: 0.4, 104665: 0.0, 109465: 0.4},
    "model2": {31772: 0.0, 31774: 0.4, 31777: 0.0, 31778: 0.0, 32829: 0.0, 32833: 0.0, 32835: 0.0, 33471: 0.0, 33472: 0.0, 33474: 0.4, 76870: 0.0, 89443: 0.0, 91695: 0.0, 104665: 0.4, 109465: 0.0},
    "model3": {31772: 0.5, 31774: 0.5, 31777: 0.5, 31778: 0.5, 32829: 0.5, 32833: 0.5, 32835: 0.5, 33471: 0.5, 33472: 0.5, 33474: 0.5, 76870: 0.5, 89443: 0.5, 91695: 0.5, 104665: 0.5, 109465: 0.5},
    "model4": {31772: 0.4, 31774: 0.4, 31777: 0.4, 31778: 0.4, 32829: 0.0, 32833: 0.4, 32835: 0.0, 33471: 0.4, 33472: 0.4, 33474: 0.4, 76870: 0.0, 89443: 0.0, 91695: 0.0, 104665: 0.4, 109465: 0.0},
    "model5": {31772: 0.0, 31774: 0.0, 31777: 0.0, 31778: 0.0, 32829: 0.0, 32833: 0.0, 32835: 0.4, 33471: 0.0, 33472: 0.0, 33474: 0.0, 76870: 0.4, 89443: 0.4, 91695: 0.0, 104665: 0.0, 109465: 0.0},
    "model6": {31772: 0.0, 31774: 0.0, 31777: 0.0, 31778: 0.0, 32829: 0.4, 32833: 0.0, 32835: 0.0, 33471: 0.0, 33472: 0.0, 33474: 0.0, 76870: 0.0, 89443: 0.0, 91695: 0.4, 104665: 0.0, 109465: 0.4},
}

最终,借用了一个公开 Notebook 中的分歧处理集成方法 (https://www.kaggle.com/code/kishanvavdara/ensemble-gemma-qwen-deepseek),其产生的公开 LB 结果略优于简单的加权集成:

base[c]  = sum(w_m * p_m[c])      # 加权概率和
conf[c]  = max(w_m * p_m[c])      # 最高置信度
agree[c] = (top3 votes) / M       # 共识分数

final[c] = 0.6*base[c] + 0.3*agree[c] + 0.1*conf[c]

4. 无效尝试 (What Didn't Work)

  • 标签平滑(传统的均匀平滑和基于频率的智能平滑):无改进,保持 eps=0.0
  • 数据增强(过采样、合成数据):无改进
  • 在提示中添加 AnswerId 格式 (A/B/C/D)
  • 纯局部头 (lambda=1.0):严重过拟合
  • 硬负例挖掘 (Hard negative mining):无改进
  • CausalML 方法(成对重排序):纯分类方法效果更好
同比赛其他方案