大語言模型(LLM)作為一種根據統計的模型,其核心能力在於預測給定文字序列的下一個詞或字元。提示工程的目標是精心設計輸入的提示,引導LLM生成預期的結果。這不僅涉及單一提示的建構,更涵蓋整個根據LLM的應用程式設計,包括提示的構建、答案的解讀以及使用者、應用程式和LLM之間的迭代溝通。LLM的複雜度體現在多個層面,從基本的文字修改和擴充到狀態化的互動、工具的使用,最終發展到賦予LLM代理能力,使其能夠自主決策並完成複雜任務。理解LLM的運作原理,包括Transformer架構、注意力機制和tokenization過程,對於有效運用提示工程至關重要。同時,也需要認識到LLM的侷限性,例如過度擬合、幻覺和真實性偏見,才能更好地應用LLM解決實際問題。

提示工程導論

現在,我們即將踏上提示工程的旅程。大語言模型(LLMs)的核心能力是完成文字。輸入模型的內容稱為提示(prompt),它是一份檔案或一段文字,我們期望模型能夠將其補充完整。簡單來說,提示工程就是精心設計提示,使其生成的結果能夠包含解決當前問題所需的資訊。

在本文中,我們將提供更廣泛的提示工程視野,不僅僅侷限於單一提示,而是涵蓋整個根據LLM的應用程式。在此過程中,提示的構建和答案的解讀都是透過程式設計實作的。為了開發高品質的軟體和使用者經驗,提示工程師必須在使用者、應用程式和LLM之間建立迭代溝通模式。使用者將問題傳達給應用程式,應用程式構建一個虛擬檔案傳送給LLM,LLM完成檔案,最後,應用程式解析完成的內容並將結果傳回給使用者,或代表使用者執行某項操作。提示工程的科學與藝術在於確保這種溝通結構能夠在非常不同的領域之間進行最佳轉換,即使用者的問題空間和LLM的檔案空間。

提示工程的多層次複雜度

提示工程有多個複雜度層次。最基本的形式只使用非常薄的應用層。例如,當你與ChatGPT互動時,你幾乎是在直接製作提示;應用程式只是用特殊的ChatML markdown包裝了對話執行緒。(你將在第3章瞭解更多相關內容。)同樣,當GitHub Copilot首次為程式碼補全建立時,它所做的只是將當前檔案傳遞給模型進行補全。

在下一個複雜度層次中,提示工程涉及修改和擴充使用者輸入模型的內容。例如,LLMs處理文字,因此技術支援熱線可以將使用者的語音轉錄成文字,並在傳送給LLM的提示中使用。此外,來自先前幫助記錄或相關支援檔案的相關內容可以包含在提示中。作為一個真實世界的例子,當GitHub Copilot的程式碼補全功能發展時,我們意識到如果我們將使用者鄰近標籤中的相關程式碼片段納入其中,補全品質會顯著提高。這是有道理的,對吧?使用者開啟這些標籤是因為他們正在參考那裡的資訊,因此模型也可以從這些資訊中受益。另一個例子是新的Bing聊天式搜尋體驗。在這種情況下,傳統搜尋結果的內容被拉入提示中。這使得助手能夠勝任地討論它在訓練資料中從未見過的資訊(例如,因為它指的是模型訓練後發生的事件)。更重要的是,這種方法有助於減少幻覺(hallucinations),我們將在下一章中多次重溫這個話題。

狀態化的互動與工具的使用

在這個複雜度層次上的另一個方面是,當與LLM的互動變得有狀態(stateful)時,也就是說它們保持了上下文和先前互動中的資訊。聊天應用程式是這裡的典型例子。隨著使用者每次新的交流,應用程式必須回憶先前交流中發生的事情,並生成一個忠實代表互動的提示。隨著對話或歷史記錄變長,您必須小心不要使提示過滿或包含可能分散模型注意力的無關內容。您可以選擇丟棄最早的交流或先前交流中較不相關的內容,甚至可以使用摘要來壓縮內容。

另一個方面涉及為根據LLM的應用程式提供工具,使LLM能夠透過發出API請求來讀取資訊或甚至建立或修改網際網路上可用的資產。例如,一個根據LLM的電子郵件應用程式可能會收到來自使用者的輸入:「傳送邀請給Diane參加5月5日的會議。」這個應用程式將使用一個工具來識別使用者聯絡人列表中的Diane,然後使用日曆API來查詢她的可用性,然後最終傳送電子郵件邀請。隨著這些模型變得越來越便宜和強大,想像一下今天我們已經擁有的API所帶來的可能性!這裡的提示工程至關重要。模型如何知道使用哪個工具?它將如何正確使用工具?您的應用程式如何正確地與模型分享工具執行的資訊?當工具使用導致某種錯誤狀態時,我們該怎麼辦?我們將在第8章討論所有這些問題。

提供代理能力的最終層次

我們在本文中涵蓋的最終複雜度層次是如何為LLM應用程式提供代理能力(agency)——即能夠自行決定如何實作使用者提供的廣泛目標。這顯然是我們使用LLMs所能實作的前沿,但研究和實踐探索正在進行中。您已經可以下載AutoGPT並為其提供目標,它將開始一個多步驟過程來收集實作目標所需的資訊。它總是有效嗎?不。實際上,除非目標相當受限,否則它往往會在任務中失敗而不是成功。但是,給予LLM應用程式某種形式的代理能力和自主權仍然是邁向令人興奮的未來可能性的一步重要舉措。你將在第8章和第9章閱讀我們對此的看法。

內容解密:

本章節介紹了提示工程的基本概念和多層次複雜度。首先,我們瞭解了提示工程的核心,即設計能夠生成所需資訊的提示。接著,我們探討了提示工程的不同層次,包括基本形式、修改和擴充使用者輸入、狀態化的互動以及提供工具和代理能力。每個層次都對應著不同的技術和挑戰,需要提示工程師根據具體情況進行設計和最佳化。

程式碼範例

# 定義一個簡單的聊天機器人函式
def simple_chatbot(user_input):
    # 模擬處理使用者輸入
    response = f"您說的是:{user_input}"
    return response

# 測試聊天機器人
user_input = "你好"
print(simple_chatbot(user_input))

內容解密:

這段程式碼展示了一個簡單的聊天機器人函式simple_chatbot,它接收使用者輸入並傳回一個簡單的回應。首先,函式模擬處理使用者輸入,將輸入內容原樣傳回並加上字首「您說的是:」。這種簡單的實作可以作為更複雜聊天機器人的基礎,透過擴充此函式,可以加入更豐富的功能,如自然語言處理或整合外部API等。

理解大語言模型(LLM)

要成為能夠巧妙運用提示(prompt)來解鎖大語言模型(LLM)知識與處理能力的人,首先需要了解LLM如何處理資訊——它們的運作方式。

在本章中,我們將逐步探討這個問題。首先,您將從外部視角瞭解LLM,將其視為訓練後的文字模擬器。您將學習LLM如何將文字分割成稱為「token」的適當大小的片段,並瞭解如果無法輕易完成這種分割可能帶來的後果。

接著,您將學習token序列如何逐步生成,並瞭解選擇下一個token的不同方法。最後,您將深入瞭解LLM的內部工作原理,將其理解為透過一種稱為「注意力機制(attention)」的問答遊戲進行溝通的「微型腦(minibrains)」集合,並瞭解這對提示順序的影響。

大語言模型(LLM)是什麼?

在最基本的層面上,LLM是一種服務,它接收一個字串並傳回另一個字串:文字輸入,文字輸出。輸入稱為提示(prompt),輸出稱為完成(completion)或回應(response)。

圖示:LLM處理提示「One, Two,」並生成完成「Buckle My Shoe」

此圖示展示了一個LLM如何接收提示並生成相應的完成。

剛出生的未經訓練的LLM,其生成的完成看起來像是Unicode符號的隨機組合,與提示沒有明顯的關聯。它需要經過訓練才能變得有用。訓練好的LLM不僅能對字串做出回應,還能以語言回應語言。

訓練需要技巧、計算資源和時間,這遠遠超出了大多數專案團隊的能力範圍。因此,大多數LLM應用程式使用的是已經訓練好的通用模型(稱為基礎模型),這些模型可能在之後進行了微調。

什麼是微調?

訓練LLM需要大量的資料和計算資源,但許多基本的經驗教訓,如英語語法規則,在不同的訓練集之間並沒有太大的差異。因此,通常不會從零開始訓練LLM,而是從另一個已經訓練好的LLM的副本開始,可能是在不同的檔案上訓練的模型。

例如,早期版本的OpenAI Codex(用於生成原始碼的LLM,為GitHub Copilot開發)是現有模型(GPT-3,一種自然語言LLM)的副本,這些副本透過GitHub上發布的大量原始碼進行了微調。

如果您有一個在資料集A上訓練的模型,並在資料集B上進行微調,那麼您的提示通常應該像是在B上直接訓練的一樣撰寫。

LLM的訓練

LLM是使用一大組檔案(同樣是字串)進行訓練的,這些檔案被稱為訓練集。訓練集中的檔案型別取決於LLM的目的。訓練集通常是不同型別訓練輸入的混合,如書籍、文章、Reddit上的對話以及GitHub上的程式碼。

模型的任務是從訓練集中學習如何生成看起來與訓練集相似的輸出。具體來說,當模型接收到一個來自其訓練集的檔案開頭作為提示時,生成的完成應該是原始檔案中最有可能的接續文字。換句話說,模型是在模仿。

圖示:「The Pile」的組成,一個流行的開源訓練集,包含事實性散文、虛構散文、對話和其他網路內容的混合

此圖示展示了一個典型的開源訓練集的多樣性。

那麼,LLM與一個包含訓練資料的大型搜尋引擎索引有什麼不同呢?畢竟,給定一個檔案的開頭,搜尋引擎可以100%準確地找到該檔案的完成。然而,僅僅讓搜尋引擎重複訓練集並不是目標;LLM不應該只是死記硬背訓練集,而是應該將其遇到的模式(尤其是邏輯和推理模式)應用於完成任何提示,而不僅僅是來自訓練集的提示。單純的死記硬背被視為一種缺陷。LLM的內部架構(鼓勵它從具體例子中抽象出規律)和訓練過程(試圖提供多樣、非重複的資料,並在未見過的資料上衡量成功)都旨在防止這種缺陷。

LLM的核心:Transformer架構

在深入瞭解LLM的工作原理時,我們不可避免地會提到Transformer架構。這種架構是目前大多數先進LLM的基礎。Transformer是一種特殊的神經網路架構,它在處理序列資料(如文字)方面表現出色。

注意力機制:Transformer的核心

Transformer的核心創新是引入了「注意力機制」。這種機制允許模型在處理輸入序列的不同部分時,能夠動態地調整其關注點。這種能力使得Transformer能夠更有效地捕捉長距離依賴關係,從而在諸如機器翻譯、文字生成等任務中表現出色。

程式碼範例:簡化的注意力機制實作
def simplified_attention(query, key, value):
    """
    簡化的注意力機制計算。
    
    :param query: 查詢向量
    :param key: 鍵向量
    :param value: 值向量
    :return: 經過注意力機制處理後的輸出
    """
    # 計算注意力分數
    scores = dot_product(query, key)
    # 套用softmax函式以獲得機率分佈
    attention_weights = softmax(scores)
    # 計算加權和
    output = weighted_sum(attention_weights, value)
    return output

#### 內容解密:
1. **查詢query)、key和值value向量**在注意力機制中輸入序列被轉換成這三種向量分別代表了查詢的內容被查詢的內容以及查詢所對應的值
2. **注意力分數的計算**透過查詢向量和鍵向量的點積來計算注意力分數這反映了不同位置之間的相關性
3. **Softmax函式**用於將注意力分數轉換為機率分佈從而確定每個位置的重要性
4. **加權和**根據注意力權重對值向量進行加權求和得到最終的輸出

透過這種方式注意力機制能夠有效地捕捉序列中不同元素之間的複雜關係為LLM提供了強大的文字理解和生成能力

大語言模型(LLM)的運作原理與限制

大語言模型(LLM)是一種根據統計的模型,能夠預測給定文字序列的下一個詞或字元。LLM 的訓練資料通常是大量的文字資料,例如書籍、文章、網頁等。在訓練過程中,LLM 學習如何根據前面的文字預測下一個詞或字元。

過度擬合(Overfitting)的問題

在訓練過程中,LLM 可能會出現過度擬合的問題,即模型過度學習訓練資料中的特定模式或文字片段,而不是真正理解文字的含義。這種情況下,LLM 可能會在面對新的、未曾見過的文字時表現不佳。

LLM 的輸出預測

要了解 LLM 的輸出預測,可以透過思考一個問題:如果從訓練資料中隨機挑選一篇檔案,我們只知道它以某個特定的提示(prompt)開頭,那麼統計上最有可能的續寫是什麼?這就是 LLM 輸出的預測。

檔案續寫的例子

假設有以下文字: “Yesterday, my TV stopped working. Now, I can’t turn it on at”

對於這樣的文字,統計上最有可能的續寫是什麼?可能的選項包括:

  1. y2ior3w
  2. Thursday.
  3. all.

雖然前兩個選項不是完全不可能,但第三個選項 “all.” 是最有可能的續寫。實際上,大多數 LLM 都會選擇這個續寫。

訓練資料對 LLM 輸出的影響

LLM 的輸出預測取決於其訓練資料。如果 LLM 是在小說、雜誌和報紙等敘事性文字上訓練的,那麼對於上述文字,續寫 “This is why I chose to settle down with a book tonight.” 可能比其他選項更為合理。

然而,如果訓練資料中包含電子郵件和對話記錄,那麼其他續寫,如 “Shall we watch the game at your place instead?” 也可能變得合理。實際上,由 OpenAI 的 text-davinci-003 模型生成的續寫是 “First, try unplugging the TV from the wall and plugging it back in.",這反映了其訓練資料中的客戶服務對話模式。

人類思維與 LLM 處理的區別

LLM 選擇最有可能的續寫,這與人類閱讀文字時的思維過程有所不同。人類在寫作時,會根據上下文、知識和經驗進行創造性的思考,而不僅僅是根據統計規律預測下一個詞或字元。

LLM 的侷限性

LLM 無法像人類一樣進行搜尋、編輯或表達不確定性。LLM 的輸出只是根據統計規律的猜測,即使在不確定的情況下,也不會表達懷疑或提供免責宣告。

LLM 的應用限制

雖然 LLM 可以模擬出看似合理的文字,但其輸出可能並不總是正確或可靠。例如,當 LLM 生成一個網址或社會保險號碼時,它可能會產生看似合理的字串,但這些字串不一定是真實的。

大語言模型的幻覺與真實性偏見

大語言模型(LLMs)被訓練成「訓練資料模仿機器」,這導致了一個不幸的後果:產生幻覺。幻覺是指模型自信地產生的事實上錯誤但看似合理的資訊片段。在使用大語言模型時,無論是臨時還是應用於程式中,幻覺都是一個常見的問題。

瞭解幻覺的成因

由於幻覺與其他由模型生成的內容在模型視角下並無不同,因此像「不要編造內容」這樣的提示指令效果有限。相反,常見的做法是讓模型提供一些可以被驗證的背景資訊,例如對其推理過程的解釋、可獨立執行的計算、來源連結,或是可以被搜尋的關鍵字和細節。

利用真實性偏見

大語言模型的真實性偏見是指當提示中參照不存在的事物時,模型通常會假設其存在。這種特性既可以被利用,也可以帶來危險。如果希望模型評估假設或反事實情況,無需明確要求模型「假裝」某種情況,只需直接以假設情況為前提開始即可。

程式設計中的真實性偏見風險

然而,大語言模型的真實性偏見對於程式應用來說也是危險的。在程式化提示建立過程中,很容易引入反事實或無意義的元素。一個人類讀者可能會讀到提示後,揚起眉毛表示懷疑,但大語言模型沒有這種能力。它會盡力假裝提示是真實的,並且不太可能糾正錯誤。因此,提供不需要糾正的提示是使用者的責任。

大語言模型如何「看待」世界

從字元到詞彙的認知過程

大語言模型處理和生成字串,但它們並非以人類閱讀字元序列的方式來「看待」字串。對於大語言模型來說,字串首先被分解為一系列稱為「tokens」的多字母區塊,通常長度為三到四個字元,但也有更長的tokens用於常見的單詞或字母序列。

Tokenizer的作用

當向模型傳送文字時,它首先透過一個tokenizer被轉換成一系列tokens。只有這樣,它才被傳遞給大語言模型。然後,大語言模型生成一系列tokens(在內部表示為數字),這些tokens再被轉換迴文字後傳回給使用者。

import tiktoken

# 初始化tokenizer
encoding = tiktoken.get_encoding("cl100k_base")

# 示例文字
text = "大語言模型如何看待世界?"

# 將文字轉換為tokens
tokens = encoding.encode(text)

print("Tokens:", tokens)

# 將tokens轉換迴文字
decoded_text = encoding.decode(tokens)

print("解碼後的文字:", decoded_text)

內容解密:

  1. 匯入tiktoken函式庫:這是一個用於對文字進行token化的工具。
  2. 初始化cl100k_base編碼的tokenizer:這是OpenAI模型常用的編碼方式。
  3. encoding.encode(text):將輸入文字轉換為一系列tokens,這些tokens是模型的內部表示形式。
  4. encoding.decode(tokens):將tokens轉換回原始文字,展示了tokenizer的可逆性。
  5. 列印結果:顯示了原始文字被token化後的結果,以及將tokens解碼迴文字的過程。

這種tokenization過程對於理解大語言模型如何處理輸入文字至關重要。它們不是直接處理字元或單詞,而是透過將文字分解為有意義的tokens來進行理解和生成內容。

對比人類閱讀與大語言模型的閱讀方式

就像人類閱讀時會將字元分組為單詞一樣,大語言模型透過tokenization將文字分解為tokens。這種處理方式使得模型能夠更有效地理解和生成自然語言。然而,這也意味著模型的「閱讀」方式與人類不同,它依賴於預先定義的token集合。

LLM 與人類對文字理解的差異

LLM(大語言模型)與人類在文字理解上存在著根本性的差異。雖然兩者都能處理文字,但其底層機制和理解方式卻大不相同。

差異1:LLM 使用確定性分詞器

人類在閱讀文字時,會根據上下文和經驗將字母序列轉換為單詞,這個過程帶有一定的模糊性。相比之下,LLM 使用確定性分詞器,將文字分割為確定的 token 序列。這使得 LLM 在處理拼寫錯誤時能夠輕易識別,因為拼寫錯誤的單詞會被分解為不同的 token 序列。例如,在 OpenAI 的 GPT 分詞器中,「ghost」是一個單一的 token,而拼寫錯誤的「gohst」則被分解為「g-oh-st」三個 token。

內容解密:

  • LLM 使用確定性分詞器,能夠精確地將文字分解為 token 序列。
  • 拼寫錯誤的單詞會被分解為不同的 token 序列,使得 LLM 容易識別拼寫錯誤。
  • 儘管 LLM 對拼寫錯誤具有一定的容忍度,但仍可能在某些情況下出現理解偏差。

差異2:LLM 無法逐字檢查

人類可以逐字檢查文字,仔細審視每個字母。然而,LLM 只能使用其內建的分詞器,無法逐字檢查。這使得 LLM 在處理需要分解或重組 token 的任務時會遇到困難。例如,在圖 2-6 中,ChatGPT 在反轉單詞字母順序的任務中表現不佳,因為這需要分解和重組 token。

此圖示展示了 LLM 處理反轉單詞字母順序任務的流程。可以看到,分詞器的處理結果直接影響了最終的輸出結果。

內容解密:

  • LLM 無法逐字檢查文字,限制了其在某些任務上的表現。
  • 需要分解或重組 token 的任務對 LLM 而言較為困難。
  • 在應用中,應盡量避免讓 LLM 處理涉及 subtoken 級別的任務,若無法避免,應考慮在前處理或後處理中進行相關操作。

差異3:LLM 對文字的理解不同

人類對文字的理解是根據直覺和視覺經驗的。例如,人類知道哪些字母是圓的,哪些是方的,並且能夠忽略重音符號。然而,LLM 對文字的理解則是根據 token 序列的。例如,在處理大小寫轉換任務時,LLM 可能會出現錯誤,因為大小寫字母對應不同的 token。

@startuml
skinparam backgroundColor #FEFEFE
skinparam componentStyle rectangle

title 提示工程與 LLM 運作架構

package "提示工程複雜度層次" {
    component [基礎層:直接提示\n(ChatGPT 對話)] as level1
    component [擴充層:輸入修改\n(語音轉文字/RAG)] as level2
    component [狀態層:有狀態互動\n(對話歷史維護)] as level3
    component [工具層:API 呼叫\n(外部服務整合)] as level4
    component [代理層:自主決策\n(AutoGPT)] as level5
}

package "LLM 內部架構" {
    component [Tokenization\n文字分割] as tokenize
    component [Transformer\n注意力機制] as transformer
    component [Token 序列生成] as generate
}

package "LLM 限制與挑戰" {
    component [幻覺 (Hallucination)] as hallucination
    component [真實性偏見] as bias
    component [上下文長度限制] as context_limit
}

level1 --> level2
level2 --> level3
level3 --> level4
level4 --> level5

tokenize --> transformer
transformer --> generate

generate --> hallucination
generate --> bias
generate --> context_limit

note right of level2
  整合搜尋結果
  減少幻覺問題
end note

note right of transformer
  Q-K-V 注意力
  捕捉長距離依賴
end note

@enduml

此圖示展示了 LLM 處理大小寫轉換任務的流程。可以看到,不同的大小寫字母對應不同的 token 序列,這使得 LLM 在處理這類別任務時容易出現錯誤。

內容解密:

  • LLM 對文字的理解根據 token 序列,而非視覺經驗。
  • 大小寫字母對應不同的 token,這使得 LLM 在處理大小寫轉換任務時容易出現錯誤。
  • 在應用中,應注意 LLM 對文字的理解特點,避免出現不必要的錯誤。