大型
语言模型
(下文称为:大模型)在代码生成上表现出了强大的能力。大模型依赖于 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 在解决一个需求(例如:求取一个列表中的最大值)时的思维过程:
i. 如果它大于 Result,则更新 Result
显然,这种基于程序结构的思维过程更贴近程序语言的解题逻辑,因此有利于引导后续的编码实现。
受到上述分析的启发,研究者们提出:使用程序结构来组织思维过程,得到结构化的思维链 – SCoT。
图 1(b)展示了一个 SCoT 的示例。相较于标准的 CoT,SCoT 具有两点不同:
Bohm 和 Jacopini 在 1966 年指出:任何简单或复杂的算法都可以由顺序结构、分支结构和循环结构这三种基本结构组合而成[1]。因此,研究者们引入三种基础结构,并约束大模型使用这三种基础结构去生成思维链。这要求大模型从程序语言的角度去思考如何解决需求,并使用三种基础结构准确地表达思维过程。
例如在图 1(b)中,SCoT 清晰地展示了一个大致的解题流程。其中,它使用一个循环结构准确地描述了第二行的遍历操作(例如:作用域、循环起止点),并使用一个分支结构去描述不同情况下的处理方法。而在标准的 CoT 中,第二行和第四行的遍历操作存在歧义,例如:作用域模糊。这会误导后续的生成过程,导致生成错误的代码。
每一个程序都包含输入输出结构,它指明了程序的输入输出参数及其类型。例如:图 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,它利用大模型先生成结构化的思维链,再生成源代码;
-
进行了大量的定性和定量实验,展示了结构化思维链的有效性。
标准的思维链(CoT)初始是为自然语言生成任务而设计,使用自然语言顺序地描述如何逐步地解决问题。在代码生成上,CoT 带来的提升有限,大模型的准确率仍旧较低。
在本文中,研究者们提出一种结构化的思维链(Structured CoT,SCoT)。SCoT 显式地引入程序结构去撰写思维链,引导大模型使用程序语言的逻辑去思考如何解决需求。图 2 展示了 SCoT 的一些样例。
现有研究表明:任何简单或复杂的算法都可以由顺序结构、分支结构和循环结构这三种基本结构组合而成。因此,研究者们使用这三种基本结构撰写思维链。三种基本结构的详情如下所示:
-
顺序结构:中间步骤被顺序地组织,所有的步骤位于相同的层级。
-
分支结构:它以一个条件(condition)作为起始,并基于条件的不同结果放置不同的中间步骤。在本文中,分支结构包含三种形式:if …, if … else, if … elif … else。
-
循环结构:重复地执行一系列中间步骤,直到某项条件不被满足。在本文中,循环结构包括两种形式:for 循环和 while 循环结构。
不同的程序结构可以被嵌套使用,这允许大模型自主地设计更复杂的 SCoT 去解决困难的需求。如图 2 所示,SCoT 灵活地使用各种程序结构去构建一个解题流程。
除了三种基本结构,研究者们还引入了输入输出结构,它包括输入输出参数及其类型。研究者们认为输入输出结构反映了程序的入口和出口。生成输入输出结构有助于澄清需求并引导后续的推理过程。
基于结构化的思维链(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。
研究者挑选了代码生成上已有的三种 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 与伪代码具有一些相似之处。二者都包含输入输出结构和一个大致的解题流程。研究者们随机挑选了 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 在代码生成上的优越性。
最近,一些研究人员提出各种排序技术(例如: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