大语言模型、检索增强生成和知识图谱的人工智能体构建指南(二)

原文:zh.annas-archive.org/md5/7f895f3dd97095da095ae95fcffc355d

译者:飞龙

协议:CC BY-NC-SA 4.0

第六章:信息检索和增强的高级 RAG 技术

在上一章中,我们讨论了 RAG 以及这种范式如何演变以解决 LLMs 的一些不足。然而,即使是简单的 RAG(这种范式的原始形式)也并非没有其挑战和问题。简单的 RAG 由几个简单的组件组成:一个嵌入器、一个用于检索的向量数据库和一个用于生成的 LLM。正如前一章所述,简单的 RAG 涉及将文本集合嵌入到数据库中;一旦用户查询到达,就会搜索与查询相关的文本块,并将其提供给 LLM 以生成响应。这些组件使我们能够有效地响应用户查询;但正如我们将看到的,我们可以添加额外的组件来改进系统。

在本章中,我们将看到在高级 RAG 中,我们可以修改或改进管道中的各个步骤(数据摄入、索引、检索和生成)。这解决了简单 RAG 的一些问题,并使我们能够更好地控制整个过程。我们稍后将看到,对更多灵活性的需求导致了进一步的进步(模块化 RAG)。我们还将讨论 RAG 的重要方面,特别是在系统(RAG 基础产品)被生产时。例如,我们将讨论在数据量或用户数量很大时的挑战。此外,由于这些系统可能包含敏感数据,我们将讨论鲁棒性和隐私。最后,尽管 RAG 是当今流行的系统,但它仍然相对较新。因此,仍然存在一些未解决的问题和令人兴奋的前景。

在本章中,我们将讨论以下主题:

  • 讨论简单的 RAG 问题

  • 探索高级 RAG 管道

  • 模块化 RAG 和与其他系统的集成

  • 实现高级 RAG 管道

  • 理解 RAG 的可扩展性和性能

  • 开放性问题

技术要求

本章中的大部分代码可以在 CPU 上运行,但最好在 GPU 上运行。代码是用 PyTorch 编写的,大部分使用标准库(PyTorch、Hugging Face Transformers、LangChain、chromadbsentence-transformerfaiss-cpu等)。

本章的代码可以在 GitHub 上找到:github.com/PacktPublishing/Modern-AI-Agents/tree/main/chr6

讨论简单的 RAG 问题

在上一章中,我们介绍了 RAG 的基本版本(称为简单 RAG)。尽管 RAG 的基本版本在解决 LLMs 的一些最紧迫问题方面已经取得了长足的进步,但仍然存在一些问题。特别是对于工业应用(以及医疗、法律和金融),简单的 RAG 是不够的,我们需要一个更复杂的管道。现在,我们将探讨与简单 RAG 相关的问题,每个问题都与管道中的特定步骤(查询处理、检索和生成)相关。

https://github.com/OpenDocCN/freelearn-dl-zh/raw/master/docs/bd-ai-agt-llm-rag-kg/img/B21257_06_01.jpg

图 6.1 – 简要概述了简单的 RAG 问题以及识别管道中可能出现问题的不同步骤

让我们详细讨论这些问题:

  • 检索挑战:检索阶段在精确度(检索到的片段不匹配)和召回率(找到所有相关片段)方面存在困难。此外,知识库可能过时。这可能导致幻觉,或者根据使用的提示,可能得到“对不起,我不知道答案”或“上下文不允许回答查询”之类的响应。这也可以从数据库索引不良或文档类型不同(PDF、HTML、文本等)且处理不当(例如,对所有文件类型进行分块)中得出。

  • 遗漏顶级文档:对于回答查询至关重要的文档可能不在列表的顶部。通过选择前 k 个文档,我们可能会选择不那么相关(或不含答案)的顶级片段,而没有返回真正相关的片段给 LLM。嵌入模型的语义表示能力可能较弱(即,我们选择了一个无效的模型,因为它太小或不适合我们文档的领域)。

  • 上下文中没有相关信息:找到了包含答案的文档,但太多以至于无法适应 LLM 的上下文。例如,响应可能需要几个片段,而这些片段对于模型的上下文长度来说太多了。

  • 提取失败:可能已经将正确的上下文返回给 LLM,但它可能无法提取正确的答案。通常,这发生在上下文中噪声过多或信息冲突的情况下。即使提示(上下文)中包含答案,模型也可能产生幻觉(上下文幻觉)。

  • 答案格式错误:查询可能有额外的具体要求。例如,我们可能希望 LLM 生成项目符号或以表格形式报告信息。LLM 可能会忽略这些信息。

  • 不正确的具体性:生成的答案不够具体或过于具体,不符合用户的需求。这通常与系统的设计及其目的有关。我们的 RAG 可能是为设计给学生的产品的一部分,必须就某个主题给出清晰和全面的答案。另一方面,模型可能回答得过于模糊或过于技术化,不适合学生。通常,当查询(或指令)不够清晰时,会出现这个问题。

  • 增强障碍或信息冗余:我们的数据库可能包含来自不同语料库的信息,许多文档可能包含冗余信息或具有不同的风格和语气。LLM 随后可能会生成重复内容和/或创造幻觉。此外,由于模型未能整合来自各个片段的信息,答案可能质量不高。

  • 不完整的答案:这些答案并不错误,但是不完整的(这可能是因为没有找到所有必要的信息或 LLM 在处理上下文时的错误)。有时,这也可能是查询过于复杂的问题(“总结 A、B 和 C 项”),因此修改查询可能更好。

  • 缺乏灵活性:这是当系统不够灵活时;它目前不允许高效更新,并且我们不能整合来自用户、过去交互等的反馈。系统不允许我们处理我们语料库中丰富的某些文件(例如,我们的系统不允许 Excel 文件)。

  • 可扩展性和整体性能:在这种情况下,我们的系统可能太慢,无法进行嵌入、生成响应等。或者,我们无法每秒处理多个文档的嵌入,或者我们遇到特定于我们的产品或领域的问题。系统安全性是一个痛点,尤其是如果我们有敏感数据的话。

现在我们已经了解了朴素 RAG 的问题,让我们来看看高级 RAG 是如何帮助我们解决这些问题的。

探索高级 RAG 管道

高级 RAG 引入了一系列特定的改进,试图解决朴素 RAG 中提出的问题。换句话说,高级 RAG 通过修改 RAG 的各个组件来尝试优化 RAG 范式。这些各种修改发生在 RAG 的不同步骤中:检索前检索后

检索前过程中,目的是优化索引和查询。例如,添加元数据可以提供更细粒度的搜索,并且我们向 LLM 提供更多内容以生成文本。元数据可以简洁地包含信息,否则这些信息将分散在整个文档中。

在朴素 RAG 中,我们将文档分成不同的片段,并为每个文档找到相关的片段。这种方法有两个局限性:

  • 当我们拥有许多文档时,它会影响延迟时间和性能

  • 当文档很大时,我们可能无法轻松找到相关的片段

在朴素 RAG 中,只有一个级别(即使它们来自不同的文档,所有片段都是等效的)。然而,一般来说,对于许多语料库,存在层次结构,利用它可能是有益的。

为了解决这些限制,高级 RAG 引入了几个增强功能,旨在提高检索和生成的效率。在接下来的小节中,我们将探讨一些技术

层次化索引

对于由多个章节组成的文档,我们首先可以找到感兴趣的章节,然后从那里搜索感兴趣的各个部分。由于章节可能相当大(噪声丰富),嵌入可能无法最好地代表它们的上下文意义。解决方案是使用摘要和元数据。在层次索引中,你在每个层次级别创建摘要(这可以被认为是摘要)。在第一级,我们有只突出显示大文档段中关键点的摘要。在较低级别,粒度将增加,这些摘要将越来越接近仅包含相关数据部分。接下来,我们将对这些摘要进行嵌入。在推理时间,我们将计算与这些摘要嵌入的相似度。当然,这意味着我们可能需要手动编写摘要,或者使用 LLM 进行摘要。然后,使用相关的元数据,我们可以找到与摘要匹配的块,并将其提供给模型。

https://github.com/OpenDocCN/freelearn-dl-zh/raw/master/docs/bd-ai-agt-llm-rag-kg/img/B21257_06_02.jpg

图 6.2 – 层次索引

如前图所示,语料库被分为文档;然后我们获取每个文档的摘要并将其嵌入(在简单的 RAG 中,我们是将文档分割成块并嵌入块)。在下一步中,我们将嵌入文档较低层次级别的摘要(章节、部分、标题和副标题),直到达到块级别。在推理时间,对摘要进行相似度搜索以检索我们感兴趣的块。

这种方法有一些变体。为了获得更多控制,我们可以为每种文件类型(HTML、PDF 和 GitHub 仓库)选择一个分割方法。这样,我们可以使摘要数据类型特定,并嵌入摘要,这相当于一种文本归一化。

当我们拥有的文档对于我们的 LLM 摘要器来说太长时,我们可以使用映射和归约,首先对文档的各个部分进行摘要,然后整理这些摘要,得到一个单一的摘要。如果文档过于百科全书式(即涉及太多主题),存在语义噪声影响检索的风险。为了解决这个问题,我们可以为每个文档提供多个摘要(例如,每 10K 个标记或每 10 页文档一个摘要)。

层次索引提高了对文档的上下文理解(因为它尊重其层次结构并捕获各个部分之间的关系,如章节、标题和副标题)。这种方法允许在查找结果时具有更高的准确性,并且它们更加相关。另一方面,这种方法在检索前阶段和推理阶段都有成本。层次太多,你可能会面临组合爆炸的风险,即由于可能组合的指数级增加而导致的复杂性的快速增长,以及巨大的延迟成本。

https://github.com/OpenDocCN/freelearn-dl-zh/raw/master/docs/bd-ai-agt-llm-rag-kg/img/B21257_06_03.jpg

图 6.3 – 层次索引变化

在前面的图中,我们可以看到这些层次索引的变化:

  • A: 对每种文档类型进行不同的处理,以更好地表示其结构

  • B: 映射和归约处理过长的文档(创建中间摘要,然后用于创建最终文档摘要)

  • C: 当文档讨论太多主题时,每个文档的多摘要

假设性问题与 HyDE

对简单的 RAG 管道的另一种修改是尝试使块和可能的问题在语义上更加相似。通过了解我们的用户是谁,我们可以想象他们将从我们的系统中获得什么样的用途(对于一个聊天机器人,大多数查询将是问题,因此我们可以调整系统以适应这些类型的查询)。假设性问题是一种策略,其中我们使用一个 LLM 为每个块生成一个(或多个)假设性问题。然后,将这些假设性问题转换为向量(嵌入),并在有用户查询时使用这些向量进行相似性搜索。当然,一旦我们确定了与我们的实际查询最相似的假设性问题,我们就找到块(多亏了元数据)并将它们提供给模型。我们可以为每个块生成单个查询或多个查询(这增加了准确性以及计算成本)。在这种情况下,我们不是使用块的向量表示(我们不进行块的嵌入,而是假设性问题)。此外,我们不一定需要保存假设性问题,只需它们的向量(重要的是我们能够将它们映射回块)。

假设文档嵌入HyDE)则试图将用户答案转换为更好地匹配块。给定一个查询,我们为它创建假设性答案。之后,我们对这些生成的答案进行嵌入,并执行相似性搜索以找到感兴趣的块。这些生成的答案应该与用户的查询在语义上最为相似,使我们能够找到更好的块。在某些变体中,我们创建五个不同的生成答案,并在进行相似性搜索之前对这些答案的嵌入向量进行平均。这种方法可以帮助我们在检索步骤中召回率低时,或者当文档(或查询)来自与检索域不同的特定领域时。事实上,嵌入模型在它们未见过的知识域中泛化得不好。一个有趣的注释是,当 LLM 生成这些假设性答案时,它不知道确切的答案(这甚至不是这种方法的目的),但它能够捕捉到问题中的相关模式。然后,我们可以使用这些捕获的模式来检索块。

让我们详细地看看两种方法之间的差异。在假设问题方法中,我们生成假设问题,并使用这些假设问题的嵌入来找到感兴趣的片段。在 HyDE 中,我们生成针对查询的假设答案,然后使用这些答案的嵌入来找到感兴趣的片段。

https://github.com/OpenDocCN/freelearn-dl-zh/raw/master/docs/bd-ai-agt-llm-rag-kg/img/B21257_06_04.jpg

图 6.4 – 假设问题和 HyDE 方法

然后,我们可以详细地比较两种方法之间的差异,假设我们有一个假设用户问题(“使用对乙酰氨基酚的潜在副作用是什么?”):

  • 检索前阶段:在此阶段,我们必须创建我们的药物嵌入数据库。我们将文档(药物安全报告的部分)减少为片段。在假设问题方法中,对于每个片段,使用 LLM 生成假设问题(例如,“这种药物的副作用是什么?”或“是否有提到任何不良反应?”)。然后,将这些假设问题嵌入到向量空间中(这些问题的向量数据库)。在此阶段,HyDE 等同于经典的 RAG;没有进行任何变异。

  • 查询阶段:在假设问题方法中,当用户提交查询时,它会被嵌入并与嵌入的假设问题进行匹配。系统寻找与用户问题最相似的假设问题(在这种情况下,可能是“这种药物的副作用是什么?”)。在此阶段,识别出生成这些假设问题的片段(我们使用元数据)。这些片段被提供在上下文中进行生成。在 HyDE 中,当用户查询到达时,LLM 生成假设答案(例如,“对乙酰氨基酚可能引起恶心、肝损伤和皮疹等副作用”或“潜在的不良反应包括头晕和胃肠道不适”)。

    注意,这些答案是通过不检索的 LLM 知识生成的。在此阶段,我们对这些假设答案进行嵌入(我们使用嵌入模型),然后对查询进行嵌入,并尝试将其与嵌入的假设答案匹配。例如,“对乙酰氨基酚可能引起恶心、肝损伤和皮疹等副作用”是与用户查询最接近的一个。然后我们搜索与这些假设答案最接近的片段,并将这些片段提供给 LLM 生成上下文。

上下文丰富

另一种技术是上下文丰富,其中我们找到更小的段(更细粒度以获得更好的搜索质量),然后添加周围上下文。句子窗口检索是其中一种技术,其中文档中的每个句子都单独嵌入(嵌入的文本单元更小,因此更细粒度)。这使我们能够在寻找答案时具有更高的精确度,尽管我们可能会失去 LLM 推理的上下文(因此生成效果较差)。为了解决这个问题,我们扩展了上下文窗口。在找到句子 x 后,我们取文档中围绕它的 k 个句子(在文档中位于我们的句子 x 之前和之后的句子)。

父文档检索器 是一种类似的技术,试图在搜索小段和提供大段上下文之间找到平衡。文档被分成小段子文档,但我们保留了它们父文档的层次结构。在这种情况下,我们直接针对查询的具体内容进行小段的嵌入(确保大段的相关性)。然后我们找到较大的父文档(找到的段所属的文档)并将它们提供给 LLM 进行生成(更多上下文信息和深度)。为了避免检索过多的父文档,一旦找到前 k 个段,如果超过 n 个段属于一个父文档,我们将此文档添加到 LLM 上下文中。

这些方法在以下图中展示:

  1. 一旦找到一个段,我们就使用前一个和后一个段来扩展选择。

  2. 我们对小段文本进行嵌入并找到前 k 个段;如果大部分段(大于参数 n)都来自一个文档,我们将该文档作为上下文提供给 LLM。

https://github.com/OpenDocCN/freelearn-dl-zh/raw/master/docs/bd-ai-agt-llm-rag-kg/img/B21257_06_05.jpg

图 6.5 – 上下文丰富方法

查询转换

查询转换 是一系列利用 LLM 来提高检索的技术。如果一个查询过于复杂,它可以被分解成一系列子查询。实际上,我们可能找不到一个直接响应查询的段,但更容易找到响应每个子查询的段(例如,“电报和电话的发明者是谁?”最好分解成两个独立的查询)。回退提示 使用 LLM 生成一个更通用的查询,以匹配高级上下文。这源于当人类面对困难任务时,他们会退一步进行抽象以到达高级原则的想法。在这种情况下,我们使用这个高级查询的嵌入和用户的查询,并将这两个找到的上下文都提供给 LLM 进行生成。查询重写,另一方面,使用 LLM 重新表述初始查询以简化检索。

https://github.com/OpenDocCN/freelearn-dl-zh/raw/master/docs/bd-ai-agt-llm-rag-kg/img/B21257_06_06.jpg

图 6.6 – 查询转换的三个示例

查询扩展是一种类似于查询重写的技巧。其背后的理念是,向查询中添加术语可以使它找到与查询没有词汇重叠的相关文档(从而提高检索召回率)。同样,我们使用 LLM 来修改查询。有两种主要可能性:

  • 请求一个 LLM(大型语言模型)对查询生成答案,之后将生成的答案和查询嵌入并用于检索。

  • 生成几个与原始查询相似的查询(通常是一个前缀数字n)。然后,将这n个查询向量化并用于搜索。

这种方法通常可以提高检索效果,因为它有助于消除查询歧义并找到否则可能找不到的文档;它还有助于系统更好地编制查询。然而,另一方面,它也可能导致找到不相关的文档,因此结合后处理技术来查找文档是值得的。

关键词搜索和混合搜索

提高搜索的另一种方法是不仅要关注上下文信息,还要关注关键词。基于关键词的搜索是通过某些特定关键词的精确匹配来进行的搜索。这种搜索对特定术语(如产品或公司名称或特定行业术语)有益。然而,它对拼写错误和同义词敏感,并且无法捕捉上下文。向量或语义搜索则相反,它找到查询的语义含义,但找不到精确的术语或关键词(这在某些查询中有时是必不可少的,尤其是在某些领域,如市场营销)。混合搜索通过结合关键词搜索和向量搜索的模型,取两者之长。

最常用的关键词搜索模型是 BM25(我们在上一章中讨论过),它生成稀疏嵌入。BM25 然后允许我们识别包含查询中特定术语的文档。因此,我们创建了两个嵌入:一个使用 BM25 的稀疏嵌入和一个使用 transformer 的密集嵌入。为了选择最佳片段,你通常试图平衡不同类型搜索的影响。最终得分是两个得分的加权组合(你使用一个 alpha 超参数):

<mrow><mrow><msub><mrow><mi>s</mi><mi>c</mi><mi>o</mi><mi>r</mi><mi>e</mi></mrow><mrow><mi>h</mi><mi>y</mi><mi>b</mi><mi>r</mi><mi>r</mi><mi>i</mi><mi>d</mi></mrow></msub><mo>=</mo><mfenced close=")" open="("><mrow><mn>1</mn><mo>−</mo><mi mathvariant="normal">α</mi></mrow></mfenced><mo>∙</mo><msub><mrow><mi>s</mi><mi>c</mi><mi>o</mi><mi>r</mi><mi>e</mi></mrow><mrow><mi>s</mi><mi>p</mi><mi>a</mi><mi>r</mi><mi>s</mi><mi>e</mi></mrow></msub><mo>+</mo><mi mathvariant="normal">α</mi><mo>∙</mo><msub><mrow><mi>s</mi><mi>c</mi><mi>o</mi><mi>r</mi><mi>e</mi></mrow><mrow><mi>d</mi><mi>e</mi><mi>n</mi><mi>s</mi><mi>e</mi></mrow></msub></mrow></mrow>

α 的值介于 0 和 1 之间(0 表示纯向量搜索,而 1 表示仅关键词搜索)。通常,α 的值是 0.4 或 0.5(其他文章甚至建议 0.3)。

作为实际例子,我们可以想象一个拥有庞大产品目录的电子商务平台,包含数百万种商品,涵盖电子、时尚和家用电器等类别。用户会使用不同类型的查询来搜索产品,以下是一些可能的查询类型:

  • 特定术语,如品牌或产品名称(例如,“iPhone 16”)

  • 一般描述(例如,“中等价位的具有良好摄像头的手机”)

  • 包含混合元素的查询(例如,“价格低于 500 美元的 iPhone”)

纯关键词搜索(例如 BM25 算法)在处理模糊或纯粹描述性的描述时会遇到困难,而基于向量的语义搜索可能会错过产品的精确匹配。混合搜索结合了两者之长。BM25 优先考虑精确匹配,如“iPhone”的匹配,使我们能够通过关键词找到特定项目。语义搜索允许我们捕捉到“具有良好摄像头的手机”之类的短语的语义含义。混合搜索是解决前述三种情况的一个很好的解决方案。

查询路由

到目前为止,我们假设一旦查询到达,就会在向量数据库中进行搜索。实际上,我们可能希望以不同的方式或控制系统内的流程进行搜索。例如,系统应该能够与不同类型的数据库(向量、SQL 和专有数据库)、不同来源或不同类型的模态(图像、文本和声音)进行交互。因此,一些查询可能不需要使用 RAG 进行搜索;模型的参数化内存可能就足够了(我们将在开放问题和未来展望部分进行更深入的讨论)。查询路由因此允许控制系统如何响应查询。你可以想象它是一系列 if/else 条件,尽管它不是硬编码的,我们有一个路由器(通常是一个 LLM),每当查询到达时都会做出决定。显然,这意味着我们有一个非确定性的系统,它并不总是能做出正确的决定,尽管它可以对性能产生重大积极影响。

路由器可以是一组逻辑规则或一个神经网络模型。以下是一些路由器的选项:

  • 逻辑路由器:一组逻辑规则,可以是 if/else 语句(例如,如果查询是图像,则搜索图像数据库;否则,搜索文本数据库)。逻辑路由器不理解查询,但它们非常快且确定。

  • 关键词路由器:一种稍微复杂一些的替代方案,我们尝试通过在查询和选项列表之间匹配关键词来选择路由。这种搜索可以使用稀疏编码器、专用包甚至 LLM 来完成。

  • 零样本分类路由器:零样本分类是一个任务,其中要求一个大型语言模型(LLM)对一组未指定和训练过的标签进行分类。每个查询都提交给一个 LLM,它必须从列表中选择一个路由标签。

  • LLM 函数调用路由器:不同的路由被描述为函数(具有特定的描述),模型必须通过选择函数(在此方法中,我们利用其决策能力)来决定将查询导向何处。

  • 语义路由器:在此方法中,我们使用语义搜索来决定最佳路由。简而言之,我们有一个示例查询列表及其相关路由。然后,这些查询被嵌入并保存为数据库中的向量。当查询到达时,我们对我们数据库中的其他查询进行相似度搜索。然后,我们选择与最佳相似度匹配的查询相关的选项。

https://github.com/OpenDocCN/freelearn-dl-zh/raw/master/docs/bd-ai-agt-llm-rag-kg/img/B21257_06_07.jpg

图 6.7 – 查询路由

一旦我们找到了上下文,我们需要将其与查询整合,并提供给 LLM 进行生成。有几种策略可以改进这个过程,通常称为检索后策略。在向量搜索之后,检索返回前k个文档(一个预先确定的任意截止值)。这可能导致相关信息丢失。最简单的解决方案是增加前k个块的价值。显然,我们无法返回所有检索到的块,因为它们不会适合模型的上下文长度,而且 LLM 在处理所有这些信息时也会遇到问题(有效利用长上下文长度)。

我们可以想象一家公司提供不同领域的不同服务,例如银行、保险和金融。客户通过与聊天机器人互动来寻求银行服务(账户详情、交易等)、保险服务(保单详情、索赔等)和金融服务(建议、投资等)的协助。每个领域都有其独特性。由于法规和隐私问题,我们希望防止聊天机器人搜索其他服务的客户详情。此外,对每个查询搜索所有数据库既不高效,还会导致更多延迟和无关的结果。

重排序

解决这一困境的一个建议方案是最大化文档检索(增加前k个检索到的结果,从而提高检索召回率指标),同时最大化 LLM 的召回率(通过最小化提供给 LLM 的文档数量)。这种策略被称为重排序。重排序包括两个步骤:

  1. 首先,我们进行经典检索并找到大量块。

  2. 接下来,我们使用重排序器(第二个模型)重新排序块,然后选择前k个块提供给 LLM。

重新排序器提高了返回给 LLM 的块的质量,并减少了系统中的幻觉。此外,重新排序考虑了与查询相关的对比信息,然后考虑与查询相关的上下文中的块。有几种类型的重新排序器,每种都有其自身的局限性和优点:

  • 交叉编码器:这些是变换器(如 BGE),它们接受两个文本序列(查询和各个块逐个)作为输入,并返回 0 到 1 之间的相似度。

  • 多向量重新排序器:这些仍然是变换器(如 ColBERT)并且比交叉编码器(两个序列之间的交互是后期阶段)需要的计算更少。原理类似;给定两个序列,它们返回 0 到 1 之间的相似度。有改进版本,具有较长的上下文长度,如 jina-colbert-v1-en。

  • 用于重新排序的 LLM:LLM 也可以用作重新排序器。使用几种策略来提高 LLM 的排名能力:

    • 点 wise 方法用于计算查询和单个文档的相关性(也称为零样本文档重新排序)。

    • 成对方法包括向 LLM 提供查询和两个文档,并要求它选择哪个更相关。

    • 按列表方法,另一方面,建议向 LLM(大型语言模型)提供一个查询和一个文档列表,并指示它生成一个按排名排序的输出列表。通常使用 GPT 等模型,但存在高计算或经济成本的风险。

  • 微调的 LLM:这是一类专门用于排名任务的模型。尽管 LLM 是通用模型,但它们没有针对排名的特定训练,因此无法准确衡量查询-文档的相关性。微调使它们能够提高其能力。通常,使用两种类型的模型:编码器-解码器变换器(RankT5)或仅解码器变换器(例如 Llama 和 GPT 的衍生品)。

所有这些方法都会对性能(检索质量)和成本(计算成本、系统延迟和潜在的系统成本)产生影响。一般来说,多向量是那些计算成本较低且性能离散的。基于 LLM 的方法可能具有最佳性能,但计算成本很高。一般来说,重新排序对系统有积极影响,这也是为什么它经常是管道的一部分。

https://github.com/OpenDocCN/freelearn-dl-zh/raw/master/docs/bd-ai-agt-llm-rag-kg/img/B21257_06_08.jpg

图 6.8 – 重新排序方法。用红色突出显示的块是与查询相关的块

另外,还有其他后处理技术。例如,如果达到的相似度低于某个分数阈值,如果它们不包含某些关键词,如果与块关联的元数据中不存在某个值,如果块比某个日期旧,以及许多其他可能性。另一个策略是,一旦我们找到了块,从嵌入向量开始,我们进行k 近邻kNN)研究。换句话说,我们在那些找到的块在潜在空间中的邻居中添加其他块(这种策略可以在重新排序之前或之后进行)。

此外,一旦选择了要提供给 LLM 的上下文中的块,我们就可以改变它们的顺序。如图所示,2023 年发表的一项研究表明,对于 LLM 来说,将重要信息放在输入上下文长度的开头或末尾可以获得最佳性能(如果信息位于上下文长度的中间,性能会下降,尤其是如果它非常长):

https://github.com/OpenDocCN/freelearn-dl-zh/raw/master/docs/bd-ai-agt-llm-rag-kg/img/B21257_06_09.jpg

图 6.9 – 改变相关信息的位置会影响 LLM 的性能 (arxiv.org/abs/2307.03172)

正因如此,有人提出了重新排序块的建议。它们可以按照相关性排序,也可以按照交替模式(偶数索引的块放在列表开头,奇数索引的块放在列表末尾)。交替模式特别适用于使用宽顶 k 块时,这样最相关的块就被放在了开头和末尾(而不那么相关的块则位于上下文长度的中间)。

你可以注意到重新排序提高了系统的性能:

https://github.com/OpenDocCN/freelearn-dl-zh/raw/master/docs/bd-ai-agt-llm-rag-kg/img/B21257_06_10.jpg

图 6.10 – 重新排序提高了问答性能 (arxiv.org/pdf/2409.07691)

除了重新排序之外,还可以在检索阶段之后应用几种补充技术来进一步细化传递给 LLM 的信息。这包括提高引用准确性的方法、管理聊天历史、压缩上下文和优化提示公式。让我们看看其中的一些。

参考文献引用并不是系统改进的技术,但它作为 RAG 系统的一个组成部分被高度推荐。特别是如果我们使用不同的来源来组成我们的查询响应,跟踪使用了哪些来源(例如,LLM 使用的文档)是很好的。我们可以简单地保护用于生成的来源(哪些文档的块对应)。另一种可能性是在 LLM 的提示中提及使用的来源。一种更复杂的技术是模糊引用查询引擎。模糊匹配是对生成的响应与找到的块进行字符串搜索以匹配(一种基于将块中的单词分为 n-gram 然后进行 TF-IDF 的技术)。

ChatEngine是 RAG 的另一个扩展。对模型进行微调是复杂的,但与此同时,我们希望 LLM 记住与用户的先前交互。RAG 使得这样做变得容易,因此我们可以保存与用户的先前对话。一种简单的方法是将之前的聊天包括在提示中。或者,我们可以对聊天进行嵌入并找到亮点。另一种技术是尝试捕捉用户对话的上下文(聊天逻辑)。由于讨论可能贯穿几条消息,为了避免可能超过上下文长度的提示,一种解决方案是提示压缩。我们通过减少与用户的先前交互来缩短提示长度。

通常来说,上下文压缩是一个有助于 LLM 在生成过程中使用的概念。它还能节省计算(或经济,如果通过 API 使用模型)资源。一旦找到文档,我们可以压缩上下文,目的是只保留相关信息。实际上,上下文通常也包含与查询无关的信息,甚至重复信息。此外,句子中的大多数单词都可以直接从上下文中预测出来,因此不需要在生成过程中提供给 LLM。有几种策略可以减少提供给 LLM 的提示:

  • 上下文过滤:在信息理论中,低熵的标记容易被预测,因此包含冗余信息(向 LLM 提供较少的相关信息,对其理解上下文的影响很小)。因此,我们使用一个 LLM 为每个词汇单元分配一个信息值(它期望在上下文中看到该标记或句子的程度)。我们按降序进行排名,只保留位于前 p 百分位的标记(我们事先决定,或者它可以依赖于上下文)。

  • LongLLMLingua:这是基于信息熵的另一种方法,使用上下文和查询(问题感知)中的信息。该方法通过动态压缩和重新排序文档来提高生成效率。

  • 自动压缩器:这使用了一种对系统和摘要向量的微调。其背后的想法是,长文本可以被总结成一个小的向量表示(摘要向量)。这些向量可以用作软提示,为模型提供上下文。这个过程依赖于在提示中引入可训练的标记的同时保持 LLM 的权重冻结。这些标记在训练期间学习,使得系统可以在不修改模型核心参数的情况下进行端到端优化。在生成过程中,这些向量被连接起来,模型随后变得具有上下文意识。已经训练好的模型如下:

https://github.com/OpenDocCN/freelearn-dl-zh/raw/master/docs/bd-ai-agt-llm-rag-kg/img/B21257_06_11.jpg

图 6.11 – A) 上下文压缩和过滤。B) 自动压缩器。(改编自arxiv.org/abs/2305.14788)

提示工程是提高生成的另一种解决方案。一些建议适用于与任何 LLM 的交互。因此,提供清晰(“使用上下文回复”)和不模糊(“如果答案不在上下文中,写我不知道”)的指令原则适用于 RAG。然而,可能存在针对设计我们系统最佳提示的具体方向或甚至示例。其他指令可能特定于我们想要的输出方式(例如,作为列表、HTML 等)。还有用于创建遵循特定格式的 RAG 提示的库。

响应优化

在进行最终响应之前,管道中的最后一步是改进用户的响应。一种策略是响应合成器。基本策略是将提示、上下文和查询连接起来,并将其提供给 LLM 进行生成。更复杂的策略涉及 LLM 的更多调用。对此想法有几种替代方案:

  • 一次使用一个片段迭代地细化响应。将之前的响应和随后的片段发送到模型中,以新的信息改进响应。

  • 使用不同的片段生成几个响应,然后将它们全部连接起来,生成一个总结响应。

  • 层次化摘要从为每个不同上下文生成的响应开始,递归地组合它们,直到我们得到单个响应。虽然这种方法提高了摘要和生成答案的质量,但它需要显著更多的 LLM 调用,因此在计算资源和财务费用方面都变得昂贵。

一个有趣的发展是使用 RAG 作为智能体系统组件的可能性。正如我们在*第四章中介绍的那样,RAG 可以作为系统的记忆。RAG 可以与智能体**结合。一个大型语言模型(LLM)能够进行推理,可以与 RAG 合并,并在查询需要额外步骤时调用工具或连接到网站。智能体还可以处理不同的组件(检索聊天历史、执行查询路由、连接到 API 和执行代码)。一个复杂的 RAG 流程可以包含几个不适合每个情况的组件,而 LLM 可以决定使用哪些最佳组件。

https://github.com/OpenDocCN/freelearn-dl-zh/raw/master/docs/bd-ai-agt-llm-rag-kg/img/B21257_06_12.jpg

图 6.12 – 高级 RAG 流程中的不同元素

到目前为止,我们假设流程应该只执行一次。标准做法是我们先进行检索,然后生成。然而,对于需要多步推理的复杂问题,这种方法可能是不够的。在这种情况下有三种可能性:

  • 迭代检索:在这种情况下,检索会进行多次。给定一个查询,我们进行检索,生成结果,然后由 LLM 进行判断。根据判断,我们重复这个过程,直到 n 次。这个过程提高了每次迭代后答案的鲁棒性,但也可能导致无关信息的积累。

  • 递归检索:这个系统是为了增加搜索结果的深度和相关性而开发的。它与之前的一个类似,但在每次迭代中,查询会根据之前的搜索结果进行细化。目的是通过利用反馈循环来找到最相关的信息。许多这些方法利用思维链CoT)来指导检索过程。在这种情况下,系统将查询分解成一系列必须解决的中间步骤。当查询不是特别清晰或所需信息高度专业或需要仔细考虑细微差别时,这种方法是有利的。

  • 自适应检索:在这种情况下,LLM 主动决定何时搜索以及检索的内容是否最优。LLM 不仅判断检索步骤,还判断自己的操作。LLM 可以决定何时响应、何时搜索或是否需要额外的工具。这种方法不仅用于在 RAG 上搜索时,也用于进行网络搜索。Flare(一种 RAG 的自适应方法)在生成过程中分析置信度,并在置信度低于某个阈值时做出决定。另一方面,Self-RAG 引入了反思令牌来监控过程并强制 LLM 进行反思。

https://github.com/OpenDocCN/freelearn-dl-zh/raw/master/docs/bd-ai-agt-llm-rag-kg/img/B21257_06_13.jpg

图 6.13 – RAG 管道的增强 (arxiv.org/pdf/2312.10997)

为了更好地理解高级 RAG 技术如何解决已知的限制,表 6.1展示了关键问题与最近研究中提出的最有效解决方案之间的映射。

需要解决的问题解决方案
朴素 RAG 的问题:在许多或大型文档中延迟和性能下降使用分层索引:总结大块,创建多级嵌入,使用元数据,并实现长文档的 map-reduce 或多主题的多摘要等变体。
当语料库包含固有结构时,扁平层次结构限制了相关性应用分层索引:尊重文档的结构(章节、标题和副标题),并根据分层摘要和嵌入检索上下文。
低检索准确率和特定领域的泛化挑战为每个块生成和嵌入假设问题(假设 Qs)。使用 HyDE:生成与查询语义匹配的假设答案,嵌入它们,并检索相关块。
在细粒度分块中丢失上下文使用上下文丰富:使用句子窗口扩展检索到的块周围的上下文,或检索父文档以扩展上下文。
从初始检索中产生的复杂查询和低召回率应用查询转换:将复杂查询分解为子查询,使用回退提示或查询扩展。嵌入转换后的查询以改进检索。
特定术语或关键词的上下文不匹配使用混合搜索:结合基于关键词的(例如,BM25)和基于向量的检索,使用加权评分。
管理多种查询类型时的低效实现查询路由:使用逻辑规则、基于关键词或语义分类器、零样本模型或基于 LLM 的路由器将查询定向到适当的后端。
由于任意的 top-k 截止值而丢失相关块应用重新排序:使用交叉编码器、多向量重新排序器或基于 LLM 的(点对点、成对或列表)重新排序来重新排序检索到的块。
在 LLM 上下文中丢失信息或效率使用上下文压缩:过滤低熵标记,动态压缩或重新排序块(例如,LongLLMLingua),或应用摘要向量和自动压缩器。
低效的响应生成优化响应:使用迭代细化、分层摘要或多步响应合成。提高提示质量和特异性。
对话系统中的内存限制使用 ChatEngine 技术:保存并嵌入过去的对话,压缩用户对话,并将聊天历史与当前查询合并。
需要复杂推理或动态查询适应采用自适应和多步骤检索:使用递归、迭代方法,并带有反馈循环和自我反思(例如,Flare,Self-RAG)。
生成响应中缺乏源追踪包含引用:使用模糊引用匹配、元数据标记或在提示中嵌入源引用。
根据查询复杂性模态进行管道定制的需求增强 RAG 管道:与推理、工具使用和决策的代理结合。对于复杂查询,应用自适应和递归检索循环。

表 6.1 – RAG 中的问题和解决方案

模块化 RAG 及其与其他系统的集成

模块化 RAG 是一个进一步的发展;它可以被视为高级 RAG 的扩展,但更侧重于适应性和多功能性。在这种情况下,模块化系统意味着它具有可以单独使用或并行使用的独立组件。

管道本身经过重新设计,交替进行搜索和生成。一般来说,模块化 RAG 涉及优化系统性能并适应不同任务。模块化 RAG 引入了专门化的模块来实现这一点。以下是一些包含的模块示例:

  • 搜索模块:此模块负责查找关于查询的相关信息。它允许通过搜索引擎、数据库和知识图谱KGs)进行搜索。它还可以使用复杂的搜索算法、机器学习并执行代码。

  • 记忆模块:此模块用于在搜索过程中存储相关信息。此外,系统可以检索之前搜索过的上下文。

  • 路由模块:此模块试图确定查询的最佳路径,它可以在不同的数据库中搜索不同的信息或分解查询。

  • 生成模块:不同的查询可能需要不同类型的生成,例如摘要、释义和上下文扩展。此模块的重点是提高输出质量和相关性。

  • 任务自适应模块:此模块允许系统动态适应请求的任务。这样,系统可以动态调整检索、处理和生成。

  • 验证模块:此模块评估检索到的响应和上下文。系统可以识别错误、偏见和不一致性。这个过程变得迭代,系统可以改进其响应。

https://github.com/OpenDocCN/freelearn-dl-zh/raw/master/docs/bd-ai-agt-llm-rag-kg/img/B21257_06_14.jpg

图 6.14 – RAG 的三个不同范式 (arxiv.org/pdf/2312.10997)

模块化 RAG 具有适应性优势,因为这些模块可以根据需要替换或重新配置。不同模块之间的流程可以精细调整,从而提供额外的灵活性。此外,如果原始和高级 RAG 以“检索和阅读”机制为特征,那么模块化 RAG 允许“检索、阅读和重写”。实际上,通过评估和提供反馈的能力,系统可以优化对查询的响应。

随着这一新范式的传播,人们尝试了有趣的可选方案,例如整合来自 LLM 参数化记忆的信息。在这种情况下,模型在检索之前被要求生成一个响应(背诵和回答)。演示-搜索-预测DSP)展示了如何通过 LLM 和 RAG 之间的不同交互来解决复杂查询(或知识密集型任务)。DSP 展示了模块化 RAG 如何同时实现稳健和灵活的管道。另一方面,自我反思的检索增强生成Self-RAG)将批评元素引入系统。LLM 反思它所生成的,从事实性和整体质量的角度对其输出进行批评。另一种选择是使用交错 CoT 生成和检索。这些方法通常在我们有需要推理的问题时效果最好。

训练和无训练方法

RAG 方法分为两组:无训练和基于训练。原始 RAG 方法通常被认为是无训练的。无训练意味着系统的两个主要组件(嵌入器和 LLM)从一开始就被冻结。这是可能的,因为它们是两个预训练的组件,因此已经获得了使我们能够使用它们的技能。

或者,我们可以有三种基于训练的方法:独立训练、顺序训练和联合训练。

独立训练中,检索器和 LLMs 在完全独立的过程中分别进行训练(训练过程中没有交互)。在这种情况下,我们对系统的各个组成部分进行单独的微调。当我们想要将我们的系统适应到特定领域(例如法律、金融或医疗)时,这种方法很有用。与无训练方法相比,这种类型的训练提高了系统对我们应用领域的功能。LLMs 也可以进行微调,以更好地利用上下文。

顺序训练,另一方面,假设我们按顺序使用这两个组件,因此最好找到一种能够增加这些组件之间协同效应的训练形式。组件可以先独立训练,然后按顺序训练。其中一个组件保持冻结状态,而另一个则进行额外的训练。根据训练的顺序,我们可以有两种类型,检索优先或 LLM 优先:

  • 检索优先:在这个课程中,训练者的训练是先进行的,然后保持冻结状态。然后,训练语言模型(LLM)理解如何在检索器上下文中使用知识。例如,我们独立地对检索器进行微调,然后使用检索到的片段对 LLM 进行微调。LLM 在其微调过程中接收检索器片段,并学习如何最好地使用这个上下文进行生成。

  • LLM 优先:这稍微复杂一些,但它是利用 LLM 的监督来训练检索器。LLM 通常比检索器更强大,因为它有更多的参数,并且已经在更多的标记上进行了训练,因此它是一个很好的监督者。从某种意义上说,这种方法可以被视为一种知识蒸馏,我们利用更大模型的更多知识来训练更小的模型。

训练方法领域/应用推理
检索优先**搜索引擎(通用或特定领域)**例如,法律文件搜索,医学文献搜索,或电子商务产品搜索专注于快速准确地检索最相关的文档。对于领域特定精度至关重要的系统,检索器必须处理大量、结构化或半结构化语料库。
企业知识管理例如,内部企业文档,常见问题解答,或 CRM 系统强调从专有数据库中高效检索正确的文档,其中检索的质量比生成的质量有更大的影响。
科学研究存储库例如,PubMed,arXiv,或专利确保在高度技术或专业领域中进行精确和召回优化的检索,在这些领域中,高质量的检索对于下游任务(如摘要或报告生成)至关重要。
监管和合规系统例如,金融合规检查,或法律案例法数据库在准确性和合规性至关重要的领域,检索器必须可靠地呈现最相关的内容,同时最大限度地减少不相关或低置信度的检索。
LLM-first对话代理例如,客户支持聊天机器人或个人助理严重依赖 LLM 的生成能力来提供细微、对话式的响应。检索是次要的,因为 LLM 解释并整合检索到的内容。
创意应用例如,内容创作、讲故事或头脑风暴LLM 从检索数据中创建、综合和推断的能力至关重要。检索通过提供更广泛的环境来支持生成,而不是作为优化的焦点。
复杂推理任务例如,多步骤问题解决或决策系统LLM 作为推理者的角色超过了检索的精确度,因为重点是处理、关联和推断知识的能力。检索主要确保推理时能够访问补充信息。
教育工具例如,学习助手或个性化辅导系统LLM 适应并生成针对用户上下文定制的教学内容的能 力比精确检索更重要。检索作为次要机制,确保信息的完整性。

表 6.2 – 训练方法

根据这篇文章(Izacard,arxiv.org/abs/2012.04584),LLM 中的注意力激活值是定义文档相关性的良好代理,因此它们可以用来为检索器提供标签(一种指南),以说明搜索结果有多好。因此,检索器使用基于 LLM 中注意力的度量进行训练。对于一种更经济的方案,可以使用一个小型 LLM 来生成标签,然后训练检索器。这些方法有各种变体,但所有方法都基于这样一个原则:一旦我们微调了 LLM,我们希望使检索器与之对齐。

另一方面,联合方法代表系统的端到端训练。换句话说,检索器和生成器同时(同时)对齐。我们的想法是希望系统同时提高其发现知识的能力和使用这些知识进行生成的能力。优势在于我们在训练期间有协同效应。

https://arxiv.org/pdf/2405.06211(img/B21257_06_15.jpg)

图 6.15 – RAG 中的不同训练方法(arxiv.org/pdf/2405.06211)

现在我们知道了我们可以应用到我们的 RAG 上的不同修改,让我们在下一节尝试它们。

实施高级 RAG 管道

在本节中,我们将描述如何实现高级 RAG 管道。在这个管道中,我们使用朴素 RAG 的更高级版本,包括一些附加组件来改进它。这表明我们的起始基础是一个经典的 RAG 管道(嵌入、检索和生成),但插入了一些更复杂的组件。在这个管道中,我们使用了以下附加组件:

  • 重排器:这允许我们在检索步骤中找到的上下文中进行排序。这是高级 RAG 中最广泛使用的元素之一,因为它已被证明可以显著提高结果。

  • 查询转换:在这种情况下,我们使用简单的查询转换。这是因为我们希望尝试扩大我们的检索范围,因为一些相关文档可能会被遗漏。

  • 查询路由:这使我们能够避免对所有查询一视同仁,并允许我们为更有效的检索建立规则。

  • 混合搜索:通过这种方式,我们将基于关键词的搜索的强大功能与语义搜索相结合。

  • 摘要:通过这种方式,我们尝试从检索到的上下文中消除冗余信息。

当然,我们可以添加其他组件,但通常,这些是最常用的,并概述了我们可以添加到朴素 RAG 中的组件。

我们可以在以下图中看到我们的管道是如何修改的:

https://github.com/OpenDocCN/freelearn-dl-zh/raw/master/docs/bd-ai-agt-llm-rag-kg/img/B21257_06_16.jpg

图 6.16 – 高级 RAG 的管道

完整的代码可以在仓库中找到;在这里,我们只看重点。在这个代码片段中,我们定义了一个函数来表示查询转换。在这种情况下,我们只是在查询中进行了小的修改(搜索查询中的其他相关术语):

def advanced_query_transformation(query):
    """
    Transforms the input query by adding synonyms, extensions, or modifying the structure
    for better search performance.
    Args:
        query (str): The original query.
    Returns:
        str: The transformed query with added synonyms or related terms.
    """
    expanded_query = query + " OR related_term"
    return expanded_query

接下来,我们执行查询路由。查询路由强制执行一个简单的规则:如果查询中存在特定关键词,则执行基于关键词的搜索;否则,使用语义(基于嵌入)搜索。在某些情况下,我们可能首先只想检索包含某些关键词的文档——例如,对特定产品的引用——然后使用语义搜索进一步缩小结果:

def advanced_query_routing(query):
    """
    Determines the retrieval method based on the presence of specific keywords in the query.
    Args:
        query (str): The user's query.
    Returns:
        str: 'textual' if the query requires text-based retrieval, 'vector' otherwise.
    """
    if "specific_keyword" in query:
        return "textual"
    else:
        return "vector"

接下来,我们执行混合搜索,这使我们能够使用基于语义和关键词内容的搜索。这是今天 RAG 管道中最广泛使用的组件之一。当使用分块时,有时只有包含关键词的文档与查询相关(例如,产品的名称、人名等)。显然,并非所有包含关键词的分块都是相关文档(特别是对于我们更感兴趣于语义概念的查询)。使用混合搜索,我们可以平衡两种类型的搜索,选择从一种或另一种类型的搜索中取多少分块:

def fusion_retrieval(query, top_k=5):
    """
    Retrieves the top_k most relevant documents using a combination of vector-based
    and textual retrieval methods.
    Args:
        query (str): The search query.
        top_k (int): The number of top documents to retrieve.
    Returns:
        list: A list of combined results from both vector and textual retrieval methods.
    """
    query_embedding = sentence_model.encode(query).tolist()
    vector_results = collection.query(query_embeddings=[query_embedding], n_results=min(top_k, len(documents)))
    es_body = {
        "size": top_k,  # Move size into body
        "query": {
            "match": {
                "content": query
            }
        }
    }
    es_results = es.search(index=index_name, body=es_body)
    es_documents = [hit["_source"]["content"] for hit in es_results['hits']['hits']]
    combined_results = vector_results['documents'][0] + es_documents
    return combined_results

如前所述,重排器是使用最频繁的元素之一;它是一个用于重新排序上下文的 transformer。如果我们找到了 10 个片段,我们会重新排序找到的片段,并通常取它们的一个子集。有时,语义搜索可以再次找到最相关的片段,但这些片段可能被放置在顺序的更下方。重排器确保这些片段实际上被放置在 LLM 的上下文中:

def rerank_documents(query, documents):
    """
    Reranks the retrieved documents based on their relevance to the query using a pre-trained
    BERT model.
    Args:
        query (str): The user's query.
        documents (list): A list of documents retrieved from the search.
    Returns:
        list: A list of reranked documents, sorted by relevance.
    """
    inputs = [rerank_tokenizer.encode_plus(query, doc, return_tensors='pt', truncation=True, padding=True) for doc in documents]
    scores = []
    for input in inputs:
        outputs = rerank_model(**input)
        logits = outputs.logits
        probabilities = F.softmax(logits, dim=1)
        positive_class_probability = probabilities[:, 1].item()
        scores.append(positive_class_probability)
    ranked_docs = sorted(zip(documents, scores), key=lambda x: x[1], reverse=True)
    return [doc for doc, score in ranked_docs]

如前所述,上下文也可能包含冗余信息。LLM 对噪声很敏感,因此减少这种噪声可以帮助生成。在这种情况下,我们使用一个 LLM 来总结找到的上下文(当然,我们设定了一个限制,以避免丢失太多信息):

def select_and_compress_context(documents):
    """
    Summarizes the content of the retrieved documents to create a compressed context.
    Args:
        documents (list): A list of documents to summarize.
    Returns:
        list: A list of summarized texts for each document.
    """
    summarized_context = []
    for doc in documents:
        input_length = len(doc.split())
        max_length = min(100, input_length)  than 100
        summary = summarizer(doc, max_length=max_length, min_length=5, do_sample=False)[0]['summary_text']
        summarized_context.append(summary)
    return summarized_context

一旦定义,我们只需将它们组装成一个单一的管道。一旦完成,我们就可以使用我们的 RAG 管道。检查存储库中的代码并尝试修改代码。一旦你有一个可以工作的 RAG 管道,下一步自然就是部署。在下一节中,我们将讨论部署可能面临的潜在挑战。

理解 RAG 的可扩展性和性能

在本节中,我们将主要描述与 RAG 系统部署相关或随着系统扩展可能出现的挑战。与 LLM 相比,RAG 的主要优势在于它可以不进行额外训练而进行扩展。开发和生产的目的和需求主要不同。LLM 和 RAG 提出了新的挑战,尤其是在你希望将系统投入生产时。生产化意味着将像 RAG 这样的复杂系统从原型转变为稳定、可操作的环境。当你必须管理可能远程连接的不同用户时,这可能会非常复杂。在开发过程中,准确性可能是最重要的指标,而在生产过程中,必须特别小心地平衡性能和成本。

尤其是大型组织,可能已经存储了大量的数据,因此可能希望使用 RAG 与这些数据结合。大数据对 RAG 系统来说可能是一个重大的挑战,特别是考虑到数据的量、速度和多样性。"可扩展性"在讨论大数据时是一个关键问题;同样的原则也适用于 RAG。

数据可扩展性、存储和预处理

到目前为止,我们已经讨论了如何查找信息。我们假设数据是以文本形式存在的。文本的数据结构是一个重要的参数,将其投入生产可能会遇到问题。因此,我们的系统可能需要整合以下内容:

  • 非结构化数据:文本是语料库中最常用的数据类型。它可以有不同的来源:百科全书式(来自维基百科),特定领域(科学、医学或金融),行业特定(报告或标准文件),从互联网下载,或用户聊天。因此,它可以由人类生成,但也可能包括由自动化系统或 LLMs 本身(与用户的先前交互)生成的数据。此外,它可能是多语言的,系统可能需要进行跨语言搜索。今天,既有用不同语言训练的 LLMs,也有多语言嵌入器(专门设计用于多语言能力)。还有其他类型的非结构化数据,如图像和视频。我们将在下一节更详细地讨论多模态 RAG。

  • 半结构化数据:通常,这意味着包含文本和表格信息的混合数据(如 PDF)。半结构化数据的其他例子包括 JSON、XML 和 HTML。这些类型的数据通常很难与 RAG 一起使用。通常有特定于文件的管道(分块、元数据存储等),因为它们可能会给系统带来问题。在 PDF 的情况下,分块可以将表格分成多个块,使得检索效率低下。此外,表格使相似性搜索更加复杂。一种替代方法是提取表格并将它们转换为文本或插入到兼容的数据库中(如 SQL)。由于可用的方法尚未优化,该领域的研究仍然非常活跃。

  • 结构化数据:结构化数据是指以标准格式存储的数据,可以由人类和软件高效访问。结构化数据通常具有一些特殊特征:定义了属性(所有数据值都使用表格中的相同属性),关系属性(表具有将不同数据集连接起来的公共值;例如,在客户数据集中,有 ID 可以找到用户及其购买记录),定量数据(数据优化用于数学分析),以及存储(数据以特定格式和精确规则存储)。结构化数据的例子包括 Excel 文件、SQL 数据库、网页表单结果、销售点数据和产品目录。另一个结构化数据的例子是知识图谱(KGs),我们将在下一章详细讨论。

这些因素必须被考虑在内。例如,如果我们正在设计一个需要在各个地区和不同语言中搜索合规文件的系统,我们需要一个能够进行跨语言检索的 RAG。如果我们组织主要有一种类型的数据(PDF 或 SQL 数据库),那么考虑这一点并优化系统以搜索此类数据是很重要的。有一些特定的替代方案可以改善 RAGs 处理结构化数据的能力。一个例子是链表,这是一种将 CoT 提示与表转换相结合的方法。在 LLM 和一组预定义操作的逐步过程中,它提取并修改表格。这种方法旨在处理复杂的表格,并利用逐步推理和逐步表格操作来完成这一任务。这种方法在数据源为复杂的 SQL 数据库或大量数据框时很有用。然后,还有更复杂的替代方案,它们结合了符号推理和文本推理。混合自洽是一种针对表格数据理解的专用方法,它使用文本和符号推理以及自洽性,从而创建推理的多路径,然后通过自洽性进行聚合。对于 PDF 和 JHTML 等半结构化数据,有专门的包允许我们从它们中提取信息或解析数据。

不仅数据类型会影响 RAG 的性能,数据量本身也会影响。随着数据量的增加,找到相关信息变得越来越困难。同样,这也可能增加系统的延迟。

数据存储是在将系统投入生产之前需要解决的焦点之一。分布式存储系统(将数据分割成几个物理服务器或数据中心的基础设施)可以是大数据量的解决方案。这具有提高系统速度和降低数据丢失风险的优点,但可能会增加成本和管理复杂性。当你有不同类型的数据时,使用称为数据湖的结构可能是有利的。数据湖是一个设计用于存储和处理结构化、半结构化和非结构化数据的集中式存储库。数据湖的优势在于它是一个可扩展且灵活的结构,用于摄取、处理和存储不同类型的数据。数据湖对 RAG 有利,因为它允许比其他数据结构维护更多的数据上下文。另一方面,数据湖需要更多的专业知识才能发挥作用。替代方案可能包括将数据分割成更小、更易于管理的分区(基于地理位置、主题、时间等),这允许更有效的检索。在大量请求的情况下,频繁访问的数据缓存可以避免重复。这些策略可以在大数据存储和访问的情况下使用。

另一个重要的方面是构建用于数据预处理和清洗的稳固管道。在开发阶段,通常与经过良好打磨的数据集一起工作,但在生产中并非如此。特别是在大数据中,确保没有不一致性或系统可以处理缺失或不完整的数据至关重要。在大数据环境中,数据来自许多来源,并非所有来源都是高质量的。因此,可以使用插补技术(如 KNN 或其他)来填补缺失数据。其他可以改进流程的补充包括消除噪声或错误数据的技巧,例如异常检测算法、归一化技术和用于消除错误数据点的正则表达式技术。

数据去重是处理 LLMs 时另一个重要的方面。重复数据会损害 LLMs 的训练,在生成过程中被发现时也会造成损害(可能导致不准确、有偏见或质量低下的输出)。随着数据量的增加,数据重复的风险会线性增加。可以使用模糊匹配和基于哈希的去重技术来消除重复元素。通常,应该创建一个管道来控制系统中数据的质量和治理(数据质量监控)。这些管道应包括规则和跟踪系统,以便能够识别有问题的数据和其来源。尽管这些管道是必不可少的,但过于复杂难以维护或过多地减慢系统速度的管道应避免。

一旦我们确定了我们的数据存储基础设施,我们需要确保我们拥有高效的数据索引和检索。有一些索引方法专门针对大数据,例如 Apache Lucene 或 Elasticsearch。此外,最常用的数据可以被缓存,或者检索过程可以被分布式以创建并行基础设施,并在有多个用户时减少瓶颈。鉴于这些技术的复杂性,在投入生产之前进行测试和基准测试总是最好的做法。

并行处理

尤其对于拥有大量用户的软件应用,并行处理可以显著提高系统可扩展性。这显然需要一个良好的云基础设施,以及组织良好的集群。将并行处理应用于 RAG 可以显著降低系统延迟,即使存在大量数据集。Apache Spark 和 Dask 是实现 RAG 并行计算最广泛使用的解决方案之一。正如我们所见,并行计算可以在 RAG 管道各个阶段实现:存储、检索和生成。在存储阶段,可以使用各个节点来执行整个数据预处理管道,即预处理、索引和部分数据集(直到嵌入)的块化。尽管这看起来不太直观,但在检索阶段,数据集可以分配给各个节点,每个节点负责从特定的数据集分片中查找信息。这样,我们减轻了每个节点的计算负担,并使检索过程并行化。

类似地,生成过程也可以并行化。实际上,LLMs 计算密集,但基于 Transformer。Transformer 的设计考虑了训练和推理的并行化。存在一些技术可以在长序列或大数据批量情况下实现并行化。后来,开发了更复杂的技巧,例如张量并行、模型并行和专用框架。然而,并行化系统本身存在固有的挑战和出现错误的风险。因此,在系统使用期间监控系统并实施容错机制(如检查点)、高级调度(如动态任务分配)和其他潜在解决方案是很重要的。

RAG 是一个资源密集型过程(或者至少某些步骤是),因此实施动态分配资源和监控各种进程的工作负载的技术是很好的做法。此外,建议使用模块化方法,将各种组件(如数据摄取、存储、检索和生成)分开。无论如何,建议有一个过程来监控不仅准确性方面的性能,还包括内存使用、成本、网络使用等方面。

https://github.com/OpenDocCN/freelearn-dl-zh/raw/master/docs/bd-ai-agt-llm-rag-kg/img/B21257_06_17.jpg

图 6.17 – RAG 可扩展性的大数据解决方案

我们已经一般性地讨论了 RAG。然而,正如我们之前看到的,今天的 RAG 可以由几个组件组成。通过高级 RAG 和模块化 RAG,我们看到了如何通过添加额外的组件快速扩展系统,这些组件会影响系统的准确性和计算及延迟成本。因此,我们的系统有许多替代方案,很难选择哪些组件最重要。迄今为止,只有少数基准研究对性能和计算成本进行了严格的分析。在最近的一项研究中(王,2024),作者分析了潜在的最佳组件,并提供了关于使用哪些元素的指导。在 图 6*.18 中,标记为蓝色的组件是研究作者认为能提供最佳性能的组件,而加粗的则是可选组件。

https://arxiv.org/pdf/2407.01219](https://github.com/OpenDocCN/freelearn-dl-zh/raw/master/docs/bd-ai-agt-llm-rag-kg/img/B21257_06_18.jpg)

图 6.18 – 对最佳 RAG 每个组件的贡献(arxiv.org/pdf/2407.01219)

例如,添加一些组件可以提高系统准确性,但延迟也会明显增加。HyDE 实现了最高的性能分数,但似乎有显著的计算成本。在这种情况下,性能提升并不能证明这种增加的延迟是合理的。其他组件增加了计算成本,但它们的缺失会导致性能显著下降(这种情况发生在重新排序中)。摘要模块有助于模型实现最佳准确性;如果延迟不是问题,它们的成本是可以接受的。尽管在系统性的搜索中测试所有组件几乎是不可能的,但可以提供一些指导方针。最佳性能是通过查询分类模块 HyDE、重新排序模块、上下文重组和摘要实现的。然而,如果从计算或延迟的角度来看成本过高,则最好避免使用 HyDE 等技术,并坚持使用其他模块(例如,选择参数更少的重新排序器)。以下表格总结了单个模块和技术的性能和计算效率:

https://arxiv.org/pdf/2407.01219](https://github.com/OpenDocCN/freelearn-dl-zh/raw/master/docs/bd-ai-agt-llm-rag-kg/img/B21257_06_19.jpg)

图 6.19 – 单个模块和技术对准确性和延迟的影响(arxiv.org/pdf/2407.01219)

此外,还有专门为 RAG 设计的并行化策略。LlamaIndex 提供了一种并行管道用于数据摄取和处理。为了提高系统的鲁棒性,还有防止错误的系统。例如,在使用模型时,你可能会遇到运行时错误(特别是如果你使用外部 API,如 OpenAI 或 Anthropic)。在这些情况下,拥有后备模型是有益的。LLM 路由器是一种允许你将查询路由到不同 LLM 的系统。通常,有一个预测模型来智能地决定哪个 LLM 最适合给定的提示(考虑到潜在的准确性或成本等因素)。这些路由器可以用作封闭源模型,或者将查询路由到不同的外部 LLM API。

安全和隐私

当系统投入生产时,需要考虑的一个重要方面是系统的安全和隐私。RAG 可以处理大量敏感和机密数据;系统被破坏可能导致组织遭受毁灭性的后果(例如监管罚款、诉讼、声誉损害等)。其中一个主要的解决方案是数据加密。一些算法和协议在业界得到广泛应用,也可以应用于 RAG(例如,AES-256 和 TLS/SSL)。同样,实施内部政策以保护密钥并频繁更换它们也很重要。此外,必须实施凭证和权限系统,以确保用户受控访问。如今,使用如多因素认证MFA)、强密码规则以及多设备访问策略是良好的实践。再次强调,这其中的一个重要部分是对潜在破坏的持续监控、事件报告以及如果发生事件时的政策。在部署之前,对系统和其鲁棒性进行测试以识别潜在漏洞是至关重要的。

隐私是当今一个关键且日益敏感的话题。系统遵守关键法规,如通用数据保护条例GDPR)和加州消费者隐私法案CCPA)非常重要。特别是在处理大量个人数据时,违反这些法规会使组织面临巨额罚款。为了避免处罚,实施强大的数据治理、跟踪实践和数据管理是明智之举。还有一些技术可以用来提高系统隐私,例如差分隐私和安全的多方计算。此外,应跟踪事件,并应制定处理问题和解决问题的政策。

https://github.com/OpenDocCN/freelearn-dl-zh/raw/master/docs/bd-ai-agt-llm-rag-kg/img/B21257_06_20.jpg

图 6.20 – RAG 系统和潜在风险(arxiv.org/pdf/2402.16893

然后,目前存在一些针对 RAG 系统的特定安全问题。例如,向量可能看起来像是简单的数字,但实际上可以转换回文本。嵌入过程可以被视为有损的,但这并不意味着它不能解码回原始文本。理论上,嵌入向量应仅保持原始文本的语义意义,从而保护敏感数据。实际上,在某些研究中,他们已经能够恢复原始文本中超过 70%的单词。此外,不需要极其复杂的技巧。在所谓的嵌入反转攻击中,你获取向量并将它们解码回原始文本。换句话说,与普遍看法相反,你可以从向量中重建文本,因此这些向量也应受到保护。此外,任何包含 LLM 的系统都容易受到提示注入攻击。这是一种看似合法的提示,其中添加了恶意指令。这可能是为了提示模型泄露信息。提示注入是模型面临的最大风险之一,通常,文献中会描述新的方法,因此所有先前的预防措施很快就会过时。此外,特定的提示可能会诱导 RAG 产生非预期的输出。对抗性前缀是添加到 RAG 提示中的前缀,可以诱导生成幻觉和不准确的事实输出。

另一种攻击类型是毒化 RAG,其目的是输入错误的数据,然后 LLM 会使用这些数据生成偏颇的输出。例如,为了生成虚假信息,我们可以构建目标文本,当注入时会导致系统生成期望的输出。在图例的例子中,我们注入文本以毒化 RAG 来影响问题的答案。

https://github.com/OpenDocCN/freelearn-dl-zh/raw/master/docs/bd-ai-agt-llm-rag-kg/img/B21257_06_21.jpg

图 6.21 – 毒化 RAG 概述 (arxiv.org/pdf/2402.07867)

成员推理攻击MIAs)是另一种攻击类型,其目的是推断某些数据是否存在于数据集中。如果一个样本位于 RAG 数据集中,它可能会在特定查询中被找到,并插入到 LLM 的上下文中。通过 MIAs,我们可以知道数据是否存在于系统中,然后尝试通过提示注入(例如,通过让 LLM 输出检索到的上下文)来提取它。

正因如此,针对 RAG(或更普遍的 LLMs)有特定的解决方案。一个例子是NeMo Guardrails,这是一个由 NVIDIA 开发的开源工具包,旨在为基于 LLM 的应用程序添加可编程轨道。这些轨道提供了一种控制模型 LLM 输出的机制(因此我们直接在生成级别进行操作)。通过这种方式,我们可以提供约束(不涉及有害话题、在对话中遵循路径、不回应某些请求、使用特定语言等)。与其他嵌入式技术(如训练时的模型对齐)相比,这种方法的优势在于它发生在运行时,我们不需要对模型进行额外的训练。这种方法也是模型无关的,通常,这些轨道是可解释的(在对齐过程中,我们应该分析用于训练的数据集)。NeMo Guardrails 通过一种可解释的语言(称为 Colang)实现用户定义的可编程轨道,允许我们为 LLMs 定义行为规则。

使用这个工具包,我们可以使用不同类型的轨道:输入轨道(拒绝输入、进行进一步处理或修改输入,以避免敏感信息泄露)、输出轨道(在内容有问题的情况下拒绝生成输出)、检索轨道(拒绝块并因此不将其放入 LLM 的上下文中,或更改现有块),或对话轨道(决定是否执行操作、使用 LLM 进行下一步,或使用默认响应)。

https://github.com/OpenDocCN/freelearn-dl-zh/raw/master/docs/bd-ai-agt-llm-rag-kg/img/B21257_06_22.jpg

图 6.22 – LLMs 的可编程与嵌入式轨道对比(arxiv.org/abs/2310.10501)

另一方面,Llama Guard是一个旨在检查输入(通过提示分类)和输出(通过响应分类)并判断文本是否安全或不安全的系统。然后,这种方法使用 Llama 2 进行分类,并使用专门调整的 LLM 作为评判者。

开放性问题与未来展望

尽管 RAG 技术在很大程度上取得了进步,但仍然存在挑战。在本节中,我们将讨论这些挑战和前景。

最近,关于 LLM(大型语言模型)上下文长度扩展的话题引起了广泛的兴趣和讨论。如今,大多数表现最好的 LLM 的上下文长度超过 100K 个 token(有些甚至超过 100 万个)。这种能力意味着模型具有进行长文档问答的能力(换句话说,能够在单个提示中插入如书籍等长文档)。1 到 1000 万个 token 的上下文长度可以覆盖许多小型用例。长上下文 LLM(LC-LLM)的优势在于它能够对提示中的信息进行交错检索和生成,并对整个文档进行一次性推理。特别是在摘要任务中,LC-LLM 具有竞争优势,因为它可以对整个文档进行扫描,并将文档顶部和底部出现的信息联系起来。对一些人来说,LC-LLM 意味着 RAG(检索-生成)注定要消失。

事实上,LC-LLM 并不与 RAG 竞争,RAG 在短期内也不注定要消失。LC-LLM 并没有高效地使用整个框架。特别是,上下文中中间的信息被关注得要少得多。同样,推理受到无关信息的影响,而长的提示不可避免地提供了回答查询所需的不必要的大量细节。LC-LLM 的幻觉比 RAG 要多得多,后者允许进行参考检查(使用了哪些文档,从而使检索和推理过程变得可观察和透明)。LC-LLM 在处理结构化数据(许多行业中的大多数数据)方面也存在困难,并且成本相当高(长提示会导致延迟显著增加,以及每查询的成本)。最后,考虑到即使是小型组织拥有的数据量,100 万个 token 也不是很多(因此检索总是必要的)。

LC-LLM 为开发者开辟了令人兴奋的可能性。首先,这意味着精确的分块策略将很少需要。分块可以更大(每个分块可以是一个文档,或者至少是一组页面)。这将意味着减少对粒度和性能之间平衡的需求。其次,需要更少的提示工程。特别是对于推理任务,一些问题可以用一个分块中的信息来回答,但其他问题则需要在不同部分或多个文档中进行深入分析。而不是复杂的 CoT(概念重叠),可以用单个提示来回答这些问题。第三,使用 LC-LLM 进行摘要更容易,因此可以单次检索完成。最后,LC-LLM 允许更好地定制和与用户交互。在这样的长提示中,可以上传与用户整个对话。尽管如此,仍然存在一些开放性的挑战,尤其是在为 LC-LLM 检索文档方面。

同样,目前还没有能够处理类似上下文长度的嵌入模型(目前,嵌入器的最大上下文长度为 32K)。因此,即使有 LC-LLM,块的大小也不能超过 32K。LC-LLM 在性能方面仍然很昂贵,并且可能会严重影响系统的可扩展性。无论如何,已经有研究正在探讨一些考虑 LC-LLM 的潜在 RAG 变体——例如,在从小到大的检索中,你找到必要的块,然后发送与 LC-LLM 关联的整个文档,或者将查询路由到管道化整个文档检索(如整个文档摘要任务)或找到块(特定问题或需要不同文档块的多部分问题)。许多公司使用 KV 缓存,这是一种在生成过程中存储来自键和注意力层的激活以及查询的方法(这样你就不必在整个序列生成过程中重新计算整个激活)。因此,有人提出,RAG 也可以用来查找缓存

我们可以在以下图中直观地看到这些可能的演变:

https://github.com/OpenDocCN/freelearn-dl-zh/raw/master/docs/bd-ai-agt-llm-rag-kg/img/B21257_06_23.jpg

图 6.23 – RAG 与 LC-LLM 可能的演变

  • A. 首先检索块,然后检索相关的文档

  • B. 路由器决定是否需要检索小块或整个文档

  • C. 首先检索文档,然后为 LC-LLM 进行 KV 缓存

多模态 RAG是一个令人兴奋的前景和挑战,已经被讨论过。大多数组织不仅拥有文本数据,还有大量其他模态的数据(图像、音频、视频等)。此外,许多文件可能包含不止一种模态(例如,一本书不仅包含文本,还包含图像)。在不同的上下文和应用中搜索多模态数据可能特别有趣。另一方面,多模态 RAG 的复杂性在于每个模态都有其自身的挑战。我们有几种不同的方法可以实现多模态 RAG。我们将看到三种可能的策略:

  • 将所有模态嵌入到相同的向量空间中:我们之前在第三章(一个通过对比学习训练以实现图像和文本独特嵌入的模型)中看到了这种情况,它允许我们同时搜索图像和文本。我们可以使用像 CLIP 这样的模型来对所有模态进行嵌入(在这种情况下,是图像和文本,但还存在其他跨模态模型)。然后我们可以找到图像和文本,并使用多模态模型进行生成(例如,我们可以使用 BLIP2 或 BLIP3 作为视觉语言模型)。多模态模型可以对图像和文本进行推理。这种方法的优势在于我们只需要更改嵌入模型到我们的系统中。此外,多模态模型可以通过利用图像和文本中的信息来进行推理。例如,如果我们有一个包含表格的 PDF,我们可以找到感兴趣的片段及其相关的图表。模型可以使用两种模态中包含的信息来更有效地回答查询。缺点是 CLIP 是一个昂贵的模型,多模态大型语言模型(MMLLMs)比仅文本的 LLMs 更昂贵。此外,我们需要确保我们的嵌入模型能够捕捉到图像和文本的所有细微差别。

  • 单基础模态:另一个选择是将所有模式转换为基本模式(这取决于应用的焦点)。例如,我们从 PDF 中提取文本,并为每个图像创建文本描述以及元数据(对于音频,我们可以使用转录)。在某些变体中,我们保留存储中的图像。在检索过程中,我们再次找到文本(因此我们使用经典的嵌入模型和仅包含从文本中获得的向量的数据库)。然后我们可以在生成阶段使用 LLM 或 MMLLM(如果我们想添加通过检索元数据或描述获得的图像)。再次,主要优势是我们不必训练任何新的模型类型,但作为一种方法,它可能很昂贵,并且我们失去了图像的一些细微差别。

  • 为每个模态分别检索:在这种情况下,每个模态都是单独嵌入的。例如,如果我们有三个模态,我们将有三个单独的模型(音频-文本对齐模型、图像-文本对齐模型和文本嵌入器)和三个单独的数据库(音频、图像和文本)。当查询到达时,我们为每个模式进行编码(因此是音频、图像和文本)。所以,在这种情况下,我们进行了三次检索,可能会找到不同的元素,因此有一个重新排序步骤(以有效地组合结果)是值得的。显然,我们需要一个专门的多模态重新排序器,以便我们可以检索最相关的块。它简化了组织,因为我们有针对每个模式的专用模型(一个对所有模式都工作得很好的模型很难获得),但它增加了系统的复杂性。同样,虽然一个经典的重新排序器必须重新排序n个块,但一个多模态重新排序器的复杂性是重新排序m x n个块(其中m是模态的数量)。

    最后,一旦获得了多模态块,可能会有替代方案;例如,我们可以使用一个 MMLM 来生成响应,然后这个响应需要被整合到上下文中以形成一个最终的 LLM。正如我们之前看到的,我们的 RAG 管道可以比简单的 RAG 更复杂。然后我们可以将之前看到的所有元素组合成一个单一的系统。

https://github.com/OpenDocCN/freelearn-dl-zh/raw/master/docs/bd-ai-agt-llm-rag-kg/img/B21257_06_24.jpg

图 6.24 – 多模态 RAG 的三个潜在方法

尽管 RAG 有效地缓解了幻觉,但它们仍然可能发生。我们之前已经讨论过幻觉是 LLM 的灾难。在本节中,我们将主要讨论 RAG 中的幻觉。其中最奇特的情况之一是情境幻觉,在这种幻觉中,正确的信息在上下文中提供,但 LLM 仍然生成错误的输出。尽管模型提供了正确的信息,但它产生了错误的答案(这种情况通常发生在摘要或基于文档的问题等任务中)。这是因为在 LLM 中存在自己的先验知识,假设模型不使用这种内部知识是错误的。此外,模型经过指令微调或以其他方式对齐,因此它隐式地做出决定,是使用上下文还是忽略上下文并使用其知识来回答用户的问题。在某些情况下,这甚至可能是有用的,因为可能我们找到了错误或误导性的上下文。总的来说,对于许多闭源模型,我们不知道它们是在什么上进行训练的,尽管我们可以监控它们对答案的信心。给定一个问题 x,模型将回答一个答案 x。根据其知识,这将有一个信心 c(这是基于模型生成的标记的概率)。基本上,一个模型对其答案越自信,它在上下文暗示不同的情况下改变答案的可能性就越小。一个有趣的发现是,如果正确答案与 LLM 的知识略有不同,LLM 很可能会改变其答案。在存在较大差异的情况下,LLM 将选择自己的答案。例如,对于“药物 x 的最大剂量是多少?”这个问题,模型可能在训练中看到了 20 µg。如果上下文暗示是 30,LLM 将提供 30 作为输出;如果上下文暗示是 100,LLM 将声明 20。较大的 LLM 通常更自信,更喜欢自己的答案,而较小的模型更愿意使用上下文。最后,这种行为可以通过提示工程来改变。更严格的提示将迫使模型使用上下文,而较弱的提示将推动模型使用其先验知识。

https://github.com/OpenDocCN/freelearn-dl-zh/raw/master/docs/bd-ai-agt-llm-rag-kg/img/B21257_06_25.jpg

图 6.25 – 标准提示与宽松或严格提示的比较示例 (arxiv.org/pdf/2404.10198)

其他因素也有助于减少 RAG 中的幻觉:

  • 数据质量:数据质量通常对系统质量有重大影响。

  • 情境意识:LLM 可能并不完全理解用户的意图,或者找到的上下文可能不是正确的。查询重写和其他高级 RAG 组件可能是解决方案。

  • 负面拒绝:当检索未能找到查询的适当上下文时,模型仍然尝试做出回应,从而产生幻觉或错误答案。这通常是由于查询编写不当造成的,因此可以通过修改查询的组件(如 HyDE)来改进。或者,更严格的提示会迫使 LLM 只有在有上下文的情况下才做出回应。

  • 推理能力:一些查询可能需要推理或过于复杂。系统的推理限制取决于 LLM;RAG 是为了找到回答查询的上下文。

  • 领域不匹配:通用模型在处理过于技术化的领域时会遇到困难。微调嵌入器和 LLM 可以是一个解决方案。

  • 目标不匹配:嵌入器和 LLM 的目标不一致,因此今天有系统试图优化端到端检索和生成。这可以解决复杂查询或特定领域的问题。

还有其他令人兴奋的视角。例如,有一些工作正在研究使用强化学习来提高 RAG 对复杂查询的响应能力。其他研究涉及整合图研究;我们将在下一章中更详细地讨论这一点。此外,我们迄今为止一直假设数据库是静态的,但在互联网时代,有一个关于如何将互联网整合到 RAG 中的讨论(例如,在组织受保护的数据中进行混合搜索,并通过互联网搜索找到上下文)。这提出了令人兴奋但复杂的问题,例如是否进行数据库更新、如何过滤掉不相关的搜索引擎结果以及安全问题。此外,还有越来越多的 RAG 专用应用,作者专注于创建针对其应用领域的优化系统(例如,用于数学、医学、生物学等)。所有这些都表明了 RAG 的活跃研究和对其应用的兴趣。

摘要

在本章中,我们最初讨论了朴素 RAG 的问题。这使得我们看到了许多可以用来解决朴素 RAG 痛点的附加组件。使用这些附加组件是现在所说的先进 RAG 范式的基础。随着时间的推移,社区随后转向了一种更灵活和模块化的结构,现在称为模块化 RAG。

我们随后看到了如何在大数据存在的情况下扩展这种结构。像任何基于 LLM 的应用一样,当你需要将系统从开发环境迁移到生产环境时,会面临计算和成本挑战。此外,LLM 和 RAG 都可能存在安全和隐私风险。这些是重要的问题,尤其是当这些产品向公众开放时。今天,对合规性的关注越来越多,越来越多的法规正在被考虑。

最后,我们注意到一些问题仍然悬而未决,例如与长上下文 LLM 的关系或这些模型的跨模态扩展。此外,检索和生成之间存在微妙的平衡,我们在出现问题时探讨了潜在解决方案。最近,对与知识图谱(KG)的集成进行了积极的研究。GraphRAG 今天经常被讨论;在下一章中,我们将讨论什么是知识图谱以及图与 RAG 之间的关系。

进一步阅读

第七章:创建和连接知识图谱到 AI 代理

在前两章中,我们详细讨论了 RAG 框架。我们从简单的 RAG 开始,然后看到了我们如何添加不同的组件,替换其他组件,或修改整个管道以满足我们的需求。整个系统非常灵活,但有些概念保持不变。首先,我们从语料库(或多个文本语料库)开始,对这些文本进行嵌入以获得向量数据库。一旦用户查询到达,我们就对这个向量数据库进行相似度搜索。无论文本的范围或类型如何,我们的管道都基于以某种方式对文本进行矢量化,然后向 LLM 提供发现的文本中包含的信息。

文本通常充满了冗余信息,在上一章中,我们看到了 LLMs 对输入中噪声量的敏感性。大多数人已经看到了创建图表笔记或思维导图的好处。这些图表之所以简洁,是因为在书中划线一切就像没有划线一样。这些图表的原则是提取关键信息以便我们能够记住,这将使我们能够回答未来的问题。图表应该展示基本信息和连接它们的关系。这些模式可以表示为图形,更精确地说,是知识图谱。这些图形的优点是它们紧凑,将知识表示为实体和关系,并且我们可以对它们进行分析和使用图搜索算法。多年来,这些知识图谱KGs)由主要公司或机构构建,现在可供使用。许多这些 KGs 已被用于信息提取,其中信息通过一系列查询来提取以回答问题。这些提取的信息是一系列实体和关系,知识丰富但对我们人类来说不太容易理解。自然的下一步是使用这些信息为 LLM 的上下文生成自然语言响应。这种范式被称为GraphRAG,我们将在本章中详细讨论。

在任何情况下,没有什么阻止我们使用 LLM 在 KG 的所有步骤中。事实上,LLMs 具有许多固有的能力,即使对于它们未经过训练的任务也很有用。这正是我们将看到我们可以使用 LLMs 来提取关系和实体并构建我们的 KGs 的原因。然而,LLMs 也具备推理能力,在本章中,我们将讨论我们如何使用这些模型来推理图中包含的信息以及图的结构本身。最后,我们将讨论哪些观点和问题仍然是开放的,以及所提出方法的优缺点。

在本章中,我们将讨论以下主题:

  • 知识图谱简介

  • 使用你的 LLM 创建知识图谱

  • 使用知识图谱和大型语言模型检索信息

  • 理解图推理

  • 知识图谱和 GraphRAG 的持续挑战

技术要求

大部分代码都可以在 CPU 上运行,但更倾向于在 GPU 上运行。代码是用 PyTorch 编写的,大部分使用标准库(PyTorch、Hugging Face Transformers、LangChain、ChromaDB、sentence-transformerfaiss-cpu等)。

在本章中,我们也将使用 Neo4j 作为图数据库。尽管我们将使用 Python 进行所有操作,但 Neo4j 必须安装,并且您必须注册才能使用它。代码可以在 GitHub 上找到:github.com/PacktPublishing/Modern-AI-Agents/tree/main/chr7

知识图谱简介

知识表示是人工智能的开放问题之一,并且有着非常悠久的历史(莱布尼茨认为所有知识都可以被表示并用于计算)。对知识表示的兴趣基于这样一个事实,即它是进行计算机推理的第一步。一旦这些知识被有序地组织起来,就可以用来设计推理算法和解决推理问题。早期的研究主要集中在使用演绎法来解决有关有序实体的问题(例如,通过使用本体)。这对于许多玩具问题来说效果很好,但它很费力,通常需要一套硬编码的规则,并且存在陷入组合爆炸的风险。因为这些空间中的搜索可能极其计算密集,因此尝试定义了两个概念:

  • 有限理性:找到解决方案的同时,也要考虑其成本

  • 启发式搜索:限制搜索空间,从而找到半优解(局部但非全局最优解)

这些原则激发了一系列算法的产生,这些算法随后使得信息搜索变得更加高效和可行。在 20 世纪 90 年代末,随着万维网的诞生和快速准确进行互联网搜索的需求,对这些算法的兴趣急剧增长。关于数据,万维网也基于三个技术原则:

  • 分布式数据:数据分布在全球各地,可以从世界各地的任何地方访问

  • 连接数据:数据是相互关联的,而不是孤立的;数据的意义是其与其他数据连接的函数

  • 语义元数据:除了数据本身之外,我们还有关于其关系的信息,并且这些元数据使我们能够高效地搜索

因此,我们开始寻找一种能够尊重这种新型数据本质的技术。自然而然地,我们转向了图形表示。实际上,根据定义,图模型化了不同实体之间的关系。这种方法始于 2012 年,当时 Google 开始为每个搜索的概念添加知识卡片。这些知识卡片可以被视为名称实体的图,其中连接是图链接。这些卡片随后允许进行更相关的搜索和用户便利化。随后,“知识图谱”一词开始指任何通过一系列有意义的连接连接实体的图。这些关系通常表示实体之间的语义关系。

https://github.com/OpenDocCN/freelearn-dl-zh/raw/master/docs/bd-ai-agt-llm-rag-kg/img/B21257_07_01.jpg

图 7.1 – Google 中的知识卡片

图和知识图谱的正式定义

由于知识图谱是图的一种子类型,我们将从图的简要介绍开始。图是由节点(或顶点)组成的数据结构,这些节点通过关系(或边)连接,以表示一个领域的模型。图可以以紧凑的方式表示知识,同时试图减少噪声。存在不同类型的图:

  • 无向图: 边没有方向

  • 有向图: 边有定义的方向(有明确的开始和结束)

  • 加权图: 边带有权重,表示关系的强度或成本

  • 标记图: 节点与特征和标签相关联

  • 多重图: 同一对节点之间存在多个边(关系)

下图展示了这些图的视觉表示:

https://github.com/OpenDocCN/freelearn-dl-zh/raw/master/docs/bd-ai-agt-llm-rag-kg/img/B21257_07_02.jpg

图 7.2 – 不同类型的图架构

因此,知识图谱是一个具有三个主要属性的子图:

  • 节点代表现实世界实体: 这些实体可以代表人、地点或特定领域的实体(基因、蛋白质、疾病、金融产品等)

  • 关系定义节点之间的语义连接: 例如,两个人可能通过代表友谊的关系相连,或者一个特定的基因与某种特定疾病相关联

  • 节点和边可能具有关联属性: 例如,所有人都会有一个属性,即他们是人类(一个标签),但他们也可以有定量属性(出生日期、特定标识符等)

因此,更正式地说,我们可以认为我们有一个以事实三元组形式表示的知识库(事实数据库)。事实三元组的形式为(头,关系,尾)(主体,谓词,对象),或者更简洁地,(e1,r1,e2),例如(拿破仑,出生地,阿雅克肖)。知识图谱是这种知识库的表示,它允许我们进行解释。鉴于这些三元组的结构,知识图谱是一个有向图,其中节点是这些实体,边是事实关系。

https://github.com/OpenDocCN/freelearn-dl-zh/raw/master/docs/bd-ai-agt-llm-rag-kg/img/B21257_07_03.jpg

图 7.3 – 知识库和知识图谱的示例 (arxiv.org/pdf/2002.00388)

知识图谱定义为由一组实体E、关系R和事实F组成的图,其中每个事实f是一个三元组:

G=E,R,F;f=(h,r,t)

如您所见,知识图谱是知识的另一种表示形式。相同类型的数据可以以表格或图的形式表示。我们可以直接从表格中创建三元组,然后直接在知识图谱中表示它们。我们不需要表头,并且更新这种结构更容易。图被认为是通用数据表示,因为它们可以应用于任何类型的数据。实际上,我们不仅可以将表格数据映射到知识图谱,还可以从其他格式(如 JSON、XML、CSV 等)获取数据。图还允许我们灵活地表示递归结构(如树和文档)或循环结构(如社交网络)。此外,如果我们没有所有属性的信息,表格表示将充满缺失值;而在知识图谱中,我们不会遇到这个问题。

图本身代表网络结构。这在许多业务案例(例如,在金融和医学领域)中非常有用,因为这些数据已经以网络的形式结构化。另一个优点是,合并图比合并表格要容易得多。合并表格通常是一个复杂的过程(你必须选择要合并的列,避免创建重复项,以及其他潜在问题)。如果数据以三元组形式存在,合并两个知识图谱数据库就极其简单。例如,看看将这个表格转换为图是多么简单;它们是等价的:

https://github.com/OpenDocCN/freelearn-dl-zh/raw/master/docs/bd-ai-agt-llm-rag-kg/img/B21257_07_04.jpg

图 7.4 – 表格和图表是等价的

知识图谱不应被视为一个静态实体。实际上,知识是不断演变的;这会导致新的实体或关系被添加。在知识图谱推理(KGR)中最广泛使用的任务之一是预测新的关系。例如,如果 A 是 B 的丈夫和 C 的父亲,这暗示 B 是 C 的母亲,这可以通过逻辑推理得出:(A, husband of, B) ^ (A, father of C) -> (B, mother of, C)。在这个预存数据中,我们推断出一个缺失的关系。

另一个非常重要的任务是,一旦我们获得了新的三元组(这可能需要复杂的预处理),如何更新知识图谱。这非常重要,因为一旦我们整合了一些新的知识,我们就可以进行进一步的分析和推理。此外,作为一个图,我们可以使用图分析算法(如中心性度量、连通性、聚类等)来处理我们的业务案例。利用这些算法使得在知识图谱中进行搜索或复杂查询比在关系数据库中要容易得多。

https://github.com/OpenDocCN/freelearn-dl-zh/raw/master/docs/bd-ai-agt-llm-rag-kg/img/B21257_07_05.jpg

图 7.5 – 知识图谱是一个动态实体

此外,知识图谱比人们想象的要灵活和适应性强得多。知识图谱可以适应不同的任务。例如,有知识图谱的扩展,如层次知识图谱,其中我们有多级。在层次知识图谱中,一个级别的实体可以连接到下一个级别(例如,当我们有本体时,这非常有用)。实体也可以是多模态的,因此一个节点可以代表一个图像,其他实体(文本或其他图像,或其他类型的模态)与之相连。另一种类型的知识图谱是时序知识图谱,其中我们引入了时间维度。这种类型的知识图谱对于预测分析非常有用。我们可以在以下图中看到这些知识图谱:

https://github.com/OpenDocCN/freelearn-dl-zh/raw/master/docs/bd-ai-agt-llm-rag-kg/img/B21257_07_06.jpg

图 7.6 – 不同类型的知识图谱

分类法和本体

图和 KG 之间的主要区别在于,前者是一个给定的结构,表示实体之间的关系,而后者通过允许推理和推理使语义关系明确,对人类和机器都是如此。因此,KG 的优势在于我们可以使用图算法和特定的推理算法(我们将在“理解图推理”部分中稍后详细说明)。通过整合元数据,这些能力得到了增强。实际上,我们可以构建dogcat实体可能被分组在mammals下)。此外,如果需要,可以整合多个分类法(因此拥有多个树,以便进行更精细的搜索)。这些分类法有助于搜索或在我们需要过滤和操作非常大的 KG 时。例如,maximum_speed属性为100 km,因此鲍勃将无法在不到一个小时的时间内到达那里,因为他的工作有一个distance_from_home属性为120 km)。规则使我们能够改进搜索并解决以前过于复杂的任务(例如,我们可以为关系分配不同的属性:如果married_to是传递性的,我们可以在没有指定关系的情况下自动推断有关某人的信息)。多亏了本体,我们可以有效地快速进行某些类型的推理,例如演绎推理、类推理、传递推理等。

本体通常分为两组:

  • 独立于领域的本体:这些本体提供的是与特定领域无关的基本概念。它们提供了一个高级视图,有助于数据集成,尤其是在存在多个领域的情况下。通常,这些本体数量较少,它们代表第一级,并且是首先构建的。

  • 领域本体:这些本体专注于一个领域,用于提供基本术语。它们对于医学和金融等专业化领域非常有用。它们通常位于独立于领域的本体之下,并且是它的子类。

在本节中,我们看到了 KG 是如何作为灵活的系统来存储数据并能轻松找到知识的。这种灵活性使它们成为后续分析的有力工具,但同时也使得构建它们并不容易。在下一节中,我们将看到如何从一组文本中构建 KG。

使用你的大型语言模型(LLM)创建知识图谱

KG 的构建通常是一个多步骤的过程,包括以下步骤:

  1. 知识创造:第一步,我们定义这个知识图谱(KG)的目的,即收集提取知识的来源。在这个步骤中,我们必须决定如何构建我们的 KG 以及在哪里维护它。一旦构建完成,KG 就必须被存储,并且我们必须有一个高效的查询结构。

  2. 知识评估:在这个步骤中,我们评估获得的 KG 的质量。

  3. 知识清洗:有多个步骤和程序来确保没有错误,然后进行纠正。这一步可以与知识评估同时进行,并且一些流程将它们一起进行。

  4. 知识增强:这涉及一系列步骤来识别知识中是否存在空白。我们还可以整合额外的来源(从其他数据集中提取信息、整合数据库或合并多个知识图谱)。

  5. 知识部署:在这一最终步骤中,知识图谱可以作为独立的应用程序(例如,作为图数据库)部署,或者用于其他应用程序中。

我们可以在以下图中看到这个过程:

https://github.com/OpenDocCN/freelearn-dl-zh/raw/master/docs/bd-ai-agt-llm-rag-kg/img/B21257_07_07.jpg

图 7.7 – 知识图谱构建流程

知识创建

通常,从头开始构建知识图谱时,本体定义是第一步。有关于如何构建它们的几个指南(包括库和可视化工具)。努力构建清晰、可验证和可重用的本体。定义本体时应有一个目的,与各种利益相关者讨论知识图谱的目的,然后相应地定义本体。最相关的本体应该被选择(知识图谱的第一级),然后定义层次结构和属性。有两种方法:自上而下(首先定义核心本体,然后是更专业的本体)或自下而上(首先定义专业本体,然后将它们分组到超类中)。特别是对于专业领域,我们可以从已经构建的本体开始(有几种是为金融、医学和学术研究定义的),这确保了更好的互操作性。

下一步是从我们的来源中提取知识。在这个步骤中,我们必须从文本语料库或其他来源(数据库、结构化和非结构化数据)中提取三元组(或一组事实)。可以定义两个任务:

  • 命名实体识别(NER):NER 是从文本中提取实体并将它们分类的任务

  • 关系抽取(RE):RE 是识别上下文中各种实体之间联系的任务

NER 是自然语言处理(NLP)中最常见的任务之一,不仅用于知识图谱的创建,而且在我们想要从非结构化文本转换为结构化数据时也是一个关键步骤。它通常需要一个由多个步骤组成的管道(文本预处理、实体识别和分类、上下文分析以及数据后处理)。在 NER 过程中,我们首先进行预处理步骤以避免管道中的错误(例如,正确的标记化),然后尝试识别实体。一旦识别出实体,它们通常会被分类(例如,通过添加如人物、组织或地点等标签)。此外,还尝试使用周围文本来消除歧义(例如,试图识别文本中的Apple代表的是水果还是公司)。然后进行预处理步骤以解决歧义或合并多标记实体。

https://github.com/OpenDocCN/freelearn-dl-zh/raw/master/docs/bd-ai-agt-llm-rag-kg/img/B21257_07_08.jpg

图 7.8 – NER 示例 (arxiv.org/pdf/2401.10825)

RE 是我们理解各种提取实体之间关系的任务。更正式地说,我们使用一个模型来识别和分类文本中实体之间的连接(例如,在句子Bob works at Apple中,我们提取关系works at)。它可以被视为一个单独的任务,或者在某些情况下与 NER 一起进行(例如,使用单个模型)。此外,RE 是知识图谱创建的关键步骤,同时对于其他几个 NLP 任务(如问答、信息检索等)也很有用。

有几种方法可以进行 NER 和 RE。最早且最费力的方法是基于知识或基于规则的。例如,在财务文件中识别公司名称的最简单方法之一是使用诸如大写字母等指标(识别Mr.Ms.元素以提取人物,等等)。基于规则的方法在标准化文档(如临床笔记或官方文件)中效果很好,但可扩展性较差。这些方法需要建立繁琐的上游规则和特定知识,风险是错过不同数据集中许多实体。

基于隐马尔可夫模型、条件随机字段或最大熵(基于从训练数据中学习到的可能性预测实体的方法)的统计方法,使得更大的可扩展性成为可能。然而,这些方法需要大量、高质量的已定义标签的数据集。其他监督学习算法已被用于预测实体然后提取它们。这些算法在高计算成本和特别需要标签的情况下表现良好。获取标签是昂贵的,这些数据集很快就会过时(新公司、新产品等不断出现)。

最近,鉴于无监督学习(如 transformer 模型)的进步,决定使用 LLM 进行 NER 和 RE 以及构建 KG(在某些研究中,这些被称为LLM 增强的 KG)。处理大量文本语料库的能力、在预训练期间获得的知识以及它们的通用性使 LLM 在构建 KG(以及我们稍后将看到的其他相关任务)中变得非常有用。

由于它们能够利用上下文信息和语言能力,最先进的方法通常使用基于 transformer 的模型进行 NER 任务。以前的方法在处理具有复杂结构的文本(属于多个实体的标记,或文本中不连续的实体)时存在问题,而 transformer 在这些情况下表现更优。以前曾使用基于 BERT 的模型,这些模型后来被微调用于不同的任务。然而,如今,我们利用 LLM 的能力,它不需要微调,但可以通过上下文学习来学习任务。然后 LLM 可以直接从文本中提取实体,无需标签,并以所需格式提供它们(例如,我们可能希望 LLM 提供三元组列表或三元组加上给定的标签)。为了避免歧义问题,我们可以要求 LLM 在提取时提供额外的信息。例如,在音乐领域,Apple可以指苹果音乐、英国迷幻摇滚乐队 Apple 或歌手 Fiona Apple。LLM 可以帮助我们根据时期上下文区分这些实体中它指的是哪一个。同时,LLM 的灵活性允许我们在提取过程中将实体与各种本体联系起来。

类似地,LLM 也可以帮助进行 RE 任务。有几种方法可以实现这一点。其中最简单的一种是进行句子级别的 RE,即你向模型提供一个句子,它必须提取两个实体之间的关系。这种方法的扩展是在整个文档级别提取实体之间的所有关系。由于这并非易事,因此可以使用更复杂的、包含多个 LLM 的方法来确保我们能够理解文档的局部和全局关系(例如,在文档中,两个实体之间的局部关系位于同一句子中,但我们也可以有全局关系,其中一个实体位于文档的开头,而另一个实体位于文档的结尾)。

https://github.com/OpenDocCN/freelearn-dl-zh/raw/master/docs/bd-ai-agt-llm-rag-kg/img/B21257_07_09.jpg

图 7.9 – 基于 LLM 的知识图谱构建的一般框架(此信息来源于 2023 年发表的一篇文章;arxiv.org/pdf/2306.08302)

如前所述,两个任务不需要分别进行(NER 和 RE),但 LLM 提供了在单步中执行它们的灵活性。在这种情况下,定义正确的提示非常重要,其中我们指导模型提取实体和关系,并指定我们想要的输出格式。然后我们可以迭代地进行,从大量文本中提取实体和关系。或者,我们可以使用一组针对不同任务的提示(一个用于实体提取,一个用于关系提取等)并使用 LLM 自动扫描语料库和这些提示。在某些方法中,为了保持更大的灵活性,使用一个 LLM 进行提取,然后使用一个更小的 LLM 进行校对。

另一个有趣的视角是,一个 LLM 就足以创建一个 KG。实际上,LLMs 是用大量文本训练的(最新的 LLMs 使用了包含从互联网抓取和数千本书的万亿个标记进行训练)。今天,有几项研究表明,即使是小型 LLMs(大约 70 亿个参数)也具有相当的知识,特别是关于事实(在 LLM 中的知识定义也很复杂,因为它不是与单个参数相关联,而是广泛分布的)。因此,一些作者提出了直接从 LLM 中蒸馏知识。在这种情况下,通过利用为任务构建的提示,我们进行所谓的 LLM 知识搜索以提取三元组。通过这种方式,直接从 LLM 中提取事实,然后我们可以直接构建我们的 KG。以这种方式构建的 KG 在质量、多样性和新颖性方面与使用大型文本数据集构建的 KG 具有竞争力。

https://github.com/OpenDocCN/freelearn-dl-zh/raw/master/docs/bd-ai-agt-llm-rag-kg/img/B21257_07_10.jpg

图 7.10 – 从 LLM 中蒸馏 KG 的一般框架 (arxiv.org/pdf/2306.08302)

使用 LLM 创建知识图谱

在本教程中,我将使用 Neo4j 和 LangChain 创建一个由 LLM 构建的 KG。LangChain 允许我们高效地使用 LLM 从文本语料库中提取信息,而 Neo4j 是一个用于分析和可视化图的程序。完整的代码在本书的 GitHub 仓库中(github.com/PacktPublishing/Modern-AI-Agents/tree/main/chr7);在这里,我们将描述一般过程和最重要的代码片段。我们可以有两种方法:

  • 自定义方法:LLMs 天生具备完成任务的能力;我们可以利用这些通用能力

  • LangChain 图转换器:如今,有库可以简化工作,只需几行代码就能达到相同的效果

自定义方法简单来说就是定义一个提示,让模型能够理解任务并高效执行。在这种情况下,我们的提示由以下元素构成:

  • 一个清晰的带有项目符号的任务定义。任务描述可以包含模型必须做什么以及不能做什么。

  • 提供额外的上下文,帮助模型更好地理解如何执行任务。由于这些模型是针对对话任务训练的,提供它们关于应该扮演什么角色的信息有助于提高性能。

  • 一些示例来解释如何执行任务。

这种方法建立在我们在 第三章 中学到的知识之上。我们使用的模型是指令微调的(训练用于执行任务),因此提供清晰的指令有助于模型理解任务并执行它。添加一些示例利用了情境学习。使用精心制作的提示使我们能够灵活地调整提示以适应我们的需求:

#Custom method
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.messages import SystemMessage
from langchain_core.output_parsers import StrOutputParser
prompt = ChatPromptTemplate.from_messages([
    SystemMessage(content="""
    You are a helpful assistant in creates knowledge graphs by Generating Cypher Queries.\n
    Task:
     *  Identify Entities, Relationships and Property Keys from Context.\n
     *  Generate Cypher Query to Create Knowledge Graph from the Entities Relationships and Property Keys discovered.\n
     *  Extract ALL Entities and RelationShips as Possible.\n
     *  Always extract a person Profession as an Entity.\n
     *  Be creative.
     *  Understand hidden relationships from the network.
     Note: Read the Context twice and carefully before generating Cypher Query.\n
     Note: Do not return anything other than the Cypher Query.\n
     Note: Do not include any explanations or apologies in your responses.\n
     Note: Do not hallucinate.\n
     Entities include Person, Place, Product, WorkPlaces, Companies, City, Country, Animals, Tags like peoples Profession and more \n
     Few Shot Prompts:
      Example Context:
       Mary was born in 1995\. She is Friends with Jane and John. Jane is 2 years older than Mary.
       Mary has a dog named Max,and is 3 years old. She is also married to John. Mary is from USA and a Software Engineer.
      Answer:
        MERGE (Mary:Person {name: "Mary", birth_year: 1995})
        MERGE (Jane:Person {name: "Jane", age:1993})
        MERGE (John:Person {name: "John"})
        MERGE (Mary)-[:FRIENDS_WITH]->(Jane)
        MERGE (Mary)-[:FRIENDS_WITH]->(John)
        MERGE (Jane)-[:FRIENDS_WITH]->(Mary)
        MERGE (John)-[:FRIENDS_WITH]->(Mary)
        MERGE (Mary)-[:HAS_DOG]->(Max:Dog {name: "Max", age: 3})
        MERGE (Mary)-[:MARRIED_TO]->(John)
        MERGE (Mary)-[:HAS_PROFESSION]->(SoftwareEngineer:Profession {name: "Software Engineer"})
        MERGE (Mary)-[:FROM]->(USA:Country {name: "USA"})
    """),
    ("human", "Context:{text}"),
])

执行前面的代码应该产生以下结果:

https://github.com/OpenDocCN/freelearn-dl-zh/raw/master/docs/bd-ai-agt-llm-rag-kg/img/B21257_07_11.jpg

图 7.11 – 结果截图

结果显示,精心制作的提示如何成功生成三元组(然后我们可以使用这些三元组来构建我们的 KG)。这突出了 LLM 的巨大灵活性。

我们并不总是想要一个定制的解决方案,但可能想要使用一个更成熟的流程。LangChain 只需几行代码就能提供这种能力:

from langchain_core.documents import Document
from langchain_experimental.graph_transformers import LLMGraphTransformer
llm_transformer = LLMGraphTransformer(llm=llm)
documents = [Document(page_content=content)]
graph_documents = llm_transformer.convert_to_graph_documents(documents)

LangChain 以一种已经结构化的格式给我们相同的成果,这简化了我们的工作。

图形可以在 Neo4j 中进行可视化,我们可以在图上工作,进行搜索,选择节点等操作。

https://github.com/OpenDocCN/freelearn-dl-zh/raw/master/docs/bd-ai-agt-llm-rag-kg/img/B21257_07_12.jpg

图 7.12 – 从 Neo4j 中获取的图形截图

一旦生成了图形,我们就可以用它来进行查询。显然,我们可以在 Neo4j 中进行这些查询,但也可以在 Python 中进行。例如,LangChain 允许我们对我们的 KG 进行查询:

from langchain.chains import GraphCypherQAChain
graphchain = GraphCypherQAChain.from_llm(
    llm, graph=graph, verbose=True, return_intermediate_steps=True
)
results = graphchain.invoke({"query":"People who have kids"})
print(results["result"])

执行代码后,你应该获得以下结果:

https://github.com/OpenDocCN/freelearn-dl-zh/raw/master/docs/bd-ai-agt-llm-rag-kg/img/B21257_07_13.jpg

图 7.13 – 查询 KG

如我们所见,LangChain 在这种情况下生成相应的 Cypher 查询,然后执行图查询。这样,我们使用一个 LLM 在 Cypher 中生成查询,同时我们也可以直接用自然语言编写。

知识评估

一旦知识图谱(KG)创建完成,就需要检查错误以及 KG 的整体质量。KG 的质量是通过一系列维度(每个维度都有相应的指标)来评估的,这些维度用于监控 KG 在可访问性、表示、上下文和内在质量方面的表现。以下是一些指标:

  • 准确性:这个指标从语法和语义的角度评估准确性。

  • 完整性:这个指标衡量 KG 在某个领域或任务中的知识量。它通常衡量 KG 是否包含一个领域所需的所有实体和关系(有时会与黄金标准 KG 进行比较)。

  • 简洁性:知识图谱允许知识以高效的方式表达,但它们也面临着快速扩展的风险。空白节点(代表匿名或未命名的实体的一种特定类型的节点,在图中需要节点但未精确指示时使用)在创建过程中经常被生成。如果不加注意,就有可能使知识图谱充满空白节点。

  • 时效性:知识也应定期更新,因为知识可能会变化并变得过时。因此,决定更新的频率很重要。

  • 易用性、操作简便性和操作性:知识图谱(KGs)用于搜索或其他任务;目前存在一些度量标准来衡量知识图谱的有用性。实际上,对于一个知识图谱来说,要变得有用,它必须易于访问、能够被操作,并且能够进行研究和更新。

  • 易于理解:由于知识图谱旨在用于代表人类的知识,一些作者提出了衡量知识图谱对人类可解释程度的方法。确实,今天,在人工智能中,对模型的透明度和可解释性的重视程度更高。

  • 安全性、隐私性和可追溯性:目前也存在一些度量标准来控制谁可以访问知识图谱以及它是否能够抵御外部访问。同样,知识也需要被追踪,因为我们需要确保它来自哪个来源。可追溯性还允许我们遵守隐私法规。例如,我们的知识图谱可能包含关于用户的敏感数据,或者来自错误或有问题的文档。可追溯性允许我们纠正这些错误,删除需要删除其数据的用户的数据,等等。

知识清洗

评估了我们的知识图谱的质量后,我们可以看到其中存在错误。一般来说,错误检测和纠正统称为知识清洗。知识图谱中可能发生不同类型的错误:

  • 我们可能存在语法错误的实体或关系

  • 一些错误可能与本体相关(分配给不存在的本体、将它们连接到错误的本体、本体的错误属性等等)

  • 一些可能是语义的,可能更难识别

  • 知识来源的错误也可能导致知识错误(症状 x 不是疾病 y 的症状,人 x 不是公司 y 的首席执行官等等)

有几种方法可以检测这些错误。最简单的方法是统计方法,通过利用概率和统计建模来识别 KG 中的异常值。也有更复杂的变化,利用简单的机器学习模型。这些模型并不特别准确。由于我们可以使用逻辑推理和本体与 KG,因此有基于知识的推理方法来识别异常值(例如,一个人的实例不能也是地点的实例,因此通过利用类似的规则,我们可以识别异常值)。最后,有基于 AI 的方法,也可以使用一个 LLM 来检查错误。LLM 拥有知识和推理技能,因此可以用来验证事实是否正确。例如,如果我们对于某个错误,我们有三元组 (Vienna, CapitalOf, Hungary),LLM 可以识别这个错误)。然后,还有类似的 KG 修正方法。然而,已经建立了几个框架来进行检测和修正。

知识丰富化

知识丰富化(或 KG 补全)通常是下一步。KGs 通常是不完整的,因此可能需要进行几轮 KG 补全和修正。KG 的完整性有时很难定义,并且与领域和应用任务相关。完成 KG 的第一步通常是识别额外的信息来源(例如,对于一个医学 KG,这可能是一个额外的生物医学数据库或额外的科学文章语料库)。通常,在构建 KG 的第一步中,我们只使用一种数据类型(非结构化文本),然后在第二步中扩展提取到其他数据类型(CSV、XML、JSON、图像、PDF 等)。每种数据类型都提出了不同的挑战,因此我们应该修改我们的管道。我们使用的来源越多,KG 清理和对齐任务就越重要。例如,来源越异构,实体解析(在 KG 级别识别重复实体)的重要性就越大。

一个有趣的替代方案是使用一个 LLM(或其他 transformer 模型)来推断知识。例如,已经探索了三种可能的方法:

  • (h,r,t) 被作为一个 transformer 模型输入以预测其存在的概率(0 表示三元组无效,而 1 是一个有效的三元组)。这种方法的变体是从模型中提取最终隐藏状态并训练一个线性分类器以二进制方式预测三元组是否有效。

  • (h,r,?),我们可以尝试填补这个空缺。

  • (h,r)(t)。这样,我们从模型中获得了两种表示(模型的最终隐藏状态被使用)。之后,我们使用评分函数来预测这个三元组是否有效。这种方法确实更准确,但存在组合爆炸的风险。正如你所见,在这个方法中,我们试图计算两个文本表示之间的相似性((h,r)(t) 的表示)。

这些方法实际上与我们之前章节中尝试计算两个句子之间相似性时看到的方法非常相似。

https://github.com/OpenDocCN/freelearn-dl-zh/raw/master/docs/bd-ai-agt-llm-rag-kg/img/B21257_07_14.jpg

图 7.14 – LLMs 作为 KG 补全的编码器(arxiv.org/pdf/2306.08302

或者,可以使用少量示例或其他提示技术,并要求一个 LLM 直接完成它们。此外,这种方法还允许你在提示中提供额外的项目。在以前的方法中,我们只提供了三元组 (h,r,t);通过提示工程,我们还可以提供其他上下文元素(关系描述、实体描述等)或添加指令以更好地完成任务。

https://github.com/OpenDocCN/freelearn-dl-zh/raw/master/docs/bd-ai-agt-llm-rag-kg/img/B21257_07_15.jpg

图 7.15 – 基于提示的 KG 补全(arxiv.org/pdf/2306.08302

知识托管和部署

最后一步是 KG 的托管和部署。KG 是一组节点和关系,因此我们可以使用图特定的范式来存储数据。当然,托管 KG 并非没有挑战:

  • 大小:KG 越大,其管理就越复杂

  • 数据模型:我们必须选择一个系统,使我们能够最优地访问我们任务所需的信息,因为不同的系统有不同的优缺点

  • 异质性:图可能包含多种模式,这使得存储更加复杂

  • 速度:随着其增长,知识更新的复杂性也随之增加

  • 用户需求:用户可能有异质的需求,这些需求可能相互冲突,需要我们实施规则和约束

  • 部署:系统必须对用户可访问,并允许轻松推理

KG 的存储有不同选择:

  • KG 可以托管在经典的关系数据库(例如,结构化查询语言(SQL)中,实体和关系存储在表中)。然后,可以从这些表中使用投影重建图的关系结构。使用关系数据库存储大型 KG 可能导致大型表不切实际,或者大量具有复杂层次结构的表。

  • 另一种选择是文档模型,其中数据以元组(键值对)的形式存储,然后组织成集合。这种结构在搜索时可能很方便;它是一个允许快速写入的示意图系统,但更新嵌套集合中的知识可能是一场噩梦。

  • 图数据库是针对存储和搜索图以及数据转换进行优化的数据库。图数据模型包含节点和边,并附加了各种元数据。查询语言也适应了这种结构(并且有些类似于 SQL)。图数据库还有允许异构性和支持速度的优势。Neo4j 是最广泛使用的之一,并使用了一种修改后的查询语言(Cypher)。

  • 三元组存储是数据库直接由三元组组成的地方。今天存在一些数据库,它们以三元组的形式保存信息,并允许在数据库中执行查询。通常,这些数据库也具有对本体和进行逻辑推理的原生支持。

托管带来了一系列挑战,选择数据模型时应考虑到后续应用。例如,如果我们的 KG 需要从我们的关系型数据库中检索数据,使用这个系统在集成方面有优势。然而,在这种情况下,我们将为了异构性和速度而牺牲性能。图数据库在处理性能和图的结构的结构性质方面做得更好,但它可能与其他系统组件的集成不佳。无论我们使用什么系统进行存储,我们都可以根据应用构建混合系统,或者创建一个作为数据库之上的层的 KG。

部署是管道中的最后一步。但这并不意味着故事的结束。KG 很容易过时,因此我们需要考虑知识更新或能够处理新应用的管道。同样,新知识的进入意味着我们必须有知识评估的管道(监控 KG 的质量,确保没有错误输入或没有冲突)。某些知识可能过时或需要删除,以解决法律或隐私问题;因此,我们应该有清理 KG 的管道。其他管道应专注于控制系统的访问和安全。

在本节中,我们看到了创建和部署 KG 所需的所有步骤。现在我们有了我们的 KG,我们可以使用它;在下一节中,我们将讨论如何查找信息并将其作为我们 LLM 的上下文。

使用知识图谱和 LLM 检索信息

在前两章中,我们讨论了 RAG 的能力及其在减少 LLM 生成的幻觉中的作用。尽管 RAG 在研究和工业应用中已被广泛使用,但仍存在局限性:

  • 忽略关系:数据库中的文本是相互关联的,而不是孤立的。例如,一个文档被分割成块;因为这些块属于单个文档,它们之间存在语义联系。当无法通过语义相似性捕获时,RAG 无法捕捉结构化的关系知识。一些作者指出,在科学中,一篇文章与先前作品之间存在重要的关系,这些关系通常通过引用网络来突出显示。使用 RAG,我们可以找到与查询相似的文档,但我们无法找到这个引用网络,从而丢失了这些关系信息。

  • 冗余信息:传递给 LLM 的上下文是一系列连接的块。今天,随着 LLM 的发展,我们可以添加越来越多的上下文(模型的上下文长度越来越长),但它们在冗余信息的存在上遇到了困难。我们添加到上下文中的块越多,用于回答查询的冗余或非必要信息就越多。这种冗余信息的存在降低了模型的表现。

  • 缺乏全局信息:RAG 找到一组文档,但无法找到全局信息,因为文档集不能代表全局信息。这是一个问题,尤其是在摘要任务中。

图检索增强生成GraphRAG)作为一种新的范式出现,试图解决这些挑战。在传统的 RAG 中,我们通过对嵌入向量进行相似性分析来找到文本块。在 GraphRAG 中,我们在知识图谱上进行搜索,并将找到的三元组提供给上下文。因此,主要区别在于,当用户查询到达时,我们在知识图谱中进行搜索,并使用图中包含的信息来回答查询。

https://arxiv.org/pdf/2408.08921(img/B21257_07_16.jpg)

图 7.16 – 直接 LLM、RAG 和 GraphRAG 的比较(arxiv.org/pdf/2408.08921)

正式来说,我们可以将 GraphRAG 定义为一个利用知识图谱(KG)为语言模型(LLM)提供上下文并生成更好响应的框架。因此,该系统与经典的 RAG 非常相似;为了避免混淆,在此语境中,我们将称之为向量 RAG。在 GraphRAG 中,知识图谱是知识库,从中我们可以找到实体和关系的信息。GraphRAG 由三个主要步骤组成:

  1. 基于图的索引G-indexing):在这个初始阶段,目标是构建一个图数据库并正确索引它。

  2. q,在自然语言中,我们希望提取一个子图,我们可以用它来正确回答查询。

  3. 图增强生成G-generation):最后一步是使用找到的知识进行生成。这一步使用一个 LLM 来完成,该 LLM 接收上下文并生成答案。

https://github.com/OpenDocCN/freelearn-dl-zh/raw/master/docs/bd-ai-agt-llm-rag-kg/img/B21257_07_17.jpg

图 7.17 – 图 RAG 框架在问答任务中的概述 (arxiv.org/pdf/2408.08921)

在接下来的小节中,我们将详细讨论每个步骤。

基于图的索引

在第一步,我们需要选择我们的图数据将是什么。通常,使用两种类型的知识图谱:开放的知识图谱或自构建的知识图谱。在前一种情况下,我们可以使用已经可用的知识图谱,并将其适应到我们的 GraphRAG 中。今天,已经建立了许多知识图谱,并且可供使用(例如,Wikidata 是一个收集来自各种维基百科相关项目的数据的知识库)。一些知识图谱专注于特定领域;这些知识图谱对特定领域有更深入的理解(其中一些是开放的且可用的)。或者,你也可以构建自己的知识图谱。

在构建它或在使用它之前,你应该注意索引。适当的索引使我们能够拥有更快、更高效的 GraphRAG。虽然我们可以将知识图谱可视化为一个图,但它仍然存储在数据库中。索引使我们能够访问我们想要找到的信息。因此,存在几种索引类型。例如,我们可以将文本描述与节点、三元组或本体关联起来,然后在搜索中使用这些描述。另一种方式是将图数据转换为向量,并在这些向量空间上进行搜索(嵌入)。我们还可以使用更尊重数据图性质的索引或混合版本。

https://github.com/OpenDocCN/freelearn-dl-zh/raw/master/docs/bd-ai-agt-llm-rag-kg/img/B21257_07_18.jpg

图 7.18 – 基于图的索引概述 (arxiv.org/pdf/2408.08921)

图引导检索

在 GraphRAG 中,检索对于生成高质量响应至关重要(类似于向量 RAG)。对知识图谱的搜索面临两个需要解决的问题:

  • 爆炸性候选子图:随着图的增长,知识图谱中的子图数量呈指数增长。这意味着我们需要高效的算法来探索知识图谱并找到相关的子图。其中一些算法使用启发式方法以提高效率。

  • 相似度测量不足:我们的查询是文本形式,但我们希望在图上执行相似度搜索。这意味着我们的算法必须能够理解文本和结构信息,并且能够成功比较来自不同来源的数据之间的相似度。

我们可以有不同类型的检索器。最简单的是奥巴马实体,在 KG 中,我们取相邻实体,k-hop=1,或者甚至邻居的邻居,k-hop=2,等等)。非参数检索器是最简单且也是最快的系统,但它们在检索准确性方面存在问题(可以通过学习来改进)。有一些机器和深度学习模型是原生在图上训练的。基于 GNN 的检索器是一个例子。图神经网络GNNs)是原生处理图的神经网络,可以用于图上的许多任务(节点分类、边预测等),因此它们搜索与查询相似的子图。

或者,我们可以使用基于 LLM 的检索器,其中我们有一个基于 transformer 的模型来进行搜索。然后模型处理和解释查询以进行搜索。这些 LLM 中有一些是在文本上训练过的模型,然后进行微调以搜索图。一个优点是 LLM 可以用作代理,并使用不同的工具或函数来搜索图。基于 LLM 的检索器和基于 GNN 的检索器都显著提高了检索精度,但计算成本很高。今天也有使用不同方法(同时使用 GNN 和 LLM,或与 LLM 一起使用启发式方法)的替代方案,或者过程可以是多阶段的(例如,先用 LLM 进行初始搜索,然后细化结果)。

就像为向量 RAG 所做的那样,我们可以添加额外的组件来进行增强。例如,在前一章中,我们看到我们可以重写查询或分解过于复杂的查询。查询修改有助于更好地捕捉查询的含义(因为有时查询没有捕捉到用户意图的隐含含义)。检索也可以是一个灵活的过程。在前一章中,我们看到在简单的 RAG 中,检索只进行了一次,但在高级和模块化 RAG 中建立了检索可以多阶段或迭代的变体。更复杂的变体使过程根据查询自适应,因此对于简单的查询,只进行一次检索,而对于更复杂的查询,可能需要进行多次迭代。同样,检索后获得的结果也可以进行修改。例如,即使在使用 GraphRAG 的情况下,我们也可以对检索到的知识进行压缩。实际上,如果我们进行多个检索阶段,我们可能也会发现冗余信息,因此方便过滤掉不相关信息。

今天,使用 GraphRAG 也有重新排序检索结果的方法。一个例子是对找到的各种子图进行排序,并可能选择前k个子图。

https://arxiv.org/pdf/2408.08921](https://github.com/OpenDocCN/freelearn-dl-zh/raw/master/docs/bd-ai-agt-llm-rag-kg/img/B21257_07_19.jpg)

图 7.19 – 基于图的检索的一般架构 (arxiv.org/pdf/2408.08921)

与向量 RAG 相比,另一个区别在于我们如何控制搜索粒度。在向量 RAG 中,粒度是通过决定块的大小来控制的。在 GraphRAG 的情况下,我们不进行分块或寻找块。然而,我们可以通过选择我们找到的内容来在检索过程中控制粒度:

  • 节点: 在 GraphRAG 中,我们可以检索单个实体。节点可以与它们关联属性,然后只将实体及其属性添加到上下文中。这对于目标查询可能很有用。

  • 三元组: 通过扩展搜索粒度,我们选择检索三元组(因此不仅包括节点,还包括它们的关系)。当我们不仅对实体本身感兴趣,还对它们的关系感兴趣时,这很有用。

  • 路径: 在这种情况下,我们仍然扩展检索。路径是一系列节点和关系,因此从实体 X 到实体 Y 的路径是连接它们的实体和关系的所有链。显然,不同实体之间存在多条路径,并且随着图的大小增加,这些路径呈指数增长。因此,我们通常定义规则、使用 GNN 或选择最短路径。

  • 子图: 子图可以被定义为 KG 内部节点和关系的子集。提取子图使我们能够回答复杂查询,因为它允许我们分析实体之间的复杂模式和依赖关系。提取子图有几种方法:我们可以使用特定的模式或进行不同路径的合并。

  • 混合粒度: 我们可以同时使用不同的粒度或选择一个自适应系统。

在 GraphRAG 的情况下,平衡粒度和效率很重要。我们不希望用元素填满上下文,以防止后续 LLM 与无关信息作斗争。这也取决于查询的复杂性:对于简单查询,即使低粒度也足够,而对于复杂查询,更高的粒度更有益。自适应方法可以使系统更高效,同时在需要时保持细微差别。

https://github.com/OpenDocCN/freelearn-dl-zh/raw/master/docs/bd-ai-agt-llm-rag-kg/img/B21257_07_20.jpg

图 7.20 – 不同级别的检索粒度

一旦找到并清理了知识,我们就可以将其提供给 LLM 以生成对查询的响应。这种知识进入提供给 LLM 的提示中,模型生成响应。或者,这种找到的知识可以用于某些特定任务的模型(例如,一个用于回答多项选择题的 GNN)。主要问题是图具有非欧几里得性质,与文本信息的集成并不理想。为此,可以使用图翻译器将找到的图信息转换成 LLM 更容易理解的信息。这种转换提高了 LLM 理解信息的能力。因此,一旦我们在图形式(节点、关系、路径或子图)中找到信息,我们就将其置于 LLM 的上下文中。有一些替代方案:

  • 图形格式:我们可以在提示中直接添加关系和节点的集合,或者我们可以使用一种图形结构表示形式,如邻接表或边表。后者紧凑地传达了更好的关系结构。另一种想法是节点序列,它是根据预定规则生成的。这是一种紧凑的表示,包含了图中节点的顺序。

  • 自然语言:存在特定的图语言,可以将信息转换成自然语言,这种表示对 LLM 来说更为亲切。在这个过程中,我们将找到的子图转换成描述性形式。可以使用模板将图转换,其中节点和关系被填充。在某些模板中,您可以定义节点的最近邻和最远邻(图中的 1-hop 和 2-hop),或者您可以使用 LLM 将此子图转换成自然语言描述。

  • 语法树:图被展平并表示为语法树。这些树具有层次结构的优势,并保持了图的拓扑顺序。这种方法保持了图的属性,但使信息对 LLM 来说更容易消化。

  • 代码形式:检索到的图可以被转换成标准格式,例如图形标记语言GraphML)。这些语言专门为图设计,但它们是结构和文本的混合体。

转换仍然存在困难,因为它必须确保结果是简洁且完整的,同时,对 LLM 来说是可理解的。理想情况下,这种表示还应包括图的结构的详细信息。无论检索到的子图是否转换,结果都输入到 LLM 提示中,并生成响应。

https://github.com/OpenDocCN/freelearn-dl-zh/raw/master/docs/bd-ai-agt-llm-rag-kg/img/B21257_07_21.jpg

图 7.21 – 通过子图转换增强生成(arxiv.org/pdf/2408.08921)

GraphRAG 应用

GraphRAG 有多个应用。第一个是问答(与 RAG 相同的应用)中,我们提取子图,而 LLM 使用它们进行后续推理和回答。问答的一个子分支是常识推理问答,它通常以多选题的形式出现。对于这个子任务,我们通常不使用 LLM,而是使用 GNN 或其他机器学习(ML)模型。然而,KG(因此也是 GraphRAG)在信息检索方面有广泛的应用,例如,如果我们想研究一些感兴趣实体之间的关系。KG 可以单独用来提取实体之间的关系,但添加 LLM 的生成功能使我们能够更好地探索这些关系和上下文细微差别。这是学术和文献研究的一个吸引因素。事实上,在学术界,一篇文章是由来自不同机构的多个作者共同撰写的。一篇文章建立在先前研究的基础上,因此对于每篇文章,都有一个引用网络。这些结构元素很容易用图来建模。最近发表的一个有趣的应用展示了 Ghafarollahi 等人如何使用多个 KG 智能体来分析已发表的文献并提出新的研究假设。简而言之,他们从一个由 1,000 篇文章构建的 KG 中提取路径或子图,然后一个智能体分析本体,然后从这个本体中生成新的研究假设。在这个有趣的应用中,多个智能体协作以创建新的潜在材料搜索。

https://github.com/OpenDocCN/freelearn-dl-zh/raw/master/docs/bd-ai-agt-llm-rag-kg/img/B21257_07_22.jpg

图 7.22 – 科学发现辅助的多智能体图推理系统概述 (arxiv.org/pdf/2409.05556v1)

对 GraphRAG 感兴趣的一个原因是,知识图谱过去常用于事实核查(毕竟,知识图谱是一系列事实的集合),因此向大型语言模型(LLM)提供事实应该会减少 LLM 的幻觉。这一特性使其特别适合生物医学应用。事实上,幻觉是医疗决策应用中的一个严重问题。在医学中,如果 RAG 向量可以减少幻觉,它不允许有全面的视角,尤其是在需要概述来回答问题时。因此,Wu 等人建议使用基于 GraphRAG 的方法,称为MedGraphRAG。在这项工作中,他们使用几个医学来源来创建他们的系统,并利用知识图谱的层次性质。他们为他们的知识图谱构建了三个层级。在第一层级,有用户提供的文档(医院的医疗报告)。这一层级的实体随后连接到一个更基础的信息层级。第二层级由医学教科书和科学文章构建。最后,在第三层级,有从标准化和可靠来源获得的明确定义的医学术语和知识关系。利用从这个知识图谱中的检索获得了在主要医学问答基准数据集上的最先进结果。其优势在于,这个系统也优于在医学知识上微调的模型,从而实现了大量的计算节省。

https://github.com/OpenDocCN/freelearn-dl-zh/raw/master/docs/bd-ai-agt-llm-rag-kg/img/B21257_07_23.jpg

图 7.23 – MedGraphRAG 框架 (arxiv.org/pdf/2408.04187)

对 GraphRAG 的进一步兴趣源于使用知识图谱(KGs)向用户提出推荐。在电子商务平台上,推荐系统被用来预测用户的未来购买意图,并建议其他感兴趣的产品。因此,有人提出,该系统将新用户与从过去具有相似行为的用户中派生出的子图相匹配,并利用这些信息来预测可能的未来购买并建议合适的产品。此外,这种方法对于法律和金融领域也很有用。在法律领域,案例和司法意见之间存在广泛的引用,法官使用过去的案例和意见来做出新的裁决。给定一个法律案例,GraphRAG 可以建议先前的法律案例并帮助决策。在金融领域,GraphRAG 可能会建议先前的金融交易或客户案例。

最后,到目前为止,我们建议 GraphRAG 和向量 RAG 是相互对立的。实际上,这两个系统都有优点和缺点,因此将它们协同使用会更有用。Sarmah 等人提出了HybridRAG,在该系统中同时使用了 GraphRAG 和向量 RAG。他们的系统在金融响应方面显示出优势。未来,可能会有系统利用这两种方法,并增加一个路由器来选择是否搜索知识图谱或向量数据库。或者,可能会有更复杂的系统用于上下文中的知识融合(特别是如果知识图谱搜索和块提供了一些冗余信息)。

在本节中,我们讨论了如何将 LLM 连接到知识图谱,以及如何使用它来查找丰富 LLM 上下文的信息。在下一节中,我们将讨论 LLMs 和 KGs 协同有用的其他任务。

理解图推理

本节致力于讨论如何解决图数据任务。在本节中,我们将讨论一些用于解决知识图谱上任务的途径:知识图谱嵌入、图神经网络(GNNs)和大型语言模型(LLMs)。知识图谱嵌入和 GNNs 各需要至少一章内容;因此,这些主题超出了本书的范围,但我们相信对这些内容的介绍对实践者是有益的。实际上,嵌入和 GNNs 都可以与 LLMs 和代理协同使用。

有许多任务需要模型理解结构来解决,这些任务统称为图结构理解任务。许多这些任务都是使用专门设计来学习这些任务的算法或模型来解决的。今天,我们正在开发一种新范式,我们试图使用 LLMs 来解决这些任务;我们将在本节末尾深入讨论这一点。任务的例子可能包括度计算(一个节点有多少邻居)、路径搜索(定义两个节点之间的路径,计算哪条是最短路径等)、哈密顿路径(识别一个只访问每个节点一次的路径)、拓扑排序(识别节点是否可以按拓扑顺序访问)以及许多其他任务。其中一些任务很简单(度计算和路径搜索),但其他任务则要复杂得多(拓扑排序和哈密顿路径)。

https://github.com/OpenDocCN/freelearn-dl-zh/raw/master/docs/bd-ai-agt-llm-rag-kg/img/B21257_07_24.jpg

图 7.24 – 图结构理解任务 (arxiv.org/pdf/2404.14809)

另一方面,图学习任务要求模型不仅包括图的结构,还包括图的属性(节点、边和图的特征),从而理解图的语义信息。一些任务的例子包括节点分类(根据节点的属性及其邻居的属性进行分类)、图分类(你必须理解整个图才能对其进行分类)、边分类和节点特征解释(解释节点的某个特征)。知识图谱问答KGQA)是这类任务之一,因为我们需要理解实体和关系的结构和意义来回答问题。一个类似的任务是执行知识图谱查询以生成文本(这也可以被视为一个子任务)。知识图谱嵌入捕获图中的多关系语义和潜在模式,使它们在关系推理和符号链接预测任务(例如 KG 链接预测)中特别有用。另一方面,图神经网络(GNNs)捕获图结构和节点/边特征;这使得它们在需要归纳推理、使用特征或图结构的局部/全局表示(节点或图分类/回归)的任务中表现良好。

https://github.com/OpenDocCN/freelearn-dl-zh/raw/master/docs/bd-ai-agt-llm-rag-kg/img/B21257_07_25.jpg

图 7.25 – 图学习任务 (arxiv.org/pdf/2404.14809)

知识图谱嵌入

知识图谱(KGs)在表示知识方面非常有效,但它们在规模上的操作却很复杂。这在我们对特定任务感兴趣时,例如链接预测和实体分类,会使得它们的使用变得复杂。因此,知识图谱嵌入KGE)被提出以简化这些任务。我们已经在第一章中讨论了嵌入的概念。嵌入是将数据投影到低维且连续的向量空间中,这在我们的数据具有稀疏表示(如文本和图)时特别有用。对于一个知识图谱,嵌入是将图(节点、边及其特征向量)投影到这个降低的空间中。然后,KGE 模型试图学习一个既保留结构又保留信息的投影,以便它可以用于下游任务。

学习这种表示方法并非易事,已经提出了几种类型的算法。例如,一些 KGEs 试图保留实体之间的关系模式。TransE 是一种将 KGs 嵌入欧几里得空间的方法,其中实体之间的关系是向量。TransE 基于这样的想法,即三元组中连接的两个实体在空间中应该尽可能接近。此外,从三元组 (h, r, t) 中,它试图学习一个空间,其中 h + r ≈ t,从而允许我们进行各种数学运算。RotatE,另一种方法,也试图使用复杂的向量空间来保留其他关系模式(对称性、反对称性、逆运算和组合)。当我们想要回答需要这种对称性概念(婚姻是对称的)或组合(我的侄子是我哥哥的儿子)的问题时,这非常有用。然而,其他方法试图保留结构模式。实际上,较大的 KGs 包含在嵌入过程中丢失的复杂和复合结构。例如,层次结构、链结构和环结构在经典嵌入过程中丢失。当我们想要进行推理或为某些任务提取子图时,这些结构很重要。ATTH(另一种 KG 嵌入方法)使用双曲空间来同时保留层次结构和逻辑模式。然而,其他方法试图模拟实体和关系的不确定性,使诸如链接预测等任务更容易。

https://arxiv.org/pdf/2211.03536(img/B21257_07_26.jpg)

图 7.26 – KGs 中三种典型结构的示意图(arxiv.org/pdf/2211.03536)

KGEs 已被广泛用于诸如链接预测等任务。在这种情况下,利用小空间试图识别最有可能缺失的链接。同样,连续空间允许模型用于三元分类。进一步的应用是使用学习到的嵌入来构建推荐系统。

图神经网络

在使用图形与 ML 算法原生结合时存在几个挑战。首先,经典的 ML 模型接受的是矩形或网格状的数据,这使得将其应用于图形非直观。此外,对于图形,我们想要使用的信息有:节点、边、全局上下文和连通性。最后一个尤其难以表示,我们通常使用邻接矩阵。这种表示是稀疏的,随着图中节点数量的增加而大幅增长,因此空间效率低下。此外,由于图中没有顺序,我们可以得到几个传递相同信息的邻接矩阵,但可能不会被模型识别。

GNN 是一种深度学习模型,它原生地接受一个图,并在其学习过程中利用其结构。有不同类型的 GNN(在 进一步阅读 部分有一些综述,你可以深入了解这个主题),但在这里我们将关注主要框架:消息传递。大多数 GNN 可以被视为图卷积网络,其中我们为每个节点聚合来自其邻居的信息。GNN 的一个优点是,在训练任务的同时,它还为每个节点学习一个嵌入。在每一步,这个节点嵌入都会用来自邻居的信息进行更新。

消息传递框架在某种程度上与神经网络非常相似,正如我们之前所看到的。在这种情况下,有两个主要步骤:收集各个邻居节点的嵌入,然后跟随一个聚合函数(这取决于不同的架构而不同)和非线性函数。然后,在每一步,我们更新每个节点的嵌入,学习图的新表示。一个经典的 GNN 可以由一系列 GNN 块和一个最终层组成,该层利用学习到的表示来完成任务。这可以写成如下公式:

<mml:math display=“block”>mml:msubsupmml:mrowmml:mih</mml:mi></mml:mrow>mml:mrowmml:miv</mml:mi></mml:mrow>mml:mrowmml:mo(</mml:mo>mml:mil</mml:mi>mml:mo+</mml:mo>mml:mn1</mml:mn>mml:mo)</mml:mo></mml:mrow></mml:msubsup>mml:mo=</mml:mo>mml:msupmml:mrow<mml:mi mathvariant=“bold-italic”>W</mml:mi></mml:mrow>mml:mrowmml:mo(</mml:mo>mml:mil</mml:mi>mml:mo+</mml:mo>mml:mn1</mml:mn>mml:mo)</mml:mo></mml:mrow></mml:msup>mml:mo∙</mml:mo>mml:mrowmml:munder<mml:mo stretchy=“false”>∑</mml:mo>mml:mrowmml:miw</mml:mi>mml:mo∈</mml:mo><mml:mi mathvariant=“script”>N</mml:mi>mml:mo(</mml:mo>mml:miv</mml:mi>mml:mo)</mml:mo>mml:mo⋃</mml:mo>mml:mo{</mml:mo>mml:miv</mml:mi>mml:mo}</mml:mo></mml:mrow></mml:munder>mml:mrowmml:mfracmml:mrowmml:mn1</mml:mn></mml:mrow>mml:mrowmml:msubmml:mrowmml:mic</mml:mi></mml:mrow>mml:mrowmml:miw</mml:mi>mml:mo,</mml:mo>mml:miv</mml:mi></mml:mrow></mml:msub></mml:mrow></mml:mfrac></mml:mrow></mml:mrow>mml:msubsupmml:mrowmml:mih</mml:mi></mml:mrow>mml:mrowmml:miw</mml:mi></mml:mrow>mml:mrowmml:mo(</mml:mo>mml:mil</mml:mi>mml:mo)</mml:mo></mml:mrow></mml:msubsup></mml:math>

在这里,在第 l+1 层,我们基于之前的嵌入学习一个表示,hW 是一个特定于层的权重矩阵,v 是一个节点,w 是邻居集合,c 是归一化系数。

https://github.com/OpenDocCN/freelearn-dl-zh/raw/master/docs/bd-ai-agt-llm-rag-kg/img/B21257_07_27.jpg

图 7.27 – GNNs

在这种情况下,我们假设每个邻居的贡献是相同的。这可能并不成立,因此受到 RNN 和 transformers 的注意力机制的启发,提出了图注意力网络(GATs)。在这种类型的 GNN 中,模型学习为每个邻居学习不同的重要性级别。目前存在几种 GNN 层模型,但基本原理变化不大。

GNNs 已成功应用于多个图任务,但仍存在一些局限性,如难以扩展、批处理问题等。它们也已被应用于知识图谱,但增加了复杂性。

LLM 在知识图谱上的推理

LLM 的优势在于它们不是为特定任务训练的,而是在训练过程中获得了一组广泛技能。此外,LLM 具有可以通过特定方法改进的推理技能。因此,一些研究人员建议使用 LLM 进行图推理。接近 LLM 的主要方法是用提示作为输入。有三种方法:

  • 手动提示:最简单的方法是向 LLM 提供一个提示,要求它们在图上解决一个任务。在提示中,输入图,并可以添加额外信息(例如,如果我们希望 LLM 使用深度优先搜索(DFS)算法来解决任务,我们提供该算法如何工作的简要说明)。这些提示的局限性在于,无法在提示中插入宽图(这是由于 LLM 的上下文长度限制造成的)。

  • 自我提示:LLM 通过持续更新提示来使任务解决更加容易。换句话说,给定一个原始提示,LLM 进行提示更新以更好地定义任务及其解决方式。然后,基于 LLM 的输出,生成一个新的提示并反馈给 LLM。这个过程可以多次进行以优化输出。

  • API 调用提示:这种提示类型受到代理的启发,其中 LLM 被提供一组它可以调用的 API,用于对图或其他外部工具进行推理。

https://github.com/OpenDocCN/freelearn-dl-zh/raw/master/docs/bd-ai-agt-llm-rag-kg/img/B21257_07_28.jpg

图 7.28 – 将 LLM 应用于图任务的提示方法 (arxiv.org/pdf/2404.14809)

寻找复杂提示策略的替代方法是监督微调(SFT)方法。在这种情况下,使用包含图任务及其解决方案的数据集来训练模型,以提高其推理能力。

LLMs(大型语言模型)的另一个有趣方面是它们也可以与其他模型结合使用。这使得它们的特性可以与专门针对某些任务而优化的模型相结合。例如,我们可以将 LLMs 与 GNNs(图神经网络)结合,在这种情况下,LLMs 可以作为 GNNs 的增强器。GNN 在处理图结构方面比 LLM 做得更好,但 LLM 在处理文本属性方面做得更好。然后我们可以利用这两个模型的优势,以获得一个更强的协同模型。LLMs 比其他模型具有更大的语义和句法能力,这使得它们能够创建强大的文本嵌入。然后 LLM 可以生成数值嵌入,这些嵌入随后被 GNN 用作节点特征。例如,我们有一个科学论文之间的引用网络(我们的图中每个节点是一篇论文),我们希望将文章分类到各种主题中。我们可以取每篇文章的摘要,并使用 LLM 创建摘要的嵌入。这些数字向量将成为文章的节点特征。在这个阶段,我们可以训练我们的 GNN,比没有特征时获得更好的结果。或者,如果节点特征是文本的,我们可以使用 LLM 来生成标签。例如,对于我们的文章网络,我们每个节点都有与之关联的文章标题,我们使用 LLM 在零样本设置下生成一组标签。这很有用,因为手动标注很昂贵,所以我们可以更快地获得标签。当我们获得标签后,我们可以在图上训练一个 GNN。或者,我们也可以考虑同时进行 LLM 和 GNN 的微调。

https://github.com/OpenDocCN/freelearn-dl-zh/raw/master/docs/bd-ai-agt-llm-rag-kg/img/B21257_07_29.jpg

图 7.29 – LLM 和 GNN 协同

另一种有趣的方法是图形成推理。一些用于推理的提示技术没有考虑到人类思维不是线性的,因此根据某些观点,这种推理方法可以用图来近似。有两种利用这种想法的方法:

  • 距离 = 60 km时间 = 1.5 小时使用速度 = 距离 ÷ 时间速度 = 40 km/h,边表示每个思想如何导致下一个。这种图结构使模型能够逐步推理,探索替代方案或验证计算。

  • 在图上验证:在这种方法中,我们使用图来验证推理的正确性和一致性。例如,如果不同的路径应该导致一个逻辑结论,它们应该是相同的或相似的。所以,如果有矛盾,这意味着推理是错误的。通常,人们会为一个问题生成几个推理,将它们结构化为一个图,并分析它们以改进最终答案。这种方法需要一个验证者来分析这个图,通常是另一个 LLM。

https://github.com/OpenDocCN/freelearn-dl-zh/raw/master/docs/bd-ai-agt-llm-rag-kg/img/B21257_07_30.jpg

图 7.30 – 在图上思考并在图上验证(arxiv.org/pdf/2404.14809

在本节中,我们讨论了图和 LLM 之间错综复杂的关系以及它们如何使我们能够解决一些以前使用图 ML 算法执行的任务。在下一节中,我们将讨论一些尚未解决的问题领域的令人兴奋的视角。

知识图谱和 GraphRAG 的持续挑战

KGs 是存储和组织信息的有力媒介,但仍存在局限性和未解决的问题。特别是对于大型 KGs,可扩展性很重要;必须在表达性和计算效率之间取得平衡。此外,构建 KG 需要大量的计算工作(使用 LLM 从大量文本语料库中提取三元组可能很昂贵,并且需要足够的基础设施)。此外,一旦 KG 构建完成,就必须对其进行评估和清理,这也需要一些努力(手动或计算)。此外,KG 的增长也意味着支持访问或使用的基础设施成本的增长。查询大型 KGs 需要拥有优化算法以避免越来越大的延迟时间风险。工业 KGs 可以包含数十亿个实体和关系,代表着复杂和庞大的规模。许多算法是为小型 KGs(多达数千个实体)设计的,因此在大规模 KGs 中的检索仍然具有挑战性。

此外,知识图谱(KGs)众所周知是不完整的,这意味着必须拥有管道来完善 KG。这意味着需要既有添加额外来源的管道,也有更新数据源的管道。大多数数据库都是静态的,因此创建动态系统具有挑战性。这对于在金融等领域充分利用 KG 至关重要,在这些领域,我们希望考虑到市场的快速变化。顺便提一下,KGs 可以是多模态的,但整合这些模式并不容易。虽然添加模态可以显著提高推理过程,对存储知识的细微差别和 KG 的丰富性的理解,但它也显著增加了管理复杂性(需要更多存储,更复杂的管道,更复杂的知识协调等等)。

GraphRAG 是一种相对较新的技术,并且尚未完全优化。一方面,信息检索可以改进,尤其是在用户的文本查询和知识图谱检索之间的转换。随着知识图谱的不断扩大,找到冗余信息并损害生成过程的风险也随之增加。检索之后,我们可能会得到一个很长的上下文,这个上下文被提供给大型语言模型进行生成。为了减少噪声和降低计算量,我们可以压缩上下文,但这会带来信息丢失的风险。目前,无损压缩是一个活跃的研究领域,而现有方法最多只能允许在压缩和信息保留之间进行权衡。目前还缺乏基准标准来评估新的 GraphRAG 方法;这不允许对当前或未来的方法进行轻松比较。GraphRAG 允许考虑实体间关系和结构化知识信息,减少冗余文本信息,并能够再次找到全局信息。然而,与此同时,文本的细微差别却丢失了,GraphRAG 在抽象问答任务或当问题中没有明确提到实体时表现不佳。因此,向量和 GraphRAG(或 HybridRAG)的结合对未来来说是一个令人兴奋的前景。了解这两种技术如何以最佳方式集成仍然很有趣。

重要的一点是,LLM 并没有专门训练用于图任务。正如我们在第三章中提到的(B21257_03.xhtml#_idTextAnchor042),LLM 被训练来预测序列中的下一个单词。通过优化这个简单的目标,它们获得了大部分技能。显然,仅从文本中吸收空间理解是困难的。这意味着 LLM 通常在结构数据上遇到困难。这一点在表格数据中得到了突出,LLM 在理解表格和关系上存在问题。第一个问题是 LLM 在数值表示上遇到困难,因为标记化步骤使得 LLM 难以理解整个数字(缺乏一致的十进制表示,以及数值运算问题)。这随后影响了需要这种数值理解的图任务的执行。对图理解的具体研究表明,LLM 对图结构有基本理解。LLM 以线性形式理解这些图,并且比图的结构拓扑结构更好地理解与节点相关的标签。根据这些研究,LLM 有基本理解,这强烈受到提示设计、提示技术、提供的语义信息和示例存在的影响。下一代多参数 LLM 在解决小图上的简单任务时取得成功,但随着图和任务复杂性的增加,它们的性能迅速下降。这种对结构数据缺乏理解有两个原因。第一个原因是,在用于训练 LLM 的大文本语料库中,没有多少来自图的数据。因此,LLM 只能学习基本的时空关系,因为这些关系在文本中有所描述。因此,在图数据集上进行的 SFT 可以产生比许多更大的模型更好的结果。

https://arxiv.org/pdf/2403.04483(img/B21257_07_31.jpg)

图 7.31 – 在图数据上使用 SFT 比大 LLM 对小 LLM 有更好的性能(arxiv.org/pdf/2403.04483)

另一方面,第二个原因源于人类理解空间结构良好的原因。人类从他们在外部世界的经验中学习空间关系。大脑创建心理地图,使我们能够在空间中定位自己。这些地图还使我们能够更好地理解抽象的空间概念,如图形。LLM 没有心理地图,也不能有外部世界的经验,这使得它们在理解抽象空间概念方面处于不利地位。

摘要

第五章第六章中,主要问题是如何找到信息以及如何使用这些信息来生成对用户问题的答案。动态地查找信息使我们能够减少我们模型的幻觉并保持其知识更新。

在本章中,我们从一个文本语料库开始,创建了一个系统来找到生成答案的最相关信息(朴素 RAG)。接下来,我们创建了一个更复杂的系统,试图提取仅相关的信息,避免冗余信息或噪声。对于一些研究人员来说,文本本质上包含与背景噪声混合的相关信息。重要的是实体及其关系。从这个还原论方法中产生了在知识图谱中表示基本知识的想法。图使我们能够使用算法来搜索信息或探索可能的连接。长期以来,图推理和 LLM 一直在平行轨道上运行,但最近,它们的故事开始交织在一起。我们已经看到了 LLM 和 KG 之间如何以各种方式进行交互。例如,LLM 可以用来提取用于图构建的关系和实体,或者 LLM 可以用来对 KG 进行推理。同样,我们可以使用 KG 来寻找知识并丰富 LLM 的上下文,从而使其能够有效地回答用户问题。

目前,存在一种类似摩尼教式的定义:要么是向量 RAG,要么是 GraphRAG。两者都有优点和缺点,研究趋势指向这两个世界的统一(HybridRAG)。在未来,我们将找到更多将知识图谱和向量结合在一起的高级方法。此外,对于 LLM 的一侧的图结构理解仍然不成熟。随着训练数据集的增长,新一代 LLM 暴露于更多图示例。然而,在抽象概念如图中的空间关系理解,也意味着在具有更大现实相关性的问题中理解它们。因此,这是一个活跃的研究领域,特别是对于必须在太空中互动并使用人工智能的机器人。

进入太空是人工智能的下一个前沿领域之一。太空中的交互带来了独特的挑战,例如平衡探索和开发。在下一章中,我们将更抽象地讨论这一概念。我们将关注强化学习和代理在太空关系中的行为。无论是棋类游戏、视频游戏还是现实世界环境,代理必须学习如何与太空互动以实现目标。在下一章中,我们将探讨如何使代理能够在不失去目标的情况下探索世界。

进一步阅读

第八章:强化学习与 AI 代理

第 5-7 章中,我们讨论了如何让我们的模型访问外部记忆。这种记忆存储在另一种类型的数据库中(向量或图),通过搜索,我们可以查找回答问题所需的信息。然后,模型将接收到在特定环境中所需的所有信息,然后回答,提供明确和离散的现实世界信息。

然而,正如我们在第七章中稍后看到的,LLMs 对现实世界的知识和理解有限(无论是常识推理还是空间关系)。

人类通过探索学习在空间中移动并与环境互动。在这个过程中,我们通过试错学习,人类知道我们不能触摸火或如何找到回家的路。同样,我们通过与他们的互动学习如何与其他人类建立联系。我们与真实世界的互动使我们能够学习并改变我们的环境。环境通过感知为我们提供信息,我们处理并从中学习这些信息,最终用于改变环境。这是一个循环过程,其中我们感知环境的变化并做出反应。

我们不能仅仅通过读书来学习所有这些技能;这是不可能的。因此,与环境互动对于学习某些技能和知识至关重要。没有这一点,我们将发现很难完成某些任务。所以,我们需要一个系统,允许人工智能通过探索与环境互动和学习。强化学习RL)是一种范式,它专注于描述智能代理如何在动态环境中采取行动。RL 控制代理的行为,在给定环境中采取哪些行动(以及该环境的状况),以及如何从中学习。

因此,在本章中,我们将讨论 RL。我们将从该主题的一些理论开始。我们将从一个简单的案例开始,其中代理需要理解如何平衡探索与利用,以找到解决问题的获胜策略。一旦定义了基础知识,我们将描述如何使用神经网络作为代理。我们将查看目前用于与环境交互和学习的最流行的一些算法。此外,我们将展示如何使用代理来探索环境(例如训练代理解决视频游戏)。在最后一节中,我们将讨论大型语言模型LLMs)与 RL 的交集。

在本章中,我们将讨论以下主题:

  • 强化学习简介

  • 深度强化学习

  • LLM 与 RL 模型的交互

技术要求

大部分代码可以在 CPU 上运行,但最好在 GPU 上运行。当我们讨论如何训练一个代理学习如何玩电子游戏时,这一点尤其正确。代码是用 PyTorch 编写的,大部分使用标准库(PyTorch、OpenAI Gym)。代码可以在 GitHub 上找到:github.com/PacktPublishing/Modern-AI-Agents/tree/main/chr8

强化学习简介

在前面的章节中,我们讨论了一个从大量文本中学习的模型。人类——以及越来越多的 AI 代理——通过试错学习得最好。想象一个孩子学习堆叠积木或骑自行车。没有明确的老师指导每一个动作;相反,孩子通过行动、观察结果并调整来学习。这种与环境互动——其中行动导致结果,而这些结果又塑造未来的行为——是我们学习的关键。与被动地从书籍或文本中学习不同,这种学习是有目标的,并且基于经验。为了使机器能够以类似的方式学习,我们需要一种新的方法。这种学习范式被称为强化学习(RL)。

更正式地说,婴儿通过与环境互动,从行为及其效果之间的因果关系中学到东西。孩子的学习不仅仅是探索性的,而是有特定目标的;他们学习必须采取哪些行动才能实现目标。在我们的一生中,我们的学习往往与我们的环境互动以及环境如何对我们的行为做出反应有关。这些概念被视为学习理论和一般智能的基础。

强化学习被定义为机器学习的一个分支,其中系统必须做出决策,以在给定情况下最大化累积奖励。与监督学习(其中模型从标记的例子中学习)或无监督学习(其中模型通过在数据中检测模式来学习)不同,在强化学习中,模型从经验中学习。事实上,系统没有被告知必须执行哪些动作,而是必须探索环境并找出哪些动作可以使它获得奖励。在更复杂的情况下,这些奖励可能不是立即的,而是稍后才会到来(例如,在棋盘上牺牲一个棋子但最终获胜)。因此,在更广泛的意义上,我们可以说强化学习的基本原理是试错和延迟奖励的可能性。

从这个基础上,我们推导出两个重要的概念,这些概念将成为我们讨论强化学习(RL)的基础:

  • 探索与利用:模型必须利用之前获得的知识来实现其目标。同时,它必须探索环境以便在未来做出更好的选择。在这两个方面之间必须取得平衡,因为解决问题可能不是最明显的路径。因此,模型必须在利用最佳行动(利用)之前测试不同类型的行动(探索)。即使今天,选择最佳平衡仍然是强化学习理论的一个开放挑战。理解这一点的有用方法是想象有人在新的城市尝试不同的餐厅。一开始,他们可能会尝试各种地方(探索)以了解有什么可用的。在发现几个喜欢的餐厅后,他们可能会开始更频繁地去同一个地方(利用)。但如果他们总是坚持去熟悉的地方,他们可能会错过发现更好的餐厅的机会。挑战在于知道何时尝试新事物,何时坚持有效的方法——这仍然是强化学习中的一个开放问题。

  • 在不确定的环境中实现全局目标:强化学习专注于实现目标,而不需要将问题重新构造成子问题。相反,它解决了一个经典的监督机器学习挑战,这涉及到将复杂问题分解成一般子问题,并制定一个有效的计划。在强化学习的情况下,另一方面,直接定义一个代理必须解决的问题。这并不意味着必须只有一个代理,但可以有多个具有明确目标的代理相互交互。一个相关的例子是学习在新城市高效通勤。一开始,你不会将任务分解成“学习公交时刻表”、“估计步行时间”或“优化天气暴露”这样的子问题。相反,你将目标视为整体:每天按时到达工作地点。通过试错——尝试不同的路线、尝试火车与公交车的对比、调整交通状况——你学会了哪些选项最有效。随着时间的推移,你制定了一个策略,而无需明确标记问题的每个部分。如果你与室友或朋友住在一起,他们也在做同样的事情,你们可能会交换建议或为了最快的路线而竞争,就像强化学习中的多个代理相互作用一样。

强化学习系统中存在几个元素:一个 代理,一个 环境,一个 状态,一个 策略,一个 奖励信号,和一个 价值函数。代理显然是学习者或决策者(与环境交互、做出决策并采取行动的模型)。另一方面,环境是与环境交互的一切。状态代表在特定时间环境的一个特定条件或配置(例如,在移动之前棋盘上棋子的状态)。给定一个状态,代理必须做出选择并选择一个要采取的动作。并非所有空间总是可观察的;我们的代理只能访问状态的局部描述。例如,在一个迷宫中导航的机器人代理只能通过摄像头获取信息,因此只能观察到它面前的东西。从摄像头获得的信息是观察结果,因此模型将只使用状态的一个子集。

https://github.com/OpenDocCN/freelearn-dl-zh/raw/master/docs/bd-ai-agt-llm-rag-kg/img/B21257_08_01.jpg

图 8.1 – 强化学习系统中元素的表现

在前面的图中,我们可以看到环境(在这种情况下,游戏屏幕)是如何以向量形式表示的(这就是状态)。此外,三种可能的行为在这个例子中以标量表示。这使我们能够训练一个算法。

动作是代理在环境中可以执行的可能决策或移动(棋盘上的棋子只能向特定方向移动:主教只能斜着走,车可以垂直或水平移动,等等)。动作集可以是离散的(迷宫中的移动)但也可以是连续的动作空间(在这种情况下,它将是实值向量)。这些动作是实现特定目标策略的一部分,根据环境的状态和政策。

https://github.com/OpenDocCN/freelearn-dl-zh/raw/master/docs/bd-ai-agt-llm-rag-kg/img/B21257_08_02.jpg

图 8.2 – 代理与环境交互选择动作

在前面的图中,我们可以看到时间 0 (t0) 对应于状态 t0;如果我们的代理执行一个动作,这将改变环境。在时间 t1,环境将不同,因此我们将拥有不同的状态,t1

策略定义了代理在特定时间的行为方式。给定环境的状态和可能的行为,策略将行为映射到系统的状态。策略可以是一组规则、查找表、函数或其他东西。策略也可以是随机的,通过为每个行为指定一个概率。从某种意义上说,策略是强化学习的心脏,因为它决定了代理的行为。在心理学中,这可以定义为一系列刺激-反应规则。例如,一个策略可能是每当有机会时吃掉对手的棋子。更常见的是,策略是参数化的:策略的输出是一个可计算的函数,它依赖于一组参数。最广泛使用的系统之一是神经网络,其参数通过优化算法进行优化。

奖励是从环境中接收到的正或负信号。它是一个关键因素,因为它为代理在每个时间步提供了目标。这个奖励被用来定义代理的局部和全局目标。换句话说,在每个时间步,代理从环境中接收一个信号(通常是一个数字),而在长期,代理的目标是优化这个奖励。奖励然后使我们能够确定模型是否表现正确,并使我们能够理解正负事件之间的差异,理解我们与环境的互动以及对于系统状态的适当响应。例如,失去一个棋子可以被视为局部负奖励,而赢得游戏则是全局奖励。奖励通常用于改变策略并对其环境进行校准。

https://github.com/OpenDocCN/freelearn-dl-zh/raw/master/docs/bd-ai-agt-llm-rag-kg/img/B21257_08_03.jpg

图 8.3 – 正负奖励的示例

然而,奖励虽然告诉我们即时什么是正确的,而价值函数则定义了长期的最佳方法。用更技术性的术语来说,状态的价值是一个代理从该状态开始可以期望在未来获得的奖励总量(例如,从该位置开始,代理在游戏中可以收集多少分数)。简单来说,价值函数帮助我们理解如果我们考虑该状态及其后续状态会发生什么,以及未来可能发生什么。然而,奖励和价值之间存在依赖关系;没有前者,我们无法计算后者,尽管我们的真正目标是价值。例如,牺牲一个棋子可能有低奖励,但最终可能是赢得游戏的关键。显然,建立奖励要容易得多,而建立价值函数则很困难,因为我们不仅要考虑当前状态,还要考虑代理所做的所有先前观察。

强化学习的一个经典例子是必须导航迷宫的智能体。状态 S 定义了智能体在迷宫中的位置;这个智能体有一个可能的动作集 A(向东、西、北或南移动)。策略 π 指示智能体在特定状态下必须采取什么动作。奖励 R 可以是当智能体选择不允许的动作时的惩罚(例如撞墙),而价值是走出迷宫。在 图 8*.4* 中,我们展示了智能体与其环境之间的交互:

https://github.com/OpenDocCN/freelearn-dl-zh/raw/master/docs/bd-ai-agt-llm-rag-kg/img/B21257_08_04.jpg

图 8.4 – 强化学习系统概述模型 (arxiv.org/pdf/2408.07712)

在给定的时间步 (t), 智能体观察到环境的当前状态 (St),根据策略 π 选择动作 (At),并获得奖励 (Rt)。此时,循环在新状态 (St+1*) 上重复。策略可以是静态的,也可以在每个循环结束时更新。

在下一节中,我们将开始讨论强化学习的初始示例,从经典的多臂老虎机开始。

多臂老虎机问题

k-armed bandit problem 可能是介绍强化学习最经典的例子。在所有那些模型必须从其动作中学习而不是从正例中接受指导的问题中,都需要强化学习。在 k-armed bandit 问题中,我们有一个具有 n 个独立臂(带枪)的老虎机,每个带枪都有自己的成功概率分布。每次拉动臂时,我们都有随机概率要么获得奖励要么失败。在每次动作中,我们必须选择拉动哪个杠杆,奖励就是我们获得的。目标是最大化在一定时期内(例如,1,000 个动作或时间步)的期望总奖励。换句话说,我们必须找出哪些杠杆给我们带来最好的回报,并将最大化我们的动作(即,我们将更频繁地拉动它们)。

问题可能看起来很简单,但它远非微不足道。我们的智能体无法访问真正的带枪概率分布,必须通过试错来学习最有利的带枪。此外,尽管这个问题很简单,但它与几个现实世界的案例场景有相似之处:为患者选择最佳治疗方案、A/B 测试、社交媒体影响等等。在每一个时间步 t,我们可以选择一个 At 动作并获得相应的奖励 (Rt)。任意动作 a 的价值,定义为 q(a),是在时间步 t 选择此动作时的期望奖励:

<mml:math display=“block”>mml:msubmml:mrowmml:miq</mml:mi></mml:mrow>mml:mrowmml:mi*</mml:mi></mml:mrow></mml:msub><mml:mfenced separators=“|”>mml:mrowmml:mia</mml:mi></mml:mrow></mml:mfenced>mml:mo=</mml:mo><mml:mi mathvariant=“double-struck”>E</mml:mi>mml:mo[</mml:mo>mml:msubmml:mrowmml:miR</mml:mi></mml:mrow>mml:mrowmml:mit</mml:mi></mml:mrow></mml:msub>mml:mo|</mml:mo>mml:msubmml:mrowmml:miA</mml:mi></mml:mrow>mml:mrowmml:mit</mml:mi></mml:mrow></mml:msub>mml:mo=</mml:mo>mml:mia</mml:mi>mml:mo]</mml:mo></mml:math>

如果我们知道每个动作的价值,我们实际上已经解决了这个问题(我们会总是选择价值最高的那个)。然而,我们并不知道动作的价值,但我们可以计算一个估计值,定义为 Qt(a),我们希望它接近 q(a)*。在每一步,我们都有估计值 Qt(a),这些值比其他值大;选择这些动作(拉动杠杆)被称为贪婪动作和利用当前知识。相反,选择估计值较低的动作被称为探索(因为它允许我们探索其他动作会发生什么,从而提高我们对这些动作的估计)。

https://github.com/OpenDocCN/freelearn-dl-zh/raw/master/docs/bd-ai-agt-llm-rag-kg/img/B21257_08_05.jpg

图 8.5 – 多臂老虎机

探索可能会在后续步骤中减少收益,但保证长期收益更大。这是因为我们的估计可能不正确。探索允许我们纠正动作的估计值。特别是在早期步骤中,探索更为重要,以便系统可以了解哪些动作最好。在最后一步,模型应该利用最好的动作。为此,我们需要一个系统,允许我们平衡探索和利用。

为了得到价值的初始估计,我们可以取收到的奖励的平均值:

Qta=sumofrewardswhentheactionaistakenpriortotimesteptnumberoftimestheactionaistakenpriortotimestept=∑i=1t−1Ri·1{Ai=a}∑i=1t−11{Ai=a}

在这个简单方程中,1代表一个变量,表示在时间步长中是否使用了动作(使用时为 1,未使用时为 0)。

1{Ai=a}=1ifactionaistakenattimestepi0otherwise

如果动作从未被使用过,分母将为零;为了避免结果为无穷大,我们使用默认值(例如,0)。如果步数趋于无穷大,估计值应收敛到真实值。一旦获得这些估计值,我们就可以选择动作。选择动作最简单的方法是选择最大值(贪婪动作)。arg max函数正是如此:

At=argmaxaQta

正如我们之前所说的,我们并不总是想选择贪婪动作,但我们希望模型也能探索其他动作。为此,我们可以引入一个概率 ε,这样智能体将以相等的概率从其他动作中选择。简单来说,模型几乎总是选择贪婪动作,但以概率 ε 选择其他动作(无论其值如何)。通过增加步骤数,其他动作也将被测试(在无限大时,它们将被测试无限多次),确保 Q 收敛到 q*。同样,选择最佳动作的概率收敛到 1 - ε。这种方法被称为 ε-greedy 方法,它允许在利用和探索之间进行一些平衡。

举一个简单的例子,我们可以想象一个有 10 个臂的老虎机(k=10),其中我们有一个动作值 q,我们有一个正态分布来表示每个动作的真实值。在这里,我们绘制了 1,000 个示例:

https://github.com/OpenDocCN/freelearn-dl-zh/raw/master/docs/bd-ai-agt-llm-rag-kg/img/B21257_08_06.jpg

图 8.6 – 动作值分布

在以下示例中,我们比较了不同的 ε-贪婪方法。奖励随着智能体经验的增加而增加,然后达到平台期。与允许探索的方法相比,纯贪婪方法次优。同样,选择一个过高的 ε 常数(ε=0.5)会导致比纯贪婪方法更差的结果。

https://github.com/OpenDocCN/freelearn-dl-zh/raw/master/docs/bd-ai-agt-llm-rag-kg/img/B21257_08_07.jpg

图 8.7 – 不同贪婪方法的时间步平均奖励

为了研究这种现象,我们可以查看智能体的最优选择(图 8*.8*)。

https://github.com/OpenDocCN/freelearn-dl-zh/raw/master/docs/bd-ai-agt-llm-rag-kg/img/B21257_08_08.jpg

图 8.8 – 不同贪婪方法的时间步最优选择百分比

贪婪方法只有三分之一的时间选择最优选择,而包含一些探索的方法有 80%的时间(ε=0.1)%选择最优选择。这个结果表明,能够探索环境的智能体可以达到更好的结果(可以识别最优动作),而长期贪婪的智能体会选择次优动作。此外,ε-贪婪方法比贪婪方法更快地找到最优动作。

在这个例子中,我们探索了简单的方法,其中 ε 是常数。在某些变体中,ε 随着步骤数的增加而减少,允许智能体在环境被充分探索后从探索转移到利用。ε-greedy 方法在几乎所有情况下都表现最佳,尤其是在存在更大的不确定性(例如,更大的方差)或系统非平稳时。

到目前为止,我们看到的系统在样本数量较多时并不高效。因此,我们不是取观察到的奖励的平均值,而是可以使用一种逐步方法(目前最广泛使用的方法)。对于一个被选择i次的动作,奖励将是Ri*,我们可以计算估计值Qn*如下:

Qn=R1+R2+⋯+Rnn−1

在这一点上,我们不必每次都重新收集平均值,而是可以记录计算结果,并以这种方式逐步进行更新:

<mml:math display=“block”>mml:msubmml:mrowmml:miQ</mml:mi></mml:mrow>mml:mrowmml:min</mml:mi>mml:mo+</mml:mo>mml:mn1</mml:mn></mml:mrow></mml:msub>mml:mo=</mml:mo>mml:msubmml:mrowmml:miQ</mml:mi></mml:mrow>mml:mrowmml:min</mml:mi></mml:mrow></mml:msub>mml:mo+</mml:mo>mml:mfracmml:mrowmml:mn1</mml:mn></mml:mrow>mml:mrowmml:min</mml:mi></mml:mrow></mml:mfrac>mml:mo[</mml:mo>mml:msubmml:mrowmml:miR</mml:mi></mml:mrow>mml:mrowmml:min</mml:mi></mml:mrow></mml:msub>mml:mo-</mml:mo>mml:msubmml:mrowmml:miQ</mml:mi></mml:mrow>mml:mrowmml:min</mml:mi></mml:mrow></mml:msub>mml:mo]</mml:mo></mml:math>

这可以简单地看作是一种逐步调整期望值:

Estimatednew←Estimatednew+step_size[Target−Estimatedold]

实际上,我们可以将[目标 - 估计旧值]视为一种我们在尝试逐步纠正并使其接近真实目标的估计误差。智能体试图将价值估计移动到真实值。

我们可以测试这种逐步实现,并看到在初始的探索阶段之后,智能体开始利用最优选择:

https://github.com/OpenDocCN/freelearn-dl-zh/raw/master/docs/bd-ai-agt-llm-rag-kg/img/B21257_08_09.jpg

图 8.9 – 逐步实现

1/n可以被替换为一个固定的步长参数α

<mml:math display=“block”>mml:msubmml:mrowmml:miQ</mml:mi></mml:mrow>mml:mrowmml:min</mml:mi>mml:mo+</mml:mo>mml:mn1</mml:mn></mml:mrow></mml:msub>mml:mo=</mml:mo>mml:msubmml:mrowmml:miQ</mml:mi></mml:mrow>mml:mrowmml:min</mml:mi></mml:mrow></mml:msub>mml:mo+</mml:mo>mml:miα</mml:mi>mml:mo[</mml:mo>mml:msubmml:mrowmml:miR</mml:mi></mml:mrow>mml:mrowmml:min</mml:mi></mml:mrow></mml:msub>mml:mo-</mml:mo>mml:msubmml:mrowmml:miQ</mml:mi></mml:mrow>mml:mrowmml:min</mml:mi></mml:mrow></mml:msub>mml:mo]</mml:mo></mml:math>

使用α不仅简化了计算,还减少了这种方法固有的偏差。实际上,对于动作的初始值估计Q1(a),的选择可以显著影响早期决策和收敛行为。α还允许你更好地处理非平稳问题(其中奖励概率随时间变化)。初始期望值通常设置为 0,但可以选择大于 0 的值。这种替代方案称为乐观贪婪策略;这些乐观值刺激智能体更多地探索环境(即使我们使用ε=0 的纯贪婪方法)。缺点是我们必须测试不同的初始Q值,而在实践中,几乎所有从业者都将其设置为 0 以方便。

通过测试乐观贪婪方法,我们可以看到它的行为与ε-贪婪方法相似:

https://github.com/OpenDocCN/freelearn-dl-zh/raw/master/docs/bd-ai-agt-llm-rag-kg/img/B21257_08_10.jpg

图 8.10 – 乐观贪婪与ε-贪婪方法的比较

在非平稳问题中,α可以设置为给予近期奖励比先前奖励更大的权重。

最后一点:到目前为止,我们选择了具有更高估计值的贪婪动作。相比之下,我们随机选择非贪婪动作。我们不仅可以随机选择它们,还可以根据它们的潜在最优性和不确定性来选择。这种方法称为上置信界UCB),其中动作A的选择基于:

At=argmaxa[Qta+clntNt(a)]

其中 ln(t) 代表 t 的自然对数,c> 0 控制探索,而 Nt 是动作 A 被测试的次数。这种方法意味着所有动作都将被测试,但那些估计值较低(并且已被频繁测试)的动作将以递减的方式再次被选择。想象一下在挑选不同的餐厅。UCB 帮助你在去你已经喜欢的餐厅(高估计值)和尝试可能更好但尚未访问过的餐厅(高不确定性)之间取得平衡。随着时间的推移,它自然会减少对表现不佳的选项的探索,同时继续测试那些尚未充分探索但可能很好的选项。UCB 工作得非常好,尽管它在除了多臂老虎机以外的其他方法中很难应用。

如您所见,UCB 通常给出更好的结果:

https://github.com/OpenDocCN/freelearn-dl-zh/raw/master/docs/bd-ai-agt-llm-rag-kg/img/B21257_08_11.jpg

图 8.11 – UCB 对贪婪方法的改进

多臂老虎机是强化学习的一个经典例子,但它允许人们开始理解强化学习是如何工作的。

多臂老虎机已被用于多个应用,但它是一个过于简化的系统,不能应用于不同的现实世界情况。例如,在棋局中,目标不是吃掉棋子,而是赢得比赛。因此,在下一小节中,我们将开始探讨考虑比即时收益更早目的的方法。

马尔可夫决策过程

马尔可夫决策过程MDPs)是动作不仅影响即时奖励,还影响未来结果的问题。在 MDPs 中,延迟奖励比我们在多臂老虎机问题中看到的权重更大,而且在决定不同情况下的适当动作时也是如此。

想象你正在穿越迷宫。你进入的每个交叉点或走廊都是一个状态,你做出的每个转弯都是一个改变你状态的行动。一些路径会带你更接近出口(最终奖励),而其他路径可能会让你绕圈子或走到死胡同。每一步的奖励可能不是即时的——你只有在到达终点时才会得到大奖励。因此,你采取的每个行动都需要考虑它如何影响你后来达到目标的机会。

在马尔可夫决策过程(MDP)中,这个想法被形式化:智能体必须在每个状态下决定最佳动作,而不仅仅是即时奖励,还要最大化长期成功,这使得它们比像多臂老虎机这样的简单问题更复杂,在多臂老虎机问题中,只考虑即时奖励。

之前我们只是估计 q(a)。现在,我们想要在状态 s 的存在下估计动作 a 的价值,即 q(s,a)。在每个时间步,智能体接收环境的表示 St 并执行动作 At,获得奖励 R,并移动到新的状态 St+1*。可以看出,智能体的动作可以改变环境的状态。

在有限的 MDP 中,状态、动作和奖励的集合包含有限数量的元素。变量 RS 是依赖于先前状态和动作的概率分布。我们可以使用状态转移概率函数 p(s’, r | s, a) 来描述这个系统的动力学:

ps′,r|s,a=PrSt=s′,Rt=rSt−1=s,At−1=a}

换句话说,状态和奖励取决于先前的状态和动作。在每一步,新的状态和奖励将来自前一个周期。这将重复有限的一系列事件。因此,每个状态将总结所有先前信息(例如,在井字棋中,新的系统配置给我们关于先前移动的信息),并被称为马尔可夫状态,具有马尔可夫属性。马尔可夫状态的优势在于每个状态都包含我们预测未来的所有所需信息。前面的函数描述了当我们执行动作时,一个状态如何演变到另一个状态。遵循此属性的 RL 问题被称为 MDP。因此,从这个函数中,我们可以推导出关于环境的任何我们关心的事情。然后我们可以推导出状态转移概率:

ps′|s,a=PrSt=s′St−1=s,At−1=a}=∑r∈Rps′,r|s,a

我们还可以推导出状态-动作对的预期奖励:

<mml:math display=“block”>mml:mir</mml:mi><mml:mfenced separators=“|”>mml:mrowmml:mis</mml:mi>mml:mo,</mml:mo>mml:mia</mml:mi></mml:mrow></mml:mfenced>mml:mo=</mml:mo><mml:mi mathvariant=“double-struck”>E</mml:mi><mml:mfenced close=“]” open=“[” separators=“|”>mml:mrowmml:msubmml:mrowmml:miR</mml:mi></mml:mrow>mml:mrowmml:mit</mml:mi></mml:mrow></mml:msub></mml:mrow>mml:mrowmml:msubmml:mrowmml:miS</mml:mi></mml:mrow>mml:mrowmml:mit</mml:mi>mml:mo-</mml:mo>mml:mn1</mml:mn></mml:mrow></mml:msub>mml:mo=</mml:mo>mml:mis</mml:mi>mml:mo,</mml:mo>mml:msubmml:mrowmml:miA</mml:mi></mml:mrow>mml:mrowmml:mit</mml:mi>mml:mo-</mml:mo>mml:mn1</mml:mn></mml:mrow></mml:msub>mml:mo=</mml:mo>mml:mia</mml:mi></mml:mrow></mml:mfenced>mml:mo=</mml:mo>mml:mrowmml:munder<mml:mo stretchy=“false”>∑</mml:mo>mml:mrowmml:mir</mml:mi>mml:mo∈</mml:mo>mml:miR</mml:mi></mml:mrow></mml:munder>mml:mrowmml:mir</mml:mi></mml:mrow></mml:mrow>mml:mrowmml:munder<mml:mo stretchy=“false”>∑</mml:mo>mml:mrowmml:mis</mml:mi>mml:mo∈</mml:mo>mml:miS</mml:mi></mml:mrow></mml:munder>mml:mrowmml:mip</mml:mi><mml:mfenced separators=“|”>mml:mrowmml:msupmml:mrowmml:mis</mml:mi></mml:mrow>mml:mrowmml:mi’</mml:mi></mml:mrow></mml:msup>mml:mo,</mml:mo>mml:mir</mml:mi></mml:mrow>mml:mrowmml:mis</mml:mi>mml:mo,</mml:mi>a</mml:mi></mml:mrow></mml:mfenced></mml:mrow></mml:mrow></mml:math>

我们可以推导出状态-动作-下一个状态三元组的期望奖励:

rs,a,sʹ=ERt|St−1=s,At−1=a</mml:mi>,St=s′=∑r∈</mml:mi>R</mml:mi>rps′,r|s,aps′|s,a

这展示了该框架的灵活性。简而言之,t 不一定是时间步,而是一系列状态(一系列决策、机器人的移动等),这使得 MDP 成为一个灵活的系统。毕竟,在 MDP 中,每个问题都可以归结为三个信号:动作、状态和奖励。这使我们能够更好地抽象化,以表示以目标为导向的学习。

对于一个智能体来说,其目标显然是在长时间内最大化累积奖励(而不是立即收益)。这个系统已被证明非常灵活,因为许多问题都可以用这种方式形式化(只需要找到一种定义奖励的方法,以便智能体学会如何最大化它们)。有一点需要注意的是,智能体会尝试以任何可能的方式最大化奖励;如果目标定义不明确,可能会导致意想不到的结果(例如,在棋类游戏中,目标是赢得游戏;如果奖励是吃掉一个棋子而不是赢得游戏,那么智能体将尝试最大化吃掉的棋子数量,即使这可能导致输掉游戏)。

这可以更正式地表达,其中G是从步骤t开始并在此之后收到的累积奖励总和:

<mrow><mrow><msub><mi>G</mi><mi>t</mi></msub><mo>=</mo><msub><mi>R</mi><mrow><mi>t</mi><mo>+</mo><mn>1</mn></mrow></msub><mo>+</mo><msub><mi>R</mi><mrow><mi>t</mi><mo>+</mo><mn>2</mn></mrow></msub><mo>+</mo><mo>…</mo><mo>+</mo><msub><mi>R</mi><mi>T</mi></msub></mrow></mrow>

因此,我们的目标是最大化G。对于有明确结束的序列来说,这更容易定义(例如,只能进行有限次移动的游戏)。一系列定义明确的步骤被称为一个回合,最后一个状态被称为终止状态。请注意,每个回合都是相互独立的(输掉一局不会影响下一局的结果)。当然,这并不总是可能的;还有一些明确的连续任务,其中没有净结束(例如,在环境中移动的机器人),对于这些任务,前面的方程不适用。在这种情况下,我们可以使用所谓的折现率γ。这个参数允许我们决定智能体的行为:当γ接近 0 时,智能体将尝试最大化即时奖励,而当γ接近 1 时,智能体将更加重视未来的奖励:

<mrow><mrow><msub><mi>G</mi><mi>t</mi></msub><mo>=</mo><msub><mi>R</mi><mrow><mi>t</mi><mo>+</mo><mn>1</mn></mrow></msub><mo>+</mo><mi>γ</mi><msub><mi>R</mi><mrow><mi>t</mi><mo>+</mo><mn>2</mn></mrow></msub><mo>+</mo><msup><mi>γ</mi><mn>2</mn></msup><msub><mi>R</mi><mrow><mi>t</mi><mo>+</mo><mn>3</mn></mrow></msub><mo>+</mo><mo>…</mo><mo>=</mo><mrow><munderover><mo>∑</mo><mrow><mi>k</mi><mo>=</mo><mn>0</mn></mrow><mi mathvariant="normal">∞</mi></munderover><mrow><msup><mi>γ</mi><mi>k</mi></msup><msub><mi>R</mi><mrow><mi>t</mi><mo>+</mo><mi>k</mi><mo>+</mo><mn>1</mn></mrow></msub></mrow></mrow></mrow></mrow>

正如我们在多臂老虎机问题部分所看到的,我们可以估计价值函数(一个智能体处于某个状态的好坏)。考虑策略π,状态S的价值是从S开始并遵循策略π的期望回报:

vπs=EπGt|St=s=Eπ∑k=0∞γkRt+k+1|St=sforalls∈S

这被称为策略 π 的状态-价值函数,Gt 是期望回报。同样,我们可以在策略 π 下定义在状态 S 中采取动作 A 的价值:

qπs,a=EπGt|St=s,At=a=Eπ∑k=0∞γkRt+k+1|St=s,At=a

这被称为策略 π 的动作-价值函数。我们可以通过经验(与环境交互)来估计这些函数,并且在无限远处,它们应该接近真实值。这种进行许多实际回报随机样本平均的方法被称为蒙特卡洛方法。

为了提高效率,我们可以将其重写为递归形式(使用折现):

vπs=EπGt|St=s=EπRt+1+γGt+1|St=s=∑aπ(a|s)∑s′,rps′,r|s,ar+γvπs′foralls∈S

这种简化的形式被称为 Bellman 方程。这可以表示为从上一个状态向前思考下一个状态。从一个具有策略的状态,我们选择一个动作,并按照给定的概率获得奖励(或不获得)。Bellman 方程对这些概率进行平均,给它们发生的可能性赋予权重。这个方程是许多优秀强化学习算法的基础。

![图 8.12 – Bellman 回退图

图 8.12 – Bellman 回退图

现在我们既有状态值函数 vπ(s) 和动作值函数 qπ(s, a),我们可以评估策略并选择最好的策略。动作值函数允许我们根据状态选择最佳动作。例如,考虑德克萨斯扑克游戏的情况。一个玩家有 100 美元,必须从状态 π 出发选择策略。策略 π1 的状态值函数返回 10,而 π2 的回报为 -2。这意味着第一个策略带来期望收益 10,而 π2 带来期望损失 2。给定一个状态 s,玩家想要弄清楚选择哪个动作。例如,选择是否下注 10 或 5,qπ (s, a) 告诉我们从这个动作中期望累积奖励是多少。因此,前面的方程使我们能够确定选择哪个动作或策略以最大化奖励。

图 8.12可以理解,解决强化学习任务意味着找到一种最优策略,在长期内成功收集到许多奖励。对于马尔可夫决策过程(MDPs),我们可以定义一个最优策略,因为我们可以在所有状态下评估一个策略是否比另一个策略更好,如果它对所有状态的预期回报更高。π* 表示最优策略,它是所有可能策略中具有最大值函数的那个:

v*s=maxπvπsforall<mi}s∈S

最优策略共享相同的最佳动作值函数 q*,它被定义为所有可能策略中最大动作值函数:

q*s,a=maxπqπs,aforall<mi}s∈S

这两个函数之间的关系可以总结如下:

qs,a=ERt+1+γv(St+1)|St=s,At=a

该方程表示给定一个状态-动作对时的累积回报。然而,在强化学习(RL)中,最优值函数是一个理想的状态,但找到最优策略尤其困难,尤其是在任务复杂或计算成本高昂时。因此,强化学习试图通过使用动态规划DP)等方法来近似它们。动态规划的目的就是利用值函数来寻找好的策略(即使不是精确解)。在这个阶段,我们可以推导出最优状态值函数 v(s)* 和最优动作值函数 q∗ *(*s, a) 的贝尔曼最优方程:

qs,a=ERt+1+maxaʹq(St+1,aʹ)|St=s,At=a=∑s′,rp(s′,r|s,a)r+γmaxa′q*(s′,aʹ)

对于有限马尔可夫决策过程(MDP),贝尔曼最优性方程只有一个解,如果我们知道系统的动态特性,那么它们是可以被解决的。一旦我们得到 v,就很容易识别出最优策略 q;拥有最优的 q,我们就可以识别出最优动作。v 的美妙之处在于它允许我们在考虑长期目标的同时选择最佳动作。为一个问题求解这些方程就是通过强化学习(RL)来解决问题。另一方面,对于许多问题,解决它们意味着计算所有可能性,因此计算成本过高。在其他情况下,我们可能无法确定环境的动态特性,或者状态可能不具有马尔可夫性质。然而,这些方程是强化学习的基础,许多方法都是这些方程的近似,通常使用先前状态的经验。因此,这些算法并不识别最佳策略,而是识别一个近似。例如,许多算法学习最频繁状态的最优动作,但可能对不常见或罕见的状态选择次优动作。技巧是这些选择不应影响未来的奖励量。例如,即使代理在罕见情况下没有做出最佳动作,它仍然可能赢得游戏。

DP 指的是一组算法,这些算法用于在给定环境的完美模型作为马尔可夫决策过程(MDP)的情况下计算最佳策略。现在,这些算法需要大量的计算,并且完美模型的假设并不总是成立。因此,这些算法不再实际使用;同时,今天可以将算法定义为受 DP 算法启发,目的是减少计算量,即使在环境完美模型的假设不成立的情况下也能工作。简而言之,DP 算法是通过将贝尔曼方程转换为更新规则来改进所需价值函数的近似而获得的。这允许使用价值函数来组织对良好策略的搜索。为了评估策略,我们可以使用状态价值函数,并评估遵循策略 π 从每个状态预期的回报:

vπs=ERt+1+γvπ(St+1)|St=s,At=a=∑aπ(a|s)∑s′,rp(s′,r|s,a)[r+γvπ(s′)]

计算策略的价值函数旨在识别更好的策略。对于一个状态 s,我们想知道是否应该保持该策略,改进它,或者选择另一个策略。记住,策略的选择决定了智能体将采取哪些行动。为了回答“改变策略是否更好?”这个问题,我们可以考虑在状态 s 下遵循策略 π 选择一个动作会发生什么:

qπs,a=ERt+1+γvπ(St+1)|St=s,At=a=∑s′,rp(s′,r|s,a)[r+γvπ(s′)]

一个更好的策略 π’ 应该为我们提供更好的 vπ*(s)* 值。如果 π’ 小于或等于 vπ*(s),我们可以继续相同的策略。换句话说,根据具有更好 vπ(s)* 的策略 π’ 来选择动作比另一个策略 π 更有益。

在本节中,我们看到了经典的强化学习算法,但它们都没有使用神经网络或其他机器学习模型。这些算法在简单情况下表现良好,而对于更复杂的情况,我们希望有一个更复杂和适应性强的系统。在下一节中,我们将看到如何将神经网络集成到强化学习算法中。

深度强化学习

深度强化学习深度强化学习)是强化学习的一个子领域,它将强化学习与深度学习相结合。换句话说,其背后的思想是利用神经网络的学习能力来解决强化学习问题。在传统的强化学习中,策略和价值函数由简单的函数表示。这些方法在低维状态和动作空间中表现良好(即当环境和代理可以轻松建模时)。当环境变得更加复杂或更大时,传统方法无法泛化。在深度强化学习中,策略和价值函数由神经网络表示。理论上,神经网络可以表示任何复杂的函数(通用逼近定理),这使得深度强化学习方法能够解决具有高维状态空间的问题(如呈现图像、视频或连续任务)。通过建模复杂函数,这使得代理能够学习到更通用和灵活的策略,这在复杂情况下是必要的,在这些情况下,使用传统方法定义函数是不可能的。这种学习能力使得深度强化学习方法能够解决视频游戏、移动机器人等问题。

https://arxiv.org/abs/1708.05866(img/B21257_08_13.jpg)

图 8.13 – 深度强化学习概述 (arxiv.org/abs/1708.05866)

在接下来的小节中,我们将讨论如何对这些算法进行分类以及它们之间的区别。

无模型与基于模型的比较方法

目前深度强化学习的方法如此之多,以至于很难对这些模型进行分类。尽管如此,深度强化学习方法可以大致分为两大类:无模型和基于模型。这种划分可以通过回答以下问题来表示:智能体是否有权访问(或学习)环境的模型?

  • 无模型方法:这些方法在不需要构建环境模型的情况下确定最优策略或价值函数。这些模型直接从观察到的状态、动作和奖励中学习。智能体直接从试错中学习,从环境中获得反馈,并使用这些反馈来改进其策略或价值估计。这些方法通常更容易实现和进行参数调整(它们只需要观察状态-动作-奖励序列或转换)。它们也更容易扩展,计算复杂度更低。

  • 基于模型的方法:这些方法依赖于环境内部模型来预测给定任何状态-动作对的未来状态和奖励。这个模型可以在训练前学习或预先定义。拥有模型允许智能体在类似情况下进行规划和为未来场景(例如,游戏中对手的未来动作以及预测它们)制定计划。基于模型的方法的优势在于它们可以减少与真实环境的交互,并且在规划复杂任务方面表现更好。潜在的改进性能是以增加复杂性(构建准确的环境模型可能具有挑战性,尤其是在高维环境中)和增加计算成本为代价的。

https://github.com/OpenDocCN/freelearn-dl-zh/raw/master/docs/bd-ai-agt-llm-rag-kg/img/B21257_08_14.jpg

图 8.14 – 无模型与基于模型的比较方法

基于模型强化学习方法的主要优势在于其规划和前瞻性思考的能力。通过利用模型(通常是一个神经网络)来模拟环境的动态,它可以预测未来场景,这在复杂环境或需要考虑长期结果的情况下特别有用。例如,当奖励稀疏或延迟——如在国际象棋中,只有赢得游戏才能获得奖励——模型可以模拟各种路径以优化智能体达到奖励的策略。

在动态环境中,规划也证明是有益的。模型可以快速更新其内部表示,使智能体能够调整其策略而无需从头开始重新学习。这最小化了大量重新训练的需求,如在自动驾驶等应用中,智能体可以调整其策略而无需大量新的数据集。从这种规划中获得的认识可以随后被提炼成学习策略,随着时间的推移提高智能体的性能。

此外,模拟与环境交互减少了大量现实世界探索的需求,这在交互成本高、危险或耗时的情况下至关重要,例如在机器人或自动驾驶汽车中。通过利用其内部模型,智能体可以优先考虑行动并优化其探索过程,以更有效地更新或改进其对环境的理解。

这有助于智能体优化长期目标,因为它可以模拟其行动的长期后果,监控其向更远目标的进展,并使其行动与遥远的目标保持一致。

模型构建可能是一项复杂的任务。然而,环境的确切模型并不总是对智能体可用。在这种情况下,智能体被迫仅从经验中学习以创建自己的模型。这可能导致智能体模型中的偏差。因此,智能体可能在学习的模型上表现最优,但在真实环境中表现极差(或次优)。

在策略方法和离策略方法之间

在强化学习(RL)中,另一个重要的分类是模型如何从经验中学习,以及它们是否从当前策略或不同策略中学习(它们根据策略与策略更新的关系进行分类):

  • 策略学习方法:这些方法从代理当前策略中学习到的动作中进行学习(因此代理既收集数据又从同一策略中学习)。策略学习方法评估和改进用于决策的策略;这是基于遵循当前策略时采取的动作和收到的奖励(代理通过直接评估和改进策略来执行策略更新)。因此,代理不使用来自其他策略的数据。其优点是代理往往更稳定,且不太容易受到变化的影响(优化的策略实际上是用来与环境交互的策略)。策略学习方法效率不高,因为它们丢弃了从先前策略中获得的数据(样本效率低下),这限制了它们在复杂环境中的应用,因为它们需要大量的数据。此外,这些方法不太具有探索性,因此在需要更多探索的地方不太有益(它们更适用于稳定的环境)。一个例子是聊天机器人,它学习给出更好的答案来回答用户的问题:聊天机器人使用特定的策略来给出答案,并通过利用从用户那里收到的反馈来优化这个策略。策略学习方法确保学习到的策略与聊天机器人采取的动作以及与用户的真实互动相关联(这确保了稳定性)。

  • 离策略方法:离策略方法独立于智能体的动作学习最优策略的价值(智能体从由不同策略生成并由学习策略使用的经验中学习)。因此,这些方法可以从过去的数据或由其他策略生成的数据中学习。离策略方法将行为策略(用于收集数据)与目标策略(正在学习策略)分开。换句话说,行为策略用于探索环境,而目标策略用于提高智能体的性能(在学习最优目标策略的同时确保更多的探索行为)。离策略方法具有更高的样本效率,因为它们可以重用数据并允许更好的探索,这可以导致更快地收敛到最优策略。另一方面,它们不太稳定(因为它们不学习当前策略已经采取的动作,行为策略和目标策略之间的差异可能导致更新中的更高方差)并且可能更加复杂。一个例子是音乐推荐系统,它向用户推荐新的标题,并必须探索不同的流派和新发行的音乐。行为策略鼓励探索,因此生成关于用户偏好的数据,而目标策略寻求优化用户的推荐性能。将这两种策略分开可以尝试不同的推荐策略,而不会影响最终推荐的品质。这些方法的优势在于它们允许广泛的探索,这对于复杂和动态环境非常有用。

https://github.com/OpenDocCN/freelearn-dl-zh/raw/master/docs/bd-ai-agt-llm-rag-kg/img/B21257_08_15.jpg

图 8.15 – 在策略和离策略方法

在下一个子节中,我们将开始详细讲解深度强化学习是如何工作的。

详细探索深度强化学习

我们将从定义开始,以便更好地理解深度强化学习。在一个系统中的状态 s 通常是一个向量、矩阵或其他张量。在每个时间步 t,我们可以将环境描述为张量的形式(例如,棋盘上棋子的位置可以用矩阵表示)。同样,智能体可以选择的动作 a 也可以用张量表示(例如,每个动作可以关联到一个独热向量、一个矩阵等)。所有这些都是机器学习中已经常见的结构,我们可以将它们作为深度学习模型的输入。

到目前为止,我们泛泛地讨论了策略,但我们使用什么函数来建模它们?通常,我们使用神经网络。因此,在本节中,我们将积极探讨神经网络如何在强化学习算法中使用。我们现在看到的是基于本章的内容,但我们将使用神经网络来决定采取什么行动(而不仅仅是函数)。正如我们在第一章中看到的,神经网络由一系列按层次组织在一起的神经元组成。神经网络以张量作为输入并产生张量作为输出。在这种情况下,神经网络的输出是选择一个动作。在这种情况下,优化策略意味着优化神经网络的参数。基于经验的强化学习算法可以改变策略函数的参数,使其产生更好的结果。

https://github.com/OpenDocCN/freelearn-dl-zh/raw/master/docs/bd-ai-agt-llm-rag-kg/img/B21257_08_16.jpg

图 8.16 – 作为强化学习策略的神经网络

神经网络是众所周知的深度学习模型,我们了解如何优化它们。使用基于梯度的方法使我们能够理解参数变化如何影响函数的输出。在这种情况下,我们想知道我们应该如何更新我们的策略 P(神经网络模型)的参数,以便在将来收集更多的奖励。因此,有一个函数告诉我们策略的预期奖励,我们可以使用梯度来改变策略的参数,从而最大化回报。

在强化学习中使用神经网络作为策略有几个优点:

  • 神经网络是高度表达的功能逼近器,因此它们可以映射输入(状态)和输出(动作)之间的复杂非线性关系。这对于复杂环境非常有用,例如玩电子游戏或控制 3D 环境中的机器人。此外,神经网络对于具有大而复杂的状态和动作空间的环境具有良好的扩展性。

  • 神经网络具有将情况推广到他们之前未遇到的情况的能力。这种能力使它们在处理意外状态变化时特别有用,从而促进了智能体的适应性。所有这些都使神经网络能够灵活适应不同范围的任务和环境。

  • 神经网络可以处理连续的动作而不是离散的动作,从而使其能够在现实世界问题中使用(在现实世界中,动作通常不受离散集合的限制)。

  • 神经网络是通用的。它们可以与不同类型的数据一起使用,并且不需要特征工程。当特征可能很复杂或状态表示很复杂(传感器、图像、视频等)时,这一点很重要。

  • 它们可以产生概率分布,因此可以与随机策略一起使用。当我们想要添加随机性和鼓励探索时,这一点很重要。

https://github.com/OpenDocCN/freelearn-dl-zh/raw/master/docs/bd-ai-agt-llm-rag-kg/img/B21257_08_17.jpg

图 8.17 – 使用神经网络学习如何玩 Atari 游戏的截图示例 (arxiv.org/abs/1312.5602)

我们现在将介绍五种不同的算法,以便了解不同类型方法之间的差异(离政策和在线策略、无模型和基于模型的方法)。

https://github.com/OpenDocCN/freelearn-dl-zh/raw/master/docs/bd-ai-agt-llm-rag-kg/img/B21257_08_18.jpg

图 8.18 – 强化学习方法的总结表

Q 学习与深度 Q 网络(DQN)

Q 学习深度 Q 网络DQN)的基础,这是一种由 DeepMind 使用的算法,用于训练一个能够解决视频游戏的智能体。在 Q 学习算法中,我们有一个状态-动作值 Q 表,其中每一行代表一个状态,每一列代表一个动作,每个单元格包含对应状态-动作对的估计 Q 值。Q 值最初被设置为 0。当智能体从与环境交互中接收反馈时,我们迭代地更新这些值(直到它们收敛到最优值)。请注意,此更新使用贝尔曼方程进行(表中的 Q 值代表智能体从该状态采取该动作并随后遵循最佳策略时的预期未来奖励)。

Q 学习通过学习每个状态-动作对的优化 Q 值来找到最优策略。最初,智能体随机选择动作,但通过与环境的交互和接收反馈(奖励),它学会了哪些动作是最好的。在每次迭代中,它使用贝尔曼方程进行表格更新。智能体通常选择具有最高 Q 值的动作(贪婪策略),但我们可以通过控制探索程度(ε-贪婪策略)来控制。随着时间的推移,这些估计变得越来越准确,模型收敛到最优 Q 值。

https://github.com/OpenDocCN/freelearn-dl-zh/raw/master/docs/bd-ai-agt-llm-rag-kg/img/B21257_08_19.jpg

图 8.19 – Q 学习示例

在复杂环境中,由于可能的大规模和计算不可行性,使用表格存储值变得不切实际。相反,我们可以使用 Q 函数,它将状态-动作对映射到 Q 值。鉴于神经网络可以有效地模拟复杂函数,它们可以用来有效地近似 Q 函数。通过提供状态S作为输入,神经网络提供状态-动作对的 Q 值作为输出(换句话说,从该状态可以采取的所有动作的 Q 值)。原理与 Q 学习算法非常相似。我们开始时对 Q 值进行随机估计,使用ε-贪婪策略探索环境,并更新估计。

DQN 架构由三个主要组件组成:两个神经网络(Q 网络和目标网络)以及一个经验回放组件。Q 网络(一个经典的神经网络)是训练用来产生最优状态-动作值的代理。另一方面,经验回放用于生成训练神经网络所需的数据。

Q 网络在多个时间步和多个回合上进行训练,目的是最小化预测 Q 值和目标 Q 值之间的差异。在代理与环境交互的过程中,每个经验(状态、动作、奖励和下一个状态的元组)都存储在这个经验回放缓冲区中。在训练过程中,从缓冲区中随机选择经验批次(新旧经验的混合)来更新 Q 网络。这有助于打破连续经验之间的相关性(有助于稳定训练)并多次重用过去经验(提高数据效率)。目标网络是用于生成训练目标 Q 值的 Q 网络的副本。定期(例如每几千步)通过复制 Q 网络权重来更新目标网络权重;这稳定了训练。在训练过程中,Q 网络预测给定状态的行动 Q 值(预测 Q 值),目标网络预测给定状态的行动目标 Q 值。预测 Q 值、目标 Q 值和观察到的奖励用于计算损失并更新 Q 网络的权重。

https://github.com/OpenDocCN/freelearn-dl-zh/raw/master/docs/bd-ai-agt-llm-rag-kg/img/B21257_08_20.jpg

图 8.20 – DQN 训练算法

DQN 有许多创新和优势:

  • 经验回放使训练更加稳定和高效。神经网络通常以一批数据作为输入,而不是单个状态,因此在训练过程中,梯度将具有更小的方差,权重收敛更快。经验回放还可以在训练过程中减少噪声,因为我们可以对经验进行一种“洗牌”,从而更好地实现泛化。

  • 目标网络的概念引入减轻了非平稳目标的问题,这可能导致训练不稳定。目标网络未经训练,因此目标 Q 值稳定且波动很小。

  • DQN 在处理高维空间如图像时非常有效,能够自行提取特征并学习有效的策略。这些能力使得 DQN 能够通过输入原始像素来掌握 Atari 游戏。

当然,也存在一些缺点:

  • 虽然它比 Q 学习更高效,但 DQN 仍然需要大量的样本来有效地学习;这限制了它在数据很少的任务中的应用(样本效率低)

  • 当动作空间是连续的时,它不稳定,而对于离散动作空间则表现良好

  • 它对超参数的选择(如学习率、重放缓冲区大小和目标网络更新频率)敏感

REINFORCE 算法

DQN 专注于在不同状态下学习动作的价值。REINFORCE则是一种基于策略的方法。这些方法直接学习策略,将状态映射到动作而不学习价值函数。核心思想是通过最大化代理在一段时间内收到的期望累积奖励来优化策略。REINFORCE 是学习如何训练代理处理复杂、连续动作空间的基础算法。

策略由一个神经网络表示,它以当前状态作为输入并产生一个关于所有可能动作的概率分布(代理执行特定动作的概率)。这被称为随机策略,因为我们没有直接输出动作,而是输出概率。策略梯度方法试图直接改进策略(通过在训练期间改变参数),以便策略产生更好的结果。因此,我们再次从一个随机策略(神经网络权重随机初始化)开始,让代理根据其策略在环境中行动,这会导致产生一个轨迹(一系列状态和动作)。如果这个轨迹收集到高奖励,我们将更新权重,使得这个轨迹在未来更有可能被产生。相反,如果代理表现不佳,权重的更新将指导使该轨迹不太可能发生。

https://github.com/OpenDocCN/freelearn-dl-zh/raw/master/docs/bd-ai-agt-llm-rag-kg/img/B21257_08_21.jpg

图 8.21 – 轨迹示例

因此,这个过程的第一步是初始化一个神经网络(策略P)及其参数θ。由于这些权重最初是随机的,以状态作为输入的策略将导致随机动作。然后我们生成一个轨迹τ,让代理与环境交互。从状态s0 开始,我们让代理根据策略P和参数θ进行移动。在实践中,状态S被作为输入给神经网络,并生成一个动作分布。我们从这个分布中采样一个动作a0。这个过程会尽可能重复(例如,直到游戏结束),状态和动作的集合就是我们的轨迹。

https://github.com/OpenDocCN/freelearn-dl-zh/raw/master/docs/bd-ai-agt-llm-rag-kg/img/B21257_08_22.jpg

图 8.22 – 从神经网络获取分布

在轨迹过程中,我们收集奖励(称为奖励到去或也称为回报 Gt)。回报是从时间步 t 到剧集结束所收到的总累积奖励,通过一个因子 γ 进行折现。折现因子决定了未来奖励相对于即时奖励的重要性。在这种情况下,我们有一个函数,它为我们提供给定策略的预期回报,我们希望最大化它。因此,我们计算梯度,并通过梯度上升修改我们神经网络的参数。

REINFORCE 算法在概念上简单且易于实现,适用于连续动作空间(因为它直接学习策略),并支持端到端学习(算法直接从原始数据中学习)。然而,这个算法的一些挑战包括策略更新的高方差(它依赖于完整剧集的返回,更新可能嘈杂且不稳定),需要大量数据(因为它在每次更新后丢弃数据且不重复使用经验,所以需要大量的剧集),它不适用于数据昂贵的环境,并且在存在延迟奖励的情况下表现不佳。

注意,REINFORCE 算法是一个基于策略的算法,因为策略只根据使用相同策略收集的经验进行更新。在每次迭代中,智能体使用更新的策略并使用它来收集经验以进行更新。在离策略方法的情况下,也会使用使用其他策略收集的经验。例如,这就是我们在 DQN 中看到的情况,我们使用的是使用不同策略收集的批次经验。

近端策略优化(PPO)

近端策略优化PPO)是强化学习中最广泛引用和使用的算法之一。由 OpenAI 于 2017 年引入,PPO 旨在平衡策略梯度方法(如 REINFORCE)的简单性与更复杂算法(如信任域策略优化TRPO))的稳定性。本质上,PPO 是一个实用且高效的算法,在基准测试中表现良好,同时相对容易实现和调整。

PPO 与 REINFORCE 相似,但包括重要的改进,使训练更加稳定。基于策略的方法中的一个挑战是超参数的选择(尤其是学习率)以及不稳定权重更新的风险。PPO 的关键创新在于确保策略更新不要太大,因为这可能会破坏训练的稳定性。PPO 通过使用一个限制目标函数的约束来实现这一点,该约束限制了策略在单个更新中可以改变的程度,从而避免了网络权重的剧烈变化。

传统策略梯度方法的一个重大问题是它们无法从较差的更新中恢复。如果一个策略表现不佳,智能体可能在下一次迭代中生成稀疏或低质量的训练数据,从而形成一个难以摆脱的自我强化循环。PPO 通过稳定策略更新来解决这一问题。

在 PPO 中,策略由一个神经网络表示,πθ(a|s),其中θ代表网络的权重。网络将当前状态s作为输入,并输出一个关于可能动作a的概率分布。最初,权重是随机初始化的。随着智能体与环境交互,它根据当前策略生成经验批次(状态、动作、奖励)。智能体还计算优势估计,这衡量所选动作相对于状态期望值的优劣。

与简单的策略梯度方法的主要区别在于 PPO 使用剪辑目标函数。这个函数确保策略更新是稳定的,并防止出现大的、破坏性的变化。如果新旧策略之间的概率比rt(θ)超出范围[1−ϵ,1+ϵ],其中ϵ是一个小的超参数(例如,0.2),则更新将被剪辑。这种剪辑机制确保策略更新保持在安全范围内,防止策略在单次更新中偏离太多。

PPO 的一种常见变体使用actor-critic 架构,其中 actor 学习策略,而 critic 学习价值函数。critic 提供关于动作质量的反馈,有助于减少更新方差并提高学习效率(我们稍后会更详细地讨论这一点)。

总体而言,PPO 是一个既稳定又鲁棒的算法,比简单的策略梯度方法更不容易不稳定,比 TRPO 等更复杂的算法更容易使用。它不需要解决复杂的优化问题或计算二阶梯度,使其成为许多应用的实用选择。然而,PPO 仍然需要仔细调整超参数,例如剪辑参数ϵ、学习率和批量大小。此外,它可能在具有长周期或延迟奖励的环境中出现高方差。

Actor-critic 算法

Actor-critic 算法是 RL 中另一种流行的方法,它结合了两种不同方法的优势:基于价值的方法(如 Q-learning)和基于策略的方法。actor-critic 模型由两个组件组成:

  • Actor:actor 负责决定在环境当前状态下应该采取什么动作。策略通常是一个产生动作概率分布的神经网络。actor 通过优化策略来最大化期望回报。

  • 评价者:评价者通过估计价值函数来评估动作评价者采取的动作。这个函数表示一个动作在预期未来奖励方面的好坏。价值函数可以是状态价值函数 V(s) 或动作价值函数 Q(s,a)

这种方法背后的洞察是,动作评价者是决策者,他学习如何随着时间的推移改进决策。另一方面,评价者是一种顾问,他评估动作的好坏,并对策略提供反馈。

https://github.com/OpenDocCN/freelearn-dl-zh/raw/master/docs/bd-ai-agt-llm-rag-kg/img/B21257_08_23.jpg

图 8.23 – 动作-评价者方法

该过程可以定义为四个步骤:

  1. 代理与环境交互,并根据其策略,基于当前状态选择一个动作。然后它从环境中以奖励和新的状态的形式接收反馈。

  2. 在第二步中,评价者使用奖励和新的状态来计算一个时间差分TD)误差。TD 误差衡量评价者当前对价值函数的估计与观察结果之间的差距。TD 误差然后是时间步长 t 的奖励(加上评价者对下一个状态价值 V(st+1) 的估计的折扣因子 γ,以平衡即时和未来奖励的影响)与评价者对当前状态价值 V(st) 的估计之间的差异。

  3. 评价者通过梯度下降更新其价值函数参数以最小化 TD 误差。

  4. 动作评价者也会更新。动作评价者使用 TD 误差作为反馈信号。如果误差为正,这意味着动作比预期的好,动作评价者应该更频繁地采取这个动作(增加未来采取这个动作的概率)。如果误差为负,动作评价者应该减少概率。动作评价者使用梯度上升来最大化策略;我们希望最大化期望回报。

动作-评价者方法在连续动作空间中表现良好,而基于价值的方法存在问题。它是一个稳定且高效的方法,并减少了策略梯度更新的方差。另一方面,它对超参数敏感,你必须训练两个网络,并且它比 Q 学习或 REINFORCE 更复杂。

优势动作-评价者A2C)是一个流行的变体,其中多个代理并行地与环境的多个实例交互。这允许更快地训练。

AlphaZero

AlphaZero 是 DeepMind 在 2017 年开发的一个开创性的基于模型的强化学习算法,能够掌握国际象棋、将棋(日本象棋)和围棋。它已经达到了超人类的表现,在这些游戏中击败了人类冠军。AlphaZero 的成功在于其创新性地结合了深度学习和蒙特卡洛树搜索MCTS),这使得它能够在没有人类专业知识或手工制定的规则的情况下有效地学习和规划。

AlphaZero 完全通过自我对弈进行学习,除了游戏的基本规则外,没有任何先验知识。它与自己进行数百万次对弈,通过试错逐渐理解什么构成好或坏的走法。这种自我对弈的方法使得 AlphaZero 能够发现最优策略,通常甚至超过由专家人类玩家开发出的策略。此外,它还使模型能够生成大量的训练数据,远远超过仅通过分析人类游戏所能获得的数据。该算法使用深度神经网络来表示策略(采取哪些行动)和价值函数(从给定状态出发游戏的预期结果)。

传统的国际象棋引擎曾经依赖于游戏树搜索技术。在每一步,它们会构建一个表示从当前位置出发所有可能合法走法的游戏树,并执行到一定深度的深度优先搜索(DFS)。这种蛮力搜索检查了所有合法走法,根据由棋类社区制定的启发式评估为最终节点分配值。这些启发式,如国王安全、兵的结构和中心控制,模仿了人类棋手用来判断走法质量的因素。

在评估最终节点后,传统引擎会回溯并分析位置,剪枝较少有希望的分支以简化搜索。尽管有这些优化,这种方法仍然有限制,往往导致次优走法,并且计算成本高昂。这就是 MCTS(蒙特卡洛树搜索)的用武之地。

MCTS 是一种算法,用于在需要提前规划数步的环境中进行决策,特别是在具有大状态空间的游戏中,其中全面搜索是不可行的。MCTS 通过多次模拟游戏来构建搜索树,逐渐通过经验改进对最佳行动的理解。

MCTS 通过以下四个主要步骤操作,重复以细化搜索树:

  1. 选择:从根节点(当前状态)开始,算法使用一种平衡探索(尝试较少探索的走法)和利用(选择已显示出希望的走法)的策略选择子节点。这通常使用树的上界置信度(UCT)公式来完成,该公式考虑了每个节点的平均奖励和访问次数。

  2. 扩展:如果选定的节点不是终端状态(游戏的结束),算法会添加一个或多个子节点,代表从该状态出发的可能行动。这种扩展使得搜索能够覆盖新的潜在走法和结果。

  3. 模拟(滚动出):从新添加的节点开始,MCTS 通过使用简单或随机策略将游戏玩到终端状态来进行模拟,或称为“滚动出”。这个模拟的结果(赢、输或平局)提供了一个奖励,作为对采取的行动价值的估计。

  4. 反向传播:然后,将模拟的奖励反向传播到树中,更新从根节点到路径上每个节点的值。这包括更新每个节点的平均奖励和访问次数。随着时间的推移,这些更新帮助算法确定哪些移动最有希望。

https://github.com/OpenDocCN/freelearn-dl-zh/raw/master/docs/bd-ai-agt-llm-rag-kg/img/B21257_08_24.jpg

图 8.24 – 蒙特卡洛树搜索 (en.wikipedia.org/wiki/Monte_Carlo_tree_search)

AlphaZero 随后使用一个神经网络(一个以棋盘上棋子排列为输入的卷积神经网络)并产生两个输出:策略头(所有可能移动的概率分布,指导智能体考虑哪些移动)和价值头(从当前棋盘位置获胜的可能性,帮助智能体评估各种状态的力量)。AlphaZero 使用 MCTS 来模拟潜在的移动及其结果(智能体在游戏中计划数步)。通过 MCTS,模型探索看似最有希望的移动,并逐渐提高其对游戏的理解。树搜索使用神经网络的策略和价值输出来优先考虑探索树的哪些分支。AlphaZero 通过自我对弈(自我玩耍)来学习。在每一场比赛中,智能体使用 MCTS 来决定移动并保存状态(棋盘上的位置)、选择的移动和结果。这些数据用于改进策略和价值估计(神经网络权重更新)。

https://github.com/OpenDocCN/freelearn-dl-zh/raw/master/docs/bd-ai-agt-llm-rag-kg/img/B21257_08_25.jpg

图 8.25 – AlphaZero 流程 (www.mdpi.com/2079-9292/10/13/1533)

因此,AlphaZero 提出了三个主要创新:

  • 跨游戏泛化:相同的算法用于三种不同的游戏(国际象棋、将棋和围棋),无需针对特定游戏进行调整。

  • 无需人类知识:与使用大量人类游戏和策略数据库的传统棋类引擎不同,AlphaZero 自行学习游戏。该模型优先考虑在游戏中提供长期奖励的策略,而不是仅仅关注单个移动的即时收益。这种方法使模型能够发现人类或传统棋类引擎未曾探索的创新策略。

  • 高效的搜索和学习:使用 MCTS 和深度学习允许更有效地使用计算资源。AlphaZero 不是对所有可能的移动进行广泛的搜索,而是仅关注最有希望的移动。

当然,AlphaZero 也不是没有缺陷。该算法的计算成本巨大,因为它必须与自己玩数百万场比赛。此外,该算法在游戏(或信息完全的环境)中表现良好,但将其适应到信息不完整的环境则更困难。最后,关于实际上理解游戏或学习抽象概念存在讨论,因为模型在某些对人类来说很容易的棋类谜题上失败了。

在下一个子节中,我们将讨论强化学习(RL)的挑战以及新的、令人兴奋的研究方向。

深度 RL 的挑战和未来方向

尽管 RL 取得了重大进展,但仍然存在一些挑战和活跃的研究方向:

  • 在未见过的环境中的泛化:在智能体未见过的环境中的泛化仍然是一个复杂任务。智能体通常在模拟环境中或特定设置中进行训练,在训练后能够表现出色。然而,将学习到的技能转移到新环境、动态环境或变化条件下是困难的。这限制了深度 RL 算法在现实世界中的应用,因为现实环境很少是静态或完全可预测的。真正的泛化要求模型不仅学习特定任务的解决方案,而且能够适应各种情况(即使这些情况在训练期间没有发生)。

  • 奖励函数设计:奖励函数控制智能体的行为、学习和表现。设计奖励函数很困难,尤其是在复杂、分散的环境中。在稀疏奖励设置中,由于反馈有限且通常延迟,定义奖励和函数是复杂但至关重要的。即便如此,仍然存在创建偏差的风险,导致策略过度拟合或出现意外行为,或者使其次优。

  • 基于模型的规划中的复合误差:基于模型的 RL 存在复合误差的风险。预测的视野越长,模型预测中的错误积累越多,导致与最优轨迹的显著偏差。这在复杂或高维空间环境中尤其如此,因此限制了它们在现实环境中的应用。

  • 多任务学习:创建一个可用于多个任务的智能体仍然很困难,存在风险是智能体只学会了较容易的任务而忽略了更复杂(或表现非常差的)任务。此外,多任务模型通常的表现远低于针对单个任务优化的智能体。因此,设计可用于多任务 RL 的智能体是困难的,并且仍然是一个活跃的研究领域。

  • 多模态强化学习:随着计算机视觉和自然语言处理技术的进步,现在有一些深度学习模型可以单独处理一种模式,或者同时处理多种模式。这就是为什么越来越多的讨论使用多模态强化学习,其中智能体可以在多模态环境中移动,并整合来自各种模态的信息。例如,机器人可以从图像中获取环境信息,并以自然语言接收命令或指令。在视频游戏中,智能体接收视觉信息,但也会从与角色的对话或其他玩家那里获取信息。多模态学习仍然很复杂,因为智能体必须同时学习如何处理多模态信息并优化策略以在复杂环境中交互。同样,为这些情况设计奖励函数仍然很困难。

在下一节中,我们将看到如何使用神经网络来学习如何玩视频游戏。

使用强化学习学习如何玩视频游戏

在本小节中,我们将讨论如何训练智能体来玩视频游戏。在这种情况下,智能体将由神经网络参数化。遵循此策略,它将在视频游戏允许的动作中进行选择,从环境中获得反馈,并使用这些反馈进行参数更新。一般来说,视频游戏提供复杂和动态的环境,模拟现实世界场景,因此它们是强化学习算法的优秀测试平台。视频游戏提供高维状态空间(基于像素的状态,详细的世界)和丰富的动作空间(离散或连续),灵感来源于现实世界,可以提供即时和延迟的奖励(例如,某些动作可能导致主角直接死亡,而解决谜题或赢得游戏则需要长期策略)。此外,许多游戏要求用户在掌握环境之前先探索环境。敌人通常是动态的,模型必须学习如何击败对手或理解复杂行为以克服它们。游戏还提供清晰的奖励(通常是频繁的或可以加速的),这使得奖励函数可以轻松定义,从而成为一个安全的游乐场(例如,用于机器人算法)。此外,还有明确的基准,可以快速比较新算法的质量。

我们选择 actor-critic 方法进行此训练,因为它具有以下特点:

  • Actor-critic 可以处理复杂和连续的动作空间(例如控制 3D 环境中的角色),因此可以用于各种游戏。

  • 系统中的 actor 直接学习策略,这使得在找到策略至关重要的场景中效率更高。这在需要快速决策和战略规划的视频游戏中是必要的。

  • 与纯粹基于策略的方法相比,评论家提供了反馈并加快了学习速度。使用价值函数(评论家)来评估动作减少了策略更新的方差,因此在奖励分散的环境中更加稳定和高效。

  • 行为者-评论家允许高效地管理探索和利用之间的平衡,其中行为者通过探索环境,而评论家通过提供反馈来引导它。对于更复杂的环境,行为者-评论家可能不足以满足需求,尽管它是一个良好的初始选择,并且通常足够用。

  • 行为者-评论家(Actor-critic)也可以处理长期规划。在视频游戏中,往往存在长期奖励;评论家的价值函数帮助代理理解其行动的长期影响。

  • 一些变体在并行化和使用数据方面效率很高。A2C 是并行化环境和收集更多数据的良好选择,从而加快训练和收敛速度。

我们选择超级马里奥作为我们的游戏,因为它提供了一个丰富且复杂的环境。这个环境类似于现实世界,像素观察作为输入的表示与真实世界计算机视觉任务相似,这使得超级马里奥成为需要从视觉数据中提取有意义特征的强化学习代理的良好测试平台。这个环境也是部分可观察的,因此需要代理去探索和学习环境。不同的关卡可能需要不同的策略,因此模型必须能够平衡探索和利用。

在游戏中,存在不同种类的挑战,例如导航障碍、面对不同种类的敌人,以及学习最优和动态地跳跃。这些不同的挑战代表了代理应该发展的不同技能:测试代理做出精确和及时行动的能力(跳过障碍或缺口),评估威胁并决定何时避免或参与(避免或参与敌人),以及空间意识和战略规划(导航复杂关卡)。关卡是逐步增加的,因此难度随着代理的学习而增加。此外,既有即时奖励(收集金币),也有延迟奖励(例如,完成关卡),从而允许评估长期策略。

最后,超级马里奥在强化学习研究社区中被广泛采用作为基准。主要的库都支持它,或者它被直接集成,从而允许快速测试算法或进行比较。同时,也有已经深入研究过的策略;游戏文档详尽,是强化学习初学者和专家的良好示例。此外,还有一些可并行化的实现,从而允许有效且快速的训练。

https://github.com/OpenDocCN/freelearn-dl-zh/raw/master/docs/bd-ai-agt-llm-rag-kg/img/B21257_08_26.jpg

图 8.26 – 超级马里奥训练截图

所有代码都可以在以下链接的仓库中找到:github.com/PacktPublishing/Modern-AI-Agents/tree/main/chr8/RL_SuperMario

https://github.com/OpenDocCN/freelearn-dl-zh/raw/master/docs/bd-ai-agt-llm-rag-kg/img/B21257_08_27.jpg

图 8.27 – 仓库截图

脚本描述

为了执行训练,我们将使用一些流行的强化学习库(OpenAI 的 Gym 和 PyTorch)。在仓库中,有一些用于训练智能体的不同脚本:

  • env:此脚本定义了我们的智能体行动的环境(超级马里奥),并允许我们记录智能体玩游戏的视频,预处理模型图像,定义奖励函数,设置世界,设置虚拟摇杆等。

  • model:此脚本定义了一个用于演员-评论家架构的 PyTorch 神经网络模型。该模型旨在处理类似图像的输入,提取特征,然后使用这些特征输出动作概率(演员)和状态价值估计(评论家)。

  • optimizer:此代码定义了一个名为 GlobalAdam 的自定义优化器类,它扩展了 PyTorch 内置的 Adam 优化器的功能。

  • train:此脚本使用异步优势演员-评论家A3C)方法设置和运行一个分布式强化学习系统,以训练一个能够玩超级马里奥兄弟的智能体。

  • test:模型测试在单独的脚本中。此脚本允许您加载训练好的模型来玩游戏,同时渲染游戏过程。

  • process:此脚本作为连接件,将所有前面的组件集成到一个统一的强化学习系统中,用于训练和测试玩超级马里奥兄弟的智能体。

https://github.com/OpenDocCN/freelearn-dl-zh/raw/master/docs/bd-ai-agt-llm-rag-kg/img/B21257_08_28.jpg

图 8.28 – 脚本的全局视图

设置环境

env 脚本允许我们设置环境,特别是对于深度 Q 学习或演员-评论家等强化学习算法。在脚本中,我们导入所需的库,之后有一些用于创建世界和定义智能体如何与之交互的函数:

  • MonitorMonitor 类允许用户保存智能体游戏过程的视觉记录,这对于调试、分析智能体性能和分享结果非常有用。此函数允许我们使用 .ffmpeg 保存游戏视频。

  • process_frameprocess_frame 函数用于预处理游戏帧,使其更适合训练强化学习智能体。此函数检查帧是否为正确格式,将其转换为灰度并减小尺寸,然后进行归一化(简化输入)。这允许智能体专注于视觉信息的重要细节。

  • CustomReward:这是对奖励的修改,以鼓励有用的行为,跟踪当前分数,添加奖励,检查代理是否完成关卡,如果没有完成则对其进行惩罚。这样,它试图通过惩罚失败来激励完成关卡并取得进步。

  • CustomSkipFrame:这个功能通过允许跳过帧来加速训练,从而减少计算量(更少的环境更新)。

  • create_train_env:这个函数设置了一个完全定制和优化的超级马里奥环境,使其准备好用高效的预处理、奖励塑造和帧跳过训练 RL 代理。

定义模型

model脚本中,我们定义了算法的架构。ActorCritic类控制着架构,作为一个神经网络,它基于 PyTorch(实际上,我们使用nn.Module,这是 PyTorch 中的一个经典神经网络)。该类有两个组件代表Actor(负责选择动作)和Critic,它提供反馈。你可以看到我们有一个共享的特征提取器:

self.conv1 = nn.Conv2d(num_inputs, 32, 3, stride=2, padding=1)
self.conv2 = nn.Conv2d(32, 32, 3, stride=2, padding=1)
self.conv3 = nn.Conv2d(32, 32, 3, stride=2, padding=1)
self.conv4 = nn.Conv2d(32, 32, 3, stride=2, padding=1)
self.lstm = nn.LSTMCell(32 * 6 * 6, 512)

这里,我们有一个卷积网络来从游戏中提取空间特征;然后这个输出被重塑成一个 2D 张量,传递给 LSTM。LSTM 更新隐藏状态hx和细胞状态cx(我们在第一章中详细描述了 LSTM),从而管理游戏记忆。

之后,我们初始化这两个组件:

self.critic_linear = nn.Linear(512, 1)
self.actor_linear = nn.Linear(512, num_actions)

使用单个特征提取器允许我们节省计算资源。两个组件产生两个不同的输出:actor_linear为 actor 生成输出,它是一个大小为num_actions的向量。这代表了采取每个动作的概率。critic_linear组件为 critic 生成输出,它是一个单一的标量值。这个值代表了当前状态的估计值(从这个状态期望的回报)。这种分离确保了两个层有各自的目标和不同的学习信号。

接下来,我们将定义不同的损失函数,以便允许不同的学习。正如我们所见,两个组件产生不同的输出:

def forward(self, x, hx, cx):
        x = F.relu(self.conv1(x))
        x = F.relu(self.conv2(x))
        x = F.relu(self.conv3(x))
        x = F.relu(self.conv4(x))
        hx, cx = self.lstm(x.view(x.size(0), -1), (hx, cx))
        return self.actor_linear(hx), self.critic_linear(hx), hx, cx

由于我们希望我们的进程针对分布式学习进行优化,我们使用 Adam 的自定义版本。Adam 是一种经典的优化器,用于更新神经网络的参数。GlobalAdam 类是为分布式强化学习设计的,其中多个进程或智能体共享相同的优化器。关键思想是使优化器状态的一部分在进程间共享,允许智能体高效地协调对模型参数的更新。这特别适用于演员-评论家和特别是有多个智能体在相同环境中行动的变体。想法是我们异步玩几轮游戏,然后进行全局更新,减少计算。GlobalAdam 脚本简单地将 Adam 适配到强化学习问题,允许从不同进程中进行平均和学习:

import torch
class GlobalAdam(torch.optim.Adam):
    def __init__(self, params, lr):
        super(GlobalAdam, self).__init__(params, lr=lr)
        for group in self.param_groups:
            for p in group['params']:
                state = self.state[p]
                state['step'] = 0
                state['exp_avg'] = torch.zeros_like(p.data)
                state['exp_avg_sq'] = torch.zeros_like(p.data)
                state['exp_avg'].share_memory_()
                state['exp_avg_sq'].share_memory_()

训练模型

train 脚本允许我们使用不同的进程异步训练模型。脚本允许我们提供几个参数(默认参数已经输入)。例如,我们可以决定游戏的难度级别(--world--stage),动作的类型(--action_type),优化器的学习率(--lr),针对算法和强化学习(RL)的特定超参数(--gamma--tau--beta),或者与进程及其并行化相关的参数(--num_processes--num_local_steps--num_global_steps)。

train 函数允许我们初始化训练环境,初始化策略,并使用 GPU。global_model.share_memory() 方法允许全局模型的参数对所有进程可访问,从而实现并行更新。您可以看到我们使用 GlobalAdam 来更新全局模型的参数。torch.multiprocessing 包装器(是对多进程模块的包装)允许我们创建多个异步操作的多进程。然后,此脚本定义了使用多个并行进程训练我们的模型。同时,脚本允许轻松配置和定制:

def train(opt):
    torch.manual_seed(123)
    if os.path.isdir(opt.log_path):
        shutil.rmtree(opt.log_path)
    os.makedirs(opt.log_path)
    if not os.path.isdir(opt.saved_path):
        os.makedirs(opt.saved_path)
    mp = _mp.get_context("spawn")
    env, num_states, num_actions = create_train_env(opt.world, opt.stage, opt.action_type)
    global_model = ActorCritic(num_states, num_actions)
    if opt.use_gpu:
        global_model.cuda()
    global_model.share_memory()
    if opt.load_from_previous_stage:
        if opt.stage == 1:
            previous_world = opt.world - 1
            previous_stage = 4
        else:
            previous_world = opt.world
            previous_stage = opt.stage - 1
        file_ = "{}/A3CSuperMarioBros{}_{}".format(opt.saved_path, previous_world, previous_stage)
        if os.path.isfile(file_):
            global_model.load_state_dict(torch.load(file_))
    optimizer = GlobalAdam(global_model.parameters(), lr=opt.lr)
    processes = []
    for index in range(opt.num_processes):
        if index == 0:
            process = mp.Process(target=local_train, args=(index, opt, global_model, optimizer, True))
        else:
            process = mp.Process(target=local_train, args=(index, opt, global_model, optimizer))
        process.start()
        processes.append(process)
    process = mp.Process(target=local_test, args=(opt.num_processes, opt, global_model))
    process.start()
    processes.append(process)
    for process in processes:
        process.join()

测试系统

test 脚本允许定制,例如决定一些参数,如游戏难度、动作等。一旦我们训练了我们的模型,我们就可以加载它,玩游戏,并注册正在玩游戏的智能体。然后,模型根据其策略进行游戏,在此脚本中没有进行优化,因此允许我们观察智能体的表现。

连接所有组件

process 脚本将我们迄今为止看到的所有脚本连接成一个系统。此脚本使用 env 模块中的 create_train_env 函数来设置超级马里奥兄弟游戏环境。这是我们代理与环境交互并学习的地方。脚本还初始化了 ActorCritic 模型(包括演员和评论家),并使用此模型做出决策和评估游戏状态。local_train 函数负责训练,需要 GlobalAdam 优化器。此脚本还用于评估训练模型性能,因此它使用了我们在测试脚本中定义的元素。因此,这个脚本是我们拥有一个完全功能性的强化学习系统的核心部分。它协调环境、模型和训练算法,使一切协同工作以训练一个能够玩超级马里奥兄弟的代理。

local_train 函数使代理能够在更新共享的全局模型的同时并行训练。此函数为可重复性设置了一个种子,因此我们可以重现结果。之后,我们初始化环境(create_train_env)和模型(ActorCritic);如果有 GPU,我们将模型移动到 GPU 并初始化 TensorBoard:

def local_train(index, opt, global_model, optimizer, save=False):
    torch.manual_seed(123 + index)
    if save:
        start_time = timeit.default_timer()
    writer = SummaryWriter(opt.log_path)
    env, num_states, num_actions = create_train_env(opt.world, opt.stage, opt.action_type)
    local_model = ActorCritic(num_states, num_actions)
    if opt.use_gpu:
        local_model.cuda()

在这个点上,我们开始训练循环,其中每个迭代代表一次游戏回合。局部参数与全局参数同步,在每个回合结束时,LSTM 的隐藏和细胞状态被重置:

local_model.train()
    state = torch.from_numpy(env.reset())
    if opt.use_gpu:
        state = state.cuda()
    done = True
    curr_step = 0
    curr_episode = 0
    while True:
        if save:
            if curr_episode % opt.save_interval == 0 and curr_episode > 0:
                torch.save(global_model.state_dict(),
                           "{}/a3c_super_mario_bros_{}_{}".format(opt.saved_path, opt.world, opt.stage))
            print("Process {}. Episode {}".format(index, curr_episode))
        curr_episode += 1
        local_model.load_state_dict(global_model.state_dict())
        if done:
            h_0 = torch.zeros((1, 512), dtype=torch.float)
            c_0 = torch.zeros((1, 512), dtype=torch.float)
        else:
            h_0 = h_0.detach()
            c_0 = c_0.detach()
        if opt.use_gpu:
            h_0 = h_0.cuda()
            c_0 = c_0.cuda()

在这个阶段,我们开始收集多个步骤(opt.num_local_steps)的经验。然后,对于某个状态,模型(局部模型)生成一组概率,并从这些概率中采样一个动作。选择了一个动作后,我们与环境交互,从而获得奖励和新的状态。对于这些步骤中的每一个,我们记录以下内容:是否结束了一个回合,动作的对数概率,价值估计,奖励和政策熵。如果回合结束,状态被重置,隐藏状态被断开以防止梯度反向传播:

for _ in range(opt.num_local_steps):
            curr_step += 1
            logits, value, h_0, c_0 = local_model(state, h_0, c_0)
            policy = F.softmax(logits, dim=1)
            log_policy = F.log_softmax(logits, dim=1)
            entropy = -(policy * log_policy).sum(1, keepdim=True)
            m = Categorical(policy)
            action = m.sample().item()
            state, reward, done, _ = env.step(action)
            state = torch.from_numpy(state)
            if opt.use_gpu:
                state = state.cuda()
            if curr_step > opt.num_global_steps:
                done = True
            if done:
                curr_step = 0
                state = torch.from_numpy(env.reset())
                if opt.use_gpu:
                    state = state.cuda()
            values.append(value)
            log_policies.append(log_policy[0, action])
            rewards.append(reward)
            entropies.append(entropy)
            if done:
                break
        R = torch.zeros((1, 1), dtype=torch.float)
        if opt.use_gpu:
            R = R.cuda()
        if not done:
            _, R, _, _ = local_model(state, h_0, c_0)

现在,是计算损失和进行反向传播的时候了。在这里,我们使用广义优势估计GAE)来平衡偏差和方差,从而使训练更加高效。简单来说,优势函数 A(s,a) 衡量了一个动作 a 相对于给定状态 s 中平均动作的好坏。在下一个脚本中,GAE 用于计算推动演员策略更新的优势值。我们使用 GAE 在演员损失中更新策略,以最大化期望回报但保持方差低。换句话说,我们希望使训练更加稳定。通过添加 GAE,训练过程变得更加高效,并且对高方差回报的噪声或偏差价值估计的不准确性的敏感性降低:

        gae = torch.zeros((1, 1), dtype=torch.float)
        if opt.use_gpu:
            gae = gae.cuda()
        actor_loss = 0
        critic_loss = 0
        entropy_loss = 0
        next_value = R
        for value, log_policy, reward, entropy in list(zip(values, log_policies, rewards, entropies))[::-1]:
            gae = gae * opt.gamma * opt.tau
            gae = gae + reward + opt.gamma * next_value.detach() - value.detach()
            next_value = value
            actor_loss = actor_loss + log_policy * gae
            R = R * opt.gamma + reward
            critic_loss = critic_loss + (R - value) ** 2 / 2
            entropy_loss = entropy_loss + entropy
        total_loss = -actor_loss + critic_loss - opt.beta * entropy_loss
        writer.add_scalar("Train_{}/Loss".format(index), total_loss, curr_episode)
        optimizer.zero_grad()
        total_loss.backward()
        for local_param, global_param in zip(local_model.parameters(), global_model.parameters()):
            if global_param.grad is not None:
                break
            global_param._grad = local_param.grad
        optimizer.step()
        if curr_episode == int(opt.num_global_steps / opt.num_local_steps):
            print("Training process {} terminated".format(index))
            if save:
                end_time = timeit.default_timer()
                print('The code runs for %.2f s ' % (end_time - start_time))
            return

注意,我们有三个独立的损失。第一个是演员损失,它鼓励导致更高奖励的动作。评论员损失惩罚价值估计中的错误,熵损失通过惩罚过于自信的动作分布(在其他过于贪婪的惩罚策略中)来鼓励探索。一旦我们计算了总损失,我们就像任何神经网络一样执行反向传播。目前,我们已经完成了本地训练,因此我们使用本地模型的梯度来进行全局模型更新。每隔一定时间间隔,我们保存模型并将损失日志发送到 TensorBoard。当达到总的全局步数时,过程结束。

local_test 函数允许我们评估训练好的模型。它作为一个独立的过程运行,以测试代理使用学习到的策略的表现:

def local_test(index, opt, global_model):
    torch.manual_seed(123 + index)
    env, num_states, num_actions = create_train_env(opt.world, opt.stage, opt.action_type)
    local_model = ActorCritic(num_states, num_actions)
    local_model.eval()
    state = torch.from_numpy(env.reset())
    done = True
    curr_step = 0
    actions = deque(maxlen=opt.max_actions)
    while True:
        curr_step += 1
        if done:
            local_model.load_state_dict(global_model.state_dict())
        with torch.no_grad():
            if done:
                h_0 = torch.zeros((1, 512), dtype=torch.float)
                c_0 = torch.zeros((1, 512), dtype=torch.float)
            else:
                h_0 = h_0.detach()
                c_0 = c_0.detach()
        logits, value, h_0, c_0 = local_model(state, h_0, c_0)
        policy = F.softmax(logits, dim=1)
        action = torch.argmax(policy).item()
        state, reward, done, _ = env.step(action)
        env.render()
        actions.append(action)
        if curr_step > opt.num_global_steps or actions.count(actions[0]) == actions.maxlen:
            done = True
        if done:
            curr_step = 0
            actions.clear()
            state = env.reset()
        state = torch.from_numpy(state)

再次,我们进行设置和初始化,并在评估模式(实际上,在推理模式下,模型在此过程中不会更新)下加载本地的 ActorCritic 模型。此时,我们开始循环,从全局模型中加载最后一步的权重。对于某个状态,我们计算每个动作的概率,并选择概率最高的动作。注意,在训练过程中,我们进行了动作的采样;而在评估模式下,我们则采用贪婪策略选择动作。我们与环境交互,渲染游戏,进行动作跟踪,并检查代理是否陷入困境或无限重复相同的动作。如果代理超过最大步数或陷入困境,则剧集结束,我们重置状态。此函数评估训练代理的性能,渲染游戏玩法,以便用户可以观察代理学习玩超级马里奥兄弟的情况。它确保策略有效,并提供视觉反馈。

运行脚本,我们可以看到训练是并行进行的:

https://github.com/OpenDocCN/freelearn-dl-zh/raw/master/docs/bd-ai-agt-llm-rag-kg/img/B21257_08_29.jpg

图 8.29 – 脚本运行截图

您可以在此处查看视频:www.youtube.com/watch?v=YWx-hnvqjr8

总结来说,我们使用了几个脚本来实现动作-评论家算法(A3C 方法)的一个变体。这种方法涉及并行训练多个代理以探索环境、收集经验并异步更新共享的全局模型。换句话说,我们使用了一个允许我们加快训练速度并学习更鲁棒模型的变体,因为它从不同的代理那里检索不同的经验。为了更清晰的组织,我们将过程分解为几个脚本,然后将它们链接成一个单一的脚本(过程脚本)。我们定义了具有两个组件的公共提取器,以便我们可以节省一些计算。此外,我们使用 LSTM 来处理一个状态与另一个状态之间存在的时序依赖性。我们必须修改我们的优化器,因为我们需要共享内存来处理多个进程以更新全局模型。异步训练确实具有更高的复杂性,其中每个代理都需要访问和更新全局模型。之后,我们定义了如何通过收集一些经验来训练我们的模型。收集经验后,我们更新模型权重,计算损失并执行反向传播。定期地,我们同步全局和局部模型,对全局模型进行更新。之后,我们定义了如何使用全局模型的参数来评估我们的代理。代理使用学到的策略来玩游戏。

这些脚本由于采用了 A3C 方法,因此能够实现高效的并行训练。实际上,我们可以使用多个代理并行地探索环境、收集经验,然后导致全局模型更新。使用并行系统使得代理探索环境的不同部分,导致更多样化的经验,从而产生更通用的策略。一般来说,这是有利的,因为在视频游戏中可能需要不同的策略。同样地,我们添加了熵损失来鼓励探索并防止代理陷入次优策略。该脚本旨在高效利用资源,减少计算,并实现快速训练(我们没有添加经验回放缓冲区以节省内存,从而消耗更少的 RAM)。使用全局模型确保一个代理学到的知识立即对所有代理可用;这通常促进快速收敛。

选择在线策略学习方法如 A3C 可能会导致策略更新的高方差。这种方差随后可能被异步性放大,这可能会使得在运行之间获得一致的结果变得困难。实际上,异步方法引入了非确定性,意味着结果可能在运行之间有显著差异。这使得过程更不可预测,并复杂化了超参数的选择(这就是为什么我们提供了默认参数,尽管可以尝试调整它们)。虽然我们已经尝试优化此脚本的资源消耗,但整个过程仍然资源密集(就像 RL 一般)。

A3C 主要依赖于基于 CPU 的并行性;然而,采用对 GPU 友好的方法可以显著提高训练效率。例如 PPO 这样的算法可以利用 GPU 优化训练过程。有效使用 GPU 可以更高效地进行批量处理,允许积累经验并对模型进行批量更新。对于有兴趣探索基于 GPU 优化方法的读者,以下是一些潜在的想法:

  • 测试不同的超参数并改变它们的值,以更好地理解它们的影响。在脚本中,您可以轻松地设置和更改超参数。我们邀请您测试 lambda (λ) 以找到偏差和方差之间的更好平衡。

  • 尝试 PPO。PPO 是 A3C 的一个流行替代方案,它利用多个 epoch 的 mini-batch 更新。正如我们所见,它是一种促进稳定性且在许多情况下表现良好的算法。它也不需要很多超参数,默认参数通常效果良好。

  • 采用同步 A2C,因为它是一个更简单、同步的 A3C 版本。这种方法并行收集经验,并使用批量进行更新。它通常较慢但更容易调试。

本项目中展示的模型可以应用于其他几个视频游戏,展示了 RL 算法如何解决实际任务。

LLM 与 RL 模型之间的交互

RL 算法对于能够导航复杂环境、优化策略和做出决策的智能体至关重要,在机器人技术和视频游戏等领域取得了成功。另一方面,LLMs 对自然语言处理NLP)产生了重大影响,使机器能够理解人类语言和指令。尽管可以想象潜在的协同效应,但到目前为止,这两种技术是并行发展的。然而,近年来,随着对 LLMs 兴趣的增加,这两个领域越来越多地交叉。在本节中,我们将讨论 RL 和 LLMs 之间的交互。

我们可以有三种交互情况:

  • RL 增强 LLM:使用 RL 增强一个 LLM 在一个或多个 NLP 任务中的性能

  • LLMs 增强 RL:使用 LLMs 训练一个执行非必要是 NLP 任务的 RL 算法

  • RL 和 LLMs:结合 RL 模型和 LLMs 来规划技能集,而不使用任一系统来训练或对另一个系统进行微调

让我们详细讨论这些内容。

RL 增强的 LLMs

我们已经在第三章中讨论了对齐和提示工程。然后,RL 被用于微调、提示工程和 LLMs 的对齐。如第三章中提到的,LLMs 被训练来预测序列中的下一个单词,导致 LLMs 的训练目标与人类价值观之间存在不匹配。这可能导致 LLMs 生成带有偏见或其他不安全内容的文本,同样也可能在遵循指令方面表现不佳。对齐的作用是将模型重新对齐到人类价值观,或者使 LLM 在更安全的部署中更有效。最广泛使用的技术之一是从人类反馈中进行强化学习RLHF),其中奖励是从人类偏好中推断出来的,然后用于训练 LLM。这个过程遵循三个步骤:收集人类反馈数据,在此数据上训练奖励模型,以及使用 RL 对 LLM 进行微调。通常,最受欢迎的 RL 算法选择是 PPO 或其衍生方法。实际上,我们不希望我们的对齐模型与原始模型有显著差异,这是 PPO 保证的。

与 LLMs 的交互通过提示进行,提示应浓缩我们希望 LLM 完成的任务的全部指令。一些研究工作集中在使用 RL 来设计提示。提示优化可以表示为一个 RL 问题,其目标是将人类知识融入其中,从而生成可解释和可适应的提示。代理被用来构建查询依赖和优化的提示。一个人也可以训练一个策略网络来生成期望的提示,其优点是提示通常可以在 LLMs 之间迁移。这种方法的一个有趣方面是,其中一些优化提示在语法上是“胡言乱语”,这表明对于任务的优质提示不一定需要遵循人类语言模式。

LLM 增强的 RL

LLM 增强的 RL指的是使用预训练的 LLM 的多模态信息处理、生成、推理或其他高级认知能力来辅助 RL 代理的方法。换句话说,与传统 RL 的不同之处在于使用了 LLM,并以某种方式利用其知识和能力。以某种形式添加 LLM 具有双重优势:首先,LLM 拥有推理和规划技能,这有助于提高学习效率,其次,它具有更强的泛化能力。此外,LLM 在预训练阶段获得了广泛的知识,这些知识可以跨领域和任务进行迁移,从而更好地适应尚未见过的环境。通常,预训练的模型无法扩展其知识或获取新的能力(持续学习是深度学习的开放挑战),因此使用大量知识训练的模型可以帮助这一方面(LLMs 是通才,在记忆中拥有不同领域的海量信息)。

LLM 可以插入到经典 RL 系统(代理与环境交互并接收反馈)的多个点。LLM 可以集成以提取信息、重新处理状态、重新设计奖励、做出决策、选择动作、解释策略、分析世界相似性等。

https://arxiv.org/pdf/2404.00282(img/B21257_08_30.jpg)

图 8.30 – 经典代理-环境交互中 LLM 增强的 RL 框架(arxiv.org/pdf/2404.00282)

因此,LLM 可以作为信息处理器、奖励设计师、决策者和生成器在系统中使用。

信息处理器

当一个任务需要文本信息或视觉特征时,代理同时理解信息和优化策略可能会很复杂。正如我们之前看到的,卷积神经网络可以用来处理图像,使模型能够与视频游戏或棋盘游戏交互。在聊天机器人的情况下,我们可以使用一个理解语言的模式。或者,我们可以在不直接使用语言模型的情况下,使用 LLM 提取特征,使代理能够更快地学习。LLMs 可以作为好的特征提取器,从而降低信息的维度和复杂性。或者 LLMs 可以将自然语言翻译成代理能够理解的特定形式化语言。例如,在机器人案例中,不同用户的自然语言可能不同且不统一,这使得代理难以学习。LLM 可以将指令转换成标准、形式化的语言,使代理更容易学习。

一个广泛的预训练模型学习数据的表示,然后可以用于后续的应用。因此,一个模型可以用来提取我们可以用来训练智能体的数据表示。一个 LLM 可以被冻结使用(即,不需要进一步训练)来提取环境的压缩历史表示。一些研究使用 LLM 来总结提供给智能体的过去视觉观察,这样我们就可以为智能体提供一个记忆。使用冻结的模型显然是最简单的替代方案,但当智能体在现实世界中部署时,由于现实世界的变化与训练环境之间的差异,性能可能会迅速下降。因此,我们可以对智能体和 LLM 进行微调。使用特征提取器(一个 LLM 或其他大型模型)使智能体更容易学习,因为这些特征对环境变化(亮度、颜色等变化)的鲁棒性更强,但另一方面,它们有额外的计算成本。

LLM 的能力可以用来使任务更清晰。例如,自然语言中的指令可以通过 LLM 调整为对智能体更清晰的指令集(例如,在玩视频游戏时,任务的文本描述可以转换为一套关于如何移动角色的指令)。LLM 还可以用来将智能体的环境翻译成可用的信息。这些方法特别有前景,但目前范围有限。

https://arxiv.org/pdf/2404.00282(img/B21257_08_31.jpg)

图 8.31 – LLM 作为信息处理器(arxiv.org/pdf/2404.00282)

奖励设计师

当问题知识可用,或者当奖励可以通过一个清晰且确定性的函数定义(例如游戏得分或胜负条件)时,设计奖励函数是直接的。例如,在 Atari 游戏(或其他游戏)中,很容易绘制出奖励函数(例如,胜利代表一个积极信号,失败代表一个消极信号)。在许多应用中,这是不可能的,因为任务既长又复杂,奖励分散,等等。在这种情况下,LLM 固有的知识(在预训练期间获得的知识、编码能力和推理技能)可以用来生成奖励。它可以间接使用(隐式奖励模型)或直接使用(显式奖励模型)。例如,用户可以在提示中定义期望的行为,LLM 可以在训练期间评估智能体的行为,提供奖励和惩罚。因此,你可以使用 LLM 的直接反馈,或者 LLM 可以生成奖励函数的代码。在第二种方法中,函数可以在训练期间由 LLM 修改(例如,在智能体获得一些技能后,使其更难获得奖励)。

一个 LLM 可以是一个隐式奖励模型,它根据任务描述提供奖励(或辅助奖励)。实现这一点的技术之一是直接提示,其中向 LLM 提供指令以评估代理的行为或决定奖励。这些方法可以模拟人类反馈以实时评估代理的行为。或者,可以使用对齐分数,例如,在动作结果和目标之间(换句话说,评估预期结果与现实之间的相似性)。在某些方法中,使用语言指令和代理的图像观察之间的对比对齐,从而利用多模态模型。显然,对齐人类意图和 LLM 奖励生成的过程并不容易。可能会有歧义,系统并不总是能够处理低质量的指令,但这似乎是一条有希望的途径。

显式奖励模型利用了 LLM 生成代码的能力,从而生成一个函数(使 LLM 的决策和奖励生成过程更加透明)。这允许自动生成子目标的函数(例如,让机器人通过使用 LLM 翻译成奖励函数的高级指令来学习低级任务)。这种方法的主要局限性是 LLMs 的常识推理限制。LLMs 无法进行真正的推理或真正的泛化,因此它们受限于预训练期间所见的内容。在预训练期间,LLMs 没有看到高度专业化的任务,因此限制了这些方法在选定任务集上的适用性。添加上下文和附加信息可以缓解这个问题。

https://github.com/OpenDocCN/freelearn-dl-zh/raw/master/docs/bd-ai-agt-llm-rag-kg/img/B21257_08_32.jpg

图 8.32 – LLM 作为奖励设计师 (arxiv.org/pdf/2404.00282)

决策者

由于在许多情况下 RL 存在样本和探索效率低下的问题,LLMs 可以在决策中使用,从而帮助选择行动。LLMs 可以用来减少某个状态下的动作集合(例如,当存在许多可能的动作时)。减少动作集合减少了探索空间,从而提高了探索效率。例如,LLM 可以用来训练机器人在世界中的行动,减少探索时间。

变换器(或其衍生模型)在强化学习(RL)中显示出巨大的潜力。其背后的想法是将这些问题视为序列建模问题(而不是试错)。因此,LLM 可以被看作是一个决策模型,它必须决定一系列问题(正如我们在第二章中提到的,变换器是在一系列问题上进行训练的,因此对一系列状态做出决策符合其训练)。一个 LLM 可以被微调以利用模型的内部表示。实际上,通过这种方式,我们利用从 LLM(使用大量文本进行训练,LLM 已经积累了大量可以应用于任务的已知知识)学习到的表示来决定一个动作。使用先验知识减少了数据收集和探索的需求(因此,提高了样本效率),并使系统在长期奖励或稀疏奖励环境中更加高效。一些研究表明,不仅从 LLM 学习到的知识可以转移到其他模型,而且整个系统在不同基准上的性能也得到了提高。此外,视觉-语言模型可以用来使系统能够适应多模态环境。将 LLM 用作决策者仍然计算成本高昂(即使仅在推理中使用且不需要微调)。因此,当前的研究正致力于尝试降低这些方法的计算成本。

或者,LLM 可以通过生成合理的动作候选或专家动作来引导智能体选择动作。例如,在基于文本的游戏等环境中,动作空间非常大,而目前只有一小部分动作可用,因此智能体可以通过广泛的试错来学习;然而,这种探索效率非常低。LLM 可以通过理解任务来生成动作集,从而减少动作空间。这使得减少探索并使其更有效率、收集更多奖励和加快训练成为可能。通常,在这些方法中,我们有一个生成动作集的 LLM 和另一个生成候选动作 Q 值的神经网络。相同的方法已经扩展到需要遵循人类指令的机器人,其中 LLM 生成可能的动作。这种方法由于 LLM 的偏差和局限性的继承而受到限制(因为 LLM 决定动作空间并根据其知识和偏差生成它)。

https://github.com/OpenDocCN/freelearn-dl-zh/raw/master/docs/bd-ai-agt-llm-rag-kg/img/B21257_08_33.jpg

图 8.33 – 作为决策者的 LLM (arxiv.org/pdf/2404.00282)

生成器

基于模型的 RL 依赖于世界模型来学习环境的动态并模拟轨迹。LLM 的能力可以是生成准确的轨迹或解释策略选择。

LLM 具有固有的生成能力,使其可以用作生成器。然后,LLM 可以用作世界模型模拟器,系统生成智能体用于学习和规划的准确轨迹。这已经在视频游戏中得到应用,其中 LLM 可以生成轨迹,从而减少智能体学习游戏所需的时间(提高样本效率)。LLM 的生成能力还可以用于预测未来。尽管前景看好,但仍然难以将 LLM 的抽象知识与环境现实对齐,限制了其生成能力的影响。

另一种有趣的方法是使用一个大型语言模型(LLM)来解释强化学习(RL)系统的策略。可解释的强化学习XRL)是可解释机器学习和强化学习交叉领域的一个新兴子领域。XRL 旨在向人类清晰地解释智能体的行为。然后,LLM 可以用来用自然语言解释智能体为何做出某种决策或对环境变化做出某种反应。作为一个策略解释器,给定一个状态和一个动作,LLM 应该解释智能体的行为。这些解释应该对人类来说是可理解的,从而允许检查智能体的安全性。当然,解释的质量取决于 LLM 理解环境特征表示和政策隐含逻辑的能力。使用领域知识或例子来提高对复杂策略(尤其是对复杂环境)的理解是困难的。

https://github.com/OpenDocCN/freelearn-dl-zh/raw/master/docs/bd-ai-agt-llm-rag-kg/img/B21257_08_34.jpg

图 8.34 – LLM 作为生成器 (arxiv.org/pdf/2404.00282)

LLM 增强的 RL 可以在各种应用中发挥作用:

  • 机器人技术:使用 LLM 可以提高人类与机器人之间的交互,帮助机器人更好地理解人类需求或人类逻辑,并提高它们的决策和规划能力。

  • 自动驾驶:在自动驾驶中,强化学习用于在复杂环境中做出决策,这些环境复杂,需要分析来自不同传感器(视觉、激光雷达、雷达)的输入,并考虑上下文信息(交通法规、人类行为、意外问题)。LLM 可以提高处理和整合这种多模态信息的能力,更好地理解指令,并改善目标和奖励(例如,设计考虑安全、乘客舒适度和发动机效率的奖励函数)。

  • 医疗建议:在医疗保健中,强化学习用于学习建议和推荐。LLM 可以用于其广泛的知识和能够分析大量患者数据和医疗数据的能力,加速智能体的学习过程,或为更好的学习提供信息。

  • 能源管理:强化学习用于提高能源的使用、运输、转换和储存。此外,它预计将在未来的技术(如核聚变)中发挥重要作用。LLM 可用于提高样本效率、多任务优化等。

尽管有这些机会,但在强化学习中使用 LLM 也存在一些局限性。第一个挑战是 LLM 增强的 RL 范式高度依赖于 LLM 的能力。LLM 存在偏差并且可能产生幻觉;智能体随后从 LLM 继承了这些问题。此外,LLM 还可能误解任务和数据,尤其是在它们复杂或嘈杂时。此外,如果任务或环境没有在它们的预训练中表示,LLM 在适应新环境和任务时会有问题。为了限制这些影响,已经提出了使用合成数据、微调模型或使用持续学习的方法。持续学习可能允许模型适应新任务和新环境,而不会忘记模型之前学到的内容。然而,到目前为止,持续学习和灾难性遗忘仍然是深度学习中的开放性问题。

此外,添加一个大型语言模型(LLM)会带来更高的计算成本(在训练和推理过程中),以及延迟时间的增加。可以使用几种技术来降低这种计算成本,例如量化、剪枝或使用小型模型。一些方法使用专家混合,允许条件计算、变换器变体(状态空间模型)、缓存策略等。

最后,不应忘记使用 LLM 也引发了道德、法律和安全问题。我们在第三章中看到的问题也适用于这些系统。例如,数据隐私和知识产权仍然是敏感领域(如医疗保健或金融)应用中的开放性问题。

关键要点

由于本章在理论方面内容密集,我们决定添加一个小结部分。本章介绍了强化学习(RL)作为使智能体通过与环境交互并通过试错来学习的一种核心方法,类似于人类通过行动、观察结果和调整行为来学习的方式。RL 与监督学习不同,它侧重于从奖励而非标记数据中学习,并且特别适合具有延迟反馈和演变决策序列的任务。

强化学习是一种机器学习范式,其中智能体通过与环境的交互来学习做出决策,以最大化累积奖励。它通过试错来学习,平衡探索(尝试新动作)和利用(使用已知策略)。

总结来说,我们有以下几类方法:

  • 无模型与 基于模型 的强化学习

    • 无模型方法(例如,DQN、REINFORCE)直接从交互中学习,而不对环境进行建模。它们更简单且更具可扩展性。

    • 基于模型的方法使用内部模型来模拟结果并提前规划。它们更有效率,适用于规划至关重要的环境,但设计和计算更困难。

  • 策略方法与离策略方法的比较

    • 离策略方法从当前策略生成的数据中学习(例如 REINFORCE、PPO),这使得它们更稳定但样本效率较低。

    • 离策略方法(例如 DQN)可以从过去或替代策略中学习,从而提高样本效率和探索灵活性。

  • 讨论的主要算法

    • Q-Learning 和 DQN:使用查找表或神经网络学习价值函数。

    • REINFORCE:使用随机策略的基本策略梯度方法。

    • PPO:通过剪辑策略更新来平衡稳定性和性能。

    • Actor-Critic:结合价值估计和政策学习,以实现更稳健的更新。

    • AlphaZero:将深度学习与蒙特卡洛树搜索相结合,用于复杂游戏中的基于自我对弈的策略优化。

  • 实际应用案例

    • 游戏:例如 AlphaZero 和 DQN 这样的强化学习代理已经掌握了围棋、象棋和 Atari 游戏等。

    • 机器人技术:强化学习允许机器人通过模拟和现实世界的反馈学习复杂的运动和交互策略。

    • 自动驾驶汽车:强化学习使代理能够在动态和不确定的环境中学习驾驶策略。

    • 优化和控制:应用于金融、医疗保健、物流和工业自动化中的顺序决策。

摘要

在前几章中,主要问题是如何找到信息以及如何有效地将信息传递给一个语言模型(LLM)。在这种情况下,模型是一个被动代理,接收信息并做出响应。在本章中,我们试图摆脱这种范式,转向一个理念,即代理探索环境,通过这种探索学习,执行动作,并从环境提供的反馈中学习。在这种观点下,模型是一个主动组件,与环境互动并可以对其进行修改。这种观点也与人类学习的方式更为接近。在我们对外部世界的探索中,我们接收到的反馈引导我们的学习。尽管世界的大部分内容已经在文本中被记录下来,但现实世界不能简化为文本描述。因此,代理在与世界互动之前无法学习某些知识和技能。强化学习是人工智能的一个领域,它关注代理与环境之间的互动以及它如何从中学习。

因此,在本章中,我们介绍了强化学习(RL)的基础知识。在第一部分,我们讨论了 RL 系统的基本组成部分(代理、环境、奖励和动作)。然后,我们讨论了 RL 的主要问题,即如何平衡探索和利用。实际上,代理有一个目标(完成任务),但通过探索来学习如何完成这个任务。例如,我们在多臂老虎机示例中看到,贪婪模型的表现不如探索所有可能性的模型。当我们定义代理来解决复杂问题,如解决视频游戏时,这一原则仍然是基本的。为了解决复杂任务,我们引入了神经网络(深度强化学习)的使用。我们看到了不同类型的算法具有不同的优缺点,并看到了我们如何将其中之一设置为在经典视频游戏中获胜。一旦我们训练了我们的模型,我们就讨论了 LLM 和 RL 领域如何日益交叉。这样,我们就看到了两个领域的优势如何协同作用。

从本章开始,重点将更加应用性。我们将看到代理通常如何完成任务。在接下来的章节中,代理将主要是一个 LLM,它将使用工具执行动作和完成任务。因此,代理的选择将不是采取哪个动作,而是选择哪个工具来完成一个任务。尽管 LLM 代理与环境交互,但一个主要区别是不会有训练。训练 LLM 是一个复杂任务,因此在这些系统中,我们尽量减少训练。如果在之前的章节(5–7)中,我们试图利用 LLM 的理解能力,那么在接下来的章节中,我们将尝试利用 LLM 与环境或与其他代理交互的技能——这些技能无论如何都是可能的,因为 LLM 可以理解任务和指令。

进一步阅读

第三部分:创建解决复杂场景的复杂 AI

本最后一部分专注于将前几章中介绍的部分组件组装起来,以构建完全成熟、适用于生产的 AI 系统。它从单代理和多代理系统的设计和编排开始,其中 LLMs 与工具、API 和其他模型协作,以解决复杂的多步骤任务。该部分随后引导您了解使用现代工具(如 Streamlit、异步编程和 Docker 等容器化技术)构建和部署 AI 代理应用程序的实际方面。最后,本书以对 AI 代理未来的展望结束,讨论其在医疗保健和法律等行业的跨行业影响,以及未来面临的伦理和技术挑战。本部分使您能够从实验过渡到现实世界的部署,为参与下一波智能系统做好准备。

本部分包含以下章节:

  • 第九章*,创建单代理和多代理系统*

  • 第十章*,构建人工智能代理应用*

  • 第十一章*,未来的展望*

第九章:创建单代理和多代理系统

在前面的章节中,我们讨论了许多可以与 LLMs 相关联的组件或工具,以扩展其功能。在第五章第六章中,我们详细讨论了如何使用外部记忆来丰富上下文。这允许模型获得额外的信息,以便在它不知道答案时(当它在预训练期间没有看到文档或它涉及训练日期之后的信息)回答用户的问题。同样,在第七章中,我们看到了知识图谱可以用来扩展模型的知识。这些组件试图解决 LLMs 最令人头疼的限制之一,即幻觉(模型产生的非事实正确的输出)。此外,我们还看到,使用图可以使模型进行图推理,从而增加新的能力。

第八章中,我们看到了强化学习(RL)和 LLMs 的交集。与 LLMs 相关的一个问题是它们可能会产生有害内容(如偏见或有害内容或错误信息)。RL 算法使我们能够使模型的行为与人类偏好保持一致,从而降低有害内容的风险。

我们可以使用类似的方法使模型更擅长执行任务或遵循指令。在未来,这些强化学习算法可能有助于克服 LLMs 的一个重要限制:缺乏持续学习。

正如我们将看到的,工具的定义相当广泛。实际上,任何软件或算法都可以是工具。正如我们在前面的章节中已经看到的,LLMs 可以执行代码或连接到应用程序编程接口APIs)。但这意味着它们也可以调用其他模型来执行它们自己无法完成的任务。

在任何情况下,所有这些元素都为所谓的代理革命奠定了基础,在这个革命中,LLM 可以与环境交互并在现实世界中执行任务(无论是互联网还是未来,超越计算机的限制)。

在本章中,我们专注于大型语言模型(LLMs),其各种工具,以及如何将这些工具结合起来与环境交互。我们将从自主代理的定义开始,然后继续讨论工具(APIs、模型等)是什么以及它们如何被组织。我们将看到使用提示工程技巧(我们在第三章中讨论过)如何使我们能够创建不同类型的代理。之后,我们将讨论文献中先前使用的一些策略,这些策略用于将 LLM 连接到其工具。

这将使我们能够详细了解一些技术限制和挑战是如何被解决的。然后,我们将详细讨论 HuggingGPT(一个连接到数百个模型的 LLM),这是代理创建的一个转折点。我们将看到 HuggingGPT 如何允许 LLM 使用其他专家模型解决复杂任务。然后,我们将看到如何创建多代理平台,而不是单个代理。不同代理的交互将使我们能够解决越来越复杂的问题。此外,我们将看到这些方法如何应用于复杂领域,如医疗保健、化学和法律。然后,我们将使用 HuggingGPT 将我们所学的内容付诸实践。接下来,我们将通过一个多代理平台扩展这一概念,这将使我们能够理解现代系统是如何工作的。

一旦我们了解了代理或多代理的工作方式,我们将详细讨论新兴的新商业模式,例如软件即服务SaaS)、模型即服务MaaS)、数据即服务DaaS)和结果即服务RaaS)或成果即服务OaaS)。正如我们将在本章中看到的,这些商业模式各有优缺点。

在本章中,我们将涵盖以下主题:

  • 自主代理简介

  • HuggingGPT 和其他方法

  • 与 HuggingGPT 一起工作

  • 多代理系统

  • SaaS、MaaS、DaaS 和 RaaS

技术要求

本章中的代码需要使用 GPU。特别是对于使用 HuggingGPT 的部分,需要 GPU 和硬盘驱动器上的大量空间(将下载多个模型,包括扩散模型。为此,将需要使用 Git 大型文件存储LFS),它允许通过 Git 下载大文件)。应安装 Anaconda 以获取各种库(必要的库将在安装过程中直接设置)。对于没有这些资源的读者,使用 HuggingGPT 在网络上部分展示了如何在网上使用 HuggingGPT。对于本地使用 HuggingGPT,需要 OpenAI 令牌,而对于网络使用,也需要 Hugging Face 令牌。多代理系统基于 Python 库(NumPy、scikit-learn、SentenceTransformers 和 Transformers)。

HuggingGPT 应在 GPU 上运行。多代理系统应在 GPU 上运行,但也可以在 CPU 上运行;然而,这被高度不建议。代码可以在 GitHub 上找到:github.com/PacktPublishing/Modern-AI-Agents/tree/main/chr9

自主代理简介

在人工智能的背景下,自主代理指的是可以独立执行任务或做出决策的系统或实体,而无需人类干预。这些代理被设计为感知其环境,对其进行分析,根据其目标做出决策,并据此采取行动以实现这些目标。自主代理被认为是通往通用人工智能(AGI)的重要一步,AGI 预计将进行自主规划和行动。

使用 LLMs 作为代理的主要原因在于 LLMs 已经显示出一些推理和规划能力。LLMs 使用推理来解释输入,进行推理,并做出决策(显示出一定程度的演绎、归纳和类比推理)。这使得 LLMs 能够将一般规则应用于特定情况(演绎推理),从例子中学习模式(归纳推理),并从不完全数据中推断解释(类比推理)。此外,LLMs 能够通过串联想法进行逐步推理,从而使其能够解决方程或调试代码。解决某些问题(如数学问题)需要遵循一系列步骤。本质上,一个 LLM 必须经常将任务分解成一系列动作,预测这些动作的结果,并根据结果调整其行为。然而,这些能力仅限于用户提供的上下文或预训练期间获得的知识,对于医学或金融等领域,这不足以解决大多数问题。因此,对此限制的自然反应是扩展 LLM 的能力,使用外部工具,或者将 LLM 连接到外部环境。

因此,一些研究和研究的目的是通过一系列工具来扩展大型语言模型(LLMs)的能力。这些工作和由此产生的库试图为 LLMs 配备人类能力,例如记忆和规划,使它们的行为像人类一样,并有效地完成各种任务。

随着 LLMs 能力的不断发展,对这些代理的兴趣也在增长,已经发表了众多文章和框架。

https://github.com/OpenDocCN/freelearn-dl-zh/raw/master/docs/bd-ai-agt-llm-rag-kg/img/B21257_09_01.jpg

图 9.1 – 对 LLM 自主代理的兴趣日益增长 (arxiv.org/pdf/2308.11432)

在构建这些类型的系统时,首先要考虑的是架构的设计以及如何使用它来执行任务。自主代理必须执行不同的角色,感知环境,并从中学习。架构的目的是帮助 LLM 最大化其能力,以便用作代理。为此,已经开发出几个模块,可以分为四个主要组:配置文件、记忆、规划和行动。

https://github.com/OpenDocCN/freelearn-dl-zh/raw/master/docs/bd-ai-agt-llm-rag-kg/img/B21257_09_02.jpg

图 9.2 – 构建基于 LLM 的自主代理的可能模块 (arxiv.org/pdf/2308.11432)

让我们更详细地逐一介绍这些内容:

  • 配置文件模块:通常,代理在特定的角色(也称为角色扮演)中执行任务,例如编码者、领域专家、教师或助手。配置文件模块负责在给定的 LLM 特定提示中定义这些角色(特征、角色、心理和社会信息,以及与其他代理的关系)。然后,这些配置文件可以是手写的(手写配置文件是由开发者或领域专家手动制作的个人或角色定义);例如,对于软件开发系统,我们可以创建不同的工作角色(“你是一名负责代码审查的软件工程师”)。手写配置文件允许高度的控制、丰富上下文,并且可以高度特定于领域(解决细微差别、软技能、复杂知识)。尽管手写方法非常灵活,但它耗时且可扩展性有限。因此,一些研究探索了 LLM 自动生成配置文件的系统(使用少量示例、规则和模板,或特定外部数据集作为工作描述)。这种方法具有更高的可扩展性和对不同情况的适应性(特别是如果系统需要动态或如果收到用户反馈)。然而,另一方面,控制程度较低(系统失去了细微差别和深度,存在变得通用的风险),质量参差不齐(取决于提示工程技术,一些示例可能质量较差),并且仍然需要人工验证。

  • 记忆模块:记忆模块存储系统从环境或其他来源感知到的信息;这些记忆随后有助于未来的行动。专门的记忆组件也可以是复杂的,并受到人类认知的启发,具有针对感知、短期或长期信息的组件。常见的记忆随后被输入到系统提示中(因此 LLM 的上下文长度是可用于代理的记忆的限制)。例如,完成任务所需的与用户的聊天历史。作为另一个例子,协助游戏开发的代理将具有刚刚发生的事件和其他描述作为短期记忆。混合记忆是一种扩展记忆的方法,其中过去的事件和思想被保存并再次找到,以促进代理的行为。混合记忆结合了短期(在 LLM 上下文中)和长期(外部)记忆,以扩展代理的能力,使其超越 LLM 的上下文窗口。这些思想、对话或其他信息可以通过 RAG 或其他系统(数据库、知识图谱等)保存。当需要时,相关信息被检索并注入到 LLM 提示中,使代理能够根据先前知识行事,而不超过上下文限制。例如,在 RAG 中,搜索机制根据当前查询检索相关的文档或记忆片段,使响应在时间上更加知情和一致。此外,此模块应涵盖三个操作:记忆读取(提取对代理行动有用的信息)、记忆写入(存储可能对未来有用的环境信息,同时避免重复和内存溢出),以及记忆反思(评估和推断更抽象、复杂和高级的信息)。具体来说,记忆读取检索信息以支持代理的决策(增加上下文的连续性和一致性),记忆写入允许保存对代理与环境交互有用的信息(从而减少冗余并允许克服不可编辑记忆的限制),而记忆反思允许从存储信息的分析中得出见解,从而允许调整行为以实现目标。

  • 规划模块:规划模块通常用于将复杂任务分解成更易于管理的任务,以使大型语言模型(LLM)的行为更加合理、强大和可靠。规划模块可以包含或不包含反馈。

    在没有反馈的规划中,代理在执行行动后不会收到影响其未来行为的反馈。在单路径推理中,任务被分为几个中间步骤,这些步骤以级联序列连接。思维链CoT)推理通常用于为这种策略制定逐步计划。相比之下,多路径推理涉及一个树状结构,其中每个中间步骤都可以分支成多个后续步骤。这些方法通常利用自洽思维链CoT-SC)或思维树ToT)框架,以评估所有中间步骤以确定最佳策略。该树甚至可以与复杂的策略相结合,如蒙特卡洛树搜索MCTS)或外部规划器。

    带反馈的规划主要用于长期任务,在这种情况下,从开始就难以生成有效的计划,或者动态可能发生变化。因此,你可以结合来自环境和观察的反馈。例如,ReAct 框架使用思考-行动-观察三元组。另一种选择是使用人类反馈或另一个模型来提高代理的规划能力。

https://arxiv.org/pdf/2308.11432(img/B21257_09_03.jpg)

图 9.3 – 单路径和多路径推理策略的比较(arxiv.org/pdf/2308.11432)

  • 动作模块:动作模块负责将规划转化为特定的结果;然后该模块负责交互。一般来说,该模块专注于任务的执行以及具有特定目标的行为。该模块还负责与其他代理(如果存在)通信、探索环境、寻找必要的记忆以及执行计划。为了实现这些目标,LLM 可以使用在预训练阶段从 LLM 获得的知识或外部工具(外部模型、API、数据库或其他工具)。预训练知识允许 LLM 使用学习到的信息执行许多任务,例如生成文本、回答问题或根据先前数据做出决策。然而,对于更动态、实时或专业的任务,动作模块使用外部工具,如 API、数据库、软件应用程序或其他模型。这些工具使代理能够访问最新信息、操作数据、执行计算或在外部系统中触发操作。预训练知识和外部工具共同使代理能够与环境进行有意义的交互,实现目标,并根据其行动的结果进行适应。模型的行为对环境或模型内部状态有影响,并由该模块评估和考虑。

除了系统架构之外,我们还应该考虑开发更好代理的策略。通常,最常用的策略之一是对模型进行微调。微调通过将通用 LLM 适应特定任务、领域或行为目标,在提高代理性能方面发挥着关键作用。它有助于使模型与人类价值观(安全性)保持一致,提高指令遵循性,或在教育或电子商务等领域进行专业化。在大多数情况下,用于特定任务的数据集都是人工标注的。正如我们在第三章中讨论的那样,这可能是出于安全原因(与人类价值观保持一致)、使其更易于遵循指令(指令调整)或训练特定领域或任务。以 WebShop 为例(arxiv.org/pdf/2308.11432),论文的作者们收集了来自 amazon.com 的 120 万个世界产品,并创建了一个模拟的电子商务网站。之后,他们收集了网站上的用户行为(当用户在网站上浏览和执行操作时,他们的行为会被记录),从而创建了一个专门用于帮助产品选择的代理的微调数据集。或者,在 EduChat 的例子(arxiv.org/pdf/2308.11432)中,为了创建一个用于教育场景的代理,作者们收集了一个覆盖各种教育场景的标注数据集(数据集由心理学家等专业人士评估和编辑)。

收集这些数据集成本高昂,并且在某些情况下需要专业的技术人员。因此,一个替代方案是使用大型语言模型(LLM)来标注数据集。当采用这种方法时,质量和成本之间存在权衡:数据集不如人工标注的好,但成本大幅降低。例如,在 ToolBench(一个将 LLM 连接到 API 的代理系统)中,该工作的作者们(arxiv.org/pdf/2308.11432)收集了超过 16,000 个真实世界的 API,然后使用 ChatGPT 对此数据集进行标注。然后,他们在该数据集上微调了 LLaMA。微调后的模型在使用这些 API 时表现更出色。

https://arxiv.org/pdf/2307.16789(img/B21257_09_04.jpg)

图 9.4 – ToolBench 的构建(arxiv.org/pdf/2307.16789)

或者,你可以收集大量未标注的数据,这样模型就可以在微调过程中自行找出。例如,Mind2Web 收集了大量用于网络浏览的数据(arxiv.org/abs/2306.06070)。

标注数据集和自标注数据集之间的权衡在于,LLM 标注的数据可能缺乏人类标注的准确性、细微差别或可靠性,这可能会影响性能。然而,它允许更广泛的覆盖范围和更快的迭代。在实践中,结合这两种方法——使用 LLM 进行大量标注,而人类进行验证或高风险任务——在质量和成本之间提供了平衡,使微调更加容易,同时仍然增强了代理的能力。

由于与模型的交互通常是通过提示进行的,因此许多开发者简单地使用提示工程,而不需要微调。其理由是必要的知识已经存在于 LLM 的参数中,我们希望使用一个提示,使模型能够最大限度地利用它。其他方法添加了充当评论家的代理、进行辩论的其他代理或其他变体。

我们迄今为止所看到的内容使我们能够理解什么是自主代理以及它是如何构成的。正如我们所见,一个代理的核心是一个 LLM,以及围绕它的复杂生态系统,这些元素可以根据研究者的选择进行组合。在接下来的章节中,我们将详细探讨不同的自主代理方法,这些方法使我们能够理解文献中实施的一些解决方案。

Toolformer

Toolformer(Schick, 2023)是一项开创性的工作,它采用了这样的想法:一个大型语言模型(LLM)可以访问外部工具来解决任务(如搜索引擎、计算器和日历)而不牺牲其通用性或需要大规模的人类标注。Toolformer 的关键创新在于将工具使用视为一种可推广的技能,而不是局限于特定任务。Toolformer 不是为每个工具或任务设计独立的系统,而是教会模型在统一的语言建模框架内做出关于使用哪个工具、何时使用以及如何使用工具的智能决策。

根据作者的观点,一个大型语言模型(LLM)应该根据两个原则来学习工具的使用:以自监督的方式学习,并保持模型的一般性。Toolformer 旨在以大量自监督的方式进行学习,解决了人工智能发展中的一大瓶颈:人工标注数据的成本和努力。模型不是通过手动标注工具使用的数据,而是被展示了一些工具(API 调用)的工作示例。然后,它在语言建模过程中自动标注了一个大型、未标记的数据集,其中包含工具使用的机遇。这些标注序列被用来微调模型,使其能够自然地学习工具交互。这一点很重要,因为标注数据集是有成本的,但它也教会了 LLM 如何使用工具。一个核心目标是确保 LLM 在执行不同任务时保持其广泛的技能,同时获得使用工具的能力。工具的使用不是为特定提示硬编码的——它成为模型一般技能集的一部分。LLM 学习何时工具可以提高性能,并只在必要时调用它,以保持灵活性并避免过度依赖。简而言之,工具的使用与特定任务无关,而成为一个一般概念。Toolformer 背后的理念是将工具视为对 API 的调用。这种抽象简化了集成,并且可以轻松扩展到不同的工具。例如,当面对数学问题时,模型可能会决定调用计算器 API,或者当需要外部知识时,调用搜索引擎。在给出一系列人类编写的 API 使用示例后,作者使用 LLM 对大量语言建模数据集进行了标注,其中包含潜在的 API 调用。之后,作者对模型进行了微调,以提升模型的能力。采用这种方法,LLM 学习如何控制各种工具,以及何时应该使用它们。

https://arxiv.org/pdf/2302.04761](https://github.com/OpenDocCN/freelearn-dl-zh/raw/master/docs/bd-ai-agt-llm-rag-kg/img/B21257_09_05.jpg)

图 9.5 – Toolformer 方法 (arxiv.org/pdf/2302.04761)

HuggingGPT

HuggingGPT (Shen, 2023) 提出了一个强大的概念:将语言作为一种通用接口,使大型语言模型(LLM)能够与各种模态的外部 AI 模型协作,例如视觉、语音和结构化数据。LLM 不再局限于文本任务,而是获得了管理和协调其他模型以解决复杂、现实世界问题的能力。HuggingGPT 基于两个想法:如果 LLM 无法访问文本之外的信息(如视觉和语音),则其能力有限;在现实世界中,复杂任务可以分解为更易于管理的较小任务。对于特定任务,LLM 在零样本或小样本学习方面表现出色,但通用模型的能力不如特定训练模型。因此,对于作者来说,解决方案是 LLM 必须能够与外部模型协调以利用它们的强大功能。在文章中,他们专注于寻找合适的中间件来连接 LLM 和 AI 模型之间的联系。换句话说,这个想法是 LLM 可以与其他模型进行对话,从而利用它们的特性。其背后的直觉是每个 AI 模型都可以通过总结其功能来用语言描述。换句话说,每个模型都可以从功能上和文本上进行描述。这种描述可以被 LLM 使用。对于作者来说,这代表了新概念的引入:语言作为 LLM 与 AI 模型协作的通用接口。在这个系统中,LLM 充当“大脑”,负责解释用户请求,将其分解为子任务,根据它们的文本描述选择适当的模型,调度和协调模型执行,整合结果,并生成最终响应。

由于与 LLM 的交互是通过提示进行的,因此可以将模型的功能描述输入到 LLM 提示中。然后,LLM 可以被视为管理 AI 模型以进行规划、调度和协作的“大脑”。因此,LLM 不是直接完成任务,而是调用特定模型来解决任务。例如,如果用户问,“图片中有什么动物?”,LLM 会处理这个问题,并推理出它应该使用哪种类型的模型(即图像分类器);模型被调用,返回输出(出现的动物),然后 LLM 生成文本输出以回答“这只动物是 一只鸡。”

到目前为止,主要问题在于收集这些模型功能的文本描述。幸运的是,机器学习ML)社区为特定任务及其使用的模型(语言、视觉、语音等)提供了高质量的描述。因此,我们需要将 LLM 与社区(GitHub、Hugging Face 等)联系起来。

简而言之,HuggingGPT 是一个由 LLM 驱动的代理,旨在自主解决各种复杂任务。HuggingGPT 将 LLM(原文中是 ChatGPT)与 ML 社区(Hugging Face,但原理可以推广)连接起来;LLM 可以接受不同模态作为输入并完成不同的任务。LLM 充当大脑,将用户的请求分解为子任务,然后将它们分配给专业模型(根据模型描述);然后执行这些模型并整合结果。以下图示强调了这些原则:

https://github.com/OpenDocCN/freelearn-dl-zh/raw/master/docs/bd-ai-agt-llm-rag-kg/img/B21257_09_06.jpg

图 9.6 – HuggingGPT 总体方案(arxiv.org/pdf/2303.17580)

整个 HuggingGPT 过程可以分成四个步骤:

  1. 任务规划:ChatGPT 分析用户请求(理解意图)并将问题转化为可能可解的任务。

  2. 模型选择:ChatGPT 选择 Hugging Face 中存在的适当模型(专家模型)(模型的选择基于提供的描述)。

  3. 任务执行:模型被调用并执行,然后将结果返回给 ChatGPT。

  4. 响应生成:ChatGPT 整合模型的结果并生成答案。

在 Toolformer 中,我们有一个 LLM,模型通过 API 调用调用工具。HuggingGPT 采用类似的方法,但无需微调。在 HuggingGPT 中,一个 LLM 可以被视为一个控制器,将用户请求路由到专家模型。换句话说,LLM 理解任务并规划行动,但这个行动由专家模型(LLM 只是整合结果)执行。这里的 LLM 只是一个促进者,组织不同模型在不同领域解决不同任务的协作。LLM 随后保持其通用性,并可以选择使用哪个工具以及何时使用它(在这种情况下,模型就是工具)。例如,如果一个 LLM 在某个模式下没有能力,它将利用专家模型的能力来完成该任务。LLM 只需要知道调用哪个模型来解决特定任务。因此,HuggingGPT 代表了一个灵活的系统,我们只需要向 LLM 提供文本描述,然后 LLM 就会整合不同的专家模型。

https://github.com/OpenDocCN/freelearn-dl-zh/raw/master/docs/bd-ai-agt-llm-rag-kg/img/B21257_09_07.jpg

图 9.7 – HuggingGPT 过程(arxiv.org/pdf/2303.17580)

任务规划

在第一步,任务规划,LLM 必须理解任务并将其分解为子任务。在现实世界中,用户请求可能很复杂,其意图复杂,需要任务分解。这是因为单个模型可能无法解决整个任务;相反,可能需要多个模型来处理不同的方面。然后,LLM 需要将任务分解为一系列子任务,并理解这些任务之间的依赖关系以及它们应该执行的顺序。这是通过创建一个特定的提示来完成的。

为了标准化系统,HuggingGPT 的作者使用了一套特定的指令。然后,LLM 必须遵守这些规范以进行任务规划。他们设计了一个标准化的任务模板,并指示 LLM 通过槽位填充进行任务解析。LLM 在槽位填充的指导下填写此模板,从而实现子任务的持续解析和执行。模板必须填充以下四个槽位:

  • 任务 ID:模型为每个任务提供唯一的标识符。此 ID 用于识别任务及其依赖任务,以及所有生成的资源。

  • 任务类型:此槽位包括任务类型;每个任务可以是各种类型(语言、视觉、视频、音频等)。

  • 任务依赖关系:此槽位定义了每个任务的前提条件(模型只有在所有前提条件都完成的情况下才会启动任务)。

  • 任务参数:此槽位包含执行任务所需的所有参数(从文本到图像或其他资源)。这些内容可以来自用户的查询或其他任务的输出。

https://arxiv.org/pdf/2303.17580(img/B21257_09_08.jpg)

图 9.8 – HuggingGPT 类型的任务 (arxiv.org/pdf/2303.17580)

作者使用演示来指导模型执行任务(如图像到文本、摘要等)。正如我们在第三章中看到的,添加演示允许模型将任务(少样本提示和情境学习)映射。这些演示告诉模型如何划分任务,顺序如何,以及是否存在依赖关系。此外,为了支持复杂任务,作者包括聊天记录(与用户进行的先前讨论)作为一种工具。这样,模型就可以知道是否已经指示了可以有助于任务的其他资源或请求。

提示提供了 LLM 所需的所有信息。在提示中,我们提供了关于其任务的说明(规划任务分解)、信息检索的位置、如何执行任务的示例以及我们期望的输出。

https://arxiv.org/pdf/2303.17580(img/B21257_09_09.jpg)

图 9.9 – HuggingGPT 中提示设计的细节 (arxiv.org/pdf/2303.17580)

模型选择

在规划任务之后,模型开始选择适合任务的适当模型,或模型选择。一旦我们有一个子任务的列表,我们需要选择适当的模型。这是可能的,因为我们有模型及其功能的描述。这项工作的作者已经从机器学习社区(例如,Hugging Face)收集了专家模型的描述。实际上,在 Hugging Face 上,通常是模型的开发者自己用功能、架构、支持的语言和领域、许可等方面的术语来描述模型。

https://github.com/OpenDocCN/freelearn-dl-zh/raw/master/docs/bd-ai-agt-llm-rag-kg/img/B21257_09_10.jpg

图 9.10 – Hugging Face 上模型描述示例的截图 (huggingface.co/docs/transformers/model_doc/bert)

因此,模型分配被表述为一个单选模型,其中 LLM 必须在给定的特定上下文中从可用的模型中选择最佳模型。然后,考虑到用户的需求和上下文,LLM 可以选择最适合执行任务的专家模型。当然,上下文长度是有限的,你不能输入所有模型描述而不超过这个长度。为了解决这个问题,HuggingGPT 系统应用了一个两阶段过滤和排名过程。首先,根据任务规划期间识别的任务类型(例如,语言、视觉或音频)对模型进行过滤。只有与特定子任务类型相关的模型被保留,显著缩小了模型池。在过滤后的模型中,系统根据下载量对它们进行排序,这作为质量、可靠性和社区信任的代理。假设广泛使用的模型更有可能表现良好。最后,系统选择前 k 个模型描述(其中 k 是一个可配置的超参数)并将它们包含在提示中。然后,LLM 执行单选模型选择,评估上下文和用户需求,从候选列表中选择最合适的模型。这种策略提供了一个平衡的权衡:它保持了提示在可管理的令牌限制内,同时仍然允许 LLM 有足够的选择来做出明智和有效的模型选择。

https://github.com/OpenDocCN/freelearn-dl-zh/raw/master/docs/bd-ai-agt-llm-rag-kg/img/B21257_09_11.jpg

图 9.11 – HuggingGPT 中用于模型选择的提示设计细节 (arxiv.org/pdf/2303.17580)

模型执行

一旦将特定模型分配给特定任务,就必须执行该模型。请注意,这些模型仅在推理中使用。这些模型通过 Hugging Face API 使用。为了加快执行速度,HuggingGPT 使用混合推理端点。选定的模型将任务参数作为输入,然后将结果发送回语言模型(ChatGPT)。此外,如果没有资源依赖关系,其推理可以并行化。换句话说,相互不依赖的任务可以同时执行。否则,系统会考虑一个模型的输出与另一个模型的输入之间有多少关联(例如,如果一个任务必须有一个子任务的输出才能执行)。为了进行推理,HuggingGPT 使用混合推理端点,主要依赖于 Hugging Face API。当模型通过这些 API 可用且功能正常时,系统会远程执行它们。然而,如果 API 端点不可用或速度慢或遇到网络问题,则使用本地推理作为后备。这种混合设置确保了执行过程中的灵活性和鲁棒性。

作者指出:“尽管 HuggingGPT 能够通过任务规划来开发任务顺序,但在任务执行阶段,仍然可能难以有效管理任务之间的资源依赖关系。”为了解决这个问题,作者简单地使用了一个独特的符号 <resource> 来处理依赖关系。<resource> 是一个特殊标记,代表任务所需的资源(这与任务标识符相匹配),如果所需的任务已完成,则该标记将被资源替换。

https://github.com/OpenDocCN/freelearn-dl-zh/raw/master/docs/bd-ai-agt-llm-rag-kg/img/B21257_09_12.jpg

图 9.12 – 模型执行(arxiv.org/pdf/2303.17580

响应生成

一旦所有任务都执行完毕,就必须生成响应。HuggingGPT 将之前步骤中获得的所有信息(任务规划、模型选择和任务执行)整合成一种简明的摘要(任务、使用的模型和模型的结果)。请注意,模型整合了多个其他模型的结果,特别是通过推理获得的结果,这些结果可能具有不同的格式。这些结果以结构化格式呈现(例如,边界框、概率等),HuggingGPT 将这些结果转换为自然语言以响应用户。因此,HuggingGPT 不仅为任务获取结果,而且以人性化的方式响应用户。

https://github.com/OpenDocCN/freelearn-dl-zh/raw/master/docs/bd-ai-agt-llm-rag-kg/img/B21257_09_13.jpg

图 9.13 – 响应生成(arxiv.org/pdf/2303.17580

定性来看,我们可以看到模型能够解决多个任务。因此,模型能够将任务分解成各种子任务,选择合适的模型,检索结果并有效地整合它们。例如,模型可以进行图像标题、姿态生成,甚至姿态条件图像生成任务。不仅如此,任务可以是多模态的(如文本到视频生成、为视频添加音频等)。其中最有趣的一个方面是,所有这些都是在没有任何额外 LLM 训练的情况下完成的。事实上,所有这些都是在推理过程中完成的(对于 LLMs 和推理中的模型)。优势在于,你可以不进行任何训练就集成额外的模型以处理额外任务;你只需要添加新模型的函数描述。

例如,在这种情况下,我们可以看到多模态任务(文本、视频和音频)的执行。模型被要求执行两个任务:根据描述生成视频和为视频配音。模型并行执行这两个动作。在以下图的底部部分,模型必须依次执行这两个任务:模型首先从图像生成文本,然后生成音频。

https://github.com/OpenDocCN/freelearn-dl-zh/raw/master/docs/bd-ai-agt-llm-rag-kg/img/B21257_09_14.jpg

图 9.14 – 视频和音频模态多模型合作的定性分析 (arxiv.org/pdf/2303.17580)

研究的作者还探索了更复杂的任务,在这些任务中,一个大型语言模型必须组织多个模型的合作才能成功解决问题。HuggingGPT 可以通过任务规划步骤来组织多个模型的合作。结果显示,HuggingGPT 能够在多轮对话场景(用户将请求分成几个轮次)中应对复杂任务。此外,该模型可以通过为每个任务分配一个专家模型来解决复杂任务。例如,“尽可能详细地描述图像”需要模型解决五个任务(图像标题、图像分类、目标检测、分割和视觉问答任务)。这五个任务不是由一个模型解决,而是由五个不同的模型调用和执行。然后,这些模型各自提供必须整合到详细答案中的信息。这些模型在推理过程中并行工作,然后合并最终信息。

https://github.com/OpenDocCN/freelearn-dl-zh/raw/master/docs/bd-ai-agt-llm-rag-kg/img/B21257_09_15.jpg

图 9.15 – 复杂任务案例研究 (arxiv.org/pdf/2303.17580)

HuggingGPT 的局限性

然而,仍存在一些局限性:

  • 效率:HuggingGPT 需要从 LLM 中进行多次调用;这发生在四个过程中的三个步骤中(任务规划、模型选择和响应生成)。这些交互成本高昂,可能导致响应延迟和用户体验下降。此外,原始文章中使用了闭源模型(GPT-3.5 和 GPT-4),这导致了额外的成本。技术上,可以使用开源模型以相同的方法进行操作。

  • 规划:规划取决于 LLM 的能力。显然,LLM 的能力越强,系统的能力就越好,但 LLM 的推理能力有限,因此规划可能并不总是最优或可行的。您可以选择测试不同的 LLM 或使用经过微调以创建高效计划的 LLM,或者使用经过推理链微调的模型。

  • 上下文长度:模型的上下文长度有一个明确的限制,对于复杂任务来说,这是一个问题。在原始文章中,作者指出,对于某些任务来说,32K 就足够了(特别是如果连接了多个模型)。因此,解决方案可能是使用上下文长度更长的模型。然而,到目前为止,似乎模型并没有有效地使用长上下文。另一个解决方案可能是使用摘要。

  • 不稳定性:这源于 LLM 的随机性质。尽管 LLM 被训练生成文本,并且在这种情况下我们提供了上下文,但模型可以忽略上下文并产生幻觉。文章的作者指出,模型在预测过程中可能无法遵守指令或给出错误答案。这会产生程序流程错误或错误答案。幻觉仍然是 LLM 的一个开放性问题,但有一些策略可以减轻它们。

那么,HuggingGPT 是一个能够通过使用语言作为接口来协调不同专家模型以解决复杂任务的系统。在这里,LLM 仅作为各种 AI 模型的控制器和管理者。它的唯一任务就是协调模型并生成响应。然后模型生成计划,选择模型,并将结果整合到最终响应中。LLM 本身不执行任何任务,而是要求各种专家模型提供解决方案。所有这些都是在推理过程中进行的,而不涉及任何训练。用户提出问题后,系统执行过程,并以自然语言进行响应,从而使交互人性化且流畅。

在以下子节中,我们将探讨各种旨在克服 HuggingGPT 局限性或解决其他专业领域关键挑战的模型。通过这些探索,您将深入了解不同的策略,并学习如何将这些代理应用于现实世界场景。

ChemCrow

我们之前将 HuggingGPT 视为一个协调不同工具(模型)的系统,作为一个通用型模型用于通用任务。在本小节中,我们想要讨论一个应用于专门领域的类似系统。ChemCrow(Bran,2023)遵循与 HuggingGPT 相似的设计理念,但将其应用于专门领域——化学。

通用型大型语言模型(LLM)的局限性在于它们具有通用知识,因此既未针对特定领域进行专门化,也未更新最新信息。这对于许多应用领域(尤其是科学、金融和医疗保健等专门领域)可能是一个问题。此外,LLM 使用一组启发式方法进行计算,而不是通过严格的过程。对于化学等领域的应用,这是一个问题,因此自然想到通过外部工具扩展模型的能力。外部工具提供确切答案,并弥补 LLM 在特定领域的不足。因此,将 LLM 与多个工具集成可以使 LLM 在即使其固有特性构成其适用性限制的领域也能被使用。

一个可以从 LLM 的使用中受益的领域是科学研究。一方面,LLM 已经显示出理解化学的能力,另一方面,有许多针对化学的专门模型,或者至少是针对特定应用的模型。许多这些工具都是由开源社区开发的,并且可以通过 API 访问。尽管如此,整合这些工具并不容易,需要计算编码方面的专业知识,而这通常不是化学研究者的技能之一。受先前工作的启发,本研究(Bran,2023)的作者们提出了他们所谓的 LLM 驱动的化学引擎(ChemCrow),旨在“简化跨药物和材料设计及合成等领域的各种常见化学任务的推理过程。”ChemCrow 与我们所看到的 HuggingGPT 非常相似,其中我们有一个中央 LLM(GPT-4),协调多个工具(在这种情况下,高度专门化于化学)。中央 LLM 被提示特定的指令和信息,以便执行特定任务并以特定格式响应。为了指导 LLM 的推理和工具使用,ChemCrow 采用了一种称为“思考、行动、行动输入和观察”的结构化提示格式,以提示模型对任务(及其当前状态)进行推理,当前状态如何与最终目标相关联,以及如何规划下一步:

  • 思考:模型反思当前问题,考虑其进展,并概述通向最终目标的推理

  • 行动:它选择下一个要使用的适当工具(例如,分子生成器或反应预测器)

  • 行动输入:它指定应发送到所选工具的输入

  • 观察:它记录工具的输出,然后将其纳入下一个推理周期

https://arxiv.org/pdf/2304.05376(img/B21257_09_16.jpg)

图 9.16 – ChemCrow 概述(arxiv.org/pdf/2304.05376)

因此,在这个系统中,模型通过一个思维步骤(可以将其视为行动规划)并使用工具及其输入(选择和使用模型)来进行。模型获取结果,观察它们,然后再次进行思维步骤,直到达到答案。这个过程与我们之前看到的类似,但更加重视推理和模型的专门化。此外,工具不仅包括模型,还包括搜索互联网或文献的能力;模型还可以运行代码。因此,我们也有系统能力和灵活性的扩展。因此,该研究的作者将这个系统视为一种化学任务的科研助手。

https://arxiv.org/pdf/2304.05376(img/B21257_09_17.jpg)

图 9.17 – 人/模型交互导致新分子的发现(arxiv.org/pdf/2304.05376)

因此,想法是将 LLM 的推理技能与专业知识以及化学计算工具相结合。结果显示,类似的方法可以导致特定领域的实际应用,例如化学。

SwiftDossier

SwiftDossier 是应用基于代理系统在科学和医疗领域的一个显著例子,特别关注解决这些领域中最关键的挑战之一:幻觉。在医学和制药等领域,幻觉输出——即自信但错误或无法验证的信息——可能导致严重的法律、伦理和安全风险。LLM 拥有巨大的内存,但生成文本是随机的,没有明显验证其来源。这对制药行业或潜在的医疗用途来说是个问题。为了在 SwiftDossier 中解决这个问题,使用了 RAGs 和 LLM 驱动的代理来强制模型生成。该系统不是仅仅依赖 LLM 的内部知识——虽然知识量巨大,但生成是随机的且没有来源验证——而是迫使模型将其响应建立在外部、可靠的数据源上。系统使用不同的工具集来回答不同的问题:科学文章、互联网访问、数据库和其他 ML 模型。使用这一套工具,LLM 可以成功生成报告并最小化幻觉的风险。

https://arxiv.org/pdf/2409.15817(img/B21257_09_18.jpg)

图 9.18 – SwiftDossier 架构(arxiv.org/pdf/2409.15817)

ChemAgent

在之前看到的两个例子中,我们有一个添加了工具的代理来弥补通用 LLM 的知识缺陷。换句话说,我们试图通过使用外部信息或工具来执行操作来弥补 LLM 的不足。此外,如果任务本身很复杂,几种方法试图将其分解成更可管理的子任务。代理首先生成一个日程表,然后执行各种子任务,从而结合推理和执行。尽管如此,LLM 仍然可能产生错误,尤其是在化学等复杂领域。

LLMs,虽然作为强大的通用工具,在化学领域面临一些挑战,在这些领域中,任务需要精确推理、准确计算和深厚的领域知识。这些挑战源于 LLMs 生成文本和代码的局限性,并且在科学应用中更为明显,因为小的错误可能导致重大不准确:

  • 在特定领域公式上的挣扎:LLMs 可能会误解或错误地应用专业的化学方程式或符号,尤其是在所需的公式在一般训练数据中不常见的情况下

  • 中间推理步骤错误:在复杂的多步骤任务中(例如,合成规划或性质预测),仅一步的错误可能会级联并导致最终输出错误

  • 代码生成错误:当将文本推理与代码(通常是 Python)结合时,LLMs 经常虚构函数、使用错误的库、产生语法错误或生成无法执行的代码——尤其是对于需要精确库调用和数值稳定性的科学计算

https://github.com/OpenDocCN/freelearn-dl-zh/raw/master/docs/bd-ai-agt-llm-rag-kg/img/B21257_09_19.jpg

图 9.19 – 化学领域 LLM 失败示例 (arxiv.org/pdf/2501.06590)

与 LLMs 不同,人类从过去的经验和错误中学习。对于 LLMs 来说,在预训练结束后无法进行学习(微调是一种昂贵的方法,且不能重复使用),因此持续学习仍然是人工智能的一个开放性问题。另一方面,人类可以记住用于类似问题的策略;一旦遇到新问题,他们会学习新的策略,这些策略可以在未来使用。因此,在 ChemAgent 中,作者试图找到一种模拟这一过程的方法。他们提出一个动态库,允许通过持续更新和改进其内容来促进迭代问题解决。该库作为分解化学任务的存储库。换句话说,一个任务被分解成各种子任务,然后解决方案被保存在库中以备将来使用。一旦出现新的任务,库就会更新新的子任务和相应的解决方案,保持库的相关性,并随着时间的推移提高其有用性。受人类认知的启发,该系统有三个不同的记忆组件:计划记忆(高级策略)、执行记忆(特定任务解决方案)和知识记忆(基本的化学原理)。这些记忆组件存储在外部,允许系统在需要时再次找到信息,并且是动态更新的。

https://github.com/OpenDocCN/freelearn-dl-zh/raw/master/docs/bd-ai-agt-llm-rag-kg/img/B21257_09_20.jpg

图 9.20 – ChemAgent 框架(arxiv.org/pdf/2501.06590)

因此,ChemAgent 不仅被动地使用它在记忆中找到的内容,而且允许系统动态地更新记忆。它还使用内存分区来改进问题解决的各个阶段。ChemAgent 将过程分为计划和执行(为每个步骤关联一个特定的记忆)并添加一个作为基本化学原理和公式的参考的记忆。当出现问题时,它被分解成一系列子任务,这些子任务被解决,这些解决方案被保存在记忆中。

法律领域的多代理

另一个可能从代理的使用中受益的领域是法律行业。法律服务对于保护公民权利至关重要,但它们可能特别昂贵,而且律师的数量并不总是足够。此外,公正的判决是一项基本权利,但人类也表现出偏见。在这个领域使用代理可以通过降低成本和允许更公平的访问来彻底改变法律服务。在法律领域,幻觉尤其成问题,如果不是消除,也应尽可能减少。幻觉既源于模型的随机性,也源于训练它们的数据质量。因此,必须从两个轴向上采取行动,以减轻这种现象。

在本小节中,我们希望介绍两种以法律为中心的方法来展示一些已经使用过的有趣元素。再次强调,原则是相同的:一切围绕一个中心元素展开,这个元素是一个 LLM。例如,Chatlaw 关注数据质量以减轻 LLM 幻觉的风险。此外,为了充分利用作者收集到的质量数据集,他们使用了一个知识图谱。此外,他们没有使用单个智能体,而是使用了一个多智能体系统。使用多个智能体使得系统能够模拟与 LLM 交互时不同领域的专业知识,这得益于提示的灵活性。使用多智能体使得在律师事务所内部模拟过程成为可能。作者开发了一个协议,以允许智能体之间有效协作:“四个独立的智能体角色,分别负责初步信息收集、深入材料研究、法律咨询和最终咨询报告撰写。”这样,过程就更加彻底。再次强调,他们为整个系统只使用了一个 LLM(作者使用了 GPT-4)。

https://arxiv.org/pdf/2306.16092v2(img/B21257_09_21.jpg)

图 9.21 – Chatlaw,多智能体协作(arxiv.org/pdf/2306.16092v2)

另一种有趣的方法是作者(Hamilton,2023;arxiv.org/pdf/2301.05327)使用 LLM 模拟法院的判决。在这里,同样使用了一个多智能体系统,其中每个智能体代表一位法官。每位法官提出一个意见,然后得出多数意见。因此,当案件被发送给九位法官时,系统收到九个意见,然后产生一个单一的意见。这种方法依赖于并行进行九次评估以及这些评估的一致性(多数票获胜)。

https://arxiv.org/pdf/2301.05327(img/B21257_09_22.jpg)

图 9.22 – 多法官系统(arxiv.org/pdf/2301.05327)

这项工作展示了如何利用 LLM 创建多个智能体,它们可以协同工作以减轻幻觉。作者进一步证明了使用 LLM 作为系统中心可以实现的灵活性。这项研究的局限性在于使用了同质化的法官(最好是构建由不同模型组成的集成,以避免不同法官具有相同的偏见),这可能导致重复的意见。

多智能体在医疗应用中的使用

跨学科研究是复杂的,通常需要由不同领域专家组成的研究团队。通常,科学研究是由每个研究人员处理特定方面并掌握不同技术的团队进行的。例如,AlphaFold 2 是 34 位不同专业知识(计算机科学、生物信息学和结构生物学)的研究人员的成果。显然,招募大量专家团队需要时间(而且并不总是容易找到具有正确专业知识的人),而且成本高昂。只有少数机构和公司能够承担最雄心勃勃的项目。然而,最近创建的 LLM 在科学主题上的知识越来越广泛,我们之前已经看到这种知识可以与工具的使用联系起来。ChemCrow 是解决化学问题的例子,但它无法处理开放式的跨学科研究问题。最近,人们已经通过创建可以处理端到端过程的管道来解决这一问题。例如,一位 AI 科学家(Lu,2024)进行了一个从构思想法开始,以在机器学习上撰写科学论文结束的过程。AI 科学家被赋予了一个广泛的研究方向,产生了一个想法,进行文献搜索,规划并执行实验,撰写手稿,最后校对。所有这些工作都是由一个类似于 LLM 的代理完成的,该代理连接到工具并按顺序进行。

https://arxiv.org/pdf/2408.06292(img/B21257_09_23.jpg)

图 9.23 – AI 科学家过程的说明(arxiv.org/pdf/2408.06292)

其他研究也展示了类似的过程,但它们仍然局限于特定的领域和线性过程。对于科学研究,我们希望找到结合不同专业知识的方法。因此,Swanson (2024) 提出了一种旨在对复杂问题进行跨学科科学研究的虚拟实验室,用于人机协作。在虚拟实验室中,人类引导一组跨学科代理来管理复杂过程。不同的代理拥有不同的专业知识,并由一个 LLM 运行。每个代理都与其他代理和人类进行交互。通过这种方式,研究的作者构建了一个灵活的架构。在这里,人类为代理提供指导,而代理则决定搜索方向和设计解决问题的方案。每个代理都由一个提供给 LLM(文章中为 GPT-4)的提示(包含关于角色、专业知识、目标和可用工具的信息)控制。然后,虚拟实验室通过小组或个人会议进行研究。

人类提供问题和议程以启动讨论。在团队会议中,代理讨论研究问题并共同努力实现全局目标。在个人会议中,单个代理必须解决一个任务(例如编写代码),代理可以单独工作或与另一个提供关键反馈的代理一起工作。通过一系列全局和个人会议,团队解决研究问题。

https://github.com/OpenDocCN/freelearn-dl-zh/raw/master/docs/bd-ai-agt-llm-rag-kg/img/B21257_09_24.jpg

图 9.24 – 虚拟实验室架构 (www.biorxiv.org/content/10.1101/2024.11.11.623004v1.full)

在虚拟实验室中,有一个主要研究员PI),其目的是最大化研究的影响力,并自动为项目创建一组适当的科学家代理(生物学家或计算机科学家)(基于 PI 提供的项目描述)。PI 在提示中定义每个代理的角色、专业知识和目标。此外,还可能有一个专门用于项目批评的代理。之后,会议开始。每次会议都遵循一组组织成结构的输入:议程(讨论内容的描述)、议程问题(会议中要回答的问题集)、议程规则(使会议更顺畅的可选规则)、摘要(前次会议的可选摘要)、背景(有助于会议的额外信息)和回合(讨论回合的数量,以防止讨论无休止地进行)。在团队会议中,所有代理都参与讨论,人类编写议程(可选地,包括规则和问题),然后进行不同回合的讨论。PI 开始,然后每位科学家代理(加上批评代理)对讨论发表意见。最后,PI 总结代理提出的问题点,对代理的输入做出决定,并提出后续问题。经过各种回合后,PI 编写一个人类可以阅读的最终摘要。

在个人会议中,人类提供议程并选择代理,代理执行任务(此外,还可能有批评代理,提供批评)。在代理和批评者之间经过一系列回合后,代理提供回应。此外,还可以进行并行会议,其中多个代理执行相同任务,并在与 PI 的最终会议中得出最终答案。

https://github.com/OpenDocCN/freelearn-dl-zh/raw/master/docs/bd-ai-agt-llm-rag-kg/img/B21257_09_25.jpg

图 9.25 – 虚拟实验室并行会议 (www.biorxiv.org/content/10.1101/2024.11.11.623004v1.full)

以这种方式,作者创建了一个灵活的框架,结合了在单人和协作环境中工作的异构代理。需要注意的是,在这种方法中,有一个人类参与其中;也就是说,人类是系统的中心,并积极与 AI 合作。这个过程模仿(尽管当然是以简化的方式)了人类团队在解决复杂问题时的工作和决策过程。为了测试这项工作的实用性,作者测试了虚拟实验室在设计和结合 SARS-CoV-2 KP.3 变异株刺突蛋白的抗体或纳米抗体方面的能力。这是一个复杂的问题,因为 SARS-CoV-2 进化迅速,因此必须找到一个快速的系统来设计可以阻止它的抗体。虚拟实验室首先创建了一个能够应对这个问题的团队(PI 为这个问题组建了合适的研究人员团队)。在团队会议上,项目方向被描述,并讨论了主要细节。然后,举行了一次关于可以使用哪些工具以及如何选择的团队会议,以及一系列个人会议,研究人员使用各种工具创建抗体设计工作流程。在与 PI 的会议中,定义了工作流程。

https://github.com/OpenDocCN/freelearn-dl-zh/raw/master/docs/bd-ai-agt-llm-rag-kg/img/B21257_09_26.jpg

图 9.26 – 抗体设计虚拟实验室 (www.biorxiv.org/content/10.1101/2024.11.11.623004v1.full)

虚拟实验室成功地设计出了抗体,随后通过实验进行了验证。该系统成功地创建了一个复杂的流程,使用序列模型来设计抗体(从而解决了一个真实且复杂的问题)。构建这样的系统通常需要一个跨学科团队,因为需要用不同的专业知识来解决问题。因此,拥有不同专业知识的代理可以从前不同的角度讨论问题,从而为科学研究的基本要素(批判性)增添了内容。这是通过一系列会议来完成的,其中 AI 是人类合作伙伴。我们看到的是,通过多轮会议(小组和个人)的创建,形成了一个既灵活又复杂的系统。

在这个阶段仍然存在一些限制。例如,模型的知识截止到某个点,因此它们可能不知道最新发布的工具,因此可能会建议旧模型(或实现中存在问题的模型)。解决这个问题的一个方法可能是使用 RAG 或互联网搜索。另一个限制是,该系统并不完全自包含;它附带了一个议程和一组经过精心设计的提示。在这个系统中,人类仍然参与其中,必须提供指导。没有指导,AI 模型可能会给出模糊的答案,或者除非特别要求,否则不会做出决定。有时它们无法完成任务,或者偏离了它们应该做的事情。无论如何,这个系统是灵活的,可以无差别地应用于许多其他问题。

将不同领域的专业知识与人类反馈相结合似乎是取得更好成果的关键。在这方面,代理实验室被设计用来生成整个研究工作流程(从文献综述和实验到报告撰写),这一切都基于人类提供的初始研究想法。在这个系统中,过程从收集和分析相关论文开始,接着是协作规划和数据准备,一系列实验,以及报告生成。这个过程可以分为三个阶段:

  • 文献综述阶段:在这个阶段,为给定的研究想法收集文章。博士代理利用 arXiv API 检索相关论文,综合它们,并提供见解。这个代理使用搜索 API、摘要模型和文献管理系统作为工具。这个过程会迭代,直到达到一定数量的相关文章。

  • 实验阶段:第一步是制定计划,根据文献综述和研究目标生成一个计划。在这个阶段,博士和博士后代理进行协作和讨论如何实现目标,生成一个定义了要实施哪些机器学习模型、使用哪些数据集以及其他必要实验步骤的计划。一旦计划确定,数据准备阶段就开始了,在这个阶段,根据定义的计划生成数据准备代码。机器学习工程师代理可以访问 Hugging Face 数据集,然后代码被编译并提交。在运行实验阶段,机器学习工程师代理执行实验计划。在这个阶段,代码被生成、测试和改进。然后对结果进行解释。在这个阶段结束时,博士和博士后代理讨论结果。如果他们同意发现的有效性,他们将提交结果,这些结果将成为报告的基础。

  • 报告撰写:在报告撰写阶段,博士和教授代理将研究结果综合成一份全面的学术报告。从初始框架(摘要、引言、背景、相关工作、方法、实验设置、结果和讨论)开始,他们开始生成文本(使用 LaTeX 编写,以便于修订和校正)。在撰写过程中,系统访问文献,并迭代地纠正文章以确保准确性、清晰度和与研究目标的契合度。最后,进行一种论文审查以确保文章的正确性。请注意,在此过程中,系统会收到来自人类的反馈。

该系统的关键特性是代理能够自主执行重复性任务(例如,文献检索和编码),但在需要创造力和判断力的情况下允许人类输入。代理之间通过沟通中间结果来确保各方之间的协同。在每一个阶段,通过反思和反馈进行迭代改进。

https://github.com/OpenDocCN/freelearn-dl-zh/raw/master/docs/bd-ai-agt-llm-rag-kg/img/B21257_09_27.jpg

图 9.27 – 代理实验室工作流程 (arxiv.org/pdf/2501.04227)

代理实验室旨在快速探索想法并帮助研究人员能够同时探索多个研究方向。代理实验室的结构允许它从由人类研究人员提出的一个想法开始,执行整个工作流程。在这项工作中,他们不仅关注结果的准确性,还试图找到一种更有效率的任务解决方法(先前的工作需要过多的计算成本)。

https://github.com/OpenDocCN/freelearn-dl-zh/raw/master/docs/bd-ai-agt-llm-rag-kg/img/B21257_09_28.jpg

图 9.28 – 代理实验室方案 (arxiv.org/pdf/2501.04227)

作者指出,在各个阶段融入人类反馈显著提高了研究成果的质量。此外,他们还表示,由代理实验室生成的机器学习代码达到了现有最先进方法的性能,并且为人类阅读的报告质量显著良好。

这些系统表明,通过结合人类反馈,可以解决复杂任务。然而,这些系统依赖于人类反馈,因为截至目前,大型语言模型(LLMs)尚不具备真正的推理能力。这有几个局限性:系统可能在设计超出标准方法的创新实验方面遇到困难,尤其是在需要创造性问题解决或新颖方法的应用领域。系统仍然会在代码中产生错误(错误或低效),它继续维持高计算成本(多个 LLM 调用),智能体之间的通信尚未完美,与专家相比,报告生成仍然不够优化,它对高度专业化的或利基研究领域的泛化能力较差(它们在训练数据和文献中表现不佳),并且存在几个尚未解决的伦理问题。

在本节中,我们探讨了具有单个智能体或多个智能体的不同系统。在下一节中,我们将看到 HuggingGPT 的实际工作方式以及我们如何创建多智能体系统。

与 HuggingGPT 一起工作

您可以使用两种方式使用 HuggingGPT:

  • 在本地克隆仓库

  • 使用网络服务

在这里,我们将探讨两种方法。主要区别在于,当我们本地克隆仓库时,我们会下载所有模型,系统执行将在本地进行。相比之下,网络服务方法要求执行在服务中进行。在两种情况下,所有模型都用于推理;区别在于模型的执行位置和使用的资源。此外,两种方法都支持使用基于网络的图形用户界面。

在本地使用 HuggingGPT

要克隆 HuggingGPT(相应的仓库称为 Jarvis),使用 Git LFS 非常有用。Git LFS 是 Git 的开源扩展。Git 旨在管理代码仓库,但不处理大型二进制文件(如视频、数据集或高分辨率图像)。Git LFS 对于包含大型资产(例如,数据集、视频或二进制文件)的仓库至关重要,因为否则 Git 在处理大文件时效率低下。Git LFS 通过在常规仓库对象之外存储大文件并替换 Git 仓库中的轻量级引用(指针)来解决此问题。Git LFS 通过在仓库的常规对象之外存储大文件,使使用大型对象时标准化更好,并在与 GitHub 仓库(如克隆、推送和拉取)操作时提高性能。指针包含有关文件的各种元数据(例如,大小、哈希和位置),当我们克隆一个仓库时,Git LFS 通过利用这些指针中的信息下载文件。这使我们能够将代码操作与对大文件的操作分开。一般来说,对于涉及机器学习、游戏开发或视频编辑的项目,通常使用 Git LFS,因为它可以简化并加快下载过程。在机器学习项目中,模型权重非常大且经常更新;使用 Git LFS 允许我们高效地跟踪和管理这些文件——例如下载的模型——而不会使主仓库膨胀。正如我们提到的,HuggingGPT 使用几个大型模型(例如,有不同类型的扩散模型,可能占用几个 GB),Git LFS 允许更容易地管理。

要安装 Git LFS,您可以访问官方网站 (git-lfs.github.com/) 并下载适用于您操作系统的安装程序(Windows、macOS 或 Linux)。运行下载的安装程序。在 macOS 上,双击.pkg文件或使用 Homebrew 包管理器:

brew install git-lfs

运行以下命令以启用 Git LFS 对您的用户:

git lfs install

一旦您将 Git LFS 作为 Git 扩展安装到您的计算机上,它将自动识别和跟踪仓库中是否存在大文件,并对其进行管理。它修改或创建一些 Git 配置条目(例如在~/.gitconfig中),以便您创建的未来克隆和仓库可以无额外麻烦地使用 LFS。

克隆启用了 LFS 的仓库就像它是一个常规仓库一样简单(Git LFS 会在后台处理文件,并自动管理大文件):

git clone https://github.com/example/repo.git

如果我们想,我们可以轻松地进行大文件跟踪:

git lfs track "*.bin"
git add .gitattributes
git commit -m "Track large .bin files with LFS"

Git LFS 与经典 Git 命令兼容。拉取/推送操作与正常 Git 工作流程中的操作一样——除非仓库需要特定的凭据或令牌,否则不需要特殊步骤。

到目前为止,我们可以继续安装 HuggingGPT。HuggingGPT 仓库存储在github.com/microsoft/JARVIS

https://github.com/OpenDocCN/freelearn-dl-zh/raw/master/docs/bd-ai-agt-llm-rag-kg/img/B21257_09_29.jpg

图 9.29 – Microsoft HuggingGPT

第一步是克隆仓库:

git clone https://github.com/example/microsoft/JARVIS.git

https://github.com/OpenDocCN/freelearn-dl-zh/raw/master/docs/bd-ai-agt-llm-rag-kg/img/B21257_09_30.jpg

图 9.30 – Microsoft HuggingGPT 克隆

git clone命令从远程 URL 启动仓库的下载。终端输出指示正在下载的仓库:对象(元数据和更改)和差异压缩(一个通过仅发送版本之间的差异来最小化传输数据量的过程)。注意以下内容:

  • 接收对象:100% (150/150),完成。:这确认了所有对象(文件和历史记录)都已接收

  • 解析差异:100% (85/85),完成。:Git 通过应用接收到的更改(差异)来重建实际的仓库状态

一旦我们克隆了仓库,我们就可以转到本地仓库(本地文件夹):

cd JARVIS/hugginggpt/server

这一步是为创建或管理conda环境做准备,确保操作在相关项目目录的上下文中执行。

然后,我们创建一个新的名为jarvisconda环境(或我们可以选择其他名称),并指定它应使用 Python 版本 3.8:

conda create -n jarvis python=3.8

注意,-n表示我们想要为我们的项目创建一个新的环境,而python=3.8表示我们明确指定此环境的 Python 版本为 3.8:

conda环境允许我们隔离依赖关系,避免与全局 Python 安装或其他项目发生冲突。

注意conda正在处理以下过程:

  • conda从其仓库中检索有关所需包和依赖项的信息。这确保了 Python 3.8 与任何其他要安装的库之间的兼容性。

  • conda解决潜在的依赖冲突,并最终确定要安装的包列表。

由于你可能之前已经安装了conda,我们只需要更新它:

conda update -n base -c defaults conda

https://github.com/OpenDocCN/freelearn-dl-zh/raw/master/docs/bd-ai-agt-llm-rag-kg/img/B21257_09_31.jpg

图 9.31 – 更新 conda

在解决环境并准备创建它之后,conda安装新环境所需的基本包。每个包都列在仓库(pkgs/main)及其特定版本(在这种情况下,我们使用 macOS)旁边。

终端提示我们继续([y]/n)?。请记住,用y响应以确认安装这些包。

注意以下元素:

  • conda确保必要的依赖关系准备就绪,且无冲突

  • 验证交易:它检查包元数据的完整性,并确保所有包之间的兼容性

  • conda将包安装到指定的环境中

一旦完成这些步骤,新的环境(jarvis)就准备好使用了。

成功创建后,conda为用户提供管理新环境的命令。

要激活此环境,请使用以下命令:

conda activate jarvis

要停用活动环境,请使用以下命令:

conda deactivate

记住,激活操作会将用户的终端会话切换到使用jarvis环境,隔离其依赖和 Python 版本。注意,提示符从(base)变为(jarvis),表示终端现在正在jarvis环境中运行。该环境的隔离 Python 版本(3.8)及其依赖现在正在使用。从此点开始安装的任何库或工具都将局限于该环境,避免与其他项目发生干扰。

https://github.com/OpenDocCN/freelearn-dl-zh/raw/master/docs/bd-ai-agt-llm-rag-kg/img/B21257_09_32.jpg

图 9.32 – conda 激活

在这一点上,我们开始安装各种需求:

conda install pytorch torchvision torchaudio pytorch-cuda=11.7 -c pytorch -c nvidia

以下命令使用pip安装requirements.txt文件中列出的依赖项(通常,在需求文件中提供了一个包列表)。这些需求是安装 HuggingGPT 所必需的:

pip install -r requirements.txt

HuggingGPT 中的以下注释强调必须安装 Git LFS。此脚本(作为项目的一部分提供)自动化下载本地或混合推理模式所需的模型文件。提醒一下,本地意味着模型完全在本地机器上运行,混合意味着推理涉及本地和远程执行的混合,正如 HuggingGPT 论文(arxiv.org/abs/2303.17580)和前述章节所述:

# download models. Make sure that `git-lfs` is installed.
bash download.sh # required when `inference_mode` is `local` or `hybrid`

一旦安装完毕,我们就可以开始执行:

python model_server.py --config config/config.default.yaml # required when `inference_mode` is `local` or `hybrid`.
python awesome_chat.py --config config/config.default.yaml --mode server # for text-davinci-003

仓库中有不同的脚本:

  • model_server.py:此脚本运行一个模型服务器,根据配置文件(config/config.default.yaml)处理机器学习模型。配置文件指定了参数,例如推理模式(本地或混合)、模型路径和硬件要求。

  • awesome_chat.py:此脚本启动一个用于文本生成或聊天机器人功能的服务器。

https://github.com/OpenDocCN/freelearn-dl-zh/raw/master/docs/bd-ai-agt-llm-rag-kg/img/B21257_09_33.jpg

图 9.33 – Microsoft HuggingGPT 完成安装

由于我们已经初始化了awesome_chat.py,我们可以使用一个用户友好的网页。

在网上使用 HuggingGPT

如果您不想安装 HuggingGPT,可以使用在线套件(在 Hugging Face Gradio 上:huggingface.co/gradio)。Hugging Face Gradio是一个 Python 库,简化了创建用户友好的基于 Web 的界面以供机器学习模型和其他 Python 应用程序使用的流程。

使用 Gradio,开发者可以快速构建交互式演示,例如文本生成、图像分类和音频处理等任务。这些界面允许用户通过提供输入(例如文本、图像或音频)并在浏览器中查看实时输出,直接在浏览器中测试模型。Gradio 高度可定制,支持与流行的 ML 框架(如 PyTorch、TensorFlow 和 Hugging Face 模型)集成,并可通过公共链接或嵌入到 Web 应用程序中轻松共享演示。

作者创建了一个 Gradio 界面(从本地启动 Jarvis 允许此类界面)。Gradio 空间可以通过以下链接访问:huggingface.co/spaces/microsoft/HuggingGPT

正如之前在系统及其安装描述中所述,HuggingGPT 是一个将 LLM 与 ML 社区连接起来的系统。在 Web 界面上,它也执行了完全相同的功能:将一个 LLM 与托管在 Hugging Face 上的 ML 模型集连接起来。由于硬件限制,Web 界面仅在local/inference端点部署了少量模型(此界面作为了解和查看系统工作原理的示例)。

注意,我们需要两个令牌,用户需要从每个网站获取:

  • Hugging Face 令牌:这是一个个人认证密钥,允许用户安全地访问 Hugging Face 的服务,包括其 API、模型、数据集以及平台托管的其他资源。此令牌作为您账户的标识符,确保您的请求得到 Hugging Face 系统的授权并与您的账户关联。该令牌随后用于验证和使用推理中的模型。Hugging Face 对某些服务实施速率限制,尤其是对 Web 推理。

  • OpenAI 密钥:这是 OpenAI 提供的唯一认证密钥,允许开发者安全地访问和交互 OpenAI 的 API 和服务,例如 GPT(例如 GPT-3.5 或 GPT-4)、DALL·E、Codex 和 Whisper。此密钥作为个性化凭证,用于识别您的账户并授权您使用 OpenAI 的平台。该密钥是向 OpenAI API 端点发送请求进行验证所必需的。OpenAI 使用您的 API 密钥跟踪您的使用情况(例如,API 调用次数和处理的令牌数)并相应地向您收费。在这种情况下,使用的是 GPT-4 的连接。

一旦我们准备好了令牌,我们就可以输入我们的问题并点击提交

https://github.com/OpenDocCN/freelearn-dl-zh/raw/master/docs/bd-ai-agt-llm-rag-kg/img/B21257_09_34.jpg

图 9.34 – HuggingGPT 界面

我们可以看到有两个主要面板:

  • 左侧面板:提供了一个标记为聊天机器人的文本输入框。该字段用于用户输入,例如问题或命令,以与 HuggingGPT 系统交互。

  • 右侧面板:在聊天机器人旁边预留了一个空白的框,用于显示 HuggingGPT 生成的响应或输出。

在聊天框下方,有一个标有发送的按钮,允许用户将他们的查询提交给 HuggingGPT。

注意,系统已经提供了我们可以使用的现成示例:

https://github.com/OpenDocCN/freelearn-dl-zh/raw/master/docs/bd-ai-agt-llm-rag-kg/img/B21257_09_35.jpg

图 9.35 – HuggingGPT 提供的示例

我们输入 OpenAI 和 Hugging Face 的标记。使用标有聊天机器人的文本输入框,我们可以向 HuggingGPT 发送自然语言查询(“你能告诉我你在图片中看到哪种披萨吗?”)并通过发送按钮发送查询。此外,还可以添加图像或其他多媒体元素(在我们的案例中,我们添加了一张披萨的图片):

https://github.com/OpenDocCN/freelearn-dl-zh/raw/master/docs/bd-ai-agt-llm-rag-kg/img/B21257_09_36.jpg

图 9.36 – HuggingGPT 交互示例

在图右侧的面板上,我们看到系统正在处理的过程:1 个放在木桌上的意大利辣香肠披萨. 这表明系统成功处理了输入图像,并识别出描绘为意大利辣香肠披萨的对象。这是一个典型的目标检测任务,系统正在使用一个模型来识别对象(这并不是一个进行图像识别的 LLM,而是一个由 LLM 调用的专门模型)。

聊天机器人根据推理结果提供详细的答案:

Sure, based on the inference results, the pizza in the picture is a pepperoni pizza.

HuggingGPT 解释了过程:

  1. 第一步涉及使用图像到文本模型来获取图像的描述。ViT-GPT2-COCO-EN 是一个视觉语言模型,它结合了一个用于图像编码的视觉 TransformerViT)和一个用于自然语言生成的GPT-2,在COCO 数据集上进行了图像标题任务的微调。该模型为输入图像生成英文描述性标题,有效地将视觉内容转换为连贯的文本描述。它利用 ViT 提取详细图像特征,并利用 GPT-2 的语言生成能力来生成准确且上下文丰富的标题。

  2. 然后,HuggingGPT 使用一个目标检测模型来识别图像中的对象。这个目标检测模型也提供了类似的响应,因为它识别了披萨和餐桌。DETR-ResNet-101 是一个用于目标检测和图像分割的视觉模型。它结合了一个用于特征提取的ResNet-101 主干(卷积神经网络)和一个用于检测和定位图像中对象的基于 transformer 的架构DEtection TRansformerDETR)使用 transformer 来建模图像中的全局关系,从而在不需要传统区域提议网络的情况下实现更准确的目标检测。

  3. 然后,一个视觉回答模型确认图像中是哪种披萨。ViLT-B/32-Finetuned-VQA 是一个针对视觉问答VQA)任务进行微调的视觉和语言转换器模型。它结合了一个轻量级的视觉和语言转换器ViLT)架构、基于补丁的图像分词器和转换器层,以联合处理视觉和文本输入。B/32 指的是使用 32 x 32 像素的补丁大小进行图像编码。该模型专门针对 VQA 数据集进行微调,旨在通过推理视觉和文本信息来回答关于输入图像的自然语言问题。

  4. 最后,LLM 观察到三个模型意见一致,因此对响应有信心。

回顾一下,HuggingGPT 接收用户的请求并选择模式。这些模式被执行,输出被收集。系统分析这些输出是什么,并生成最终响应。

https://github.com/OpenDocCN/freelearn-dl-zh/raw/master/docs/bd-ai-agt-llm-rag-kg/img/B21257_09_37.jpg

图 9.37 – HuggingGPT 响应示例

HuggingGPT 通过一个简单的例子展示了如何使用 LLM 解决多模态任务。这一切都是使用提示中的信息和一系列工具完成的。

在本节中,我们看到了一个单个 LLM(单个代理)处理任务,将其分解为子任务,并执行不同的模型。一个更优雅的方法是使用多个代理从不同的角度处理任务,协作并交互以解决问题。在下一个小节中,我们将看到如何实现这一点。

多代理系统

在本节中,我们看到了如何创建一个考虑不同代理和一系列工具(如 ML 模型)的系统。整个代码可以在Multi_Model–Travel_Planning_System.py脚本中找到。

作为一般概述,该系统实现了一个使用多个代理创建个性化旅行计划的旅行规划助手。然后,系统结合天气预测、酒店推荐、行程规划和电子邮件摘要。换句话说,我们有四个不同的代理,每个代理处理旅行规划的不同方面:

  • WeatherAnalysisAgent:使用随机森林回归器根据历史天气数据预测最佳访问地点的时间。在过去的天气数据(月份、纬度、经度和天气评分)上训练,并根据天气评分预测最佳的旅行月份。然后,该代理使用一个 ML 模型进行预测(一个专门为系统训练的模型)。

  • HotelRecommenderAgent:使用句子转换器嵌入根据用户偏好查找酒店。存储酒店描述并将它们转换为嵌入,然后使用语义相似性将用户偏好与最相关的酒店匹配。基于用户偏好,该代理在其库中搜索可能的解决方案。

  • ItineraryPlannerAgent:使用 GPT-2(文本生成管道)创建个性化的旅行行程。代理根据目的地、天气预测和酒店推荐生成行程计划。

  • SummaryAgent:使用 GPT-2 为客户生成摘要电子邮件。此摘要包括酒店费用(每晚费用×持续时间)和额外的每日费用。之后,它生成包含旅行详情、费用细分和行程高光的个性化电子邮件。

下图展示了代理和流程的架构:

https://github.com/OpenDocCN/freelearn-dl-zh/raw/master/docs/bd-ai-agt-llm-rag-kg/img/B21257_09_38.jpg

图 9.38 – AI 旅行规划系统工作流程的活动图,显示了从数据加载和代理初始化到行程规划和结果输出的完整序列

TravelPlanningSystem将所有代理连接在一起,基本上是系统的主控制器。因此,系统模仿了以下流程:

  1. 用户提供目的地、偏好和持续时间。

  2. 天气代理预测最佳访问时间。

  3. 酒店代理找到匹配的住宿。

  4. 行程代理创建每日计划。

  5. 摘要代理生成电子邮件并计算费用。

详细来说,我们可以看到这里的代理被定义为类。WeatherAnalysisAgent是一个基于机器学习的组件,它分析历史天气数据并预测给定位置的最好访问月份。它使用随机森林回归器来完成这项工作。我们可以将其视为一个使用机器学习模型执行任务的代理。此片段正在初始化代理:

class WeatherAnalysisAgent:
def __init__(self):
           self.model = RandomForestRegressor(n_estimators=100)

此代理创建一个RandomForestRegressor模型(n_estimators=100表示模型由 100 个决策树组成),必须从历史天气数据中学习模式,然后必须预测不同月份和位置的天气分数:

def train(self, historical_data: Dict):
        X = np.array([[d['month'], d['latitude'], d['longitude']] for d in historical_data])
        y = np.array([d['weather_score'] for d in historical_data])
        self.model.fit(X, y)

如前所述,此模型不是预先训练的(即,它不用于推理)而是在现场训练的。为此,我们类中有一个train方法。随机森林使用月份、纬度和经度来学习预测一个weather_score值(表示该月天气好坏的数值分数)。在这个片段中,数据被处理,模型被训练。

在这一点上,我们可以使用predict_best_time作为方法,根据训练的天气模型预测最佳访问月份。在这种情况下,该方法只接受两个输入(位置的纬度和经度)并返回其预测:

def predict_best_time(self, location: Dict) -> Dict:
        # Predicts the best time to visit a location based on weather patterns
        predictions = []
        for month in range(1, 13):
            # predict returns a 2D array, we take the first (and only) element
            prediction = self.model.predict([[
                month,
                location['latitude'],
                location['longitude']
            ]]).item()  # .item() converts numpy array to scalar
            predictions.append({'month': month, 'score': float(prediction)})
        return {
            'best_months': sorted(predictions, key=lambda x: x['score'], reverse=True)[:3],
            'location': location
        }

注意,我们初始化预测,它将包含 12 个月的所有分数(实际上,预测是通过循环所有 12 个月进行的,从一月到十二月)。最后,我们将列表从最好到最坏排序,以确定最佳访问月份。然后,该方法返回具有最高预测天气分数的前三个月份的列表。

HotelRecommenderAgent是一个酒店推荐系统,它利用语义相似性将酒店与用户偏好相匹配,并使用自然语言处理(NLP)来理解和比较酒店描述和用户偏好:

class HotelRecommenderAgent:
    def __init__(self):
        self.encoder = SentenceTransformer('all-MiniLM-L6-v2')
        self.hotels_db = []
        self.hotels_embeddings = None

在代理初始化期间,加载了all-MiniLM-L6-v2(一个为语义相似性设计的预训练 NLP 模型)。这个模型是一个嵌入器(如第五章中所述),将文本(酒店描述和用户偏好)转换为向量嵌入(多维空间中的数值表示)。一旦我们有了向量,我们就可以测量两个向量之间的相似度(用户偏好和酒店描述)。代理检索可用的酒店(self.hotels_db)并可以存储所有酒店描述的预计算嵌入(数值向量)。

接下来,在下面的代码片段中,我们有一个add_hotels函数,它将酒店添加到数据库中,并计算描述的嵌入,然后将它添加到我们的嵌入数据库中。find_hotels函数通过语义相似性找到符合用户偏好的酒店:

def add_hotels(self, hotels: List[Dict]):
        self.hotels_db = hotels
        descriptions = [h['description'] for h in hotels]
        self.hotels_embeddings = self.encoder.encode(descriptions)
    def find_hotels(self, preferences: str, top_k: int = 5) -> List[Dict]:
        pref_embedding = self.encoder.encode([preferences])
        similarities = np.dot(self.hotels_embeddings, pref_embedding.T).flatten()
        top_indices = similarities.argsort()[-top_k:][::-1]
        return [
            {**self.hotels_db[i], 'similarity_score': float(similarities[i])}
            for i in top_indices
        ]

发生的情况是我们对用户的偏好进行嵌入,然后计算与所有存储的酒店向量的余弦相似度。在这种情况下,我们选择与我们的酒店描述最接近的五个酒店(top_k=5表示选择前五个酒店)。

ItineraryPlannerAgent负责根据目的地信息(城市或景点)、天气预测(最佳访问月份)、酒店推荐(选择的住宿)和旅行时长(天数)自动生成旅行行程。它使用自然语言模型(GPT-2)根据这些输入生成定制的旅行行程:

class ItineraryPlannerAgent:
    def __init__(self):
        # Uses a language model for generating itineraries
        self.planner = pipeline(
            "text-generation",
            model="gpt2",  # In production, use a more powerful model
            max_length=500,
            truncation=True,
            pad_token_id=50256
        )

代理使用 Hugging Face transformers 库初始化一个 NLP 模型(GPT-2 模型,这是一个用于文本生成的预训练语言模型)。我们选择一个专注于文本生成的管道("text-generation"表示模型将根据提示生成文本)。其他参数意味着我们将生成的文本限制在 500 个标记以内(max_length=500)并确保截断。

由于我们通过提示与 LLMs 交互,我们有一个方法允许我们创建一个结构化的提示,然后我们将使用这个提示与模型交互。这个提示被设计成能够生成一个旅行计划,其中它输入一些特定的信息:停留时长(持续时间)、目的地、天气信息(我们之前确定的最佳月份)、酒店选择(由之前的代理识别),以及一个景点列表:

def _create_prompt(self, destination_info: Dict, weather_info: Dict,
                   hotel_info: Dict, duration: int) -> str:
    return f"""Create a {duration}-day itinerary for {destination_info['name']}.
Weather: {weather_info['best_months'][0]['month']} is the best month.
Hotel: Staying at {hotel_info[0]['name']}.
Attractions: {', '.join(destination_info['attractions'])}."""

在这一点上,我们可以创建行程;create_itinerary 方法精确地接受包含我们所需所有信息的先前提示(目的地、天气、酒店选择和旅行时长)。在 create_itinerary 方法内部有一个名为 _create_prompt 的方法来生成提示。GPT-2 模型接受输入提示并生成详细的行程:

def create_itinerary(self, destination_info: Dict, weather_info: Dict,
                     hotel_info: Dict, duration: int) -> Dict:
    prompt = self._create_prompt(destination_info, weather_info, hotel_info, duration)
    #Generate the itinerary
    response = self.planner(prompt)[0]['generated_text']
    return {
        'itinerary': response,
        'duration': duration,
        'destination': destination_info['name']
    }

最后一个代理,即 SummaryAgent,负责总结旅行详情,计算总估算成本,并使用 GPT-2 为客户生成个性化电子邮件。我们的代理初始化与之前的代理类似;唯一的区别在于,在这种情况下,生成长度更大(max_length=1000):

class SummaryAgent:
    def __init__(self):
        # In production, use a more powerful LLM like GPT-4 or Claude
        self.llm = pipeline(
            "text-generation",
            model="gpt2",
            max_length=1000,
            truncation=True,
            pad_token_id=50256
        )

calculate_total_price 是代理用来计算旅行总成本的工具(记住,LLMs 在算术方面并不擅长,因此最好使用外部工具):

def calculate_total_price(self, hotel_info: Dict, duration: int) -> float:
        # Calculate total trip price
        hotel_cost = hotel_info[0]['price'] * duration
        # Estimate additional costs (activities, meals, transport)
        daily_expenses = 100  # Simplified example
        additional_costs = daily_expenses * duration
        return hotel_cost + additional_costs

代理执行一系列非常简单的计算:

  • 每晚的酒店价格乘以停留时间

  • 使用固定的每日费用 $100 来估算餐饮、交通、活动和观光门票的成本

  • 将酒店和额外费用添加以返回最终估算

create_email 允许您创建将发送给客户的电子邮件摘要:

def create_email(self, trip_data: Dict, client_name: str) -> Dict:
        total_price = self.calculate_total_price(
            trip_data['recommended_hotels'],
            trip_data['itinerary']['duration']
        )
        prompt = f"""
        Dear {client_name},
        Based on your preferences, I'm pleased to present your travel plan:
        Destination: {trip_data['itinerary']['destination']}
        Duration: {trip_data['itinerary']['duration']} days
        Best time to visit: Month {trip_data['weather_analysis']['best_months'][0]['month']}
        Recommended Hotel: {trip_data['recommended_hotels'][0]['name']}
        Itinerary Overview:
        {trip_data['itinerary']['itinerary']}
        Estimated Total Cost: ${total_price}
        Please let me know if you would like any adjustments.
        """
        # Generate email using LLM
        response = self.llm(prompt)[0]['generated_text']
        return {
            'email_content': response,
            'total_price': total_price,
            'summary_data': {
                'destination': trip_data['itinerary']['destination'],
                'duration': trip_data['itinerary']['duration'],
                'hotel': trip_data['recommended_hotels'][0]['name'],
                'best_month': trip_data['weather_analysis']['best_months'][0]['month']
            }
        }

如我们所见,电子邮件将被结构化以包含成本(我们使用之前描述的方法)以及我们之前获得的其他信息。请注意,我们使用了一个模板。

记住,TravelPlanningSystem 是主控制器,它集成了所有 AI 代理以实现自动旅行规划:

class TravelPlanningSystem:
    def __init__(self):
        self.weather_agent = WeatherAnalysisAgent()
        self.hotel_agent = HotelRecommenderAgent()
        self.itinerary_agent = ItineraryPlannerAgent()
        self.summary_agent = SummaryAgent()

在第一步中,我们初始化我们的四个代理。每个代理将处理一个特定的任务。如果您注意到了,我们使用了一个模块化系统。其优势如下:

  • 每个组件独立运行,使系统可扩展

  • 组件可以更新或替换,而不会影响其他组件

  • 它遵循 单一职责原则SRP)以实现干净的代码架构

在这一点上,我们可以开始设置——获取最佳酒店和最佳访问月份:

def setup(self, historical_weather_data: Dict, hotels_database: List[Dict]):
        # Initialize and train the models
        self.weather_agent.train(historical_weather_data)
        self.hotel_agent.add_hotels(hotels_database)

最后,您必须协调整个旅行并生成包含成本估算和行程安排的总结电子邮件:

def plan_trip(self, destination: Dict, preferences: str, duration: int, client_name: str) -> Dict:
        # 1\. Weather analysis and best time prediction
        weather_analysis = self.weather_agent.predict_best_time(destination)
        # 2\. Hotel search
        recommended_hotels = self.hotel_agent.find_hotels(preferences)
        # 3\. Itinerary creation
        itinerary = self.itinerary_agent.create_itinerary(
            destination,
            weather_analysis,
            recommended_hotels,
            duration
        )
        # 4\. Create summary email and calculate price
        trip_data = {
            'weather_analysis': weather_analysis,
            'recommended_hotels': recommended_hotels,
            'itinerary': itinerary
        }
        summary = self.summary_agent.create_email(trip_data, client_name)
        return {
            **trip_data,
            'summary': summary
        }

现在我们已经创建了多代理平台,我们必须执行它。main() 函数作为运行 旅行规划系统 的入口点。它通过以下方式演示了系统的功能:

  1. 初始化样本数据(天气历史和酒店)

  2. 设置和训练 AI 模型

  3. 执行旅行规划过程

  4. 打印生成的旅行摘要

我们向系统提供了有关天气、目的地、酒店等方面的各种信息。之后,系统被初始化并执行。此时,它打印旅行摘要详情和由 GPT-2 生成的个性化电子邮件,并显示估计的总旅行成本:

def main():
    # Example data with a full year of weather information
    historical_weather_data = []
    # Sample hotel database
    hotels_database = []
    # Initialize the system
    system = TravelPlanningSystem()
    system.setup(historical_weather_data, hotels_database)
    # Plan a trip
    destination = {
        'name': 'Rome',
        'latitude': 41.9028,
        'longitude': 12.4964,
        'attractions': ['Colosseum', 'Vatican', 'Trevi Fountain']
    }
    preferences = """Looking for a luxury hotel in the city center,
    preferably with spa facilities and fine dining options"""
    client_name = "John Smith"
    # Generate trip plan
    trip_plan = system.plan_trip(destination, preferences, duration=3, client_name=client_name)
    # Print results in a readable format
    print("\nTRAVEL PLANNING RESULTS:")
    print("-" * 50)
    print(f"Client: {client_name}")
    print(f"Destination: {destination['name']}")
    print("\nGenerated Email:")
    print("-" * 20)
    print(trip_plan['summary']['email_content'])
    print("\nEstimated Total Price:")
    print(f"${trip_plan['summary']['total_price']}")

确保只有当脚本直接执行时,main() 脚本才运行:

if __name__ == "__main__":
    main()

到目前为止,我们只需要对其进行测试。一旦运行了脚本,结果应该是这样的:

https://github.com/OpenDocCN/freelearn-dl-zh/raw/master/docs/bd-ai-agt-llm-rag-kg/img/B21257_09_39.jpg

图 9.39 – 展示执行的截图

这个旅行规划系统是一个原型,展示了 AI 智能体如何协作来自动化现实世界的问题。

当然,可以对系统进行一系列改进,使其更有用:

  • 使用的数据是静态的(这是一个玩具示例)。你可以连接到多个 API 以获取实时数据,例如天气(OpenWeatherMap 或 AccuWeather)、酒店(Booking.com 或 Expedia API)和目的地(Google Places API 或 Yelp)。还可以添加扩展,如航班和交通(Google Flights API 或 Rome2Rio)。

  • GPT-2 已经过时了(我们使用它是因为它比其他模型小得多)并且没有针对旅行进行微调。你可以用更大的或针对旅行优化的模型替换 GPT-2。例如,你可以使用更大的模型,如 GPT-4 或 Claude,或者开源替代品,如 LLaMA。此外,开源模型可以在 Tripadvisor、Lonely Planet 或 Reddit 的真实旅行行程上进行微调。

  • 行程是通用的,不能适应不同类型的旅行者。你可以向旅行者询问不同的信息,例如预算偏好、他们喜欢什么样的活动(文化、冒险、美食、家庭友好型等),或者他们是否需要特殊住宿(轮椅、与老年人同行或宠物友好型)。这需要一个更大的模型,你也可以测试推荐模型。此外,还有实现多标准决策制定MCDM)的方法和模型,以进行更复杂的排名。

在任何情况下,尽管这个系统很简单,但它让我们看到了几个有趣的因素:

  • 与使用一个大型的单体 AI 模型不同,该系统被分解为专门的智能体。这个想法对于现代软件设计来说非常有用。

  • 这个简单的例子模拟了多智能体 AI 平台在自动驾驶汽车、金融、医疗保健和机器人技术中的工作方式。实际上,多智能体协作是一个旨在考虑可扩展性、模块化和效率的系统,这对于现实世界的应用是必要的。

  • 该系统可以动态生成个性化推荐(尽管在我们的案例中是硬编码的,我们模拟了用户输入偏好时发生的情况)。

  • 该系统还分析了多个因素(天气、酒店和景点)并优化旅行计划。类似地做这件事的现代系统使用精确的机器学习模型(在我们的例子中,我们使用了随机森林),拥有庞大的数据库(在我们的案例中,我们模拟了一个酒店数据库),考虑用户偏好,并使用自动化系统响应用户(我们的电子邮件)。

尽管这是一个非常简单的系统,但我们可以思考一个类似的系统如何在其他各种行业中得到应用:

  • AI 医疗助手推荐治疗方案、优化医院日程安排并预测疾病风险

  • 基于用户偏好和购买历史推荐产品的 AI 购物助手

  • 用于自动驾驶汽车的多智能体 AI 系统(导航、行人检测或交通优化)

  • 由人工智能驱动的顾问,帮助制定投资策略、风险管理以及欺诈检测

  • 一个由人工智能驱动的城市规划师,优化交通、能源使用和公共交通路线

在本节中,我们探讨了如何创建一个多智能体系统。在下一节中,我们将讨论多智能体系统如何适应今天存在或正在进一步发展的各种商业模式。这将提供一个重要的视角,因为它将使你能够了解如何调整你的多智能体平台以满足企业的需求。

软件即服务(SaaS)、移动即服务(MaaS)、数据即服务(DaaS)和机器人即服务(RaaS)

在本节中,我们将探讨受最近人工智能进步影响的多种商业模式。虽然多智能体 LLM 代表了尖端技术,但它们的价值在于能够适应满足商业需求,使它们能够有效地打包、营销并交付给企业和消费者。考虑到这些系统开发和维护成本极高,读者了解收入模式非常重要,这样他们就可以思考、设计和开发与公司战略一致的产品。理解这些模式使我们能够理解多智能体系统不是一个独立的项目,而应该被视为一个产品,并且这个产品可以通过多种方式来营销。此外,LLM 是极其昂贵的产品,并且每个商业模式在持续更新、可扩展性和人工智能部署的灵活性方面都有其优势和劣势。同时,这些商业模式规范了技术访问,无论你是想开发人工智能模型还是作为客户。在产品开发之前必须做出这些选择(关于平台、商业模式等),因为成本不允许试错。商业模式的选择由产品的结构和多智能体系统,以及公司的经济可行性所定义。

软件即服务(SaaS)

SaaS 是一种服务模式,其中软件由提供商托管在云端,并通过互联网向用户提供服务。在传统模式中,软件提供给用户安装和本地使用(在用户的设备上)。另一方面,SaaS 允许通过互联网访问,通常是通过网页浏览器或移动应用。通常,SaaS 是通过订阅而不是一次性购买来提供的。SaaS 范式始于 1999 年,当时 Salesforce 推出了其客户关系管理CRM)作为云托管服务。SaaS 现在是不同公司最广泛使用的销售范式,特别是对于企业对企业B2B)应用。其受欢迎程度正在增长,预计未来几年 SaaS 软件的收入将不断增长。

SaaS 应用通常是为了托管在云端而构建的(它们被称为云原生)。开发这些应用的公司可以决定是否在自己的基础设施上托管,或者利用云服务提供商的基础设施(例如 Google Cloud、IBM Cloud、OVH、Aruba、亚马逊网络服务AWS)和 Microsoft Azure)。鉴于对应用提供商的需求,一些提供商为托管这些应用创建了专门的基础设施,因此我们也谈论平台即服务PaaS)。

在 PaaS 解决方案中,提供商通过专用基础设施托管硬件和软件,这些基础设施可供产品开发者使用。这允许开发者专注于编码,而无需担心维护或管理其背后的基础设施。该平台允许托管应用程序和数据,甚至训练模型,只留下编码工作给开发者。这已经使许多企业能够加速产品开发,他们设法避免了投资昂贵的基础设施(尽管广泛使用这些平台可能成本高昂,尤其是在应用是生成式 AI 时)。尽管 PaaS 简化了流程,但开发者被迫使他们的应用程序符合平台和环境的要求。这并不总是可能的,导致部署或其他问题。因此,出现了一种新的范式,使用户具有更大的灵活性、控制和适应性,尤其是在应用程序或业务需要时。这种范式被称为基础设施即服务IaaS)并大约在 2010 年出现。在 IaaS 中,用户可以通过网络服务访问计算资源,从而能够根据需要租赁基础设施(服务器、网络和存储)。用户对基础设施的控制更多,而提供商则专注于硬件(例如 Google Compute Engine、DigitalOcean 和 Amazon Elastic Compute Cloud)。因此,PaaS 和 IaaS 可以被视为 SaaS 的扩展,或为需要支持生态系统的企业提供服务。

https://arxiv.org/pdf/2311.05804(img/B21257_09_40.jpg)

图 9.40 – 不同范式的比较(arxiv.org/pdf/2311.05804)

因此,SaaS 应用程序被设计为可以通过互联网连接从必须连接到互联网才能访问应用程序的设备(未连接的设备无法访问应用程序,并且允许本地访问不是必需的)进行访问。软件是开发用来通过网络浏览器或特定应用程序(移动软件)使用的。某些 SaaS 应用程序(例如 Adobe Acrobat)可能要求用户在他们的计算机上下载并安装一个专用的客户端(一个轻量级程序,不是完整的应用程序,需要安装在本地 PC 上),但这通常是少数情况。SaaS 应用程序通常是一个 多租户软件架构,其中软件应用程序的单个实例(包括其数据库和硬件)为不同的用户账户(或多个租户)提供服务。租户就是所谓的软件用户,它是一个组织内的单个用户或用户组。

在 SaaS 中,拥有一个确保每个租户的数据被隔离且对其他租户不可访问的架构至关重要。这种方法通过使软件能够针对单一硬件和基础设施进行优化,然后由所有用户共享,从而降低了成本。它还允许实现更大的可扩展性、更易于定制的维护(提供商可以在自己的基础设施和单一架构上自行进行更新)。

SaaS 因此成为最广泛使用的范式之一,因为它具有许多优势:

  • 成本效益:客户无需承担任何前期成本,例如硬件或软件许可的费用。在 SaaS 中,客户可以选择按订阅或按使用付费。

  • 可扩展性:SaaS 对客户来说易于扩展,且不需要额外的硬件。同样,软件的结构使得扩展客户变得容易。在人工智能模型的情况下,客户不需要大型硬件,可以直接利用提供商提供的硬件。

  • 可访问性:客户可以通过互联网连接从世界任何地方访问应用程序。此外,使用网络浏览器,软件针对客户拥有的任何硬件进行了优化。SaaS 还通过使用模板、API 和框架,降低了客户访问人工智能的门槛(资源较少且对专业知识的需求较低)。

  • 集成和定制的便捷性:从资源和时间方面来看,开发者提供更新、安全补丁和维护要容易得多。通常以更简单的方式为客户提供定制管理的能力,同时保持控制。同样,对于 AI 系统,可以提供更新的模板。

  • 快速部署:SaaS 通过在市场上立即可用来减少部署和市场接入时间。

  • 数据和模型共享:模型和数据的访问可以轻松地同时允许来自不同团队或不同地点的用户,并且高效且有效地进行。

当然,SaaS 也有一些限制和缺点:

  • 对互联网连接的依赖:SaaS 需要稳定的连接,连接中断可能会停止关键流程并引发错误。农村地区和基础设施薄弱的国家可能无法覆盖。

  • 定制有限:SaaS 解决方案是在一个产品中尽可能覆盖更多业务的想法下开发的。通常,它们提供有限的定制选项,可能无法满足特定企业的所有需求。在 AI 系统的案例中也是如此;客户对模型的控制很少,模型可能无法满足客户的要求。

  • 数据安全和隐私问题:托管在第三方服务器上带来了数据泄露或未经授权访问的风险。此外,可能与欧盟等国家的法规不符(例如,数据必须存储在特定国家的服务器上)。训练或使用 AI 模型可能需要共享敏感数据,这可能违反 GDPR 或其他法规(以及额外的隐私风险)。

  • 供应商锁定:企业可能依赖于特定的 SaaS 提供商,然后由于成本和复杂性而无法迁移到其他平台。此外,不同的提供商可能会终止服务(或被收购),突然增加成本,或取消被认为至关重要的功能。随着时间的推移,SaaS 可能会变得昂贵,尤其是当基于订阅时(一些提供商随着用户数量的增加而收费更多)。

  • 性能问题:在多租户架构中共享资源可能导致高峰时段性能变慢。此外,可能会有意外的服务器停机或维护计划,这会损害业务(例如,如果维护在太平洋时间晚上进行,将干扰欧洲的业务时间),而客户对此无能为力。必须实时运行的 AI 系统可能存在延迟或性能问题(无论是在训练还是在推理)。此外,提供商可能不提供最先进的 AI,或者尚未实施(或者他们可能使用不适合客户需求的模型)。

  • 高计算成本:SaaS 对开发者来说有基础设施成本,在 AI 的情况下,这种成本可能更高(使用 GPU 或大存储成本)。其中一些服务对用户来说特别昂贵。

模型即服务 (MaaS)

MaaS 是随着大数据、AI 和 Web 3.0 的发展而产生的一种新范式。MaaS 是一种基于云计算的服务范式,为开发者和企业提供 AI 和 ML 模型以及相关的 IaaS。

MaaS 旨在简化那些既没有专业知识也没有基础设施来训练生成 AI 或一般模型的企业的 AI 访问。MaaS 通过简单的界面、API 或浏览器使用预训练的 ML 模型和算法。就像 SaaS 一样,模型的访问是通过互联网进行的(并且要求企业有互联网连接)。然后,提供商必须托管模型并允许开发者访问已训练的模型。开发者可以使用这些模型将 AI 功能添加到他们的系统和应用中。MaaS 通常是一个平台,用于托管在大量数据上训练或针对可能任务优化的模型。MaaS 减少了管理这些模型的复杂性(尤其是培训和部署),并允许开发者专注于使用模型或如何将它们集成到特定应用中。由于开发者不必从头开始训练这些模型,因此他们节省了时间和资源。因此,MaaS 在某些方面与 PaaS 和 IaaS 相似,但进行了一个额外的抽象级别,并专注于 AI 解决方案。从某种意义上说,MaaS 可以被视为 SaaS 和 PaaS 或 IaaS 之间的中间解决方案。它不仅提供了一种服务,还提供了一种基础设施,使定制产品的开发成为可能。

SaaS 和 MaaS 之间的另一个区别在于两种范式的底层架构。SaaS 侧重于应用程序(应用层),这些应用程序依赖于一个操作系统(无论是移动还是桌面应用程序)来运行,以及一个允许应用程序托管的层。在 MaaS 的情况下,架构侧重于需要特定框架来托管的模型。

https://arxiv.org/pdf/2311.05804(img/B21257_09_41.jpg)

图 9.41 – 传统技术与基于模型的技术堆栈的比较(arxiv.org/pdf/2311.05804)

在 MaaS 中,以下元素通常存在:

  • 云计算:MaaS 基于云上的基础设施,其中维护和部署了各种模型。这使得对模型的访问变得容易,并实现了更大的可扩展性。

  • 模型训练和优化:MaaS(模型即服务)提供商负责在大数据集上训练大型模型。他们还负责整个生态系统,以实现模型更有效的利用。例如,他们可以提供不同大小的模型,包括针对特定应用的量化或微调版本。

  • API 和开发工具:MaaS 提供商还提供 API 和工具,允许开发者轻松地将模型用于其应用程序。目的是允许轻松地将模型集成到其他应用程序和基础设施中。因此,API 作为端点,接收数据,并返回预测。

  • 监控和分析:迄今为止,越来越多的关注点是如何在生产环境中监控模型。MaaS 提供商通常提供一系列工具来监控模型性能、识别问题存在、集成反馈或改进资源分配。

  • 可扩展性、安全性和隐私性:MaaS 提供商通过允许客户同时管理多个用户(从而根据需要分配不同的带宽、计算能力或存储)来关注其系统的可扩展性。同时,今天对隐私和安全性的关注越来越多(尤其是在有更多监管的情况下)。平台通常有一系列工具,可以增加集成其模型的应用的隐私性和安全性。

Hugging Face 是一个 MaaS 提供商的例子。Hugging Face 为计算机视觉、NLP、音频、视频等领域提供数千个预训练模型(来自公司本身、其他公司或用户)。这些模型托管在其模型中心,可以通过 API 使用或本地安装。因此,不想下载模型的用户可以使用推理 API,而不需要拥有管理模型所需的基础设施(此 API 使用按使用付费系统)。没有专业知识或资源的开发者可以直接使用端点 API,将其应用程序中的 AI 模型直接集成。此外,Hugging Face 还提供了一个平台,用于托管和部署模型以及应用程序,扩展 MaaS 功能,并为希望使用自定义模型的客户提供灵活性。Hugging Face 还提供工具以提高模型的可扩展性,开源库以促进模型开发或集成(例如,Transformers、Datasets、Diffusers、句子嵌入等),以及提供论坛以促进用户交流、用户教育资源和其他服务。还有其他 MaaS 提供商,例如 Google AI(提供 NLP(自然语言 API)、视觉(视觉 API)、语音转文本、翻译或使用 Vertex AI 的自定义模型训练)和 AWS(提供语言、图像和文本(例如,AWS Comprehend、Rekognition 和 Translate)的预训练模型或自定义模型的基础设施)。

MaaS 在 AI 领域具有以下优势:

  • 简化的模型开发和部署:MaaS 降低了使用生成式人工智能的技术门槛。公司不需要对技术或不同算法有专长的开发者,因为大多数模型都是通过端点交付的。这使得公司能够专注于其产品的应用和模型集成。如果需要,MaaS 还简化了为应用微调模型的方法。与 SaaS 不同,MaaS 针对整个 AI 工作流程进行了定制,并提供用于部署、训练、管理和扩展模型的工具,从而为有兴趣使用人工智能的公司提供更好的支持。

  • 高性能和可扩展性:云计算的使用促进了系统扩展。实际上,使用人工智能可能需要高昂的成本和大量的资源(尤其是在使用大型语言模型时),而 MaaS 通过便于访问大型模型而无需不同企业承担初始进入成本,从而实现了更好的资源管理。通常,用户根据其需求支付消费费用并接收计算服务,从而实现更好的性能和可扩展性。由于 MaaS 针对人工智能工作负载进行了优化,因此当计算需求波动时,它可以轻松扩展(SaaS 通常侧重于分配可变数量的用户,但用户对计算的需求可能因模型的不同使用而不同)。

  • 共享知识和协作:MaaS 建立在收集大量数据集和训练大型模型的基础上。这些预训练模型随后可以被对将模型适应特定应用感兴趣的开发商微调。这意味着开发商需要收集的数据量更少,也不必从头开始训练大型模型。这节省了资源和成本(微调的计算成本远低于预训练)。此外,MaaS 允许标准化,从而降低了使用这些模型所需的技术知识,并允许轻松获取信息和教程。模型还可以在平台上由社区共享,这些平台上也交换信息和经验(这促进了协作环境并加速了新模型的发展)。

  • 商业支持:MaaS 采用灵活的支付模式,例如基于订阅的支付方式,您只需为当前消费付费。通常,这种解决方案对许多小型企业来说既经济又实惠。对提供商来说很方便,因为一旦他们选择了一种技术并将其集成到他们的产品中,用户就会保持忠诚。模型集成使企业能够以简单且经济的方式获得洞察力(用于预测或其他预测的模型、报告编写和可视化)。

  • 灵活性:MaaS 为大量应用程序提供模型,并允许企业集成大量潜在模型,提供广泛的灵活性(例如,自然语言处理、计算机视觉、时间序列等众多其他应用)。此外,开发者可以快速测试许多预训练模型,而无需更改设置(例如,Hugging Face 提供了数千个只需少量管道即可使用的模型)。同样,MaaS 提供商通常提供许多工具来简化 AI 生命周期(数据标注、数据格式集成、监控工具等),从训练到部署。

MaaS 是一种新的范式,生成式 AI 领域也在积极发展中,因此存在需要解决的一些挑战和可能的缺点:

  • 安全和隐私:通常,在模型训练过程中会传输大量数据,这可能会被拦截。此外,基于敏感数据训练的模型最终可能会输出敏感数据。这些模型也可能是在受版权保护的数据上训练的,而使用此类数据进行训练的立法并不完全明确。因此,坚持特别受监管行业的组织可能不会采用 MaaS。数据是这些模型的基础,但模型可能是基于低质量数据进行训练的,或者可能因为低质量数据而出现偏差。通常,没有关于这些模型训练数据的信息。在这些情况下,平台和这些模型的使用企业都可能面临罚款或其他监管。

  • 供应商锁定:MaaS 提供商使用专有工具和 API,这使得从一个提供商切换到另一个提供商变得困难(例如,更换提供商会复杂化模型集成或导出经过微调的模型)。这种困难可能会降低灵活性和创新,并使企业依赖于单一提供商。可能会有停机或服务中断,这会影响构建的应用程序。这也使得本地实验更加困难。

  • 有限的定制性:并非所有 MaaS(移动即服务)提供商都允许对预训练模型进行微调或修改。预训练模型可能不适合某些特定操作,或者企业可能需要控制超参数和基础设施。此外,MaaS 提供商可能会进行更改或计划更新,这可能会影响业务或不再允许其应用程序的一些核心功能。

  • 模型和结果的解释性:模型通常是一个黑盒,用户无法访问决策过程。特别是对于生成式 AI 模型,很难理解模型如何处理输入并得到输出。对于敏感应用,这可能会引起问题,特别是当模型产生幻觉或错误的输出时。此外,平台的缺乏透明度可能会影响诊断错误或了解如何纠正它们的能力。

  • 性能和成本:延迟指的是请求与相应响应之间经过的时间。模型的延迟取决于底层基础设施,在高峰使用期间可能会承受压力。在 MaaS 平台中的共享多租户环境在高峰使用时段可能会导致资源瓶颈。企业可能会遇到延迟显著增加的情况,使得他们的应用程序无法使用。MaaS 允许按需付费,但大规模训练或推理可能会迅速变得昂贵。

MaaS 对于许多企业来说仍然是一个不断发展的范式。例如,在医疗保健领域,由于存在大量数据,并且已经开发了许多模型,MaaS 可能会产生重大影响。这些模型可以在平台上提供,并在需要时由从业者或制药公司使用。显然,在医疗保健领域,数据安全和输出一致性至关重要(尤其是如果这些应用程序用于医院或其他医疗机构)。MaaS 在其他领域也在增长,例如金融、区块链和 Web 3.0。

https://github.com/OpenDocCN/freelearn-dl-zh/raw/master/docs/bd-ai-agt-llm-rag-kg/img/B21257_09_42.jpg

图 9.42 – MaaS 中各种行业的应用(arxiv.org/pdf/2311.05804

数据即服务(DaaS)

DaaS 是一种商业模式,无论用户地理位置或组织边界如何,都可以按需向用户提供数据。在 DaaS 中,数据存储在云端,客户可以通过向提供商支付订阅费来访问它(无论是否需要额外工具)。因此,DaaS 围绕数据是资产并且可以按需提供给用户的概念构建。这种访问可以通过平台、API 的使用或其他方式来实现。此外,提供商可以提供原始数据或经过标准化以供机器读取或准备好的数据。

人工智能模型以数据需求量大而闻名,获取高质量数据可能并不容易。因此,有一些参与者专注于收集难以获取的数据,然后将它们卖给其他参与者。例如,患者数据可能难以收集,一家公司可能会收集和处理数据,然后将数据卖给制药公司。或者,DaaS 允许公司创建新的商业模式,利用他们在正常运营期间收集的数据作为可以出售的资产。例如,一家收集了其用户数据的电信公司可以将匿名数据卖给零售商。这些数据通过安全门户出售,可以按访问次数收费或通过订阅收费。订阅通常是最受欢迎的方法,可以分为三种子类别:时间模型、基于数量的定价模型和按呼叫或数据类型付费的模型。

DaaS 提供商可能只是销售其收集的原始数据,但更常见的是,它还会对其进行处理,使其可以通过模型进行分析。一些 DaaS 提供商汇总不同的来源,进行处理,从而简化了客户的分析过程。实际上,这些数据的目的在于改善客户的业务流程和决策,或者允许客户训练他们的 AI 模型。

还可能存在双向性,其中提供商收集数据并将其协调以与自己的数据集成,然后再将其提供给客户端。通过将其与其他数据相关联,客户端可以从其自身数据中提取额外的价值。

DaaS 有一些优点:

  • 成本效益:DaaS 减少了客户构建和维护数据基础设施和团队的需求。由于其灵活性,它还降低了数据访问的成本。客户不需要存储数据;他们可以在需要时直接访问数据流。

  • 易于访问:按需提供数据允许实时访问,节省了获取数据信息的时间和专业知识。用户不需要了解数据及其背后的结构,但可以轻松学习如何使用它。此外,只要有互联网连接,客户端就可以始终访问数据。

  • 可扩展性:它易于扩展以适应不断增长的数据需求,而无需额外的基础设施投资。客户可以轻松选择他们需要或能够处理的数据工作量。

  • 集中式数据管理:DaaS 实现了一致和集中的数据存储,减少了数据的不一致性和冗余。这简化了数据治理并符合法规要求。

  • 专注于核心活动:DaaS 节省资源和时间,使企业能够专注于从数据中提取价值,而不是管理数据。此外,它还促进了不同团队成员和协作者的更好合作,他们可以访问相同的数据(以相同的格式)。

  • 与其他服务的集成:DaaS 使数据与业务中的其他服务集成变得容易,尤其是在分析平台、可视化工具和其他云服务方面。同样,它还促进了数据集的定期更新,并使用户能够访问最准确和最新的数据。

  • 数据质量:由于数据集中,数据质量往往得到改善。一旦这些数据经过测试,如果没有更新,就无需进一步测试。

DaaS 的缺点与其他与云计算相关的模型类似:

  • 数据安全和隐私风险:显然,数据在云上的位置可能意味着敏感和专有数据可以被第三方访问或面临泄露风险。提供商必须遵守日益严格的法规。保护基础设施的成本正在增加,数据盗版攻击也在上升。此外,尽管数据以匿名方式出售,但在某些情况下,有可能重建信息。

  • 对提供商的依赖性:DaaS 导致对外部提供商的临界数据产生依赖。提供商端的服务中断或故障会影响客户以及所有与访问此数据相关的服务。客户通常可以访问数据流,但不会下载数据,因此可能会被切断对其业务必要的数据。

  • 有限的定制性:DaaS 可能无法提供所需格式的数据或具有正确的粒度。提供商有提供对尽可能多的客户有用的数据的兴趣,但特定客户可能有不同的需求。不合适的格式使得将其集成到现有系统或自己的工作流程中变得更加复杂,需要承担适应系统或数据的成本。

  • 质量保证:在 DaaS 中,数据的准确性方面的质量至关重要,低质量的数据可能导致决策失误或相关服务中的错误。数据的质量、准确性和可靠性取决于提供商。因此,提供商必须确保数据的相关性、更新性和高质量。

  • 延迟和性能问题:通过互联网访问数据可能导致引入延迟(尤其是在连接不好或数据集非常大时)。此外,这种延迟如果数据流嵌入到其他服务中,可能会降低性能。

结果即服务(RaaS)

RaaS,或 OaaS,是近年来发展起来的一种新范式。RaaS 是一种商业模式,服务提供商提供特定的结果或成果,而不是提供工具、平台或原始数据。这种模式在数据分析、人工智能和自动化等领域引起了人们的关注。在 RaaS 中,提供商使用人工智能(包括 LLMs 和代理)为顾客提供个性化的见解。虽然提供商进行整个分析,但客户可以专注于业务洞察,无需专门的技术人员。一般来说,客户不是一次性支付服务费用,而是通过订阅定期接收分析。

由于客户越来越要求模型的价值(企业对模型获得的价值比对额外工具的兴趣更大),RaaS 专注于提供结果,而不是模型(或数据)。此外,客户正在寻找降低采用技术成本但保留其价值的方法,因此 RaaS 寻求将企业的初始成本降至最低。提供商专注于确定实现结果所需的技术或工具,而客户则说明他们的需求和需求。

RaaS 的目的是建立客户忠诚度,因此提供商有每个兴趣自动化分析过程。因此,可以设想 AI 代理成为这种商业模式的新核心组件。LLM 本身能够几乎瞬间生成可能的报告,从而为客户生成见解。这些报告可以使用 LLM 进行个性化,并提供针对客户的定制见解。添加工具和数据库既可添加定量组件,又可扩展 LLM 的功能。然后,代理允许自动和常规地完成任务。实际上,代理可以分析大量数据,并可以补充额外的模型。生成的报告(甚至演示)可用于做出明智的决策。

因此,RaaS 具有以下优势:

  • 以结果为导向的方法:企业只为结果(以及交付的价值)付费,而不是为工具、基础设施和专业知识付费。这降低了企业的风险,因为它对使用软件或进行分析没有责任。

  • 成本效益:对于客户来说,无需花费金钱来构建基础设施和专业知识。相反,服务提供商可以自动化流程并降低成本(对于小企业来说可能相当昂贵)。此外,客户可以以商定的价格采用订阅计划(附带的好处是结果导向的定价模型直接将成本与实现的结果相匹配),而服务提供商则获得稳定的月收入。

  • 关注核心竞争力:由于公司不必投资资源来构建和维护系统或管理流程,RaaS 提供了巨大的时间优势。这也允许企业实施新能力,只需从提供商那里执行。然后,客户可以专注于其核心竞争力,并将结果直接纳入其流程中。

  • 可扩展性、准确性和灵活性:该系统具有可扩展性和灵活性,因为提供商可以为不同的客户重复使用这项技术。由于他们的支付或声誉取决于服务的成功,因此提供商有动力提供高质量的结果。

RaaS 也可能存在一些劣势:

  • 控制权丧失:客户对如何实现这些结果的控制有限。他们无法跟踪过程或诊断过程中出现的潜在问题。此外,还可能存在客户可能没有注意到的合规性、质量或道德实践方面的潜在问题。总的来说,RaaS 不促进透明度,它依赖于客户对提供商的信任。

  • 对提供商的依赖:对于客户来说,RaaS 意味着对服务提供商的高度依赖,这可能导致供应商锁定、更换供应商困难或更换供应商成本高昂。任何提供商方面的失败或低效都会直接影响客户运营。在这些情况下,客户的选择有限。

  • 数据安全和隐私风险:敏感数据可能需要与服务提供商共享,从而产生隐私和安全方面的担忧。由于监管规定,企业可能无法共享这些数据,这可能导致潜在的安全漏洞和巨额罚款。同时,如果敏感数据被截获,企业可能会面临严重的声誉损害或罚款。因此,RaaS 服务提供商需要承担大量成本来维护系统安全、数据存储和连接。

  • 衡量结果复杂性:定义明确、可衡量的结果可能具有挑战性,尤其是在目标或分析复杂的情况下。客户和提供商之间的期望不一致可能导致关于是否实现结果的争议。这些争议可能变成昂贵的诉讼,并影响提供商的声誉。

  • 潜在的高成本:一方面,RaaS 可以降低前期成本,但从长远来看,这项服务对于企业来说可能会变得昂贵。此外,还可能产生进一步分析的成本,或者如果性能和目标不一致,也可能产生额外成本。

  • 有限的定制性:RaaS 解决方案可能由广泛的应用定义,可能无法满足企业特定的、细分的需求。服务提供商有自动化任务和创建对最多客户有用的解决方案的强烈兴趣。这意味着特定客户的需求可能需要额外成本,可能不会被解决,或者可能不被提供商充分理解。

  • 质量保证挑战:提供商有降低成本的兴趣;这是通过自动化和尝试实现适合所有客户的解决方案来实现的。提供商可能会为了快速实现结果而走捷径,这可能会损害长期价值。

在任何情况下,RaaS 都是一个不断发展的商业模式,尤其是在对 AI 和生成式 AI 的兴趣日益增长的情况下(许多企业希望整合 AI 服务,但既没有专业知识也没有基础设施来实现这一点)。许多公司只对模型的结果(如维护预测或患者的预后)感兴趣,而不是模型本身。许多企业会对结果进行定制,以满足他们的特定需求,而不需要开发整个流程。因此,随着竞争加剧,不同的提供商开始为不同类型的行业提供高度专业化的产品。这推动了创新,因为公司努力满足目前尚未满足的需求。随着提供的产品增多,客户的需求也将不断发展,使公司能够专注于改善其业务的关键要素。

不同范例的比较

我们可以将范例的选择总结如下:

  • SaaS:当提供商希望通过订阅提供稳定和可预测的收入流时,应选择 SaaS;他们的产品可扩展到大量客户(从而降低解决方案的成本),易于支持更新和维护,他们有利用云基础设施来最小化硬件成本的能力,可以保证频繁的软件改进,并确保客户忠诚度。当客户需要快速访问软件而不必投资硬件或维护时,应选择 SaaS;软件的灵活性和可扩展性至关重要,或者他们更愿意按订阅方式支付软件费用而不是进行大量前期投资。当客户更喜欢由外部提供商处理更新、维护和安全时,或者他们对远程可访问的应用程序感兴趣(例如,他们有分布在不同国家或不同地点的团队),SaaS 也是一个不错的选择。使用 SaaS 的例子包括 Salesforce(一个在各个行业广泛使用的基于云的 CRM 系统)、Microsoft 365(通过云订阅提供 Word、Excel 和 Teams 等生产力工具)、Adobe Creative Cloud(提供 Photoshop 和 Illustrator 等创意工具的访问权限,并持续进行云更新),以及 Slack(一个由分布式团队用于消息和协作的通信平台)。

  • MaaS: 当提供商能够与其他合作伙伴(或拥有稳固的基础设施)降低模型交付成本时,应考虑采用 MaaS;如果他们已经开发出高性能的 AI/ML 模型,这些模型可以服务于多个行业(例如,医疗保健、金融或零售),希望在不共享算法的情况下货币化开发出的模型或专业知识,并且能够安全可靠地保证模型访问,也应考虑 MaaS。当用户需要高级 AI/ML 模型但缺乏内部构建或训练的资源,或者更愿意外包模型维护、再培训和优化而不是内部管理时,应考虑这些解决方案。当成本效益和灵活性是优先考虑的因素时,特别是对于正在尝试 AI/ML 的初创公司和企业,以及当 AI/ML 驱动应用的上市时间至关重要时,也应考虑这些模型。使用 MaaS 的例子包括 OpenAI(通过 API 提供 GPT 模型的访问权限,用于文本生成或摘要等任务)、Google Cloud AI 平台(提供翻译、视觉、语音识别等模型)、AWS SageMaker JumpStart(允许企业快速部署预训练模型,用于欺诈检测等任务),以及 Hugging Face(通过其推理 API,提供数千个开源模型的托管访问权限)。

  • DaaS: 如果提供商能够访问高价值、独特的数据集,这些数据集可以惠及多个行业,他们希望利用对数据在决策和数据分析中日益增长的依赖性,他们希望为公司创造额外的商业机会(例如,销售随着时间的推移所获得的数据),他们能够确保符合数据保护法规(例如,GDPR 或 CCPA),他们拥有进行数据共享的基础设施,或者他们提供(或打算提供)超出原始数据的价值,例如见解、可视化或与工具的集成,应选择 DaaS。如果客户需要大量数据但不想投资于存储和处理基础设施,他们的业务依赖于外部或专业数据集(例如,市场数据、天气数据、地理位置数据、财务数据、医疗保健数据等),他们更喜欢访问不同数据集的灵活性和可扩展性,或者他们不想处理数据合规性、维护和安全,应考虑 DaaS。例子包括 Snowflake(一个云数据平台,允许组织之间安全地共享数据)、Quandl by Nasdaq(为分析师和机构提供金融、经济和替代数据)、Clearbit(提供 B2B 数据以丰富销售和营销)、以及 Copernicus 的气候数据存储(提供用于科学和商业用途的环境和气候数据集)。

  • RaaS:如果提供商拥有适当的基础设施来保证向客户提供可靠和可衡量的结果,并倾向于通过专注于提供价值和结果来区分自己,能够衡量性能并向客户保证结果,并且有专业知识来减轻风险和保证性能,他们可能会考虑 RaaS。当客户希望实现特定结果而不需要管理底层流程、基础设施或技术;当他们的重点是结果(例如,性能改进或运营效率)而不是工具或输入;当他们希望通过仅支付成功的成果或结果来最小化风险;当他们缺乏实现某些复杂和特定结果的专业知识;或者当他们希望降低成本并在一段时间内分散成本时,他们应该选择 RaaS。使用 RaaS 的公司的例子包括 Pymetrics(基于神经科学和 AI 提供招聘建议,而不暴露内部机制)、Afiniti(使用 AI 优化呼叫中心配对并基于改进的性能收费)、Uptake(在工业环境中提供与正常运行时间或效率提升相关的预测性维护)和 ZS Associates(在医疗保健和制药领域提供基于 KPI 和性能改进的分析驱动解决方案)。

下表提供了每个范例对提供商和用户的优缺点总结:

类别SaaSMaaSDaaSRaaS
**优点(**提供商)- 可重复收入模式。- 可扩展的基础设施。- 更容易的软件更新。- 成本效益的开发生命周期。- 使 AI/ML 模型货币化。- 可扩展的计算资源分配。- 支持医疗保健和金融等各个行业。- 减少基础设施需求(例如,云托管 ML 模型)。- 扩展到利基 AI/ML 应用的机会。- 数据货币化机会。- 数据集中管理。- 可预测的收入。- 利用现有数据集的能力。- 为不同行业提供服务时的灵活性。- 稳定且可预测的收入流。- 鼓励基于结果的定价。- 在竞争市场中区分产品。- 使提供商能够专注于交付结果而不是销售产品。- 提高客户保留率。
**优点(**用户)- 低前期成本。- 容易访问最新的软件版本。- 从任何地方都可以访问。- 订阅的灵活性以适应业务需求。- 无需构建或训练即可访问高级模型。- 可扩展的计算能力以高效处理模型。- 使用模型进行预测或自动化的灵活性。- 通过避免构建内部 AI/ML 基础设施节省成本- 使 AI 应用更快上市。- 容易且快速访问经过策划、可用的数据。- 数据系统的拥有成本较低。- 消除了需要大型数据存储/处理基础设施的需求。- 可伸缩性。- 基于结果的支付降低了风险。- 专注于结果,无需担心底层基础设施。- 可预测的性能和价值。- 无需进行大量初始投资。- 在专家支持下简化实现预期结果。
**缺点(**提供者)- 竞争激烈和客户流失。- 基础设施和更新的持续成本。- 与区域法规和合规性方面的挑战。- 模型的初始开发成本高。- 确保 AI/ML 模型中的公平性、可靠性和合规性具有挑战性。- 管理模型在多种用例中的性能预期。- 资源密集型的模型更新和再训练。- 数据使用中的隐私/安全问题。- 实时数据交付的基础设施。- 需要遵守复杂的数据法规(例如,GDPR)。- 收入取决于结果的成功交付。- 为性能保证而支付的高前期成本。- 测量和问责制指标复杂。- 如果结果难以交付或期望不一致,则存在利润率降低的风险。
**缺点(**用户)- 依赖互联网连接。- 数据安全和隐私风险。- 长期成本可能超过直接购买软件。- 依赖第三方模型。- AI/ML 模型中可能存在偏差或错误。- 如果经常需要,可能会产生长期成本。- 有限的能力为高度特定的需求定制模型。- 在某些 AI/ML 应用中存在隐私问题。- 对数据所有权和供应商锁定问题的担忧。- 可能存在高额的长期成本。- 可能过度依赖第三方数据。- 敏感数据的安全风险。- 依赖于供应商以实现结果的成功。- 过程如何实现结果缺乏透明度。- 在合同期间修改结果的能力有限。- 可能不适合具有高度特定、非标准化需求的用户。- 如果结果定义不明确,成本可能会上升。

表 9.1 – 提供者和用户的优缺点

商业范式的选择非常重要。每个范式都会对用户和业务产生影响。找到正确的范式可以节省资源并增加收入。范式的选择会影响开发多智能体系统的技术选择。

摘要

在本章中,我们看到了如何将之前章节中讨论的工具添加到 LLM 中。我们了解到 LLM 具有规划和推理的能力,但在执行方面产生的结果较弱。LLM 能够生成文本,但与此同时,学习到的海量信息也允许它发展超越文本生成的技能。虽然要求 LLM 对图像进行分类是一种计算上的浪费,但 LLM 可以使用专门的模型来完成任务。正如我们通过 HuggingGPT 所看到的,一个模型可以调用其他模型来识别图像中的披萨。在这种情况下,我们看到了 LLM 调用多个模型,收集它们的输出,并对结果进行推理(注意,模型在图像中披萨的类型上达成一致)。然后 LLM 可以进行推理,选择需要运行的模型,收集输出,并观察任务是否完成。

这个概念使得彻底改变各种工业应用成为可能。例如,客户可以通过电子邮件请求更换商品,因为他们购买的商品尺寸太小。大型语言模型(LLM)能够理解投诉,制定计划并执行它。该模型可以使用工具来验证购买,另一个工具来查看是否备有所需尺寸的商品,软件来安排发货,一旦订单完成,就向客户回复他们的请求已经得到满足。因此,代理能够自动化各种任务,因为它们允许 LLM 使用完成任务所需的其它工具。正如我们所看到的,这种方法可以扩展到许多其他应用:法律领域的代理,化学和生物学研究中的代理,等等。例如,人工智能代理可以作为法律助理帮助撰写论文,协助教授创建讲座,或者帮助研究人员定义科学假设。

虽然这些看起来像是高级场景,但必须理解 LLMs 在推理方面的局限性,并且目前它们可以自动化简单任务,但还不能满足复杂的商业需求。为此,需要有人工监督,并且开发者需要意识到系统的局限性。此外,LLMs 消耗资源,这些系统可能计算成本高昂。可扩展性是想要采用代理的企业面临的主要问题之一。因此,在本章的最后部分,我们讨论了随着 LLMs 的到来而开放的多种商业范式。SaaS 是过去三十年中主导的经典范式;它是在互联网革命期间构思的,但在 AI 作为大众产品到来之前。DaaS 专注于 AI 和企业在做出明智决策时对高质量数据的需求。MaaS 致力于那些想要提供 ML 和 AI 模型的公司,而 RaaS 则仅关注这些模型的输出。SaaS 和这些范式之间存在明显的相似之处,但它们考虑了两个因素:AI 模型需要基础设施和资源来训练和使用,而开发和维护这些模型需要相当的专业知识。因此,MaaS 和 RaaS 允许企业减少在基础设施、培训和专业知识方面的初始投资。根据他们的需求和资源,提供者或客户的选择是不同的,因此我们提供了一张比较表和一些指导方针。

因此,在本章中,我们定义了在实践中什么是代理(或对于多代理平台而言,一组代理)以及这些代理如何被整合到商业活动中。换句话说,我们定义了一个基于代理的系统。这个系统不是一个孤立的实体;在下一章中,我们将关注代理周围的生态系统以及代理如何融入其中。

进一步阅读

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值