RAG-Semantic Chunking
Semantic Chunking(语义分块)实现详解
Semantic Chunking介绍
-
Semantic Chunking(语义分块)是一种高级文本处理技术,通过分析文本的语义连贯性来智能地将长文本分割成有意义的语义单元。与传统的固定长度分块方法不同,语义分块能够识别自然的语义边界,保持上下文的完整性,从而提高检索增强生成(RAG)系统的性能。
该技术通过计算相邻句子之间的语义相似度,识别语义断点,在语义变化显著的位置进行分割,确保每个文本块都包含完整且相关的信息。这种方法能够有效减少信息碎片化,提高检索精度,进而增强大语言模型生成回答的质量和连贯性。
技术原理
-
Semantic Chunking的工作流程主要包括以下几个关键步骤:
- 文本预处理:将文档转换为纯文本并分割成句子
- 句子向量化:为每个句子生成嵌入向量表示
- 相似度计算:计算相邻句子之间的语义相似度
- 断点识别:基于相似度变化识别语义断点
- 语义分块:根据断点将句子组合成语义连贯的文本块
- 块向量化:为生成的语义块创建嵌入向量
- 语义检索:基于用户查询检索最相关的语义块
代码实现
-
Semantic Chunking的核心组件实现
def get_embedding(text):"""为文本生成嵌入向量"""response = client.embeddings.create(model=semantic_chunking_config.embedding_model_id,input=text)embedding_data = np.array(response.data[0].embedding)return embedding_datadef cosine_similarity(vec1, vec2):"""计算两个向量之间的余弦相似度"""return np.dot(vec1, vec2) / (np.linalg.norm(vec1) * np.linalg.norm(vec2))def compute_breakpoints(similarities, method="percentile", threshold=90):"""根据相似度下降计算分块的断点"""# 根据选定的方法确定阈值if method == "percentile":# 计算相似度分数的第 X 百分位数threshold_value = np.percentile(similarities, threshold)elif method == "standard_deviation":# 计算相似度分数的均值和标准差mean = np.mean(similarities)std_dev = np.std(similarities)# 将阈值设置为均值减去 X 倍的标准差threshold_value = mean - (threshold * std_dev)elif method == "interquartile":# 计算第一和第三四分位数(Q1 和 Q3)q1, q3 = np.percentile(similarities, [25, 75])iqr = q3 - q1 # iqr为四分位全距# 使用 IQR 设置阈值threshold_value = q1 - 1.5 * iqr # 这个是箱线图的下线else:# 如果提供了无效的方法,则抛出异常raise ValueError("Invalid method. Choose 'percentile', 'standard_deviation', or 'interquartile'.")# 找出相似度低于阈值的索引return [i for i, sim in enumerate(similarities) if sim < threshold_value]def split_into_chunks(sentences, breakpoints):"""将句子分割为语义块"""chunks = [] # 初始化空列表存储文本块start = 0 # 初始化起始索引# 遍历每个断点以创建块for bp in breakpoints:# 将从起始位置到当前断点的句子块追加到列表中chunks.append("。".join(sentences[start:bp + 1]) + "。")start = bp + 1 # 将起始索引更新为断点后的下一个句子# 将剩余的句子作为最后一个块追加chunks.append("。".join(sentences[start:]))return chunks
-
配置管理与参数设置
class Config(BaseModel):"""配置类,集中管理所有配置参数"""llm_base_url: str = os.getenv("LLM_BASE_URL")llm_api_key: str = os.getenv("LLM_API_KEY")embedding_model_id: str = os.getenv("EMBEDDING_MODEL_ID")llm_model_id: str = os.getenv("LLM_MODEL_ID")chunk_size: int = 500chunk_overlap: int = 100top_k: int = 2temperature: float = 0.1top_p: float = 0.8presence_penalty: float = 1.05max_tokens: int = 4096
断点识别算法
-
Semantic Chunking实现了三种断点识别算法:
算法 描述 适用场景 百分位法 将相似度低于特定百分位数的点识别为断点 适用于相似度分布较为均匀的文本 标准差法 将相似度低于均值减去X倍标准差的点识别为断点 适用于相似度分布接近正态分布的文本 四分位距法 使用箱线图下界识别异常低的相似度点作为断点 适用于相似度分布存在明显异常值的文本
工作流程详解
-
Semantic Chunking的完整工作流程可以分为以下几个阶段:
阶段 操作 功能描述 1 文档加载 从PDF文件中提取文本内容 2 句子分割 将文本按句号分割成句子列表 3 句子向量化 为每个句子生成嵌入向量表示 4 相似度计算 计算相邻句子之间的余弦相似度 5 断点识别 使用选定算法识别语义断点 6 语义分块 根据断点将句子组合成语义连贯的文本块 7 块向量化 为生成的语义块创建嵌入向量 8 查询处理 接收用户查询并转换为向量表示 9 语义检索 检索与查询最相关的语义块 10 上下文构建 将检索到的语义块组织成结构化上下文 11 回答生成 使用大语言模型基于上下文生成回答
语义检索实现
-
Semantic Chunking中的语义检索实现:
def semantic_search(query, text_chunks, chunk_embeddings, k=5):"""查询找到最相关的文本块"""# 为查询生成嵌入query_embedding = get_embedding(query)# 计算查询嵌入与每个块嵌入之间的余弦相似度similarities = [cosine_similarity(query_embedding, emb) for emb in chunk_embeddings]# 获取最相似的 k 个块的索引top_indices = np.argsort(similarities)[-k:][::-1]# 返回最相关的 k 个文本块return [text_chunks[i] for i in top_indices]
使用示例
-
完整使用流程示例
if __name__ == "__main__":# 加载PDF文档pdf_path = 'data/AI_Information.en.zh-CN.pdf'extracted_text = extract_text_from_pdf(pdf_path=pdf_path)# 每个句子以中文 "。" 结尾,创建句子级别的embeddingsentences = extracted_text.split("。")sentences = [sentence for sentence in sentences if sentence]sentence_embeddings = [get_embedding(sentence) for sentence in sentences]logger.info(f"共生成了 {len(sentence_embeddings)} 个句子embedding.")# 计算相似度similarities = [cosine_similarity(sentence_embeddings[i], sentence_embeddings[i + 1]) for i in range(len(sentence_embeddings) - 1)]# 计算分块的断点(这里使用百分位法)breakpoints = compute_breakpoints(similarities, method="percentile", threshold=90)logger.info(f"共生成了 {len(breakpoints)} 个断点.")# 将文本基于断点分割成语义块text_chunks = split_into_chunks(sentences, breakpoints)logger.info(f"共生成了 {len(text_chunks)} 个文本块.")# 创建语义块的嵌入向量chunk_embeddings = [get_embedding(chunk) for chunk in text_chunks]# 评估查询性能with open('data/val.json', encoding="utf-8") as f:val_data = json.load(f)# 批量评估evaluation_scores = []for entry in val_data:query = entry['question']reference_answer = entry['ideal_answer']ai_response, evaluation_score = evaluate_response(query=query,reference_answer=reference_answer,text_chunks=text_chunks,chunk_embeddings=chunk_embeddings)evaluation_scores.append((query, reference_answer, ai_response, float(evaluation_score)))# 计算平均分df = pd.DataFrame(evaluation_scores, columns=['query', 'reference_answer', 'ai_response', 'evaluation_score'])logger.info(f'平均分:{df['evaluation_score'].mean()}') # 平均分:0.62
与传统分块方法的比较
-
Semantic Chunking与传统固定长度分块的对比:
特性 传统固定长度分块 语义分块 分块依据 字符数或token数 语义连贯性 上下文完整性 可能在句子中间截断 保持语义单元完整 块长度 均匀一致 不等长,根据语义变化 信息密度 可能包含不相关信息 语义相关性高 实现复杂度 简单 较复杂 计算开销 低 中等(需要计算嵌入和相似度) 检索效果 一般 更好
性能优化建议
-
基于Semantic Chunking的实现,以下是一些可能的优化方向:
- 多级分块:结合段落级和句子级分块,形成层次化的分块结构
- 自适应阈值:根据文档特性动态调整断点识别的阈值
- 并行处理:对句子嵌入生成和相似度计算进行并行化处理
- 缓存机制:缓存常用句子和块的嵌入向量,减少重复计算
- 混合断点算法:结合多种断点识别算法的优势,提高断点识别的准确性
- 语义增强:在分块过程中保留关键词和实体信息,增强语义表示
总结
-
Semantic Chunking作为一种高级文本处理技术,通过识别自然的语义边界,将长文本分割成语义连贯的文本块,有效提高了检索增强生成系统的性能。与传统的固定长度分块相比,语义分块能够更好地保持上下文的完整性,减少信息碎片化,提高检索精度。
实验结果显示,基于语义分块的RAG系统在测试集上获得了0.62的平均评分,与传统分块方法相当,但在处理复杂查询和需要深度语义理解的场景中,语义分块展现出更大的优势。随着断点识别算法的进一步优化,语义分块技术有望在RAG系统中发挥更重要的作用。