# chunk_analysis **Repository Path**: bluedream_pp/chunk_analysis ## Basic Information - **Project Name**: chunk_analysis - **Description**: No description available - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-09-03 - **Last Updated**: 2026-09-03 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # RAG 文本切分策略对比评估工具 > 面向 AI Agent RAG 初学者的可直接运行项目:自动化对比多种文本切分策略,输出检索指标(Hit Rate@K / Recall@K / Precision@K / MRR)和生成指标(忠实度 / 答案相关性 / 上下文相关性)的对比结果。 --- ## 一、项目简介 RAG(检索增强生成)的效果 = **检索质量 × 生成质量**,而文本切分(Chunking)是检索的前提——80% 的 RAG 效果差问题,根源都在切分不合理。 本项目帮你解决三个问题: 1. **怎么切?** —— 内置 6 种切分策略,从简单到进阶,代码可直接复用 2. **切得好不好?** —— 自动化计算 7 项标准指标,量化对比 3. **怎么选?** —— 用真实测试数据跑出结果,告诉你不同策略的优劣权衡 ### 内置 6 种切分策略 | 策略名 | 说明 | 核心特点 | |---|---|---| | `fixed_no_overlap` | 固定长度切分(无重叠) | 最简单,一刀切,容易截断语义 | | `fixed_with_overlap` | 固定长度切分(10%重叠) | 带重叠,缓解边界截断问题 | | `recursive_small` | 递归字符切分(小块120字符) | 按段落→句子→标点递归切,小块更精准 | | `recursive_medium` | 递归字符切分(中块200字符) | 工业界最常用,性价比最高 | | `recursive_large` | 递归字符切分(大块350字符) | 大块上下文完整,但噪音多 | | `semantic` | 语义切分(按语义相似度断点) | 用 Embedding 判断语义跳转,效果最好但块数多 | --- ## 二、项目结构(PyCharm 规范) ``` D:/bigdata/chunk_project/ ├── main.py # 主程序入口,串联全流程 ├── requirements.txt # 依赖清单 ├── README.md # 本文档 ├── config/ │ ├── __init__.py │ └── settings.py # 全局配置(路径、切分参数、评估参数) ├── data/ │ ├── raw/ │ │ └── employee_handbook.md # 测试文档:公司员工手册(约5000字符,8章35节) │ └── eval/ │ └── test_questions.json # 标注测试集:18个问题 + 正确章节 + 标准答案 ├── src/ │ ├── __init__.py │ ├── chunking/ │ │ ├── __init__.py │ │ └── chunkers.py # 切分策略:固定长度/递归字符/语义切分 │ ├── retrieval/ │ │ ├── __init__.py │ │ ├── retriever.py # 向量检索器(ChromaDB,支持自定义Embedding) │ │ └── lightweight_embedder.py # 轻量级Embedding(字符n-gram TF-IDF,无需下载模型) │ ├── evaluation/ │ │ ├── __init__.py │ │ ├── retrieval_metrics.py # 检索指标:HitRate/Recall/Precision/MRR @K │ │ └── generation_metrics.py # 生成指标:忠实度/答案相关性/上下文相关性 │ └── utils/ │ ├── __init__.py │ └── helpers.py # 工具函数:文本清洗、章节提取、句子分割、抽取式回答 ├── results/ # 运行结果输出目录 │ ├── chunking_comparison.csv # 对比表(Excel可直接打开) │ ├── chunking_comparison_full.json # 完整指标结果 │ └── detailed_qa_results.json # 每个问题的详细问答结果(人工检查用) └── tests/ └── __init__.py ``` --- ## 三、环境要求 - **Python**: 3.10+(已在 3.14.6 验证) - **操作系统**: Windows / macOS / Linux - **依赖**: langchain、chromadb、numpy、pandas ### 安装依赖 ```bash pip install -r requirements.txt ``` > 本项目默认使用**轻量级本地 Embedding**(字符 n-gram TF-IDF),无需下载任何外部模型,安装依赖后即可直接运行。 > > 如需更高质量的语义检索,可切换为 ChromaDB 内置的 ONNX all-MiniLM-L6-v2 模型(首次运行自动下载约 80MB),见下文「自定义指南」。 --- ## 四、快速开始 ### 一键运行 ```bash cd D:/bigdata/chunk_project python main.py ``` ### 运行流程 程序自动执行以下 4 步: 1. **加载数据** —— 读取员工手册文档和 18 个标注测试问题 2. **初始化 Embedding** —— 用文档全文拟合轻量级向量化器 3. **逐策略评估** —— 对 6 种切分策略分别执行:切分 → 建索引 → 检索 → 抽取式回答 → 计算指标 4. **汇总输出** —— 打印对比表,保存 CSV / JSON 结果文件 ### 输出文件 运行完成后,`results/` 目录下生成 3 个文件: | 文件 | 说明 | |---|---| | `chunking_comparison.csv` | 所有策略 × 所有K值的指标对比表,可用 Excel 打开 | | `chunking_comparison_full.json` | 完整的汇总指标结果(JSON格式) | | `detailed_qa_results.json` | 每个问题在每种策略下的详细问答结果(便于人工检查) | --- ## 五、评估指标详解 ### 5.1 检索阶段指标(衡量"找得准不准") 所有指标带 `@K`,表示取前 K 个检索结果计算,本项目测试 K=1, 3, 5。 | 指标 | 公式 | 大白话解释 | |---|---|---| | **Hit Rate@K** | 命中问题数 ÷ 总问题数 | 前K个结果里**有没有**至少1个正确答案,有就算命中 | | **Recall@K** | TopK中正确文档数 ÷ 全部正确文档数 | 该找的答案**找到了多少**,衡量漏检 | | **Precision@K** | TopK中正确文档数 ÷ K | 前K个结果里**真正有用的有多少**,衡量错检/垃圾信息 | | **MRR** | 第一个正确答案排名的倒数,取平均 | 正确答案排第1得1.0,排第2得0.5,衡量排序质量 | ### 5.2 生成阶段指标(衡量"答得好不好") | 指标 | 计算方法 | 大白话解释 | |---|---|---| | **忠实度 Faithfulness** | 回答中有依据的句子数 ÷ 总句子数 | 回答的每句话是否都能在检索上下文中找到依据,有没有幻觉 | | **答案相关性 Answer Relevance** | 回答与问题的语义相似度 | 回答是否紧扣问题,有没有答非所问 | | **上下文相关性 Context Relevance** | 检索文档与问题的平均语义相似度 | 检索到的文档是否真的与问题相关,有没有凑数的垃圾 | > **实现说明**:传统生成指标常用 LLM-as-Judge(让大模型当裁判),本项目为确保可独立运行,采用基于 Embedding 语义相似度的近似评估方法。所有策略使用同一评估标准,横向对比公平有效。 --- ## 六、实际运行结果与解读 以下是在员工手册测试集(18个问题)上的真实运行结果(@3,最常用配置): | 策略 | 文本块数 | HitRate | Recall | Precision | MRR | 忠实度 | 答案相关性 | 上下文相关性 | |---|---|---|---|---|---|---|---|---| | fixed_no_overlap | 64 | 0.944 | 0.611 | 0.407 | 0.880 | 1.000 | 0.703 | 0.580 | | fixed_with_overlap | 66 | 0.889 | 0.611 | 0.444 | 0.861 | 1.000 | 0.702 | 0.583 | | recursive_small | 59 | 0.944 | **0.778** | 0.463 | 0.944 | 1.000 | 0.695 | 0.578 | | recursive_medium | 35 | **1.000** | **1.000** | 0.352 | **1.000** | 1.000 | 0.693 | 0.566 | | recursive_large | 35 | **1.000** | **1.000** | 0.352 | **1.000** | 1.000 | 0.693 | 0.566 | | semantic | 135 | **1.000** | 0.455 | **0.593** | 0.917 | 1.000 | 0.696 | **0.601** | ### 结果解读 1. **recursive_medium / recursive_large(中大块)**:HitRate 和 Recall 都是 1.0,因为本文档每节约 150-200 字符,中大块刚好每节 1 块,只要检索到该节就命中。但 Precision 只有 0.35,说明块里包含大量与问题无关的内容(噪音多)。 2. **recursive_small(小块)**:Recall 达到 0.778,比固定长度切分高,因为递归切分按句子边界拆分,语义更完整。块数适中(59块),在召回和精确之间取得较好平衡。 3. **fixed_no_overlap(固定无重叠)**:容易从句子中间截断,导致部分答案被切断,Recall 只有 0.611。 4. **semantic(语义切分)**:Precision 最高(0.593),上下文相关性最高(0.601),因为每个块语义单一、噪音少。但 Recall 只有 0.455,因为块太细(135块,接近句子级),正确答案分散在多个小块中,TopK 难以覆盖全部。 5. **忠实度全部为 1.0**:因为本项目使用抽取式回答(直接从检索结果中提取句子拼接),回答内容必然来自上下文,所以忠实度满分。实际使用 LLM 生成时,忠实度会低于 1.0,此时切分质量对忠实度的影响会更明显。 ### 选型建议 | 场景 | 推荐策略 | 理由 | |---|---|---| | 快速验证、文档短(每节<300字) | recursive_medium(200字符) | 每节1块,召回率最高,实现简单 | | 通用场景、文档中等长度 | recursive_small(120字符)+ 10%重叠 | 在召回和精确之间平衡最好 | | 长文档、专业领域、追求精度 | semantic(语义切分) | 语义边界最准,Precision最高,但需配合较大的K值 | | 不推荐 | fixed_no_overlap | 截断语义,效果最差 | --- ## 七、自定义指南 ### 7.1 替换为你自己的文档 1. 将你的文档(Markdown / TXT 格式)放入 `data/raw/` 目录 2. 修改 `config/settings.py` 中的 `DEFAULT_DOC_PATH` 指向你的文档 3. 准备标注测试集,放入 `data/eval/`,格式如下: ```json [ { "id": 1, "question": "你的问题", "ground_truth_sections": ["3.3"], "expected_answer": "标准答案" } ] ``` > `ground_truth_sections` 是正确答案所在的章节编号。如果你的文档没有章节编号,可以修改 `src/utils/helpers.py` 中的 `extract_sections_from_markdown` 函数,或直接用段落索引。 ### 7.2 调整切分参数 编辑 `config/settings.py` 中的 `CHUNKING_STRATEGIES` 字典: ```python CHUNKING_STRATEGIES = { "my_strategy": { "description": "我的自定义策略", "chunk_size": 300, # 块大小(字符数) "chunk_overlap": 30, # 重叠大小 }, # ... } ``` ### 7.3 切换为高质量 Embedding 模型 编辑 `main.py`,将轻量级 Embedding 替换为 ChromaDB 内置的 ONNX MiniLM: ```python # 注释掉轻量级 Embedding 初始化 # embedder = create_lightweight_embedder([document_text]) # embed_fn = embedder.transform # 使用 ChromaDB 内置 Embedding(首次运行自动下载约80MB模型) from src.retrieval.retriever import VectorRetriever temp_retriever = VectorRetriever( persist_dir="./.chroma_init", collection_name="init", custom_embed_fn=None, # 设为 None 即使用内置模型 ) embed_fn = temp_retriever.embed_texts ``` 同时将 `run_single_strategy` 中创建 VectorRetriever 时的 `custom_embed_fn=embed_fn` 改为 `custom_embed_fn=None`。 ### 7.4 接入真实 LLM 生成回答 当前使用抽取式回答(从检索结果中提取句子拼接)。如需接入 LLM(如 OpenAI / 文心 / 通义),修改 `src/utils/helpers.py` 中的 `generate_extractive_answer` 函数,替换为 LLM 调用即可。评估流程无需改动。 --- ## 八、核心代码复用示例 ### 8.1 使用递归字符切分器 ```python from src.chunking.chunkers import RecursiveChunker chunker = RecursiveChunker(chunk_size=200, chunk_overlap=25) chunks = chunker.split_document(your_markdown_text) for chunk in chunks: print(chunk.chunk_id, chunk.metadata["section_id"], chunk.text[:50]) ``` ### 8.2 使用向量检索器 ```python from src.retrieval.retriever import VectorRetriever from src.retrieval.lightweight_embedder import create_lightweight_embedder embedder = create_lightweight_embedder([your_document_text]) retriever = VectorRetriever( persist_dir="./chroma_db", collection_name="my_docs", custom_embed_fn=embedder.transform, ) retriever.add_chunks(chunks) results = retriever.query("用户问题", top_k=5) for r in results: print(r["score"], r["metadata"]["section_id"], r["text"][:80]) ``` ### 8.3 计算检索指标 ```python from src.evaluation.retrieval_metrics import ( hit_rate_at_k, recall_at_k, precision_at_k, mrr_at_k ) gt_sections = ["3.3"] retrieved_docs = [...] # retriever.query 的返回结果 print("HitRate@3:", hit_rate_at_k(retrieved_docs, gt_sections, k=3)) print("Recall@3:", recall_at_k(retrieved_docs, gt_sections, all_correct_count, k=3)) print("Precision@3:", precision_at_k(retrieved_docs, gt_sections, k=3)) print("MRR:", mrr_at_k(retrieved_docs, gt_sections, k=3)) ``` --- ## 九、常见问题 **Q: 为什么忠实度都是 1.0?** A: 因为当前使用抽取式回答(直接从检索结果提取句子),回答内容必然来自上下文。接入真实 LLM 后,忠实度会反映 LLM 是否产生幻觉。 **Q: 为什么 recursive_medium 和 recursive_large 结果一样?** A: 因为测试文档每节约 150-200 字符,200 和 350 的块大小都大于大部分章节长度,所以每节只切 1 块。换更长的文档或调小块大小即可看到差异。 **Q: 语义切分为什么块数这么多(135块)?** A: 语义切分按句子间的语义相似度判断断点,当前阈值 0.75 较严格,大部分句子间相似度低于阈值,所以接近句子级切分。可调高 `similarity_threshold`(如 0.85)减少切分点。 **Q: 可以用中文以外的语言吗?** A: 可以。轻量级 Embedding 基于字符 n-gram,对任何语言都适用。切分器的标点分隔符包含中英文标点,如需支持其他语言可修改 `src/chunking/chunkers.py` 中的 `separators`。 --- ## 十、技术栈 | 组件 | 技术 | 版本 | |---|---|---| | 切分框架 | LangChain Text Splitters | 1.x | | 向量数据库 | ChromaDB | 1.x | | Embedding | 字符 n-gram TF-IDF(自研轻量) / ONNX all-MiniLM-L6-v2 | - | | 数据处理 | NumPy / Pandas | 最新 | | 评估方法 | 基于语义相似度的无参考评估 | - | --- *项目位置:`D:/bigdata/chunk_project`* *最后更新:2026-09-03*