將大文檔分割成較小的分塊是一項關鍵而復雜的任務,對RAG系統的性能有著重大的影響。一般地,RAG系統旨在通過將基于檢索的方法和基于生成的方法相結合,提高產出的質量和相關性。有多種框架提供了文檔分塊方法,每種方法都有自己的優點和典型用例。或許,利用主題感知的句子嵌入來識別文檔中的主題變更,確保每個塊封裝一個主題會是一種不錯的選擇。
1.回顧RAG
RAG系統是一個復雜的機器學習模型,它融合了基于檢索的技術和生成式AI。RAG 系統的主要目標是通過合并從數據集中檢索的信息來提高生成內容的質量和相關性。回顧一下 RAG 系統的工作原理:
- 檢索階段: 系統首先根據輸入查詢檢索相關文檔或信息。這個階段依賴于搜索算法和索引方法來快速識別大量集合中最相關的數據。
- 生成階段: 一旦檢索到相關文檔,就會使用一個通常是基于transformer的大語言模型,如 GPT-4來創建一個連貫的、與上下文相適應的響應。此模型使用檢索到的信息來確保生成的內容是準確的,而且信息豐富。
RAG 系統的混合特性使它們對于知識密集型任務特別有效,在這些任務中,檢索和生成的結合極大地提高了總體性能。
2. 常見的文本分塊技術
文本分塊是許多自然語言處理任務的基礎步驟,可以采用多種技術來確保分塊方式保留了語義和上下文。根據任務的具體要求,可以以多種方式來實現文本分塊,下面是針對不同需求分塊方法:
2.1 按字符分塊
此方法將文本分解為單個字符。它適用于需要細粒度文本分析的任務,例如字符級語言模型或某些類型的文本預處理。
2.2 按Token分塊
將文本分割成token,是自然語言處理中的一種標準方法。基于令牌的組塊對于文本分類、語言建模和其他依賴于token化輸入的 NLP 應用程序等任務來說是必不可少的。
2.3 按段落分塊
按段落分段整理文本有助于維護文檔的整體結構和流程。此方法適用于需要較大上下文的任務,如文檔摘要或內容提取。
2.4 遞歸分塊
這涉及到重復地將數據分解成更小的塊,通常用于分層數據結構。遞歸組塊有利于需要多級分析的任務,如主題建模或層次聚類。
2.5 語義分塊
根據意義而非結構元素對文本進行分組對于需要理解數據上下文的任務至關重要。語義塊利用諸如句子嵌入等技術來確保每個塊代表一個連貫的主題或想法。
2.6 代理分塊
這種方法的重點是在識別和分組文本的基礎上增加參與的代理,如人或組織。它在信息抽取和實體識別任務中非常有用,因為理解不同實體之間的角色和關系非常重要。
3.基于Langchain的文本分塊技術——5行代碼
Langchain 框架中提供了很多可以開箱即用的技術,常見的文本分塊技術如下:
- 遞歸字符分塊
- token分塊
- 句子分塊
- 正則分塊
- Markdown分塊
3.1 遞歸字符文本分塊
此方法基于字符數來遞歸地分割文本。每個塊都保持在指定的長度以下,這對于具有自然段落或句子間斷的文檔特別有用,確保了塊的可管理性和易于處理性,而不會丟失文檔的固有結構。
Langchain中的遞歸字符文本分割器方法根據字符數將文本分割成塊,以確保每個塊低于指定的長度。這種方法有助于保持文檔中段落或句子的自然斷開。
from langchain.text_splitter import RecursiveCharacterTextSplitter
text = " long document text here..."
# 初始化 RecursiveCharacterTextSplitter,塊大小1k字符以及50個跨文本字符
charSplitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=50)
# 分塊
chunks = charSplitter.split_text(text)
# 打印輸出
for chunk in chunks:
print(chunk)
3.2 Token文本分塊
這種技術使用token劃分文檔,token可以是單詞或詞元。在處理具有token限制的大語言模型時,它確保了每個塊都符合模型的約束。在自然語言處理任務中,通常使用基于token分塊來保持文本的完整性,同時遵守模型的限制。
from langchain.text_splitter import TokenSplitter
text = " long document text ..."
# 初始化TokenSplitter,最大token限制為 512
splitter = TokenSplitter(max_tokens=512)
chunks = splitter.split_text(text)
for chunk in chunks:
print(chunk)
3.3 句子分塊
通過在句子邊界上分割文本,保持了文本的上下文完整性。句子通常代表完整的思想,這使得這種方法非常適合那些對內容有連貫理解的場景。
from langchain.text_splitter import SentenceSplitter
text = "long document text ..."
# 初始化SentenceSplitter ,每個塊最多5個句子
splitter = SentenceSplitter(max_length=5)
chunks = splitter.split_text(text)
for chunk in chunks:
print(chunk)
3.4 正則分塊
此方法使用正則表達式來自定義拆分點。它為各種用例提供了最高的靈活性,允許用戶根據特定于他們的用例模式來拆分文檔。例如,可以在特定關鍵字或標點符號的每個實例上文檔拆分。
from langchain.text_splitter import RegexSplitter
# Example long document text
text = "Your long document text goes here..."
# 用一個模式初始化 RegexSplitter,以雙換行符分割文本
splitter = RegexSplitter(pattern=r'\n\n+')
chunks = splitter.split_text(text)
for chunk in chunks:
print(chunk)
3.5 Markdown 的文檔分塊
該方法專為 markdown文檔定制,根據特定元素(如標題、列表和代碼塊)分割文本,保留了標記文檔的結構和格式,使其適合于技術文檔和內容管理。
from langchain.text_splitter import MarkdownSplitter
text = "long markdown document..."
splitter = MarkdownSplitter()
chunks = splitter.split_text(text)
for chunk in chunks:
print(chunk)
4. 面向主題的分塊技術
大型文檔,如學術論文、長篇報告和詳細文章,通常包含多個主題。langchain中的分割技術,都難以準確識別主題轉換點。這些方法經常會錯過細微的轉換或錯誤地識別它們,導致分塊重疊。
面向主題的分塊技術旨在使用句子嵌入來識別文檔中主題的變化。通過標識主題轉移的位置,確保每個塊封裝一個單一的、連貫的主題,具體包括:
- 句子嵌入: 句子嵌入將句子轉換成高維向量,從而捕捉句子的語義。通過分析這些向量,我們可以確定主題變化的點。
- 主題檢測: 使用為主題建模的相關算法,檢測主題的變化并確定分割文檔的最佳點。這確保了每個塊在主題上是一致的。
- 增強的檢索和嵌入: 通過確保每個塊代表一個主題,RAG 系統中的檢索和嵌入步驟變得更加有效。每個塊的嵌入更有意義,從而提高檢索性能和響應的準確性。
這種技術已經在過去主題建模的場景下得到了證明,但是它同樣適用于 RAG 系統。通過采用這種方法,RAG 系統可以在其生成的內容中實現更高的準確性和相關性,使其更有效地完成復雜和知識密集型的任務。
4.1 生成句子嵌入
可以使用Sentence-BERT (SBERT) 為單個句子生成嵌入,這些嵌入是密集的向量表示,封裝了句子的語義內容,使我們能夠衡量它們的相似性。
from sentence_transformers import SentenceTransformer
sentences = ["Sentence 1...", "Sentence 2...", ...]
model = SentenceTransformer('paraphrase-MiniLM-L6-v2')
embeddings = model.encode(sentences)
4.2 計算相似度
句子之間的相似度是通過余弦距離或者其他距離度量來衡量的,比如曼哈頓或者歐氏距離。這有助于識別連續句之間的連貫性。
from sklearn.metrics.pairwise import cosine_similarity
similarity_matrix = cosine_similarity(embeddings)
4.3 差異評分
為了檢測主題轉換,我們定義了一個參數 n,指定要比較的句子數。該算法根據余弦距離計算差距得分。
import numpy as np
#定義參數
n = 2
# 計算差異評分
gap_scores = []
for i in range(len(embeddings) - n):
similarity = cosine_similarity(embeddings[i:i+n], embeddings[i+n:i+2*n])
gap_scores.append(np.mean(similarity))
為了解決差異分數中的噪聲,可以采用平滑算法,窗口大小 k 決定了平滑的程度。
# 定義窗口大小 k
k = 3
# 平滑差異評分
smoothed_gap_scores = np.convolve(gap_scores, np.ones(k)/k, mode='valid')
4.4 邊界檢測
通過分析平滑后的差距得分來識別局部極小值,這表明潛在的話題轉換,可以用閾值來確定重要的邊界。
# 檢測本地極小值
local_minima = (np.diff(np.sign(np.diff(smoothed_gap_scores))) > 0).nonzero()[0] + 1
# 設置閾值 c
C = 1.5
# 確定顯著的界限
significant_boundaries = [i for i in local_minima if smoothed_gap_scores[i] < np.mean(smoothed_gap_scores) - c * np.std(smoothed_gap_scores)]
4.5 分段的聚類
對于較長的文檔,類似的主題可能會重新出現。為了處理這個問題,使用類似的內容聚類算法,可以減少冗余并確保每個主題都是唯一表示的。
from sklearn.cluster import KMeans
# 轉化為embedding
segment_embeddings = [np.mean(embeddings[start:end], axis=0) for start, end in zip(significant_boundaries[:-1], significant_boundaries[1:])]
# Kmeans 聚類示例
kmeans = KMeans(n_clusters=5)
clusters = kmeans.fit_predict(segment_embeddings)
這里的代碼只是示意, 還可以通過自動參數優化、采用 transformer 模型、基于知識圖譜的層次分類等方法來進一步增強面向主題感知的分塊技術。
5.一句話小結
在RAG系統中, 文本分塊技術是必不可少的。對于大型文檔而言,可以嘗試采用面向主題感知的句子嵌入來提升RAG 系統的性能,使其生成更相關且一致的內容。