提示工程 vs. 盲目提示
原文由 Mitchell Hashimoto 于 發布,訂閱此部落格
「提示工程」(Prompt Engineering)一詞隨著語言模型的快速發展而出現,用來描述透過提示(prompting)有效從語言模型中提取資訊的過程,通常是為了應用在真實世界的場景中。
如今聲稱自己在做提示工程的人,有很多其實只是在盲目提示(blind prompting)。1 「盲目提示」是我用來描述一種做法的詞彙:用粗糙的試誤法來建立提示,搭配極少或完全沒有測試,以及對提示技巧只有非常淺層的理解。盲目提示不是提示工程。
也有不少人質疑,提示工程是否真的能被稱為「工程」,還是只是追逐炒作的人所宣揚的「巫術」。我認為,這種質疑大多源於一個事實:我看過許多自稱在談提示工程的推文和部落格文章,充其量也只是比盲目提示多了一層薄薄的包裝。
在這篇部落格文章中,我將論證提示工程是一項可以透過真實實驗方法來培養的實在技能。我會用一個貼近現實的範例,一步步示範如何透過提示工程為一個能為應用程式帶來實際價值的問題打造解決方案。
整篇文章都會偏重於文字輸出,因為這是我使用語言模型時最主要的應用場景。某些技巧——例如測試技巧——並無法一對一地套用到圖像等其他類型的輸出上。不過,本文的所有內容對於多模態的輸入同樣適用。
什麼是提示(Prompting)?
如果你已經非常熟悉「提示」(prompting)這個詞,或已經了解什麼是「提示詞」(prompt),可以跳過這一節。
對於語言模型(如果你對這個詞不熟悉,可以想成 ChatGPT),「提示詞」(prompt)就是使用者輸入給模型的內容。在 ChatGPT 中,你可以把它理解為你輸入文字的對話框。接著語言模型會對你的提示詞「推論」出一個「補完」(completion)。舉例來說,如果我在 ChatGPT 中輸入「4 + 3 = 」,它大概會回應「7」。在這個例子中,「4 + 3 = 」就是提示詞,而「7」就是補完。
「提示」(Prompting)就是利用提示詞從模型中提取所需資訊的行為。這是一個很吸引人的做法,因為你不需要龐大的離線訓練資料集,不需要離線存取模型,而且即使是非工程背景的人也覺得直觀易懂。提示只是調校模型的一種方法。
最後,「提示工程」所描述的是一門更嚴謹的學問(如本文後續將展示的),其目標是將提示作為一種手段,為真實世界的應用打造可靠的功能。它與 ChatGPT 式的提示不同,因為透過提示工程產生的提示詞,通常是為了在大量且多樣的情境中反覆使用,以穩定地為應用程式解決某個特定問題。
問題
首先,你必須有一個想要解決的問題。這個問題可以用來評估提示是否為最佳解法,或是否存在更適合的替代方案。工程的起點不是為了用某種方法而用,而是基於相信它是正確的方法才去採用。
在這個範例中,我們假設自己是一家開發行事曆應用程式的公司。我們希望使用者能夠用自然語言來新增行程。例如:
Dinner with Alice next Tuesday at Taco Bell
CorpConf on 11/4
1:1 with Bob tomorrow at 10 AM
語言模型或許是個不錯的解法,可以接收這些自然語言輸入,並提取出結構化的輸出來說明行程,供我們在應用程式中使用。
當然也有其他可能的解法。我們可以利用一組正規表示式和字串搜尋來尋找常見的片語(on <Day of Week>、tomorrow、today、next week 等)。
語言模型也有其優勢:它們或許能更好地處理其他語言,或更能容忍錯字與文法錯誤,最不濟也能在正規表示式失效時作為備援。無論如何,繼續將提示作為潛在解法來探索,已經有足夠的理由。
示範集
接下來,我們必須建立一個示範集。示範集中包含預期的輸入以及對應的預期輸出。這個集合有幾個用途:
它將用來衡量提示詞的準確率。透過使用單一示範的輸入,我們可以驗證是否能得到預期的輸出。
它明確定義了我們期望提示詞的輸入與輸出長什麼樣子,讓我們這些工程師能判斷這樣的形式是否適合解決我們的問題。
我們可以將這個示範集的子集作為少樣本(few-shot)方法的範例,如果我們決定採用少樣本提示的話。對於不熟悉「少樣本」一詞的人來說,少樣本是一種除了提示詞本身之外還會提供範例的提示風格。請參閱這裡以了解 Few-Shot 與 Zero-Shot 提示的概覽。
上述的第 (2) 點極為重要。我們需要大致了解預期的輸入與預期的輸出是什麼樣子,因為在這兩端通常都有需要確保資料符合特定格式、並期待特定格式回傳的軟體。這與典型的軟體工程並無二致——我們將問題拆解成一組具有輸入/輸出預期的函式。
我們可以沿用前面的範例,並將它們擴充為完整的示範:
Q: Dinner with Alice next Tuesday at Taco Bell
A: next Tuesday
Q: CorpConf on 11/4
A: 11/4
Q: 1:1 with Bob tomorrow at 10 AM
A: tomorrow
關於示範集大小的說明:在這篇部落格文章中,我們只有三筆示範。實際上,你可能會想要至少十幾筆。示範越多,你能做的測試就越完善,但也會因為 token 使用量而變得越昂貴。達到一定規模後,直接對語言模型進行微調往往更符合經濟效益。
上述示範中做了兩個重要的決定。對於任何提示問題,你都必須做出類似的抉擇。
第一,我們只提取一項資訊。你或許會想讓模型一次性提取整個行程的所有資訊,例如活動名稱、參與者、時間、地點等,並輸出成漂亮、可直接使用的 JSON 或其他格式。模型或許做得到。但在面對新問題時,我建議先將問題拆解為單一任務。這會讓問題更容易處理,最終也能提供一個基準準確率,讓你用來評估多重輸出的方法是否真的值得。
第二,我們的輸出沒有經過轉換。我們並沒有試圖把所有內容都轉成日期,或確保大小寫都正確等等。我們做的是字面上的文字提取。市面上已經有非常出色的確定性函式庫,能以極高的準確率將像「next Tuesday」這樣的字串轉換為時間戳。這不是我們需要語言模型來幫忙做的事。因此,我們就只讓模型提取出粗略的日期格式(「next Tuesday」、「11/4」、「tomorrow」),再用傳統的程式方法來解決將其轉為時間戳的問題,因為那是個簡單的問題。輸出越單純,就越容易達到更高的準確率。
最後,關於輸出解碼的簡短說明:LLM 會以各種方式來補完你的提示詞:它可能會回一個完整句子、可能會加上句號、可能會大寫等等。你應該決定你希望 LLM 的輸出要多麼完美,以及在驗證示範集之前,你願意做多少正規化處理。
舉例來說,如果我在做文字提取,我通常會覺得去除空白與句號、並將整個輸出轉為小寫是合理的。如果我在做更進階的 JSON 生成,我可能會先解析再以某種確定的順序與格式重新編碼 JSON,讓比較變得可確定。依此類推。
我的建議是:讓 LLM 的輸出盡可能保持簡單與彈性,並在你的應用程式中執行一些正規化操作。一開始不要試圖強迫 LLM 輸出完全完美的格式。太早在 LLM 端做過多的「輸出塑形」,會讓你難以區分 LLM 執行核心任務(在此例中是資訊提取)的能力,與其結構化輸出的能力。
候選提示詞
接下來我們要想出一些候選提示詞。候選提示詞就是我們認為可能引導語言模型產生期望行為的提示。我們會提出多個候選,因為不太可能一開始就選到最好的提示詞。
為了保持入門程度的說明,我們將手動來構思提示詞。要做得有效,提示工程師在建立提示時應該運用一些基本知識。舉例來說,語氣堅定通常比防禦性語氣來得好;清晰簡潔通常比重複冗長來得好。在建立少樣本提示時,標籤的平均分布很重要,展示完整的標籤集合也很重要等等。在挑選範例時,通常 LLM 最容易答錯的範例效果最好,也有研究顯示範例按由短到長的順序排列時效果往往最佳等等。
需要引用來源!抱歉,我沒有為這些建議附上支持的實驗研究。老實說,是我太懶得去翻我讀過的那些論文(每個觀點往往都有多篇)。如果你選擇不相信我,沒關係,更重要的重點是,關於提示技巧及其有效性的實驗研究確實存在。但我保證這些不是我編造的,儘管有些可能在新一代模型上已經過時。
要逐一介紹這些技巧,本身就值得另寫一篇專文,這也不是本文的目標。本文的目標是展示從頭到尾的高層次流程,並說明從 LLM 中提取價值確實有一套工程方法。
在這個階段,目標是想出一些不錯的零樣本(zero-shot)提示。零樣本提示可以轉換為少樣本,再進一步轉換為思維鏈(chain of thought)。而每一種又都可以再轉為批次提示等等。因此,既然零樣本是必須嘗試的基礎,我們就聚焦於此。
以下是我想出的三個候選提示詞:
Identify the date or day mentioned in the given text and provide it as the output.
Identify the date or day mentioned in the given event description.
Determine the date or day from each input and provide the output accordingly as a single word or date.
這些都是合理的提示詞。交給任何受過教育的人類,每個提示大概都能達到很高的準確率。但語言模型不是人類,所以我們不能自動期待有同等的表現。我之前就展示過,非常合理的提示詞也可能表現得一塌糊塗。因此,下一步就是透過測試與量測來為我們的決策提供依據。
提示詞測試
有了候選提示詞集合與示範集,我們現在就能衡量準確率。據我目前所知,最好的做法是使用像 LangChain 這樣的函式庫,建立一個簡單的 Python 腳本來執行測試。在我的測試中,我通常會遍歷每一筆示範,並套用以下提示模板:
{{prompt}}. Q: {{input}} A:
我總是先測試零樣本。我想先取得一個基準準確率。接著,你可以再測試少樣本,不僅比較不同的候選提示,也比較不同的提示類型。依此類推。
這必須針對每個模型分別進行。同一個提示即使在更強大的模型上,也不保證會有相同或更好的準確率;準確率是有可能下降的2。
提示測試最基本的結果應該會是一張如下的表格。你可能還會有其他維度,例如模型(例如 GPT-3.5 vs GPT-4)。
| Zero-Shot | Few-Shot | ... |
-----------------------------------------
Prompt 1 | 64 | 68 | ... |
-----------------------------------------
Prompt ... | 44 | 52 | ... |
-----------------------------------------
Prompt N | 23 | 22 | ... |
這張表格在 Y 軸上顯示候選提示詞,在 X 軸上顯示使用這些提示詞的提示類型。數值是答對率的百分比。延續行事曆應用的例子,「Prompt 1」作為零樣本提示搭配一筆示範,看起來會像這樣:
Identify the date or day mentioned in the given text and provide it as the output. Q: CorpConf on 11/4 A:
而我們預期答案是「11/4」。少樣本版本則可能長這樣:
Identify the date or day mentioned in the given text and provide it as the output.
Q: Dinner with Alice next Tuesday at Taco Bell. A: next Tuesday
Q: 1:1 with Bob tomorrow at 10 AM. A: tomorrow
Q: CorpConf on 11/4. A:
給有經驗的提示者的說明:這個少樣本範例並沒有加上類似「請模仿下方範例」這樣的指示。實驗研究顯示,這麼做並不能穩定地提升準確率,所以我喜歡先在不加這句話的情況下測試,以節省 token。其次,這個少樣本範例的範例中完全沒有展示「MM/DD」這種提取形式,這是不好的做法。在實際的少樣本設定中,展示所有提取樣式可能很重要(Zhao 等人,2021)。
對於某些類型的問題,例如分類問題,你可以使用混淆矩陣(confusion matrix)(Strobelt 等人,2022)來視覺化其他標籤的機率,並藉此判斷你的標籤集合是否可以進一步優化。
除了準確率之外,你還會想衡量使用的 token 數量、請求次數等。所有這些在選擇最終提示詞時都必須納入考量。
選擇提示詞
最後,你要選出一個候選提示詞整合到應用程式中。這不一定是最準確的那個。這是一場基於所用模型、所需 token 數量與呈現的準確率所做的成本與準確率權衡。
舉例來說,你可能會發現少樣本變體表現最好,但在測試集上只準確了 4%,卻需要多 200% 的 token(對於目前以 API 計費的模型來說,實際上就是成本翻倍)。你可能會為你的業務判斷,與其多花一倍成本,不如少 4% 的準確率更划算。
或者,你可能會決定回頭測試其他提高準確率的方法。例如,你可以嘗試在成本較低的模型上使用自洽性解碼(self-consistency decoding)策略(Wang 等人,2022),看看是否能足夠提升準確率。有時候,在低成本模型上使用更多 token,反而比在高成本模型上使用少量 token 更省錢。舉例來說,現在 GPT-4 的價格約為 GPT-3.5 的 15 倍。這意味著你實際上多了 15 倍的 token 預算來提升 GPT-3.5 提示詞的準確率(當然還需注意速率限制等前提)。
以本文的範例來說,我們可能會選擇表格中 Prompt 1 的零樣本版本,因為它有 64% 的準確率,而且可能使用明顯較少的 token。或許我們會認為 64% 的準確率已經足以至少為行事曆應用程式填好行程範本。對於這個特定問題,我認為我們可以做得比 64% 好得多,但這只是我在本文中使用的數字。
最重要的是,你現在有了數據,可以做出有根據的決策。
信任但要驗證,以及持續改進
由於生成式 AI 本質上是機率性的,你的提示詞很可能仍存在一些問題。即使你在測試集上的準率高達 100%,也可能還有未知的輸入會產生錯誤的輸出。因此,你應該信任但要驗證,並將驗證失敗的案例加入你的示範集,以便開發新的提示詞並提升準確率。
驗證方式高度取決於問題本身。以我們的行事曆應用為例,我們可能會明確詢問使用者:「這個行程正確嗎?」如果他們回答「否」,就將該筆自然語言輸入記錄下來以供人工審核。或者,我們或許可以做得更好,自動追蹤使用者在我們自動提取資訊後手動修改的任何行程3。
舉另一個例子,如果我們的提示是用來產生程式碼(例如正規表示式或程式語言文本),我們至少可以嘗試去解析它。解析不應該帶來安全疑慮4,而且至少能提供最基本的驗證——確認語法是正確的。同樣地,如果這項驗證失敗,我們可以記錄下輸入與輸出,擴充示範集,並開發出更好的提示詞。
驗證也有助於防範對抗性提示(adversarial prompting)。對抗性提示本身就是一個完整的議題,我不會在本文中深入探討。
接下來⋯⋯
這篇部落格文章展示了——依我之見——如何將開發提示詞視為一種工程實踐。它描述了一套有系統的方法來辨識問題、形成解法、驗證這些解法,並透過持續改進來完善它們。
相較於本文的方法,「盲目提示」依賴的是軼事經驗與無所不在的試誤法來得出某種提議的解法,而且往往沒有建立適當、有系統的基礎架構,來隨著時間可靠地迭代提示詞。
我想特別說明,這篇部落格文章非常基礎。文中多處都可以用已經廣為人知的更進階技巧來改進。此外,我也沒有涵蓋對抗性提示等重要主題。舉一個具體例子,在為少樣本提示挑選最佳範例方面,就有更科學的方法,但我希望盡可能讓本文維持在基礎層面。如果你想學習更多進階技巧,Prompt Engineering by Lilian Weng 提供了一個非常棒的總覽。
此外,大家正迅速轉向更高階的 LLM 整合方式:提示鏈(prompt chaining)、代理(agents)等等。有些人認為,像這類未來的創新將使人類的提示變得過時。無論這是否為真,我個人相信從「第一原理」5出發來學習是有價值的,而且我認為學習像這樣的提示技巧,只會提升我運用更高階語言模型技術的能力。我也相信,像這樣基礎的提示仍能讓更高階的概念發揮更好的效能。
註腳
隨機一篇部落格
留言
登入後參與討論