从 LangChain 中学习检索增强

发布于:2024-06-07 ⋅ 阅读:(134) ⋅ 点赞:(0)

前言

之前讲了一些关于 RAG 的使用技巧和经验,今天我们提供两个进阶版的文本切分和检索的方法,希望对你有所帮助。以下两种方法取自 LangChain 的官方示例,感兴趣也可以直接去阅读官方文档。

一、父文档检索 Parent Document Retriever

当我们去切分文本的时候,会有两方面的考虑:

细粒度的文档块,这样在使用向量模型进行编码的时候,就能更好的表示这个文档块的语义含义,而且这个文档块所含的噪音数据会更少

粗粒度的文档块,当我们召回后需要调用大模型进行生成回答,细粒度的文档块会丢失一些上下文信息,有一定的语义损失

LangChain 中的 Parent Document Retriever 就相当于结合了不同粒度的文本块去构建检索过程,具体的实现流程如下:

首先就是使用两个文本分割器去将文本切分为父文档块和子文档块,然后建立向量存储区存储子块,建立内存存储区存储父块。之后我们需要创建 Parent Document Retriever,将上面定义好的分割器、存储器,并执行add_documents 方法将文档添加到检索器中。

在使用的时候,调用 get_relevant_documents 方法,这个时候实际上会调用向量检索返回子块的 ID,然后根据子块ID 将对应父块的内容返回给用户。

下面是示例代码

from langchain.retrievers import ParentDocumentRetriever
from langchain.vectorstores import Chroma
from langchain.embeddings import OpenAIEmbeddings
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain.storage import InMemoryStore
from langchain.document_loaders import TextLoader

loaders = [
    TextLoader('../../paul_graham_essay.txt'),
    TextLoader('../../state_of_the_union.txt'),
]
docs = []
for l in loaders:
    docs.extend(l.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)

retrieved_docs = retriever.get_relevant_documents("justice breyer")

二、多向量检索 MultiVector Retriever

父文档检索的思路是,我利用相似度召回了一个语义上相似的小块,然后返回一个能提供完整上下文信息的大块,这样可以帮助模型更好的理解上下文的语义,进行高质量的回答。

而多向量检索的思路是为同一篇文档提供不同视角的向量建模,比如:

分割文档,就是将文档切分为不同大小的块,然后分别构建向量

摘要,为每个文档创建摘要,将其与文档一起嵌入(或者替代文档);可以利用大模型进行文档总结,输出摘要内容。摘要一般包含了整篇文档的语义信息,但是会比原文要更精简。当然,使用大模型进行总结的时候,需要对大模型效果进行评估,正常来说 6B、7B 大小的模型效果不会太好。

假设性问题:为每个文档创建适合回答的假设性问题,将其与文档一起嵌入。这个是一个典型的逆向思维的例子,就是根据现在有的这篇文档,先让大模型提出几个这篇文档能够解答的问题,然后将这些问题编码成向量进行存储。这其实基于一个假设就是如果一篇文章能回答某一个问题,那么也可以回答与其相似的问题,当然,这个假设在一般情况下都是成立的,因此可以拿来使用。

import uuid
from langchain.retrievers.multi_vector import MultiVectorRetriever
from langchain.vectorstores import Chroma
from langchain.embeddings import OpenAIEmbeddings
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain.storage import InMemoryStore
from langchain.document_loaders import TextLoader
from langchain.chat_models import ChatOpenAI
from langchain.prompts import ChatPromptTemplate
from langchain.schema.output_parser import StrOutputParser
from langchain.schema.document import Document
from langchain.output_parsers.openai_functions import JsonKeyOutputFunctionsParser
def func1():
    # 方法1 分割文档,生成更小的组块
    child_text_splitter = RecursiveCharacterTextSplitter(chunk_size=400)
    sub_docs = []
    for i, doc in enumerate(docs):
        _id = doc_ids[i]
        _sub_docs = child_text_splitter.split_documents([doc])
        for _doc in _sub_docs:
            _doc.metadata[id_key] = _id
        sub_docs.extend(_sub_docs)
    return sub_docs

def func2():
    # 方法2 生成摘要
    chain = (
        {"doc": lambda x: x.page_content}
        | ChatPromptTemplate.from_template("Summarize the following document:\n\n{doc}")
        | ChatOpenAI(max_retries=0)
        | StrOutputParser()
    )
    summaries = chain.batch(docs, {"max_concurrency": 5})
    summary_docs = [Document(page_content=s,metadata={id_key: doc_ids[i]}) for i, s in enumerate(summaries)]
    return summary_docs

def func3():
    # 方法3 生成假设性的问题
    functions = [
        {
        "name": "hypothetical_questions",
        "description": "Generate hypothetical questions",
        "parameters": {
            "type": "object",
            "properties": {
            "questions": {
                "type": "array",
                "items": {
                    "type": "string"
                },
            },
            },
            "required": ["questions"]
        }
        }
    ]
    chain = (
        {"doc": lambda x: x.page_content}
        # Only asking for 3 hypothetical questions, but this could be adjusted
        | ChatPromptTemplate.from_template("Generate a list of 3 hypothetical questions that the below document could be used to answer:\n\n{doc}")
        | ChatOpenAI(max_retries=0, model="gpt-4").bind(functions=functions, function_call={"name": "hypothetical_questions"})
        | JsonKeyOutputFunctionsParser(key_name="questions")
    )
    hypothetical_questions = chain.batch(docs, {"max_concurrency": 5})
    question_docs = []
    for i, question_list in enumerate(hypothetical_questions):
        question_docs.extend([Document(page_content=s,metadata={id_key: doc_ids[i]}) for s in question_list])
    return question_docs

def get_docs(func_num):
    if func_num==1:
        return func1()
    elif func_num==2:
        return func2()
    elif func_num==3:
        return func3()
    else:
        return []

# 检索过程的代码
loaders = [
    TextLoader('../../paul_graham_essay.txt'),
    TextLoader('../../state_of_the_union.txt'),
]
docs = []
for l in loaders:
    docs.extend(l.load())

text_splitter = RecursiveCharacterTextSplitter(chunk_size=10000)
docs = text_splitter.split_documents(docs)

# The vectorstore to use to index the child chunks
vectorstore = Chroma(
    collection_name="full_documents",
    embedding_function=OpenAIEmbeddings()
)
# The storage layer for the parent documents
store = InMemoryStore()
id_key = "doc_id"
# The retriever (empty to start)
retriever = MultiVectorRetriever(
    vectorstore=vectorstore, 
    docstore=store, 
    id_key=id_key,
)

doc_ids = [str(uuid.uuid4()) for _ in docs]
# 根据不同方法类型,选择不同策略
func_num =1
candidate_docs = get_docs(func_num) 

retriever.vectorstore.add_documents(candidate_docs)
retriever.docstore.mset(list(zip(doc_ids, docs)))

# search
retriever.vectorstore.similarity_search("justice breyer")[0]

那么,我们该如何学习大模型?

作为一名热心肠的互联网老兵,我决定把宝贵的AI知识分享给大家。 至于能学习到多少就看你的学习毅力和能力了 。我已将重要的AI大模型资料包括AI大模型入门学习思维导图、精品AI大模型学习书籍手册、视频教程、实战学习等录播视频免费分享出来。

一、大模型全套的学习路线

学习大型人工智能模型,如GPT-3、BERT或任何其他先进的神经网络模型,需要系统的方法和持续的努力。既然要系统的学习大模型,那么学习路线是必不可少的,下面的这份路线能帮助你快速梳理知识,形成自己的体系。

L1级别:AI大模型时代的华丽登场

L2级别:AI大模型API应用开发工程

L3级别:大模型应用架构进阶实践

L4级别:大模型微调与私有化部署

一般掌握到第四个级别,市场上大多数岗位都是可以胜任,但要还不是天花板,天花板级别要求更加严格,对于算法和实战是非常苛刻的。建议普通人掌握到L4级别即可。

以上的AI大模型学习路线,不知道为什么发出来就有点糊,高清版可以微信扫描下方CSDN官方认证二维码免费领取【保证100%免费

二、640套AI大模型报告合集

这套包含640份报告的合集,涵盖了AI大模型的理论研究、技术实现、行业应用等多个方面。无论您是科研人员、工程师,还是对AI大模型感兴趣的爱好者,这套报告合集都将为您提供宝贵的信息和启示。

img

三、大模型经典PDF籍

随着人工智能技术的飞速发展,AI大模型已经成为了当今科技领域的一大热点。这些大型预训练模型,如GPT-3、BERT、XLNet等,以其强大的语言理解和生成能力,正在改变我们对人工智能的认识。 那以下这些PDF籍就是非常不错的学习资源。

img

四、AI大模型商业化落地方案

img

作为普通人,入局大模型时代需要持续学习和实践,不断提高自己的技能和认知水平,同时也需要有责任感和伦理意识,为人工智能的健康发展贡献力量。


网站公告

今日签到

点亮在社区的每一天
去签到