吴恩达DeepLearning.AI课程系列 - 大模型检索增强生成(四):向量数据库中的检索优化

科技   2024-10-31 20:34   广东  

SmartFlowAI


点击上方蓝字关注我们

作者:李剑锋

全文约 5300 字,预计阅读时间 10 分钟

前言

在前面的课程中,我们已经介绍了关于文本数据如何被载入并进行切分,以及如何将这部分切分后的文本数据进行向量化后存储在我们的向量数据库当中。完成了以上的步骤其实就是意味着我们前期数据处理工作已经完成了,即便是我们需要载入更多的数据存储到向量数据库中,我们只需要重复上述的步骤即可。在上节课的末尾,我们简单尝试了一下在向量数据库中通过similarity_search的方式进行相关数据的检索,那这节课我们来更加深入的阐述一下这部分的内容。

那对于Retrival这个事情而言,除了最基本的similarity_search通过计算计算查询向量和所有候选文档向量之间的相似度以外,我们其实还有一些其他的方法,包括Maximum Marginal Relevance(MMR,最大边际相关性)、Metadata(元数据)以及LLM Aided Retrieval(大型语言模型辅助检索)等。那下面我们将逐一介绍一下这些方法。

Similarity Search 方法详解

Similarity Search(相似度搜索) 是向量数据库中最基础的检索方法之一。它通过计算查询向量与数据库中所有文档向量之间的相似度,来找到与查询最相关的文档。通常使用的相似度度量方法包括 余弦相似度欧氏距离 等,这些方法能够有效衡量两个向量在高维空间中的接近程度。

在相似度搜索中,用户输入一个查询,系统会将这个查询转化为一个向量,然后与数据库中的向量进行比较,从而找到最相似的内容。这种方法的优点在于它的简单和直观,适合用于快速找到与查询内容高度相关的结果。例如,当用户查询“深度学习的基本概念”时,相似度搜索能够返回与“深度学习”内容最接近的文档片段。

相似度搜索提供了直接匹配用户查询的能力,是许多向量数据库的基础检索方式。然而,单纯依赖相似度搜索可能导致结果的多样性不足,因为它仅关注与查询的匹配程度,而忽略了内容之间的差异性。在一些应用场景下,特别是需要覆盖多个不同方面的信息时,Maximum Marginal Relevance (MMR) 这样的扩展方法可以更好地平衡相关性和多样性。

Maximum Marginal Relevance 方法详解

在信息检索和自然语言处理领域,检索最相似的信息并非总是最优的选择,特别是在用户需求涵盖多个不同方面时,仅选择与查询最相似的响应可能会造成信息重复,缺乏多样性。为了解决这一问题,Maximum Marginal Relevance (MMR) 方法应运而生。MMR 通过在相关性和多样性之间进行权衡,从而优化最终检索结果,使用户能够获得既相关又互补的信息。

MMR 方法的原理与动机

如图所示,当用户提出查询(例如"Tell me about all-white mushrooms with large fruiting bodies")时,系统会尝试寻找与查询最相似的响应。然而,完全基于相关性的检索可能会忽略信息的多样性。例如,如果只选择最相似的响应,得到的结果可能会非常相似,甚至包含重复的内容。这时,虽然每个结果与查询的相似度都很高,但它们却未能提供全面且多样化的内容。

MMR 的核心思想是平衡相关性与多样性,即在选择与查询最相关的信息的同时,也要确保这些信息在内容上具有多样性。通过减少信息之间的重复性,MMR 能够帮助用户获得更加全面和丰富的知识视角,涵盖不同的细节和角度,而不仅仅是获取多个相似的答案。例如,在检索某一特定主题的文档时,MMR 不仅会选出与查询高度匹配的结果,还会确保所选文档在涵盖的内容和信息层次上存在差异,从而使得用户得到的结果既有深度又有广度。

MMR 算法的工作流程

MMR 算法的具体工作流程可以通过下图来理解,下面逐步分解介绍其主要步骤:

  1. 查询向量存储(Query the Vector Store)

  • 首先,将用户的查询转换为向量,并在向量存储库中进行匹配。在向量存储中,每个文档或片段都表示为一个向量,这些向量通常是通过嵌入模型(如 OpenAI Embedding)进行计算的。
  • 选择最相似的响应(Choose the fetch_k most similar responses)

    • 接下来,从向量存储中检索出与查询最相似的前 k 个响应(这些响应的相似性是基于余弦相似度等度量方法计算的)。这部分的结果确保了选出的内容是对查询最有意义、最相关的。
  • 在相似响应中选择最具多样性的结果(Within those responses choose the k most diverse)

    • 在初步选出的 k 个最相似响应中,MMR 方法会进一步选择出 k 个最具多样性的响应。通过计算每对响应之间的相似度,MMR 会偏好那些彼此差异较大的结果,以增加信息的覆盖面。这一过程确保了返回的结果不仅仅是“最相似”,更是“互补的”。

    MMR 的关键在于它通过一个权重参数(通常表示为 λ)来实现对相关性和多样性的平衡。当 λ 趋向于 1 时,MMR 更偏重选择与查询最相似的响应;而当 λ 接近于 0 时,MMR 更倾向于增加检索结果的多样性。通过调节这一权重,MMR 能够适应不同的检索场景。

    • 相关性(Similarity) :确保检索出的文档与用户查询之间有很高的语义相似性,能够直接回答用户的问题。
    • 多样性(Diversity) :避免信息之间的冗余,使得每个返回的结果都包含新的、有用的内容。

    MMR 的实际应用

    MMR 方法在许多信息检索和推荐系统中得到了广泛应用。它在解决那些信息覆盖面广泛的问题上尤为有效,特别是用户的需求涉及多个不同方面时。比如在搜索与某一主题相关的文档时,MMR 可以帮助系统同时返回一些具体细节的内容,以及一些覆盖整个主题的概述性文档,从而让用户对所查询的主题有更完整的理解。但其实究其本质还是基于similarity_search的方法,但是就像大语言模型一样需要调整一下temperature的值使其随机性更高,更符合人类日常说话的状态。因为说话永远没有所谓的最优解,我们都会有一些灵光一闪的时候。

    利用元数据提高检索精确度:基于自查询检索器的方法

    在信息检索过程中,如何提高检索的精度,特别是在面对大量、复杂的文档时,是一个非常重要的问题。在前面我们提到向量数据库中的similarity_search以及Maximum Marginal Relevance可以帮助我们找到与查询内容最相似的文档,但有时,仅依靠相似度还不够精确,尤其当用户的查询具有非常明确的限定条件时。为了解决这种情况,我们可以借助元数据(Metadata)来进一步提高检索的准确性。

    元数据提供了与每个嵌入的文本片段相关的上下文信息,这些信息可以用来更好地限定检索的范围。例如,当用户询问特定课程的内容时,我们不希望返回其他课程的结果。为此,许多向量数据库都支持在检索时对元数据进行操作,这使得检索变得更加准确。

    以如下查询为例:

    问题:"第三讲中提到回归分析的内容是什么?"

    在这个问题中,用户明确希望得到第三讲的内容。因此,如果我们不对查询进行任何过滤,那么可能会得到来自其他讲次的相关内容。为了解决这一问题,我们可以利用元数据中的信息,在similarity_search时加入过滤条件,例如指定源文件为"docs/cs229_lectures/MachineLearning-Lecture03.pdf",确保只从特定的文档中进行检索。这样做就能精确地找到用户想要的内容,而不会被其他不相关的讲次干扰。

    元数据的使用不仅限于来源文档的过滤,还可以包括诸如页码、作者、时间戳等各种信息,这些信息都可以在检索时作为过滤条件,提高查询的精确性。

    实战演练

    下面就让我们进入实战的环节来尝试一下如何实现向量数据库检索吧!主要包的环境版本如下所示:

    langchain                0.3.0
    langchain-community      0.3.0
    pypdf                    5.0.0
    openai                   1.47.0
    beautifulsoup4           4.12.3
    chromadb                 0.5.15

    首先我们要先运行一下上一期内容“吴恩达DeepLearning.AI课程系列 —— 大模型检索增强生成(三):向量数据库及嵌入”的代码,从而在指定的位置生成出相关的Chroma数据库后我们才能开始本次的操作。只要看到该文件生成即代表数据库创建完毕。

    下面我们新建一个名为retrieval.py的文件来将测试数据库是否正确生成的代码写入并查看。假如能够正常打印出数值则代表没有问题。

    from langchain_community.embeddings import BaichuanTextEmbeddings
    from langchain_chroma import Chroma  # 从 langchain_chroma 中引用 Chroma 类

    persist_directory = r'D:\langchain'

    # 初始化嵌入
    embeddings = BaichuanTextEmbeddings(baichuan_api_key="sk-xxx"# 查看上期内容并填入对应的百川API Key

    # 使用 embedding_function 参数初始化 Chroma
    vectordb = Chroma(
        persist_directory=persist_directory,
        embedding_function=embeddings
    )

    # 打印集合中的文档数量
    print(vectordb._collection.count())

    Similarity Search(相似度搜索)

    那我们首先可以尝试一下最基础的方法,就是我们的Similarity Search。我们可以在上面代码的基础上添加以下内容。我们问一下大语言模型是怎么样推理的来从网页里(尤其是目录)中找到对应的内容。

    # 定义问题,即要查询的内容
    question = "大语言模型是怎么推理的?"

    # 在向量数据库中进行相似性搜索,查找与给定问题最相似的 3 个文档
    docs_ss = vectordb.similarity_search(question, k=2)

    # 输出最相似的文档中前 100 个字符的内容
    print(docs_ss[0].page_content[:100])
    print(docs_ss[1].page_content[:100])

    我们在终端的结果中可以看到输出的内容。这部分内容确实与推理息息相关。

    # 第一部分
    9. 现代循环神经网络
    9.1. 门控循环单元(GRU)
    9.2. 长短期记忆网络(LSTM)
    9.3. 深度循环神经网络
    9.4. 双向循环神经网络
    9.5. 机器翻译与数据集
    9.6. 编码器-解

    # 第二部分
    10. 注意力机制
    10.1. 注意力提示
    10.2. 注意力汇聚:Nadaraya-Watson 核回归
    10.3. 注意力评分函数
    10.4. Bahdanau 注意力
    10.5. 多头注意力

    Maximum Marginal Relevance (MMR)

    我们也可以尝试一下使用MMR的方法进行检索看是否效果更好。

    # 定义问题,即要查询的内容
    question = "大语言模型是怎么推理的?"
    docs_mmr = vectordb.max_marginal_relevance_search(question,k=2)
    print(docs_mmr[0].page_content[:100])
    print(docs_mmr[1].page_content[:100])

    我们发现输出的内容有些许的问题。首先是终端先显示了一个警告说默认的返回内容为20个,比我们数据库里包含的18个内容要多。然后我们发现虽然第一部分内容是一致的,而这个第二部分的内容和之前有点不同,但是显然这部分内容的相近程度没有上面直接使用Similarity Search的接近。从这里我们也可以看出添加了随机性后模型的效果未必会好

    # 警告内容
    Number of requested results 20 is greater than number of elements in index 18, 
    updating n_results = 18

    # 第一部分内容
    9. 现代循环神经网络
    9.1. 门控循环单元(GRU)
    9.2. 长短期记忆网络(LSTM)
    9.3. 深度循环神经网络
    9.4. 双向循环神经网络
    9.5. 机器翻译与数据集
    9.6. 编码器-解

    # 第二部分内容
    Vardhman Mahaveer Open University     
          Vietnamese-German University    
          Vignana Jyothi Institute        

    为了消除警告内容,我们就需要添加一个额外的参数fetch_k = 8。这个参数指在进行 最大边际相关性 搜索时,初始化从数据库中提取的 候选文档数量。也就是说,在做最终选择之前,docs_mmr会从数据库中取出与 question 最相似的 8 个文档,然后从这些候选文档中选出 k=2 个文档,来最大化它们之间的多样性。

    docs_mmr = vectordb.max_marginal_relevance_search(question,k=2, fetch_k=8)
    print(docs_mmr[0].page_content[:100])
    print(docs_mmr[1].page_content[:100])

    当我们再次运行程序时,警告就消失了。

    9. 现代循环神经网络
    9.1. 门控循环单元(GRU)
    9.2. 长短期记忆网络(LSTM)
    9.3. 深度循环神经网络
    9.4. 双向循环神经网络
    9.5. 机器翻译与数据集
    9.6. 编码器-解
    Vardhman Mahaveer Open University     
          Vietnamese-German University    
          Vignana Jyothi Institute  

    当然其实我们也可以通过调整fetch_k的值来控制随机答案的生成,比如我们可以尝试fetch = 2fetch = 3。首先我们试试fetch_k=2的情况。

    docs_mmr = vectordb.max_marginal_relevance_search(question,k=2, fetch_k=2)
    print(docs_mmr[0].page_content[:100])
    print(docs_mmr[1].page_content[:100])

    所得的结果和前面Similarity Search是一模一样的。这是因为我们就从里面找到两个概率最高的内容,然后k=2的情况就把所有这两个都拿过来了,并没有能够随机的地方。

    9. 现代循环神经网络
    9.1. 门控循环单元(GRU)
    9.2. 长短期记忆网络(LSTM)
    9.3. 深度循环神经网络
    9.4. 双向循环神经网络
    9.5. 机器翻译与数据集
    9.6. 编码器-解

    10. 注意力机制
    10.1. 注意力提示
    10.2. 注意力汇聚:Nadaraya-Watson 核回归
    10.3. 注意力评分函数
    10.4. Bahdanau 注意力
    10.5. 多头注意力

    假如fetch = 3时。

    docs_mmr = vectordb.max_marginal_relevance_search(question,k=2, fetch_k=3)
    print(docs_mmr[0].page_content[:100])
    print(docs_mmr[1].page_content[:100])

    所得的结果就有所不同,这个时候由于找到另外一块内容,为了满足随机的要求,因此就必然会将这部分内容进行展示。

    9. 现代循环神经网络
    9.1. 门控循环单元(GRU)
    9.2. 长短期记忆网络(LSTM)
    9.3. 深度循环神经网络
    9.4. 双向循环神经网络
    9.5. 机器翻译与数据集
    9.6. 编码器-解
    参考文献
    Table Of Contents

    前言
    安装
    符号

    1. 引言
    2. 预备知识
    2.1. 数据操作
    2.2. 数据预处理
    2.3. 线性代数
    2.4.

    所以我们可以看到在方法MMR中我们可以根据各种参数来对其进行调整从而能够输出更随机更多样的内容。

    元数据(Metadata)

    更进一步,我们也可以用检索特定元数据来进行更精确的搜索。我们可以先查查看我们的内容有哪些可能的元数据。

    # 假设你有一个 vectordb 对象,并且可以获取所有文档
    all_docs = vectordb.similarity_search("", k=18)  # 可以使用一个空查询或任意查询返回多个文档

    # 遍历文档并输出每个文档的元数据
    for i, doc in enumerate(all_docs):
        print(f"Document {i+1} metadata: {doc.metadata}")

    我们可以在终端里看到,由于我们都是在一个链接里进行搜索的,所以所有的元数据都是一样的。

    "metadata": {
        "source""<https://zh.d2l.ai/>",
        "language""en",
        "title""《动手学深度学习》 — 动手学深度学习 2.0.0 documentation",
    }

    那这个时候其实我们也可以尝试添加一些新的内容进去,大家可以在上期代码的基础上加上以下的代码(在运行前可以将原本数据库删除,不然会一直添加)。

    # 添加新文档
    new_loader = WebBaseLoader("<https://www.deeplearning.ai/>")  # 新文档来源
    new_docs = new_loader.load()

    # 文本切分
    new_splits = text_splitter.split_documents(new_docs)

    # 添加到现有的向量库
    vectordb.add_documents(new_splits)

    # 输出更新后的文档数量
    print(f"更新后的文档数量: {vectordb._collection.count()}")

    运行后我们可以看到的结果为新的网址提供了额外了两份内容。

    初始文档数量: 18
    更新后的文档数量: 20

    这个时候我们再查看里面的元数据会发现有额外两条新的内容。

    Document 8 metadata: {'description''DeepLearning.AI | Andrew Ng | Join over 7 million people learning how to use and build AI through our online courses. Earn certifications, level up your skills, and stay ahead of the industry.''language''en''source''<https://www.deeplearning.ai/>''title''DeepLearning.AI: Start or Advance Your Career in AI'}
    Document 18 metadata: {'description''DeepLearning.AI | Andrew Ng | Join over 7 million people learning how to use and build AI through our online courses. Earn certifications, level up your skills, and stay ahead of the industry.''language''en''source''<https://www.deeplearning.ai/>''title''DeepLearning.AI: Start or Advance Your Career in AI'}

    这时候我们可以通过文章来源尝试一下在similarity searchsh上假如元数据的搜索。

    question = "大语言模型是怎么推理的?"
    docs_meta = vectordb.similarity_search(question, k=1, filter={"source""<https://www.deeplearning.ai/>"})
    print(docs_meta[0].page_content[:100])

    这个时候我们发现结果就只会出现DeepLearning.AI网页里的内容了。

    prevnextIn Collaboration WithprevnextThe largest weekly AI newsletterWhat matters in AI right nowOct

    本门课完整代码展示

    main.py[1] —— 前期准备

    from langchain_community.document_loaders import WebBaseLoader
    from langchain.text_splitter import RecursiveCharacterTextSplitter
    from langchain_community.embeddings import BaichuanTextEmbeddings
    from langchain_chroma import Chroma
    # 文件导入
    loader = WebBaseLoader("<https://zh.d2l.ai/>")
    docs = loader.load()

    # 文本切分
    text_splitter = RecursiveCharacterTextSplitter(
        chunk_size = 1500,
        chunk_overlap = 150
    )
    splits = text_splitter.split_documents(docs)
    print(len(splits))

    # 文本嵌入
    embeddings = BaichuanTextEmbeddings(baichuan_api_key="sk-83842453061e34d80b392edba11f62fe")

    # 测试
    # text_1 = "今天天气不错"

    # query_result = embeddings.embed_query(text_1)
    # print(query_result)

    # 路径设置
    persist_directory = r'D:\langchain'

    # 向量库创建
    vectordb = Chroma.from_documents(
        documents=splits,
        embedding=embeddings,
        persist_directory=persist_directory
    )
    print(vectordb._collection.count())

    # # 检索
    # question = "图像识别"
    # docs = vectordb.similarity_search(question,k=3)
    # print(len(docs))
    # print(docs[0].page_content)

    # 添加新文档
    new_loader = WebBaseLoader("<https://www.deeplearning.ai/>")  # 新文档来源
    new_docs = new_loader.load()

    # 文本切分
    new_splits = text_splitter.split_documents(new_docs)

    # 添加到现有的向量库
    vectordb.add_documents(new_splits)

    # 输出更新后的文档数量
    print(f"更新后的文档数量: {vectordb._collection.count()}")

    retrieval.py[2] —— 检索方法

    from langchain_community.embeddings import BaichuanTextEmbeddings
    from langchain_chroma import Chroma  # 从 langchain_chroma 中引用 Chroma 类

    persist_directory = r'D:\langchain'

    # 初始化嵌入
    embeddings = BaichuanTextEmbeddings(baichuan_api_key="sk-83842453061e34d80b392edba11f62fe")

    # 使用 embedding_function 参数初始化 Chroma
    vectordb = Chroma(
        persist_directory=persist_directory,
        embedding_function=embeddings
    )

    # 打印集合中的文档数量
    # print(vectordb._collection.count())

    # question = "大语言模型是怎么推理的?"
    # docs_ss = vectordb.similarity_search(question,k=2)
    # print(docs_ss[0].page_content[:100])
    # print(docs_ss[1].page_content[:100])

    # docs_mmr = vectordb.max_marginal_relevance_search(question,k=2, fetch_k=3)
    # print(docs_mmr[0].page_content[:100])
    # print(docs_mmr[1].page_content[:100])

    # 假设你有一个 vectordb 对象,并且可以获取所有文档
    # all_docs = vectordb.similarity_search("", k=20)  # 可以使用一个空查询或任意查询返回多个文档

    # # 遍历文档并输出每个文档的元数据
    # for i, doc in enumerate(all_docs):
    #     print(f"Document {i+1} metadata: {doc.metadata}")

    question = "大语言模型是怎么推理的?"
    docs_meta = vectordb.similarity_search(question, k=1, filter={"source""<https://www.deeplearning.ai/>"})
    print(docs_meta[0].page_content[:100])

    总结

    总的来说,在这篇文章中,我们深入探讨了如何在向量数据库中进行更加精准的检索,从基础的相似度搜索到更高级的检索方法,例如 Maximum Marginal Relevance (MMR)元数据辅助检索,并结合了 大型语言模型(LLM) 来提高检索的智能化水平。相似度搜索为我们提供了最直接的查询匹配,而 MMR 方法在相关性和多样性之间找到了平衡,使检索结果更全面。在元数据辅助检索中,我们可以利用元数据过滤条件进行精确搜索,避免无关内容的干扰。

    下一节课我们将会讲述一下 自查询检索器(SelfQueryRetriever)、大语言模型辅助检索(LLM Aided Retrieval )Compression(压缩) 的内容。这些方法的结合使得检索系统能够更好地理解用户需求,并提供符合用户期望的高质量答案。那我们下节课见啦!

    参考资料
    [1]

    main.py: http://main.py

    [2]

    retrieval.py: http://retrieval.py


    往期 · 推荐

    FastChat(一):200 行代码实现 Mini FastChat

    落地分享:来看 UFH AI 医疗大模型如何助力国际化诊疗场景

    一文详解大模型推理:从基础知识到 vLLM

    你不知道的MMLU那些事儿

    🌠 番外:我们期待与读者共同探讨如何在 AI 的辅助下,更好地发挥人类的潜力,以及如何培养和维持那些 AI 难以取代的核心技能。通过深入分析和实践,我们可以更清晰地认识到 AI 的辅助作用,并在 AI 时代下找到人类的独特价值和发展空间。“机智流”公众号后台聊天框回复“cc”,加入机智流大模型交流群!

    一起“点赞”三连👇

    机智流
    共赴 AI 时代浪潮~涉及涵盖计算机视觉、大语言模型、多模态模型等AI领域最新资讯知识分享~
     最新文章