大模型架构记录【RAG优化】

发布于:2025-03-25 ⋅ 阅读:(27) ⋅ 点赞:(0)

一 RAG回顾

RAG(Retrieval-Augmented Generation)的改进方案的提纲。RAG是一种结合了检索和生成的技术,用于提高问答系统的性能。以下是对提纲内容的简单分析:

  1. RAG回顾以及有可能存在的问题:

    • 文档的读取的准确性,及数据清洗的逻辑。

    • 拆分文档也比较麻烦,拆分为chunk

    • query 的不完整性,提问问题的处理

    • 搜索相似的文档,比较 向量的相似度,搜索结果是否能满足回复问题的背景信息。

    • 是否对搜索文档做排序,怎么排序

    • 是否需要 对模型回复的结果做 post-process? 

  2. RAG的改进方案

    • 这一部分详细列出了四个改进方案:

      • Query的改造:可能涉及对查询语句的优化,使其更有效地检索相关信息。

      • Retriever的改造:可能指的是对检索器的改进,以提高检索结果的相关性和准确性。

      • Ranking的改造:可能涉及对检索结果进行重新排序,以确保最相关的信息排在前面。

      • RAGAS:RAG评估:可能是指对RAG系统进行评估的方法或工具,以衡量其性能和改进效果。

  3. Advanced RAG + 实战项目讲解

    • 这一部分可能会介绍更高级的RAG技术,以及如何在实际项目中应用这些技术。这可能包括具体的案例分析和最佳实践。


二 RAG优化

2.1 Self-querying retrieval

是RAG优化中的一种技术,它涉及到自我查询检索的过程。以下是对这一概念的简单解读:

  • Self-querying:自我查询是一种策略,其中 模型会生成自己的查询来检索信息。这意味着模型不仅仅是被动地接收输入查询,而是能够主动地生成查询以获取更相关的信息。

  • Retrieval检索是指从大量数据中找到与查询最相关的信息的过程。在RAG模型中,检索器负责从文档集合中找到与输入查询最相关的文档片段。

  • Self-querying retrieval in RAG:在RAG模型中,自我查询检索可能涉及到模型在生成答案的过程中,根据当前的上下文和已检索到的信息,动态地生成新的查询来进一步检索更精确的信息。这种方法可以帮助模型更深入地理解问题,并从文档中检索到更相关的内容,从而提高生成答案的质量。

2.1.1 写入数据库
from langchain_community.vectorstores import Chroma
from langchain_core.documents import Document
from langchain_openai import OpenAIEmbeddings

docs = [
    Document(
        page_content="A bunch of scientists bring back dinosaurs and mayhem breaks loose",
        metadata={"year": 1993, "rating": 7.7, "genre": "science fiction"},
    ),
    Document(
        page_content="Leo DiCaprio gets lost in a dream within a dream within a dream within a ...",
        metadata={"year": 2010, "director": "Christopher Nolan", "rating": 8.2},
    ),
    Document(
        page_content="A psychologist / detective gets lost in a series of dreams within dreams within dreams and Inception reused the idea",
        metadata={"year": 2006, "director": "Satoshi Kon", "rating": 8.6},
    ),
    Document(
        page_content="A bunch of normal-sized women are supremely wholesome and some men pine after them",
        metadata={"year": 2019, "director": "Greta Gerwig", "rating": 8.3},
    ),
    Document(
        page_content="Toys come alive and have a blast doing so",
        metadata={"year": 1995, "genre": "animated"},
    ),
    Document(
        page_content="Three men walk into the Zone, three men walk out of the Zone",
        metadata={
            "year": 1979,
            "director": "Andrei Tarkovsky",
            "genre": "thriller",
            "rating": 9.9,
        },
    ),
]
vectorstore = Chroma.from_documents(docs, OpenAIEmbeddings())
2.1.2 自查询
from langchain.chains.query_constructor.base import AttributeInfo
from langchain.retrievers.self_query.base import SelfQueryRetriever
from langchain_openai import ChatOpenAI

metadata_field_info = [
    AttributeInfo(
        name="genre",
        description="The genre of the movie. One of ['science fiction', 'comedy', 'drama', 'thriller', 'romance', 'action', 'animated']",
        type="string",
    ),
    AttributeInfo(
        name="year",
        description="The year the movie was released",
        type="integer",
    ),
    AttributeInfo(
        name="director",
        description="The name of the movie director",
        type="string",
    ),
    AttributeInfo(
        name="rating", description="A 1-10 rating for the movie", type="float"
    ),
]
document_content_description = "Brief summary of a movie"
#llm = ChatOpenAI(temperature=0)
retriever = SelfQueryRetriever.from_llm(
    llm,
    vectorstore,
    document_content_description,
    metadata_field_info,
)

# This example only specifies a filter
retriever.invoke("I want to watch a movie rated higher than 8.5")

2.2  parent-child splitting and retrieval

Parent-Child Splitting and Retrieval 是一种文档检索技术,它通过将文档分割成多级结构的文本块来提高检索精度和效率。这种方法特别适用于处理大规模和复杂的信息,如电子商务、知识管理、法律和医疗文献检索等领域。

具体来说,该技术包含以下几个步骤:

  1. 文档分割:首先,将文档分割成较大的文本块,称为Parent Chunks,每个Parent Chunk代表文档中的一个较大部分,通常包含多个段落或章节。然后,每个Parent Chunk进一步细分为更小的块,称为Child Chunks,每个Child Chunk通常包含一个段落或几句话。

  2. 向量嵌入:使用预训练的模型(例如BERT、GPT等)将每个Child Chunk转换为向量,这些向量代表了文本块的语义信息,可以通过向量检索算法来进行比对和匹配。

  3. 保持关联关系:Child Chunks与其对应的Parent Chunk保持关联,在后续的检索过程中可以对Parent Chunk进行合并,确保用户获取的信息上下文连贯。

from langchain.retrievers import ParentDocumentRetriever

from langchain.storage import InMemoryStore
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain_community.document_loaders import TextLoader
from langchain_community.vectorstores import Chroma
from langchain_openai import OpenAIEmbeddings

loaders = [
    TextLoader("example_data/FDR_State_of_Union_1944.txt"),
    TextLoader("example_data/Lincoln_State_of_Union_1862.txt"),
]
docs = []
for loader in loaders:
    docs.extend(loader.load())

# This text splitter is used to create the parent documents
parent_splitter = RecursiveCharacterTextSplitter(chunk_size=2000)
# This text splitter is used to create the child documents
# It should create documents smaller than the parent
child_splitter = RecursiveCharacterTextSplitter(chunk_size=400)
# The vectorstore to use to index the child chunks
vectorstore = Chroma(
    collection_name="split_parents", embedding_function=OpenAIEmbeddings()
)
# The storage layer for the parent documents
store = InMemoryStore()

retriever = ParentDocumentRetriever(
    vectorstore=vectorstore,
    docstore=store,
    child_splitter=child_splitter,
    parent_splitter=parent_splitter,
)

retriever.add_documents(docs)

sub_docs = vectorstore.similarity_search("justice breyer")

print(sub_docs[0].page_content)

retrieved_docs = retriever.get_relevant_documents("justice breyer")
len(retrieved_docs[0].page_content)

2.3 Multiquery Retriever

MultiQueryRetriever是一种检索技术,它通过使用大型语言模型(LLM)从不同角度为给定的用户输入查询生成多个查询,从而自动化提示调优的过程。对于每个查询,它检索一组相关文档,并在所有查询中取唯一的并集,以获取更大的一组潜在相关文档。通过对同一问题生成多个视角,MultiQueryRetriever可以减轻基于距离的检索的一些局限性,并获得更丰富的结果集。

具体来说,MultiQueryRetriever的工作原理如下:

  1. 接收用户输入的查询。

  2. 使用LLM生成多个相关但不同的查询。

  3. 对每个生成的查询进行向量数据库检索。

  4. 合并所有检索结果,去重后返回。

# Build a sample vectorDB
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain_community.document_loaders import WebBaseLoader
from langchain_community.vectorstores import Chroma
from langchain_openai import OpenAIEmbeddings

# Load blog post
loader = WebBaseLoader("https://lilianweng.github.io/posts/2023-06-23-agent/")
data = loader.load()

# Split
text_splitter = RecursiveCharacterTextSplitter(chunk_size=500, chunk_overlap=0)
splits = text_splitter.split_documents(data)

# VectorDB
embedding = OpenAIEmbeddings()
vectordb = Chroma.from_documents(documents=splits, embedding=embedding)

from langchain.retrievers.multi_query import MultiQueryRetriever
from langchain_openai import ChatOpenAI

question = "What are the approaches to Task Decomposition?"
llm = ChatOpenAI(temperature=0)
retriever_from_llm = MultiQueryRetriever.from_llm(
    retriever=vectordb.as_retriever(), llm=llm
)

unique_docs = retriever_from_llm.get_relevant_documents(query=question)
len(unique_docs)   # 5

2.4 Ensemble Retriever

Ensemble Retriever是一种检索工具,它能够集成多个基础检索器(Base Retriever)的结果,通过结合每个检索器的输出来提升整体的检索效果。这种方法利用了不同检索算法的优势,例如将稀疏检索器(如BM25)与密集检索器(如基于向量嵌入的检索)结合起来,因为它们的优势互补,这也被称为"混合搜索"。稀疏检索器擅长通过关键字找到相关文档,而密集检索器则擅长通过语义相似性找到相关文档。

EnsembleRetriever通过初始化一个包含多个BaseRetriever对象的列表,并使用Reciprocal Rank Fusion算法对这些检索器的结果进行重排序。最常见的模式是结合稀疏检索器和密集检索器,因为它们在不同场景下表现出不同的优势。

# !pip install rank_bm25
from langchain.retrievers import BM25Retriever, EnsembleRetriever
from langchain_community.vectorstores import FAISS
from langchain_openai import OpenAIEmbeddings

doc_list_1 = [
    "I like apples",
    "I like oranges",
    "Apples and oranges are fruits",
]

# initialize the bm25 retriever and faiss retriever
bm25_retriever = BM25Retriever.from_texts(
    doc_list_1, metadatas=[{"source": 1}] * len(doc_list_1)
)
bm25_retriever.k = 2

doc_list_2 = [
    "You like apples",
    "You like oranges",
]

embedding = OpenAIEmbeddings()
faiss_vectorstore = FAISS.from_texts(
    doc_list_2, embedding, metadatas=[{"source": 2}] * len(doc_list_2)
)
faiss_retriever = faiss_vectorstore.as_retriever(search_kwargs={"k": 2})

# initialize the ensemble retriever
ensemble_retriever = EnsembleRetriever(
    retrievers=[bm25_retriever, faiss_retriever], weights=[0.5, 0.5]
)

docs = ensemble_retriever.invoke("apples")
docs