使用繁體中文評測 RAG 的 Chunking 切塊策略

延續之前做 EmbeddingReranker 評測,這次來研究 RAG 系統中的 Chunking 切塊環節。由於 embedding 和 LLM 模型的長度限制,我們必須將所有文本資料,拆成小塊後再轉成向量放進向量資料庫。

七月份 Chroma 做的這篇非常棒 Evaluating Chunking Strategies for Retrieval,評測了幾個 Chunking 策略,並且提出兩種新的切塊策略,想當然他是用英文文本做的。

因為他有公開 Github Repo 程式碼可以重現他的實驗(非常棒,是真的可以順利執行的),因此我就改成用繁體中文文本試試,排列組合出評測 38 種不同 chunking 的方式。

評估實驗方式

首先根據文本來合成測試問題,他是從你給定的文本中隨機挑 4000 tokens,產生問題和至多五個 references 參考來源。不需要產生答案,因為這個評估是做檢索,不是問答,不涉及答案的生成。然後他還會刪除類似的、不好的問題。因此需要多跑幾次才能湊滿足夠的題目。

用不同 chunking 策略切塊後,用 OpenAI text-embedding-3-large 檢索出前5筆/10筆/20筆,計算檢索後的 Recall 和 Precision 指標,而且這是從 tokens 層面來計算的,超仔細。

  • Recall 召回率: 有多少相關的 tokens 被檢索出來
  • Precision 準確率: 檢索出來的 tokens 中,有多少是真正相關的
  • PrecisionΩ 最大準確率: 假設 Recall 是滿分,最大的準確率是多少
  • IoU: 這是作者發明的綜合性指標 (跟 F1 Score 有點類似),高分代表不但能夠準確檢索相關內容,同時最小化不相關或冗餘的內容。低分代表過多無關內容,或是遺漏了相關資訊。

Chunking 策略說明

  • FixedTokenChunker: 固定照 tokens 硬切 。不過他的這個實作在中文不能用,因為中文不是剛好一個 token,他硬切 tokens 的結果造成錯誤,根本不能跑。
  • RecursiveTokenChunker: 就是最多人用的 RecursiveCharacterTextSplitter 切法,我有稍改一下分隔符號,加上中文的逗號句號等等。這個 chunking 方式除了指定 chunk 大小,還可以指定要前後重複多少 tokens,以改進上下文語意。總共測試了 2000, 1500, 1000, 800, 600, 400, 200 切塊大小以及是否重疊。
  • KamradtModifiedChunker: 由 Kamradt 提出的比較每段 embedding 差異來聰明切塊的方式,我測試了 2000, 1500, 1000, 800, 600, 400, 200 切塊大小。
  • ClusterSemanticChunker: 根據 embedding 來分群,一樣測試了 2000, 1500, 1000, 800, 600, 400, 200 切塊大小。
  • LLMSemanticChunker: 用 LLM 來幫我們切塊,標記哪段跟哪段要拆開。他下的 prompt 傳送門

我使用的中文測試文本

每篇大約 1~2 萬 tokens (cl100k_base),乾淨的純文字有段落分行,最後我合成出100題

合成 Prompt 修改

我有修改他的 prompt,要求合成出來的問題,也必須是繁體中文: This question must be in Traditional Chinese (as used in Taiwan).
另外為了節省成本,模型換成 gpt-4o-2024-08-06,他本來用 gpt-4-turbo。

評估結果

一些結論想法

我會優先看 Recall 指標,畢竟正確的參考資料都沒檢索出來的話,後面 LLM 要生成也就 gg 了。

  • 在 top-k 是 5 的情況下
    • RecursiveTokenChunker 的 chunk 切太大 1500 以上,或太小 600 以下看來不行
    • RecursiveTokenChunker_800_400 這個配方看來是 OK 的
  • 若在相同總 chunk tokens 數的條件下,如果切小塊但是 top-k 取高的話,例如 RecursiveTokenChunker_400_200 用 top-k 10,則 Recall 可以更高! 但也不能切太小塊。
  • 所以 chunking 的大小,也跟到底要檢索多少筆的 top-k 有關,總 chunk tokens 能用多少跟你 LLM context window 限制有關
  • 有用 chunk overlap 標準差會比較小,結果會比較穩定,代價是 Precision 比較差一點,雜訊變多,比較浪費成本,對模型生成也是,例如可能檢索出 4 個 chunks,但裡面快一半都是重複的文字…. XD
  • (在 top-k 5 的情況下) KamradtModifiedChunker 跟 ClusterSemanticChunker 做出來沒想像中好,原評測用英文是高分的,但這裡我做中文比較差。個人猜測是這個依賴於 embedding 模型能力,而中文的 embedding 能力畢竟還是差了一點
  • LLMSemanticChunker 還不錯,Precision 比 RecursiveTokenChunker_800_400 高,但原評測用這招的 Recall 分數是第一,換成中文後性能也是有所下降。如果文件本來就有用 LLM 清理資料,我會考慮順道試試這招。

在 Recall 差不多的情況下,可以選 Precision 高的,比較有效率。畢竟後面 LLM 生成時,在一樣的 context window 限制下: 單一 chunk 切越大,最後能取的 chunks 總數就比較少,例如 top-k 5,因為一塊就這麼大。切越小,最後取的 chunks 總數就比較多,例如 top-k 10,比較有彈性空間。

至於是否要犧牲 Recall 換 Precision,我認為跟你模型強不強、context window 可以塞多少 chunks 有關:

  • Precision 跟切塊大小是非常相關,切越小 Precision 越準,切越大 Precision 越差
  • 如果是能力強的大模型,Precision 低一點,有雜訊也沒關係
  • 如果是能力弱的小模型,容易被雜訊影響,Precision 太低生成恐會不好,因此切塊需要小

最後,作者的這個切塊評估框架蠻不錯的,如果有實際專案用的文本推薦可以自己跑一跑,看看適合你的最佳 Chunking 策略是什麼。

此評測 Facebook 貼文討論 傳送門 ↗️ (歡迎按讚、追蹤、分享)

參與討論

3 則留言

  1. 想請問一下 ihower 大大,評估結果的長條圖是另外用 matplotlib 繪製的嗎 ?

發佈留言

發表迴響