文档解析方案(读取文件)

文档解析步骤
1. 文件加载:找到文件存放位置并载入处理流水线
    常见问题:文件损坏、权限不足
2. 格式转换:消除格式差异,统一转为纯文本
    将PDF中的表格转为Markdown格式
    将图片转换为txt格式
3. 元数据提取:获取文档信息标签
    捕捉文档的显性和隐性特征(作者、创建时间、文档类型等)
4. 结构化处理:将文本转化为有逻辑关系的知识网络(转化为机器可以理解的知识)

实际落地:
MinerU:https://mineru.net/
Docling:https://github.com/DS4SD/docling
把文档解析为md格式,实测Docling表格解析更好,目录解析不好,MinerU对PDF更好点

技术难点
# 处理扫描件:
    1. 使用OCR(光学字符识别)技术识别文字
    2. 校正识别错误(如将"3"识别为"B")
    3. 保留原始版式信息
# 处理复杂表格:
    | 姓名 | 年龄 |  职业  |
    |-----|----- |-------|
    | 张三 |  28  | 工程师 |
    | 李四 |  35  | 设计师 |
文档解析案例
  • 测试文档:包含表格和文字的 pdf 文件
# 测试文档:包含表格和文字的pdf文件
# 基础解析(使用llamaindex提供的文档解析方法具有局限性,只适合做单纯的文本处理)
from llama_index.core import SimpleDirectoryReader
reader = SimpleDirectoryReader(
    input_files=["/aiproject/data/report_with_table.pdf"]
)
docs = reader.load_data()
print(f"Loaded {len(docs)} docs")
print(docs)

# 高级解析(推荐使用python的第三方模块)
import pdfplumber
with pdfplumber.open(r"E:\ai大模型笔记\teacher\demo_19\data\report_with_table.pdf") as pdf:
    # 提取所有文本
    text = ""
    for page in pdf.pages:
        text += page.extract_text()  # extract_text: 取出页面的文本内容
    print(text[:200]) # 打印前200字符
    # 提取表格(自动检测)
    for page in pdf.pages:
        tables = page.extract_tables()
    for table in tables:
        print("\n表格内容:")
        for row in table:
            print(row)
  • 测试文档:html 网页
# 测试文档:html网页
import requests  
from bs4 import BeautifulSoup  
url = 'http://www.esjson.com/urlEncode.html'  
response = requests.get(url)  
soup = BeautifulSoup(response.content, 'html.parser')  # soup返回的是一个html文件
print(soup)
# 提取你需要的内容(获取文本内容)  
content = soup.get_text()  # 或者根据需求提取特定元素

文本切分 / 分块方案

# dataconnecters在默认解析时一个文档会保留成一个node节点
# 文本切割太大
    上下文丢失:过大的文本片段可能包含多个主题,使得模型难以理解特定上下文,导致答案不精准
    计算资源消耗:处理大文本块需要更多的计算资源,可能导致效率低下,响应时间延长
    信息冗余:大文本可能包含不必要的信息,使模型的注意力分散,从而影响关键信息的提取
# 文本切割太小
    上下文不足:过小的文本片段可能缺乏必要的上下文,导致模型误解文本的意思
    频繁拆分:切割过于细碎会导致模型在理解多段信息时出现困难,尤其是在推理和整合信息时
    模型性能下降:太小的切割可能使得训练时数据样本稀疏,影响模型的学习效果和泛化能力
分块三要素
要素说明推荐值
块大小每段文字的长度200~500
块重叠相邻重复内容10%~20%
切分依据按句子 / 段落 / 语义划分语义分割最优
分块策略对比表
策略类型优点缺点使用场景
固定大小实现简单可能切断完整语义技术文档
按段落分割保持逻辑完整性段落长度差异大文学小说
语义分割确保内容完整性计算资源消耗大专业领域文档
分块常见问题:
  • 如何确定最佳块大小(测试不同尺寸查看检索效果)
# 测试块大小对召回率的影响
sizes = [128, 256, 512]
for size in sizes:
  test_recall = evaluate_chunk_size(size)  # evaluate_chunk_size评估块大小(自己编写该函数)
print(f"块大小{size} → 召回率{test_recall:.2f}%")
  • 分块重叠是否越多越好(适当重叠可防止信息断裂,过多会导致冗余)
from llama_index.core import SimpleDirectoryReader
# 加载文档
documents = SimpleDirectoryReader(input_files=["/aiproject/data/ai_development_history.txt"]).load_data()

# 使用固定节点分割
from llama_index.core.node_parser import TokenTextSplitter
fixed_splitter = TokenTextSplitter(chunk_size=256, chunk_overlap=20)
fixed_nodes = fixed_splitter.get_nodes_from_documents(documents)
print("固定分块示例:", [len(n.text) for n in fixed_nodes])  # 输出:[200, 200, 200]
print("第一个节点内容:\n", fixed_nodes[0].text)
print("第二个节点内容:\n", fixed_nodes[1].text)

# 使用句子分割器
from llama_index.core.node_parser import SentenceSplitter
splitter = SentenceSplitter(chunk_size=256)
nodes = splitter.get_nodes_from_documents(documents)
# 查看结果
print("固定分块示例:", [len(n.text) for n in nodes])
print("第一个节点内容:\n", nodes[0].text)
print("第二个节点内容:\n", nodes[1].text)
固定分块 vs 语义分块
# 案例1:固定分块
from llama_index.core.node_parser import TokenTextSplitter
fixed_splitter = TokenTextSplitter(chunk_size=200, chunk_overlap=20)
fixed_nodes = fixed_splitter.get_nodes_from_documents(docs)
print("固定分块示例:", [len(n.text) for n in fixed_nodes[:3]]) # 输出:[200, 200, 200] 结果可能不是固定的分块[120,78,90],结果跟所选的分词器有关
# 案例2:语义分块
from llama_index.core.node_parser import SemanticSplitterNodeParser
from llama_index.embeddings.huggingface import HuggingFaceEmbedding
semantic_splitter = SemanticSplitterNodeParser(
buffer_size=1,
embed_model=HuggingFaceEmbedding("BAAI/bge-small")
)
semantic_nodes = semantic_splitter.get_nodes_from_documents(docs)
print("语义分块示例:", [len(n.text) for n in semantic_nodes[:3]]) # 输出:[183,
217, 195]

实际落地:
优先根据自有文档结构,使用正则切分,实在不行使用人工切分

召回率提升方案

召回率介绍

真正例 (TP):能正确识别为正样本的数量

假负例 (FN):实际为正样本但被错误识别为负样本的数量​

召回率 = 真正例 /(真正例 + 假负例)

召回率:模型在所有真实正样本中正确识别出的比例

  • 高召回率:意味着模型能够识别更多的正样本,适用于需要尽量减少漏报的场景,比如医疗诊断
  • 低召回率:表示模型漏掉了很多正样本,可能导致重要信息的丢失
提升召回率的三大策略

查询扩展:给问题添加修饰词(获取跟多相关内容)

混合检索:结合关键词搜索和语义搜索
  混合检索通过结合不同搜索方法提高信息检索的全面性和相关性,但同时增加了系统复杂性和资源消耗
在这里插入图片描述

关键词搜索适用场景:用户在提问涉及到比较多专业术语,问题中专业术语不多可能效果不好

向量优化:微调模型使其更好的理解专业术语

效果验证方法
  1. 准备测试问题集(至少 50 个典型问题)

  2. 记录基础方案召回率

  3. 应用优化策略后再次测试

  4. 对比提升幅度

基础检索 vs 向量检索
# 案例1:基础(向量)检索
from llama_index.core import VectorStoreIndex
vector_index = VectorStoreIndex(nodes)
vector_retriever = vector_index.as_retriever(similarity_top_k=3)
print("向量检索结果:", [node.text[:30] for node in vector_retriever.retrieve(query)])
# 案例2:混合检索
from llama_index.core import KeywordTableIndex
keyword_retriever = KeywordTableIndex(nodes).as_retriever(retriever_mode="bm25",
similarity_top_k=3)
from llama_index.core.retrievers import QueryFusionRetriever
fusion_retriever = QueryFusionRetriever([vector_retriever, keyword_retriever])
print("混合检索结果:", [node.text[:30] for node in
fusion_retriever.retrieve(query)])

检索结果重排序(Rerank 模型)

常见 Rerank 模型
模型名称速度精度硬件要求适用场景
BM25快中低关键词搜索任务
Cross-Encoder慢高高小规模精准排序 (较小规模数据集)
ColBERT中高中速度与精度要保持平衡 (较大规模数据集)
无排序 vs Cohere Reranker
# 初始检索结果(按相似度排序):
results = [
"模型正则化方法简述", # 相关度0.7
"硬件加速技术进展", # 相关度0.65
"过拟合解决方案详解", # 相关度0.92 ← 正确答案
"数据集清洗方法"
]
# 应用重排序
from llama_index.postprocessor.cohere_rerank import CohereRerank
reranker = CohereRerank(api_key="YOUR_KEY", top_n=2)
reranked_results = reranker.postprocess_nodes(results, query_str=query)
print("重排序后结果:", [res.text for res in reranked_results])
排序变化对比
原始排序:
1. 模型正则化方法简述(相关度0.7)
2. 硬件加速技术进展(相关度0.65)
3. 过拟合解决方案详解(相关度0.92)← 正确答案
4. 数据集清洗方法
重排序后:
1. 过拟合解决方案详解(评分0.95)← 正确答案
2. 模型正则化方法简述(评分0.88)

ReRank 模型位于 RAG 检索与生成模型之间,负责对检索出的文本进行重排序,从而提高相关性和准确度

Rerank 模型与 Embedding 模型对比
方面Rerank 模型Embedding 模型
目标对已有检索结果进行重新排序,提高结果相关性将文本(词、句子、文档等)转换为向量,方便相似度计算和下游任务
输入初步检索得到的一组候选结果和查询原始文本(query、document)
输出按相关性重新排序的候选列表向量表示(固定长度的实数向量)
使用方法通常基于深度学习模型对候选进行打分,如 BERT 做 pair-wise 或 point-wise 排序通过训练得到固定或动态的向量,用于计算相似度、聚类、分类等
技术区别重点是 fine-tune 排序任务,输入 query 和候选一起,生成相关性分数重点是学习语义表示,向量捕捉文本语义和句法特征
应用场景搜索引擎的二级排序、问答系统答案排序语义检索、推荐系统、文本分类、聚类、相似度检索

完整流程

from llama_index.embeddings.huggingface import HuggingFaceEmbedding
from llama_index.core import Settings, VectorStoreIndex
from llama_index.llms.huggingface import HuggingFaceLLM
from llama_index.core.schema import TextNode
import json
import torch

# 初始化本地模型
# 1. 初始化本地模型
def setup_local_models():
    # 设置本地embedding模型
    embed_model = HuggingFaceEmbedding(
        model_,
        device="cuda" if torch.cuda.is_available() else "cpu"
    )

    # 设置本地LLM模型
    llm = HuggingFaceLLM(
        model_,
        tokenizer_,
        model_kwargs={"trust_remote_code": True},
        tokenizer_kwargs={"trust_remote_code": True},
        device_map="auto",
        generate_kwargs={"temperature": 0.3, "do_sample": True}  # 修改为do_sample=True避免警告
    )

    # 全局设置
    Settings.embed_model = embed_model
    Settings.llm = llm
    Settings.chunk_size = 512

# 2. 加载数据并处理格式
def load_data(file_path):
    with open(file_path, 'r', encoding='utf-8') as f:
        data = json.load(f)

    nodes = []
    for item in data:
        if isinstance(item, dict):
            # 处理DPR格式数据
            if 'query' in item and 'positive_passages' in item:
                text = f"查询: {item['query']}\n相关文档: {item['positive_passages'][0]['text']}"
            # 处理QA对格式
            elif 'question' in item and 'answer' in item:
                text = f"问题: {item['question']}\n答案: {item['answer']}"
            else:
                continue
        elif isinstance(item, str):
            text = item
        else:
            continue

        node = TextNode(text=text)
        nodes.append(node)

    return nodes

# 3. 初始化本地模型
setup_local_models()

# 4. 加载数据
data_path = "/home/cw/projects/demo_19/data/qa_pairs.json"
nodes = load_data(data_path)

# 5. 示例查询
query = "如何预防机器学习模型过拟合?"

# 案例1:向量检索(使用本地embedding模型)
vector_index = VectorStoreIndex(nodes)
vector_retriever = vector_index.as_retriever(similarity_top_k=3)
print("向量检索结果:", [node.text[:50] + "..." for node in vector_retriever.retrieve(query)])

# 案例2:关键词检索(不使用bm25模式)
from llama_index.core import KeywordTableIndex
keyword_index = KeywordTableIndex(nodes)
keyword_retriever = keyword_index.as_retriever(similarity_top_k=3)  # 使用默认模式
print("关键词检索结果:", [node.text[:50] + "..." for node in keyword_retriever.retrieve(query)])

# 案例3:查询引擎(使用本地LLM生成回答)
query_engine = keyword_index.as_query_engine()
response = query_engine.query(query)
print("LLM生成回答:", response)

完整流程

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐