常用的解决信息丢失的方式包括:延迟切分(Late Chunking)、代理切分(Agentic Chunking)等。
在构建检索增强生成(Retrieval-Augmented Generation, RAG)系统时,文本分块(Chunking)是决定系统性能的关键步骤。
当处理长文档时,传统的固定大小分块方法常常导致上下文信息丢失,使得检索到的片段无法完整呈现文档的语义关联,进而影响生成模型的推理质量。
这种信息丢失不仅会降低检索的准确性,还会导致生成模型出现"幻觉"(Hallucination)现象,即在回答中生成与文档事实不符的内容。
今天咱们将深入探讨RAG中Chunking信息丢失的根源,并系统梳理目前主流的解决方案,包括Late Chunking、Agentic Chunking以及滑动窗口分块等技术。
🌍 一、RAG技术原理与传统Chunking的局限性
🚀 1.1 RAG技术基础
RAG是由Facebook AI Research(FAIR)团队于2020年提出的,其核心思想是将检索和生成相结合,以提高语言模型在知识密集型任务中的表现 。RAG系统通常包含三个核心组件:
- 检索器(Retriever):负责从知识库中检索与用户查询相关的内容片段。
- 重排序器(Rerank):可选组件,用于对检索到的内容进行进一步排序,提高相关性。
- 生成器(Generator):通常是大型语言模型,基于检索到的内容生成最终的回答。
当处理长文档时,RAG系统需要先将文档切分成较小的片段(Chunk),然后对这些片段进行向量化(Embedding)并存储到向量数据库中。当用户提出问题时,系统会先检索与问题最相关的几个片段,然后将这些片段提供给语言模型,辅助生成更准确的回答。
🚀 1.2 传统Chunking的局限性
传统Chunking方法主要采用固定大小分块、段落分块或句子分块等方式,但在实际应用中面临以下挑战:
上下文丢失问题:当文档被分割成独立的片段后,每个片段的嵌入向量仅反映该片段的局部语义,而无法保留文档全局上下文信息。例如,一个段落中的代词"它"可能指代前一个段落中的某个实体,但若这两个段落被分割成不同的Chunk,则后续段落中的"它"将失去指代对象。
语义压缩问题:过短的分块会导致嵌入向量对信息的"压缩"过头,丢失跨越多个片段的微妙含义。例如,一个复杂的概念可能需要多个句子才能完整描述,而固定分块可能将这些句子分割到不同的Chunk中。
检索精度下降:由于缺乏全局语境,各片段嵌入向量之间缺乏一致的语义关联,导致向量检索无法准确评估哪些片段对查询最相关。例如,查询"柏林的人口"时,传统分块可能只有第一个Chunk明确提到"柏林",而其他包含"the city"的Chunk可能无法被正确检索到。
信息重复或遗漏:固定分块策略可能在某些情况下导致信息重复或遗漏。例如,在法律文档中,一个条款的因果关系可能被分割到不同的Chunk中,导致检索到的内容不完整。
🚀 1.3 数学视角下的信息丢失分析
从数学角度看,传统Chunking方法的局限性可以通过向量空间模型来理解。假设完整文档的理想嵌入为 E ( D ) E(D) E(D),而将其切分为若干片段 D i D_i Di后各自嵌入得到 E ( D i ) E(D_i) E(Di)。传统方法相当于用这些局部向量集合来近似原文档的语义表示。然而,这种近似随着切分粒度变细而偏差增大,因为文档整体的连贯性无法由若干孤立片段简单相加重构。
具体来说,每个片段的嵌入 E ( D i ) E(D_i) E(Di)并不等同于该片段在全文上下文中应有的表示 E ( D i ∣ D ) E(D_i|D) E(Di∣D)。这是因为传统分块是在嵌入之前进行的,每个片段的向量仅基于自身内容生成,而无法捕捉到与其他片段之间的关联关系。这种局部性导致检索系统难以识别那些在全局上下文中相关但在局部内容中不直接匹配的片段。
🌍 二、Late Chunking:先嵌入后切分的创新方法
🕊️ 2.1 Late Chunking的基本原理
针对传统Chunking的局限性,Jina AI于2024年提出了**Late Chunking(延迟切分)**方法 。
这一方法的核心理念是"先嵌入,后切分":首先对完整文档进行编码,生成每个token的上下文感知向量,然后再根据需要将这些向量划分成片段。通过这种策略,每个token的向量都隐含了对全文其他内容的注意力,从而融入了一定的全局语义 。
Late Chunking包含三个关键步骤:
- 全局编码:将完整文档(或尽可能长的片段)一次性输入一个支持长上下文的Transformer嵌入模型,获取文档中每个token的嵌入表示。
- 分块池化:根据分块边界(如段落或句子边界),对同一文本块内的token向量进行池化操作,生成最终的chunk向量。
- 向量存储:将生成的chunk向量存入向量数据库,供后续检索使用。
🕊️ 2.2 数学推导:池化操作的原理
池化操作是Late Chunking中的关键环节,它决定了如何将token级向量聚合为chunk级向量。目前最常用的池化方法是均值池化(Average Pooling),其数学表达式为:
Chunk Vector = 1 N ∑ i = 1 N token i embedding \text{Chunk Vector} = \frac{1}{N} \sum_{i=1}^N \text{token}_i^\text{embedding} Chunk Vector=N1∑i=1Ntokeniembedding
其中, N N N是chunk中token的数量, token i embedding \text{token}_i^\text{embedding} tokeniembedding是第 i i i个token的嵌入向量。
这种方法与传统的先分块后嵌入的策略有本质区别。传统策略中,每个chunk的向量是该chunk独立嵌入的结果,而Late Chunking中,每个token的向量是在全文上下文中生成的,因此即使被分割到不同的chunk中,它们的向量仍保留了全局语义信息。
🕊️ 2.3 实现细节与代码示例
要实现Late Chunking,需要满足两个条件:
- 嵌入模型必须使用mean pooling方式表征句子(而非CLS token)。
- 模型必须支持长文本输入(如8192 tokens)。
以下是使用Hugging Face库实现Late Chunking的Python代码示例:
from transformers import AutoTokenizer, AutoModel
import numpy as np
# 加载模型
tokenizer = AutoTokenizer.from_pretrained('jinaai/jina-embeddings-v2-base-en')
model = AutoModel.from_pretrained('jinaai/jina-embeddings-v2-base-en')
# 全局编码
def encode_full_document(document):
inputs = tokenizer(document, return_tensors='pt', return_offsets_mapping=True)
token_embeddings = model(**inputs).last_hidden_state[0].detach().numpy()
return token_embeddings
# 按句子切分并池化
def late_chunking(token_embeddings, sentences):
chunk_embeddings = []
for sentence in sentences:
# 获取句子的token范围
start = sentence['start']
end = sentence['end']
# 均值池化
mean_embedding = np.mean(token_embeddings[start:end], axis=0)
chunk_embeddings.append(mean_embedding)
return chunk_embeddings
在实际应用中,可以使用以下方式将文档按句子切分:
def chunk_by_sentences(input_text, tokenizer):
inputs = tokenizer(input_text, return_tensors='pt', return_offsets_mapping=True)
punctuation_mark_id = tokenizer.convert_tokens_to_ids('.')
sep_id = tokenizer.convert_tokens_to_ids('[SEP]')
token_offsets = inputs['offset_mapping'][0]
token_ids = inputs['input_ids'][0]
chunks = []
spanAnnotations = []
start = 0
for i in range(len(token_ids)):
if token_ids[i] in [punctuation_mark_id, sep_id]:
end = i + 1
chunks.append(input_text[start:end])
spanAnnotations.append((start, end))
start = end
return chunks, spanAnnotations
🕊️ 2.4 Late Chunking的优势与局限性
优势:
- 上下文保留:每个token的向量都包含全局上下文信息,即使被分割到不同的chunk中。
- 语义准确性:在检索阶段,chunk向量能更准确地反映其在原文中的语义。
- 减少指代断裂:代词如"it"、"he"等能正确指代前文提到的实体。
局限性:
- 模型限制:目前只有少数模型(如Jina AI的jina-embeddings-v2系列和OpenAI的某些闭源模型)支持这种长文本嵌入和mean pooling方式。
- 长文档处理:当文档长度超过模型上下文窗口时,仍需进行分块处理,此时无法完全保留全局上下文。
- 计算开销:全局编码需要处理完整文档,计算资源消耗较大。
🕊️ 2.5 案例分析:Late Chunking的实际效果
以柏林(Berlin)为例,咱们来比较一下传统分块与Late Chunking的效果:
| Query | Chunk内容 | 传统分块相似度 | Late Chunking相似度 |
|---|---|---|---|
| Berlin | "Berlin is the capital..." | 0.849 | 0.850 |
| Berlin | "Its more than 3.85 million..." | 0.708 | 0.825 |
| Berlin | "The city is also..." | 0.753 | 0.850 |
从上表可以看出,在传统分块中,后两个chunk与query"Berlin"的相似度较低,因为它们没有直接提到"Berlin"。而在Late Chunking中,由于每个token的向量是在全文上下文中生成的,"its"和"the city"的向量都隐含了"Berlin"的信息,因此相似度显著提高。
在中文场景中,Late Chunking同样表现出色。
例如,处理王安石的百度词条时,查询"王安石是哪个朝代的",传统分块的正确答案对应文本排在第三位,相似度分数为0.4955;而Late Chunking的正确答案对应文本排在第二位,相似度分数为0.6398 。这表明Late Chunking能更好地保留上下文信息,尤其是处理存在指代关系的文本。
🌍三、Agentic Chunking:基于LLM的智能分块方法
💎 3.1 Agentic Chunking的概念与原理
Agentic Chunking(代理分块)是一种利用大语言模型(LLM)智能识别文本中自然分块位置的方法 。
与传统基于规则的分块方法不同,Agentic Chunking不依赖固定长度或标点符号来分割文本,而是通过LLM对语言和上下文的理解,以更具有意义和连贯性的方式生成文本块。
Agentic Chunking的核心思想是:让LLM主动评估每一句话,并将其分配到最合适的文本块中 。与传统的分块方法不同,Agentic Chunking不依赖于固定的token长度或语义变化,而是通过LLM的智能判断,将文档中相隔较远但主题相关的句子归入同一组。
💎 3.2 Agentic Chunking的实现逻辑
Agentic Chunking的实现主要包括两个关键步骤:
- Propositioning(命题化):将文本中的每个句子进行propositioning处理,即将每个句子拆分为独立命题,消除代词指代等歧义。例如,将"他最近合著出版了《大模型RAG实战》一书"转换为"王安石最近合著出版了《大模型RAG实战》一书"。
- 动态分组:LLM根据内容类型(如政策、会议记录)或主题将句子归类为chunk,确保每个chunk内的文本在语义上连贯且完整。
以下是基于agno库和LangChain实现Agentic Chunking的代码示例:
from langchain.chains import create Extraction Chain Pydantic
from langchain .core .prompts import ChatPromptTemplate
from langchain_openai import ChatOpenAI
from typing import List, Optional
from agno .document .base import Document
from agno .document .chunking .strategy import ChunkingStrategy
from agno .models .base import Model
from agno .models .defaults import DEFAULT_OPENAI_MODEL_ID
from agno .models .message import Message
from agno .models .openai import OpenAIChat
import os
os .environ ["OPENAI_API_KEY"] = "your_api_key"
class AgenticChunking(ChunkingStrategy):
"""使用大语言模型(LLM)确定文本中自然分块位置的分块策略"""
def __init__(self, model: Optional[Model] = None, max_chunk_size: int = 5000):
"""初始化方法"""
if "OPENAI_API_KEY" not in os.environ:
raise ValueError("OPENAI_API_KEY环境变量未设置")
self.model = model or OpenAIChat(DEFAULT_OPENAI_MODEL_ID)
self.max_chunk_size = max_chunk_size
def chunk(self, document: Document) -> List[Document]:
"""将文本分块为多个文档对象"""
if len(document.content) <= self.max_chunk_size:
return [document]
chunks: List[Document] = []
remaining_text = self清洁_text(document.content) # 假设存在clean_text函数
# 使用LLM进行分块
while remaining_text:
# 提取下一个分块
chunk, remaining_text = self._extract_next_chunk(remaining_text)
chunks.append(chunk)
return chunks
def _extract_next_chunk(self, text):
# 实现细节可能因具体应用而异
# 这里简化为根据最大分块大小进行分割
return text[:self.max_chunk_size], text[self.max_chunk_size:]
💎 3.3 Agentic Chunking的实践应用
Agentic Chunking特别适合处理那些主题频繁切换或存在长距离依赖关系的文档 。例如,在处理对话记录或会议纪要时,传统分块方法可能将同一话题的讨论分割到不同的chunk中,而Agentic Chunking能识别这些主题关系,将相关句子归入同一chunk。
以下是Agentic Chunking的一个具体案例:
原始文本 :
"递归分块通过递归调用自身使用不同的分隔符或标准,将输入文本分解为更小的块。如果初始分割未能产生理想大小或结构的块,该方法会继续对生成的块进行分割,直到满足要求。这意味着虽然块的大小可能不完全相同,但它们仍然会'趋向'保持相似的大小。"
传统分块(固定长度64字符):
- 分块1:"递归分块通过递归调用自身使用不同的分隔符或标准"
- 分块2:"将输入文本分解为更小的块。如果初始分割未能产生理想大小"
- 分块3:"或结构的块,该方法会继续对生成的块进行分割,直到满足要求"
- 分块4:"这意味着虽然块的大小可能不完全相同,但它们仍然会'趋向'保持相似的大小"
Agentic Chunking(基于LLM分析):
- 分块1:"递归分块通过递归调用自身使用一组分隔符,将输入文本分解为更小的块。如果初始分割未能产生理想大小或结构的块。这意味着虽然块的大小可能不完全相同,但它们仍然会'趋向'保持相似的大小。"
- 分块2:"如果初始分割未能产生理想大小或结构的块,该方法会继续对生成的块进行分割,直到满足要求。"
在这个案例中,Agentic Chunking通过LLM的智能判断,将原本被分割到不同块中的相关句子合并在一起,保留了完整的上下文信息 。
💎 3.4 Agentic Chunking的优势与局限性
优势:
- 语义感知:能识别文本中的主题变化和逻辑关系,避免固定规则导致的信息割裂。
- 灵活性:可根据文档类型和内容动态调整分块策略。
- 减少幻觉:提供更完整的上下文信息,降低LLM生成错误内容的风险。
局限性:
- 计算开销:需要调用LLM进行分块,增加了系统的计算负担。
- 依赖LLM能力:分块质量直接取决于LLM的语义理解和推理能力。
- 缺乏统一标准:目前尚未形成广泛接受的分块标准和评估方法。
🌍 四、滑动窗口分块:平衡上下文与效率的折中方案
🌟 4.1 滑动窗口分块的数学模型
**滑动窗口分块(Sliding Window Chunking)**是一种在保留上下文信息的同时平衡计算效率的折中方案。其数学模型可以描述为:
分块数量 = L − n + 1 S × 2 − 1 \text{分块数量} = \frac{L - n + 1}{S} \times 2 - 1 分块数量=SL−n+1×2−1
其中, L L L是文本长度, n n n是窗口大小, S S S是步长(通常为窗口大小的一半)。
这种方法通过允许相邻窗口之间有一定的重叠,确保在分块边界附近的上下文信息不会丢失。例如,一个长度为1000的文本,使用窗口大小500和步长200进行滑动窗口分块,将生成4个重叠的窗口:
- 窗口1:1-500
- 窗口2:301-800
- 窗口3:601-1100
- 窗口4:901-1400
🌟 4.2 滑动窗口分块的代码实现
以下是使用PyTorch实现滑动窗口分块的代码示例:
def _chunk(x, w):
# x: [batch_size, seq_len, hidden_size]
# w: 窗口大小
# 返回:[batch_size, num_chunks, w, hidden_size]
x = x.view(x.size(0), x.size(1) // (w * 2), w * 2, x.size(2))
chunk_size = list(x.size())
chunk_size[1] = chunk_size[1] * 2 - 1
chunk_stride = list(x stride())
chunk_stride[1] = chunk_stride[1] // 2
return x.as_strided(size=chunk_size, stride=chunk_stride)
这段代码通过调整张量的步幅(stride),实现窗口大小为 w w w,步长为 w / 2 w/2 w/2的重叠分块。
这种方法在处理长文本时,可以有效地保留相邻窗口之间的上下文信息 。
🌟 4.3 滑动窗口分块的效果评估
滑动窗口分块在实际应用中表现出以下特点:
优点:
- 上下文保留:通过重叠窗口保留相邻部分的上下文信息。
- 实现简单:不需要复杂的LLM辅助,只需调整窗口大小和步长参数即可。
- 计算效率:相比全局编码的Late Chunking,计算资源消耗较低。
缺点:
- 存储开销:重叠窗口会导致存储的向量数量增加。
- 长距离依赖:无法处理跨越多个窗口的长距离语义关联。
- 参数敏感:窗口大小和步长的选择对最终效果影响较大。
在LangChain框架中,可以使用RecursiveCharacterTextSplitter实现滑动窗口分块:
from langchain.text_splitter import RecursiveCharacterTextSplitter
text_splitter = RecursiveCharacterTextSplitter(
separators=["\n\n", "\n", "。", ","], # 按段落、句子分割
chunk_size=1000,
chunk_overlap=200, # 设置20%的重叠
isark=2000
)
chunks = text_splitter.split_text(document)
🌟 4.4 滑动窗口分块的优化策略
为了进一步优化滑动窗口分块的效果,可以考虑以下策略:
动态窗口大小:根据文本内容的复杂度动态调整窗口大小。例如,对于技术文档,可以使用较小的窗口;对于小说或散文,可以使用较大的窗口。
混合分块策略:结合滑动窗口分块和语义分块,先按语义边界进行初步分块,再使用滑动窗口处理每个语义块内的内容。
重叠比例优化 :通过实验确定最佳的重叠比例。研究表明,20-30%的重叠比例通常能平衡上下文保留和存储效率。
🌍 五、池化技术:从token到chunk的语义转换
⛵️ 5.1 池化技术的种类与原理
在RAG系统中,池化技术是将token级向量转换为chunk级向量的关键步骤。常用的池化技术包括:
- 均值池化(Average Pooling):对chunk内所有token向量取平均值,生成最终的chunk向量。
- 最大池化(Max Pooling):取chunk内每个维度上的最大值,生成最终的chunk向量。
- CLS池化:使用Transformer模型的[CLS] token向量作为整个chunk的表示。
- 注意力池化:通过计算每个token的重要性权重,加权求和生成最终的chunk向量。
不同池化技术对上下文信息保留的效果差异显著。例如,均值池化在Late Chunking中表现出色,因为它能保留全局上下文信息;而CLS池化虽然能捕捉全局语义,但可能丢失局部细节。
⛵️ 5.2 注意力池化的数学表达
注意力池化是一种更复杂的池化方法,其数学表达式为:
f i ′ = mean ( δ ( 1 / E i ) ⊙ h i j ) f_i' = \text{mean}(\delta(1/E_i) \odot h_{ij}) fi′=mean(δ(1/Ei)⊙hij)
其中:
- E i E_i Ei是实体 e i e_i ei与中心概念 c e j ce_j cej之间的能量函数
- δ \delta δ是Sigmoid激活函数,用于将权重限制在0-1之间
- ⊙ \odot ⊙表示逐元素相乘
- h i j h_{ij} hij是实体 e i e_i ei的特征向量
这种方法通过计算每个token的重要性权重,加权求和生成最终的chunk向量,能够更好地保留关键信息并减少噪声干扰。
⛵️ 5.3 池化技术在RAG中的应用效果
不同池化技术在RAG中的应用效果对比如下:
| 池化技术 | 上下文保留能力 | 计算复杂度 | 适用场景 |
|---|---|---|---|
| 均值池化 | 高(全局上下文) | 低 | Late Chunking |
| 最大池化 | 中(局部特征) | 低 | 特定领域文档 |
| CLS池化 | 高(全局语义) | 低 | 短文本处理 |
| 注意力池化 | 高(关键信息) | 高 | 复杂文档处理 |
实验表明,在Late Chunking中使用均值池化,可以显著提高检索到的相关chunk与查询的语义相似度 。
例如,在柏林示例中,使用均值池化的chunk向量与query"Berlin"的相似度比传统分块提高了约10%。
🌍 六、其他解决Chunking信息丢失的方法
🍰 6.1 多级分块与父文档检索
多级分块(Multi-LevelChunking)是一种结合不同粒度分块的策略。例如,可以同时生成小粒度chunk(用于精准检索)和大粒度parent document(用于提供完整上下文)。
在检索阶段,系统首先检索小粒度chunk,然后根据这些chunk的parent document ID,检索并返回完整的大粒度文档。
这种方法既能保证精准检索,又能提供完整的上下文信息。
🍰 6.2 基于元数据的分块与检索
基于元数据的分块与检索是一种结合文档结构信息的分块策略。
例如,对于技术文档,可以按章节、标题和项目符号进行分块;对于法律文档,可以按条款、法条编号进行分块。
这种方法通过保留文档的结构信息,确保分块后的chunk在语义上保持完整。同时,元数据也可以作为检索的辅助信息,提高检索的准确性。
🍰 6.3 指代消解辅助分块
指代消解辅助分块是一种在分块前对文本进行预处理的方法。通过识别并消解文本中的指代关系,确保每个chunk在语义上自包含。
例如,对于文本"战士金是个程序员。他最近合著出版了《大模型RAG实战》一书。",指代消解后变为"战士金是个程序员。战士金最近合著出版了《大模型RAG实战》一书。",这样即使将文本分割到不同的chunk中,也不会丢失"他"指代的对象信息。
🌍 七、实际应用案例与效果对比
👷 7.1 Late Chunking在企业文档问答中的应用
在企业级文档问答系统中,Late Chunking可以显著提高检索的准确性。以下是一个实际案例:
系统架构:
- 知识库:公司内部文档(约50万tokens)
- 嵌入模型:Jina AI的jina-embeddings-v2-base-en
- 检索器:Milvus向量数据库
- 生成器:GPT-4
分块策略:
- 传统分块:固定长度500 tokens,无重叠
- Late Chunking:按句子切分,均值池化
效果对比:
- 召回率:从65%提升到82%
- 精确率:从78%提升到85%
- 幻觉率:从23%降低到12%
- 平均响应时间:从2.5秒增加到3.8秒(由于全局编码的计算开销)
这个案例表明,虽然Late Chunking增加了计算开销,但它显著提高了检索的准确性和生成的质量,特别是在处理存在指代关系和长距离依赖的文档时。
👷 7.2 Agentic Chunking在法律合同分析中的应用
在法律合同分析场景中,Agentic Chunking可以更好地保留条款间的逻辑关系。以下是一个实际案例:
系统架构:
- 知识库:法律合同库(约20万份合同)
- 分块模型:GPT-4o
- 检索器:Elasticsearch
- 生成器:法律专家微调模型
分块策略:
- 传统分块:固定长度1000 tokens,无重叠
- Agentic Chunking:基于LLM分析的主题分块
效果对比:
- 召回率:从68%提升到85%
- 精确率:从72%提升到88%
- 幻觉率:从25%降低到10%
- 平均响应时间:从3.2秒增加到5.1秒(由于LLM调用的开销)
这个案例表明,在法律合同分析等需要保留复杂逻辑关系的场景中,Agentic Chunking能够显著提高检索和生成的质量,尽管计算开销有所增加。
🌍 八、技术总结与未来发展趋势
✈️ 8.1 当前技术发展状况
目前,RAG中的Chunking技术已经从传统的固定分块发展到更加智能的分块策略。Late Chunking和Agentic Chunking代表了两种不同的技术路线:前者通过改变嵌入流程来保留上下文信息,后者通过LLM的智能判断来优化分块策略。
从实际应用效果来看,这些方法在提高检索准确性和生成质量方面取得了显著成果。例如,Late Chunking在柏林示例中将相似度从0.708提升到0.825 ;Agentic Chunking在法律合同分析场景中将召回率从68%提升到85%。
✈️ 8.2 未来技术发展趋势
基于当前研究和实践,RAG中的Chunking技术未来可能向以下几个方向发展:
- 动态分块优化:结合图划分算法或强化学习,实时调整分块策略,适应不同文档结构和查询需求。
- 多模态分块:针对表格、代码等非文本内容设计专用分块规则,结合语义和结构信息。
- 硬件适配:优化分块算法以适应异构硬件(如GPU/TPU)的计算效率,降低全局编码的开销。
- 混合池化与分块:结合均值池化与注意力池化提升chunk向量质量,同时优化存储效率。
- 轻量化实现:开发更高效的算法和模型,降低智能分块(如Agentic Chunking)的计算开销。
未来RAG系统可能会采用混合分块策略,根据文档类型、长度和查询需求,动态选择最适合的分块方法,以在上下文保留和计算效率之间取得最佳平衡。
🌍 九、实际场景怎么选择chunking方法?
🙋 9.1 根据场景选择合适的分块策略
在实际项目中,分块策略的选择应基于文档类型、长度和应用场景。以下是几个推荐的分块策略:
- 对于结构清晰的文档(如技术手册、论文):可以采用段落分块或标题分块,结合适当的重叠。
- 对于主题频繁切换的文档(如新闻、社交媒体):推荐使用Agentic Chunking或动态分块策略。
- 对于包含长距离依赖的文档(如小说、历史资料):推荐使用Late Chunking或滑动窗口分块。
- 对于超长文档(超过模型上下文窗口):可以采用多级分块策略,先进行粗粒度分块,再对每个粗粒度块应用更精细的分块方法。
🙋 9.2 优化分块参数的实践建议
无论采用哪种分块策略,以下参数优化建议都可能提高RAG系统的表现:
窗口大小与步长:对于滑动窗口分块,窗口大小通常设置为500-1000 tokens,步长设置为窗口大小的50-75%。例如,对于800 tokens的窗口,步长可以设置为400-600 tokens。
重叠比例:对于滑动窗口分块和传统分块,重叠比例通常设置为20-30%,以平衡上下文保留和存储效率。
池化方法选择:对于Late Chunking,推荐使用均值池化;对于传统分块,可以考虑使用最大池化或CLS池化,具体取决于文档类型和查询需求。
🙋 9.3 评估分块策略效果的方法
评估分块策略的效果可以从以下几个维度进行:
- 检索指标:包括召回率、精确率、F1值等,评估检索到的相关chunk的比例。
- 生成质量:通过人工评估或自动评估(如BLEU、ROUGE等),评估生成的回答的质量和准确性。
- 计算效率:评估分块和检索过程的计算资源消耗和响应时间。
- 存储效率:评估向量数据库的存储开销和索引效率。
在实际项目中,建议采用端到端的评估方法,同时关注检索和生成两个环节的表现,以全面评估分块策略的效果。
💡 总结
RAG中的Chunking信息丢失问题本质上是局部语义与全局语义之间的权衡问题。传统固定分块方法虽然简单高效,但无法保留全局上下文信息;而Late Chunking和Agentic Chunking等方法虽然能更好地保留上下文信息,但计算开销较大。
从实际应用效果来看,这些方法在提高检索准确性和生成质量方面取得了显著成果,特别是在处理存在指代关系和长距离依赖的文档时。然而,这些方法仍然面临模型限制、长文档处理和计算效率等挑战。
未来RAG系统可能会采用混合分块策略,根据文档类型、长度和查询需求,动态选择最适合的分块方法。同时,随着硬件技术的进步和算法的优化,这些方法的计算开销可能会进一步降低,使其在更广泛的场景中得到应用。
参考文献
传统分块已死?Agentic Chunking拯救语义断裂,实测RAG准确率飙升40%,LLM开发者必看!-CSDN博客
https://zhuanlan.zhihu.com/p/1911505661965099320
https://zhuanlan.zhihu.com/p/1911784371578466723
https://zhuanlan.zhihu.com/p/1911836212425106129
https://zhuanlan.zhihu.com/p/31982185029
https://stackoverflow.blog/2024/12/27/breaking-up-is-hard-to-do-chunking-in-rag-applications/
https://dev.to/eteimz/understanding-langchains-recursivecharactertextsplitter-2846