如何用大語言模型構建一個知識問答系統

傳統搜索系統基於關鍵字匹配,在面向:遊戲攻略、技術圖譜、知識庫等業務場景時,缺少對用戶問題理解和答案二次處理能力。
本文探索使用大語言模型(Large Language Model, LLM),通過其對自然語言理解和生成的能力,揣摩用戶意圖,並對原始知識點進行彙總、整合,生成更貼切的答案。
關於基本思路,驗證效果和擴展方向,可以參考正文的介紹。

需求描述

打造 特定領域知識(Domain-specific Knowledge) 問答 系統,具體需求有:

  • 通過自然語言問答的形式,和用戶交互,同時支持中文和英文。
  • 理解用戶不同形式的問題,找到與之匹配的答案。可以對答案進行二次處理,比如將關聯的多個知識點進行去重、彙總等。
  • 支持上下文。有些問題可能比較複雜,或者原始知識不能覆蓋,需要從歷史會話中提取信息。
  • 準確。不要出現似是而非或無意義[1]的回答。

從大語言模型(Large Language Model, LLM)[2]角度而言,上面的需求是在兩階段訓練模式下,面向下游場景進行適配的問題。基礎模型(Foundation Model[3]),面向特定領域不能直接應用,因爲領域知識不在預訓練的數據集中,比如:

  • 較新的內容。同一個知識點不斷變更:修改、刪除、添加。如何反饋當前最新的最全面的知識。比如對於 ChatGpt 而言,訓練數據全部來自於 2021.09 之前。
  • 未公開的、未聯網的內容。

    大語言模型兩階段訓練示意圖

方案分析

基於 LLM 搭建問答系統的解決方案有以下幾種:

  • Fine-Tuning
  • 基於 Prompt Engineering[4],比如 Few-Shot 方式。
  • 與普通搜索結合,使用基礎模型對搜索結果加工。

Fine-Tuning

使用下游特定領域的知識對基礎模型進行微調,改變神經網絡中參數的權重。業界已經不少 chatgpt 的平替方案都支持微調,比如:

  • 清華大學於 2023.03 提出的 ChatGLM[5]支持中英雙語,具有 62 億參數,可以在消費級顯卡上部署,INT4 量化級別下最低只需要 6GB 顯存。
  • Alpaca[6] 是在 Meta 提出的 LLaMA 7B 模型基礎上微調的結果。原生的 Alpaca 對中文的支持並不好,不過已經業界也做了些擴充中文詞表的開源方案[7]

微調方式比較適合特化的任務或風格,但也存在一些問題:

  • 沒有解決事實性問答可靠性的問題。
  • 消耗的資源量雖然相對大模型預訓練減少,但還是不容小覷的。比如 Alpaca 的微調,據作者介紹他們使用 8 個 顯存 80GB A100 ,花費了 3 個小時。如果領域支持頻繁更新,且需要需要較高的實時性,顯然是無法滿足要求的。
  • 需要構建特定領域微調的訓練語料,可以參考Dataset Engineering for LLM finetuning[8]。如果想要獲得較好的結果,高質量訓練數據集的構建需要精心設計,開銷也是不容忽視的。
  • 微調的結果不一定符合預期。在 ChatGLM-6B 微調實踐中可以發現,使用 ADGEN 數據集微調後,模型對“廣告詞生成”任務的確變好,但其他任務的回答均不如原始模型。

基於 Prompt

將特定領域的知識作爲輸入消息提供給模型。類似於短期記憶,容量有限但是清晰。舉個例子給 ChatGPT 發送請求,將特定的知識放在請求中,讓 ChatGPT 對消息中蘊含的知識進行分析,並返回處理結果。

  • 優勢。正確性和精度高。
  • 劣勢。一次可以處理的文本量有限制,如果知識庫較大,無論從可行性還是效率而言都是不合適的。Chatgpt 的限制[9]如下表所示:
Model Maximum text length
gpt-3.5-turbo 4,096(~5 pages)
gpt-4 8,192(~10 pages)
gpt-4-32k 32,768(~40 pages)

與搜索結合

Fine-Tuning 和基於 Prompt 方式均存在缺陷,比如效率低下、數據不夠精確、不能支持大規模數據量等問題。這裏提出第三種方法,嘗試克服這些困難,基本思想是:

  • 使用傳統搜索技術構建基礎知識庫查詢。好處在於:
    • 問答可控性更高一些
    • 無論是數據規模、查詢效率、更新方式都可以滿足常見知識庫應用場景的需要
    • 技術棧成熟,探索風險低
  • 使用 LLM 作爲用戶和搜索系統件溝通的介質,發揮其強大的自然語言處理能力:對用戶請求進行糾錯、提取關鍵點等預處理實現 “理解”;對輸出結果在保證正確性的基礎上二次加工,比如——概括、分析、推理等。

整個方案設計如下圖所示由兩部分組成:

圖片
問答系統框架概要
  • LLM。主要功能有以下幾點:
    • 完成對用戶問題的預處理。糾正語法錯誤,提取關鍵點,通過交互方式引導用戶補充問題足夠多的信息等。
    • 對本地搜索系統的原始答案進行二次處理。比如內容過多,可以進行概括;也可以進行簡單推理。
    • 提供上下文交互的能力。一個常見的例子是 “比較”,比如遊戲中販售道具,倚天劍和屠龍刀。原始知識系統只會提供兩件兵器的基礎屬性,但不會提供各屬性的對比和總體評價。提問的過程可以是:
  1. 玩家諮詢倚天劍的屬性
  2. 玩家諮詢屠龍刀的屬性
  3. 玩家要求比較倚天劍和屠龍刀。這裏 LLM 已經獲取兩件兵器的屬性,使用既有的推理能力進行對比。在同一個會話過程中,可以讓 LLM 從會話歷史中提取信息並分析。
  • 本地搜索系統。解決查詢匹配的問題,在Search: Query Matching via Lexical, Graph, and Embedding Methods[10] 一文中介紹了三種基本方式:
    • Lexical-based search。通過歸一化、拼寫糾錯、擴展、翻譯等方式對查詢請求中的詞進行替換。性能好、可控性強,儘管存在一些語義鴻溝問題,但仍被廣泛的應用在現有的搜索引擎架構中。
    • Graph-based search。以圖的形式描述知識點以及相互間的關係,然後通過圖搜索算法尋找與查詢請求匹配的結果。
    • Embedding-based search。將文字形式的查詢請求,編碼爲數值向量的形式,體現潛在的關係。該文[11]介紹了 Word Embedding 的一些技術實現。

    方案實現

    本文的實現,參考了 OpenAI 提供的樣例,主要理由是 ChatGPT 對外提供了良好的 API 以及中英文支持。但從框架角度而言,不會綁死在 OpenAI 上,每一個具體實現都可以由業務結合自己的需求進行替換。上節提到的方案落地到 ChatGPT 上特化爲:

    圖片
    基於 OpenAI 的一種實現

    說明如下:

    1. 使用 OpenAI 的 Embedding 接口將專業領域知識轉化爲向量,連同原始材料一併保存在 Redis 中。
    2. 用戶提問的搜索處理:
      1. 使用 OpenAI API 對用戶的問題進行 Embedding,獲得向量。
      2. 使用問題向量在 Redis 中搜索,找到與之最匹配的若干記錄。將這些記錄的原始材料返回。
    3. 使用 OpenAI 的 Completion API 對這些原始材料進行加工完善,並將最終結果返回。

    下面對上述過程展開描述。

    領域知識入庫

    該過程的主要目的是:將原始知識庫分拆爲若干知識點,並生成與之對應的字典:

    • key 是知識點 Embedding 之後生成的向量
    • value 是知識點的原始記錄

    該字典的作用是用戶提問時,通過 Embedding 之後的向量比對,實現問題和答案的匹配。具體過程涉及以下幾點,如圖所示:

    圖片
    領域知識入庫流程示意圖
    1. 數據源可能來自於網絡(遊戲已經對外的攻略)、本地文本文件(技術文檔、設計稿)或者數據庫(業務自己維護的 UGC,比如用戶帖子、評論等)。採用合適的方式收集這些數據並整理爲純文本的格式。這裏提供一個 python 庫textract[12],支持從多種類型文件中提取文字信息,普通文本文件自不必說,其它各種常用格式文件也都支持,比如:Microsoft 全家桶 docx, xlsx;圖像 gif, jpg 等;音頻文件 mp3, ogg 等。
    2. 生成分詞器 tokenizer,將文本分成一個個詞元,保證各個詞元擁有相對完整和獨立的語義,以供後續任務比如 Embedding 使用。tiktoken[13]是一種 Byte Pair Encoding(BPE)[14] 分詞器,有多種編碼方法可選,如:r50k_base, p50k_base, cl100k_base 等。面向 OpenAI 的 gpt-4, gpt-3.5-turbo 和 text-embedding-ada-002 模型通常使用 cl100k_base 編碼方法。
    3. 分片。將原始知識庫拆分爲若干個獨立、較短的知識點。每個知識點會作爲問答的最小記錄,與問題進行匹配。在實際使用過程中有以下幾點建議:
    • 原始內容在編寫、組織時最好原子化、正交化。對於樹狀結構的知識點,可以按層級關係表示,最好不要混爲一談。比如倚天劍可能基礎屬性,也有適合的打法,偏向的英雄天賦,那麼三者應該獨立描述,而不要混雜在一起。
    • 可以在原始語料中設計明確的分片標記,簡化處理過程。對於 html、markdown 等類型的文檔而言,天然結構化處理會簡單一些。
    • 基本的分片方式。粒度從細到粗可以使用,標點符號、段落、章節等進行區分。分片粒度過細,知識點會比較零碎影響了相互間的關係;分片粒度過粗,在匹配時可能會攜帶冗餘信息,另外對 Embedding、處理、索引的效率也有影響。
    • 分片要使用 tokenizer,原始文本經過分詞然後再進行 embedding,分片大小需要考慮分詞之後生成的 token 數量。基本目標是:分片不能破壞知識點的完整性,生成的分片對應的 token 數量應該在預設範圍內,不要過小或過大。
  • 詞嵌入(Embedding)。使用 OpenAI API 對每個分片後的每個知識點進行處理,獲得向量化的結果。這裏需要調用 openai.Embedding.create 接口。
  • 存儲。將 Embeddings 生成的向量連同原始分片(知識點),以 kv 形式存儲,便於後續快速匹配索引。專業的解決方案是 vector database[15],但實際上很多傳統的數據庫或存儲中間件也已經提供了支持,比如:
    • RediSearch 提供的 Vector Similarity[16] ,支持使用向量字段和向量相似性查詢。它可以加載、索引和查詢存儲在 Redis 哈希或 JSON 文檔(通過與 RedisJSON 模塊集成)中的向量。Vector Similarity 提供了實時向量索引、實時向量更新/刪除、K-最近鄰(KNN)搜索和範圍過濾等功能。
    • pgvector[17]基於 PostgresQL,提供了類似的向量索引支持。和 Redis 的基本功能差不多,在向量距離計算方面,也提供了:L2、點積和 COSINE 這三種方法。使用 Redis 比較簡單高效,接口和文檔非常豐富,如果沒有特別要求可以直接使用。

    搜索

    搜索的核心流程包含兩步:

    1. 將用戶的問題通過 OpenAI API openai.Embedding.create進行 embedding 得到向量。
    2. 向 redis 發起查詢獲得與之最匹配(距離最近、相似度最高)的若干答案。

    除此外,也可以利用 LLM 對用戶的問題進行預處理,常見的方式有:

    • 簡化概括用戶的問題
    • 利用思維鏈(Chain-Of-Thought, COT)提示的能力,提供範本,讓 LLM 按樣例和用戶進行交互,將問題逐步完善,直到獲取足夠的信息爲止。以遊戲 NBA2K 爲例,球員的打法在不同比賽模式中是不同的,比如王朝 5v5 和街頭 3v3 就不一樣。用戶可能並未意識到這一點,希望 LLM 能夠自動和玩家交互直到獲得:問題和比賽模式兩個信息,再進行處理。細節可以參考知乎的文章ChatGPT 系列教程—提問篇:Prompt 的高級概念[18],本文不再贅述。這裏僅提供一個示例,告知 ChatGPT 使用案例中的樣式和玩家交互:

      你是一個遊戲客服。你需要在和用戶的交流過稱中提取一個問題和比賽模式這兩個信息。請按照下面的方式一步步思考:- 玩家問了一個問題 - 如果問題中沒有包含比賽模式,你需要請他提供比賽模式信息,比如可以問他:你指的是哪一種比賽模式?- 一旦你獲得了比賽模式,那麼說:好的,開始爲您查找。例子:User:請問科比的打法是什麼?Assistant:你指的是哪一種比賽模式?User: 王朝模式 Assistant: 好的,開始爲您查找。

    圖片
    問題預處理的 Prompt 交互示意圖

    對於 ChatGPT 而言,上述這種預設對話行爲來引導用戶的方式稱之爲 ChatCompletion,可以 openai.ChatCompletion.create api,將多輪會話的上下文整合起來,對提問和回答過程提供更加強大、靈活的定製能力。比如:

    • 要求用戶的提問必須提供足夠的指定類型的信息。
    • 爲用戶提供的多輪信息進行進行總結。

    交互式會話中, 提供了三種不同的角色(role):user,system,assistant。

    • user 代表用戶,記錄用戶的提問
    • system 用於向 chatgpt 發出指令,定義其應答行爲
    • assistant 代表 chatgpt 返回的結果

    具體可以參考 ChatGPT API Transition Guide | OpenAI Help Center[19]

    結果整合

    結果整合的主要作用是將本地搜索系統返回的結果進行二次加工,比如發揮 LLM 的:

    • 總結、概括
    • 格式整理
    • 去重、翻譯
    • 從會話歷史中,提取上下文,進行分析處理等能力

    實現的方法還是基於 ChatCompletion ,方式很多,業務完全可以結合場景自由發揮。這裏提供一個在 NBA2K Online2 中實現的方式:

    圖片
    完整問答交互示意圖
    • 首先交互式提問完成後,對會話歷史進行總結,讓 ChatGPT 用一句話概括,須包含問題和賽季信息。
    • 基於該問題,進行 Embedding 並在本地搜索,搜索的結果可能有多條,也可能沒找到。如果找不到則返回特殊語言標記,比如:本地知識庫找不到。
    • 以 ChatCompletion 的方式和 OpenAI 交互,讓 ChatGPT 基於本地搜索結果和歷史會話,進行總結整理。需要以 system 的身份,注入 Prompt 定義 ChatGPT 的應答行爲。具體方法是:
    1. 本地 search result 有效,可能有多條最貼近的知識點,則整理總結作爲最終結果。
    2. 本地 search result 返回特殊標記,比如:本地知識庫找不到,則基於 conversation history 分析;如果還是找不到則提示找不到。
    1. 基於用戶的請求 user_query 觸發本地搜索,獲得答案 search result
    2. 將答案以 system role 的身份插入 conversation history 中,要求對於用戶發出的請求 user_query 使用 search result 回覆
    3. 將包含本地搜索答案的 system 指令和用戶問題依次推入 conversation history
    4. 交給 chatgpt 的 ChatCompletion 處理:
  • 另外一個比較特殊的情況是,本地搜索的結果不滿足要求比如相似度過低,那麼嘗試基於會話歷史讓 chatgpt 進行處理。
  • 應用效果

    上述方案在測試過程中,以 NBA2K Online2 官網的攻略信息[20]爲基礎進行嘗試,基於 OpenAI API,搭建簡單的 CLI 的應用。效果如下所示(爲了簡化過程,一律省略多輪交互問答的過程)。

    基礎能力

    對問題在本地進行搜索,找到多條匹配語料,然後自動整合使用無序列表的形式返回。

    • 問題:關於詹姆斯的打法。

    • 本地 Redis 搜索命中的關聯度最大的若干條答案:

      圖片
      本地知識庫搜索命中的原始材料
    • 整合後的反饋:

      圖片
      使用 LLM 整合概括的結果

    基於會話歷史的問答

    知識庫中僅保存了:奧拉朱旺、科比、詹姆斯三人各自的打法信息,並沒有直接提供三者的比較。所以如果僅通過一個問題要求比較三人的打法差異,是無法在 Redis 中直接匹配命中的。但是可以使用會話歷史,當本地無法命中時,讓 ChatGPT 基於過往的信息自動進行整合,如下所示:

    圖片
    基於會話上下文的交互示意

    總結

    本文針對特定領域知識問答系統的問題,進行方案比較和選型。不難發現:傳統的搜索模式、LLM 的 Fine-Tuning、Prompt Engineer 等方式均存在不同程度的缺陷。經過分析比較後,決定探索 LLM +搜索 的方式進行處理,並在 NBA2K Online2 攻略應用場景進行驗證。該方法:

    • 將本地知識通過傳統搜索框架進行處理,並作爲答案的基礎數據源。這保證了答案的精準和可靠。
    • 同時基於 Prompt Engineering 激發 LLM 的自然語言理解、生成和簡單推理能力,對用戶的問題預處理、對原始答案進行加工。從而提供了更加智能和友好的交互方式。

    在實踐過程中,選擇 ChatGPT 作爲 LLM 的經典實現,使用 RediSearch 提供的 Vector Similarity 作爲問題答案的匹配索引框架。但 LLM+搜索的方式在框架上是非常通用的,不侷限於上述選擇,業務完全可以基於自身場景使用其他基礎模型和搜索方案。另外業務在和 LLM 交互過程中也可以定製更加靈活、智能的提示詞來引導交互過程。本文的細節實踐僅供參考,希望可以起到拋磚引玉的效果。

    參考資料

    [1]似是而非或無意義: https://www.entrepreneur.com/growth-strategies/the-advantages-and-disadvantages-of-chatgpt/450268
    [2]大語言模型(Large Language Model, LLM): https://research.aimultiple.com/large-language-models/
    [3]Foundation Model: https://en.wikipedia.org/wiki/Foundation_models
    [4]Prompt Engineering: https://www.allabtai.com/prompt-engineering-tips-zero-one-and-few-shot-prompting/
    [5]ChatGLM: https://github.com/THUDM/ChatGLM-6B
    [6]Alpaca: https://github.com/tatsu-lab/stanford_alpaca
    [7]擴充中文詞表的開源方案: https://link.juejin.cn/?target=https://arxiv.org/pdf/2304.08177v1.pdf
    [8]Dataset Engineering for LLM finetuning: https://www.flowrite.com/blog/dataset-engineering-llm-finetuning
    [9]Chatgpt 的限制: https://github.com/openai/openai-cookbook/blob/main/examples/Question_answering_using_embeddings.ipynb?spm=wolai.workspace.0.0.357e11a2XSQ9wO&file=Question_answering_using_embeddings.ipynb
    [10]Search: Query Matching via Lexical, Graph, and Embedding Methods: https://eugeneyan.com/writing/search-query-matching/
    [11]該文: https://medium.com/intelligentmachines/word-embedding-and-one-hot-encoding-ad17b4bbe111
    [12]textract: https://textract.readthedocs.io/en/stable/
    [13]tiktoken: https://github.com/openai/tiktoken
    [14]Byte Pair Encoding(BPE): https://en.wikipedia.org/wiki/Byte_pair_encoding
    [15]vector database: https://learn.microsoft.com/en-us/semantic-kernel/concepts-ai/vectordb
    [16]Vector Similarity: https://redis.io/docs/stack/search/reference/vectors/
    [17]pgvector: https://github.com/pgvector/pgvector
    [18]ChatGPT 系列教程—提問篇:Prompt 的高級概念: https://zhuanlan.zhihu.com/p/623395924
    [19]ChatGPT API Transition Guide | OpenAI Help Center: https://www.wolai.com/9fBmz1E4WZWbHJZQYKHTji.md
    [20]攻略信息: https://nba2k2.qq.com/act/a20200520apph5/app/index.html#/ol2/news/6366

    作者:simon

    發表評論
    所有評論
    還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
    相關文章