1.论文信息
标题:MapCoder: Multi-Agent Code Generation for Competitive Problem Solving
收录的会议/期刊:ACL
作者信息:
arxiv:🔗https://arxiv.org/pdf/2405.11403
github网站:🔗
github代码:🔗https://github.com/Md-Ashraful-Pramanik/MapCoder
2.泛读
a. 名词解释
-
pass@k指标
在机器学习和相关领域中,pass@k
是一个评估指标,用于衡量模型在特定任务上的性能。这个指标特别常见于代码生成、问题回答或其他生成任务中,其中模型需要从多个可能的答案中选择正确的一个。
定义和计算方式
pass@k
表示在模型生成的前 k
个候选中,至少有一个是正确答案的比例。这里的 k
是一个超参数,可以根据任务的具体需求进行调整。例如:
-
pass@1
表示在模型生成的第一个候选中正确答案的比例。 -
pass@3
表示在模型生成的前三个候选中至少有一个正确答案的比例。
应用场景
这个指标特别有用,因为它考虑了模型生成多个候选答案的情况,而不是仅仅评估单一答案的正确性。在实际应用中,尤其是在代码生成或复杂的问答系统中,模型可能需要生成多个可能的解决方案,用户可以从中选择最合适的一个。
示例
假设一个代码生成模型被要求解决一个编程问题,它生成了三个可能的代码段作为候选答案。如果其中至少有一个代码段正确解决了问题,那么这个模型在这个特定问题上的 pass@3
指标就是 100%。
优势和局限性
优势:
-
提供了一种灵活的评估方式,适用于模型生成多个答案的情况。
-
可以更全面地反映模型的性能,尤其是在答案可能不唯一或有多个正确解的情况下。
局限性:
-
需要明确
k
的值,这可能需要根据具体任务进行调整。 -
在某些情况下,如果
k
值设置得过高,可能会导致指标过于宽松,不能准确反映模型的真实性能。
-
数据集
HumanEval 93.9%,MBPP 83.1%,APPS 22.0%,CodeContests 28.5%,和 xCodeEval 45.3%
这些百分比数字代表的是在不同的编程问题基准测试(Benchmarks)中,MapCoder 框架在代码生成任务上的性能表现。每个缩写代表一个特定的编程问题数据集或测试环境,而百分比则表示该框架在该测试中成功生成正确代码的比例。下面是每个缩写的具体含义:
-
HumanEval:
-
这是一个评估代码生成模型性能的数据集,它包含了一组需要编写函数来解决问题的任务。这些任务通常涉及对复杂算法和数据结构的理解。
-
93.9% 表示在 HumanEval 数据集中,MapCoder 框架成功生成了正确代码的比率是 93.9%。
-
-
MBPP (Most Basic Programming Problems):
-
MBPP 是一个包含基础编程问题的数据集,这些问题通常可以在LeetCode等编程练习平台上找到。
-
83.1% 表示在 MBPP 数据集中,MapCoder 框架正确解决了 83.1% 的问题。
-
-
APPS (Abstraction, Pattern, and Program Synthesis):
-
APPS 是一个专注于抽象、模式识别和程序合成的编程问题数据集。
-
22.0% 表示在 APPS 数据集中,MapCoder 框架正确解决了 22.0% 的问题。
-
-
CodeContests:
-
这个数据集可能包含了一些编程竞赛中的问题,这些问题通常比普通的编程练习更具挑战性。
-
28.5% 表示在 CodeContests 数据集中,MapCoder 框架正确解决了 28.5% 的问题。
-
-
xCodeEval:
-
这是一个可能包含多种编程问题的综合评估平台或数据集。
-
45.3% 表示在 xCodeEval 数据集中,MapCoder 框架正确解决了 45.3% 的问题。
-
-
正常结果文件/增强测试结果文件/增强测试数据集
-
正常结果文件(Normal Results File):
-
这个文件包含了模型或系统在正常条件下生成的结果。对于编程任务,它可能包含模型为解决特定编程问题而生成的源代码。这些结果通常是模型的直接输出,未经测试或验证。
-
结果文件通常以某种结构化格式(如 JSON、JSONL 或 CSV)保存,以便于进一步处理和分析。
-
在代码中,这个文件通过
NORMAL_RESULTS_PATH
参数指定,使用read_jsonl
函数读取。
-
-
增强测试结果文件(Enhanced Testing Results File):
-
这个文件包含了经过增强测试的数据集验证后的结果。增强测试是一种更严格的测试方法,它使用额外的测试用例来验证代码的正确性。
-
增强测试结果文件记录了每个测试项是否通过增强测试,以及可能的其他相关信息,如测试的准确率、错误信息等。
-
这个文件有助于评估模型生成代码的质量和可靠性。
-
在代码中,这个文件的路径通过
ET_RESULTS_PATH
参数指定,处理后的结果通过write_jsonl
函数写入。
-
-
增强测试数据集(Enhanced Testing Dataset):
-
增强测试数据集是一组专门设计的测试用例,用于验证生成代码的正确性。它通常包含比标准测试更全面的测试场景,以确保代码在各种情况下都能正常工作。
-
增强测试数据集可能包括各种输入和预期输出,以及可能的边界条件和异常情况。
-
这个数据集用于执行增强测试,即在正常结果文件中的代码生成后,使用这些测试用例来验证代码的正确性。
-
在代码中,这个数据集的路径通过
ET_DATA_PATH
参数指定,使用read_jsonl
函数读取。
-
在代码生成和评估的上下文中,这些文件共同作用,以确保生成的代码不仅在表面上看起来正确,而且在更广泛的测试条件下也能正常工作。通过这种方式,可以更准确地评估模型的性能和生成代码的可靠性。
-
多智能体系统
多智能体系统(Multi-Agent System, MAS)是由多个自主或半自主的智能体组成的系统,这些智能体通过协作、竞争或协商来共同完成复杂任务。以下是关于多智能体系统的定义、特点和应用的详细说明:
定义
多智能体系统是由多个智能体组成的集合,这些智能体可以是软件程序、机器人、传感器或其他实体,它们各自具备一定的智能和自主性,并通过交互协作来实现单个智能体难以完成的复杂目标。
核心特点
-
分布式处理:多智能体系统将复杂任务分解为多个子任务,每个智能体负责一部分,从而提高系统的模块性、可扩展性和灵活性。
-
协同工作:智能体之间可以相互通信、协商和协作,共同完成任务,提升系统的整体性能。
-
自适应性:智能体能够根据环境变化自主调整行为和策略,使系统在复杂场景中保持稳定和灵活。
-
鲁棒性:即使部分智能体出现故障,系统仍能通过其他智能体的协作继续运行。
系统架构
多智能体系统的架构设计通常包括集中式、分布式和层次化架构:
-
集中式架构:存在一个中心节点负责管理和协调各个智能体的行为。
-
分布式架构:每个智能体都具备独立的决策能力,并通过网络通信进行协作。
-
层次化架构:将系统划分为多个层次,每个层次负责不同的任务和功能,有助于提高系统的可维护性和可扩展性。
应用领域
多智能体系统广泛应用于多个领域,包括但不限于:
-
交通管理:用于交通信号控制、车辆调度和路径规划,提高道路通行效率。
-
供应链管理:优化库存管理、运输调度和需求预测,提升供应链效率。
-
游戏开发:创建智能游戏角色和场景,提升游戏的复杂性和趣味性。
-
机器人技术:实现多机器人协作、路径规划和任务分配。
-
智能电网:提升电网的稳定性和安全性。
-
软件开发:通过智能体分工协作,高效推进大型软件项目。
优势
相比单智能体系统,多智能体系统具有以下优势:
-
更高的灵活性和可扩展性。
-
更强的鲁棒性和适应性。
-
能够处理更复杂的任务。
多智能体系统在人工智能领域正逐渐成为研究与应用的热点,其通过智能体之间的协作与协调,为解决复杂问题提供了新的思路和方法。
-
消融实验
消融实验是一种科学研究方法,用于确定一个条件或参数对结果的影响程度。当研究者提出了一个新的方案或方法时,消融实验通过逐一控制一个条件或参数,来观察结果的变化。这种方法在机器学习和深度学习领域广泛应用,用于评估模型各个组件或特征的重要性及其对模型整体性能的影响。
在自动驾驶领域:通过逐步去除某些传感器数据或模型的特定部分,来分析它们对车辆控制和决策的影响,从而提高自动驾驶系统的安全性和稳定性。
-
算力
算力是指 GPU 的计算能力,即每秒可以执行的浮点运算次数。算力越大,训练速度越快,但并不直接决定模型的大小。算力主要影响训练的效率和速度。
-
显存
显存是指 GPU 的内存容量,用于存储模型参数、梯度、中间计算结果等。显存的大小直接影响模型的大小和批量大小。显存越大,可以训练的模型越大,批量大小也可以更大。
b.背景与动机
3.精读
a.架构分析
①.概述
MapCoder框架包含四个LLM Agent,分别模拟程序合成周期的各个阶段: 回顾相关示例、规划、代码生成和调试 。不同于依赖人工注释示例或外部代码检索模型,MapCoder的检索代理能够自主检索相关问题。它首先由一个检索代理开始,该代理自行生成相关示例,随后是计划、编码和迭代调试代理。动态遍历考虑生成计划的置信度作为它们的奖励分数,并利用它们相应地引导代码生成。
②.人工注释或外部代码检索模型和MapCoder的不同
假设我们需要解决一个编程问题,该问题要求编写一个函数来计算斐波那契数列的第n个数字。
依赖人工注释或外部代码检索模型的方法:
-
人工注释:
-
首先,开发者需要手动收集和注释一系列与斐波那契数列计算相关的示例代码。
-
这些示例代码需要覆盖不同的输入情况和边界条件。
-
然后,开发者将这些注释好的示例代码输入到模型中,作为训练数据。
-
-
外部代码检索模型:
-
使用一个预先训练好的代码检索模型,该模型能够根据问题的描述从外部代码库中检索出相关的代码示例。
-
检索模型可能依赖于关键词匹配、语义搜索或其他检索技术来找到最相关的代码。
-
检索到的代码示例被用来指导代码生成模型,生成解决当前问题的代码。
-
MapCoder框架的方法:
-
自主检索相关问题:
-
MapCoder的检索代理(Retrieval Agent)不需要人工注释的示例代码,也不需要依赖外部代码库。
-
它直接根据问题的描述,利用自身的语言理解能力,从内部知识库中检索出与当前问题相关的示例。
-
这些示例可能是之前解决过的类似问题,或者是通过某种方式(如代码生成)自动生成的。
-
-
规划和代码生成:
-
一旦检索到相关示例,MapCoder的规划代理(Planning Agent)会根据这些示例和当前问题的要求,生成一个解决问题的步骤计划。
-
代码生成代理(Coding Agent)根据这个计划,生成相应的代码来解决问题。
-
-
调试:
-
如果生成的代码存在错误或不能正确解决问题,MapCoder的调试代理(Debugging Agent)会利用规划代理提供的计划和示例,对代码进行调试和修正。
-
b.Prompt设计
i.检索智能体(Retrieval Agent)
检索智能体的作用是回顾过去的相关问题解决实例,类似于人类的记忆。 它能够生成与当前问题相关的k个问题及其解决方案,无需手动设计或外部检索模型。 智能体会生成问题描述、代码和计划的元数据,为后续智能体提供辅助数据。
①.翻译
给定一个问题,提供相关的问题,然后识别背后的算法并解释算法的教程。
问题:
{这里将添加问题描述}
示例:
回忆 k 个相关且不同的问题(与上述问题不同)。对于每个问题,
-
描述它
-
生成 {语言} 代码逐步解决该问题
-
最终生成解决该问题的规划
算法:
重要: 您的响应必须遵循以下 xml 格式:
<root>
<problem>
# 回忆 k 个相关且不同的问题(与上述问题不同)。按照以下格式编写每个问题。
<description># 描述问题。</description>
<code># 让我们一步一步地思考如何用{语言}编程语言解决这个问题。</code>
<planning># 解决这个问题的规划。</planning>
</problem>
# 类似地在这里添加更多问题...
<algorithm>
# 识别解决原始问题所需的算法(暴力法、动态规划、分治法、贪心算法、回溯算法、递归、二分搜索等)。
# 编写有关上述算法的有用教程。为解决这类问题提供高层次通用教程。不要生成代码。
</algorithm>
</root>
请注意,上述内容是一个模板,其中 {language}
是一个占位符,应替换为实际使用的编程语言,例如 Python、Java 或 C++。此外,<problem>
和 <algorithm>
标签内的内容需要根据具体的问题和算法进行填充。
②.Retrieval Agent返回示例
<root>
<problem>
<description>描述问题:编写一个函数来计算给定整数的阶乘。</description>
<code>
# 让我们一步一步地用 Python 解决这个问题。
# 导入 math 模块,它提供了一个计算阶乘的函数。
import math
# 定义一个函数来计算阶乘
def factorial(n):
return math.factorial(n)
# 测试函数
print(factorial(5)) # 输出应该是 120
</code>
<planning>
# 解决这个问题的规划:
# 1. 导入 Python 的 math 模块。
# 2. 定义一个名为 factorial 的函数,它接受一个整数参数 n。
# 3. 使用 math.factorial 函数来计算并返回阶乘。
# 4. 编写代码来测试 factorial 函数是否正确工作。
</planning>
</problem>
<algorithm>
# 识别算法:在这个问题中,我们实际上没有使用复杂的算法,而是直接使用了 Python 提供的内置函数。
# 教程:当解决编程问题时,如果标准库中已经提供了解决方案,优先使用这些解决方案可以节省时间和减少错误。
# 通常情况下,对于更复杂的问题,我们可能会用到如下算法:
# - 暴力法(Brute-force):尝试所有可能的解决方案直到找到正确的一个。
# - 动态规划(Dynamic Programming):将问题分解为更小的子问题,存储子问题的解并在需要时重用它们。
# - 分治法(Divide-and-conquer):将问题分解为更小的相同问题,递归解决小问题,然后合并结果。
# - 贪心算法(Greedy):在每一步选择中都采取在当前状态下最好的选择,从而希望导致结果是全局最优的。
# - 回溯算法(Backtracking):尝试分步解决,如果发现部分解决方案不可能是最终解决方案,则撤销上一步操作。
# - 递归(Recursive):函数自我调用解决问题,通常用于可分解为相似子问题的任务。
# - 二分搜索(Binary search):在有序数组中查找项的高效算法,通过反复将搜索区间一分为二。
</algorithm>
</root>
ii.规划智能体(Planning Agent)
规划智能体的目标是为原始问题创建一个分步计划。 它利用检索智能体提供的例子和计划来生成针对原始问题的计划。 该智能体会为每个检索到的例子生成一个目标计划,并为后续步骤提供每个计划的效用信息,包括计划和置信度分数。
①.翻译
规划代理(Planning Agent)
规划生成提示(Planning Generation Prompt): 给定一个竞技编程问题,生成一个具体的规划来解决这个问题。
-
问题:{示例问题描述}
-
规划:{示例问题的规划}
相关算法解决下一个问题: {检索代理获取的算法}
-
要解决的问题:{原始问题}
-
示例输入/输出:{示例输入输出}
重要提示:您应该只给出解决问题的规划。不要添加额外的解释或文字。
置信度生成提示(Confidence Generation Prompt): 给定一个竞技编程问题和一个用{语言}解决问题的规划,判断该规划是否正确解决了这个问题。
-
问题:{原始问题}
-
规划:{前一步我们问题的规划}
重要提示:您的响应必须遵循以下xml格式:
<root>
<explanation>讨论使用上述提到的规划是否可以通过解决给定的竞技编程问题。</explanation>
<confidence>关于问题可解性的置信度评分。必须在0到100之间的整数。</confidence>
</root>
②.Planning Agent返回示例
示例问题:斐波那契数列
问题描述:
编写一个函数来计算斐波那契数列的第n个数字。
规划:
-
定义一个函数
fibonacci(n)
,其中n
是要计算的斐波那契数列的位置。 -
如果
n
小于2,直接返回n
(因为斐波那契数列的前两个数字是0和1)。 -
否则,递归调用
fibonacci(n-1)
和fibonacci(n-2)
,将结果相加得到fibonacci(n)
。
相关算法:
递归
原始问题:
计算斐波那契数列的第10个数字。
规划:
-
定义一个函数
fibonacci(n)
,其中n
是要计算的斐波那契数列的位置。 -
如果
n
小于2,直接返回n
。 -
否则,递归调用
fibonacci(n-1)
和fibonacci(n-2)
,将结果相加得到fibonacci(n)
。
样本输入/输出:
-
输入:10
-
输出:55
XML格式响应:
<root>
<explanation>使用递归方法解决斐波那契数列问题是可行的,因为它直接遵循斐波那契数列的定义。</explanation>
<confidence>90</confidence>
</root>
iii.编码智能体(Coding Agent)
编码智能体接收问题描述和来自规划智能体的计划,将相应的计划翻译成代码以解决问题。 如果在测试样本输入/输出时初始代码失败,智能体会将其传递给调试智能体进行调试; 否则,预测其为最终解决方案。
①.翻译
给定一个竞技编程问题,生成 Python3 代码来解决这个问题。
相关算法解决下一个问题: {检索代理获取的算法}
要解决的问题: {我们将在这里添加问题描述}
规划: {来自规划代理的规划}
示例输入/输出: {示例输入输出}
让我们一步一步思考。
重要提示: 您的响应必须仅包含解决问题的 {语言} 代码。不要添加额外的解释或文字。
②.Coding Agent返回示例
假设我们的问题是要计算斐波那契数列的第n个数字,并且我们已经从规划代理那里得到了规划。以下是一个可能的示例响应:
# 计算斐波那契数列的第n个数字
def fibonacci(n):
if n <= 1:
return n
else:
return fibonacci(n-1) + fibonacci(n-2)
# 示例输入:10
n = 10
# 输出斐波那契数列的第n个数字
print(fibonacci(n))
在这个示例中,编码代理根据规划代理提供的规划生成了 Python 代码。代码定义了一个递归函数 fibonacci
来计算斐波那契数列的第 n
个数字,并打印结果。这个响应仅包含解决问题所需的代码,没有额外的解释或文字。
iv.调试智能体(Debugging Agent)
调试智能体使用问题描述中的样本输入/输出来修正生成代码中的错误。 类似于人类在修复错误时交叉检查他们的计划,该管道通过规划智能体提供的计划来辅助调试智能体,显著增强了MapCoder中的bug修复能力。
①.翻译
给定一个竞技编程问题,你已经生成了{语言}代码来解决问题。但是生成的代码无法通过示例测试用例。改进你的代码以正确解决问题。
相关算法解决下一个问题: {检索代理获取的算法}
规划: {前一步的规划}
代码: {前一步生成的代码}
修改后的规划: 让我们一步一步地修改{语言}代码来解决这个问题。
重要提示: 您的响应必须包含修改后的规划,然后将{语言}代码放在```块中以解决此问题。
②.Debugging Agent返回示例
假设我们之前生成的Python代码无法正确计算斐波那契数列,我们需要对其进行调试和改进。以下是一个可能的示例响应:
修改后的规划:
-
检查递归函数的基本情况是否正确处理。
-
确保递归调用正确并避免重复计算。
-
使用缓存(memoization)来存储已计算的斐波那契数。
代码:
# 使用缓存来优化斐波那契数列的计算
def fibonacci(n, memo={}):
if n in memo:
return memo[n]
if n <= 1:
return n
memo[n] = fibonacci(n-1, memo) + fibonacci(n-2, memo)
return memo[n]
# 示例输入:10
n = 10
# 输出斐波那契数列的第n个数字
print(fibonacci(n))
在这个示例中,调试代理首先提供了修改后的规划,然后给出了改进后的Python代码。代码中使用了缓存(memoization)技术来优化递归计算,避免重复计算斐波那契数。这样可以显著提高代码的效率并确保通过示例测试用例。
v.动态智能体遍历(Dynamic Agent Traversal)
MapCoder的动态遍历从规划智能体开始,它输出原始问题的所有计划及其置信度分数。 这些计划将被排序,最高分的计划将发送给编码智能体。 如果编码智能体生成的代码通过样本输入/输出测试,则返回代码; 否则,将其传递给调试智能体。 调试智能体将尝试最多t次迭代修正代码。 如果成功,返回代码; 否则,责任转回规划智能体,以获取下一个最高置信度的计划。 这一迭代过程持续进行,反映了程序员的方法。
c.实验
MapCoder在多个编程合成基准测试中进行了评估,包括基础编程和具有挑战性的竞争性程序解决基准测试。 MapCoder在代码生成方面表现出色,显著优于所有基线方法 ,并在所有基准测试中实现了新的最先进结果。MapCoder在不同编程语言和不同难度级别上均展现出卓越的性能。 未来的工作将探索将MapCoder扩展到其他领域,如 问答和数学推理 ,以扩大其范 围和影响。
不同方法的Pass@1结果。Self-collaboration的论文结果从他们的论文中收集而来。绿色文本表示最先进的结果,红色文本是直接提示方法的增益。
针对算法类型(标签)和难度级别(xCodeEval数据集)的正确答案数量。
d.消融实验
e.代码剖析
i.src/main.py
parser = argparse.ArgumentParser()
parser.add_argument(
"--dataset",
type=str,
default="HumanEval",
choices=[
"HumanEval",
"MBPP",
"APPS",
"xCodeEval",
"CC",
]
)
parser.add_argument(
"--strategy",
type=str,
default="MapCoder",
choices=[
"Direct",
"CoT",
"SelfPlanning",
"Analogical",
"MapCoder",
]
)
parser.add_argument(
"--model",
type=str,
default="ChatGPT",
choices=[
"ChatGPT",
"GPT4",
"Gemini",
]
)
parser.add_argument(
"--temperature",
type=float,
default=0
)
parser.add_argument(
"--pass_at_k",
type=int,
default=1
)
parser.add_argument(
"--language",
type=str,
default="Python3",
choices=[
"C",
"C#",
"C++",
"Go",
"PHP",
"Python3",
"Ruby",
"Rust",
]
)
args = parser.parse_args()
DATASET = args.dataset
STRATEGY = args.strategy
MODEL_NAME = args.model
TEMPERATURE = args.temperature
PASS_AT_K = args.pass_at_k
LANGUAGE = args.language
RUN_NAME = f"{MODEL_NAME}-{STRATEGY}-{DATASET}-{LANGUAGE}-{TEMPERATURE}-{PASS_AT_K}"
RESULTS_PATH = f"./outputs/{RUN_NAME}.jsonl"
print(f"#########################\nRunning start {RUN_NAME}, Time: {datetime.now()}\n##########################\n")
strategy = PromptingFactory.get_prompting_class(STRATEGY)(
model=ModelFactory.get_model_class(MODEL_NAME)(temperature=TEMPERATURE),
data=DatasetFactory.get_dataset_class(DATASET)(),
language=LANGUAGE,
pass_at_k=PASS_AT_K,
results=Results(RESULTS_PATH),
)
strategy.run()
print(f"#########################\nRunning end {RUN_NAME}, Time: {datetime.now()}\n##########################\n")
这段代码通过
argparse
模块定义了一组命令行参数,这些参数允许用户在运行脚本时指定数据集、策略、模型、温度、通过k次尝试通过的尝试次数和编程语言等。这些参数对于配置和运行脚本至关重要。
parser.add_argument( "--dataset", type=str, default="HumanEval", choices=[ "HumanEval", "MBPP", "APPS", "xCodeEval", "CC", ] )
这行代码添加了一个名为
--dataset
的命令行参数,用于指定要使用的数据集。参数类型为字符串,并且有一个默认值"HumanEval"
。choices
参数限制了用户只能选择列表中的值。
ii.src/gen-eval-plus-sample.py
这段代码的主要功能是读取特定格式的结果文件,处理这些结果(例如添加特定的导入语句),然后生成新的增强数据集文件。它支持两种不同的数据集(HumanEval 和 MBPP),并提供了排除特定任务 ID 的功能。
def generate_ep_dataset(
NORMAL_RESULTS_PATH,
EP_SAMPLES_PATH,
):
samples = []
results = read_jsonl(NORMAL_RESULTS_PATH)
for result in results:
completion = result["source_codes"][-1]
if "from typing import *" not in completion:
completion = "from typing import *\n" + completion
samples.append(
{
"task_id": result["task_id"],
"solution": completion,
}
)
write_jsonl(EP_SAMPLES_PATH, samples)
生成增强数据集:读取一个包含代码完成任务结果的 JSONL 文件,确保每个代码片段都包含特定的导入语句,然后将这些处理后的代码片段保存到一个新的 JSONL 文件中。
mbpp_not_included_set = set([
"Mbpp/304", "Mbpp/393", "Mbpp/399", "Mbpp/401", "Mbpp/408",
"Mbpp/411", "Mbpp/417", "Mbpp/434", "Mbpp/443", "Mbpp/444",
"Mbpp/452", "Mbpp/464", "Mbpp/584", "Mbpp/617", "Mbpp/625",
"Mbpp/627", "Mbpp/738", "Mbpp/747", "Mbpp/756", "Mbpp/776",
"Mbpp/802", "Mbpp/228", "Mbpp/291"
])
这部分定义了一个集合,包含了在处理 MBPP 数据集时需要排除的任务 ID。
def generate_ep_dataset_mbpp(
NORMAL_RESULTS_PATH,
EP_SAMPLES_PATH,
):
samples = []
results = read_jsonl(NORMAL_RESULTS_PATH)
for result in results:
completion = result["source_codes"][-1]
task_id = "Mbpp/" + result["name"].split("_")[1]
if task_id in mbpp_not_included_set:
continue
if "from typing import *" not in completion:
completion = "from typing import *\n" + completion
samples.append(
{
"task_id": task_id,
"solution": completion
}
)
write_jsonl(EP_SAMPLES_PATH, samples)
这个函数类似于
generate_ep_dataset
,但它专门用于处理 MBPP 数据集,并排除了特定的任务 ID。
iii.src/evaluate-et-dataset.py
dataset = read_jsonl(ET_DATA_PATH)
data_dict = {}
for item in dataset:
data_dict[item["task_id"]] = {"et_item": item}
results = read_jsonl(NORMAL_RESULTS_PATH)
for result in results:
data_dict[result["task_id"]]["result"] = result
{"task_id":{"et-item":item,"result":result}} #et_item为增强数据集,result为正常结果文件
correct_count = 0
et_results = []
for key, value in data_dict.items():
item = value["et_item"]
result = value["result"]
generated_code = result["source_codes"][0] if "source_codes" in result else result["solution"]
generated_code = result["source_codes"][0] if "source_codes" in result else result["solution"]
从
result
字典中获取生成的代码。如果result
字典包含键"source_codes"
,则取其第一个元素作为生成的代码;如果不包含,则取键"solution"
对应的值作为生成的代码。这个生成的代码将用于后续的增强测试评估。
passed = evaluate_io_et(
item['test_list'],
generated_code
)
使用
evaluate_io_et
函数评估生成的代码是否通过增强测试。
et_results = sorted(
et_results,
key=lambda x: int(x["name"].split("_")[1])
)
以任务名称中的数字部分为排序键进行排序
write_jsonl(ET_RESULTS_PATH, et_results)
print(
f"Accuracy: {correct_count}/{len(et_results)} = {correct_count/len(et_results):.2f}")
将结果写入到增强测试结果文件中
iv.src/evaluations/func_evaluate.py
def evaluate_io(
sample_io: list[str],
completion: str,
timeout: int = 5,
stop_early: bool = False,
):
定义一个函数
evaluate_io
,用于评估生成的代码片段在多个输入输出(IO)测试用例中的表现。参数包括:
sample_io
:一个包含多个 IO 测试用例的列表,每个测试用例是一个字符串。
completion
:生成的代码片段。
timeout
:执行代码的超时时间,默认为 5 秒。
stop_early
:如果为True
,在第一个测试用例失败时立即停止评估。
test_log = ""
passed = True
for io in sample_io:
try:
code = ("from typing import *\n" if "from typing import *" not in completion else "") + \
completion + "\n" + io + "\n"
function_with_timeout(
exec,
(code, globals()),
timeout
)
test_log += f"passed in test case: {io}\n"
except Exception as e:
if stop_early:
return False, f"failed in test case: {io}\n"
passed = False
test_log += f"failed in test case: {io}\n"
执行一系列 IO 测试用例,并根据测试结果更新
passed
变量和test_log
日志。如果所有测试用例都通过,passed
将保持为True
,test_log
将记录所有通过的信息;如果有任何测试用例失败,passed
将变为False
,test_log
将记录失败的信息。这种设计使得函数能够返回测试结果和详细的日志信息,便于后续分析和调试。
def evaluate_io_et(
sample_io: list[str],
completion: str,
timeout: int = 5,
prompt: str = "",
):
io = "\n".join(sample_io)
try:
code = ("from typing import *\n" if "from typing import *" not in completion else "") + \
prompt + completion + "\n" + io + "\n"
function_with_timeout(
exec,
(code, globals()),
timeout
)
return True
except Exception as e:
return False
将 IO 测试用例列表连接成一个字符串。
try:
code = ("from typing import *\n" if "from typing import *" not in completion else "") + \
completion + "\n" + problem[test_key] + \
"\n" + f"check({problem['entry_point']})"
function_with_timeout(
exec,
(code, globals()),
timeout
)
return "passed"
except Exception as e:
return f"failed: {e}"
构建要执行的完整代码字符串
code
。这个字符串由以下几个部分组成:如果
completion
中不包含"from typing import *"
,则添加这一行。这是为了确保代码中导入了必要的类型注解。
completion
:这是生成的代码片段。
problem[test_key]
:这是从问题描述字典problem
中提取的测试用例代码。test_key
是指定测试用例在字典中的键。
"\n" + f"check({problem['entry_point']})"
:这是调用检查函数的代码,用于验证completion
中的代码是否正确。problem['entry_point']
是代码的入口点,即要执行的函数或方法的名称。
v.src/evaluations/resource_limit.py