大模型寫代碼能力突飛猛進,北大團隊提出結構化思維鏈SCoT
大型語言模型(下文稱為:大模型)在代碼生成上表現出了強大的能力。大模型依賴于 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 的有效性。
未來,研究者們會進一步探索如何提升大模型在代碼生成上的可用性,包括:基于上下文的代碼生成、長代碼生成等等。