RAG 高效召回进阶方法全解析:查询扩展 / 索引扩展 / Small-to-Big

本文将系统拆解 RAG 召回环节的核心进阶策略 ——优化查询扩展(含双向改写)、索引扩展、Small-to-Big 索引策略,结合原理、场景、落地逻辑,讲清每种方法的核心价值和适配场景,同时提供可落地的代码示例(chatpdf-faiss-MultiQueryRetriever)。

一、优化查询扩展:从 “单一查询” 到 “多维度意图覆盖”

查询扩展的核心是解决 “用户查询短、表述单一、语义不充分” 导致的召回漏检问题,核心分为「多查询召回」和「双向改写」两类,本质都是 “丰富查询的语义表达”。

1. 基础版:MultiQueryRetriever(多相似查询召回)

核心逻辑

用大模型将用户原始查询改写为多个语义等价、表述不同的查询,分别召回文档后合并去重,避免因用户表述习惯导致的漏召回。

  • 适配场景:短查询、模糊查询(如 “客户经理考核标准”“如何优化深度学习训练”)。
  • 工具:LangChain 的MultiQueryRetriever(开箱即用)。
落地代码:chatpdf-faiss-MultiQueryRetriever.py
import os
from langchain_community.vectorstores import FAISS
from langchain_community.document_loaders import PyPDFLoader
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain_community.embeddings import HuggingFaceEmbeddings
from langchain.retrievers.multi_query import MultiQueryRetriever
from langchain.chat_models import ChatOpenAI
from langchain.prompts import PromptTemplate
from langchain.chains import RetrievalQA

# -------------------------- 1. 环境配置 --------------------------
# 配置OpenAI API Key(或替换为国产大模型,如智谱、通义千问)
os.environ["OPENAI_API_KEY"] = "your-api-key"
# 配置本地模型路径(BGE嵌入模型,适配中文)
EMBEDDING_MODEL_PATH = "BAAI/bge-base-zh-v1.5"

# -------------------------- 2. 加载PDF并构建向量库 --------------------------
def load_pdf_and_build_faiss(pdf_path, faiss_save_path):
    """加载PDF文档,拆分后构建FAISS向量库"""
    # 1. 加载PDF
    loader = PyPDFLoader(pdf_path)
    docs = loader.load()
    
    # 2. 文本拆分(适配长文档,避免向量截断)
    text_splitter = RecursiveCharacterTextSplitter(
        chunk_size=500,  # 每个chunk 500字
        chunk_overlap=50,  # 重叠50字,保证上下文连贯
        separators=["\n\n", "\n", "。", "!", "?", ",", ""]
    )
    split_docs = text_splitter.split_documents(docs)
    
    # 3. 初始化嵌入模型
    embeddings = HuggingFaceEmbeddings(
        model_name=EMBEDDING_MODEL_PATH,
        model_kwargs={"device": "cpu"},  # 若有GPU,改为"cuda"
        encode_kwargs={"normalize_embeddings": True}  # 归一化向量,提升相似度计算精度
    )
    
    # 4. 构建FAISS向量库并保存
    vector_db = FAISS.from_documents(split_docs, embeddings)
    vector_db.save_local(faiss_save_path, allow_dangerous_deserialization=True)
    return vector_db

# -------------------------- 3. 初始化MultiQueryRetriever --------------------------
def init_multi_query_retriever(vector_db):
    """初始化多查询召回器"""
    # 1. 初始化大模型(用于生成多查询)
    llm = ChatOpenAI(
        model_name="gpt-3.5-turbo",
        temperature=0,  # 0温度保证改写的语义一致性
        max_tokens=500
    )
    
    # 2. 自定义多查询生成提示词(适配中文场景)
    MULTI_QUERY_PROMPT = PromptTemplate(
        input_variables=["question"],
        template="""你是一个专业的检索助手,需要将用户的查询改写成3个语义相同但表述不同的中文查询,用于文档检索。
        要求:
        1. 改写后的查询需覆盖用户核心意图,不偏离主题;
        2. 表述方式多样化(比如同义词替换、句式调整);
        3. 仅输出改写后的查询,每行一个,无其他多余内容。
        用户原始查询:{question}"""
    )
    
    # 3. 创建MultiQueryRetriever
    retriever = MultiQueryRetriever.from_llm(
        retriever=vector_db.as_retriever(search_kwargs={"k": 5}),  # 每个查询召回5篇
        llm=llm,
        prompt=MULTI_QUERY_PROMPT,
        number_of_queries=3  # 生成3个改写查询
    )
    return retriever

# -------------------------- 4. 构建RAG问答链 --------------------------
def build_qa_chain(retriever):
    """构建检索增强问答链"""
    llm = ChatOpenAI(model_name="gpt-3.5-turbo", temperature=0.1)
    qa_chain = RetrievalQA.from_chain_type(
        llm=llm,
        chain_type="stuff",  # 简单场景用stuff,复杂场景用map_reduce
        retriever=retriever,
        return_source_documents=True  # 返回召回的源文档,方便验证
    )
    return qa_chain

# -------------------------- 5. 主函数:执行问答 --------------------------
if __name__ == "__main__":
    # 配置路径
    PDF_PATH = "your_pdf_file.pdf"  # 替换为你的PDF文件路径
    FAISS_SAVE_PATH = "./faiss_pdf_db"  # FAISS向量库保存路径
    
    # 1. 构建/加载向量库
    if os.path.exists(FAISS_SAVE_PATH):
        # 加载已存在的向量库
        embeddings = HuggingFaceEmbeddings(
            model_name=EMBEDDING_MODEL_PATH,
            model_kwargs={"device": "cpu"},
            encode_kwargs={"normalize_embeddings": True}
        )
        vector_db = FAISS.load_local(
            FAISS_SAVE_PATH,
            embeddings,
            allow_dangerous_deserialization=True
        )
        print("✅ 加载已存在的FAISS向量库")
    else:
        # 新建向量库
        vector_db = load_pdf_and_build_faiss(PDF_PATH, FAISS_SAVE_PATH)
        print("✅ 新建FAISS向量库完成")
    
    # 2. 初始化多查询召回器
    multi_query_retriever = init_multi_query_retriever(vector_db)
    
    # 3. 构建问答链
    qa_chain = build_qa_chain(multi_query_retriever)
    
    # 4. 执行问答
    user_query = "如何提高深度学习模型的训练效率?"
    result = qa_chain({"query": user_query})
    
    # 5. 输出结果
    print("\n===== 问答结果 =====")
    print(result["result"])
    print("\n===== 召回的源文档 =====")
    for i, doc in enumerate(result["source_documents"]):
        print(f"\n【文档{i+1}】")
        print(f"内容:{doc.page_content[:200]}...")
        print(f"来源:{doc.metadata['source']} 页码:{doc.metadata['page']}")
代码说明
  • 支持 PDF 加载、文本拆分、FAISS 向量库构建 / 加载;
  • 自定义中文多查询提示词,适配中文语义改写;
  • 返回源文档信息,方便验证召回效果;
  • 可替换大模型(如替换为智谱 GLM-4、通义千问),只需调整llm初始化逻辑。

2. 进阶版:双向改写(Query2Doc + Doc2Query)

核心解决 “短文本向量化效果差” 的问题 —— 通过 “查询→文档” 或 “文档→查询” 的双向改写,丰富语义信息,提升召回精度。

(1)Query2Doc:将短查询改写为长文档
  • 核心逻辑:用户查询通常是短文本(如 “如何提高训练效率?”),向量化时语义信息不足,将查询改写为一段完整的、包含核心意图的长文本,再做向量检索,提升匹配精度。
  • 改写示例:原始查询:“如何提高深度学习模型的训练效率?”改写后文档:
    提高深度学习模型的训练效率可以从以下几个方面入手:
    1. 使用更高效的优化算法,如AdamW或LAMB;
    2. 采用混合精度训练,减少显存占用并加速计算;
    3. 使用分布式训练技术,如数据并行或模型并行;
    4. 调整学习率调度策略,避免训练震荡;
    5. 对数据进行预处理,减少冗余计算。
    
  • 落地方式:
    1. 用大模型生成改写文档(提示词:“将以下查询扩展为一段完整的、包含核心解决方案的文档,用于向量检索:{query}”);
    2. 将改写后的文档向量化,与知识库文档向量匹配,召回相关内容。
(2)Doc2Query:为文档生成关联查询
  • 核心逻辑:对知识库中的短文档 / 文档片段,生成多个用户可能的查询语句,将这些查询作为文档的 “补充索引”,当用户查询匹配这些生成的查询时,可快速召回文档。
  • 改写示例:文档内容:“本文介绍了深度学习训练的优化技巧,包括 AdamW 优化器、混合精度训练、分布式训练。”生成查询:
    1. 如何选择深度学习模型的优化器?
    2. 混合精度训练有哪些优势?
    3. 分布式训练如何加速深度学习?
    4. 如何减少深度学习训练的显存占用?
  • 落地方式:
    1. 构建向量库时,对每个文档片段生成 5-10 个关联查询;
    2. 将 “文档原文 + 生成的查询” 合并后向量化,或单独为生成的查询建立索引;
    3. 用户查询时,同时匹配文档向量和生成查询向量,提升召回率。
双向改写的核心价值
  • 解决 “短文本语义稀疏” 问题:短查询 / 短文档向量化时,因信息少导致相似度计算偏差,改写后补充语义信息;
  • 提升召回精准度:改写后的文本更贴近知识库文档的表述方式,匹配度更高。

二、索引扩展:从 “单一向量索引” 到 “多维度索引互补”

索引扩展的核心是 “不依赖单一向量索引”,通过构建离散索引(关键词 / 实体)、多模型连续索引、混合索引,覆盖不同召回场景,提升准确性和覆盖率。

1. 离散索引扩展:关键词抽取 + 实体识别

离散索引是 “非向量” 的索引方式,通过提取文档的 “关键词 / 实体” 作为标签,补充向量检索的不足(向量检索可能忽略字面精准匹配)。

(1)关键词抽取
  • 核心逻辑:从文档中提取核心关键词(如 TF-IDF、TextRank 算法),作为离散索引,当用户查询包含这些关键词时,快速匹配文档。
  • 示例:文档内容:“本文介绍了深度学习训练的优化技巧,包括 AdamW 优化器、混合精度训练、分布式训练。”提取关键词:["深度学习", "模型训练", "优化技巧", "AdamW", "混合精度训练", "分布式训练"]
  • 落地方式:
    1. 构建向量库时,对每个文档片段抽取关键词,存储到文档元数据(metadata)中;
    2. 用户查询时,先抽取查询关键词,过滤出元数据中包含匹配关键词的文档,再做向量检索(缩小召回范围,提升精度)。
(2)实体识别
  • 核心逻辑:提取文档中的命名实体(时间、地点、人物、组织、专有名词等),作为离散索引,增强 “实体类查询” 的召回精度。
  • 示例:文档内容:“2023 年诺贝尔物理学奖授予了三位科学家,表彰他们在量子纠缠领域的研究。”提取实体:["2023 年", "诺贝尔物理学奖", "量子纠缠"]
  • 落地工具:SpaCy(多语言 NER)、HanLP(中文 NER)、BERT-NER(自定义领域 NER)。
  • 落地价值:解决 “实体类查询漏召回” 问题,比如用户问 “2023 年诺贝尔物理学奖获奖者”,实体索引可快速定位相关文档,而向量检索可能因表述差异漏检。

2. 连续索引扩展:多路向量模型召回

  • 核心逻辑:不同向量模型(如 OpenAI Ada、智源 BGE、百度 ERNIE)对文本的语义编码侧重不同,用多个模型分别构建向量索引,并行召回后合并结果,取长补短。
  • 示例:
    • OpenAI Ada:擅长通用语义匹配;
    • BGE-base-zh:擅长中文语义匹配;
    • 领域微调模型(如医疗 BGE):擅长垂直领域语义匹配。
  • 落地方式:
    1. 用 3 个不同模型分别构建 FAISS 向量库;
    2. 用户查询时,用 3 个模型分别向量化并召回 Top 5 文档;
    3. 合并结果去重,进入重排序环节。

3. 混合索引召回:离散索引 + 连续索引

  • 核心逻辑:将 “离散索引(关键词 / 实体)” 与 “连续索引(向量)” 结合,通过Ensemble Retriever(集成召回器)实现混合召回,兼顾 “字面精准匹配” 和 “语义相似匹配”。
  • 示例流程(用户查询:“人工智能在医疗领域的应用有哪些?”):
    1. 离散索引召回:匹配关键词 ["人工智能", "医疗领域", "深度学习", "医学影像分析"] 的文档;
    2. 向量索引召回:匹配语义相似的文档;
    3. 合并结果:去重后得到 “精准 + 语义” 的召回列表,再通过重排序模型筛选 Top N。
  • 落地工具:LangChain 的EnsembleRetriever(支持组合多个召回器)。
混合索引召回代码示例
from langchain.retrievers import EnsembleRetriever
from langchain_community.retrievers import BM25Retriever  # 离散索引(关键词)
from langchain_community.vectorstores import FAISS
from langchain_community.embeddings import HuggingFaceEmbeddings

# 1. 初始化BM25召回器(离散索引,关键词匹配)
bm25_retriever = BM25Retriever.from_documents(split_docs)  # split_docs为拆分后的文档
bm25_retriever.k = 5  # 召回5篇

# 2. 初始化FAISS向量召回器(连续索引,语义匹配)
embeddings = HuggingFaceEmbeddings(model_name="BAAI/bge-base-zh-v1.5")
vector_retriever = FAISS.from_documents(split_docs, embeddings).as_retriever(search_kwargs={"k": 5})

# 3. 构建混合召回器
ensemble_retriever = EnsembleRetriever(
    retrievers=[bm25_retriever, vector_retriever],
    weights=[0.4, 0.6]  # BM25权重40%,向量权重60%,可根据场景调整
)

# 4. 执行召回
docs = ensemble_retriever.get_relevant_documents("人工智能在医疗领域的应用有哪些?")

三、Small-to-Big 索引策略:长文档 / 多文档的高效召回

Small-to-Big 是针对 “长文档(如 PDF 论文、万字报告)” 的召回优化策略,核心是 “先检索小规模摘要 / 关键句,再链接到完整文档”,兼顾召回效率和上下文完整性。

1. 核心原理

将文档分为 “小规模索引内容” 和 “大规模主体内容”,通过 “先小后大” 的检索逻辑,解决长文档向量化效率低、召回精度差的问题:

  • 小规模内容(索引层):文档摘要、关键句、核心段落(短文本,易索引、检索快);
  • 大规模内容(主体层):完整文档、全文 PDF(长文本,包含详细信息)。

2. 核心流程

用户查询 → 检索小规模内容(摘要/关键句)→ 匹配到相关条目 → 链接到对应的大规模内容 → 提取完整上下文 → 生成答案

3. 关键环节拆解

(1)小规模内容构建
  • 摘要生成:用大模型 / 文本摘要算法(如 TextRank)提取文档摘要(每篇文档 1-2 段);
  • 关键句提取:提取文档中与核心主题相关的句子(如 “Transformer 通过自注意力实现并行计算”);
  • 段落拆分:将长文档拆分为核心段落(如每个段落对应一个子主题)。
(2)索引构建
  • 为小规模内容构建 “混合索引”(关键词 + 向量),保证检索速度和精度;
  • 建立小规模内容与大规模内容的映射关系(如文档 ID、页码、URL)。
(3)检索与链接
  • 先在小规模内容中检索,快速定位相关文档的 “入口”;
  • 通过映射关系找到对应的完整文档,提取所需上下文;
  • 仅将 “小规模内容 + 匹配的完整段落” 传入生成环节,避免冗余信息。

4. 核心优势

  • 效率高:小规模内容检索速度比全文检索快 10-100 倍,适配百万级文档库;
  • 精度高:摘要 / 关键句是文档核心,检索时能精准匹配用户意图;
  • 上下文完整:链接到完整文档后,可提取详细信息,保证答案的丰富性。

5. 落地示例(论文库检索)

  • 小规模内容:每篇论文的摘要、核心结论句、实验关键指标;
  • 大规模内容:论文全文 PDF、实验数据、公式推导;
  • 用户查询:“Transformer 在机器翻译中的改进方法”;
  • 检索流程:
    1. 检索小规模内容,匹配到 “Transformer 改进注意力机制的机器翻译论文摘要”;
    2. 链接到该论文的完整 PDF;
    3. 提取 “改进注意力机制” 的相关章节,作为上下文生成答案。

四、各策略适配场景与选型建议

策略类型核心优势适配场景注意事项
MultiQueryRetriever简单易落地,覆盖多表述短查询、模糊查询、通用场景避免生成过多查询(3-5 个为宜),防止冗余
双向改写解决短文本语义稀疏短查询 / 短文档、垂直领域(如医疗 / 金融)改写需保证语义一致性,避免偏离主题
离散索引扩展字面精准匹配,检索快实体查询、术语查询、精准匹配场景需维护关键词 / 实体词典,适配领域术语
混合索引召回兼顾精准与语义通用场景、多类型查询混合的场景权重需调优(如 BM25: 向量 = 4:6)
Small-to-Big高效处理长文档 / 多文档论文库、PDF 报告库、万字长文档检索需做好小规模内容与大规模内容的映射

五、总结

RAG 高效召回的核心是 “兼顾全面性与精准性”:

  1. 查询扩展解决 “意图覆盖不足” 问题,通过多查询、双向改写丰富语义;
  2. 索引扩展解决 “单一索引局限” 问题,通过离散 + 连续索引互补;
  3. Small-to-Big 解决 “长文档检索效率低” 问题,通过 “先小后大” 平衡速度与精度。

落地时建议按 “基础→进阶” 的顺序:先实现 MultiQueryRetriever + 混合索引,再根据场景补充双向改写 / Small-to-Big,最后通过重排序模型(如 BGE-Rerank)优化最终结果,可大幅提升 RAG 的召回质量和生成效果。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

心态特好

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值