摘要
大型语言模型(下文称为:大模型)在代码生成上表现出了强大的能力。大模型依赖于 prompt 作为输入,头脑链是目前用于设计 prompt 的主流法子,在代码生成上取得了目前最好的准确率。但大模型的准确率依旧较低,无法用于实际生产环境。
北京大学李戈、金芝教授团队提出了一种构造化的头脑链,显著地晋升了大模型在代码生成上的准确率。构造化的头脑链约束大模型应用程序构造(例如:顺序、分支和轮回构造)去组织头脑过程,带领大模型从程序语言的角度去思考如何解决需要。实验结果表明:构造化的头脑链稳定地超越了之前的工作(例如:标准的头脑链),进一步晋升了大模型在代码生成上的性能。
论文链接:https://arxiv.org/pdf/2305.06599.pdf
论文概述
大型语言模型(下文称为:大模型)在代码生成上表现出了强大的能力。用户的输入是一条 prompt,其中包括若干个示范样例(需要 – 代码)和一个新的需要。大模型鉴于 prompt 自动地为新需要生成源代码。
现有研究发现:prompt 的设计对于大模型的性能影响较大。因此,如何设计有效的 prompt 来晋升大模型在代码生成上的准确率是软件工程和人工智能领域的一个研究热点。
Chain-of-Thought Prompting (下文称:CoT prompting)是一种用于设计 prompt 的新兴法子,在代码生成上取得了目前最好的准确率。针对一个需要,CoT prompting 先带领大模型思考如何解决需要,生成一段头脑链(下文称:CoT)。CoT 指的是一连串的中间推理法子,描述了如何一步一步地撰写代码。图 1 揭示了在代码生成上 CoT 的示例。尽管 CoT prompting 在代码生成上取得了一定程度的晋升,但它的准确率依旧较低,无法用于实际生产环境。
今年 7 月,北京大学李戈、金芝教授团队(下文称为:研究者们)针对代码生成,提出一种构造化的头脑链(Structured Chain-of-Thought,下文称为:SCoT)。
研究者们的动机是:源代码具有较强的构造性,例如:独特的程序构造 – 顺序构造、分支构造和轮回构造。直觉上来说,一种构造化的头脑链(即中间推理法子)有利于推导出构造化的源代码。
想象一名人类程序员 Allen 在解决一个需要(例如:求取一个列表中的最大值)时的头脑过程:
1. 初始化一个变量 Result;
2. 应用轮回构造遍历列表中的值;
a. 应用分支构造对每个值进行判断,
i. 如果它大于 Result,则更新 Result
ii….
显然,这种鉴于程序构造的头脑过程更贴近程序语言的解题逻辑,因此有利于带领后续的编码实现。
受到上述分析的启发,研究者们提出:应用程序构造来组织头脑过程,得到构造化的头脑链 – SCoT。
图 1(b)揭示了一个 SCoT 的示例。相较于标准的 CoT,SCoT 具有两点分别:
(1)它应用三种基础程序构造来组织中间推理法子。
Bohm 和 Jacopini 在 1966 年指出:任何简单或复杂的算法都可以由顺序构造、分支构造和轮回构造这三种基本构造组合而成[1]。因此,研究者们引入三种基础构造,并约束大模型应用这三种基础构造去生成头脑链。这要求大模型从程序语言的角度去思考如何解决需要,并应用三种基础构造准确地表达头脑过程。
例如在图 1(b)中,SCoT 清晰地揭示了一个大致的解题流程。其中,它应用一个轮回构造准确地描述了第二行的遍历操作(例如:作用域、轮回起止点),并应用一个分支构造去描述分别情况下的处理法子。而在标准的 CoT 中,第二行和第四行的遍历操作存在歧义,例如:作用域模糊。这会误导后续的生成过程,导致生成错误的代码。
(2)它包含输入输出构造。
每一个程序都包含输入输出构造,它指明了程序的输入输出参数及其类型。例如:图 1(b)中的:Input: array: list [list]; Output: result。
研究者们认为,引入输入输出构造有助于大模型去分析需要和明确程序的出入口。同时,一个明确的输入输出构造也有利于带领出后续解题的头脑过程。
鉴于上述的 SCoT,研究者们提出一种新的代码生成法子,叫做:SCoT prompting。针对一个需要,它利用大模型先生成一段 SCoT,然后鉴于需要和 SCoT 生成相应的源代码。相比于 CoT prompting,SCoT prompting 显式地在头脑链中引入程序构造,以此来带领大模型从程序语言的角度来思考如何解决需要。这进一步释放了大模型在代码生成上的推理能力,从而晋升大模型的准确率。
研究者们将 SCoT prompting 应用至两个大模型(Codex 和 ChatGPT),并在三个代码生成数据集上进行了验证。研究者应用单元测试用例来评估生成的代码的正确性,并计算 Pass@K。实验结果表明:
在三个数据集上,SCoT prompting 稳定地超越了目前最好的法子 – CoT prompting。例如,在 Pass@1 上,SCoT prompting 在三个数据集上分别获得了 13.79%、12.31% 和 6.63% 的相对晋升;
人工评估表明:人类程序员更偏爱鉴于 SCoT prompting 生成的代码;
SCoT prompting 在分别的大模型和编程语言上都具有稳定的效果;
SCoT prompting 具有较强的鲁棒性,不依赖于具体的示范样例和写作风格。
总的来说,本文的贡献可总结为以下几点:
一种构造化的头脑链 – SCoT,它应用程序构造去组织中间推理法子;
一种新的鉴于大模型的代码生成法子 – SCoT prompting,它利用大模型先生成构造化的头脑链,再生成源代码;
进行了大量的定性和定量实验,揭示了构造化头脑链的有效性。
构造化的头脑链 – SCoT
标准的头脑链(CoT)初始是为自然语言生成任务而设计,应用自然语言顺序地描述如何逐步地解决问题。在代码生成上,CoT 带来的晋升有限,大模型的准确率仍旧较低。
在本文中,研究者们提出一种构造化的头脑链(Structured CoT,SCoT)。SCoT 显式地引入程序构造去撰写头脑链,带领大模型应用程序语言的逻辑去思考如何解决需要。图 2 揭示了 SCoT 的一些样例。
现有研究表明:任何简单或复杂的算法都可以由顺序构造、分支构造和轮回构造这三种基本构造组合而成。因此,研究者们应用这三种基本构造撰写头脑链。三种基本构造的详情如下所示:
顺序构造:中间法子被顺序地组织,所有的法子位于相同的层级。
分支构造:它以一个条件(condition)作为起始,并鉴于条件的分别结果放置分别的中间法子。在本文中,分支构造包含三种形式:if …, if … else, if … elif … else。
轮回构造:重复地执行一系列中间法子,直到某项条件不被满足。在本文中,轮回构造包括两种形式:for 轮回和 while 轮回构造。
分别的程序构造可以被嵌套应用,这允许大模型自主地设计更复杂的 SCoT 去解决困难的需要。如图 2 所示,SCoT 灵活地应用各种程序构造去构建一个解题流程。
除了三种基本构造,研究者们还引入了输入输出构造,它包括输入输出参数及其类型。研究者们认为输入输出构造反映了程序的入口和出口。生成输入输出构造有助于澄清需要并带领后续的推理过程。
SCoT prompting
鉴于构造化的头脑链(SCoT),研究者们面向代码生成提出一种新的 prompt 设计法子 – SCoT prompting。它带领大模型先生成一段 SCoT,然后再生成相应的源代码。
为了实现 SCoT prompting,研究者们设计了两种特殊的 prompts。第一个 prompt 用于带领大模型鉴于需要生成一段 SCoT,图 3 揭示了该 prompt 的一个示例。这个 prompt 包含若干个人工撰写的示范样例(即:需要 – SCoT)和一个新的需要。这些示范样例覆盖了三种基本程序构造和输入输出构造。斜体字是面向大模型的自然语言指令,描述任务的定义。大模型从示范样例中学习,并为新需要生成相应的 SCoT。
生成一段 SCoT 之后,研究者们设计第二种 prompt 来利用大模型生成最终的代码。图 4 揭示了第二种 prompt 示例。这个 prompt 包含若干个人工撰写的示范样例(即:需要 – SCoT – 代码),以及新的需要和 SCoT。斜体字是面向大模型的自然语言指令,描述任务的定义。大模型从示范样例中学习,并鉴于新需要和 SCoT 生成相应的源代码。
现有研究发现:多阶段的生成法子容易受到错误积累的影响。类似地,在 SCoT prompting 中,第一步生成的 SCoT 中可能包含噪声(例如:错误法子)。这些噪声会误导后续的编码实现,导致生成错误的代码。针对这一点,研究者们采用了两种法子来缓解错误积累问题。
如图 4 所示,研究者们要求大模型去检查 SCoT,并修复其中可能的错误。这允许大模型选择性地参考 SCoT 并忽略其中的噪声。
此外,SCoT prompting 采用了一种两阶段的生成流程,这提供了一个与人交互的窗口。在实际场景中,用户可以先检查 SCoT 的正确性并修复其中问题,然后再应用 SCoT 生成代码。
实验设计
研究者设计了一个大规模的评估来回答四个研究问题:
问题 1:相较于现有法子,SCoT prompting 在代码生成上的准确率如何?
问题 2:人类程序员是否更偏爱 SCoT prompting 生成的代码?
问题 3:SCoT prompting 对于分别的示范样例是否是鲁棒的?
问题 4:SCoT prompting 中分别程序构造的贡献是怎么样的?
数据集 & 评估指标
研究者在三个流行的代码生成数据集上进行评估,包括:HumanEval、MBPP 和 MBCPP。三个数据集的统计结果如表 1 所示。
研究者们采用单元测试来衡量生成的代码的正确性,并计算 Pass@k。
Baselines
研究者挑选了代码生成上已有的三种 prompting 法子作为 baselines。
Zero-shot prompting:利用大模型鉴于需要直接生成源代码,不需要示范样例;
Few-shot prompting:随机地挑选一些需要 – 代码对作为示范样例,利用大模型为一个新的需要直接生成源代码;
Chain-of-Thought prompting:few-shot prompting 的一个变体,采用需要 – 头脑链 – 代码作为示范样例,带领大模型先生成一段头脑链,再生成源代码。
实验结果及分析
问题 1:相较于现有法子,SCoT prompting 在代码生成上的准确率如何?
研究者将 baselines 和 SCoT prompting 应用至两个大模型(Codex 和 ChatGPT)上,并衡量它们在三个数据集上的 Pass@k。实验结果如表 2 所示。SCoT prompting 在三个数据集上显著地超越了所有的 baselines。相较于 CoT prompting,在 Pass@1 上,SCoT prompting 在三个数据集上分别取得了 13.79%、12.31% 和 6.63% 的相对晋升。这些晋升显示了 SCoT prompting 在代码生成上的有效性。
问题 2:人类程序员是否更偏爱 SCoT prompting 生成的代码?
代码生成的目的是辅助人类程序员撰写代码。因此,研究者们雇佣了 10 名人类开发者作为评估员,来评估分别法子生成的代码。评估指标如下所示:
正确性:代码是否正确地实现了需要;
代码异味:代码是否包含代码异味;
可维护性:代码的实现是否标准,是否具有较好的可读性。
每项指标的细节请见论文原文。每个指标的分数是一个从 0 到 2 的整数,分数越大则表明在该方面表现越好。人工评估的结果如表 2 所示。SCoT prompting 在三个指标上的得分都稳定地优于 baselines。
图 5 揭示了 few-shot prompting 和 SCoT prompting 在同一个需要上的输出。两个法子生成的代码都通过了所有的测试用例。但 few-shot prompting 生成的代码中包含一条很晦涩难懂的条件语句。在实际场景中,程序员需要花费额外的精力去理解和维护这样的程序。相较之下,SCoT prompting 生成的代码具有较好的可读性,更易于维护。此外,SCoT 清晰地解释了代码的整体行为,可以当做代码的注释,便于后续的维护。
问题 3:SCoT prompting 对于分别的示范样例是否是鲁棒的?
如图 3 和图 4 所示,SCoT prompting 需要一些人工撰写的示范样例来制作 prompt。在真实世界中,分别的用户会写出分别的样例,这可能会导致 SCoT prompting 的性能有一些波动。因此,研究者们探究 SCoT prompting 对于示范样例的鲁棒性。
研究者们从两个方面探究 SCoT prompting 的鲁棒性:
样例的选择。研究者们随机地选择多组需要 – 代码对作为种子,然后要求一名标注人员鉴于分别的种子撰写示范样例。之后,研究者们衡量 SCoT prompting 在分别示范样例上的性能;
写作风格。分别的标注人员有分别的写作风格。研究者挑选一组需要 – 代码作为种子,雇佣多名标注人员鉴于相同的种子撰写示范样例。之后,研究者们衡量 SCoT prompting 在分别示范样例上的性能。
为了比较,研究者们同样衡量了 CoT prompting 在上述场景下的鲁棒性。
实验结果如表 5 和表 6 所示。SCoT prompting 对于示范样例具有较强的鲁棒性。它并不依赖于特定的样例或者写作风格,在分别的设置下都优于 CoT prompting。
问题 4:SCoT prompting 中分别程序构造的贡献是怎么样的?
SCoT 中包括三种基本构造和输入输出构造。研究者们进一步探究了分别的程序构造对最终性能的贡献。具体来说,研究者们分别将基本构造和输入输出构造移除,然后衡量 SCoT prompting 在三个数据集上的性能。
实验结果如表 4 所示。从中可以看出,基本构造和输入输出构造都是必要的。研究者们进一步观察了具体的样例,并定性地分析了分别程序构造的作用。详情可见论文原文。
讨论
SCoT 和伪代码的比较
本文的 SCoT 与伪代码具有一些相似之处。二者都包含输入输出构造和一个大致的解题流程。研究者们随机挑选了 100 条生成的 SCoTs。经过人工检查,研究者们发现,26% 的 SCoTs 与伪代码很相近。其余大部分(74%)的 SCoTs 与伪代码分别,因为 SCoT 更加的抽象,不包含具体的实现细节。研究者们认为这种一定程度的相似性也增强了 SCoT prompting 的可用性。在实际场景中,程序员可以通过 SCoT 快速地了解代码的整体行为,也可以应用 SCoT 作为代码注释,便于后续的维护。
为了进一步验证 SCoT 的优越性,研究者们设计了一个变体 – SCoT-P prompting。它与 SCoT prompting 有相同的流程,但采用伪代码作为头脑链。表 7 揭示了 SCoT prompting 和 SCoT-P prompting 的比较结果。从中可以看出,SCoT prompting 稳定地优于 SCoT-P prompting。这揭示了本文 SCoT 在代码生成上的优越性。
SCoT prompting 和排序技术的比较
最近,一些研究人员提出各种排序技术(例如:CodeT)来晋升大模型在代码生成上的准确率。针对一个需要,他们先利用大模型生成大量的候选代码,然后利用测试用例或者神经网络对候选代码进行排序,选出其中的 Top-n 个代码作为最终输出。
研究者们并没有将 SCoT prompting 与这类排序技术直接对比,主要原因是:SCoT prompting 和排序技术的应用场景分别,且二者是互补的。SCoT prompting 旨在设计更有效的 prompt 来晋升大模型的准确率。排序技术并不关心大模型,而是聚焦于从大模型的输出中挑选出更好的代码。在实际场景中,程序员可以先应用 SCoT prompting 生成大量的候选代码,再应用排序技术挑选最终输出。
为了验证两种法子的互补性,研究者们挑选了一个经典的排序技术 – CodeT。研究者们将 ChatGPT 作为基础模型,逐渐地引入 CodeT 和 SCoT prompting。实验结果如图 8 所示。可以看出,引入两种法子不断地晋升 ChatGPT 的准确率。
总结和未来工作
本文提出了一种构造化的头脑链(SCoT),用于晋升大模型在代码生成上的准确率。它约束大模型应用程序构造去组织头脑过程,带领大模型从程序语言的角度去思考如何解决需要。在三个 benchmarks 上的实验结果表明了 SCoT 的有效性。
未来,研究者们会进一步探索如何晋升大模型在代码生成上的可用性,包括:鉴于上下文的代码生成、长代码生成等等。
参考链接:
[1]Corrado Böhm and Giuseppe Jacopini. 1966. Flow diagrams, turing machines and languages with only two formation rules. Commun. ACM 9, 5 (May 1966), 366–371. https://doi.org/10.1145/355592.365646