DLAI-大模型语义搜索笔记-全-

DLAI 大模型语义搜索笔记(全)

001:课程介绍 🎯

在本节课中,我们将要学习如何将大型语言模型(LLMs)集成到应用程序的信息搜索功能中。课程由Cohere公司合作推出,旨在为开发者提供构建强大LLM应用所需的工具。

欢迎来到与Cohere合作推出的“大型语言模型与语义搜索”短期课程。在本课程中,你将学习如何将大型语言模型(LLMs)整合到你自己应用程序的信息搜索功能中。

例如,假设你运营一个拥有大量文章的网站(比如类似维基百科的网站),或者一个拥有大量电子商务产品的网站。即使在LLMs出现之前,使用关键词搜索来让用户搜索网站内容也很常见。但有了LLMs,你现在可以实现更多功能。首先,你可以让用户提问,然后你的系统会搜索网站或数据库来回答问题。其次,LLM还能使检索结果与用户查询的含义或语义更加相关。

我来介绍一下本课程的讲师:Jay Alammar和Luis Serrano。Jay和Luis都是经验丰富的机器学习工程师和教育工作者。我钦佩Jay很久了,他创作了一些高度参考价值的图解来解释Transformer网络。他也是《动手学大型语言模型》一书的合著者。Luis是《机器学习图解》一书的作者,他也在Cohere教授深度学习与AI机器学习课程。Jay和Luis,以及Mia Amir,还共同运营着一个名为“LM”的网站,在教授开发者使用LLMs方面拥有丰富经验。所以当他们同意教授这门关于LLMs语义搜索的课程时,我感到非常激动。

谢谢Andrew。能和你一起教授这门课程是莫大的荣幸。你的机器学习课程在8年前将我引入了机器学习领域,并且一直激励着我继续分享所学知识。正如你提到的,Luis和我在Cohere工作。因此,我们有机会为行业内的其他人提供建议,指导他们如何为各种用例使用和部署大型语言模型。我们很高兴能开设这门课程,为开发者提供构建强大LLM应用所需的工具,并且我们很乐意分享我们在该领域获得的经验。

谢谢Jay和Luis,很高兴你们能加入我们。

本课程包含以下主题。

首先,你将学习如何使用基本的关键词搜索,这也被称为词汇搜索。在大型语言模型出现之前,许多搜索系统都依赖这种技术。它通过查找与查询词匹配度最高的文档来实现。

接下来,你将学习如何通过一种名为“重排序”的方法来增强这种关键词搜索。顾名思义,这种方法会根据结果与查询的相关性对响应进行重新排序。

之后,你将学习一种更高级的搜索方法,它极大地改进了关键词搜索的结果,因为它试图利用文本的实际含义或语义来进行搜索。这种方法被称为“密集检索”。它使用自然语言处理中一个非常强大的工具——嵌入。嵌入是一种将一段文本与一个数字向量关联起来的方式。

语义搜索包括在嵌入空间中查找与查询最接近的文档。与其他模型一样,搜索算法也需要进行适当的评估。你也会学习进行有效评估的方法。

最后,由于LLMs可用于生成答案,你还将学习如何将搜索结果输入LLM,并让它基于这些结果生成答案。结合嵌入的密集检索极大地提升了LLM的问答能力,因为它首先搜索并检索相关文档,然后根据检索到的信息生成答案。

许多人为此课程做出了贡献。我们感谢Cohere的Mia Amir、Patrick Lewis、Nils Reimers和Sebastian Hofstätter,以及DeepLearning.AI团队的Eddie Shao和Dina Elenbogen的辛勤工作。

在第一课中,你将看到在大型语言模型出现之前是如何进行搜索的。

从那里开始,我们将向你展示如何使用LLMs(包括嵌入和重排序等工具)来改进搜索。

这听起来很棒。那么,让我们开始深入探索,进入下一个视频。


本节课中我们一起学习了“大型语言模型与语义搜索”课程的概述和目标。我们了解了课程将涵盖从传统关键词搜索到利用嵌入进行语义搜索的进阶方法,并介绍了如何结合LLMs生成答案。课程由经验丰富的讲师团队设计,旨在为开发者提供实用的工具和知识。接下来,我们将从基础的关键词搜索开始我们的学习之旅。

002:关键词搜索

概述

在本节课中,我们将学习如何使用关键词搜索技术,基于数据库来回答问题。搜索是我们与世界交互的核心方式,无论是搜索引擎还是应用内搜索。关键词搜索是构建搜索系统最常用的方法。

什么是关键词搜索

上一节我们介绍了搜索的重要性。本节中,我们来看看关键词搜索的基本原理。

关键词搜索通过比较查询语句与文档之间共同词汇的数量来工作。例如,对于查询“what color is the grass?”,系统会计算它与文档库中每个句子共享的单词数,并返回共享单词最多的文档作为结果。

连接数据库与准备工作

在开始编写代码之前,我们需要连接到一个数据库。这里我们将使用Weaviate,一个开源的、支持关键词搜索和向量搜索的数据库。

以下是设置步骤:

  1. 安装客户端:如果你在自己的环境中运行代码,需要安装Weaviate客户端。
    # 安装Weaviate客户端(在课堂环境中无需运行)
    # !pip install weaviate-client
    

  1. 加载API密钥:加载访问演示数据库所需的密钥。

    import os
    # 加载API密钥(示例,实际使用时请替换)
    os.environ['WEAVIATE_API_KEY'] = 'your-public-demo-key-here'
    
  2. 导入库并连接客户端:导入Weaviate并连接到包含1000万条维基百科记录的公共数据库。

    import weaviate
    
    # 配置认证
    auth_config = weaviate.AuthApiKey(api_key=os.environ['WEAVIATE_API_KEY'])
    client = weaviate.Client(
        url="https://your-weaviate-cluster-url",
        auth_client_secret=auth_config
    )
    
    # 检查连接是否成功
    print(client.is_ready())
    

构建关键词搜索函数

现在我们已经连接到数据库,接下来构建一个执行关键词搜索的函数。

我们将使用BM25算法,这是一种常用的关键词(词汇)搜索算法。它根据一个特定的公式对文档进行评分,该公式主要考察查询与每个文档之间共享词汇的数量。

以下是keyword_search函数的定义:

def keyword_search(query,
                   results_lang='en',
                   properties=["title", "url", "text"],
                   num_results=3):
    """
    在Weaviate数据库上执行关键词搜索。

    参数:
        query (str): 用户的搜索查询。
        results_lang (str): 结果的语言代码(例如,‘en’代表英文)。
        properties (list): 希望返回的文档属性列表。
        num_results (int): 返回的结果数量。

    返回:
        dict: 包含搜索结果的响应。
    """
    response = (
        client.query
        .get("Articles", properties) # “Articles”是数据库中的集合名称
        .with_bm25(query=query)      # 使用BM25算法进行关键词搜索
        .with_where({
            "path": ["lang"],
            "operator": "Equal",
            "valueString": results_lang
        })                           # 过滤指定语言的文档
        .with_limit(num_results)     # 限制返回结果数量
        .do()
    )
    return response

执行搜索并查看结果

让我们使用上面定义的函数来搜索“收视率最高的电视节目是什么”。

# 执行搜索
query = "what is the most viewed televised event"
search_results = keyword_search(query)

![](https://github.com/OpenDocCN/dsai-notes-pt1-zh/raw/master/docs/dlai-llm-semsch/img/4eeb55102e82188c1843c2f5102db022_16.png)

![](https://github.com/OpenDocCN/dsai-notes-pt1-zh/raw/master/docs/dlai-llm-semsch/img/4eeb55102e82188c1843c2f5102db022_17.png)

# 定义一个函数来美观地打印结果
def print_results(results):
    for i, item in enumerate(results['data']['Get']['Articles']):
        print(f"\n=== 结果 {i+1} ===")
        print(f"标题: {item['title']}")
        print(f"URL: {item['url']}")
        print(f"内容摘要: {item['text'][:200]}...") # 只打印前200个字符

![](https://github.com/OpenDocCN/dsai-notes-pt1-zh/raw/master/docs/dlai-llm-semsch/img/4eeb55102e82188c1843c2f5102db022_18.png)

![](https://github.com/OpenDocCN/dsai-notes-pt1-zh/raw/master/docs/dlai-llm-semsch/img/4eeb55102e82188c1843c2f5102db022_20.png)

# 打印结果
print_results(search_results)

运行上述代码后,你可能会看到类似以下的结果。第一个结果可能因为包含“event”等关键词而被检索到,但相关性不高。第二个关于“超级碗”的结果则更相关。

尝试多语言搜索

我们的数据库支持多种语言。你可以通过修改results_lang参数来搜索其他语言的文档。

以下是支持的语言代码示例:

  • en - 英语
  • de - 德语
  • fr - 法语
  • es - 西班牙语
  • zh - 中文

例如,用德语进行搜索:

german_results = keyword_search("was ist das meistgesehene Fernsehereignis", results_lang='de')
print_results(german_results)

关键词搜索的系统架构与局限性

现在,让我们从更高层面回顾搜索系统的构成。

一个典型的搜索系统包含以下主要组件:

  1. 查询:用户输入的问题。
  2. 搜索系统:处理查询的核心。
  3. 文档库:系统预先处理并索引的文档集合。
  4. 结果列表:系统返回的、按相关性排序的文档。

更仔细地看,搜索系统通常分为两个阶段:

  • 检索阶段:使用如BM25等算法,快速从海量文档中找出可能与查询相关的候选文档子集。为了提高速度,这通常依赖于倒排索引数据结构。
  • 重排序阶段:对第一阶段的候选结果进行更精细的排序,可能引入除文本匹配外的其他信号(如点击率、权威性等)。

然而,关键词搜索有其局限性。它严重依赖字面匹配。例如,对于查询“头部一侧剧烈疼痛”,如果一个文档使用“sharp temple headache”来描述,尽管语义相同,但由于没有共享关键词,BM25可能无法检索到该文档。

总结与展望

本节课中,我们一起学习了关键词搜索的基础知识。我们了解了其工作原理,使用Weaviate数据库和BM25算法实现了关键词搜索,并探讨了其多语言能力及核心架构。

我们也看到了关键词搜索的局限:它无法理解语义,当查询和文档用词不同但意思相同时,可能会失效。

这正是语言模型可以发挥作用的地方。在接下来的课程中,我们将探讨:

  • 嵌入:如何使用语言模型将文本转换为向量(嵌入),从而在检索阶段实现语义搜索。
  • 重排序:如何利用语言模型对初步检索结果进行更智能的重新排序。
  • 生成式回答:如何结合搜索步骤,让大语言模型生成基于检索信息的答案。

让我们进入下一课,学习关于嵌入的知识。🚀

003:词嵌入(Embeddings)入门

在本节课中,我们将学习词嵌入(Embeddings)的概念。词嵌入是文本的数值化表示,它能让计算机更容易地处理文本信息。这是大型语言模型中最重要的组成部分之一。

什么是词嵌入?

上一节我们介绍了课程概述,本节中我们来看看词嵌入的核心概念。

词嵌入将文本(如单词或句子)转换为一系列数字,这些数字可以表示在坐标空间中。相似含义的文本在空间中的位置也相近。

例如,我们可以想象一个二维坐标网格。在这个网格中,词语根据其含义被放置在不同的位置。水果类的词语(如“香蕉”、“橙子”)会聚集在一起,交通工具类的词语(如“汽车”、“自行车”)会聚集在另一处。如果我们想放置“苹果”这个词,根据其含义,它应该被放在水果聚集的区域。

在数学上,一个词嵌入函数可以将一个单词 word 映射为一个数值向量:
embedding(word) -> vector
例如,embedding(“apple”) 可能得到向量 [5, 5]。在实际应用中,向量维度通常高达数百甚至数千。

环境设置与库导入

要开始使用词嵌入,我们首先需要设置编程环境并导入必要的库。

以下是需要安装和导入的核心库:

  • cohere: 提供调用大型语言模型API的功能,我们将使用其 embed 函数。
  • pandas (pd): 用于高效处理表格数据。
  • numpy: 用于数值计算。
  • umap-learn 和 altair: 用于将高维嵌入向量可视化到二维或三维空间。

在课程环境中,这些设置已完成。若想自行操作,需通过 pip install 命令安装上述包。

# 导入必要的库
import cohere
import pandas as pd
import numpy as np
# 可视化库通常在需要时导入

接下来,我们需要使用API密钥创建Cohere客户端,以便调用其服务。

# 使用你的API密钥创建Cohere客户端
co = cohere.Client(‘YOUR_API_KEY_HERE’)

为单词创建嵌入

理解了基本概念并设置好环境后,我们来实践如何为单个单词生成嵌入。

首先,我们创建一个包含三个单词的小型数据集。

# 创建一个包含三个单词的简单数据表
three_words = pd.DataFrame({‘text’: [‘joy’, ‘happiness’, ‘potato’]})

现在,我们使用Cohere的 embed 函数为这三个单词生成嵌入向量。

# 调用embed函数生成嵌入
embeddings_three_words = co.embed(
    texts=list(three_words[‘text’]), # 要嵌入的文本列表
    model=‘embed-english-v3.0’ # 指定使用的模型
).embeddings

# 将嵌入向量转换为numpy数组以便处理
embeddings_three_words = np.array(embeddings_three_words)

我们可以查看每个单词对应的向量。例如,“joy”的向量可能是一个包含4096个数字的长列表(具体长度取决于模型)。

# 获取第一个单词(‘joy’)的嵌入向量
word1_vector = embeddings_three_words[0]
# 查看该向量的前10个数值
print(word1_vector[:10])

为句子创建嵌入

词嵌入的强大之处在于它不仅能处理单词,也能处理更长的文本序列,如句子或段落。

让我们创建一个由问答对组成的小型句子数据集。

# 创建一个包含句子的数据表
sentences = pd.DataFrame({
    ‘text’: [
        ‘What color is the sky?’,
        ‘The sky is blue.’,
        ‘What is an apple?’,
        ‘An apple is a fruit.’,
        ‘Where does the bear live?’,
        ‘The bear lives in the woods.’,
        ‘Where is the World Cup?’,
        ‘The World Cup is in Qatar.’
    ]
})

同样地,我们使用 embed 函数为所有句子生成嵌入。

# 为所有句子生成嵌入
embeddings_sentences = co.embed(
    texts=list(sentences[‘text’]),
    model=‘embed-english-v3.0’
).embeddings
embeddings_sentences = np.array(embeddings_sentences)

可视化与理解嵌入空间

生成数值向量后,我们可以通过降维技术(如UMAP)将其可视化在二维平面上,直观地观察文本间的语义关系。

以下是可视化步骤的简要说明:

  1. 使用UMAP算法将高维嵌入(例如4096维)降至2维。
  2. 使用Altair等库绘制二维散点图。
  3. 观察图中点的分布。

在生成的图中,你会发现:

  • “What color is the sky?” 和 “The sky is blue.” 这两个句子的点距离非常近。
  • 同样,“What is an apple?” 和 “An apple is a fruit.” 也彼此靠近。
  • 而语义不同的句子,如关于天空的问题和关于熊的问题,则在图中相距较远。

这个特性至关重要:嵌入模型将语义相似的文本映射到向量空间中相近的点。这构成了密集检索的基础——通过寻找与问题嵌入最接近的答案嵌入,来从大型数据库中检索信息。

应用于大规模数据集:维基百科文章

掌握了小规模数据的处理方法后,我们可以将其扩展至大规模数据集。

我们将一个包含2000篇维基百科文章(标题、首段文本及其嵌入向量)的数据集加载进来。通过可视化这2000个高维嵌入点,我们可以看到清晰的语义聚类:

  • 语言相关的文章聚集在一个区域。
  • 国家相关的文章聚集在另一个区域。
  • 国王/女王足球运动员艺术家等主题也分别形成了各自的聚类。

这演示了嵌入技术如何帮助我们在海量文本数据中组织和发现信息。

总结

本节课中我们一起学习了词嵌入(Embeddings)的核心知识。我们了解到,词嵌入是将文本转换为计算机可处理的数值向量的技术,它能捕捉文本的语义信息,使语义相似的文本在向量空间中位置接近。

我们实践了如何使用Cohere API为单词和句子生成嵌入,并通过可视化观察到语义相似性如何在嵌入空间中体现。最后,我们看到了这项技术如何应用于大规模文本数据集(如维基百科),为语义搜索和密集检索奠定了基础。

在下一节课中,J将教你如何利用嵌入技术进行密集检索,即从大型数据库中精准搜索查询的答案。

004:稠密检索 🧠

在本节课中,我们将学习如何利用嵌入向量进行语义搜索,即根据含义进行搜索。上一节我们介绍了嵌入向量的概念,本节中我们来看看如何将其应用于搜索任务。

本节课分为两部分。第一部分,我们将连接到第一课中使用过的数据库,但不再使用关键词搜索,而是利用嵌入向量进行向量搜索以实现语义搜索。第二部分,在熟悉了如何查询一个已准备好的向量数据库后,我们将从头开始处理文本,学习如何从零构建一个向量索引。

第一部分:使用向量数据库进行语义搜索

首先,我们加载API密钥并设置环境,这与之前的操作一致。

import cohere
co = cohere.Client('YOUR_API_KEY')

接下来,我们设置Weaviate客户端并连接到同一个数据库。到目前为止,这些步骤都是熟悉的。

在深入搜索代码之前,让我们基于嵌入课程的知识,理解其背后的原理。假设查询是“what is the capital of Canada”,而我们的档案库中有五个可能的句子。通过嵌入模型,我们可以将这些句子和查询投射到同一个向量空间中。语义相似的句子在空间中的位置会更接近。一个为搜索优化的嵌入模型会使查询的向量最接近其答案的向量。这就是我们利用嵌入的相似性和距离属性进行搜索的方式,也就是稠密检索

以下是使用Weaviate进行稠密检索的代码。它与第一课的关键词搜索代码很相似,主要区别在于我们使用 near_text 参数进行向量搜索,而不是 BM25

response = (
    client.query
    .get("Articles", ["title", "url", "text"])
    .with_near_text({"concepts": [query]})
    .with_limit(3)
    .do()
)

执行此代码后,我们可以发送查询并查看结果。

让我们运行几个查询来比较稠密检索和关键词搜索的效果。

查询示例 1: “who wrote Hamlet”

  • 稠密检索结果:返回的文本提到了莎士比亚创作《哈姆雷特》,这是最相关的结果。
  • 关键词搜索对比:我们导入第一课的函数进行对比。

查询示例 2: “what is the capital of Canada”

  • 稠密检索结果:返回关于渥太华(加拿大首都)的维基百科页面。
  • 关键词搜索结果:返回的结果涉及加拿大君主制、早期现代时期等,与“首都”这一核心意图的相关性较弱。

查询示例 3: “tallest person in history”

  • 稠密检索结果:正确返回了历史上最高的人“Robert Wadlow”的相关记录。
  • 关键词搜索结果:返回了关于日本历史、“7 summits”等不相关的内容。

此外,稠密检索还支持多语言搜索,因为使用的嵌入模型是多语言的。例如,用阿拉伯语输入“历史上最高的人”,依然能返回正确的结果。

另一个强大的应用是探索性搜索。例如,查询“film about a time travel paradox”,可以返回《About Time》、《The Time Machine》等相关电影,帮助你探索数据集。

以上是第一部分的结束,我们演示了如何消费一个现成的向量数据库。接下来,我们将进入第二部分,学习如何从头构建自己的向量搜索数据库。

第二部分:从头构建向量搜索索引

首先,我们导入必要的库,包括近似最近邻库 Annoy

import cohere
from annoy import AnnoyIndex
import numpy as np
import textwrap

我们将使用电影《星际穿越》的维基百科页面文本作为示例数据。

构建语义搜索系统的一个关键步骤是文本分块。如何分块取决于具体任务。常见方法包括按句号分割句子或按段落分割。对于维基百科这类结构清晰的文本,按句号分割是一个简单选择。但需要注意,单独的句子可能缺乏上下文。一个改进方法是为每个块添加标题作为上下文,例如在每个句子前加上“Interstellar (film): ”。

以下是分块和添加上下文的示例代码:

# 假设 text 包含维基百科全文
sentences = text.split('. ')
title = "Interstellar (film)"
chunks = [f"{title}: {sentence}." for sentence in sentences]

完成分块后,下一步是为每个文本块生成嵌入向量

embeds = co.embed(texts=chunks).embeddings
embeds = np.array(embeds) # 转换为NumPy数组以便处理
# embeds 的维度为 (句子数量, 嵌入向量维度)

现在,我们将这些嵌入向量存入一个搜索索引。这里使用 Annoy 库来构建一个近似最近邻索引。

# 初始化索引,嵌入向量维度为4096,使用欧氏距离
search_index = AnnoyIndex(4096, 'angular')
for i, embed in enumerate(embeds):
    search_index.add_item(i, embed)
search_index.build(10) # 构建索引,10表示树的数量
search_index.save('test.ann') # 保存索引到文件

索引构建完成后,我们可以定义一个搜索函数。该函数会嵌入查询文本,然后在索引中查找最近的邻居。

def search(query):
    # 嵌入查询
    query_embed = co.embed(texts=[query]).embeddings
    query_embed = np.array(query_embed[0])
    # 在索引中搜索最近邻
    nearest_ids = search_index.get_nns_by_vector(query_embed, 3, include_distances=True)
    # 返回结果
    for _id, dist in zip(nearest_ids[0], nearest_ids[1]):
        print(f"Distance: {dist:.2f}")
        print(textwrap.fill(chunks[_id], width=60))
        print("\n")

让我们测试一下。查询“how much did the film make”,即使句子中没有直接出现“make”这个词,系统也能返回关于电影全球票房(gross)的正确句子。

稠密检索、向量搜索库与向量数据库

我们已经看到了如何使用 Annoy 这样的库进行稠密检索。现在我们来讨论一下近似最近邻向量搜索库(如 Annoy, Faiss, ScaNN)和向量数据库(如 Weaviate, Pinecone, Chroma)之间的区别。

以下是两者的主要对比:

  • 设置与复杂度:向量搜索库通常更轻量、更简单,易于设置。向量数据库功能更丰富,但可能更复杂。
  • 数据管理:向量搜索库通常只存储向量。向量数据库除了存储向量,还能存储并管理原始文本、元数据等。
  • 更新操作:向向量搜索库中添加或修改记录通常需要重建整个索引。向量数据库则支持动态增删改查,更容易更新。
  • 查询功能:向量数据库支持更复杂的查询,例如结合元数据过滤(如我们之前按语言过滤)。

在实际应用中,你无需用向量搜索完全取代关键词搜索。它们可以互补,形成混合搜索管道。即对一个查询同时执行关键词搜索和向量搜索,然后对两者的结果评分进行聚合,以呈现最佳结果。你还可以引入其他信号(如网页的权威性评分)来进一步优化排序。

在下一课中,我们将学习语义搜索的第二种主要方式:重排序,它能在搜索步骤之后显著提升结果的相关性。

总结

本节课中我们一起学习了:

  1. 稠密检索的原理:利用嵌入向量将查询和文档映射到同一空间,通过计算距离来寻找语义上最相似的文档。
  2. 使用 Weaviate 进行向量搜索:实践了如何对现有向量数据库执行语义搜索查询,并对比了其与关键词搜索的效果差异,特别是在处理语义意图、多语言查询和探索性搜索时的优势。
  3. 从头构建向量索引:学习了文本分块、添加上下文、生成嵌入以及使用 Annoy 库构建和查询近似最近邻索引的完整流程。
  4. 技术选型对比:了解了近似最近邻库与全功能向量数据库在复杂度、数据管理和更新操作上的区别,并认识了混合搜索的基本概念。

通过本课,你已经掌握了实现语义搜索的核心技能之一,并能够根据需求选择合适的技术工具来构建搜索系统。

005:重排序(Rerank)技术详解 🎯

概述

在本节课中,我们将学习语义搜索的第二个核心组件——重排序(Rerank)。这是一种利用大型语言模型对搜索结果进行排序,以提升关键词搜索和密集检索效果的方法。


重排序(Rerank)简介

上一节我们介绍了密集检索,本节中我们来看看如何通过重排序技术来优化搜索结果。

重排序是一种让大型语言模型根据搜索结果与查询的相关性,将其从最佳到最差进行排序的方法。其核心目标是基于搜索结果与查询的相关性进行排序。

重排序的工作原理

为了理解重排序的必要性,我们先来看一个密集检索的局限性示例。

我们使用密集检索来搜索查询:“What is the capital of Canada”。以下是返回的结果:

  1. 正确答案:渥太华(Ottawa)。
  2. 噪声结果:多伦多(Toronto,并非加拿大首都)。
  3. 错误答案:魁北克市(Quebec City)。

为什么会出现这种情况?让我们通过一个简化的图示来理解。

假设查询是“What is the capital of Canada?”,可能的回答有五个:

  • A:加拿大的首都是渥太华。(正确)
  • B:多伦多在加拿大。(正确但与问题无关)
  • C:法国的首都是巴黎。(正确但与问题无关)
  • D:加拿大的首都是悉尼。(错误)
  • E:安大略省的首府是多伦多。(正确但与问题无关)

在嵌入空间中,这些句子可能的位置分布如下。密集检索的工作原理是将查询也放入嵌入空间,然后返回最接近的响应。在这个例子中,最接近查询的句子可能是E:“安大略省的首府是多伦多”。虽然这个句子在语义上与查询相似(都涉及“首都”和加拿大地区),但它并没有回答“加拿大”的首都是什么这个问题。

因此,密集检索有可能返回语义相似但并非正确答案的结果。

如何解决这个问题?这正是重排序发挥作用的地方。

重排序示例

假设查询是“What is the capital of Canada?”,我们有10个可能的答案,其中一些与问题相关,一些不相关。

当我们使用密集检索时,它返回与查询最相似的前5个结果。但我们无法直接判断哪一个才是真正的答案。

此时,重排序开始工作。重排序为每个“查询-响应”对分配一个相关性分数,这个分数表明答案(或文档)与查询的相关程度。

如下图所示,相关性最高的分数(0.9)对应着“加拿大的首都是渥太华”,这正是正确答案。这就是重排序的作用。

你可能会好奇重排序模型是如何训练的。

以下是训练重排序模型的基本方法:

  1. 提供大量正样本对:即查询和响应(或文档)高度相关的配对,训练模型为这些配对给出高相关性分数。
  2. 提供大量负样本对:即查询和响应不匹配的配对(即使它们在语义上可能接近),训练模型为这些配对给出低相关性分数。

通过这种方式训练出的模型,就能在遇到高度相关的查询-响应对时给出高分,反之则给出低分。

实践应用:优化关键词搜索

现在让我们看看如何使用重排序来改进关键词搜索的效果。

我们导入第一课中使用的关键词搜索函数,并再次查询“What is the capital of Canada?”。

首先,我们输出3个结果,但这些结果并不理想(例如“monarchy of Canada”)。这是因为关键词搜索只能找到与查询有大量共同词汇的文档,但无法判断文档是否真正回答了问题。

接下来,我们扩大搜索范围,获取500个结果(仅打印标题)。面对如此多的结果,如何找到包含答案的那一个?

这时就需要重排序。我们调用重排序函数,对关键词搜索返回的500个结果进行处理,并输出相关性最高的前10个

以下是重排序后的部分结果:

  • 第1名:“Ottawa” – 相关性分数:0.98(非常接近1)。
  • 第2名:一篇关于加拿大历史上不同首都的文章 – 相关性分数:0.97。

可以看到,重排序成功地从关键词搜索提供的大量结果中,筛选出了与问题最相关的答案。

实践应用:优化密集检索

最后,我们尝试一个更难的问题:“Who is the tallest person in history?”。这对关键词搜索来说可能很困难,因为它可能返回包含“history”或“person”词汇的文章,而无法理解问题的真正含义。

我们使用密集检索函数来获取一些结果。打印后发现,它确实找到了正确答案——“Robert Wadlow”,但也包含其他相关度较低的文档。

我们再次使用重排序来处理这些结果。重排序函数会评估每个结果文本与我们给出的查询的相关性。

打印重排序后的答案,我们可以看到:

  • 相关性分数最高(0.97)的正是关于Robert Wadlow的文章。
  • 其他文章的相关性分数则较低。

因此,重排序帮助我们从密集检索返回的结果中,精准地识别出了问题的正确答案。


搜索系统的评估方法

现在我们已经掌握了多种搜索技术,你可能会想知道如何评估它们的性能。

以下是几种常见的评估指标:

  • MAP(Mean Average Precision,平均精度均值)
  • MRR(Mean Reciprocal Rank,平均倒数排名)
  • NDCG(Normalized Discounted Cumulative Gain,归一化折损累计增益)

如何构建用于评估模型的测试集?一个好的测试集应包含一系列查询及其对应的正确答案。然后,你可以将模型返回的结果与这些标准答案进行比较,其方式类似于评估分类模型的准确率、精确率或召回率。


总结

本节课中,我们一起学习了重排序(Rerank) 技术。我们了解到:

  1. 重排序是语义搜索的关键组件,用于对初步搜索结果进行相关性排序。
  2. 它通过为“查询-响应”对打分来工作,高分表示高度相关。
  3. 重排序能有效优化关键词搜索密集检索的结果,帮助我们从大量或嘈杂的返回信息中精准定位正确答案。
  4. 我们还简要了解了评估搜索系统性能的常用指标(如MAP、MRR、NDCG)。

在下一课中,你将学习更酷的内容:如何将搜索系统与生成模型结合,以人类回答问题的句子形式,直接输出查询的答案。

006:检索增强生成(RAG)实战 🚀

在本节课中,我们将学习如何在语义搜索流程的末端,增加一个使用大型语言模型(LLM)的生成步骤。通过这种方式,我们可以直接获得一个答案,而不仅仅是搜索结果。这是一种构建应用程序的实用方法,例如让用户可以与文档、书籍或文章进行对话。

概述

大型语言模型在许多方面表现出色,但在某些应用场景中,它们也需要一些辅助。例如,当你有一个关于“AI领域的副业项目是否是个好主意”的问题时,直接询问LLM可能会得到一些有趣的答案。然而,更有价值的是咨询专家或参考专家的著作。本节课,我们将利用吴恩达(Andrew Ng)关于“如何构建AI职业生涯”的系列文章,结合我们已学的语义搜索技术,构建一个能够基于特定文档生成答案的系统。

上一节我们介绍了语义搜索的核心技术,本节中我们来看看如何将搜索与生成结合,实现检索增强生成(Retrieval-Augmented Generation, RAG)。

构建文本档案库

首先,我们需要构建一个文本档案库。对于本示例,我们将使用吴恩达的三篇相关文章。以下是具体步骤:

以下是构建文本档案库的步骤:

  1. 收集目标文档的文本内容。
  2. 将长文本分割成较小的、语义连贯的块(chunks)。
  3. 使用嵌入模型(如Cohere的嵌入模型)将每个文本块转换为向量表示。
  4. 将这些向量存储到向量数据库中,构建可搜索的索引。

我们使用以下代码进行文本分块和嵌入:

# 示例:文本分块与嵌入
import cohere
co = cohere.Client(‘YOUR_API_KEY’)

# 假设 `text` 变量包含了所有文章的文本
text_chunks = chunk_text(text) # 自定义分块函数
embeddings = co.embed(texts=text_chunks).embeddings

实现语义搜索功能

在生成答案之前,我们需要一个能够从档案库中检索相关信息的搜索系统。这个系统与我们之前构建的语义搜索引擎类似。

以下是定义搜索函数的关键步骤:

  1. 将用户的查询语句转换为向量。
  2. 在向量数据库中进行相似性搜索,找到与查询最相关的文本块。
  3. 返回最匹配的结果。

搜索函数的核心代码如下:

def search_articles(query):
    # 嵌入查询语句
    query_embedding = co.embed(texts=[query]).embeddings[0]
    # 在向量索引中搜索(假设 `index` 是已构建的向量索引)
    search_results = index.search(query_embedding, k=1) # 返回最相关的1个结果
    # 返回匹配的文本内容
    return search_results[0][‘text’]

集成生成模型

现在,我们将搜索步骤与生成步骤结合起来。核心思想是:先通过搜索获取与问题最相关的上下文信息,然后将此上下文与原始问题一同作为提示词(prompt)提交给生成模型,从而获得一个基于特定文档的答案。

以下是 ask_article 函数的工作流程:

  1. 调用 search_articles 函数,获取与问题最相关的文章段落。
  2. 精心构建一个提示词(prompt),其中包含检索到的上下文、用户的问题以及明确的指令(例如:“请根据提供的文本提取答案”)。
  3. 将构建好的提示词发送给生成模型(如Cohere的Command模型)。
  4. 解析并返回模型生成的答案。

我们使用以下代码来构建提示词并调用生成模型:

def ask_article(question):
    # 1. 检索上下文
    context = search_articles(question)

    # 2. 构建提示词
    prompt = f“””
    以下是摘自吴恩达(Andrew Ng)文章《如何构建AI职业生涯》的片段:

    {context}

    问题:{question}

    请根据以上提供的文本内容提取答案。如果文本中没有相关信息,请说明“无法根据提供文本回答”。
    “””

    # 3. 调用生成模型
    response = co.generate(
        prompt=prompt,
        model=‘command-nightly’, # 使用最新的模型
        max_tokens=100,
        num_generations=1 # 生成1个答案
    )

    # 4. 返回答案
    return response.generations[0].text

测试与优化

运行上述代码后,当我们提问“在尝试构建AI职业生涯时,副业项目是个好主意吗?”,系统会先搜索文章,找到相关段落,然后生成一个简洁的答案,例如:“是的,副业项目是个好主意……”。

在开发过程中,我们可以通过调整 num_generations 参数来一次性获得模型的多个生成结果,这有助于快速评估模型响应的稳定性和质量,是进行提示工程(Prompt Engineering)调试的有效方法。

# 测试模型行为:一次性获取3个生成结果
response = co.generate(
    prompt=prompt,
    model=‘command-nightly’,
    max_tokens=70,
    num_generations=3 # 生成3个不同的答案变体
)
for i, gen in enumerate(response.generations):
    print(f“Generation {i+1}: {gen.text}\n”)

总结

本节课中我们一起学习了检索增强生成(RAG)的基本流程。我们首先构建了一个基于特定文章(吴恩达的AI职业指南)的语义搜索系统,然后将其与一个大型语言模型相结合。这种方法的核心优势在于,它让模型能够基于我们提供的、可靠的上下文信息来生成答案,而不是仅仅依赖其内部训练时学到的知识,从而提高了答案的相关性和事实准确性。

这种“先搜索,后生成”的模式是当前构建文档问答、知识聊天机器人等应用的主流方法。你可以将此框架应用于其他领域,例如让用户与播客文字记录、学术论文或公司内部文档进行交互。

007:课程总结与后续资源 🎓

在本节课中,我们将对《大型语言模型与语义搜索》课程进行总结,并介绍后续的学习资源与社区。


课程总结

这是关于大型语言模型与语义搜索课程的结尾。

我们衷心感谢您的关注,并希望您享受学习这一主题的过程。


后续学习资源与社区

上一节我们总结了课程内容,本节中我们来看看如何继续深入学习和获取支持。

我们邀请您查看Cohere提供的更全面的LLM课程,该课程名为LLM University。😊

在该课程中,您可以学习到更多关于大型语言模型的主题。

此外,您可以加入Cohere的Discord社区。在那里,Jay、我本人以及其他成员将非常乐意解答您可能遇到的任何关于大型语言模型的问题。


致谢

我们还要感谢许多人,没有他们,这门课程就不可能完成。

以下是需要感谢的人员名单:

  • Mi 或 Ammeir
  • Adrian Morrisso
  • Elliot Choi
  • Ivan Shang
  • Nils Ris
  • Patrick Lewis
  • Sebastian Hofsttter
  • S V She
  • 以及深度学习AI团队的优秀伙伴们。

我们非常期待看到您如何运用这些知识来构建出色的应用。


总结

本节课中,我们一起学习了本课程的总结,并了解了如何通过LLM University课程和Cohere Discord社区继续深入大型语言模型领域的学习与交流。感谢您的参与。

posted @ 2026-03-26 08:14  绝不原创的飞龙  阅读(1)  评论(0)    收藏  举报