返回列表

4th place solution

613. AI Mathematical Olympiad - Progress Prize 1 | ai-mathematical-olympiad-prize

开始: 2024-04-01 结束: 2024-06-27 数学与计算 AI大模型赛
第四名解决方案

第四名解决方案

作者:Zonglin Han 及团队成员

发布日期:2024-07-09

竞赛排名:第 4 名

团队成员:winter2003, Aristotle.Chen, HuiHuang Lv, Zonglin Han, Yz H

首先,我们要感谢 Kaggle 和 XTX Markets 为我们带来了这场激动人心的比赛。通过这次比赛,我们学到了很多。我们也要感谢每一位团队成员的辛勤工作。他们真的很棒,提出了许多优秀的想法并展现了出色的工程技能。

我们的解决方案基于 AbdurRafae 的伟大工作:Improved Code Interpretation (kaggle.com)。他获得的早期分享奖是实至名归的。同样,Anren 对此代码的重组也为我们提供了巨大的帮助。

下面简要介绍我们解决方案的整体流程和一些独特的技巧。我们的解决方案代码在此处:

第四名双 GPU 推理与答案比较 (kaggle.com)

本地交叉验证策略 (Local CV Strategy)

我们使用了这个 71k 的数据集:AIMO-24: Processor (Art Of Problem Solving) (kaggle.com)。从所有 AMC_12A 问题中,我们选择了最新的 50 个问题用于本地验证(排除了文本中包含 [asy] 字符串的问题,稍后将解释原因)。这与公共 leaderboard 显示出良好的相关性。

模型选择 (Model Selection)

我们使用了 deepseek-math-7b-rl,参数设置为:temperature 0.9, top_p 1.0, max tokens 2048。配合代码工具,该模型在 MATH 基准测试上可达到 58.8% 的成绩。

我们在两个 T4 GPU 上并行部署和运行此模型,以最大化自一致性从而提高最终预测准确率。双 GPU 推理将每个问题的总重复次数从 21 次增加到 30-40 次。

时间管理 (Time Management)

TimeManager 类

我们创建了一个 TimeManager 类来管理时间分配。它跟踪每个问题花费的时间,并动态调整后续问题的尝试次数,以确保在给定时间内解决尽可能多的问题。

class TimeManager:
    def __init__(self, total_problems, total_time, default_attempts):
        self.total_problems = total_problems  # 问题总数
        self.total_time = total_time  # 可用总时间
        self.default_attempts = default_attempts  # 默认尝试次数
        self.time_spent = 0  # 目前已花费时间
        self.current_problem = 0  # 当前问题索引
        self.last_problem_time = 0  # 上一个问题花费的时间
    
    def update_time_spent(self, time_for_problem):
        self.time_spent += time_for_problem  # 将问题花费时间加到总花费时间中
        self.current_problem += 1  # 增加问题索引
        self.last_problem_time = time_for_problem  # 记录当前问题花费的时间
    
    def get_next_attempts(self):
        if self.current_problem >= self.total_problems:
            return 0
        
        remaining_time = self.total_time - self.time_spent  # 剩余时间
        remaining_problems = self.total_problems - self.current_problem  # 剩余问题数
        average_time_per_problem = remaining_time / remaining_problems  # 平均每个问题的时间
        
        if self.current_problem == 0 or self.time_spent / self.current_problem <= average_time_per_problem:
            next_attempts = self.default_attempts
        else:
            next_attempts = max(1, int(self.default_attempts * (average_time_per_problem / (self.time_spent / self.current_problem)) * 0.8))
        
        return next_attempts
    
    def print_status(self):
        remaining_time = self.total_time - self.time_spent  # 剩余时间
        print(f"Time spent: {self.time_spent} seconds")  # 打印总花费时间
        print(f"Remaining time: {remaining_time} seconds")  # 打印剩余时间
        print(f"Current problem: {self.current_problem}/{self.total_problems}")  # 打印当前问题进度
        print(f"Time spent on current problem: {self.last_problem_time} seconds")  # 打印当前问题花费时间

减少困难问题的尝试次数

我们手动减少了我们认为模型无法解决的问题的尝试次数,为剩余问题提供更多尝试机会。具体来说,对于包含 [asy] 符号的问题,我们只允许 3 次重复。该符号表示包含图形的几何问题,在 LaTeX 中被 [asy] 包围。在我们的本地测试中,模型很少能正确回答这些问题,即使回答了,我们也怀疑该问题可能存在于模型的训练集中。

减少双 GPU 运行之间的等待时间

当使用 ThreadPoolExecutor 进行并行推理时,一个 GPU 通常完成得更快。如果我们看到第一个完成的 GPU 已经有足够的候选答案,我们将提前终止较慢 GPU 的尝试。然后,合并两个 GPU 生成的候选答案。这确保了更高效地使用双 GPU,并增加了所有问题的平均重复次数。

候选答案生成 (Candidate Answer Generation)

DeepSeekMath 初始化自 DeepSeek-Coder-v1.5 7B,它可以通过编写程序有效地解决和证明数学问题。我们遵循 Anren 的方法,使用两个提示,让模型使用 COT(思维链)和编写代码的方法解决问题。

我们做了一些改进:

  1. 限制模型的代码修改机会

    我们将每次重复的模型代码修改机会限制为仅 3 次,因为输入文本过长会显著降低 LLM 的输出质量。此功能由参数 while_limit 控制,我们将其设置为 6(因为每个代码输入和输出处理需要两个 while 循环)。

  2. 候选答案的加权计数排序

    我们发现模型在回答错误时倾向于输出小整数,如 0, 1, 2, 3, 4, 5。我们将这些数字的权重降低到 0.25,而其他数字保留正常的权重 1。

  3. 修复一些不正确行为

    例如,当代码输出不是有效答案(例如错误、小数、复数)时,原始方法仍尝试解析文本答案,这几乎总是错误的。在这种情况下,我们跳过记录文本答案。

其他 (Other)

我们的日志记录了每次实验的详细数据,包括候选答案、程序代码、代码准确率、文本准确率等。这些详细日志极大地促进了我们的快速实验和结果观察。我们的本地实验得分约为 21 分,pass1@ 达到 32 分,pass2@ 达到 23 分。

当然,比赛中可能需要一些运气。我们的解决方案在公共 leaderboard 上,使用不同的种子,可能会导致 3 分的分数差异。

详细本地日志(参数略有不同)
详细本地日志图片

同比赛其他方案