导语
本研究提出了一个名为Self-evolve的框架,它旨在通过大型语言模型(LLMs)实现代码生成的进化。这一框架在Text-to-Code任务中引入了一种全新的处理流程,以提高LLMs在代码生成方面的效率和准确性。在之前,尽管LLMs在代码生成方面已取得显著成效,但它们仍然面临着一次性准确生成代码的挑战。Self-evolve通过其独特的双阶段流程,有效地解决了这一问题。在第一阶段,该框架利用LLMs从输入的提示中提取知识,生成中间代码。进入第二阶段,Self-evolve转变LLMs的角色,使其成为专家级程序员,对这些生成的代码进行调试和优化,而且这个过程不需要专门设计的测试用例,仅通过解释器的错误消息即可完成。
1 引言
在现代应用程序开发中,代码生成是一个关键且具有挑战性的组成部分。随着预训练语料库的扩展,大型语言模型(LLMs)在多种任务和领域的表现显著提升,使得LLMs成为代码生成的首选模型。事实上,LLMs的表现远超过之前专门用于代码生成的深度神经网络模型。同时,LLMs已经被用于增强以前的方法,它们能够消化各种提示内容,并进行文本生成。尽管以往的研究中,人们通过添加各种辅助信号到提示中以获取更准确的代码,但这些方法大多通过外部检索器和大型知识库来获取这些信号。然而,这些方法存在一个问题:检索器在适应不同任务时可能会遇到领域不匹配的问题,这限制了它们的通用性。此外,当前的检索器并不适合处理半结构化的知识项,如库文档,这可能导致检索结果不佳。
为了避免领域不匹配和不准确的检索结果,本研究提出Self-evolve框架,旨在通过大型语言模型(LLMs)推动代码生成的进步。与以往依赖于外部检索器和大型知识库的方法不同,Self-evolve采用两阶段范式,将LLM本身视为知识来源。作者指出,LLM已编码了各种领域知识,可作为一个大型知识库。因此,Self-evolve通过提示LLM自行生成所需知识,避免了传统检索方法的领域不匹配和检索不准确的问题。
Self-evolve的创新之处在于它的自动优化(self-refinement)机制,该机制模仿人类程序员依赖调试器来确保代码实现的正确性。通过构建可运行的程序并执行测试用例,该框架能够自动纠正初步代码,提高代码的准确性和可靠性。
在实验方面,作者使用gpt-3.5-turbo(ChatGPT)来构建Self-evolve,并在多项任务上评估其性能,包括数据科学代码生成、通用代码生成和代码翻译任务。实验结果显示,Self-evolve在各项任务中均实现了显著的性能提升。此外,该框架能够提供比传统检索方法更准确的知识,并且能轻松扩展到更强大的模型,如GPT-4。
2 相关工作
增强型代码生成(Augmented code generation):在LLM时代之前,研究人员已经尝试通过编程环境和功能文档来生成代码。最近,基于各种语料库预训练的LLMs支持了零次或少次学习的上下文学习流程。例如,一些研究利用检索类似代码片段或API文档来增强代码生成模型。然而,这些方法的性能受限于当前检索器模型的能力,且难以适应目标领域。
自动代码优化(Automatic code refinement):由于语言模型常常以高置信度输出不可靠信息,一些研究致力于专门修复错误代码的模型。近期的工作中,LLMs被用作"教师"角色,修复代码中的隐藏错误。不过,这些方法在代码生成中的应用有其局限性,例如格式化问题或暴露测试用例,与真实编程场景不符。
相较于现有方法,Self-evolve不需要特定领域的微调,提供了更高的可访问性和通用性,其自动代码精炼方法也更贴近实际编码场景。
3 self-evolve: Code Evolution via Large Language Models
3.1 背景
代码生成形式化 :在自然语言 <math xmlns="http://www.w3.org/1998/Math/MathML"> d d </math>d和代码上下文 <math xmlns="http://www.w3.org/1998/Math/MathML"> c c </math>c的基础上,自回归语言模型 <math xmlns="http://www.w3.org/1998/Math/MathML"> p θ p_θ </math>pθ预测解答,其中 <math xmlns="http://www.w3.org/1998/Math/MathML"> n n </math>n表示预测长度, <math xmlns="http://www.w3.org/1998/Math/MathML"> X = [ d ; c ] X=[d;c] </math>X=[d;c]。
两阶段代码生成Pipeline:对于LLM而言,仅凭问题描述进行代码生成仍具挑战性。大多数程序员在编程时会参考知识文档并使用现有工具进行调试。基于此,本文将基于提示的代码生成方法分为两个步骤:首先是引导语言模型理解额外的知识和特定任务的指令,其次是教导模型根据人类或oracle讲师的反馈修改生成的代码解决方案。这两个步骤在优化方面遵循一定的顺序,可有序优化并融合。
3.2 所提出的Self-evolve Pipeline
知识生成:Self-evolve改进了这两个步骤,允许生成的代码仅使用单一大型语言模型逐步进化,无需额外学习。与前期工作相同,Self-evolve通过对提示中的知识作为条件来生成代码。但区别在于,这里的知识是由LLM生成的,而非从外部知识库检索。在获取第一步输出后,Self-evolve利用LLM迭代修改生成的代码,遵循Chen等人的方法,通过利用来自代码执行器的反馈来纠正代码错误,但无需特定的测试用例。
借助语言模型生成知识 根据Prompt中的知识来调整语言模型是至关重要的,但也是具有挑战性的。给定m个知识项 <math xmlns="http://www.w3.org/1998/Math/MathML"> K [ 1... M ] K[1...M] </math>K[1...M],语言模型预测生成最终代码解的下一个token:
其中的知识可以通过稀疏检索器(sparse retriever)或者稠密检索器(dense retriever)得到:
但现有检索器模型的性能可能受限,导致 <math xmlns="http://www.w3.org/1998/Math/MathML"> K K </math>K中包含不相关知识项,为LLM增加噪声,影响生成结果。为更准确地获取所需知识,本文利用语言模型作为知识来源,促使其生成信息。经过人类反馈强化学习的模型可遵循人类指令,作为知识来源,并根据适当的输入指令提供杂项知识。
其中 <math xmlns="http://www.w3.org/1998/Math/MathML"> k k </math>k为生成的知识令牌的长度。当问题描述包含隐式意图时,例如在StackOverflow中,所需的详细知识与用于描述问题的单词之间通常存在差距。这种差距的产生是因为获得所需的知识涉及推理。为了缩小这种推理差距并获得更精确的知识,本文将隐含意图的提取过程分解为:
其中 <math xmlns="http://www.w3.org/1998/Math/MathML"> c c </math>c是LLM基于问题上下文的试用代码解决方案。它包含必要但可能被误用的知识,这些知识有利于提取知识 <math xmlns="http://www.w3.org/1998/Math/MathML"> ( K ) (K) </math>(K)。 <math xmlns="http://www.w3.org/1998/Math/MathML"> K K </math>K可以格式化为任何特定于问题的结构,以适应问题指令 <math xmlns="http://www.w3.org/1998/Math/MathML"> X X </math>X,使其适用于各种任务。当问题描述 <math xmlns="http://www.w3.org/1998/Math/MathML"> X X </math>X包含显式意图时,Self-evolve使用(4)式代替,因为LLM在这种情况下可以很容易地提取知识并具有较高的准确性。
修正生成的答案:由LLM生成的中间结果可能包含错误,这些错误可能引入噪音,降低最终输出的准确性。为减少代码错误,本文模仿程序员的调试过程,引入迭代式自我优化机制来纠正错误代码。这种机制利用外部Python解释器修正错误程序,将代码上下文和样本测试用例与生成的代码解决方案结合,形成可执行程序。在沙盒环境中执行该程序,获取错误信息和标准输出。获得错误信息后,提示语言模型根据程序要求和错误信息修正错误程序。修正后的输出可能仍含错误,因此重复上述过程直至无异常或达到迭代阈值。实际应用中,根据错误类型的不同,建模方法也不同。为简化,Self-evolve仅修正API错误和不正确的断言语句,这在实证实验中显著提升性能。
总之,通过结合基于自生成知识的生成和通过错误消息的优化,Self-evolve创建了一种更有效的LLM驱动方法。这两部分相互加强,一方面,自生成的知识促进自我优化步骤,减少了修复代码所需的迭代次数。另一方面,自我优化步骤通过引入外部解释器消除生成过程中的噪音,提高了代码的整体质量。后续实证实验将展示这两个模块如何相互强化。
4 实验
4.1 Baseline
- DocPrompting:通过检索与问题相关的文档来改善LLM,并以此文档和问题描述为条件生成代码。
- Self-Debugging:依赖SQL应用或Python解释器教导语言模型修正含有错误的SQL命令或Python代码。在DS-1000和HumanEval中,由于没有训练集,采用零次学习方式实施。
- Self-evolve:本工作提出的代码生成Pipeline。主实验中使用ChatGPT作为知识生成器和代码优化器。
4.2 Self-evolve的主要结果
数据科学代码生成:在DS-1000基准上,Self-evolve通过语言模型生成与问题相关的API文档作为领域所需知识。对于自我优化模块,检查可执行程序并提示语言模型仅修复语法错误。结果显示,即使没有进一步的优化步骤,Self-evolve在Surface和Diff-Rewrite扰动类型上已超过强大的ChatGPT基准线。通过额外的自我调试模块,Self-evolve在平均pass@1得分上相比ChatGPT有显著提升,也超过了基于提示的方法,例如Self-Debugging。
通用代码生成:在HumanEval基准上评估Self-evolve。该数据集包含164个手写Python编程问题。实验结果表明,Self-evolve显著提升了ChatGPT基准的pass@1和pass@10得分,缩小了与GPT-4的差距。与仅应用优化模块的Self-Debugging相比,Self-evolve在自我生成知识的帮助下取得了更大的提升。
Python代码翻译:在TransCoder基准上测试Self-evolve,该基准要求将C++代码翻译成Python代码。实验结果表明,Self-evolve在计算精度和pass@1上均取得了最佳性能。即使没有自我优化模块,Self-evolve也在ChatGPT上取得了进步。
4.3 讨论
迭代步骤如何影响性能? 在HumanEval和TransCoder数据集上,主要的改进来自第一次优化步骤。在DS-1000上,随着优化步骤的增加,性能近乎均匀提升。
自我生成知识的人类评估:为了更好地理解生成知识在现实场景中的优势,进行了人类评估研究。结果表明,生成的知识比基于检索的知识在精确度和召回率上更准确。
扩展到更强大的模型:在GPT-4上集成Self-evolve,表明Self-evolve可以从更高级的模型中受益,而不是降低它们的性能。GPT-4基础上的Self-evolve在Scipy、Pytorch和Sklearn上取得了更高的pass@1分数。
4.4 案例研究
通过两个代表性示例展示了Self-evolve的有效性。在第一个示例中,LLM生成了特定文档,与没有文档的原始输出相比,生成了更具确定性的代码。第二个示例中,LLM阅读了np.asarray文档,但忘记了计算平均值。利用traceback信息,无需特定测试用例,LLM可以修正其代码。这两个示例说明了Self-evolve中的两种方法如何相互加强,提高性能。
5 限制与未来工作
尽管Self-evolve在生成知识和提升大型语言模型(LLMs)的性能方面显示出了前景,但仍存在一些需要解决的局限性。Self-evolve的主要挑战之一是,由于手写提示词的使用,在不同任务中可能不总是自动化的。这意味着其有效性可能在应用于其他用例时受到限制。此外,生成的知识可能并不总是适合每个任务,可能需要细致选择才能有效。然而,这些问题可以通过开发适当的提示技巧来缓解。例如,可以开发更全面的提示词集,以便更容易地将Self-evolve适应新任务。此外,可以开发更复杂的算法,自动为特定任务选择合适的知识。我们相信,解决这些问题将使Self-evolve在不同环境下成为更多功能和更有用的框架。
第六章:结论
本文提出了Self-evolve,一种简单但有效的方法,使用大型语言模型(LLMs)作为完全由LLM驱动的框架来解决代码生成问题。它充当知识提供者和自我反思的程序员,在两个步骤中生成高质量的代码,这两个步骤都由单个LLM运行。这使得它更加灵活,可扩展到其他数据集,如Spider或APPS,而不需要额外的检索器或预先设置的数据库。在多种代码生成任务上的大量实验验证了Self-evolve在各种任务和数据集下都能带来显著的性能提升,并以较大优势超越了两种强大的基于提示的方法。此外,各种分析实验表明,与传统的稠密检索器相比,Self-evolve更适合提供与问题相关的知识,并且可以轻松扩展到更智能的语言模型以带来进一步的改进。