人工智能密集型-Python-应用构建指南-全-
人工智能密集型 Python 应用构建指南(全)
原文:
zh.annas-archive.org/md5/7267c174a783a618ea95a0881f58bfe1译者:飞龙
前言
构建密集型Python AI应用是使用Python开发智能应用的全面指南。它探讨了大型语言模型(LLMs)和向量数据库之间的协同作用,这两种尖端技术是创新AI解决方案的驱动力。通过掌握这些工具,你将能够设计、实施和优化复杂的AI应用。
本书对生成式AI(GenAI)进行了彻底的探索,详细介绍了智能应用的理论概念和核心组件。通过代码片段、实际用例和专家建议,本书提供了使用Python设计AI/ML应用的实用指导。本书中涵盖的评估、改进和优化AI解决方案的策略可以帮助开发者创建满足现实世界需求的稳健且准确的AI应用。
本书面向的对象
这本书是为那些希望使用GenAI构建智能应用的软件工程师和开发者而写的。虽然适合初学者,但需要具备基本的Python编程知识。对MongoDB和OpenAI LLMs的工作知识是首选,但不是必需的。本书提供了构建AI应用的逐步方法,使其适合新手和经验丰富的从业者。
本书涵盖的内容
第1章,开始使用生成式AI,定义了与GenAI相关的关键术语,并介绍了AI/ML堆栈的组件。它还简要介绍了AI的演变、AI解决方案的益处、风险和伦理问题。
第2章,智能应用构建块,概述了智能应用的逻辑和技术构建块,探讨了定义智能应用的核心结构以及这些组件如何协同工作以创建动态、情境感知的体验。
第3章,大型语言模型,涵盖了现代基于转换器的LLM的主要组成部分,提供了对当前LLM景观的快速概述,并介绍了可以帮助你充分利用LLM的方法。
第4章,嵌入模型,对嵌入模型进行了深入探讨。它解释了不同类型的嵌入模型以及如何选择最适合你需求的一种。
第5章,向量数据库,通过详细阐述向量搜索的概念,探讨了向量数据库在AI应用中的力量,并分享了使用向量数据库增强用户体验的案例研究和最佳实践。
第6章,AI/ML应用设计,涵盖了设计AI/ML应用的关键方面。你将学习如何以安全高效的方式有效管理数据存储、流动、新鲜度和保留。
第7章,有用的框架、库和API,探讨了构建AI应用至关重要的框架、库和API生态系统,帮助您尝试一些适用于您自己的用例。
第8章,在AI应用中实现向量搜索,介绍了检索增强生成(RAG)增强AI能力的力量。它使用实际示例帮助您利用向量搜索的优势。
第9章,LLM输出评估,探讨了评估LLM输出质量的概念和方法。它讨论了各种评估技术和指标,以确保准确、连贯和相关的输出。
第10章,精炼语义数据模型以提高准确性,探讨了精炼您的语义数据模型以改进RAG应用中向量搜索的检索准确性的策略,并确保更好的输出。
第11章,生成式AI的常见失败,深入探讨了AI系统的常见陷阱,并提供了克服它们的策略,探讨了诸如幻觉、数据泄露、成本优化和性能瓶颈等问题。
第12章,纠正和优化您的生成式AI应用,讨论了增强生成式AI应用性能的几种技术,详细介绍了每种技术,并通过实际示例进行解释。
为了充分利用本书
您将需要以下软件:
| 本书涵盖的软件 | 操作系统要求 |
|---|---|
| MongoDB云账户 | Windows、macOS或Linux |
| OpenAI API密钥 | Windows、macOS或Linux |
| Jupyter Notebook | Windows、macOS或Linux |
| Python 3.10或更高版本 | Windows、macOS或Linux |
阅读本书后,我们鼓励您查看https://www.mongodb.com/developer或https://learn.mongodb.com/提供的其他资源。
如果您使用的是本书的数字版,我们建议您亲自输入代码或从本书的GitHub仓库(下一节中提供链接)获取代码。这样做将帮助您避免与代码复制和粘贴相关的任何潜在错误。
下载示例代码文件
您可以从GitHub下载本书的示例代码文件:https://github.com/PacktPublishing/Building-AI-Intensive-Python-Applications。如果代码有更新,它将在GitHub仓库中更新。
我们还有其他来自我们丰富的图书和视频目录的代码包,可在https://github.com/PacktPublishing/找到。查看它们吧!
使用的约定
在本书中使用了多种文本约定。
文本中的代码:表示文本中的代码单词、数据库表名、文件夹名、文件名、文件扩展名、路径名、虚拟URL、用户输入和Twitter/X用户名。以下是一个示例:“在这个示例中,您将创建一个名为langchain_db的数据库和一个名为test的集合。”
代码块应如下设置:
# Connect to your Atlas cluster
client = MongoClient(ATLAS_CONNECTION_STRING)
任何命令行输入或输出都应如下所示:
pip3 install prettytable==3.10.2 sacrebleu==2.4.2 rouge-score==0.1.2
粗体:表示新术语、重要单词或您在屏幕上看到的单词。例如,菜单或对话框中的单词以粗体显示。以下是一个示例:“切换搜索节点以实现工作负载隔离的单选按钮以启用。”
小贴士或重要注意事项
它看起来像这样。
联系我们
我们始终欢迎读者的反馈。
一般反馈:如果您对本书的任何方面有疑问,请通过电子邮件发送给我们 customercare@packtpub.com,并在邮件主题中提及书名。
勘误:尽管我们已经尽一切努力确保内容的准确性,但错误仍然可能发生。如果您在这本书中发现了错误,我们将非常感激您能向我们报告。请访问 www.packtpub.com/support/errata 并填写表格。
盗版:如果您在互联网上以任何形式遇到我们作品的非法副本,我们将非常感激您能提供位置地址或网站名称。请通过电子邮件发送给我们 copyright@packt.com 并附上材料的链接。
如果您有兴趣成为作者:如果您在某个主题上具有专业知识,并且您有兴趣撰写或为本书做出贡献,请访问 authors.packtpub.com。
下载本书的免费PDF副本
感谢您购买本书!
您喜欢在移动中阅读,但又无法携带您的印刷书籍到处走吗?
您的电子书购买是否与您选择的设备不兼容?
不要担心,现在,每购买一本Packt书籍,您都可以免费获得该书的DRM免费PDF版本。
在任何地方、任何地点、任何设备上阅读。直接从您最喜欢的技术书籍中搜索、复制和粘贴代码到您的应用程序中。
优惠不会就此结束,您还可以获得独家折扣、时事通讯和每日收件箱中的精彩免费内容。
按照以下简单步骤获取福利:
- 扫描下面的二维码或访问以下链接

https://packt.link/free-ebook/9781836207252
-
提交您的购买证明
-
就这样!我们将直接将您的免费PDF和其他福利发送到您的邮箱。
第一章:开始使用生成式人工智能
构建生成式人工智能应用程序有大量的选择。坦白说,这个领域非常复杂,许多满足一个标准的工具可能在另一个标准上不足。生成式人工智能应用程序发展如此迅速,以至于本书出版几周后,一些新的AI公司可能已经不存在了。因此,本章重点介绍与创建生成式人工智能应用程序所使用的技术相关的长期、高级概念。
您将学习到您的下一个Web开发项目可能从中受益的方法。本章不仅将探讨这些方法是什么,还将探讨它们是如何工作的,这将使您对生成式人工智能有更广泛的理解和视角。这应该有助于您决定何时以及如何使用生成式人工智能,以及使您创建的应用程序更加准确。
到本章结束时,您将很好地理解单个AI/ML堆栈组件为开发项目带来的好处,它们之间的关系,以及为什么生成式人工智能技术是软件革命——无论是在处理数据还是所需功能方面。
本章介绍了生成式人工智能,并快速概述了以下主题:
-
常见术语的定义
-
您选择的生成式人工智能堆栈
-
Python和生成式人工智能
-
OpenAI API
-
MongoDB向量搜索简介
-
生成式人工智能的重要特性
-
为什么使用生成式人工智能?
-
生成式人工智能的伦理和风险
技术要求
这本书包含了一个基本Python应用程序的示例代码。为了重新创建它,建议您具备以下条件:
-
Python的最新版本
-
在您的设备上为您的应用程序服务器设置本地开发环境
-
一个MongoDB Atlas云账户来托管您的数据库。您可以在https://www.mongodb.com/cloud/atlas/register注册一个账户。
-
VS Code或您选择的IDE
-
OpenAI API密钥
定义术语
对于真正的初学者,让我们从定义一些关键术语开始:AI、ML和GenAI。您将在本书中反复遇到这些术语,因此对这些术语有一个坚实的概念基础是有帮助的:
- 人工智能(AI)指的是机器执行通常需要人类智能的任务的能力。这包括感知、推理、学习和决策等任务。人工智能的发展历程从早期的推测性想法到今天复杂的技术已经发生了显著变化。图1.1展示了人工智能的发展时间线。

图1.1:人工智能的时间线
-
机器学习(ML)是AI的一个子集,涉及使用算法自动从数据中学习并在时间上不断改进。本质上,这是一种让机器在没有明确编程的情况下学习和适应的方法。ML最常用于需要分析数千个数据点的领域,在医疗诊断、市场分析和军事情报中最为有用。实际上,ML能够识别人类难以看到的数据中的隐藏或复杂模式,然后可以对下一步或行动提出建议。
-
生成式AI(GenAI)是指根据用户提示创建文本、图像、音频、视频和其他内容的能力。它为聊天机器人、虚拟助手、语言翻译和其他类似服务提供动力。这些系统使用在大量数据上训练的算法,例如来自互联网的文本和图像,来学习模式和关系。这使得它们能够生成与底层训练数据相似但不完全相同的新内容。例如,大型语言模型(LLMs)使用训练数据来学习书面语言中的模式。GenAI可以使用这些模型来模仿人类的写作风格。
生成式AI堆栈
堆栈结合工具、库、软件和解决方案,以创建统一和集成的方法。GenAI堆栈包括编程语言、LLM提供商、框架、数据库和部署解决方案。尽管GenAI堆栈相对较新,但它已经有许多变体和选项供工程师选择。
让我们讨论构建功能性的GenAI应用程序所需的内容。基本要求如下,如图1.2所示:
-
操作系统:通常,这是基于Unix/Linux的。
-
一个存储层:一个SQL或NoSQL数据库。本书使用MongoDB。
-
一个能够存储嵌入的向量数据库:本书使用MongoDB,它将嵌入存储在您的数据或内容中,而不是单独的数据库中。
-
一个网络服务器:Apache和Nginx相当受欢迎。
-
开发环境:这可能是Node.js/JavaScript、.NET、Java或Python。本书在所有示例中使用Python,并在需要时加入一些JavaScript。

图1.2:基本的GenAI堆栈
如果你想了解更多关于AI堆栈的信息,你可以在www.mongodb.com/resources/basics/ai-stack找到详细的信息。
Python和GenAI
Python是由Guido van Rossum在20世纪80年代末构思的,并于1991年正式发布。几十年来,Python已经发展成为一个多才多艺的语言,开发者们喜爱它简洁的语法和强大的功能。它拥有易于理解的简洁语法,使其成为初学者开发者的理想选择。
尽管原因并不完全清楚,但相当早的时候,Python生态系统开始引入更多针对机器学习(ML)和数据科学定制的库和框架。TensorFlow、Keras、PyTorch和scikit-learn等库和框架为这些领域的开发者提供了强大的工具。对于技术程度较低的分析师来说,使用Python相对容易上手。由于其互操作性,Python可以无缝集成到其他编程语言和技术中,这使得它与数据管道和Web应用的集成更加容易。
GenAI,由于其对高计算能力和复杂算法的需求,在Python中找到了完美的伙伴。以下是一些容易想到的例子:
-
Pandas和NumPy等库允许高效地操作和分析大数据集,这是训练生成模型的基本步骤。
-
TensorFlow和PyTorch等框架提供预构建组件,用于设计和训练复杂的神经网络。
-
Matplotlib和Seaborn等工具能够详细可视化数据和模型输出,有助于理解和优化AI模型。
-
Flask和FastAPI等框架使得将你的GenAI模型部署为可扩展的Web服务变得简单直接。
Python拥有一个易于使用且功能丰富的生态系统,让你可以快速开始,使其成为生成式人工智能(GenAI)项目的理想编程语言。现在,让我们更深入地探讨本书剩余部分你将使用的其他技术组件。
OpenAI API
本书最重要的工具是OpenAI API。在接下来的章节中,你将了解GenAI堆栈的每个组件——其中最重要的是OpenAI。虽然我们将涵盖其他大型语言模型(LLM)提供商,但我们在示例和代码库中使用的将是OpenAI。
OpenAI API,于2020年中旬推出,为开发者提供访问其强大模型的途径,使得将高级自然语言处理(NLP)功能集成到应用中成为可能。通过这个API,开发者可以访问到一些现存的最先进的人工智能模型,例如GPT-4。这些模型在庞大的数据集上进行了训练,并在自然语言理解和响应生成方面拥有无与伦比的能力。
此外,OpenAI的基础设施是构建来扩展的。随着你的项目增长并需要更多的计算能力,OpenAI确保你可以轻松扩展,无需担心底层硬件或系统架构。OpenAI的模型在自然语言处理任务上表现出色,包括文本生成、摘要、翻译和情感分析。这对于创建内容、聊天机器人、虚拟助手等非常有价值。
来自互联网、内部对话和文档的大量数据是非结构化的。OpenAI作为一家公司,已经使用这些数据来训练一个LLM,然后将其作为服务提供,这使得你能够在不托管或训练自己的LLM的情况下创建交互式生成式AI应用。你将在第3章、“大型语言模型”中了解更多关于LLM的内容。
MongoDB与向量搜索
关于MongoDB如何服务于非结构化数据的使用案例已经有很多讨论,但世界上的数据本质上都是关系型的。可以争论说,没有数据在人类认为它有意义之前是有意义的,而且那些数据的关系和结构也是由人类决定的。例如,几年前,一家领先的空间探索公司的研究员在一次会议上发表了以下令人难忘的评论:
“我们从网站和PDF文档中抓取了文本内容,并意识到试图将那些数据强行塞入 表格 *中并没有太多意义。”
MongoDB能够适应现实世界中杂乱无章、非结构化的内容——如.txt文件、Markdown、PDFs、HTML等。MongoDB足够灵活,可以拥有工程师认为最适合目的的结构,正因为这种灵活性,它非常适合用于生成式AI的应用场景。
因此,使用文档数据库进行生成式AI比使用SQL数据库要容易得多。
使用MongoDB的另一个原因是其向量搜索功能。向量搜索确保当你将短语存储在MongoDB中时,它会将该数据转换为数组。这被称为向量。向量是数据及其上下文的数值表示,如图图1.3所示。这些维度的数量被称为嵌入,你拥有的越多,效果越好。

图1.3:向量的示例
在为数据创建嵌入后,一个数学过程将识别哪些向量彼此最接近或最近,然后你可以推断出数据是相关的。这允许你返回相关词汇,而不仅仅是精确匹配。例如,如果你在寻找pets,你可能会找到cats、dogs、parakeets和hamsters——即使这些术语并不是精确的单词pets。向量是允许你接收在意义或上下文中相关或相似的结果,而不需要精确匹配的东西。
MongoDB将你的数据嵌入与数据本身一起存储。将嵌入存储在一起使得后续查询更快。通过一个带有工作原理解释的示例,你可以最容易地可视化向量搜索。你将在第8章、“在AI应用中实现向量搜索”中了解更多关于向量搜索的内容。
生成式AI的重要特性
当被要求列出GenAI应用最重要的功能时,ChatGPT——可以说是目前最流行的GenAI应用,提出了以下观点:
Content Creation: Generative AI can craft text, images, music, and even videos. It can pen articles, generate realistic images from textual descriptions, compose music, and create video content, opening endless possibilities for creative industries.
该响应生成耗时1.5秒,大多数人都会同意这一点。GenAI应用可以以闪电般的速度为你和你的用户创建内容。无论是文本、视频、图像、艺术品,甚至是Java代码,GenAI都能够轻松起草基础内容,然后由专业人士进行编辑。
但还有其他GenAI应用的关键特性也值得指出:
-
语言翻译:GenAI具有非凡的熟练度,能够实时翻译语言,保留语境和细微差别,促进跨语言障碍的无缝沟通。
-
个性化:在营销和客户服务领域,GenAI可以根据个人用户定制体验和内容。在提供适当语境的情况下,它可以通过分析偏好和行为来提供个性化的推荐、电子邮件和客户互动。
-
模拟和建模:在科学研究和工程领域,GenAI可以模拟复杂系统和现象。它通过基于大量数据集生成逼真的模型,有助于预测分子行为、气候模式和甚至经济趋势。
-
数据增强:对于机器学习,GenAI可以生成合成数据以增强训练集。在真实数据稀缺或存在偏差的情况下,这非常有价值,可以创建多样化的平衡数据集以提升模型性能。这对于测试目的尤其有用,尤其是在软件测试中。
也许最重要的是,它能够接受自然语言(如英语)的提示来完成这些任务。这使得完成你之前认为困难的任务变得极其简单。你可以在一天内使用GenAI完成多项和多样化的任务,例如审查拉取请求、指导你完成一些Golang任务,以及为书籍的内页插图生成插图。
为什么使用生成式AI?
上述每一种能力都令人信服且重要,当正确使用并组合在一起时,具有革命性。简单来说,没有哪个行业GenAI不能发挥作用。通过快速聚合和总结大量内容并简化搜索,GenAI改善了寻找想法和构建知识的使用体验。它可以帮助收集新信息,总结它,并将其重新构思为内容。它可以帮助加快或甚至自动化行政任务,并指数级提高产出。
但所有这些之外,使用GenAI的体验比今天可用的体验要好一个数量级。例如,考虑一下客户服务机器人。你们中的许多人可能熟悉以下流程:
-
客户首先会遇到一个长长的选项菜单:
如果你想要与销售或支持部门交谈,请按1。如果是账单问题,请按2。如果是管理问题,请按3。如果是订单问题,请按4。当客户遇到一个无法清晰归类的问题时,他们仍然可能会按4。 -
按下
4后,他们会被引导到一个没有他们寻求答案的支持页面。他们点击一个写着“不,这没有回答我的问题”的按钮。 -
他们自己搜索知识库,可能永远找不到答案,然后通过电话寻求帮助。
想象一下能够输入你想要的内容,而机器人能够以自然的方式回应——不是将你引导到某个页面,而是直接给出答案。再进一步想象,用户可以与机器人聊天,表示他们想要修改订单上的地址,而机器人能够在聊天窗口内完成这一操作,与用户进行多步骤对话以确认并记录他们的新信息。
这对客户来说是一种全新的、更令人愉悦的体验!
GenAI 的伦理和风险
尽管有这些好处,但使用 AI 仍然存在风险和担忧。在某些领域,对 AI 的反对声音很大,并且有正当的理由。例如,由 AI 生成的艺术作品充斥着互联网市场,取代了那些靠手艺谋生的艺术家和插画师。有人质疑使用 AI 写书是否赋予一个人自称作者的权利。在这里没有明确的答案;根据我们自己的经验,这本书的作者认为 GenAI 加速了而不是取代了今天所做的工作的现有范式。但这可能并不总是如此。随着 AI 的进步,它可能更有可能取代使用它的人类。
GenAI 的风险相当大,其中一些风险尚未得到充分理解。即使那些被充分理解的风险,例如幻觉,对于用户来说也难以识别,更不用说对抗它们了。你可以在 第11章 生成式AI的常见失败 中了解更多关于 GenAI 的挑战,以及如何在 第12章 纠正和优化你的生成式AI应用 中减轻这些挑战的建议。
摘要
本章为 GenAI 应用奠定了基础,从描述每个组件的作用到它们的优点。你学习了一些关键的定义,并了解了 AI 堆栈的基础知识。到目前为止,你也知道了为什么 Python 是构建 GenAI 应用程序的一个很好的选择,以及为什么你会使用 OpenAI API 和 MongoDB 与向量搜索来构建你的 GenAI 应用程序。最后,你也看到了一些 GenAI 的显著用例,并了解了为什么你应该首先使用 GenAI,同时也要关注使用 GenAI 的伦理和风险。既然你在阅读这篇文章,我会假设这个案例很有说服力——你仍然感兴趣,并准备好去探索。
在下一章中,你将获得一个快速、简洁且实用的概述,详细了解GenAI应用的基础构建块,并学习如何开始入门。
第二章:智能应用的基本构建块
在软件开发快速发展的环境中,一类新的应用正在兴起:智能应用。智能应用是传统全栈应用的超集。这些应用利用人工智能(AI)提供高度个性化、上下文感知的体验,超越了传统软件的能力。
智能应用能够理解复杂、非结构化的数据,并利用这种理解来做出决策并创建自然、自适应的交互。
本章的目标是向您提供一个关于智能应用逻辑和技术构建块的概述。本章探讨了智能应用如何扩展传统全栈应用的能力,定义它们的核心理念,以及这些组件如何协同工作以创建动态、上下文感知的体验。到本章结束时,您将了解这些组件如何组合在一起形成一个智能应用。
本章涵盖以下主题:
-
智能应用的基本构建块
-
作为智能应用推理引擎的LLMs
-
向量嵌入模型和向量数据库作为语义长期记忆
-
模型托管基础设施
技术要求
本章是理论性的。它涵盖了智能应用的逻辑组件以及它们如何相互配合。
本章假设读者对传统全栈应用开发组件的基本知识有所了解,例如服务器、客户端、数据库和API。
定义智能应用
传统应用通常由客户端用户界面、服务器端后端以及用于数据存储和检索的数据库组成。它们按照严格的指令集执行任务。智能应用也需要客户端、服务器和数据库,但它们通过增加AI组件来增强传统的技术栈。
智能应用通过理解复杂、非结构化的数据来脱颖而出,从而实现自然、自适应的交互和决策。智能应用可以参与开放式的交互,生成新颖的内容,并做出自主的决策。
智能应用的例子包括以下内容:
-
基于外部数据使用检索增强生成(RAG)提供自然语言响应的聊天机器人。例如,Perplexity.ai (https://www.perplexity.ai/) 是一个由AI驱动的搜索引擎和聊天机器人,它根据从网络检索到的来源为用户提供基于AI生成的答案。
-
允许您使用自然语言提示创建媒体内容(如图像、视频和音频)的内容生成器。有各种智能内容生成器专注于不同的媒体类型,例如Suno(https://suno.com/)提供文本转歌曲,Midjourney(https://www.midjourney.com/home)提供文本转图像,以及Runway(https://runwayml.com/)提供文本转视频。
-
使用客户数据提供基于其偏好和历史记录的个性化建议的推荐系统。这些建议可以通过自然语言进一步个性化客户体验。例如,Spotify的AI DJ([https://support.spotify.com/us/article/dj/](https://support.spotify.com/us/article/dj/))根据您的收听历史创建个性化的电台,包括由LLM生成的DJ插曲。
这些例子只是对开发者刚开始构建的新类别智能应用的初步探索。在下一节中,您将了解智能应用的核心组件。
智能应用的基本构建块
智能应用的核心是两个关键构建块:
-
推理引擎:推理引擎是智能应用的头脑,负责理解用户输入、生成适当的响应,并根据可用信息做出决策。推理引擎通常由大型语言模型(LLMs)驱动——这些是执行文本补全的AI模型。LLMs可以理解用户意图,生成类似人类的响应,并执行复杂的认知任务。
-
语义记忆:语义记忆指的是应用存储和检索信息的方式,能够保留其意义和关系,使推理引擎能够根据需要访问相关上下文。
语义记忆由两个核心组件组成:
-
AI向量嵌入模型:AI向量嵌入模型将非结构化数据(如文本或图像)的语义意义表示为大量数字数组。
-
向量数据库:向量数据库高效地存储和检索向量,以支持语义搜索和上下文检索。
-
推理引擎可以从语义记忆中检索和存储相关信息,使用非结构化数据来告知其输出。
驱动智能应用的LLMs和嵌入模型与传统应用相比有不同的硬件要求,尤其是在规模上。智能应用需要专门的模型托管基础设施,能够处理AI工作负载的独特硬件和可扩展性要求。智能应用还结合了持续学习、安全监控和人工反馈,以确保质量和完整性。
LLMs是智能应用的重要器官。下一节将更深入地了解LLMs在智能应用中的作用。
LLMs – 智能应用的推理引擎
LLMs是智能应用的关键技术,解锁了全新类别的AI驱动系统。这些模型在大量文本数据上训练,以理解语言、生成类似人类的文本、回答问题和进行对话。
随着新模型的发布,LLMs不断改进,具有数十亿或数万亿参数,并增强了推理、记忆和多模态能力。
LLM推理引擎的应用案例
LLMs(大型语言模型)已成为人工智能系统的一种强大通用技术,类似于传统计算中的中央处理器(CPU)。与CPU类似,LLMs作为通用计算引擎,可以被编程执行许多任务,并在基于语言的推理和生成中扮演相似的角色。LLMs的通用性质使得开发者能够利用其能力进行广泛的推理任务。
一些技术已经出现,以利用LLMs的多样化能力,例如:
-
提示工程:通过精心设计的提示,开发者可以引导LLMs执行广泛的语言任务。提示工程的一个关键优势是其迭代性。由于提示本质上只是文本,因此可以快速尝试不同的提示并查看结果。高级提示工程技术,如思维链提示(鼓励模型将推理分解为一系列步骤)和多轮提示(为模型提供示例输入/输出对),可以进一步提高LLM生成文本的质量和可靠性。
-
微调:微调涉及从预训练的通用模型开始,并在与目标任务相关的较小数据集上进行进一步训练。这可以比单独的提示工程产生更好的结果,但也有一些缺点,如成本更高、耗时更长。你应该在用尽提示工程所能达到的效果后再进行微调。
-
检索增强:检索增强将LLMs与外部知识库接口连接起来,使其能够利用最新的、特定领域的知识。在这种方法中,相关信息从知识库中检索出来并注入到提示中,使LLM能够生成上下文相关的输出。检索增强减轻了LLMs静态预训练的限制,保持其知识更新,并减少模型产生错误信息的可能性。
利用这些技术,你可以使用LLMs执行各种任务。下一节将探讨LLMs的当前应用案例。
LLMs的多样化能力
尽管本质上只是语言模型,但LLM展现出了令人惊讶的涌现能力(https://arxiv.org/pdf/2307.06435)。截至2024年春季,最先进的语言模型能够执行以下类别的任务:
-
文本生成和补全:给定一个提示,LLM可以生成连贯的后续内容,这使得它们在内容创作、文本摘要和代码补全等任务中非常有用。
-
开放式对话和聊天:LLM可以进行双向对话,保持上下文并处理开放式用户查询和后续问题。这种能力是聊天机器人、虚拟助手、辅导系统等应用的基石。
-
问答:LLM可以提供直接答案,进行研究和综合信息以回答查询。
-
分类和情感分析:LLM可以将文本分类到预定义的类别,并评估情感、情绪和观点。这使得LLM在内容审核和客户反馈分析等应用中变得非常有用。
-
数据转换和提取:LLM可以将非结构化文本映射到结构化格式,并提取关键信息,如命名实体、关系和事件。这使得LLM在数据挖掘、知识图谱构建和机器人流程自动化(RPA)等任务中非常有价值。
随着LLM在规模和复杂性上的持续增长,新的能力不断涌现,通常以令人惊讶的方式出现,这些方式并非原始训练目标直接意图。
例如,GPT-3生成功能代码的能力是一个意外的发现。随着LLM(大型语言模型)领域的进步,我们可以期待看到更多令人印象深刻且功能多样的能力出现,进一步扩展智能应用的潜力。
多模态语言模型
多模态语言模型在扩展语言模型能力方面具有特别的前景。多模态模型可以处理和生成图像、语音和视频,除了文本之外,已成为智能应用的重要组成部分。
多模态模型使得以下新的应用类别成为可能:
-
基于多种输入类型创建内容,例如用户可以提供图像和文本作为输入的聊天机器人。
-
高级数据分析,例如分析X光片和医疗记录的医疗诊断工具。
-
实时翻译,将一种语言的音频或图像翻译成另一种语言。
这样的例子突显了多模态语言模型如何增强语言模型的可能用例。
人工智能发展的范式转变
LLMs的兴起代表了AI应用开发中的范式转变。以前,许多推理任务需要专门训练的模型,这些模型的创建既耗时又计算量大。开发这些模型通常需要专门的机器学习(ML)工程团队和专业知识。
相比之下,LLMs的通用性质允许大多数软件工程师通过简单的API调用和提示工程利用其功能。虽然优化基于LLMs的工作流程以实现生产部署仍有一定的艺术性和科学性,但与传统的ML方法相比,这个过程要快得多,也更容易访问。
这种转变显著降低了AI应用的总拥有成本和开发时间表。以前可能需要复杂ML工程团队数月工作的NLP任务,现在可以通过单个软件工程师实现,该工程师可以访问LLM API并具备一些提示工程技能。
此外,LLMs解锁了之前无法或难以开发的新类别应用。LLMs理解和生成类似人类文本、参与开放式对话以及执行复杂推理任务的能力,为跨行业智能应用开辟了广泛的可能性。
你将在第3章“大型语言模型”中了解更多关于LLMs的内容,该章节讨论了它们的历史和运作方式。
嵌入模型和向量数据库——语义长期记忆
除了LLMs提供的推理能力外,智能应用还需要语义长期记忆来存储和检索信息。
语义记忆通常由两个核心组件组成——AI向量嵌入模型和向量数据库。向量嵌入模型将非结构化数据(如文本或图像)的语义意义表示为大量数字的大数组。向量数据库有效地存储和检索这些向量,以支持语义搜索和上下文检索。这些组件协同工作,使推理引擎能够根据需要访问相关上下文和信息。
嵌入模型
嵌入模型是AI模型,将文本和其他数据类型(如图像和音频)映射到高维向量表示。这些向量表示捕获输入数据的语义意义,允许进行高效的相似性比较和语义搜索,通常使用余弦相似度作为距离度量。
嵌入模型将语义意义编码成机器可解释的格式。通过将类似的概念表示为向量空间中的邻近点,嵌入模型使我们能够测量非结构化数据片段之间的语义相似性,并在大型语料库中执行语义搜索。
预训练的嵌入模型广泛可用,并且可以针对特定领域或用例进行微调。与LLMs相比,嵌入模型通常更经济实惠,并且可以在有限的硬件上运行,这使得它们适用于更广泛的应用。
嵌入模型的一些常见应用包括以下内容:
-
语义搜索和检索:嵌入模型可以用作更大AI系统的一个组件,以检索LLMs的相关上下文,特别是在RAG架构中。RAG是本书中讨论的智能应用的一个重要用例,将在第8章第8章,在AI应用中实现向量搜索中更详细地介绍。
-
推荐系统:通过将项目和用户偏好表示为嵌入,推荐系统可以识别相似的项目并生成个性化的推荐。
-
聚类和主题建模:嵌入模型可以帮助在大数据集中发现潜在的论题和主题,这对于分析用户与智能应用(如识别聊天机器人中的常见问题)的交互非常有用。
-
异常检测:通过识别与正常情况在语义上距离较远的异常向量,嵌入模型可以用于各种领域的异常检测。
-
分析实体之间的关系:嵌入模型可以根据实体的语义相似性揭示实体之间隐藏的关系和联系。
你将在第4章,嵌入模型中探索嵌入模型的技术细节和实际考虑。
向量数据库
向量数据库是针对存储和搜索高维向量进行优化的专用数据存储。它们提供了快速、近似最近邻(ANN)搜索功能,允许智能应用根据空间邻近性快速存储和检索相关信息。
ANN搜索是必要的,因为随着数据库规模的增加,对数据库中每个向量进行精确相似度计算的计算成本变得很高。向量数据库使用诸如层次可导航小世界(HNSW)之类的算法来有效地找到近似最近邻,使得大规模的向量搜索成为可能。
除了ANN搜索外,向量数据库通常支持对与向量关联的元数据进行过滤和精确搜索。这些功能的精确功能和性能在不同向量数据库产品中有所不同。
向量数据库在给定查询的情况下为智能应用提供低延迟的相关信息检索。使用内容的语义意义进行搜索,向量数据库与LLMs推理信息的方式相一致,使应用能够将相同的不结构化数据格式应用于长期记忆和推理。
在使用RAG的应用中,向量数据库扮演着至关重要的角色。应用生成一个查询嵌入,用于从向量数据库中检索相关上下文。然后,将多个相关片段作为上下文提供给LLM,LLM使用这些信息生成有见地和相关的响应。
您将在第5章 向量数据库中了解向量数据库的技术细节和实际考虑。
模型托管
要在您的智能应用中实现AI模型,您必须在计算机上托管它们,无论是在数据中心还是云中。这个过程被称为模型托管。为应用托管AI模型与托管传统软件相比,有一套不同的要求。大规模运行AI模型需要强大的图形处理单元(GPUs)并配置软件环境以高效地加载和执行模型。
模型托管的关键挑战包括高计算需求和硬件成本、GPU资源的有限可用性、管理和扩展托管基础设施的复杂性,以及使用专有解决方案时可能出现的供应商锁定或灵活性有限。因此,硬件和成本限制必须比以往任何时候都更多地纳入应用设计过程。
自托管模型
自托管模型这一术语指的是在组织自己的基础设施和硬件资源上部署和运行AI模型(如LLMs)的做法。在这种方法中,组织负责设置和维护加载和执行模型所需的所有必要的计算资源、软件环境和基础设施。
自托管AI模型需要大量前期投资于专用硬件,这对许多组织来说可能是成本高昂的。管理模型基础设施也带来运营负担,这需要ML专业知识,而许多软件团队缺乏这种知识。这可能会分散对核心应用和业务逻辑的注意力。
将自托管模型扩展以确保可用性可能具有挑战性,因为模型可能很大,并且需要时间加载到内存中。组织可能需要预留大量额外容量来处理峰值负载。此外,维护和更新模型是一项复杂的工作,因为模型可能会随着时间的推移而变得过时,需要重新训练或微调。随着该领域的积极研究,新的模型和技术不断涌现,这使得组织难以跟上。
模型托管提供商
与自托管相关的挑战使得模型托管提供商成为智能应用开发的流行选择。
模型托管提供商 是基于云的服务,提供在其基础设施上部署、运行和管理 AI 模型(如 LLMs)的平台。这些提供商处理设置、维护和扩展加载和执行模型所需基础设施的复杂性。
模型托管提供商提供以下好处:
-
外包硬件和基础设施管理:模型托管提供商处理配置、扩展、可用性、安全性和其他基础设施问题,使应用团队能够专注于其核心产品。
-
成本效益和灵活定价:通过模型托管提供商,组织只需为其使用的部分付费,并根据需要调整资源,从而减少前期投资。
-
访问广泛的模型:提供商精选并托管许多最先进的模型,持续集成最新研究。他们通常在原始模型上添加额外的功能和优化。
-
支持和专业知识:提供商可以就模型选择、提示工程、应用架构提供咨询,并在微调、数据准备、评估和其他 AI 开发方面提供帮助。
-
快速原型设计和实验:模型托管提供商使开发者能够快速测试不同的模型和方法,适应快速发展的 AI/ML 领域的新进展。
-
可扩展性和可靠性:提供商构建强大、高度可用和自动扩展的基础设施,以满足生产规模智能应用的需求。
模型托管提供商的例子包括来自模型开发者如 OpenAI、Anthropic 和 Cohere 的服务,以及来自云提供商如 AWS Bedrock、Google Vertex AI 和 Azure AI Studio 的产品。
您(即将成为的)智能应用
使用 LLMs、嵌入模型、向量数据库和模型托管,您拥有创建智能应用的关键构建块。虽然具体架构将根据您的用例而有所不同,但一个常见的模式出现:
-
LLMs 用于推理和生成
-
嵌入 和 向量搜索 用于检索和记忆
-
模型托管 以大规模提供这些组件
此 AI 堆栈与传统应用组件集成,例如后端服务、API、前端用户界面、数据库和数据管道。此外,智能应用通常包括针对 AI 特定关注点的组件,例如提示管理和优化、数据准备和嵌入生成,以及 AI 安全性、测试和监控。
本节剩余部分将介绍一个由 RAG 驱动的聊天机器人示例架构,展示这些组件如何协同工作。后续章节将更深入地探讨构建生产级智能应用的端到端流程。
样例应用 – RAG 聊天机器人
考虑一个简单的聊天机器人应用,该应用利用 RAG 允许用户与某些文档进行交流。
该应用程序有七个关键组件:
-
聊天机器人 UI:一个具有简单聊天机器人 UI 的网站,与网页服务器通信
-
Web 服务器:一个 Python Flask 服务器,用于管理用户和 LLM 之间的对话
-
数据摄取提取、转换、加载(ETL)管道:一个 Python 脚本,用于从数据源摄取数据
-
由 OpenAI 托管的
text-embedding-3-small模型 -
由 OpenAI 托管的
gpt-4-turbo模型 -
向量存储:MongoDB Atlas 向量搜索
-
MongoDB Atlas:用于持久化对话的数据库即服务
注意
这个简单的示例应用程序不包括评估或可观察性模块。
在此架构中,有两个关键的数据流:
-
聊天交互:用户使用 RAG 与聊天机器人进行交流
-
数据摄取:将数据从其原始来源引入向量数据库
在聊天交互中,聊天机器人 UI 与聊天机器人网页服务器通信,该服务器随后与 LLM、嵌入模型和向量存储进行交互。这发生在用户向聊天机器人发送的每条消息时。图 2.1 展示了聊天机器人应用程序的数据流:

图 2.1:基本 RAG 聊天机器人对话数据流示例
图 2.1 中所示的数据流可以描述如下:
-
用户从网页 UI 向聊天机器人发送消息。
-
网页用户界面向服务器发送包含用户消息的请求。
-
网页服务器向嵌入模型 API 发送请求以创建用户查询的向量嵌入。嵌入模型 API 返回相应的向量嵌入。
-
网页服务器使用查询向量嵌入在向量数据库中执行向量搜索。向量存储返回匹配的向量搜索结果。
-
服务器构建一个 LLM 将会回复的消息。这个消息由系统提示和一个包含用户原始消息和从向量搜索中检索的内容的新消息组成。然后 LLM 回复用户消息。
-
服务器将对话状态保存到数据库中。
-
服务器将 LLM 生成的消息作为对网页 UI 原始请求的响应返回给用户。
数据摄取管道准备和丰富数据,使用嵌入模型生成嵌入,并填充向量存储和传统数据库。此管道每 24 小时作为批处理作业运行。图 2.2 展示了数据摄取管道的示例:

图 2.2:RAG 聊天机器人数据摄取 ETL 管道示例
让我们看看 图 2.2 中所示的数据流:
-
数据摄取 ETL 管道从各种数据源中提取数据。
-
ETL 管道将数据清理成一致格式。它还将数据分成数据块。
-
ETL 管道调用嵌入模型 API 为每个数据块生成向量嵌入。
-
ETL 管道将数据块及其向量嵌入存储在向量数据库中。
-
向量数据库为向量搜索索引嵌入。
虽然这种简单的架构可以用来构建引人注目的原型,但从原型过渡到生产并持续迭代应用需要解决许多额外的考虑因素:
-
数据摄取策略:获取、清理和准备将被摄取到向量存储或数据库中以供检索的数据。
-
高级检索模式:结合从向量存储或数据库中高效准确地检索相关信息的技术,例如将语义搜索与传统过滤、基于AI的重新排序和查询变异相结合。
-
评估和测试:添加评估模型输出、测试端到端应用流程和监控潜在偏差或错误的模块。
-
可扩展性和性能优化:实施优化措施,如缓存、负载均衡和高效资源管理,以处理不断增长的工作负载并确保一致的响应性。
-
安全和隐私:确保应用的安全性,以便用户只能与他们有权限交互的数据进行交互,从而确保用户数据按照相关政策、标准和法律进行处理。
-
用户体验和交互设计:结合新的生成式AI界面和交互模式,例如流式响应、答案置信度和来源引用。
-
持续改进和模型更新:建立流程和系统以安全可靠地更新智能应用中的AI模型和超参数。
智能应用对软件工程的影响
智能应用的出现对软件制作方式产生了重大影响。开发这些智能应用需要扩展传统开发技能。AI工程师必须了解提示工程、向量搜索和评估,以及熟悉最新的AI技术和架构。虽然不需要完全理解底层神经网络,但了解自然语言处理(NLP)的基本知识是有帮助的。
智能应用开发也引入了新的挑战和考虑因素,例如数据管理、与AI组件的集成、AI驱动功能的测试和调试,以及解决AI输出的伦理、安全和隐私影响。AI工作负载的计算密集型特性也要求关注可扩展性和成本优化。构建传统软件的开发人员通常不需要面对这样的担忧。
为了应对这些挑战,软件开发团队必须调整其流程并采用新颖的方法和最佳实践。这包括实施AI治理、弥合软件与ML/AI团队之间的差距,并调整智能应用需求的发展生命周期。
摘要
智能应用代表了软件开发的新范式,将人工智能与传统的应用组件相结合,以提供高度个性化、上下文感知的体验。本章详细介绍了智能应用的核心组件,突出了LLMs作为推理引擎的关键作用。LLMs作为通用设计工具,能够执行各种任务,包括聊天、摘要和分类。
补充这些推理引擎的是嵌入模型和向量数据库,它们作为智能应用的语义记忆。这些组件使推理引擎能够根据需要检索相关的上下文和信息。此外,托管AI模型需要专用基础设施,因为它们的独特硬件需求与传统软件需求差异很大。使用LLMs、嵌入模型、向量数据库和模型托管基础设施等构建块,开发者可以创建能够理解复杂、非结构化数据、进行开放式交互、生成新颖内容并做出自主决策的应用程序。构建这些智能应用需要一套新的工具、方法和最佳实践。
下一章将探讨大型语言模型(LLMs)的工作原理以及它们在构建智能应用中的作用。
第一部分
人工智能基础:LLMs、嵌入模型、向量数据库和应用设计
这套章节提供了关于支撑AI密集型应用的技术和原理的深入和实践知识。您将迅速从基本概念进步到实际用例,并学习构建您AI解决方案的最佳实践。
本书这部分包括以下章节:
第三章:大型语言模型
语言模型是设计用于处理、理解和生成自然语言的计算算法。对这些算法的研究、研究和开发被称为 自然语言处理(NLP)。NLP 早在 机器学习(ML)领域之前,可以追溯到 20 世纪 50 年代和第一台计算机的发展。虽然早期的语言模型主要依赖于基于规则的途径,但 NLP 在 20 世纪 80 年代转向了统计方法,并开始与 ML 相汇合。计算能力的提升和文本语料库的增长导致了 21 世纪初深度学习和基于神经网络的深度学习语言模型的发展,这些模型在过去十年中取得了显著进展。
语言模型在 NLP 中有各种应用,用于理解和生成自然语言以及更正式的语言,如编程和数据库查询语言。它们的使用案例包括文本标注、情感分析、翻译、摘要、信息提取和问答等任务。随着 大型语言模型(LLMs)的出现,应用进一步扩展到开发对话聊天系统、个人助理、软件开发代理和通用问题解决者。在本章中,你将深入了解 LLMs 的基本概念和实现。
本章将涵盖以下主题:
-
使用 n-gram 模型进行语言建模以提供概率观点
-
人工神经网络(ANNs),其架构和训练范式
-
将 ANNs 应用于语言建模领域
-
Transformer 架构
-
LLMs 的实践应用
技术要求
本章主要理论性,包含一段简短的 Python 代码片段来展示 tiktoken 标记化库。为了跟上进度,你需要一台装有 Python 3.8 或更高版本的计算机。
要充分利用本章内容,你需要熟练掌握 Python 和 pip 软件包管理器。你还需要对概率、微积分以及软件开发概念(如 API)有基本了解。
概率框架
当构建与 LLMs 交互的 AI 密集型应用时,你可能会遇到与标记概率相关的 API 参数。为了理解 LLMs 如何与概率概念相关联,本节介绍了支撑语言模型的概率框架。
语言建模通常是以概率观点为前提进行的,而不是绝对的、确定性的术语。这允许算法处理在自然语言中经常遇到的不确定性和歧义。
为了对概率语言建模有一个直观的理解,考虑以下句子的开头,你想要预测下一个单词:
The
这显然是一个具有许多可能答案的模糊任务。英语中的 the 是一个非常常见和通用的单词,可能性是无限的。任何名词,如 house、dog、spoon 等,都可以是句子的有效可能延续。甚至形容词如 big、green 和 lazy 也可能是可能的候选人。相反,有些词很少出现在冠词之后,包括动词,如 eat、see 和 learn。
为了处理这种不确定性,考虑一个稍微不同的问题:“每个单词接下来出现的概率是多少?”
对于 this 这个问题的答案不再是单个单词,而是一个大型的查找表,为词汇表中的每个单词分配一个数字,代表这个单词跟随 the 的概率。如果这个查找表代表了英语语言,人们会期望名词和形容词比动词有更高的概率。表3.1 展示了这样一个表可能的样子,使用虚构的值来表示 概率 列。你很快就会看到这些概率是如何从文本语料库中计算出来的:
| 前一个单词 | 下一个单词 | 概率 |
|---|---|---|
| … | … | … |
| the | house | 0.012% |
| the | dog | 0.013% |
| the | spoon | 0.007% |
| … | … | … |
| the | big | 0.002% |
| the | green | 0.001% |
| the | lazy | 0.001% |
| … | … | … |
| the | eat | 0.000% |
| the | see | 0.000% |
| the | learn | 0.000% |
| …. | .. | … |
表3.1:跟随单词 the 的部分查找表
在这个简单的例子中,决定下一个单词的一种方法(但不是唯一的方法)是扫描这个查找表,找到概率最高的单词。这种方法被称为 贪婪选择,会建议 dog 是句子最可能的延续。然而,重要的是要注意,有许多可能性,每种可能性都有不同的概率。例如,单词 house 在概率上也是接近第二的选择,这表明它也可能是句子的一个可能的延续。
为了捕捉自然语言的灵活性和表现力,语言模型以概率为依据进行操作,训练语言模型的过程意味着为到目前为止的句子中的每个单词分配概率。
假设你已经多次进行了选择下一个单词的过程,发现自己已经进一步地沿着句子前进:
The quick brown fox jumps over the
这个句子接下来会如何继续?现在的概率分布是什么样的?
如果你熟悉这个句子1,你会同意在这个点上,单词 lazy 的概率将高于所有其他单词。你的内部语言模型无法帮助完成整个句子的自动补全,而 lazy dog 这几个词就会突然出现在你的脑海中。
1 这句话是一个 pangram。一个 pangram 至少包含字母表中的每一个字母。这句话已经在各种语境中使用,例如打字练习和测试计算机中的文本显示。
但为什么是这样呢?你不是和之前一样,在 the 后询问接下来是什么吗?这里的关键区别在于你有了更多的上下文;你看到了更多的句子,这表明仅考虑前面的单词不足以构建对下一个单词的良好预测器。然而,这个基本概念标志着语言模型的起点,可以被视为 ChatGPT 和其他现代 LLM 的远祖。
n-gram 语言模型
语言模型的第一种形式之一是 n-gram 模型,这是一种简单的统计语言模型,首次发表在 1948 年克劳德·香农著名的论文 《通信的数学理论》 中 (https://people.math.harvard.edu/~ctm/home/text/others/shannon/entropy/entropy.pdf)。
n-gram 语言模型可以被描述为一个巨大的查找表,其中模型考虑最后 n-1 个单词来预测下一个单词。对于 n=2,你得到一个所谓的二元模型,只回溯一个单词,如 表 3.1 所示。
如前例中的句子所示,这样一个简单的二元模型是有限的,无法捕捉自然语言的细微差别。然而,在探索当 n 扩展到更大的值时会发生什么之前,让我们简要讨论如何训练一个二元模型,也就是说,如何计算表中每一对单词的概率:
-
选取一个大型文本语料库,例如所有英文维基百科页面的集合。
-
扫描此文本,并统计单个单词以及观察到的单词对的出现的次数。
-
在查找表中记录所有计数。
-
按如下方式计算单词
在单词
后出现的概率:将单词对
的计数除以单个单词
的计数。
例如,要计算单词 dog 在单词 the 后出现的概率,按以下方式将配对计数除以单个单词计数:

这里,术语
读作“在 y 的条件下 x 的概率。”换句话说,在已经看到单词 the 的情况下看到单词 dog 的概率是看到这两个单词组合的计数(分子)除以看到 the 单独的所有计数(分母)。
因此,n-gram 语言模型的训练过程只需要对文本进行单次遍历,统计所有出现的 n-gram 和 (n-1)-gram,并将数字存储在表中。
在实践中,一些技巧可以提高n-gram模型的质量,例如在每个句子的开始和结尾包含特殊的<start>和<end>标记,将单词拆分成更小的子词,例如将*playing*拆分成*play*和*-ing*,以及许多其他改进。你将在后面的*Tokenization*部分中回顾一些这些技术,并且它们也适用于现代LLMs。
让我们现在重新审视n的选择。正如你所看到的,一个较低的值,例如n=2,并不能产生一个非常好的语言模型。这是否仅仅是扩大n直到达到所需质量水平的问题?
较大的n值可以捕捉到更多的上下文,并导致一个更具预测性的模型。对于n=8,模型可以回顾最后七个单词。正如表3.2所示,查找表将包含一行,用于捕获以下示例句子:
| Previous 7 words | Next word | Probability |
|---|---|---|
| … | … | … |
| the quick brown fox jumps over the | lazy | 99.381% |
| …. | .. | … |
表3.2:8-gram查找表中的一个可能条目
然而,将n增加到较大的值有几个挑战,这使得这种方法在实践中不可行。
查找表的大小会随着n的增大而指数级增长。《牛津高阶英汉双解大词典》包含大约273,000个英语单词(https://en.wikipedia.org/wiki/List_of_dictionaries_by_number_of_words),这允许有74.5亿种可能的两个单词组合(尽管这些组合中的许多在文本中永远不会出现)。将n-gram模型增加到n=8,八个单词的可能组合增长到天文数字!。在表中为每个组合存储条目是不可能的,因为这个数字远远超过了世界上所有可用的硬盘存储空间,尤其是在世界总数据量预计到2025年将达到175泽字节=
字节的情况下(https://www.networkworld.com/article/966746/idc-expect-175-zettabytes-of-data-worldwide-by-2025.html)。当然,这些单词组合中的大多数永远不会遇到,你可以在表中选择省略未见的n-gram。
这个挑战,被称为n,任何单个n-gram出现的概率会指数级下降。大多数n个单词的组合在现实规模的训练数据集中永远不会遇到。当处理不属于训练语料库的文本时,模型将为未见的n-gram分配零概率。在这种情况下,模型将无法做出有意义的预测,并且随着n的增大,这个问题会加剧。
总结来说,尽管n-gram在某些特定应用和教育目的上有其用途,但今天的语言模型已经超越了纯粹统计方法。大型语言模型(LLMs)使用机器学习技术来解决上述一些问题,你将在下一节中了解到这些内容。
语言建模的机器学习
在深入探讨使用机器学习进行语言建模的方法之前,本节首先介绍一些通用的机器学习概念,并对不同的神经网络架构进行高层次概述。
在本质上,机器学习是一个关注开发和研究从数据中学习的算法的领域。系统不是执行硬编码的规则,而是预期通过示例学习,查看提供的输入和期望的结果(在机器学习文献中通常称为目标)并在训练过程中调整其行为以改变其输出,使其更接近用户提供的目标。
机器学习算法大致可以分为三类:
-
监督学习
-
无监督学习
-
强化学习
这些组别各自有不同的学习目标和问题表述。对于语言建模,你可以主要考虑监督学习(和相关自监督)算法。
人工神经网络
一类监督学习算法是人工神经网络(ANNs)。所有现代LLMs都是基本ANN架构的变体。当你对一个如GPT-4之类的模型进行API调用时,你的问题会通过ANN流过以生成答案。这些模型在几十年的时间里在规模和复杂性上有所发展,但核心原理和构建块保持不变。
人类大脑中发现的神经网络架构可能启发了ANNs的原始设计,但ANNs与其生物对应物有显著不同。
人工神经网络(ANNs)由许多称为神经元的较小单元组成,这些神经元以各种模式相互连接,具体取决于网络架构。每个神经元是一个小的处理单元,从其他神经元接收数值信号,并将一个(修改后的)信号传递给其后续神经元,类似于生物神经元。ANNs具有可调节的参数,称为权重,它们位于两个神经元之间的连接上,可以影响它们之间的信号传递。
最基本的ANN架构之一是所谓的前馈网络(FFN),如图3.1所示。在这个架构中,神经元按层排列,从输入层开始,然后是一个或多个隐藏层,最后是输出层。层的大小,即每层的神经元数量,可以变化。输入层和输出层的大小由特定的应用领域决定。例如,你可能想要学习从二维输入(例如,一个人的体重指数和年龄)到一维输出(例如,每天消耗的静息卡路里)的映射。隐藏层的大小通常通过称为超参数调整的实验过程任意选择。
在前馈神经网络(FFN)中,每一层的每个神经元都与下一层的所有神经元相连,导致两个连续层之间存在多对多的关系。图3.1展示了具有一个输入层(层1)、两个隐藏层(层2和层3)以及一个输出层(层4)的前馈神经网络架构:

自动生成的描述](https://github.com/OpenDocCN/freelearn-dl-zh/raw/master/docs/bd-ai-intn-py-app/img/B22495_03_01.png)
图3.1:前馈神经网络架构
放大单个神经元的运作,图3.2展示了一个从两个其他神经元(表示为
和
)接收输入的神经元。连接到神经元的权重(表示为
和
)。输入首先与相应的权重相乘,然后求和。得到的和通过一个非线性激活函数
传递,结果形成神经元的输出(表示为
)。从数学的角度来看,这可以表示为以下公式:
尽管本章不涉及激活函数的具体细节,但可以说非线性对于网络能够学习数据中的复杂模式至关重要。

图3.2:具有两个输入的单个神经元的激活
在网络的前向传递过程中,你将输入数据(例如,一个人的BMI和年龄)呈现在输入层,计算该层所有神经元的激活,然后将这些激活传递到下一层,依此类推,直到输出层产生一个结果(在这个例子中,你可以将其解释为模型对一个人燃烧卡路里的预测)。
神经网络中单个神经元所遵循的简单激活函数能够导致复杂的模式识别能力,这似乎令人惊讶。这一现象源于万能逼近定理,该定理证明了具有足够隐藏层和神经元的神经网络可以逼近任何连续函数到任何所需的精度。
你现在已经了解了在人工神经网络(ANN)中数据是如何从前向流到输出层的。对于一个未经训练的模型,这仅仅是三个阶段中的第一个。在下一节中,你将学习训练ANN所需的另外两个阶段:损失计算和反向传播。
训练人工神经网络
到目前为止,本章已经描述了网络的正向传播,即如何计算给定输入的响应。由于人工神经网络的初始权重是随机选择的,因此未经训练的网络的输出也是随机的、无意义的。在训练过程中需要调整权重。
训练神经网络的目的是使其输出与任何给定输入提供的目标相匹配。因此,对于监督学习,训练数据集由已知正确响应的输入/目标对组成。在预测给定一个人的BMI和年龄所消耗的卡路里的例子中,训练数据集将包括许多人的BMI和年龄(输入)以及他们测量的卡路里消耗(目标)。数据集中的测量越多,模型就能更好地从输入和目标之间的关系中学习模式。
人工神经网络的训练过程可以分为三个阶段,如图3.3所示:
-
正向传播:从输入计算输出。
-
损失计算:计算输出和期望目标之间的误差信号。
-
反向传播和权重调整:将错误反向传播通过模型并调整每个权重。

图3.3:训练ANN的三个阶段
此过程在数据集的多次遍历中重复,直到权重参数不再有意义地改变。此时,模型被称为收敛,并准备好进行推理。
训练从数据的前向传播开始,输入数据并记录网络的输出。由于这个输出可能与正确目标不同(特别是对于具有随机权重的未经训练的网络),因此可以计算一个称为损失的指标,它是一个反映实际输出和期望输出之间差异的标量数。
损失是执行反向传播所必需的。这一步骤将调整网络的所有权重,以便网络将产生一个更接近给定输入目标的输出。每个神经元的激活是一个具有和、积和可微分的激活函数的良好的可微分表达式。这意味着可以通过微积分规则计算权重相对于损失的导数,以确定每个权重参数需要如何调整以最小化损失。
然后,使用微积分的链式法则,将这个梯度计算反向传播到前面的层,直到输入层。以这种方式计算出每个权重的梯度后,权重就可以更新。由一个称为学习率的参数控制,权重可以朝着最小化损失的方向移动一小步。
虽然理论上可以为训练集中的每个单独条目执行正向和反向传递的循环,但在实践中,训练集被分成小批次。一个批次可能包含数十、数百甚至数千个数据点。批次大小是另一个在实际训练过程之前通过超参数调整实验选择的超参数。以这种方式批量处理数据有以下几个目的:
-
这提高了效率,因为批次可以在并行处理,尤其是在专门的硬件上,如图形处理单元(GPU)。
-
通过网络反向传播的错误梯度在每个批次中平均。这导致训练更加稳定,因为数据中的单个异常值对权重变化的影响较小。
训练会继续进行,直到模型不再在未见过的验证数据上改进。
训练完成后,训练好的模型可以应用于之前未见过的输入。例如,该模型可以集成到健身追踪应用程序中,根据一个人的BMI和年龄预测燃烧的卡路里,预期它不仅适用于训练数据中的测量,而且可以推广到新的数据点。将训练好的模型应用于新数据的应用称为推理。
这种训练过程是每个神经网络的核心,包括大型语言模型(LLMs)。由于神经网络在数值数据上操作,下一节将展示语言如何以数值形式表示,以便与人工神经网络的使用兼容。
自然语言处理的人工神经网络
前一节展示了人工神经网络如何学习将数值输入映射到数值输出的方法。然而,语言本质上是非数值的:一个句子是从大型词汇表中离散单词的序列。基于神经网络的词预测器提出了以下挑战:
-
模型的输入是离散的单词。由于人工神经网络在数值输入和输出上操作,需要一个合适的从单词到数字以及相反的映射。
-
输入进一步变为序列化。与二元组不同,模型在预测下一个词时应该能够考虑一个以上的单词。
-
语言模型的输出需要是所有可能下一个词的概率分布。为了形成一个合适的分布,输出需要归一化,使其非负并加起来等于一。
以下章节将解释这些挑战,并回顾它们在现代语言模型中的解决方法。
分词
将文本转换为数值输入的第一个处理步骤称为分词。在这个阶段,单词被分割成常见的子词、字符和标点符号,构成了标记的词汇表。然后,每个标记被分配一个唯一的整数ID。
当与LLM交互,尤其是处理自托管的开源模型时,选择分词器非常重要,并且必须与模型训练期间使用的分词器完全匹配。幸运的是,存在许多常见的开源分词器。甚至像OpenAI这样的商业LLM提供商也已经开源了他们的分词器库,以便更容易地与其模型交互。OpenAI的tiktoken库的绑定适用于许多流行的编程语言,包括Python、C#、Java、Go和Rust。
以下代码示例演示了tiktoken Python库的使用。在用pip install tiktoken安装包之后,你可以创建一个encoder对象并编码任何文本,这将返回一个标记ID的列表。以下代码片段将句子 tiktoken is a popular tokenizer! 分词,并将每个标记ID解码回其字节字符串:
import tiktoken
# use the gpt-4 tokenizer 'cl100k_base'encoder = tiktoken.get_encoding("cl100k_base")
token_ids = encoder.encode("tiktoken is a popular tokenizer!")
print("Token IDs", token_ids)
tokens = [encoder.decode_single_token_bytes(t) for t in token_ids]print("Tokens", tokens)
运行此代码会产生以下输出:
Token IDs [83, 1609, 5963, 374, 264, 5526, 47058, 0]
Tokens [b't', b'ik', b'token', b' is', b' a', b' popular', b' tokenizer', b'!']
你可以看到单词 tiktoken 被分割成三个标记,t、ik 和 token,这很可能是由于该单词本身并不常见,不足以在词汇表中拥有自己的标记。值得注意的是,空白通常被编码为标记的一部分,例如在“ is”的开头。
当通过API与专有模型交互时,分词通常自动且在服务器端进行。这意味着你可以以文本形式提交提示,而无需自己进行分词。然而,tiktoken和类似的库在构建AI应用程序时仍然是很有用的工具。例如,你可以使用它们来计算请求的标记数量,因为API调用通常按提交和返回的标记数量计费。此外,语言模型对其输入有一个上限,称为它们的上下文大小。请求过大可能会失败或被截断,这会影响模型的响应。
在开发LLM应用程序的目的上,了解分词在文本预处理方面的知识就足够了。然而,这仅仅是使神经网络理解文本输入的第一步。尽管标记ID是数字,但标记到其ID的分配是任意的。神经网络以几何方式解释它们的输入,并且不适合处理大整数。在第二步,称为嵌入,这些整数被转换成高维浮点向量,也称为嵌入向量或简称为嵌入。
嵌入
嵌入是将数据映射到高维向量空间的过程。这个概念不仅与语言模型的训练相关,而且对于向量数据库检索语义相似项也将发挥重要作用,我们将在后面的 第 5 章,向量数据库 中讨论。可以为任意数据实体创建嵌入:单词、句子、整个文档、图像,甚至是更抽象的概念,例如在构建推荐系统时上下文中的用户或产品。
嵌入的目的有两个:
-
它们是它们对应实体的固定长度浮点数表示,非常适合由神经网络处理。
-
嵌入是向量空间中的坐标。通过正确的选择(或者更确切地说,是训练),嵌入可以通过数据实体的几何邻近性来表示语义相似性。这使得可以使用几何算法,如聚类或最近邻搜索,在嵌入数据的语义意义上进行操作。
嵌入是语言模型和向量搜索核心的基本概念。为了理解标记如何被嵌入,让我们假设一个只有三个维度的微小向量空间,如图 图 3**.4 所示。要将一个标记映射到这个空间,为每个标记分配这个空间中的一个随机点。在这里,标记由其整数 ID 表示,而空间中的随机点由其 x、y 和 z 坐标指示。映射是通过一个由 n 行和 d 列组成的嵌入矩阵完成的,该矩阵用随机浮点数初始化。在这里,n 是词汇表的大小,d 是嵌入维度性(在这个例子中,d 等于 3)。要检索一个标记的坐标,使用标记 ID 作为嵌入矩阵的行索引,返回一个 d 维向量。例如,标记 fox 可能被分配以下坐标:[-0.241, 1.356, -0.7882]。

图 3.4:嵌入到三维向量空间中的标记的视觉表示
就像在训练之前随机分配神经网络权重一样,嵌入矩阵的值也是随机选择的。此外,这是 LLM 训练中的一个关键步骤,即嵌入矩阵的值被视为神经网络的额外可学习参数。通过允许梯度完全流回嵌入层,模型可以在训练期间更新标记坐标的位置,从而有助于预测任务。
对 LLMs 的完全训练嵌入层的研究表明,模型将语义上相似的标记移动到一起。在早期示例中,您可能会找到一个名词(狐狸,狗)的簇或一个形容词(快的,懒惰的,棕色的)的簇。然而,只有三个维度,相似性仅限于三个可以比较的属性。LLMs 使用具有更大维度性的向量空间,通常在数百甚至数千维。在这样的高维空间中,标记可以通过多种方式相互关联(并在几何上彼此靠近)。一些维度可能具有可解释的意义,例如一个词的情感。然而,其中大部分可能只有模型内部有意义。
在本节中,您已经了解了如何通过将文本分割成标记并分配标记 ID 来准备文本以供神经网络训练,这些 ID 可以用作索引来在嵌入矩阵中找到相应的嵌入向量。这些向量具有几何意义,可以作为训练阶段的一部分进行更新。接下来,您将学习如何将神经网络的输出解释为选择下一个标记的概率。
预测概率分布
正如您在 n-gram 语言模型 部分中看到的,模型需要输出下一个标记的概率分布,即词汇表中的每个标记一个数值。通过选择与词汇表大小匹配的输出层大小,神经网络将给出正确的输出 形状,但这些数值在理论上可以是任何实数,包括负数或非常大的正数。
为了形成一个合适的概率分布,输出值必须满足两个附加条件:
-
输出值必须为非负。
-
输出值必须总和为 1.0。
为了达到这个目的,设计了一种特殊的激活函数称为 softmax,当期望输出概率时用于输出层。
softmax 函数的数学公式如下:![img/B22495_03_Equations14.png]
直观上,分子中使用指数函数将负无穷到正无穷的范围映射到非负数(对所有 x,见 ![img/B22495_03_Equations15.png]),通过除以所有指数的总和,将值归一化,以确保输出值的总和正好为 1。
训练模型的靶标也需要包含相同长度的向量(每个标记一个值)。由于在标记序列的每一步中下一个词是已知的,你可以使用 one-hot 编码来编码正确的标记。你可以在向量的正确标记位置分配 1.0 的值,而在所有其他位置分配 0.0,如图 图 3**.5 所示。这确保了在反向传播过程中,看到正确下一个标记的概率增加,而所有其他概率减少。
![img/B22495_03_05.jpg]
图3.5:模型预测的示例输出概率和标记fox的目标
通过标记化、嵌入和softmax激活,你可以将语言转换为ANN可以理解的数字格式。此外,ANN可以将模型的数字输出解释为下一个标记的离散概率分布。用ANN模型语言所需的最后一部分是处理序列,这将在下一节讨论。
处理序列数据
为了产生好的下一个标记预测,语言模型需要能够考虑一个相当大的上下文,回溯许多单词甚至句子。
为了证明这一点,考虑以下文本:
一只孤独的老虎在茂密的丛林中悄悄跟踪它的猎物。灌木丛在 它 攻击时低语,隐藏 它 向一只 毫无戒备的小鹿 前进的行踪。
在这个例子中的第二句话包含两个代词,it 和 its(如上所示加粗),都指代上一句话中的老虎,相隔许多单词。但是,如果没有看到第一句话,你可能会假设it指的是灌木丛,这将导致一个非常不同的句子结尾,例如这个:
灌木丛在 它 轻轻摇曳于 柔和的微风 中低语。
这表明对于语言建模和下一个标记预测来说,长距离上下文很重要。你可以构建任意长度的例子,其中代词解析依赖于许多句子之前提供的上下文。这些时间依赖性和歧义是自然语言固有的,因此一个好的语言模型需要处理长序列的单词。
然而,之前介绍的FFN架构是无状态的,并且不具备任何先前输入的记忆。它不适合序列任务,在这些任务中,未来的标记依赖于并指向前一个标记。
序列学习是机器学习中的一个基本问题,不仅对NLP,对许多其他领域也是如此,如时间序列预测、语音识别、视频理解、机器人控制等。在某些情况下,输入是序列的,在其他情况下,输出是序列的,甚至两者都是。已经提出了对FFN架构的不同修改来解决这个问题。
循环神经网络
一类处理序列数据的ANNs被称为循环神经网络(RNNs)。与FFNs不同,RNNs在同一层中包括从神经元到自身及其相邻神经元的连接。这些循环连接给模型提供了一个内部状态,其中之前的激活可以以循环方式流动,并在处理下一个输入时留在网络中,如图图3**.6所示:

图3.6:循环连接赋予RNNs内部状态
RNN的训练仍然类似于FFNs,其中RNN可以展开到时间步,并在概念上转换为一个FFN(尽管有更多层和对应于内部状态的增加输入)。
然而,RNN的一个局限性是,随着每次通过循环连接,梯度会迅速减小。网络往往倾向于忘记超过几个时间步的激活,这是一个被称为梯度消失的问题。
为了解决这个问题,已经提出了进一步的架构变化,包括长短期记忆(LSTM)和门控循环单元(GRU)网络。在这些模型中,引入了由多个神经元组成的细胞,这些细胞可以在数千个时间步内捕获梯度信号,从而缓解梯度消失问题。
LSTM已经在许多序列问题领域得到成功应用,包括机器人技术、语音和手写识别、语言翻译以及玩电子游戏。
然而,循环网络的训练是沿着时间维度顺序进行的,这意味着每个时间步都需要通过网络的单独正向和反向传递。这显著减缓了训练速度,尤其是在处理长序列时。
RNNs还有一个限制。尽管由于循环连接,网络原则上可以记住之前的激活,但这种内部状态需要为每个时间步传递。该模型无法直接访问全局上下文及其先前输入。
这两个限制在2017年的一个突破性发现中得到解决,将在下一节中讨论。
Transformer架构
2017年,谷歌发布了一种新的网络架构,旨在解决循环网络的一些缺点。这篇现在著名的论文,题为《Attention Is All You Need》(https://arxiv.org/abs/1706.03762),介绍了Transformer架构,该架构摒弃了循环连接的想法,转而依赖于注意力机制来考虑无状态神经网络中的先前标记。这一发表标志着机器学习和自然语言处理领域的一个重大转变,并为几乎所有现代LLM作为原始Transformer的变体铺平了道路。
与循环网络相比,它们的优点包括能够并行处理序列、长序列的计算复杂度降低以及处理长距离依赖关系的优越性,这些都是Transformer架构在NLP领域以及更广泛领域变得普遍的关键原因。
从高层次来看,原始的变换器模型由两个组件组成:一个编码器和一个解码器。这种架构是为了语言翻译的目的而提出的,这是一个序列到序列的学习任务,输入序列是源语言中的标记序列,由编码器处理,输出序列是目标语言中的标记序列,由解码器处理。
虽然一些LLMs仍然使用这种编码器/解码器结构,但如今其他一些模型家族使用简化的架构,仅基于编码器(例如BERT语言模型及其变体)或解码器(GPT系列)。包括OpenAI的GPT系列、Meta的Llama、Anthropic的Claude和Google的PaLM模型在内的生成模型,都将语言建模视为下一个标记的预测,其中学习任务是序列到-单个标记,与编码器/解码器结构中的序列到序列相比。这允许有一个更简单的架构,去掉了编码器,只使用变换器的解码器部分。
变换器的编码器和解码器都由许多所谓的变换器块组成。与FFNs不同,FFNs中的每一层只是下一个神经元的全连接层,变换器块在完全连接层之前还有一个额外的注意力层。
注意力层的目的是在学习处理当前标记时,确定到目前为止看到的序列中哪些标记最为相关。它会给当前上下文中高度相关的单词分配高注意力权重,而对于通用或不相关的单词则分配低注意力权重,正如你在图3.7中可以看到的。7*:

图3.7:以饥饿结尾与以美味结尾的两个句子变体的注意力图
图3.7显示了两个句子(只有最后一个单词不同)的注意力图。较深的颜色阴影表示更高的注意力权重。变换器模型会学会更加关注与饥饿相关的标记,例如第一个例子中的猫,以及在第二个例子中与美味相关的标记,例如食物。
这种注意力机制是变换器的关键。关于变换器架构的里程碑论文证明了仅此机制就能解决序列数据问题,而无需在架构中引入循环连接。
实际中的LLMs
到目前为止,本章主要讨论了LLMs的理论基础。让我们以对当前LLMs景观的概述来结束本章,讨论一些选择合适的LLMs的考虑因素以及不同的技术来调整模型响应以满足你的需求。
LLMs的发展领域
生成式AI和LLMs是一个快速发展的领域,关于这个主题的新模型、框架和研究论文频繁发布。训练LLM的大部分知识都是公开可用的,但截至写作时,从头开始训练最先进的LLM的成本仍然在数千万到数亿美元之间,这是因为需要大量的GPU计算资源。这种成本使得个人和大多数小型公司无法训练自己的模型,他们必须依赖预训练的LLMs。
截至写作时,最优秀的模型是OpenAI的GPT-4o (https://openai.com/) 和 Anthropic的Claude 3.5 Sonnet (https://www.anthropic.com/),它们仍然是闭源模型,但可以通过API按每个标记的成本模式访问。开源模型,如Meta的Llama 3 (https://llama.meta.com/),在常见基准测试中仍然落后,但差距正在迅速缩小。根据你的用例和吞吐量要求,自己托管一个开源模型或选择提供模型托管服务的众多提供商之一可能更经济高效。
在选择开源和闭源模型时,其他考虑因素包括安全性和合规性、技术支持和供应商锁定。商业LLM产品通常附带技术支持和审查端点,以过滤非法请求和有害或令人反感的内 容,并提供其API和模型的详细文档。相比之下,开源模型提供更多灵活性和定制化,以及与其他模型的透明度和互操作性,并避免潜在的供应商锁定。
提示、微调和RAG
LLMs接受以文本提示(或简称提示)形式输入,可以是问题、陈述或请求,这些可以引导模型生成响应。虽然最好的LLMs在回答各种不同请求方面非常能干且高效,但很可能一个简单的提示不会导致你应用中可接受的结果。你的用例可能需要特殊领域知识或以不常见的(自然或编程)语言进行响应,而这些语言在原始训练数据集中代表性不足,或者你可能正在处理专有且非公开的数据。这不会阻止你将LLMs集成到你的应用中。有几种策略可以应对这种场景:
-
不同的提示策略
-
在自定义数据上微调LLM
-
检索增强生成(RAG)
对LLM进行提示更像是一门艺术而不是一门硬科学,这导致了软件开发中全新的“提示工程师”角色的出现。常见的技巧包括零样本和少量样本提示以及思维链提示。对于更高级的提示技术,您可以参考https://www.promptingguide.ai/上的提示工程指南。你将在第9章,LLM 输出评估中了解更多不同的提示策略。
为了获得更加定制化的响应,可以通过称为微调的过程,在您自己的特定数据上进一步训练预训练的LLM。微调允许调整响应的语言和风格,并将领域知识注入到LLM中。然而,这个过程可能因数据集的大小而变得昂贵。微调模型需要仔细评估,因为调整权重可能会导致过拟合,这可能会影响模型在先前任务上的响应。
检索增强生成(RAG)是将专有数据的外部知识注入到LLM中的另一种策略。在这里,每个请求首先使用外部知识库(例如,一个向量数据库,你将在第5章,向量数据库)进行查询,然后相关的外部数据源信息被包含在LLM提示中。虽然这缓解了一些微调的缺点,但一个限制因素是LLM在单个请求中可以处理的提示长度(上下文大小)。因此,过滤掉无关信息以保持提示大小可管理是很重要的。
摘要
本章涵盖了基于现代transformer的LLM的主要组件以及当前LLM景观的快速概述。
它详细说明了如何将文本转换为数值数据,以便由ANN处理。总结来说,大型文本语料库的句子被分词并分配整数分词ID。分词ID索引到嵌入矩阵中,将整数转换为固定长度的实值嵌入向量。为了创建监督训练的目标,输入向右移动一个分词,因此每个位置的目标成为序列中跟随的分词。
序列数据可以通过循环神经网络进行学习,但这些已经被使用注意力机制来学习哪些先前分词与预测下一个分词最相关的transformers所取代。在序列的每一步,模型预测词汇表中每个分词的概率,这些概率可以用来生成下一个分词。
训练数据集,由输入和目标组成,被分成更小的批次。通过在网络中反复进行正向和反向传递、梯度计算和权重调整,网络学会根据先前标记的上下文调整每个标记的概率。你学习了这些机制是如何在现代大型语言模型(LLMs)中得到应用的。你还简要了解了可以帮助你最大限度地发挥语言模型作用的一些方法。
在下一章中,你将带着对嵌入模型及其在机器学习中的关键作用的理解,将这一知识向前推进。
第四章:嵌入模型
嵌入模型是强大的机器学习技术,它将高维数据简化为低维空间,同时保留基本特征。在自然语言处理(NLP)中至关重要,它们将稀疏的词表示转换为密集的向量,捕捉词语之间的语义相似性。嵌入模型还可以处理图像、音频、视频和结构化数据,增强推荐系统、异常检测和聚类等应用。
这里是一个嵌入模型实际应用的例子。假设数据库中电影的完整情节已经使用 OpenAI 的 text-embedding-ada-002 嵌入模型预先嵌入。您的目标是找到所有与《银河护卫队》相关的电影和动画,但不是通过传统的音韵或词汇匹配(您会输入标题中的某些单词)。相反,您将通过语义方式进行搜索,例如,短语“尴尬的太空防御队”。然后,您将再次使用相同的嵌入模型来嵌入这个短语并查询嵌入的电影情节。表 4.1 展示了结果的嵌入片段:
| 维度 | 值 |
|---|---|
| 1 | 0.00262913 |
| 2 | 0.031449784 |
| 3 | 0.0020321296 |
| ... | ... |
| 1535 | -0.01821267 |
| 1536 | 0.0014683881 |
表 4.1:嵌入摘录
本章将帮助您深入了解嵌入模型。您还将使用 Python 语言和 langchain-openai 库实现一个示例。
本章将涵盖以下主题:
-
区分嵌入模型和 LLM
-
嵌入模型的类型
-
如何选择嵌入模型
-
向量表示
技术要求
要跟随本章中的示例,您需要以下先决条件:
-
一个 MongoDB Atlas 集群。一个
M0免费集群应该足够了,因为您将存储一小组文档并仅创建一个向量索引。 -
一个 OpenAI 账户和 API 密钥,可以访问
text-embedding-3-large模型。 -
一个 Python 3 工作环境。
您还需要安装 MongoDB、LangChain 和 OpenAI 的 Python 库。您可以在 Python 3 环境中按照以下步骤安装这些库:
%pip3 install --upgrade --quiet pymongo pythondns langchain langchain-community langchain-mongodb langchain-openai
要成功执行本章中的示例,您需要在 MongoDB Atlas 集群上创建一个 MongoDB Atlas 向量索引。索引名称必须是 text_vector_index,在 embeddings.text 集合上创建,如下所示:
{
"fields": [
{
"numDimensions": 1024,
"path": "embedding",
"similarity": "cosine",
"type": "vector"
}
]
}
嵌入模型是什么?
嵌入模型是机器学习和人工智能中用于简化大型和复杂数据的工具类型,将数据简化为更易于管理的形式。这个过程称为嵌入,涉及减少数据的维度。
想象一下从包含高速公路、铁路、河流、小径等详细的世界地图,转换到一个只有国家边界和首都的简化、总结版本。这不仅使计算更快、资源消耗更少,还有助于识别和理解数据中的关系。由于嵌入模型简化了大数据集的处理和分析,它们在语言(文本)处理、图像和声音识别以及推荐系统等领域特别有用。
考虑一个庞大的图书馆,其中每本书代表高维空间中的一个点。嵌入模型可以帮助重新组织图书馆,以改善导航的便捷性,例如通过将相关主题的书籍聚集在一起并减少图书馆的整体规模。图4.1展示了这一概念:

图4.1:用于图书馆用例的嵌入模型示例
这种从高维或原始表示到低维表示的转换或降低,为自然语言处理(NLP)、计算机视觉等领域的发展奠定了基础。
嵌入模型与LLMs有何不同?
嵌入模型是专门算法,可以将高维数据(如文本、图像或声音)降低到密集向量的低维空间。另一方面,LLMs是预先在庞大的文本语料库上训练的有效人工神经网络。
虽然两者都基于神经网络,但它们采用了不同的方法。大型语言模型(LLMs)旨在生成连贯且与上下文相关的文本。LLMs利用大量数据来理解和预测语言模式。它们的基本构建块包括变换器架构、注意力机制以及大规模预训练后进行微调。
相比之下,嵌入模型专注于将单词、短语甚至整个句子映射到密集的向量空间中,其中语义关系得到保留。它们通常使用诸如对比损失等技术,这些技术有助于在训练期间区分相似和不同的成对项。正负样本采样是嵌入模型采用的另一种技术。正样本是相似项(如同义词或相关句子),而负样本是不相似项(如无关的单词或句子)。图4.2展示了在二维空间中对比损失和正负样本采样的一个示例。这种采样有助于模型通过最小化正样本对之间的距离并最大化负样本对之间的距离在向量空间中学习有意义的表示。

图4.2:对比损失和正负样本采样的二维可视化
总结来说,虽然 LLMs 在语言生成任务上表现出色,但嵌入模型优化于捕捉和利用语义相似性。两者都通过使机器更有效地理解和生成人类语言来增强自然语言处理(NLP)。现在,让我们看看每个的例子。
Word2vec(由 Google 开发)将单词转换为向量,并辨别语义关系,例如“king”对“man”就像“queen”对“woman”。它在情感分析、翻译和内容推荐方面很有用,增强了机器的自然语言理解。
GPT-4(由 OpenAI 开发)是一种 LLM,其特点是根据接收到的输入生成类似人类的文本。GPT-4 在一系列基于语言的任务上表现出色,包括对话、内容生成、摘要和翻译。其架构允许它理解语言的复杂细节和细微差别,使其能够执行需要深入理解上下文、幽默、讽刺和文化引用的任务。
何时使用嵌入模型与 LLMs
嵌入模型用于那些旨在捕捉和利用数据内部关系的场景。它们是以下任务的理想选择:
-
语义相似性:找到或推荐与给定项目类似的项目(如文档或产品)。
-
聚类:根据实体的语义属性进行分组。
-
信息检索:通过理解查询的语义内容来增强搜索功能。
LLMs 是处理需要文本理解、生成或两者兼备的任务的首选,例如以下任务:
-
内容创作:生成连贯、上下文相关且风格适当的文本。例如,从一部电影的完整剧情中生成摘要。
-
对话式人工智能:构建能够理解和参与类似人类对话的聊天机器人和虚拟助手,例如回答有关就业政策和员工福利的问题。
-
语言翻译:在语言多样化的数据集上进行广泛训练,使得大型语言模型(LLMs)能够处理惯用语、文化细微差别和专门术语。
嵌入模型和 LLMs 都在人工智能中扮演着至关重要的角色。嵌入模型紧凑地捕捉和操作语义属性,而 LLMs 在生成和解释文本方面表现出色。使用两者,并根据您的目标选择合适的嵌入模型,可以释放您项目中人工智能的完整潜力。
嵌入模型类型
单词级模型,包括 全局单词表示向量(GloVe)和 从 Transformer 获取的双向编码器表示(BERT),捕捉更广泛的文本意义。专门模型如 fastText 适应语言挑战。所有这些都反映了嵌入模型不断发展的格局。
在本节中,您将探索许多类型的嵌入模型:单词、句子、文档、上下文、专门化、非文本和多模态。
单词嵌入
n 个相邻符号按特定顺序排列),这有助于更好地处理前缀、后缀和罕见词语。Word2vec 和 GloVe 是这些模型的例子。
Word2vec 是嵌入模型首次尝试根据词语的上下文相似性将词语表示为向量的方法。由谷歌团队开发,它使用两种架构:连续词袋模型(CBOW),它根据上下文预测一个词语,以及跳字模型,它根据一个词语预测上下文。Word2vec 已被证明能够捕捉词语的句法关系,这体现在它能够从与词语向量进行的算术运算中推断出意义。
GloVe,由斯坦福大学开发,结合了两种领先的词语表示方法的优点:全局矩阵分解与共现统计和上下文窗口方法。通过从语料库构建共现矩阵并应用降维技术,GloVe 捕捉了全局统计和局部上下文,这对于需要深入理解词语关系的任务来说是无价的。
句子和文档嵌入
句子和文档嵌入模型通过考虑词语上下文和排列来捕捉文本块的整体语义意义。一种常见的方法是将词语向量聚合为一个连贯的向量,用于整个文本单元。这些模型在文档相似性、信息检索和文本摘要(如摘要与完整电影剧情)等方面非常有用。显著的模型包括 Doc2vec 和 BERT。
基于 Word2vec,Doc2vec(也称为段落向量),将整个句子或文档封装为向量。引入一个文档 ID 标记,允许模型在词语嵌入的同时学习文档级别的嵌入,这在文档分类和相似性比较等任务中起到了显著的帮助作用。
谷歌的 BERT 采用上下文感知嵌入,同时读取整个单词序列,与先前的线性处理文本的前辈不同。这种方法使得 BERT 能够从所有周围词语中理解一个词语的上下文,从而产生更动态和细微的嵌入,并在各种自然语言处理任务中设定了新的标准。
上下文嵌入
上下文嵌入模型旨在生成根据句子中使用的上下文而变化的词向量。这些模型通过检查整个句子,有时是周围的句子,使用深度学习架构。上下文模型产生动态嵌入,根据单词的特定上下文和语言环境捕捉细微差别。这种类型的模型架构使用双向框架来处理文本的前向和反向,从而捕捉前后的上下文中的精细语义和句法依赖关系。它们在情感分析(如解释IT支持票中的文本语气)和需要精确解释单词意义的问答任务中很有用。ELMo和GPT是两个例子。
语言模型嵌入(ELMo)引入了动态、上下文相关的嵌入,根据单词的语言环境产生可变的嵌入。这种方法通过提供更丰富的语言理解,大大提高了下游NLP任务的性能。
OpenAI的GPT系列利用转换器技术,在大量文本语料库上预训练嵌入,并针对特定任务进行微调。GPT的成功强调了将大规模语言模型与转换器架构相结合在自然语言处理中的有效性。
专用嵌入
专用嵌入模型能够捕捉特定语言属性,例如地点、人物、语气和情绪,在向量空间中。有些是针对特定语言或方言的,而有些则分析情感和情感维度。应用包括法律文件分析、支持票务分类、市场营销中的情感分析和多语言内容管理。
fastText是专用嵌入模型的一个例子。由Facebook的人工智能研究实验室开发,fastText通过将单词视为字符n-gram的包来增强Word2vec,这对于处理词汇表外(OOV)单词特别有帮助。OOV单词是在训练期间没有看到的单词,因此缺乏预学习的向量表示,这对传统模型构成了挑战。fastText通过其子词嵌入的总和来实现OOV单词的嵌入。这使得它特别适合处理罕见单词和形态学复杂的语言,这些语言具有丰富和多样的词结构,使用大量的前缀、后缀和屈折变化来传达不同的语法意义,例如芬兰语、土耳其语和阿拉伯语。
其他非文本嵌入模型
嵌入模型不仅限于将文本转换为向量表示。图像、音频、视频,甚至JSON数据本身都可以以向量形式表示:
-
图像:例如视觉几何组(VGG)和残差网络(ResNet)等模型为将原始图像转换为密集向量设定了基准。这些模型捕捉了重要的视觉特征,如边缘、纹理和颜色梯度,这些特征对于许多计算机视觉任务至关重要,包括图像分类和物体识别。VGG在识别视觉模式方面表现良好,而ResNet在复杂的图像处理任务中提高了准确性,例如图像分割或照片标记。
-
音频:OpenL3和VGGish是音频模型。OpenL3是一个从L3-Net架构中改编的模型,用于音频事件检测和环境声音分类,将音频嵌入到时间和频谱上下文丰富的空间中。VGGish源于图像的VGG架构,因此遵循将声波转换为小型、紧凑向量模式的相同原则。这简化了诸如语音和音乐类型识别等任务。
-
视频:3D卷积神经网络(3D CNNs或3D ConvNets)和膨胀3D(I3D)扩展了图像嵌入在感知动作识别和视频内容分析中至关重要的时间动态的能力。3D ConvNets在三维(高度、宽度、时间)上应用卷积滤波器,捕捉体积数据中的空间和时间依赖性,使它们特别适用于时空数据,如视频分析、医学成像和3D物体识别。I3D使用时空架构,结合两个3D ConvNets的输出:一个处理RGB帧,而另一个处理连续帧之间的光流预测。I3D模型对于体育分析和监控系统很有用。
-
图数据:Node2vec和DeepWalk捕捉图中节点的连接模式,并应用于社交网络分析、欺诈检测和推荐系统等领域。Node2vec通过在图上执行有偏随机游走来学习节点的连续向量表示,这捕捉了多样的节点关系和社区结构,从而提高了节点分类和链接预测等任务的性能。DeepWalk将随机游走视为节点序列,类似于自然语言处理中的句子,通过捕捉节点之间的结构关系并将它们编码为连续向量表示,这些表示可用于节点分类和聚类。
-
JSON数据:甚至有JSON数据嵌入模型,例如Tree-LSTM,这是一种传统的长短期记忆(LSTM)网络的变体,专门用于处理具有层次树结构的JSON等数据。与按顺序处理数据的标准LSTM单元不同,Tree-LSTM通过将多个子节点的状态合并到父节点中,在树结构数据上操作,有效地捕捉嵌套结构中的依赖关系。这使得它特别适合语义解析和情感分析等任务,在这些任务中,理解数据中的层次关系可以显著提高性能。json2vec是这种嵌入模型的一种实现。
在单模态模型之后,你可以探索多模态模型。这些模型能够同时分析多种数据类型,对于自动驾驶等应用至关重要,在这些应用中,从传感器、摄像头和激光雷达中合并数据可以构建对驾驶环境的全面视图。
多模态模型
多模态嵌入模型处理和整合来自多种数据源的信息到一个统一的嵌入空间。当不同模态相互补充或加强,并共同导致更好的AI应用时,这种方法非常有用。多模态模型非常适合深入理解多感官输入内容,例如多媒体搜索引擎、自动内容审核和可以通过视觉和语言交互吸引用户的交互式AI系统。以下是一些例子:
-
CLIP:OpenAI的一个知名多模态模型。它学习如何将视觉图像与文本描述相关联,以便在训练期间根据自然语言查询识别它从未见过的图像。
-
LXMERT:一个专注于处理视觉和文本输入的模型。它可以提高包括目标检测在内的视觉问答等任务的性能。
-
ViLBERT:视觉和语言BERT(ViLBERT)通过使用双流模型扩展了BERT架构,该模型能够同时处理视觉和文本输入。其中一个流使用预训练的卷积神经网络(CNN或ConvNet)从图像中提取视觉特征,另一个流使用交叉注意力层处理文本数据,促进两种模态之间的交互。ViLBERT用于视觉问答和视觉常识推理等任务,在这些任务中,理解图像和文本之间的关系至关重要。
-
VisualBERT:通过结合图像特征和来自类似BERT架构的上下文化词嵌入来整合视觉和文本信息。它通常用于图像-文本检索和图像字幕等任务,在这些任务中,对视觉和文本信息的对齐和理解至关重要。
你现在已经探索了单词、图像和多模态嵌入。接下来,你将学习如何根据应用程序的需求选择嵌入模型。
选择嵌入模型
嵌入模型影响应用程序的性能、理解语言和其他形式数据的能力,以及最终项目的成功。以下几节将探讨选择与任务需求、数据集特征和计算资源相匹配的正确嵌入模型的参数。本节解释了向量维度和模型排行榜,作为选择嵌入模型时需要考虑的附加信息。为了快速了解本节内容,你可以参考表 4.2。
任务需求
根据它们处理和表示文本数据的方式,每种任务可能从不同的嵌入模型中受益。例如,文本分类和情感分析等任务通常需要深入理解单词层面的语义关系。在这种情况下,Word2vec 或 GloVe 特别有益,因为它们提供了强大的单词级嵌入,能够捕捉语义含义。
对于更复杂的语言任务,如命名实体识别(NER)和词性标注(POS),理解单词使用的上下文的能力变得至关重要。在这里,BERT 或 ELMo 等模型显示出它们的优势,因为它们根据周围的文本动态生成嵌入,提供了对每个单词在句子中角色的更丰富和更精确的理解。这种深层次的上下文意识对于准确识别实体和标注词性至关重要,因为它允许模型根据其用法区分具有多个含义的单词。
如 BERT、GPT 和 Doc2vec 这样的高级模型非常适合需要细微语言理解的任务,如问答、机器翻译、文档相似性和聚类。这些模型处理文本中的复杂依赖关系,使它们适合分析整个文档。Doc2vec 在比较文档之间的主题相似性方面表现出色,例如找到相似的新闻或体育文章。
数据集特征
在选择嵌入模型时,请考虑数据集的大小和特征。对于形态丰富的语言或包含许多未知单词的数据集,如 fastText 这样的模型具有优势,因为它们能够捕捉子词信息,有效地处理新词或罕见词。对于含有多义词(具有多个含义的词)的文本,如 ELMo 或 BERT 这样的上下文嵌入至关重要,因为它们提供动态的、特定于上下文的表现形式。
数据集的大小影响嵌入模型的选择。大型数据集从复杂模型中受益,如BERT、GPT和OpenAI的text-embedding-3-large,这些模型能够捕捉深层次的语用细微差别,但需要大量的计算能力。小型数据集可能从简单模型中受益,如text-embedding-3-small,提供稳健的性能,同时计算需求较低。这确保了即使是小型数据集也能通过适当的模型获得重要的见解。
计算资源
由于资源需求不同,计算成本在选择嵌入模型时至关重要。像GPT-4这样的大型模型需要大量的计算能力,这使得它们对预算有限的小型组织或项目来说不太容易获得。
选择轻量级模型或针对特定任务进行微调可以减少计算需求,加快开发速度,并提高响应时间。高效模型对于实时任务至关重要,如翻译、语音识别以及游戏、媒体流和电子商务中的即时推荐。
一定程度的迭代实验有助于确定最合适的模型。关注最新发展至关重要,因为新模型经常取代旧模型。模型排行榜可以帮助跟踪该领域的进展,将在本节后面进行介绍。
向量表示
嵌入模型中向量的尺寸影响其捕捉数据复杂性的能力。大向量编码更多信息,允许更精细的区分,但需要更多的计算。小向量更高效,但可能会错过细微的差别。选择向量大小需要平衡详细表示与实际约束,如内存和速度。
向量维度为什么很重要?
了解向量、其尺寸和神经网络倒数第二层之间的关系对于理解模型输出的质量至关重要。倒数第二层或第二层通常作为特征提取器,其中输出向量的维度代表输入数据学习到的特征,如图图4**.3所示。这个向量的尺寸直接影响表示的粒度。

图4.3:神经网络的最后一层
为了获得这些向量,神经网络输出层(最后一层)被移除,并捕获前一层的输出——倒数第二层或第二层。通常,最后一层输出模型的预测,这促使使用其前一层的输出。输入到网络预测层的这些数据被称为向量嵌入。
向量嵌入的维度与所使用模型的底层神经网络的倒数第二层的大小相匹配,使其与向量的尺寸或长度同义。例如,384(由SBERT的all-MiniLM-L6-v2提供),768(由SBERT的all-mpnet-base-v2提供),1,536(由OpenAI的text-embedding-ada-002提供),以及2,048(由微软研究机构的ResNet-50提供)是常见的维度。现在,更大的向量也变得可用,例如OpenAI的text-embedding-3-large提供的3,072。
向量嵌入是什么意思,它通常是如何被使用的?
向量嵌入是嵌入模型的输出,表示为浮点数数组,通常范围从-1.0到+1.0。数组中的每个位置代表一个维度。
向量嵌入在上下文检索用例中扮演着关键角色,例如聊天机器人的语义搜索。数据预先嵌入并存储在向量数据库中,查询必须使用相同的嵌入模型才能获得准确的结果。每个嵌入模型根据其训练数据产生独特的嵌入,这使得它们特定于模型的领域,不可互换。例如,从训练在法律全文上的模型获得的嵌入将与训练在患者病史上的医疗数据模型获得的嵌入不同。
你可能还记得本章开头尝试为银河护卫队寻找电影的例子。你现在应该明白为什么你不得不使用相同的嵌入模型来嵌入搜索字符串(也称为查询向量)。这种在AI应用中常见的流程,在图4.4中得到了解释。4*:

图4.4:将源数据嵌入向量存储和查询向量的典型数据流
工作流程显示了两次“转换为嵌入”步骤:一次是将现有数据嵌入到向量数据库中(在左侧),另一次是实时嵌入查询(在右侧)。这两个步骤都必须使用相同的嵌入模型。
嵌入模型排行榜
面对如此众多的现有模型和不断涌现的新模型,你如何保持更新?嵌入模型排行榜,如Hugging Face等平台提供的,有助于评估各种模型在众多任务中的性能。它们基于准确性、效率等标准提供透明且具有竞争力的模型排名。通过将模型与标准数据集和基准任务进行比较,这些排行榜可以精确地指出最先进的模型及其权衡。
Hugging Face的大规模文本嵌入基准(MTEB)排行榜是一个关键资源。它提供了文本嵌入模型性能基准的全面概述。要查看哪些模型正在设定标准,请访问Hugging Face MTEB排行榜:https://huggingface.co/spaces/mteb/leaderboard。
在选择您的 AI/ML 应用架构组件时,您还可以参考其他排行榜。Hugging Face 托管了 Open LLM 排行榜(https://huggingface.co/spaces/open-llm-leaderboard/open_llm_leaderboard)以及特定语言的排行榜,例如 Open 葡萄牙 LLM 排行榜、Open Ko-LLM 排行榜(韩语)和西班牙嵌入排行榜。甚至还有行业特定的排行榜,例如 Open 医疗-LLM 排行榜。
嵌入模型概述
表 4.2 提供了本章中涵盖的一些嵌入模型的快速概述,重点关注其质量和易用性。每个模型的描述都包括基于下游任务中的准确性、语义表示的丰富性、易用性、文档质量以及计算需求等因素的嵌入质量。
| 嵌入模型 | 嵌入质量 和易用性 |
|---|---|
| Word2vec | 高质量、上下文丰富的嵌入。可在 TensorFlow 等平台上使用,但在线上可用性有限。 |
| GloVe | 强健的嵌入,尤其是对于不常用词汇。可在 TensorFlow 等平台上使用,但在线上可用性有限。 |
| BERT | 丰富且适应性强的上下文嵌入。可在网上找到。 |
| GPT | 在生成和语言理解任务中表现出色的优质嵌入。可在网上找到。 |
| Doc2vec | 适用于文档级任务;嵌入反映了比词级模型更广泛的上下文。 |
| fastText | 有效地捕捉 OOV(Out-of-Vocabulary)词汇。开源且非常轻量级。在标准硬件上运行,可以生成足够小的模型,适用于移动设备。 |
text-embedding-3-large |
适用于复杂 NLP 任务的优质嵌入,能够捕捉细微的上下文。取代了 OpenAI 的 text-embedding-ada-002。在保持高嵌入质量的同时,可以生成更小的向量。 |
text-embedding-3-small |
适用于标准 NLP 任务的优质嵌入,平衡性能和计算需求。 |
表 4.2:各种嵌入模型中的嵌入质量和易用性
虽然这个比较应该作为选择最适合特定需求的嵌入模型的指南,但鉴于该领域快速发展的态势,应始终参考之前提到的 MTEB 排行榜以及在线文档。
你是否总是需要一个嵌入模型?
不,你并不总是需要一个嵌入模型。并非所有情况都需要嵌入模型的复杂细节来以所需的向量形式表示数据。对于某些应用,更直接的向量化方法就足够了。
在某些情况下,复杂的公共嵌入模型或定制模型是不必要的。那些具有狭窄焦点、明确规则或结构化数据的任务可以在简单的向量表示上蓬勃发展。这种方法适用于简单的聚类、精确的相似度测量以及计算能力有限的场合。
例如,独热编码是一种简单技术,将分类数据转换为二进制向量,非常适合那些类别是名义的且没有内在顺序的情况。同样,词频-逆文档频率(TF-IDF)向量巧妙地传达了文本的重要性,通过突出显示文档中术语与整个语料库的相关性来用于信息检索和排名任务。
这些替代方案可能缺乏嵌入模型的语义深度,但为那些不需要复杂上下文的任务提供了计算效率和简单性。选择简单的向量表示增强了透明度,减少了计算需求或高级科学技能,并且对于快速性能或资源受限的环境(如嵌入式系统或移动设备)来说非常理想。
在你建立了嵌入模型的理解之后,你现在可以继续使用Python、LangChain、MongoDB Atlas和OpenAI进行实际演示。
执行LangChain中的代码
现在你已经探索了各种嵌入模型类型,你将看到使用它们的工作代码是什么样的。以下Python脚本(命名为semantic_search.py)使用langchain-openai库,结合OpenAI的text-embedding-3-large模型嵌入文本数据,该模型定制为产生1,024维向量而不是3,072维:
import os, pprint, time
from langchain_mongodb import MongoDBAtlasVectorSearch
from langchain_openai import OpenAIEmbeddings
from pymongo import MongoClient
os.environ["OPENAI_API_KEY"] = "YOUR-OPENAI-API-KEY"
MONGODB_ATLAS_CONNECTION_STRING = "YOUR-MONGODB_ATLAS-CONNSTRING"
client = MongoClient(MONGODB_ATLAS_CONNECTION_STRING, tls=True, tlsAllowInvalidCertificates=True)
db_name = "embeddings"
collection_name = "text"
coll = client[db_name][collection_name]
vector_search_index = "text_vector_index"
coll.delete_many({})
texts = []
texts.append("A martial artist agrees to spy on a reclusive crime lord using his invitation to a tournament there as cover.")
texts.append("A group of intergalactic criminals are forced to work together to stop a fanatical warrior from taking control of the universe.")
texts.append("When a boy wishes to be big at a magic wish machine, he wakes up the next morning and finds himself in an adult body.")
embedding_model = OpenAIEmbeddings(
model="text-embedding-3-large",
dimensions=1024,
disallowed_special=()
)
embeddings = embedding_model.embed_documents(texts)
docs = []
for i in range(len(texts)):
docs.append(
{
"text": texts[i],
"embedding": embeddings[i]
}
)
coll.insert_many(docs)
print("Documents embedded and inserted successfully.")
time.sleep(3) # allow vector store (Atlas) to undergo indexing
semantic_queries = []
semantic_queries.append("Secret agent captures underworld boss.")
semantic_queries.append("Awkward team of space defenders.")
semantic_queries.append("A magical tale of growing up.")
vector_search = MongoDBAtlasVectorSearch(
collection= coll,
embedding= OpenAIEmbeddings(
model="text-embedding-3-large",
dimensions=1024,
disallowed_special=()),
index_name= vector_search_index
)
for q in semantic_queries:
results = vector_search.similarity_search_with_score(
query = q,
k = 3
)
print("SEMANTIC QUERY: " + q)
print("RANKED RESULTS: ")
pprint.pprint(results)
print("")
控制台输出将如下所示:
(myenv) % python3 semantic_search.py
0
1
2
Documents embedded and inserted successfully.
SEMANTIC QUERY: Secret agent captures underworld boss.
RANKED RESULTS:
[(Document(metadata={'_id': '66aada5537ef2109b3058ccb'}, page_content='A martial artist agrees to spy on a reclusive crime lord using his invitation to a tournament there as cover.'),
0.770392894744873),
(Document(metadata={'_id': '66aada5537ef2109b3058ccc'}, page_content='A group of intergalactic criminals are forced to work together to stop a fanatical warrior from taking control of the universe.'),
0.6555435657501221),
(Document(metadata={'_id': '66aada5537ef2109b3058ccd'}, page_content='When a boy wishes to be big at a magic wish machine, he wakes up the next morning and finds himself in an adult body.'),
0.5847723484039307)]
SEMANTIC QUERY: Awkward team of space defenders.
RANKED RESULTS:
[(Document(metadata={'_id': '66aada5537ef2109b3058ccc'}, page_content='A group of intergalactic criminals are forced to work together to stop a fanatical warrior from taking control of the universe.'),
0.7871642112731934),
(Document(metadata={'_id': '66aada5537ef2109b3058ccb'}, page_content='A martial artist agrees to spy on a reclusive crime lord using his invitation to a tournament there as cover.'),
0.6236412525177002),
(Document(metadata={'_id': '66aada5537ef2109b3058ccd'}, page_content='When a boy wishes to be big at a magic wish machine, he wakes up the next morning and finds himself in an adult body.'),
0.5492569208145142)]
SEMANTIC QUERY: A magical tale of growing up.
RANKED RESULTS:
[(Document(metadata={'_id': '66aada5537ef2109b3058ccd'}, page_content='When a boy wishes to be big at a magic wish machine, he wakes up the next morning and finds himself in an adult body.'),
0.7488957047462463),
(Document(metadata={'_id': '66aada5537ef2109b3058ccb'}, page_content='A martial artist agrees to spy on a reclusive crime lord using his invitation to a tournament there as cover.'),
0.5904781222343445),
(Document(metadata={'_id': '66aada5537ef2109b3058ccc'}, page_content='A group of intergalactic criminals are forced to work together to stop a fanatical warrior from taking control of the universe.'),
0.5809941291809082)]
示例设置了环境,使用API密钥对OpenAI进行身份验证,并连接到MongoDB Atlas。然后嵌入并存储在MongoDB Atlas(向量存储)中的三个电影的图,然后执行不同的向量搜索以展示具有排序结果的语义搜索。
最佳实践
选择最合适的嵌入模型和向量大小不仅是一个技术决策,而且是一个战略决策,它符合你项目的独特特征、技术和组织约束以及目标。
维护计算效率和成本是有效使用嵌入模型的另一个基石。由于某些模型可能资源密集,响应时间更长,成本更高,因此优化计算方面而不牺牲输出质量是至关重要的。根据手头的任务设计系统以使用不同的嵌入模型将产生更健壮的应用程序架构。
定期评估您的嵌入模型至关重要,以确保您的AI/ML应用持续按预期表现。这包括定期检查性能指标并进行必要的调整。调整模型使用可能意味着改变向量大小以避免过拟合——即模型对训练数据过于精细调整,在未见过的数据上表现不佳。
监控向量搜索响应时间与所使用的嵌入模型和向量大小至关重要,因为这些会影响AI驱动应用的用户体验。同时考虑维护和更新嵌入模型的成本,包括重新嵌入数据的货币、时间和资源费用。为这些计划有助于在需要更新时做出明智的决定,并平衡性能、成本效益和技术进步。
摘要
本章涵盖了嵌入模型领域,这些模型是AI/ML应用中的基本工具。它们促进了将高维数据转换为更易于管理的低维空间。这个过程称为嵌入,显著提高了计算效率,并增强了描述和量化数据中关系的能力。为不同类型的数据选择合适的嵌入模型,如文本、音频、视频、图像和结构化数据,对于扩展用例和工作负载至关重要。
本章还强调了咨询排行榜以评估大量可用模型的有效性以及选择向量大小时所需的微妙平衡的重要性,强调了在细节、效率、性能和成本之间的权衡。虽然嵌入模型提供深入、情境化的洞察,但简单的向量化方法可能适用于某些任务。
下一章将深入探讨向量数据库的方面,检查向量搜索在AI/ML应用中的作用,并使用案例研究。
第五章:向量数据库
有时,数据信息丰富且结构明确。如果你知道你需要什么,那么在现代数据库系统中处理这些数据就很简单。然而,你通常不知道确切需要什么。没有特定的搜索词或短语,你可能不会收到最佳搜索结果。例如,你可能不知道你挑剔的宠物最喜欢的食物的品牌或名称。在这种情况下,传统的信息搜索和检索方法可能无法满足需求。
现代AI研究催生了一类新的方法,可以编码某物背后的语义含义,而不仅仅是其原始数据。例如,AI模型可以理解,当你要求“那个演员也出演了带有绿色下落数字的电影的新动作片”时,你是在询问最新的约翰·威克电影,该片由基努·里维斯主演,他也是黑客帝国系列电影的主角。
为了实现这一结果,这些方法将它们的输入转换为一种称为向量嵌入的数值格式。向量数据库提供了一种有效存储、组织和搜索这些向量表示的方法。这使得向量数据库成为检索任务的宝贵工具,这在AI应用中很常见。在本章中,你将了解向量搜索、与之相关的关键概念和算法,以及向量数据库的重要性。到本章结束时,你将理解图连接性的工作原理及其在RAG等架构模式中的应用。你还将了解构建向量搜索系统的最佳实践。
本章将涵盖以下主题:
-
向量嵌入和相似度
-
最近邻向量搜索
-
向量数据库的需求
-
案例研究和实际应用
-
向量搜索最佳实践
技术要求
虽然不是必需的,但了解图数据结构和操作可能会有所帮助。你可能还想知道用于创建向量的嵌入模型,这些模型在第4章“嵌入模型”中进行了更详细的讨论。
什么是向量嵌入?
在最基本层面上,一个向量是一系列数字加上一个隐含的结构,该结构决定了这些数字的定义方式以及如何比较它们。向量中的元素数量是向量的维度。
[年份, 品牌, 型号, 颜色, 行驶里程]。这些属性组成一个[2000, 本田, Accord, 金色, 122000]。
这是一个有用的模型,可以帮助您建立向量如何编码信息的直观理解。然而,每个元素并不总是对应于一个具有可数可能值的明确概念。在人工智能应用中使用的向量更加抽象,并且具有显著更多的维度。从某种意义上说,它们将具体概念分散到许多维度上,并将每个维度标准化为单一的可能值集合。例如,来自OpenAI的text-embedding-ada-002模型的向量总是有1,536个元素,每个元素是介于-1和1之间的浮点数。
在人工智能应用中使用的向量是嵌入模型的输出。这些是机器学习(ML)模型,它们经过预训练,可以将输入(通常是文本标记的字符串)转换为编码输入语义意义的向量。对于人类来说,这些向量的许多维度基本上是无法解读的。然而,嵌入模型在训练过程中为每个维度学习一个隐含的意义,并且可以可靠地为它的输入编码这种意义。
向量的具体结构在不同嵌入模型之间有所不同,但特定模型总是输出相同大小的向量。要使用向量,了解它是由哪个模型创建的是至关重要的。
向量相似度
除了存储高维向量数据外,向量数据库还支持各种操作,让您能够查询和搜索向量。
最常见的操作是最近邻搜索,它返回与输入查询向量最相似的存储向量的列表。常见的搜索界面是熟悉的领域。例如,电子商务搜索通常会优先显示与您的查询相关的产品,即使它们不是完全匹配。最近邻搜索利用嵌入模型向量的语义特性,使得找到相似向量与找到相关结果相同。
但两个向量相似意味着什么呢?简而言之,相似的向量彼此靠近,这可以通过距离来衡量。有许多定义距离的方法,包括一些在更高维度中变得更加相关的定义。对于高维向量,无法可视化距离如何工作,但对于小向量,这些想法是显而易见的,然后可以将其扩展。
如果您回想起几何课,您会记得您可以使用距离公式找到两个坐标向量之间的距离。例如,二维坐标(x, y)使用距离公式distance(a, b) = sqrt((a_x - b_x)**2 + (a_y - b_y)**2)。这也适用于三维坐标,其中公式有一个额外的维度分量:sqrt((a_x - b_x)**2 + (a_y - b_y)**2 + (a_z - b_z)**2)。这种模式可以推广到任何数量的维度,被称为两个n-维点之间的欧几里得距离。
理论上,你也可以使用欧几里得距离来测量用于人工智能应用的高维向量之间的距离。实际上,然而,随着维数的增加,欧几里得距离的有用性会降低。这种在小维度上工作良好而在高维度上失效的直觉和工具模式很常见,通常被称为维度诅咒。
与欧几里得距离不同,大多数应用使用一种称为余弦相似度的不同距离度量。与测量两个向量尖端之间空间的欧几里得距离不同,余弦相似度使用不同的公式来测量共享相同基的两个向量之间的角度大小。它有效地以数学精确的方式确定两个向量是否相同、完全不相关,或者(最可能的情况)在两者之间,如图 图 5**.1 所示。相似的向量几乎指向同一方向,不相关的向量是正交的,相反的向量指向相反的方向。

图 5.1:向量测量的比较
余弦相似度为你提供了一个测量两个向量之间距离的工具。由于向量嵌入携带语义信息的本质,它也是一个测量两个向量之间相关度或相关性的工具。如果你将这个想法扩展到更多的向量,你可以找出给定向量与任何其他向量的相关性,甚至可以根据相关性对所有向量进行排名。这是向量搜索算法背后的核心思想。
以这种方式比较许多向量会带来其自身的复杂性和挑战。为了应对这些挑战,搜索提供商已经开发了各种最近邻搜索方法,以平衡权衡并针对不同的用例进行优化。下一节将讨论两种处理实际搜索用例的方法。
精确搜索与近似搜索
有时候,你的用例需要搜索只返回真正的最近邻。例如,想象一个存储用户生物识别信息(作为嵌入向量)的认证应用,以便他们以后可以识别自己。当他们扫描指纹或面部时,应用会创建扫描数据的向量嵌入,并将其用作最近邻搜索中的查询向量。这样的应用绝不应该将用户误识别为具有相似指纹或面部的人。
这种用例非常适合精确最近邻(ENN)搜索,它保证了搜索结果是最佳可能的匹配。此类搜索必须始终返回最近的匹配存储向量,并确保它出现在其他相似但更远的匹配之前。
一种直接的方法是暴力解决问题:计算查询向量和每个存储向量的距离,然后返回一个按从近到远排序的结果列表。通过检查每个向量,你可以保证搜索结果精确地包含按顺序排列的最相关向量。虽然这种方法对小型数据集有效,但随着存储向量的数量增加,它很快就会变得计算成本高和时间消耗大。一些巧妙的方法可以帮助精确搜索扩展到更大的数据集,例如使用基于树的索引来避免为每个向量计算相似度。这使得精确搜索对某些额外的应用类型有用,但最终,这个问题扩展性不好,在大数据集上可能需要很长时间。在需要精确性的情况下,你必须接受其约束并找到绕过它们的方法。
对于其他常见情况,只要知道你的搜索结果足够接近以成为最佳匹配就足够了。这种用例被称为近似最近邻(ANN)搜索,并且对于许多日常应用来说足够强大。
例如,如果你在一个推荐应用中搜索“像《盗梦空间》一样的电影”,你不需要结果中包含特定的电影。相反,你可能只是想要一份包含几部类似科幻惊悚片,具有令人费解情节的电影列表。即使电影《星际穿越》在技术上比返回的结果更接近语义匹配,但像“《少数派报告》、《记忆碎片》、《禁闭岛》”这样的结果列表也是有用的。
精确搜索和近似搜索的选择取决于你的应用需求。你可能对精确搜索有严格的要求。然而,你可能也有一个用例,其中精确搜索虽然有用,但不是提供价值所必需的。或者,可能根本不需要进行精确搜索。在下一节中,你将学习如何评估搜索算法,以帮助你确定你的需求。
搜索度量
你可以用精确度、召回率和延迟来描述搜索算法:
-
精确度衡量搜索结果的准确性。精确搜索试图只返回与查询相关的匹配项,并且尽可能少地返回无关结果。
-
召回率衡量搜索结果的完整性。如果一个搜索返回了所有相关结果的大部分,那么它就有很高的召回率。
-
延迟衡量搜索查询从开始到结束所需的时间。每个搜索都需要一定的时间来返回结果。确切的延迟因搜索而异,但平均而言,它是搜索空间中向量的数量以及你的精确度和召回率要求的一个函数。
这些因素紧密相连,需要权衡,这定义了最近邻搜索的性质。例如,一个最近邻搜索(ENN)具有完美的精度,并将包括最相关的结果。然而,为了保持延迟合理,如果结果太多,它可能会省略一些相关的结果。因为它错过了有效结果,这种搜索的召回率相对较低。如果ENN搜索还需要高召回率,那么搜索就需要运行更长的时间以确保包括足够的相关结果。
在人工神经网络(ANN)搜索中,你可以放宽精度要求,这让你可以优化其他因素。你可以通过允许搜索花费更多时间或返回更多可能包含假阳性的结果来获得更完整的结果。例如,如果你可以容忍假阳性,比如在应用中搜索后过滤掉它们,那么你可以使用ANN来运行快速搜索,返回高度相关的结果集。
你应该评估你的应用并确定其对这些因素的最高优先级。然后,你可以选择合适的搜索操作并调整算法,直到其他因素得到适当的平衡。
调整搜索算法涉及修改确定其如何构建和遍历其索引数据结构的配置参数。为了更好地理解这意味着什么,你将在接下来的几节中了解用于启用向量搜索操作的概念和数据结构,从连通性的想法开始。
图连通性
如果你曾经使用过城市的公共交通网络来出行,你可能想知道城市是如何选择将火车站或公交车站放在那里的。有许多因素在起作用,但如果你看一个理想的情况,那么你可以将选择归结为两个相关的因素:连通性和延迟。
想想火车乘客的经历,我们称她为Alice,她要访问城市另一边的她的朋友Bob。如果有一个车站就在Bob的家旁边那会很好,因为这样Alice就可以在下车后立即看到他。当然,你不能在每座房子前都建一个火车站,并且在某个点上,增加更多的车站会增加平均旅行时间。
每次你更改车站或连接的数量,你可能会影响系统内任何两个目的地之间的旅行时间。通常,规划公共交通车站位置的工作是由知识渊博的土木工程师、城市规划师和其他利益相关者经过深思熟虑和考虑后完成的。公共交通网络的主要目标是,在合理的时间内将乘客带到相对接近他们真正最终目的地的车站。通过了解他们的目标并应用特定的策略,城市规划者试图以对公共交通乘客有用和高效的方式连接城市的偏远地区。
类似地,人工神经网络搜索的目标是在合理的时间内找到一个接近给定查询向量的向量。如果你能从交通规划者那里得到灵感,你可以利用这种相似性来设计一个有效的ANN索引。
可导航小世界
从本质上讲,无论是交通规划还是最近邻搜索,都归结为在连接性和延迟之间进行权衡的图构建和遍历问题。你可以使用一个称为可导航小世界(NSW)的算法来构建这样的图。它一次处理一个向量,并为每个向量在图中添加一个节点。每个节点也可以与其他节点(称为邻居)建立连接,这些连接在图构建过程中分配。
NSW算法旨在平衡一个节点的直接邻居的相关性与该节点与图中其余部分的连接性。它将主要分配与节点紧密相关的邻居。然而,它有时也可能连接两个在图上相对较远的、不太相似的节点。如果你考虑交通示例,这就像有一个有几个在同一社区内的停靠站的公交车路线,但也通往市中心。居民可以轻松到达他们的本地目的地。如果他们需要去社区外,他们仍然可以访问整个城市。
有关NSW图的示例,请参阅图5.2。注意,每个节点最多与三个邻居连接,并且通常附近的节点连接紧密。每个节点代表一个向量,用线条连接的节点是邻居。高亮显示的连接显示了贪婪最近邻搜索的路径。

图5.2:一个NSW图
一旦你构建了你的向量的NSW图,你可以将其用作ANN搜索的索引。你可以从一个随机节点开始,并使用搜索算法跟随邻居连接,直到你到达最近邻。这让你可以将相似性比较限制在总搜索空间的一个子集。例如,注意图5.2中的搜索路径是如何到达最近邻而不访问图中的每个节点的。
如何搜索可导航小世界
你用来遍历NSW图的精确搜索算法可能有所不同,并会影响搜索的整体行为。最常用的算法是简单的贪婪搜索,在每一步,你找到并选择最佳的直接选项,而不考虑之前的或未来的步骤。例如,NSW图的贪婪搜索首先随机选择一个节点开始,然后测量该节点与查询向量的接近程度。然后,它测量到每个邻居的距离。如果其中一个邻居比当前节点更近,则搜索转移到该节点,并继续进行测量和比较过程。否则,搜索完成,当前节点是一个近似的最近邻。
在这个基本示例中,使用贪婪搜索的 NSW,近似的定义非常广泛,搜索可能会返回次优结果。这归结于图搜索的本质,在这种情况下,它是设计用来找到图的局部最小值的。这个局部最小值不保证是全局最小值,这就是为什么搜索是近似的而不是精确的。如果贪婪搜索算法在一个远离全局最小值的局部最小值上定居,它单独返回可能会是错误阳性。
你可以通过调整图的构建参数来部分防止这种情况。然而,由于搜索查询的动态性和搜索的数据基础,你无法完全防止错误阳性局部最小值的存在。相反,你需要找到一种方法来最小化它们的影响。
一种方法是通过从不同的随机入口节点多次运行搜索。这种方法被称为随机重试,它从图中收集多个样本,并从所有样本中返回最佳结果。你还可以向算法中添加额外的机制,使其更加健壮。一种常见的架构是将贪婪搜索算法与一个可配置的优先队列配对,该队列保持搜索已看到的最近邻的排序列表。如果搜索遇到错误阳性局部最小值,队列允许它回溯并探索可能导致更近邻的其他图分支。
你使用的确切搜索方法取决于数据集和目标。例如,随机重试易于实现且可以并行运行。它们适用于可能匹配许多局部最小值的微妙、探索性搜索。然而,它们的随机性质使它们非确定性,并且每次重试都进行完整搜索,这可能会快速增加你的成本。相反,优先队列是确定性和精确的,但实现和调整起来更困难。
基于这些信息,你有了构建有用向量搜索索引的基础。你可以在这里停止构建索引并开始搜索。然而,你很快会发现这种方法存在问题,尤其是在将搜索空间扩展到在 AI 应用中常见的规模时。随机重试具有显著的计算开销,并且随着数据集的扩展,你必须进行更多的搜索。优先队列可以防止搜索陷入局部最小值,但不会阻止它在到达目标的过程中经过许多节点。
为了解决这些问题,你需要超越单个 NSW 图。在下一节中,你将看到如何将多个 NSW 图组合起来,以绕过迂回搜索并使随机重试变得不那么必要。
层次化可导航的小世界
回想一下爱丽丝的公共交通体验。如果她与鲍勃住在国家另一边的不同城市,会怎样?从理论上讲,她可以通过一系列火车、公共汽车、出租车和自行车共享服务穿越全国,从而限制自己使用公共交通服务。这显然会花费很多时间,并在沿途需要许多停靠点。这是因为交通网络仅在单个城市的规模上有效。一旦你放大到更远的范围,你需要一个不同的系统。
除了使用交通方式,爱丽丝还可以从她所在城市的机场出发,飞往鲍勃所在的城市。即使她的旅行包括转机和多次航班,这仍然可能比仅使用交通方式更快。一旦她到达鲍勃所在的城市,她就可以使用地铁系统快速有效地从机场到达他的社区。
爱丽丝的旅行发生在两个不同的层面上。首先,她从机场层面开始,在这个层面上,她可以自由地前往与她的家乡机场相连的任何目的地机场。在这一层,她可以直接访问许多不同的城市,但这种访问仅限于每个城市的一个位置:机场。她利用机场来接近鲍勃,而不必花费太多时间规划路线和旅行。一旦她到达鲍勃最近的城市机场,她就会下降到第二层,并能够访问一个能够让她更接近鲍勃的换乘网络。
这基本上是分层可导航小世界(HNSW)的概念。你可以创建一个层级的层次结构,其中每一层都是一个NSW图。例如,查看图5.3以了解典型的HNSW图结构。顶层有相对较少的节点,它们彼此之间距离较远且连接稀疏。每一层都包含上一层的所有节点,以及额外的节点和连接,使图更密集、更连通。在本章的例子中,交通节点和机场节点之间的区别是分割层的一种自然方式。机场是顶层,下一层包括机场和交通站点。

图5.3:HNSW图结构
实际的HNSW算法会以概率决定每个向量的顶层,只有存在于较低层的节点比同时存在于较高层的节点更有可能。搜索从顶层开始,通过找到与查询向量最近的节点。然后,它移动到下一层相同的节点,并从那里继续搜索。这个过程一直持续到达到最接近的邻居在最终层,此时搜索完成。在图5.3中,高亮显示的连接显示了跨多个层进行贪婪最近邻搜索的路径。
HNSW 是许多现代向量搜索应用的基础。它经过实战检验,并证明能够在合理的时间内提供有用的结果。该算法非常适合用于具有可配置参数的 ANN 用例,这些参数让你能够控制搜索的表现。
现在你已经了解了向量搜索的内部工作原理,你可以看到它需要专门设计的逻辑和数据结构。在下一节中,你将了解向量数据库如何封装所有技术细节,以便让开发者能够使用向量搜索。
向量数据库的需求
向量携带深层次的语义信息,并且有许多潜在的应用场景,在未来几年内将变得越来越普遍。与它们一起工作需要特定的复杂操作,这些操作仅处理向量数据。此外,搜索需求往往与更结构化的数据库查询需求有显著差异。
这些因素共同意味着向量操作和传统数据库工作负载在很大程度上是独立的。这导致了专门设计来处理向量数据、索引和工作负载的向量数据库的概念。从开发者的角度来看,向量数据库可以采取几种形式。
最基本的是一种独立产品,它独立于其他操作数据库。这种类型的向量数据库有自由专注于实现和优化向量操作,而不必考虑其他数据库操作。然而,通常,向量搜索应用程序需要额外的过滤或元数据,并且可能基于搜索结果执行更多传统数据库操作。这些用例需要运行时对多个数据库进行多次查询,或者需要一个额外的同步层,该层将数据从操作数据库复制到向量存储中。
或者,向量数据库可以集成到现有的数据库或数据服务中。例如,一个通用数据库管理系统可能在其查询语言中支持向量搜索操作,如果你已经定义了适当的向量搜索索引。这允许应用程序利用现有系统的功能,并在同一系统中进行搜索。向量数据库可以在系统中独立扩展和运行,但作为统一 API 的一部分向用户公开,包括传统操作。这使你的向量存储与现有数据库耦合,但导致更简单、更容易维护的架构。
不论形式如何,向量数据库都是人工智能应用中的关键工具。它们专门设计用于存储和查询向量数据。你可以配置它们以提供最佳搜索结果并推动人工智能应用。
下一节将介绍一些使用向量搜索来增强机器学习和人工智能模型的方法,包括在训练、微调和运行时。你还将了解向量搜索本身如何使人工智能应用成为可能,而无需额外的功能或模型。
如何向量搜索增强AI模型
AI模型涵盖了一类广泛的数据结构和技术。机器学习(ML)构成了大多数现代基于向量的AI模型的核心,旨在通过训练过程“教导”计算机执行特定任务。一般来说,ML处理过程通过向一个基础模型提供精心挑选的数据集,该模型能够从数据中检测和推断模式。一旦模型学会了这些模式,它就能够重新创建或插值它们以处理新的输入。这些技术和模型在AI世界中无处不在,是推动新型用例的秘密配方。
通常,ML训练和AI应用可以分为两个关注点,如下:
-
信息检索涉及找到对AI过程有用的相关信息。向量搜索非常适合这项任务。嵌入模型可以将各种输入的语义编码成标准向量形式。然后,你可以使用搜索来找到与同样广泛的输入的匹配,包括结构化和非结构化输入。
-
信息综合将多个信息片段,可能来自不同的来源,综合成一个连贯且有用的结果。这是生成式AI模型(GenAI)的领域。这些模型无法可靠地找到或生成真实事实,但它们可以有效地处理和重新格式化输入信息。
向量搜索通过在每个阶段(从训练到微调再到运行时执行)为ML和AI模型提供访问最相关数据的能力,增强了它们。
在训练过程中,你可以使用向量数据库来存储和搜索你的训练数据。你可以设计一个过程,从语料库中找到与每个训练任务最相关的数据。例如,当训练一个特定领域(如医学)的语言模型时,你可以使用向量搜索来检索每个训练批次中最相关的医学教科书章节。这确保了模型学习到最相关的信息,而不会被噪声所干扰。
你可以在微调阶段应用同样的想法,这本质上是在更通用的基础模型之上的一个次要训练阶段。例如,你可以微调医学语言模型,使其能够使用医院系统首选的风格和结构生成报告。向量搜索可以帮助找到与每个训练主题相关的由人类编写的报告。
不论你的模型是专用型还是通用型,你都可以通过修改提供给它的输入来定制其运行时行为。向量搜索可以分析原始输入并找到相关信息。然后,你可以增强或细化原始输入,以包括检索到的上下文。例如,你可能会维护一个罕见疾病的向量数据库,并搜索任何与用户描述匹配的内容,以便获得更精确的诊断。
人工智能应用形式多样,但现代应用越来越多地采用运行时定制方法,为生成式变换器模型提供相关上下文。这种架构是检索增强生成(RAG)技术的基础,你将在第8章,在AI应用中实现向量搜索中深入了解。
到目前为止,你已经学习了向量数据库和搜索操作的理论和机制。接下来,你将查看一些真实的向量数据库用例示例,突出向量是现代AI应用的核心。
案例研究和实际应用
向量搜索是一个强大的工具,它使你能够构建基于信息意义而不是精确单词的复杂系统来查找信息。通过理解数据点之间的上下文和关系,向量搜索帮助你检索高度相关的结果。到目前为止,你已经了解了向量搜索涉及的不同概念以及市场上的一些不同产品,但企业如何将向量搜索集成到他们的应用程序中呢?
在本节中,你将探索三种利用向量搜索的流行方法:语义搜索、RAG和机器人流程自动化(RPA)。你将查看适合每个这些类别的现有MongoDB Atlas向量搜索案例研究,以及这些应用如何通过更精确的搜索为最终用户提供价值,而这种搜索之前是不可能的。以下每个案例研究最初都是作为用MongoDB构建AI系列客户故事的一部分发布的(https://www.mongodb.com/resources/use-cases/artificial-intelligence?tck=blog-genai§ion=resources&contentType=case-study)。这些故事在此处展示,以展示可以在灵活、可扩展和多功能的MongoDB Atlas平台上构建的向量搜索用例的多样性。
Okta – 自然语言访问请求(语义搜索)
Okta,全球领先的身份安全提供商之一,使用自然语言RAG界面,允许用户轻松地在其组织中请求新技术角色。他们利用Atlas向量搜索和自己的定制嵌入模型构建了一个名为Okta邮箱的系统,使用户能够将自然语言查询映射到正确的角色。

图5.4:Okta邮箱用户请求表单
这是一个利用语义搜索解决问题的例子,其中Okta数据科学团队训练的嵌入模型能够将自然语言请求映射到应分配的正确用户角色。

图5.5:Okta邮箱管理员视图
这些请求将通过现有的工作流程通过Slack路由给经理。最终结果是简单直观的用户体验,使得请求者和访问管理者之间的身份管理变得更为简单,从而使得Okta作为身份和访问管理解决方案的价值主张更加显著。
Okta选择使用Atlas Vector Search查询这些向量,因为他们已经将Atlas作为他们的操作数据存储使用,这为开发者提供了简化的体验。您可以阅读更多关于此案例研究的资料https://www.mongodb.com/solutions/customer-case-studies/okta。
One AI – 基于语言的AI(在业务数据上的RAG)
One AI为不同行业提供垂直化的AI代理和聊天机器人。这些服务允许对文档进行详细的AI辅助分析,这些应用范围从金融服务和房地产到制造业和零售业。
One AI提供的聊天机器人都是使用MongoDB Atlas平台构建的,拥有来自20多个不同内部服务的超过1.5亿个索引文档。One AI将AI带入日常生活的目标,通过简单地将向量搜索索引添加到他们在Atlas中存储的数据,并使其可通过嵌入式自然语言输入进行查询,变得可行。
“在语言AI中一个非常常见的用例是创建代表语言的向量。能够在同一数据库中拥有这种向量化的语言表示,然后通过单个查询界面访问,这解决了我们作为API公司的一个核心问题。”
——Amit Ben,One AI的首席执行官和创始人
这是一个多租户RAG应用的典型案例,其中One AI提供的一种AI服务所索引和提供的数据可能对另一种服务不相关。正如本章后面所讨论的,这是一个在Atlas平台内易于构建的常见数据建模模式。您可以进一步阅读有关此案例研究的资料https://www.mongodb.com/solutions/customer-case-studies/one-ai-success-story。
诺和诺德 – 自动临床研究生成(高级RAG/RPA)
诺和诺德是全球领先的医疗保健公司之一,其使命是战胜世界上一些最严重的慢性疾病,如糖尿病。作为获取新药批准并交付给患者过程的一部分,他们必须生成一份临床研究报告(CSR)。这是一份详细记录临床试验的方法、执行、结果和分析的文件,旨在作为监管机构和其他药物审批流程利益相关者的关键真实信息来源。

图5.6:CSR示例
通常,CSR的完成需要大约12周,但诺和诺德的内容数字化团队能够利用Atlas Vector Search构建一个工具,将这个过程缩短到十分钟。他们构建了一个名为NovoScribe的RAG工作流程,利用Claude 3和ChatGPT作为他们的聊天完成模型,以及托管在Amazon Bedrock服务上的Titan进行文本嵌入。他们使用MongoDB Atlas Vector Search作为知识库,为这些模型提供相关数据。
从功能上讲,NovoScribe使用定义的内容规则和统计输出生成验证过的文本。Atlas Vector Search计算每个文本片段与相关统计数据的相似度,然后将其输入到结构化提示中,供LLM生成一个待专家审查的CSR,包括所有展示数据的来源。
“MongoDB Atlas的伟大之处在于,我们可以将报告的原生向量嵌入与其所有相关的文本片段和元数据一起存储。这意味着我们可以快速运行非常强大和复杂的查询。对于每个向量嵌入,我们可以过滤其来源的源文档、作者以及时间。”
—托比亚斯·克罗佩林博士,诺和诺德
该项目使诺和诺德能够通过在MongoDB中智能地安排数据以正确的格式,并对其定义向量搜索索引,构建一个高级临床报告生成系统。他们能够以更多方式使用新颖的嵌入模型和LLM,利用这些模型显著提高编写CSR的过程。您可以在https://www.mongodb.com/solutions/customer-case-studies/novo-nordisk了解更多关于这个案例研究的详情。
向量搜索最佳实践
本节涵盖了通过智能数据建模、部署模型选项以及原型和实际应用案例的考虑来提高向量搜索准确性的最佳实践。通过遵循本节中的指导,您更有可能提高向量搜索结果的质量,并以可扩展、生产就绪的方式运行您的搜索系统。
数据建模
在MongoDB的上下文中,$vectorSearch查询。
可以广泛地认为,利用元数据是将文档用作将数据返回给用户的方式,而不是向量。将文档作为聚合阶段的输出进行操作意味着不同的聚合阶段可以组合在一起,产生比单个阶段更大的功能,并可以从查询优化中受益。这自MongoDB发明以来一直是文档模型的精髓,在GenAI应用的时代依然如此。
本节将深入探讨在向量搜索之前、期间和之后使用其他数据的方法,以提高您基于向量的信息检索系统的准确性。
过滤
最基本且最有效的元数据使用形式是通过考虑仅满足预过滤条件的向量数据来限制向量搜索的范围。这限制了需要考虑的有效文档的范围,对于选择性过滤器(最常见的过滤器类型),这提高了准确性并减少了查询延迟。
在查询时,这些预过滤器可以作为使用$match MQL语义的$vectorSearch查询的一部分来考虑。这意味着除了点过滤器(如$eq)之外,用户还可以定义范围过滤器(如$gt或$lt),以仅针对符合一系列值的文档进行搜索,而不是匹配特定的一个。这可以显著减少需要搜索的有效文档数量,减少需要完成的工作量,并通常提高搜索的准确性。$match过滤器还可以利用逻辑运算符(如$and和$or),允许用户组合过滤器并构建更复杂的逻辑到他们的搜索应用中。
让我们看看两种常见的过滤器类型,以及何时以及如何使用它们。
动态过滤器
动态过滤器是根据搜索查询的内容而变化的元数据片段。这些可以是数据的属性,例如一本书的出版时间或其价格。它们通常在用户执行搜索时与其普通英语查询一起选择。以下是一个示例:
[
{
"_id": ObjectID("662043cfb084403cdcf5210a"),
"paragraph_embedding": [0.43, 0.57, ...],
"page_number": 12,
"book_title": "A Philosophy of Software Design",
"publication_year": 2018
},
{
"_id": ObjectID("662043cfb084403cdcf5210b"),
"paragraph_embedding": [0.72, 0.63, ...],
"page_number": 6,
"book_title": "Design Patterns: Elements of Reusable Object-Oriented Software",
"publication_year": 1994
},
{
"_id": ObjectID("662043cfb084403cdcf5210c"),
"paragraph_embedding": [0.12, 0.48, ...],
"page_number": 3,
"book_title": "Guide to Fortran",
"publication_year": 2008
}, ...
]
动态过滤器在构建语义搜索应用时最常见,因为它们通常在用户在搜索栏中执行查询之前输入。这与完全基于自然语言的RAG界面形成对比。
静态过滤器和多租户
有时候,过滤器与查询的主体无关,而是与用户的个人资料相关。用户可能正在查询只能由他们公司访问的数据,但这些数据以多租户方式存储,与许多其他租户的数据一起存储。在这种情况下,用户所属的用户ID或公司ID可能被用来过滤搜索的结果。对于有大量租户和少量向量的情况,过滤器是建模数据的推荐方法,而不是在多个集合和索引中存储大量数据。
建议在向量数与租户数之间存在高度差异,并且在同一集合或索引中建模了大量的租户时,在$vectorSearch中将exact标志设置为true。这将导致对所有对应于向量索引的所有段并行执行详尽的搜索。在许多情况下,由于过滤器的选择性和在执行过滤HNSW搜索时需要搜索和丢弃的大量潜在向量,这将加速搜索。
分块处理
在 RAG 的背景下,出现了一个有趣的类比。正如聊天模型需要智能提示工程一样,嵌入模型需要智能块化。智能块化需要找到能够有效映射到搜索或自然语言查询的正确上下文级别。这也可能是提供给 LLM 的正确上下文级别,但正如你将在后面的 父文档检索 部分中看到的,如果你智能地建模你的数据,这并不是一个严格的要求。
你将在 第 8 章,在 AI 应用中实现向量搜索 中了解更多关于基本和高级块化策略的内容。为了本节的目的,让我们考虑一种基本的块化策略,具有重叠的固定标记计数,以及你如何进行实验来评估在你的数据集上哪种效果最好。
具有重叠的固定标记计数
具有重叠的固定标记计数,这在许多 RAG 集成框架(如 LangChain)中是一个常见的默认设置,它根据每个块指定的最大标记数和块之间的期望重叠将非结构化数据分割成块。这种方法比全页摄入方法更细粒度,它允许你在你的特定数据集上进行更多的实验。它不涉及利用非结构化数据中的任何结构。这在开发简单性方面是一个优点,但当你想要建模的句子、段落或其他边界以某种方式界定语义意义时,这可能会成为一个缺点。
如果你对源数据几乎没有控制权,或者正在处理不适合使用利用文档结构(如 HTML 标签)的边界块化方法的非结构化数据,这种技术可能是一个不错的选择,因为这种技术与任何文本格式兼容。图 5.7 展示了一个示例,不同的颜色表示不同的块和重叠部分:

图 5.7:基于固定标记计数且存在重叠的块化示例
实验研究
评估哪种块化策略或嵌入模型最适合你的用例,需要整理文档的判断列表以及你预期映射到这些文档的查询。你还可以尝试不同的嵌入模型和块化策略,在嵌入数据之前看看哪种最适合你的用例。
给定的嵌入模型可能在使用固定的块化策略时表现更好或更差。你可以更容易地评估哪种块化和嵌入模型的组合最适合你的用例。你可以有多个相同数据的不同版本,每个版本都分割和处理的有所不同。通过比较这些版本,你可以确定最适合你特定搜索需求的最佳分割方法和嵌入模型。
确定嵌入模型是否有效地将您的文档映射到样本查询的最佳方法是通过检查返回的一组查询文档的相似度得分,并查看它如何与实际问题的良好响应相匹配,如图5.1表所示。
| 排名 | 原始文档 | 嵌入 | 余弦相似度 |
|---|---|---|---|
| 1 | “构建软件的主要挑战之一是管理复杂性。” | [0.23, 0.45, …] | 0.901 |
| 2 | “深度模块在简单界面上提供了深层功能。” | [0.86, 0.34, …] | 0.874 |
| 3 | “软件系统往往由于需求的演变而变得复杂。” | [0.46, 0.51, …] | 0.563 |
表5.1:基于余弦相似度的向量搜索结果
在固定标记计数且重叠策略的情况下,您需要确定您希望开始的标记计数。在信息检索社区中,300-500个标记的范围似乎对于实验来说是足够的。
混合
混合涉及在单个文档中建模多个相关来源,并在查询时联合考虑它们与单个向量搜索。这种技术体现了MongoDB支持的聚合管道的灵活性,并允许您利用向量搜索、词汇搜索、传统数据库运算符、地理空间查询等对您的搜索系统进行大量的实验和调整。
在接下来的章节中,您将探索一些混合方法中较为流行的技术,以及一些可能对您的用例相关的有希望的探索途径。
向量加词汇
向量搜索是一种利用嵌入模型能力定义的查询与索引文档之间语义相似性的有效方法。像BM25这样的词汇搜索系统,Lucene和相应的Atlas Search使用,以完全不同的方式有帮助,因为它们直接索引标记并使用一种词袋风格的搜索方法,该方法根据每个文档中出现的查询词对文档集进行排序,而不管这些词在文档中的位置如何。
尽管这种方法基于20世纪80年代开发的原始概率检索框架,但它仍然相当擅长将查询中的关键词映射到文档中的关键词,尤其是在该词在嵌入模型训练的上下文之外使用时。小数据集可能包含训练数据集中未见过或具有不同含义的标记,如图5.8所示。

图5.8:样本外的术语
一些向量搜索提供商提供稀疏向量搜索作为词汇搜索的替代方案,它可以被配置得与词汇搜索类似,但被认为不足以满足客户的需求。它还缺乏对许多词汇搜索功能的即插即用支持,例如同义词列表、分页和细分。
较低级别的上下文非常适合嵌入模型,而较高级别的上下文则可以通过关键词搜索很好地表示。MongoDB 允许用户尽可能地在这一方向上进行实验,同时允许在非键(而非简单的文档 _id)上联合查询模式。这使得可以为给定文档提供窗口级别的表示,这些表示可以通过不同的方法来考虑。以下代码展示了如何使用向量搜索索引对包含 paragraph_embeddings 的某些文档进行索引和查询,而包含 full_page_content 的其他文档则可以使用文本搜索索引进行索引和查询:
[
{
"_id": ObjectID("662043cfb084403cdcf5210d"),
"page_number": 81,
"paragraph_embedding": [0.43, 0.91, ...],
},
{
"_id": ObjectID("662043cfb084403cdcf5210e"),
"full_page_content": "Pulling complexity down makes the most sense if (a) the complexity being pulled down is closely related to the class's existing functionality, (b) pulling the complexity down will result in many simplifications elsewhere in the application, and (c) pulling the complexity down simplifies the class's interface. ...",
"page_number": 36,
}, ...
]
联合考虑前面代码中显示的两个查询的结果集,这被称为混合搜索,可以使用互逆排名融合方法来实现,如https://www.mongodb.com/docs/atlas/atlas-search/tutorial/hybrid-search/中所示。将来,Atlas Vector Search 将提供对专用阶段的支持,这使得基于排名或分数组合结果集变得更加简单。然而,基本概念将保持不变。
向量加向量
在您的数据集中可能存在多个向量相关性来源,您可能希望联合考虑,类似于您可能联合考虑整个页面的段落嵌入和关键词相关性。您正在考虑的二级嵌入字段可能是一个派生字段,例如由 LLM 生成的章节摘要然后嵌入的字段,或者它可能是一个完全不同的数据来源。以下代码展示了一个包含一组可以嵌入和索引使用向量搜索索引的源字段的单个文档:
[
{
"_id": ObjectID("662043cfb084403cdcf5210d"),
"book_title": "A Philosophy of Software Design",
"book_title_embedding": [0.67, 0.45, ...],
"chapter_title": "The Nature of Complexity",
"chapter_title_embedding": [0.51, 0.89, ...],
"chapter_summary": "This book is about how to design software systems to minimize their complexity. The first step is to understand the enemy. Exactly what is 'complexity'?...",
"chapter_summary_embedding": [0.36, 0.90, ...],
"raw_text "System designers sometimes assume that complexity can be measured by lines of code. They assume that if one implementation is shorter than another, then it must be simpler; if it only takes a few lines of code to make a change, then the change must be easy...",
"raw_text_embedding": [0.43, 0.11, ...],
}, ...
独立的 $vectorSearch 查询的结果可以采用与前面章节中看到的向量加词汇搜索查询模式相似的混合和融合模式,这将允许使用多个相关性来源来找到与查询最相关的文档。
在电子商务搜索用例中,单个项目通常有许多可以嵌入并存储在表示该项目的同一文档中的相关性来源。以下是一些例子:
-
产品描述
-
用户评论(以及用户评论的摘要)
-
产品图片
这些相关性的来源都可以通过相同的查询模式嵌入并共同考虑,这种查询模式就像人们用来共同考虑向量和词汇相关性的查询模式一样。
结合用户反馈
$vectorSearch 和使用点赞或踩作为用户相关性的代理的 $sort 阶段。
[
{
"_id": ObjectID("662043cfb084403cdcf5210a"),
"paragraph_embedding": [0.43, 0.57, ...],
"page_number": 12,
"score": 0.95,
"upvotes": 2,
"downvotes": 58
},
{
"_id": ObjectID("662043cfb084403cdcf5210b"),
"paragraph_embedding": [0.72, 0.63, ...],
"page_number": 6,
"score": 0.90
"upvotes": 81,
"downvotes": 3
},
{
"_id": ObjectID("662043cfb084403cdcf5210c"),
"paragraph_embedding": [0.12, 0.48, ...],
"page_number": 3,
"score": 0.67
"upvotes": 2,
"downvotes": 5
}, ...
]
这是一个非常简单的方法,但其背后的原理可以扩展,以允许对内容进行更个性化的定制,其中相似的用户通过他们对不同内容的相似互动来定义,这是流行推荐系统算法——协同过滤的基础。
在将用户反馈智能地集成到您的RAG应用方面,尽管目前还处于早期阶段,但文档模型的灵活性应允许您在这个领域进行丰富的实验,随着您的搜索系统以及用户如何随着时间的推移与之互动而演变。
文档查找
一旦您有一个排序的文档结果集,可能是通过多种方法以优化方式产生的,仍然可以进行一些额外的操作,这些操作可能利用您数据中固有的关系。通过文档查找,某些数据可能更容易使用外键查找键在文档外部进行建模,以在您的数据中建模树结构,例如文档内的层次结构、组织或某些其他分类法。
父文档检索
父文档检索涉及在某一层上下文中执行向量搜索,然后通过外键检索与检索到的最相关文档相连的文档。这个外键通常是一个子父关系,例如属于更大文本体特定页面的嵌入段落,其中更大的上下文可能存储在另一份完全不同的文档中。
使用这种模式,您只需在较低级别存储嵌入,然后查找包含大量文本的较高级别的上下文。如果您发现查询更容易将语义映射到更小的文本量,但您希望提供给用户或LLM的数据量很大,这可能很常见,这可能很有用。以下混合词汇和向量搜索的代码示例也是父文档检索的一个例子,因为向量嵌入被搜索以生成一整页内容提供给LLM。外键是page_number。
[
{
"_id": ObjectID("662043cfb084403cdcf5210d"),
"page_number": 81,
"paragraph_embedding": [0.43, 0.91, ...],
},
{
"_id": ObjectID("662043cfb084403cdcf5210e"),
"full_page_content": "Pulling complexity down makes the most sense if (a) the complexity being pulled down is closely related to the class's existing functionality, (b) pulling the complexity down will result in many simplifications elsewhere in the application, and (c) pulling the complexity down simplifies the class's interface. ...",
"page_number": 36,
}, ...
]
需要注意的是,与所有其他元数据一样,以这种方式捕获MongoDB文档之间的关系必须在摄取时提取。
图关系
您可以使用$graphLookup阶段利用文档之间的更多关系。这允许从$vectorSearch的结果跳转到任意数量的跳数。如果客户的数据已经包含可以以分层方式遍历的关系,这将立即为他们带来好处。
正如您可能定义文档和页面之间的关系一样,您可能递归地将文档分割成越来越小的块,使用parent_id字段将每个块与父文档相关联,并嵌入这些块。在查询时,您可以针对所有块进行搜索,并递归地跳转到所有parent_id值,以达到所需的解析级别,以提供给LLM。
部署
成功部署您的AI应用是最后的障碍。本节概述了各种部署选项,并提供了关于估计所需资源以确保最佳性能和可扩展性的指导。
部署选项
要开始使用 Atlas 向量搜索,最简单的部署模型是在现有的集群或新集群中定义一个搜索索引定义。这可以通过付费层集群的搜索索引管理命令或共享层集群的UI/Atlas 管理API进行配置。
当你在向量搜索用例中感到自信,并准备增加使用量或摄入数据的规模时,建议迁移到专用搜索节点。专用搜索资源提供了一个强大且可扩展的平台,用于处理需求较高的搜索工作负载。
这将允许实现高可用性向量搜索,更有效的资源利用,以及从核心数据库中隔离资源,这对于生产工作负载来说更为实用,如图 图 5.9 所示。

图 5.9:专用搜索节点的优势
迁移到专用搜索节点是一个零停机时间的过程,允许你的现有基础集群在新的资源启动并在此之上构建索引的同时继续服务向量搜索查询。一旦构建过程完成,$vectorSearch查询将被路由到你的专用搜索节点,并且原始集群上的索引将被删除。
可以通过以下步骤从集群配置UI配置专用搜索节点:
-
在创建新集群/编辑配置页面,将AWS 或 Google Cloud的多云、多区域和工作负载隔离单选按钮切换到启用状态。
-
切换到用于工作负载隔离的搜索节点单选按钮以启用。在文本框中输入节点数量。
-
打勾同意框。
-
选择适合你工作负载的正确节点。
-
点击创建集群。
资源需求
在 Atlas 向量搜索中当前支持的索引类型是 HNSW,它是内存驻留的。这意味着你为每个计划索引的 768d 向量需要大约 3 KB 的内存,内存需求与向量的数量和维度成线性关系。
如果你预计你的工作负载查询量较低,建议在M层集群中选择最便宜的选项,这些选项可以将 50% 的可用资源分配给存储索引到内存中。当使用专用搜索节点时,90% 的可用 RAM 可以用于托管索引。请注意,当使用M层集群时,需要使用代表性查询将索引预热到缓存中。对于专用搜索节点,索引将在索引构建完成后自动加载到缓存中。
如果你预计你的工作负载将具有高索引或查询并发性,建议使用具有高CPU选项的专用搜索节点,或者增加副本集中的专用搜索节点数量。这将增加可用于服务$vectorSearch查询的 vCPU 数量,并采用轮询方式。
摘要
在本章中,你探索了与向量搜索相关的各种概念。本章深入探讨了从嵌入模型中产生的高维向量如何成为衡量输入到这些模型的无结构数据语义相似度的有用度量。它还考察了HNSW索引及其如何用于加速查询向量和许多索引向量之间的向量相似度比较。
本章随后展示了这种类型的索引如何被大组织应用于各种现实世界场景,包括如RAG、语义搜索和RPA等架构模式。最后,本章回顾了在MongoDB Atlas中构建向量搜索系统的最佳实践,从如元数据提取等摄入时间考虑,到如专用搜索节点等部署模型考虑。
在下一章中,你将了解设计AI/ML应用的关键方面。你将学习如何有效地管理数据存储、流动、新鲜度和保留,以及确保强大安全性的技术。
第六章:AI/ML应用设计
随着智能应用领域的演变,它们的架构设计对于效率、可扩展性、可操作性和安全性变得至关重要。本章提供了一些建议,帮助你开始创建稳健和响应迅速的AI/ML应用。
本章从数据建模开始,探讨如何以最大化三种不同消费者(人类、应用程序和AI模型)的有效性的方式组织数据。你将了解数据存储,考虑不同数据类型的影响,并确定最佳存储技术。你将估算存储需求,并确定适用于示例应用的最佳MongoDB Atlas集群配置。
当你学习数据流时,你将探索数据通过摄取、处理和输出的详细移动,以保持完整性和速度。本章还讨论了数据生命周期管理,包括更新、老化和管理,确保数据保持相关性和合规性。
由于将数据或逻辑暴露给AI模型的风险,AI/ML应用的安全问题进一步扩大。本章讨论了安全措施和基于角色的访问控制(RBAC)来保护敏感数据和逻辑完整性。你还将了解数据存储、流动、建模和安全的最佳原则,提供避免常见陷阱的实用建议。
在本章中,你将使用一个虚构的新闻应用,称为MongoDB开发者新闻(MDN),它将类似于Medium.com,通过实际示例让你能够创建智能应用。
本章将涵盖以下主题:
-
数据建模
-
数据存储
-
数据流
-
新鲜度和保持
-
安全性和RBAC
-
最佳实践
技术要求
以下是在本章中跟随代码的先决条件:
-
一个MongoDB Atlas集群
M0层(免费)应该足够 -
一个具有访问
text-embedding-3-large模型权限的OpenAI账户和API密钥 -
一个Python 3工作环境
-
用于MongoDB、LangChain和OpenAI的已安装Python库
-
在MongoDB Atlas集群上创建的Atlas Search索引和Vector Search索引
数据建模
本节深入探讨了AI/ML系统所需的各种类型的数据,包括结构化、非结构化和半结构化数据,以及这些数据如何应用于MDN的新闻文章。以下是对每种数据的简要描述,以建立基本理解:
-
结构化数据符合预定义的模式,并且传统上存储在关系数据库中,用于事务信息。它为参与和智能系统提供动力。
-
非结构化数据包括二进制资产,如PDF文件、图像、视频等。对象存储,如Amazon S3,允许以较低的成本在灵活的目录结构下存储这些数据。
-
半结构化数据,如JSON文档,允许每个文档定义其架构,以适应常见的和独特的数据点,甚至可以处理某些数据的缺失。
MDN将存储新闻文章、订阅者资料、账单信息等。为了简化,在本章中,你将专注于每篇新闻文章及其相关二进制内容(例如图片)的数据。图6.1描述了articles集合的数据模型。

图6.1:文章集合的架构
文章集合表示新闻文章的元数据,包括创建细节、标签和贡献者。所有文档都包含标题、摘要、HTML和纯文本正文内容,以及相关的媒体元素,如图片。
使用嵌入丰富数据
要完成MDN数据模型,你需要考虑将通过嵌入表示和存储的数据。文本嵌入用于文章标题和摘要,将实现语义搜索,而图像嵌入将有助于找到跨文章使用的相似艺术品。表6.1描述了数据字段、要使用的嵌入模型及其向量大小。
| 类型 | 字段 | 嵌入模型 | 向量大小 |
|---|---|---|---|
| 文本 | 标题 |
OpenAI text-embedding-3-large |
1,024 |
| 文本 | 摘要 |
||
| 图片 | 内容 |
OpenAI CLIP | 768 |
表6.1:文章集合的嵌入
每篇文章都有一个标题和摘要。为了简化,你将它们连接起来,创建一个文本嵌入。理想情况下,对于图片,你将嵌入存储在每个内容对象数组中的contents数组中。然而,MongoDB Atlas目前不支持对象数组内部的字段用于向量索引,这导致了膨胀文档的反模式。最佳实践是将图像嵌入存储在单独的集合中,并使用扩展引用架构设计模式。你可以从本书进一步阅读章节中提供的链接了解更多关于使用MongoDB索引数组、膨胀文档和扩展引用模式的信息。图6.2显示了更新的数据模型。

图6.2:具有嵌入的文章架构
表6.2显示了相应的向量索引。
articles |
article_content_embeddings |
|---|---|
semantic_embedding_vix |
content_embedding_vix |
|
{
"fields": [
{
"numDimensions": 1024,
"path": "semantic_embedding",
"similarity": "cosine",
"type": "vector"
}
]
}
|
{
"fields": [
{
"numDimensions": 768,
"path": "content_embedding",
"similarity": "cosine",
"type": "vector"
}
]
}
|
表6.2:向量搜索索引定义
考虑搜索用例
在最终确定数据模型之前,让我们考虑文章的搜索用例,并对模型进行进一步的调整。以下是一些更广泛的搜索用例:
-
标题和摘要字段用于文本搜索,以及品牌和订阅类型字段用于过滤。 -
tags字段。你还需要一个向量搜索索引来覆盖标题+``摘要嵌入。 -
将
articles集合中的_id、brand和subscription_type字段合并到article_content_embeddings集合中。由于该集合中已经存在一个_id字段,您可以创建一个包含文章_id和内容_id的复合主键。图6**.3显示了更新的数据模型。

图6.3:带有嵌入的更新后的文章模式
表6.3 展示了更新的向量索引。
articles |
article_content_embeddings |
|---|---|
semantic_embedding_vix |
content_embedding_vix |
|
{
"fields": [
{
"numDimensions": 1024,
"path": "semantic_embedding",
"similarity": "cosine",
"type": "vector"
},
{
"path": "brand",
"type": "filter"
},
{
"path": "subscription_type",
"type": "filter"
}
]
}
|
{
"fields": [
{
"numDimensions": 768,
"path": "content_embedding",
"similarity": "cosine",
"type": "vector"
},
{
"path": "brand",
"type": "filter"
},
{
"path": "subscription_type",
"type": "filter"
},
{
"path": "_id.article_id",
"type": "filter"
}
]
}
|
表6.3:更新的向量搜索索引定义
表6.4 展示了新的文本搜索索引。
articles |
|---|
lexical_six |
|
{
"mappings": {
"dynamic": false,
"fields": {
"brand": {
"normalizer": "lowercase",
"type": "token"
},
"subscription_type": {
"normalizer": "lowercase",
"type": "token"
},
"summary": {
"type": "string"
},
"tags": {
"normalizer": "lowercase",
"type": "token"
},
"title": {
"type": "string"
}
}
}
}
|
表6.4:文本搜索索引定义
您在第4章,嵌入模型中学习了如何编写向量搜索查询。要了解更多关于混合搜索查询的信息,您可以参考https://www.mongodb.com/docs/atlas/atlas-vector-search/tutorials/reciprocal-rank-fusion/上的教程。
现在您已经了解了您的数据模型和所需的索引,您需要考虑MDN将承载的文章数量(包括嵌入和索引的大小)、峰值每日时间等因素,以确定整体存储和数据库集群需求。
数据存储
在本节中,您将进行容量估算,这是一种有根据的估计,用于存储需求。您需要考虑的不仅仅是体积大小和速度,还包括数据库集群的多个其他方面,这些方面对于在遵循预期的数据访问模式的同时利用您应用程序的数据是必需的。
MDN计划每天发布100篇文章。保留过去5年的文章,文章总数将达到182,500篇。有4800万订阅者和2400万每日活跃用户,每天在三个主要时区中,高峰访问时间为30分钟,如图6**.4所示。

图6.4:MDN订阅者时区和峰值时间
首先,您将估算总数据量。每篇文章都有一个用于语义搜索的1024维嵌入和五个用于图像搜索的768维嵌入,总共40 KB未压缩(维度使用双精度类型)。加上标题、摘要、正文(带和不含标记)以及其他字段,平均文章大小约为300 KB未压缩。
五年的文章将需要大约100 GB未压缩。使用MongoDB的WiredTiger压缩(Snappy、zlib和zstd也是可用的压缩选项),这将在磁盘上减少到大约50 GB。定义的向量索引增加了大约3.6 GB。图像和二进制资产将存储在Amazon S3上。为了简化,您将不会估算搜索和传统索引的大小。您可以放心地说,MDN在MongoDB Atlas上需要80到100 GB的磁盘空间,这在今天的云计算标准下是非常可管理的。
现在,您将确定最合适的MongoDB Atlas集群配置。
确定数据库集群类型
MongoDB Atlas提供两种主要的集群类型:
-
副本集有一个主节点用于写入,以及用于高可用性的辅助节点,这些节点也可以用于读取。这些集可以垂直扩展,并且可以通过在相同或不同的云区域添加更多节点来水平扩展读取。
-
分片集群由多个分片组成,每个分片都是整体数据集的一部分,并且每个分片都是一个副本集。它们在读写方面都可以垂直和水平扩展。分片可以放置在不同的云区域,以增强数据本地性和合规性。
那么,您如何确定副本集是否足够,或者是否需要分片集群?关键因素包括数据集的大小或应用程序的吞吐量,这些可能会挑战单台服务器的容量。例如,高查询率可能会耗尽服务器的CPU容量,而大于系统RAM的工作集大小可能会对磁盘驱动器的I/O容量造成压力。MDN每天发布100篇文章,因此从这个角度来看,分片不是必需的。
分片的其他原因包括数据治理和合规性以及恢复点目标(RPO)和恢复时间目标(RTO)策略,这些是灾难恢复和业务连续性规划中的关键指标。这些都不适用于MDN。
考虑到每秒写入次数较少和数据量可管理,使用副本集是合理的。现在您需要确定所需的RAM和IOPS数量;这两个都是快速响应时间的关键组件。
确定IOPS
MDN是一个低写入、高读取的使用案例。每天仅添加100篇文章,对存储系统的写入压力很小。表6.5显示了MongoDB Atlas提供的存储和IOPS选项。
| 存储类型 | 最低IOPS/存储 | 最高IOPS/存储 |
|---|---|---|
| 标准IOPS | 3,000 IOPS/10 GB | 12,288 IOPS/4 TB 16,000 IOPS/14 TB扩展存储已启用 |
| 分配IOPS | 100 IOPS/10 GB | 64,000 IOPS/4 TB |
| NVMe | 100,125 100%随机读IOPS 35,000写IOPS 380 GB | 3,300,000 100%随机读IOPS 1,400,000写IOPS 4,000 GB |
表6.5:MongoDB Atlas在AWS上的存储类型
如图6**.4所示,将有一个30分钟的峰值时段,在此期间预计每天有2400万用户活跃。因此,您需要配置6,000 IOPS,如表6.6*所示。这是基于订阅者分布、内存与磁盘读取以及每篇文章需要3 IOPS进行磁盘读取(150 KB压缩 ÷ 64 KB亚马逊EBS I/O大小)。
| 区域 | 分配 | DAU | 20% 读操作 来自磁盘 | 高峰时段的磁盘读操作/秒 | 所需的IOPS |
|---|---|---|---|---|---|
AMER^ |
40% | 9,600,000 | 1,920,000 | 1,067 | 3,200^ |
EMEA^ |
20% | 4,800,000 | 960,000 | 533 | 1,600^ |
APAC |
25% | 6,000,000 | 1,200,000 | 667 | 2,000 |
LATAM^ |
15% | 3,600,000 | 720,000 | 400 | 1,200^ |
| ^ 高峰时段重叠的区域 | 高峰IOPS | 6,000 |
表6.6:MDN全球订阅者分布
在AWS上的任何Atlas集群上的最低标准IOPS为3,000。要达到6,000 IOPS,您需要使用带有2TB磁盘的Atlas M50 层,这感觉是过度配置的,并且如果在一个云区域中部署,不会为所有读者提供低延迟。为了解决这个问题,MDN将在主要地理区域部署应用程序堆栈,实现区域配置、工作负载分配和本地读取,以提供最佳客户体验。
使用MongoDB Atlas,您可以在不同地区放置向量搜索节点。S40 层提供26,875次读取IOPS,这对于本例来说已经足够了,并且每个区域至少需要2个节点,这确保了高可用性。
虽然向量搜索节点将处理词法、语义和图像搜索,但必须在匹配后从MongoDB数据节点获取完整的JSON文档。为了完全支持本地读取,我们必须在同一地区配置只读节点并满足IOPS要求。我们可以使用Atlas M40 层来实现这一点。在确定了所需的IOPS后,您现在需要估计RAM。
确定RAM
对于数据节点,Atlas M40 层提供16 GB的RAM。MongoDB WiredTiger存储引擎为其缓存保留了(RAM - 1 GB)的50%。考虑到文档平均大小为300 KB,缓存可以存储大约28,000个文档。请注意,传统的索引大小可能会略微减少这个数字。考虑到每天增加100篇文章,M40 层的缓存可以容纳大约280天的数据,或大约9个月,这对于本例来说已经足够了。
搜索S40 层提供16 GB的RAM、2个vCPU和100 GB的存储。HNSW图或向量索引必须适应内存。
注意
您在第5章 向量数据库中学习了HNSW或分层可导航小世界。
一篇文章使用1 x 1,024向量 + 5 x 768向量 = 19.5 KB。对于182,500篇文章,需要3.5 GB的空间,16 GB的RAM对于向量搜索来说已经足够,并且还留有空间用于词法搜索索引。提供4 GB RAM、1 vCPU和50 GB存储的S30 层成本较低,但请注意,更多的CPU允许更多的并发搜索。
最终集群配置
您现在已经确定了MDN的集群配置。表6.7 描述了MDN的全局云架构,详细说明了Atlas节点在不同地区的分布。AMER 区域,被标识为主要区域,使用M40 层节点和S30 向量搜索节点为美洲提供服务读写和搜索,而EMEA、APAC 和LATAM 区域使用M40 只读节点和S30 向量搜索节点只为各自区域提供本地搜索。每个区域都需要部署MDN应用程序堆栈,如图表6.7中的全局地图所示。
| 区域 | Atlas 基础 层节点 | Atlas 只读节点 | Atlas Vector 搜索节点 |
|---|---|---|---|
AMER(主要区域) |
M40(包含三个) |
S30 x2 |
|
EMEA |
M40 x2 |
S30 x2 |
|
APAC |
M40 x2 |
S30 x2 |
|
LATAM |
M40 x2 |
S30 x2 |
|
MDN 全球 云架构![]() |
表 6.7:MDN 的 MongoDB Atlas 集群配置
性能、可用性与成本对比
注意,在 AMER 区域没有配置额外的只读节点,而是使用两个辅助节点作为只读节点。这节省了成本,因为 MDN 的写入模式较低,尽管可能存在资源竞争。在其他区域仅配置一个 M40 只读节点可以节省更多成本,但会增加维护窗口期间的延迟,因为读取将被重新路由。
为了在遵守最佳实践的同时防止 AMER 完全中断,考虑在三个区域中配置五个节点,并在每个区域部署具有两个可选举节点的应用程序堆栈。
数据流
数据流 涉及数据通过系统的移动,影响提供给消费者的结果的准确性、相关性和速度,这反过来又影响他们的参与度。本节探讨了处理数据源、处理数据、提示 LLM 和嵌入模型以使用 MDN 作为示例来丰富数据的设计考虑因素。图 6**.5 展示了此流程。

图 6.5:AI/ML 应用程序中的典型数据流
让我们从处理数据源的设计开始。数据可以以静态(静止)方式直接从文件中导入到 MongoDB Atlas,也可以以动态(移动)方式导入,允许进行持续更新、数据转换和逻辑执行。
处理静态数据源
最简单导入静态数据的方式是使用 mongoimport,它支持 JSON、CSV 和 TSV 格式。它非常适合初始加载或批量更新,因为它可以处理大型数据集。此外,将插入工作进程的数量增加到与主机 vCPU 相匹配可以提升导入速度。
mongoimport 还可以动态地更新外部数据源。您可以在运行时构建调用命令,并将它们作为进程外任务执行。一些视频游戏公司使用这种方法来更新来自移动应用商店的购买数据以更新玩家资料。
以 MDN 为例,用户在订阅时可以提供他们的 GitHub ID。使用 GitHub 的 API,您可以创建一个列表,列出用户拥有或贡献的存储库中使用的编程语言。一个计划的任务可以定期获取这些数据。然后可以将语言列表导入并合并到他们的个人资料中,以便稍后推荐文章。表 6.8 展示了如何进行此操作。
github-20240719.json |
|---|
|
{ "github_id" : "user1", "languages" : ["python", "csharp"], …}
{ "github_id" : "user2", "languages" : ["python", "cpp"], …}…
|
mdn.subscribers |
|---|
|
{ "_id" : ObjectId("669…ab8"), "github_id" : "user1", … }
{ "_id" : ObjectId("669…ab9"), "github_id" : "user2", … }…
|
mongoimport 命令用于合并匹配 github_id 字段的数据库数据 |
|---|
|
mongoimport --uri=<connection string to Atlas cluster>
--db=mdn --collection=subscribers --mode=merge
--file=github-20240719.json --upsertFields=github_id
--numInsertionWorkers=4
|
合并后的 mdn.subscribers |
|---|
|
{ "_id" : ObjectId("669…ab8"), "github_id" : "user1", "languages" : ["python", "csharp"], … }
{ "_id" : ObjectId("669…ab9"), "github_id" : "user2", "languages" : ["python", "cpp"], … }…
|
表 6.8:使用 mongoimport 合并数据的示例
虽然 mongoimport 是一个适用于各种数据导入需求的通用工具,但它不支持连续同步、逻辑执行或数据转换。现在您将探索一些支持这些功能的方法。
存储带有向量嵌入的操作数据
当原始表示存储或更新时,其相应的向量嵌入必须刷新以准确反映内容。这可以通过以下方式完成:
-
同步: 在数据库操作之前获取更新的向量嵌入,同时写入数据和嵌入。此方法适用于快速、简单的嵌入模型或当模型在本地托管时。然而,如果嵌入模型的响应时间不同,可能会失败。
-
异步: 确保主数据的即时一致性,并允许在之后提示嵌入模型。虽然这提供了可伸缩性和处理不可预测的模型的能力,但它在嵌入数据暂时过时的过程中引入了延迟。
您可以使用以下四种方法在 MongoDB 中异步保持嵌入数据的最新状态:
-
Kafka 连接器: 您可以通过 Kafka 连接器促进数据从 Apache Kafka 流入 MongoDB 集合。这是一个经过 Confluent 验证的连接器,允许数据从 Apache Kafka 主题流入 MongoDB 作为 数据汇,并将 MongoDB 的更改发布到 Kafka 主题作为 数据源。为了保持嵌入数据的最新状态,您将使用汇连接器并在 Java 中开发后处理器。您可以在https://www.mongodb.com/docs/kafka-connector/v1.3/sink-connector/fundamentals/post-processors/#sink-connector-post-processors了解更多有关汇后处理器的信息。
-
Atlas 流处理: 此方法使用与 MongoDB Atlas 数据库相同的查询 API 处理复杂的数据流。它支持连续聚合,并包括用于消息完整性和及时问题检测的模式验证。处理后的数据可以写入 Atlas 集合,并且它们集成到 Atlas 项目中,独立于 Atlas 集群。Atlas 流处理逻辑使用 JavaScript 和 MongoDB 聚合语法进行编程。有关使用 Atlas 流处理处理嵌入数据的示例,请参阅 https://www.mongodb.com/solutions/solutions-library/rag-applications。
-
Atlas 触发器:Atlas 触发器通过响应事件或遵循预定义的日程来执行应用程序和数据库逻辑。每个触发器都监听特定的事件类型,并与 Atlas 函数相关联。当发生匹配的事件时,触发器被触发,并将事件对象传递给相关联的函数。触发器可以响应各种事件,例如集合中的特定操作、用户创建或删除等认证事件,以及预定时间。它们是完全管理的变更流实例,但仅限于 JavaScript。有关使用 Atlas 触发器保持嵌入数据最新的示例,请参阅https://www.mongodb.com/developer/products/atlas/semantic-search-mongodb-atlas-vector-search/。
-
变更流:此方法提供对数据变更的实时访问。应用程序可以订阅集合、数据库或整个部署中的变更,并立即做出反应,事件按顺序处理且可恢复。使用聚合框架,变更流允许过滤和转换通知。它们可以与任何官方 MongoDB 驱动程序支持的编程语言一起使用。然而,它们不是完全管理的,需要维护一个运行的主机,以配合主应用程序。
由于本书是为 Python 开发者编写的,您将学习如何使用用 Python 编写的变更流。表 6.9 展示了使用 LangChain 和 OpenAI 将 MDN 文章的标题和摘要嵌入的 Python 3 变更流。它根据 图 6**.3 中的数据模型和 表 6.3 中的向量索引触发新文章或标题或摘要的更改。
import os
from langchain_openai import OpenAIEmbeddings
from pymongo import MongoClient
from pymongo.errors import PyMongoError
# Set the OpenAI API key as an environment variable
os.environ["OPENAI_API_KEY"] = "YOUR-OPENAI-API-KEY"
# Define the MongoDB Atlas connection string
ATLAS_CONNECTION_STRING = "YOUR-MONGODB_ATLAS-CONNSTRING"
# Create a MongoClient instance to connect to MongoDB Atlas
client = MongoClient(
ATLAS_CONNECTION_STRING, tls=True, tlsAllowInvalidCertificates=True
)
# Select the 'articles' collection from the 'mdn' database
coll = client["mdn"]["articles"]
# Instantiate the OpenAIEmbeddings model with specified parameters
embedding_model = OpenAIEmbeddings(
model="text-embedding-3-large", dimensions=1024, disallowed_special=()
)
# Define a function to handle changes detected in the MongoDB collection
def handle_changes(change):
# Extract the document ID from the change event
doc_id = change["documentKey"]["_id"]
# Create a filter to identify the document in the collection
doc_filter = {
"_id": doc_id
}
# Combine the title and summary of the document into a single text string
text = [change["fullDocument"]["title"] + " " + change["fullDocument"]["summary"]]
# Generate embeddings for the text
embeddings = embedding_model.embed_documents(text)
# Create an update document to set the 'semantic_embedding' field with the generated embeddings
set_fields = {
"$set": {
"semantic_embedding": embeddings[0]
}
}
# Update the document in the collection with the new embeddings
coll.update_one(doc_filter, set_fields)
print(f"Updated embeddings for document {doc_id}")
# Start monitoring the MongoDB collection for changes
try:
# Define a stream filter to match insert and update operations affecting the title or summary fields
stream_filter = [
{
"$match": {
"$or": [
{"operationType": "insert"},
{
"$and": [
{"operationType": "update"},
{
"$or": [
{
"updateDescription.updatedFields.title": {
"$exists": True
}
},
{
"updateDescription.updatedFields.summary": {
"$exists": True
}
},
]
},
]
},
]
}
}
]
# Open a change stream to watch for changes in the collection
with coll.watch(stream_filter, full_document="updateLookup") as stream:
print("Listening for changes...")
for change in stream:
print(f"Change detected: {change}. Processing")
handle_changes(change)
except PyMongoError as e:
# Print an error message if a PyMongoError occurs
print(f"An error occurred: {e}")
finally:
# Close the MongoDB client connection
client.close()
表 6.9:用于设置或更新嵌入的 Python 编写的变更流
现在您已经学会了如何处理设置或更新嵌入的数据流,您将学习关于数据新鲜度和保留度,这对于提供相关和及时的内容至关重要。
新鲜度和保留度
新鲜数据和有效的保留策略确保您的内容相关且及时送达。新鲜度使用户保持对最新文章、评论和推荐的参与度。保留策略管理数据生命周期,保留有价值的历史数据以供分析,同时清除过时的数据。本节探讨了确保内容更新和高效数据流的方法。
实时更新
主要关注的是实时摄取和更新新数据,使其在所有云区域可用。对于新闻网站,这意味着新文章及其向量嵌入应迅速持久化和复制,以便全球访问。
要使用分布式数据模型和应用程序实现这一点,请使用ACID事务来确保文章及其内容嵌入作为一个单一单元一起写入。有关在Python中创建MongoDB事务的示例,请参阅https://learn.mongodb.com/learn/course/mongodb-crud-operations-in-python/lesson-6-creating-mongodb-transactions-in-python-applications/learn?page=2。
接下来,在分布式设置中使用MongoDB的writeConcern、readConcern和readPreference的可调一致性来平衡数据可靠性、一致性和性能。这些修饰符有助于确保数据完整性和快速访问。以下是对这些修饰符的解释,但为了更深入的理解,您可以访问https://www.mongodb.com/docs/manual/core/causal-consistency-read-write-concerns/:
-
writeConcern:majority通过仅在数据写入大多数副本集成员后确认写入操作来确保数据一致性和持久性,从而在故障期间降低数据丢失的风险。这是默认的写入关注点。 -
readConcern:majority通过确保读取操作返回大多数副本集成员确认的最最新数据来提供读取一致性,为应用程序提供数据的一致视图。 -
readPreference:nearest通过将读取操作定向到具有最低网络延迟的副本集成员来优化延迟。对于MDN,这通过允许每个区域应用程序部署从最近的MongoDB数据和向量节点读取,从而最小化响应时间,并平衡一致性和性能。
现在您已经学会了如何确保数据可用性和速度,接下来的重点是数据生命周期管理,这是数据新鲜度和保留的关键方面。
数据生命周期
数据生命周期指的是数据从创建到删除所经历的各个阶段,以及数据如何穿越和改变系统或存储格式,包括数据归档或删除时的情况。随着最新内容的添加,旧内容的相关性可能会降低。
例如,旧文章可以移动到存档数据库或冷存储,从而降低存储成本并优化活动数据库的性能。然而,将数据移动到冷存储可能会比操作数据库降低搜索能力。以下是处理数据生命周期的三种方法及其权衡:
-
操作集群中的所有数据:将所有数据保留在操作集群中是最高性能但成本最高的方法,适用于大多数数据频繁访问的场景,例如全球在线游戏、身份验证提供商或金融平台。MongoDB Atlas通过分片集群和全局集群支持这一点。全局集群为云区域分配数据区域以进行容量管理和数据本地性。
-
活跃和历史操作数据集群:这涉及到使用高性能硬件处理最近的数据,使用能力较低的硬件处理旧数据,以平衡功能和成本节约。使用MongoDB Atlas,可以使用集群间同步和TTL索引将数据从活跃集群移动到历史集群。其他平台如Apache Kafka、Confluent和Striim也支持这种方法。
-
活跃数据集群和历史存储:可以将全部历史数据卸载到冷存储,同时在操作集群中保留关键字段,从而实现完全或有限的查询和搜索能力。对于MDN来说,这确保了用户可以通过词汇语义搜索找到历史文章,全文存储在冷存储中,并在需要时访问。使用MongoDB Atlas,可以通过在线归档和数据联邦来实现这一点。在线归档会根据设定的过期时间自动将数据从集群移动到成本更低的云存储。数据联邦允许透明地查询集群和归档,无论数据来源。
本节介绍了数据生命周期管理,强调了数据从创建到归档的管理方式。你学习了三种策略:在操作集群中保留所有数据以实现最佳性能、分离活跃和和历史数据以平衡成本和性能,以及在保留一些搜索功能的同时将历史数据卸载到冷存储。现在,你将学习如何升级嵌入模型。
采用新的嵌入模型
OpenAI于2022年12月15日用text-embedding-ada-002取代了text-search-davinci-*-001模型,随后于2024年1月25日用text-embedding-small/large取代。很可能在你阅读这本书的时候,这些模型也会被取代。
正如你在第4章“嵌入模型”中学习的,从一个模型中提取的嵌入与另一个模型不兼容。随着新模型的采用,可能需要重新嵌入先前索引的数据。这是一个资源密集型活动,需要提前进行设计考虑。
你需要选择一种方法来采用新的嵌入模型。你可以继续使用现有的向量字段并执行长时间的全有或全无升级,暂时进行双重嵌入,或者实施逐步升级。让我们探讨这三种方法:
-
使用现有的向量字段:这种方法保持应用程序代码完整,但需要停机时间来重新嵌入数据和替换向量索引。如果重新嵌入和重新索引时间适合您的允许停机窗口,则此方法适用。
-
临时双重嵌入:这种方法使用旧模型和新模型双重嵌入新或修改后的字段。它使用后台作业为未修改的数据添加新的嵌入。当所有数据都有双重嵌入时,应用程序将更新并部署以使用新的嵌入。一旦稳定,可以另一个后台作业移除过时的向量和索引。确保有足够的磁盘空间和内存,因为当两组向量共存时。如果停机窗口很小且仅容纳应用程序部署时间,则此方法适用。
-
MongoDB的向量索引的
filter类型(如表6.3所示),您可以引入一个新字段来区分具有旧向量和新向量的文档并实现联合。最终,可以删除旧向量和索引,并可以移除不必要的逻辑。如果允许没有停机时间,则此方法适用。
通过解决这三个主要问题——数据摄取和实时更新、管理数据生命周期和老化、以及升级嵌入模型——您的应用程序可以确保其数据保持新鲜和相关性,提供一个最佳的平台并努力实现最佳的用户体验。现在,您将了解安全和其在人工智能密集型应用中的考虑因素。
安全性和RBAC
安全措施保护数据免受未经授权的访问和泄露,而RBAC确保基于角色的适当访问级别。以下是保护数据完整性和隐私的关键安全和RBAC策略:
-
数据加密和安全的存储:静态和传输中的数据加密对于确保应用程序的安全性至关重要。静态加密保护数据免受未经授权的访问,而传输加密确保数据在用户与应用程序之间移动时的安全性。MongoDB Atlas提供与AWS密钥管理服务(AWS KMS)的内置集成,用于静态加密,以及TLS/SSL作为数据传输的默认设置。
-
访问控制和用户身份验证:基于角色的访问控制(RBAC)管理权限,确保用户只能访问必要的数据和功能。在MDN的情况下,如编辑者和读者这样的不同角色需要不同级别的访问。根据最小权限原则,可以为MongoDB上的不同数据库用户设置不同级别的权限。例如,仅用于嵌入数据的微服务所使用的应用程序标识符将对存储嵌入的集合具有写入权限,而用于人类操作者的应用程序标识符则只有读取权限。
-
监控和审计:持续的监控和审计能够实时检测和响应安全事件。监控工具和审计日志跟踪用户活动并识别异常访问模式。MongoDB Atlas提供高级监控和警报功能,允许管理员为可疑活动设置警报。定期审查审计日志确保符合安全策略,并为改进安全提供见解。
-
数据备份和恢复:通过定期备份维护数据完整性和可用性,以最小化安全漏洞或事件期间的中断和损失。MongoDB Atlas提供带有快照的自动化备份解决方案,确保快速恢复。如果启用了静态加密(例如,AWS KMS),则在卷和备份中,嵌入和操作数据都使用相同的密钥进行加密。
虽然有许多与安全相关的关注点,但刚刚提到的那些应该足以开始构建AI应用程序。确保安全是一个组织必须采用并执行的持续努力,以维持合规性、培养用户信任并保护应用程序完整性。
AI/ML应用程序设计的最佳实践
本节涵盖了本章涵盖的五个关注点——数据建模、数据存储、数据流、数据新鲜度和保留以及安全和RBAC的最佳实践。这些指南将帮助确保您的应用程序高效、可扩展且安全,为构建可靠且高性能的AI应用程序提供坚实的基础。以下是AI/ML应用程序设计每个方面的前两个最佳实践。
-
数据建模:以下技术确保处理嵌入的高效性和性能:
-
在单独的集合中存储嵌入:将嵌入存储在单独的集合中,以避免文档膨胀,尤其是在涉及多个嵌入和嵌套索引限制时。复制字段以确保高效的过滤并保持搜索性能。
-
混合搜索:使用互惠排名融合结合语义和词汇搜索。这种混合方法通过利用两者的优势来提升搜索功能。
-
-
数据存储:为了优化数据库集群的大小,实施以下最佳实践:
-
基于峰值使用情况的足够的IOPS和RAM:根据峰值访问时间和应用程序的读写模式计算所需的IOPS。确保数据和搜索节点有足够的RAM来处理最请求数据的缓存和索引需求。
-
本地读取:在各个区域部署节点有助于最小化读取延迟并提升用户体验。确保每个区域都有所有必要的节点以完全本地提供服务。
-
-
数据流:考虑以下策略以有效利用数据流:
-
异步嵌入更新: 通过异步更新向量嵌入来确保主数据一致性。此方法适应可扩展性和不可预测的模型响应时间,尽管它引入了暂时的延迟。
-
动态数据处理: 利用变更流、Atlas触发器、Kafka和Atlas流处理等技术来处理连续更新、转换和逻辑执行。
-
-
数据新鲜度和保留: 以下最佳实践可以确保您的应用相关且及时:
-
最新的嵌入模型: 一个模型的嵌入与另一个模型不兼容。如果可能,在停机期间计划模型升级,或者考虑逐步升级,这从架构上来说是复杂的,但不需要停机时间。利用MongoDB灵活的数据模型在嵌入之间进行转换。
-
数据分层: 通过将旧数据移动到归档集群或冷存储,同时保持最近的数据在高性能集群中,实现数据老化策略。使用MongoDB Atlas的更广泛功能,如在线归档、数据联邦等,以实现有效的数据分层。
-
-
安全和RBAC(基于角色的访问控制): 以下是为确保您的数据安全的最佳实践:
-
RBAC(基于角色的访问控制): 分配基于角色的权限并遵循最小权限原则(PoLP),确保用户和实体只能访问必要的数据和操作。例如,代码嵌入数据应仅对嵌入集合具有写入访问权限。
-
加密和存储: 启用静态加密并集成KMS,以确保所有数据卷和备份都使用您自己的密钥进行加密。
-
实施这些最佳实践可提高您AI/ML应用的效率、可扩展性和安全性。虽然这只是起点,但这些指南为构建可靠、高性能的系统奠定了坚实的基础。有了这些最佳实践,您可以导航现代AI的复杂性,并使您的应用为在快速发展的技术环境中长期成功和适应性做好准备。
摘要
本章涵盖了开发智能应用的关键架构考虑因素。您学习了数据建模以及如何使模型适应用例、解决技术限制并考虑模式和反模式。这种方法确保数据不仅有用,而且可访问并在您的AI/ML系统的各个组件中得到优化利用。
数据存储是本章的另一个关键方面,侧重于根据不同的数据类型和应用程序的具体需求选择适当的存储技术。它强调了准确估计存储需求以及选择正确的MongoDB Atlas集群配置的其他方面的重要性。MDN应用程序的虚构示例作为一个实际案例研究,说明了如何在现实世界场景中应用这些原则。
本章还探讨了数据通过摄取、处理和输出的流程,以确保数据完整性并保持数据操作的速度。本章还讨论了数据生命周期管理,包括数据新鲜度和保留的重要性。您学习了管理更新和更改应用程序使用的嵌入模型策略。
在人工智能/机器学习应用中,安全性是一个至关重要的关注点,您学习了关于保护数据完整性和应用程序逻辑的简要但重要的要点。本章以最佳实践的汇编结束,总结了数据建模、存储、流动和安全性方面的关键原则,提供了避免常见陷阱并增强稳健人工智能/机器学习应用程序开发的实用建议。
在下一章中,您将探索不同的AI/ML框架、Python库、公开可用的API和其他工具。
第二部分
构建您的Python应用程序:框架、库、API和向量搜索
以下章节将为您提供通过详细的说明和示例来增强开发者和使用者体验的必要工具,以用于人工智能开发。
本书本部分包括以下章节:
第七章:有用的框架、库和API
如您所预期,Python是构建智能AI应用程序最受欢迎的编程语言。这得益于其灵活性、易用性,以及其庞大的AI和机器学习(ML)库。Python几乎为构建生成式AI(GenAI)应用程序所需的全部必要任务提供了专门的库。
在第一章“开始使用生成式AI”中,您了解了生成式AI堆栈和AI的演变。就像AI景观一样,Python库和框架空间也经历了演变阶段。早期,pandas、NumPy和polars等库用于数据清理和转换工作,而PyTorch、TensorFlow和scikit-learn用于训练机器学习模型。现在,随着生成式AI堆栈、大型语言模型(LLMs)和向量数据库的兴起,一种新的AI框架已经出现。
这些新的库和框架旨在简化由LLMs驱动的应用程序的创建。由于构建生成式AI应用程序需要无缝集成来自多个来源的数据和使用多种AI模型,这些AI框架提供了内置功能,以促进数据的获取、迁移和转换。
本章深入探讨了AI/ML框架的世界,探讨了它们的重要性,并强调了为什么Python成为了AI/ML开发的首选语言。到本章结束时,您将能够理解最流行的框架和库,以及它们如何帮助您——开发者——构建您的生成式AI应用程序。
本章将涵盖以下主题:
-
AI/ML框架
-
Python库
-
公开可用的API和其他工具
技术要求
要执行本章中显示的步骤,您需要以下内容:
-
Python的最新主要版本。
-
运行MongoDB版本6.0.11、7.0.2或更高版本的免费层Atlas集群。
-
将您的当前IP地址添加到Atlas项目访问列表中。
-
一个用于在交互式环境中运行Python代码的环境,例如Jupyter Notebook或Colab。本章使用Jupyter Notebook。
Python用于AI/ML
Python已经在各个领域确立了其作为首选编程语言的地位,但在AI、ML以及构建由大型语言模型(LLMs)驱动的应用程序方面最为显著。Python提供了简单性、可读性和强大的库生态系统,使其成为所有类型用户的理想选择,无论是开发者、研究人员,还是刚开始接触编程的学生。Python也已成为构建新的LLM驱动应用程序的首选语言,这凸显了Python的有用性、流行性和多功能性。
在本节中,您将了解一些使Python成为构建现代AI驱动应用程序的绝佳选择的理由:
-
简洁性和可读性:Python的语法设计旨在直观且清晰,这是其核心优势之一。Python可以用几行易于阅读和理解代码来表示复杂的算法和任务。
-
丰富的库和框架生态系统:Python提供了一系列专门为AI/ML用例设计的库和框架。例如,TensorFlow、PyTorch和scikit-learn等库在机器学习任务中一直很受欢迎。Hugging Face的Transformers库也已成为构建现代LLM应用程序的开发者工作流程中不可或缺的一部分。它提供了预训练模型和简单的API,以便针对特定任务微调模型。这些库不仅加速了开发时间,还为全球的开发者提供了前沿的解决方案。
-
强大的社区和支持:Python是世界上最受欢迎的编程语言之一,因此拥有庞大的社区。根据Stack Overflow 2023调查(https://survey.stackoverflow.co/2023/),它是JavaScript(不包括HTML/CSS)之后的第二大受欢迎的编程语言。这个强大且庞大的社区提供了丰富的资源,包括教程、讨论论坛参与和开源项目,为那些致力于构建现代应用程序的人提供了一个有价值的支持系统。
-
与其他技术的集成:Python能够无缝集成到其他技术和编程语言中,使其成为AI/ML任务和构建LLM应用程序的绝佳选择。例如,Python可以轻松与C/C++等编程语言接口,用于性能关键任务。它也与Java和C#等语言很好地接口。Python的这种灵活性有助于在多样化的环境中部署LLM应用程序,确保Python可以成为大型异构系统的一部分。
-
快速原型设计和实验:构建一个复杂的AI/ML应用程序需要多次测试、实验和微调。Python允许开发者通过几行代码快速构建原型。易于测试和调试也有助于快速原型化解决方案。Python的交互式环境,如Jupyter Notebook,为此提供了一个极好的平台。使用Python,构建LLM应用程序的开发者可以快速测试假设、可视化数据并以交互式方式调试代码。
Python结合了速度、简单性、专业的库和框架以及强大的社区支持,并且易于与其他语言和技术集成,所有这些都使其成为构建现代LLM应用程序的绝佳选择。
AI/ML框架
AI/ML 框架是简化 ML 模型开发和部署的必要工具,提供预构建算法、优化性能和可扩展解决方案。它们使开发者能够专注于优化他们的模型和 GenAI 应用,而不是陷入底层实现的困境。使用框架确保效率、适应性和利用尖端 AI 进步的能力。开发者应该对这些框架感兴趣,因为它们还可以减少开发时间并提高 GenAI 突破的潜力。
MongoDB 与许多开发者可能熟悉的 AI/ML 框架有集成,例如 LangChain、LlamaIndex、Haystack、Microsoft Semantic Kernel、DocArray 和 Flowise。
在本节中,你将了解 LangChain,这是最受欢迎的 GenAI 框架之一。尽管它非常受欢迎,但它绝对不是唯一受欢迎的框架。如果你对其他框架感兴趣,你可以查看本书末尾 附录:进一步阅读 部分中链接的文档,或者查看 Python 支持的最新 AI/ML 框架列表 https://www.mongodb.com/docs/languages/python/。
LangChain
LangChain 是一个用于开发由 LLM 驱动的应用的框架。LangChain 简化了 LLM 应用生命周期的每个阶段。它使构建将外部数据源和计算连接到 LLM 的应用成为可能。基本的 LLM 链仅依赖于提示模板中提供的信息来生成响应,而 LangChain 的概念允许你扩展这些链以进行高级处理。
在本节中,你将学习如何使用 LangChain 对你的数据进行语义搜索并构建 检索增强生成 (RAG) 实现。在开始之前,请确保你已经安装并设置了本章节 技术要求 部分中列出的所有必要工具,作为准备工作。
开始使用 LangChain
执行以下步骤以设置 LangChain 的环境:
-
首先,安装必要的依赖项:
pip3 install --quiet --upgrade langchain==0.1.22 langchain-mongodb==0.1.8 langchain_community==0.2.12 langchain-openai==0.1.21 pymongo==4.5.1 polars==1.5.0 pypdf==3.15.0 -
运行以下代码以导入所需的包:
import getpass, os, pymongo, pprint from langchain_community.document_loaders import PyPDFLoader from langchain_core.output_parsers import StrOutputParser from langchain_core.runnables import RunnablePassthrough from langchain_mongodb import MongoDBAtlasVectorSearch from langchain_openai import ChatOpenAI, OpenAIEmbeddings from langchain.prompts import PromptTemplate from langchain.text_splitter import RecursiveCharacterTextSplitter from pymongo import MongoClient -
在导入必要的包之后,请确保环境变量设置正确。你有两个重要的秘密需要存储为环境变量:你的 OpenAI API 密钥 和 MongoDB Atlas 连接字符串。
运行以下命令以将你的 OpenAI API 密钥存储为环境变量:
os.environ["OPENAI_API_KEY"] = getpass.getpass("OpenAI API Key:") mongodb+srv://<username>:<password>@<clusterName>.<hostname>.mongodb.net运行以下命令以将你的 MongoDB Atlas 连接字符串存储为环境变量:
ATLAS_CONNECTION_STRING = getpass.getpass("MongoDB Atlas SRV Connection String:")现在,你已经准备好连接到 MongoDB Atlas 集群。
-
接下来,你需要实例化
MongoClient并将你的连接字符串传递给 MongoDB Atlas 数据库以建立通信。运行以下代码以建立连接:# Connect to your Atlas cluster client = MongoClient(ATLAS_CONNECTION_STRING) -
接下来,您将指定要创建的数据库和集合的名称。在这个例子中,您将创建一个名为
langchain_db的数据库和一个名为test的集合。您还将定义要创建并使用以下代码的向量搜索索引的名称:# Define collection and index name db_name = "langchain_db" collection_name = "test" atlas_collection = client[db_name][collection_name] vector_search_index = "vector_index"
通过这些步骤,您已设置连接的基本设置。现在,您有了数据库的骨架,您将想要定义您的应用程序做什么。
在这种情况下,您将执行以下操作:
-
获取一个公开可访问的 PDF 文档。
-
将其分割成更小的信息块,以便您的 GenAI 应用程序轻松消费。
-
将数据上传到 MongoDB 数据库。
此功能不是您必须从头开始构建的。相反,您将使用 LangChain 提供的免费开源库集成,称为 PyPDFLoader,您在本文本此部分的 步骤 2 中已导入。
获取和分割公开 PDF 文档
使用 PyPDFLoader 获取公开可用的 PDF 文件相当简单。在以下代码中,您将获取一个公开可访问的 PDF 文档,并将其分割成更小的块,您可以稍后将其上传到您的 MongoDB 数据库中:
# Load the PDF
loader = PyPDFLoader("https://query.prod.cms.rt.microsoft.com/cms/api/am/binary/RE4HkJP")
data = loader.load()
# Split PDF into documents
text_splitter = RecursiveCharacterTextSplitter(chunk_size=200, chunk_overlap=20)
docs = text_splitter.split_documents(data)
# Print the first document
docs[0]
然后,您将收到以下输出:
Document(metadata={'source': 'https://query.prod.cms.rt.microsoft.com/cms/api/am/binary/RE4HKJP', 'page': 0}, page_content='Mong oDB Atlas Best Practices January 20 19A MongoD B White P aper')
使用此代码,您首先实例化了 PyPDFLoader,然后传递了公开可访问的 PDF 文件的 URL:https://query.prod.cms.rt.microsoft.com/cms/api/am/binary/RE4HkJP。接下来,您将获取的 PDF 文件加载到 data 变量中。
之后,您将 PDF 文件的文本分割成更小的块。在这个例子中,您将块大小设置为 200 个字符,并在块之间允许 20 个字符的重叠。重叠保持了块之间的上下文。请注意,这个数字不是随意的,关于您的分块策略应该是什么有很多意见。本书 附录:进一步阅读 章节中讨论了一些这些资源。
您将分割的块存储在 docs 变量中,并打印了分割文档的第一块。这表明您通过 print 命令的输出请求是成功的,您可以轻松地确认此条目中的信息是否正确。
创建向量存储
在您将文档分割成块之后,您将使用以下代码实例化向量存储:
# Create the vector store
vector_store = MongoDBAtlasVectorSearch.from_documents(
documents = docs,
embedding = OpenAIEmbeddings(disallowed_special=()),
collection = atlas_collection,
index_name = vector_search_index
)
在前面的代码中,您使用 MongoDBAtlasVectorSearch.from_documents 方法创建了一个名为 vector_store 的向量存储,并指定了各种参数:
-
documents = docs: 您想要存储在向量数据库中的文档名称 -
embedding = OpenAIEmbeddings(disallowed_special=()): 使用 OpenAI 的嵌入模型为文档生成向量嵌入的类 -
collection = atlas_collection: 文档将存储的 Atlas 集合 -
index_name = vector_search_index: 用于查询向量存储的索引名称
您还需要在MongoDB数据库中创建您的Atlas向量搜索索引。有关如何操作的详细说明,请参阅第8章,在AI应用中实现向量搜索。这必须在成功运行前面的代码之前完成。在创建向量搜索索引时,请使用以下索引定义:
{
"fields":[
{
"type": "vector",
"path": "embedding",
"numDimensions": 1536,
"similarity": "cosine"
},
{
"type": "filter",
"path": "page"
}
]
}
此索引定义了两个字段:
-
text-embedding-ada-002模型。它有1,536个维度,并使用余弦相似度来衡量相似度。您还可能想考虑来自OpenAI的其他一些较新的模型,如text-embedding-3-small和text-embedding-3-large,这些模型针对不同的用例进行了优化,因此具有不同的维度数。有关更多详细信息以及当前选项,请参阅https://platform.openai.com/docs/guides/embeddings。 -
页面字段:一种用于根据PDF中的页面编号进行预过滤数据的过滤器类型字段。
现在,您可以成功运行代码,获取一个公开可用的PDF,将其分割成更小的数据部分,并将它们存储在MongoDB Atlas数据库中。完成这些步骤后,您可以执行其他任务,例如运行查询以对您的数据进行语义搜索。您可以在第8章,在AI应用中实现向量搜索和第10章,优化语义数据模型以提高准确性中了解基本语义搜索。
关于这个主题的更多信息,您还可以查阅LangChain的官方文档,可在https://python.langchain.com/v0.2/docs/integrations/vectorstores/mongodb_atlas/#pre-filtering-with-similarity-search找到。
接下来,让我们探讨一些具体的LangChain功能,这些功能在构建GenAI应用时您会发现非常有用。
带分数的LangChain语义搜索
LangChain提供了一些特别有用的方法来对您的数据进行语义搜索并返回一个分数。这个分数是指根据语义内容,查询与匹配文档之间的相关性度量。当您想向用户返回多个结果并限制结果数量时,可以使用这个分数。例如,这个分数在返回关于某个主题的前三个最相关内容时可能非常有用。
您将在这里使用的方法是similarity_search_with_score:
query = "MongoDB Atlas security"
results = vector_store.similarity_search_with_score(
query = query, k = 3
)
pprint.pprint(results)
您将查询传递给similarity_search_with_score函数,并将k参数指定为3以限制返回的文档数量为3。然后,您可以打印输出:
[(Document (page_content='To ensure a secure system right out of the box, \nauthentication and IP Address whitelist ing are\nautomatically enabled. \nReview the security section of the MongoD B Atlas', metadata={'_id': {'Soid": "667 20a81b6cb1d87043c0171'), 'source': 'https://query.prod.cms.rt.microsoft.com/cms/api/am/binary/RE4HKJP', 'page': 17}),
0.9350903034210205),
(Document(page_content='MongoD B Atlas team are also monitoring the underlying\ninfrastructure, ensuring that it i s always in a healthy state. \nApplication Logs And Database L ogs', metadata={'_id': {'soid': '66720a81b6cb1d87043 c013c'), 'source': 'https://query.prod.cms.rt.microsoft.com/cms/api/am/binary/RE4HKJP', 'page': 15}),
0.9336163997650146),
(Document(page_content="MongoD B.\nMongoD B Atlas incorporates best practices to help keep\nmanaged databases heal thy and optimized. T hey ensure\noperational continuity by converting complex manual tasks', metadata={'_id': {'so id: '66728a81b6cb1d87043c011f'), 'source': 'https://query.prod.cms.rt.microsoft.com/cms/api/am/binary/RE4HKJP', 'p age': 13)),
0.9317773580551147)]
正如您在输出中看到的,返回了三个具有最高相关性分数的文档。每个返回的文档都附有一个介于0到1之间的相关性分数。
带有预过滤的语义搜索
MongoDB允许你使用match表达式预过滤数据,以在执行更计算密集型的向量搜索之前缩小搜索范围。这为开发者提供了几个好处,例如提高性能、提高准确性和增强查询相关性。在预过滤时,请记住在创建索引时对任何你想要过滤的元数据字段进行索引。
这里有一个代码片段,展示了如何使用预过滤进行语义搜索:
query = "MongoDB Atlas security"
results = vector_store.similarity_search_with_score(
query = query,
k = 3,
pre_filter = { "page": { "$eq": 17 } }
)
pprint.pprint(results)
在此代码示例中,你有与之前执行普通语义搜索相同的查询字符串。k值设置为3,因此它只返回最匹配的前三个文档。你还提供了一个pre_filter查询,这基本上是一个MQL表达式,使用$eq运算符指定MongoDB应仅返回位于原始PDF文档第17页的内容和分块信息。
使用LangChain实现基本的RAG解决方案
LangChain的功能不仅限于在存储在向量数据库中的数据上执行语义搜索查询。它还允许你构建强大的GenAI应用程序。以下代码片段将教你一种简单的方法来完成以下操作:
-
设置一个基于相似性的MongoDB Atlas Vector Search检索器。
-
返回最相关的10个文档。
-
使用带有LLM的自定义RAG提示来根据检索到的文档回答问题:
# Instantiate Atlas Vector Search as a retriever
retriever = vector_store.as_retriever(
search_type = "similarity",
search_kwargs = { "k": 3 }
)
# Define a prompt template
template = """
Use the following pieces of context to answer the question at the end.If you don't know the answer, just say that you don't know, don't try to make up an answer.
{context}
Question: {question}
"""
custom_rag_prompt = PromptTemplate.from_template(template)
llm = ChatOpenAI()
def format_docs(docs):
return "\n\n".join(doc.page_content for doc in docs)
# Construct a chain to answer questions on your data
rag_chain = (
{ "context": retriever | format_docs, "question": RunnablePassthrough()}
| custom_rag_prompt
| llm
| StrOutputParser()
)
# Prompt the chain
question = "How can I secure my MongoDB Atlas cluster?"
answer = rag_chain.invoke(question)
print(«Question: « + question)
print(«Answer: « + answer)
# Return source documents
documents = retriever.get_relevant_documents(question)
print(«\nSource documents:»)
pprint.pprint(documents)
上一段代码将Atlas Vector Search实例化为k值为3,以仅搜索最相关的三个文档。
在前面的代码中,注意包含custom_rag_prompt = PromptTemplate.from_template(template)的行。它指的是提示模板,这些模板将在下一节中详细介绍。
LangChain提示模板和链
context作为输入变量和LLM的原始查询。
让我们设置一个链,这是LangChain的一个关键特性,它指定了三个主要组件:
-
Retriever:你将使用MongoDB Atlas Vector Search找到为语言模型提供上下文的文档。
-
提示模板:这是你之前创建的用于格式化查询和上下文的模板。
-
LLM:你将使用OpenAI聊天模型根据提供的上下文生成响应。
你将使用此链处理有关MongoDB Atlas安全建议的示例输入查询,格式化查询,检索查询结果,然后返回一个响应给用户,并附带用作上下文的文档。由于LLM的变异性,你很可能永远不会收到完全相同的两次响应,但以下是一个显示潜在输出的示例:
Question: How can I secure my MongoDB Atlas cluster?
Answer: To secure your MongoDB Atlas cluster, you can enable authentication and IP Address whitelisting, review the security section of the MongoDB Atlas documentation, and utilize encryption of data at rest with encrypted storage volumes. Additionally, you can set up global clusters with a few clicks in the MongoDB Atlas UI, ensure operational continuity by converting complex manual tasks, and consider setting up a larger number of replica nodes for increased protection against database downtime.
Source documents:
[Document (page_content='To ensure a secure system right out of the box, \nauthentication and IP Address whitelisti ng are\nautomatically enabled.\nReview the security section of the MongoD B Atlas', metadata={'_id': {'$oid': '6672
@a81b6cb1d87043c0171'), 'source': 'https://query.prod.cms.rt.microsoft.com/cms/api/am/binary/RE4HKJP', 'page': 17}),
Document(page_content='MongoD B Atlas team are also monitoring the underlying\ninfrastructure, ensuring that it is always in a healthy state. \nApplication L ogs And Database L ogs', metadata('id': ('soid': '66728a81b6cb1d87043c0 13c'), 'source': 'https://query.prod.cms.rt.microsoft.com/cms/api/am/binary/RE4HKJP', 'page': 15}),
Document(page_content='All the user needs to do in order for MongoD B Atlas to\nautomatically deploy the cluster i s to select a handful of\noptions: \n Instance size\n•Storage size (optional) \n Storage speed (optional)', metadata= {"_id": "soid: '66728a81b6cb1d87043c012a'), 'source': 'https://query.prod.cms.rt.microsoft.com/cms/api/am/binary/ RE4HKJP', 'page': 14)),
此输出不仅回答了用户的问题,还提供了来源信息,这不仅增加了用户的信任,还增强了用户根据需要跟进并获得更多细节的能力。
这个对 LangChain 框架的简要概述试图说服您这个框架的实用性和潜力,并为您提供一个其功能的预览,以便在构建您的 GenAI 应用程序时节省宝贵的时间。
关键 Python 库
除了 AI/ML 框架之外,还有许多 Python 库可以使构建您的 GenAI 应用程序的过程更加容易。无论您是否需要数据清理、格式化或转换的帮助,都可能有一打潜在的 Python 库可以解决每个问题。以下小节列出了一些最受欢迎的库,并解释了它们如何在您的 GenAI 之旅中帮助您。
对于这本书,您可以将这些库大致分为三个类别:
-
通用科学库,如 pandas、NumPy 和 scikit-learn
-
MongoDB 特定库,如 PyMongoArrow
-
深度学习框架,如 PyTorch 和 TensorFlow
本节的其余部分涵盖了这些类别中每个相关且流行的库
pandas
pandas 库是一个功能强大且灵活的开源 Python 数据操作和分析库。它提供了 DataFrame 和 Series 等数据结构,这些结构旨在直观且高效地处理结构化数据。当处理存储在电子表格或数据库中的表格数据时,pandas 是数据分析的一个优秀工具。使用 pandas,您可以执行各种操作,包括数据清理、转换和聚合。
在许多其他引人注目的开箱即用功能中,pandas 还提供了对时间序列的出色支持,并有一套广泛的工作于日期、时间和时间索引数据的工具。除了提供广泛的方法来处理数值数据外,pandas 还为基于文本的数据提供了强大的支持。
下面是一个如何使用 pandas 库的简短示例。在下面的示例中,您将从一个 Python 字典创建一个 pandas DataFrame。然后,您将打印整个 DataFrame。接下来,您将选择一个特定的列,即 Age,并打印它。然后,您将通过行标签或行的特定位置来过滤数据。
下一个示例显示了如何在 pandas 中使用布尔掩码过滤数据。在这里,您将打印出 DataFrame 格式:
pip3 install pandas==1.5.3
import pandas as pd
# Create a DataFrame
data = {
'Name': ['Alice', 'Bob', 'Charlie', 'David', 'Eve'],
'Age': [24, 27, 22, 32, 29],
'City': ['New York', 'Los Angeles', 'Chicago', 'Houston', 'Phoenix']
}
df = pd.DataFrame(data)
# Display the DataFrame
print("DataFrame:")
print(df)
您的输出应采用与 图 7.1 类似的 pandas DataFrame 格式:

图 7.1:pandas 的 DataFrame 输出
您可以通过各种方式操作这些数据,每次输出您认为合适的结果,但始终以 pandas DataFrame 的格式输出。要打印用户的年龄,您可以使用以下代码:
# Select a column
print("\nAges:")
print(df['Age'])
您将得到 图 7.2 中所示的输出:

图 7.2:年龄的 DataFrame 输出
您还可以过滤输出。在这里,您将过滤数据以仅显示年龄大于 25 的人,然后将结果作为 DataFrame 展示:
# Filter data
print("\nPeople older than 25:")
print(df[df['Age'] > 25])
此代码将过滤数据,然后将结果以 DataFrame 格式输出,如图 图 7**.3:

图 7.3:过滤后的 DataFrame 输出
你也可以以直接的方式使用 pandas 库进行计算。例如,要计算平均年龄,你会使用如下代码:
# Calculate average age
average_age = df['Age'].mean()
print("\nAverage Age:")
print(average_age)
你的输出将类似于 图 7**.4:

图 7.4:计算字段输出
如你所见,pandas 中的数据处理相当简单,输出立即可读,并且格式良好,便于进一步分析。pandas 直观的语法和强大的功能使其成为 Python 开发者的必备工具,使他们能够轻松且精确地处理大型数据集。对于构建 GenAI 应用程序的开发者来说,pandas 简化了数据预处理步骤,确保数据干净、结构化,并准备好进行模型训练。此外,它与其他 Python 库的强大集成增强了其效用,使复杂的数据分析和可视化变得简单高效。
PyMongoArrow
PyMongoArrow 是一个基于官方 MongoDB Python 驱动程序 PyMongo 构建的 Python 库,它允许你将数据从 MongoDB 数据库移动到一些最受欢迎的 Python 库中,如 pandas、NumPy、PyArrow 和 polars,反之亦然。
PyMongoArrow 简化了将数据从 MongoDB 加载到其他支持的数据格式。下面提到的示例展示了如何使用 MongoDB、PyMongoArrow 以及 pandas 和 NumPy 等库。在以下情况下,你可能会在 GenAI 应用程序中找到这很有用:
-
当你需要从 MongoDB 获取特定格式的数据(CSV、DataFrame、NumPy 数组、Parquet 文件等)进行总结和分析时
-
如果你需要合并各种类型的数据进行计算或转换,然后用于 GenAI 分析
例如,如果你有来自 Application A 的入境财务数据,来自 Application B 的入境销售数据,来自 Team 1 的 PDF 文件,以及来自 Team 2 的 .txt 文件,并且你希望你的 GenAI 应用程序能够总结来自所有这些不同地方年度数据,那么如果所有类型的数据都处于相同的格式,你可能会得到更准确的结果。这将需要一些前期编程工作,而 PyMongoArrow 简化了将 MongoDB JSON 转换为其他数据类型以及将这些其他数据类型转换为 JSON 的过程。
按照以下步骤使用 PyMongoArrow 完成此示例:
-
首先,安装并导入 PyMongoArrow 的最新版本:
pip3 install PyMongoArrow import pymongoarrow as pa -
现在,请确保你手头有 Atlas 集群连接字符串:
import getpass, os, pymongo, pprint -
接下来,您将通过
pymongoarrow.monkey模块扩展 PyMongo 驱动程序。这允许您直接将 PyMongoArrow 功能添加到 Atlas 中的 MongoDB 集合中。通过从pymongoarrow.monkey调用patch_all(),新的集合实例将包括 PyMongoArrow API,例如pymongoarrow.api.find_pandas_all()。这很有用,因为您现在可以轻松地将数据从 MongoDB 导出为各种格式,如 pandas。from pymongoarrow.monkey import patch_all patch_all() -
向您的集合添加一些测试数据:
from datetime import datetime from pymongo import MongoClient client = MongoClient(ATLAS_CONNECTION_STRING) client.db.data.insert_many([ {'_id': 1, 'amount': 21, 'last_updated': datetime(2020, 12, 10, 1, 3, 1), 'account': {'name': 'Customer1', 'account_number': 1}, 'txns': ['A']}, {'_id': 2, 'amount': 16, 'last_updated': datetime(2020, 7, 23, 6, 7, 11), 'account': {'name': 'Customer2', 'account_number': 2}, 'txns': ['A', 'B']}, {'_id': 3, 'amount': 3, 'last_updated': datetime(2021, 3, 10, 18, 43, 9), 'account': {'name': 'Customer3', 'account_number': 3}, 'txns': ['A', 'B', 'C']}, {'_id': 4, 'amount': 0, 'last_updated': datetime(2021, 2, 25, 3, 50, 31), 'account': {'name': 'Customer4', 'account_number': 4}, 'txns': ['A', 'B', 'C', 'D']}]) -
PyMongoArrow 使用一个
schema对象,并将字段名映射到类型指定符:from pymongoarrow.api import Schema schema = Schema({'_id': int, 'amount': float, 'last_updated': datetime})MongoDB 的关键特性是它能够使用嵌入文档表示嵌套数据,同时支持列表和嵌套列表。PyMongoArrow 默认完全支持这些特性,为处理嵌入文档、列表和嵌套列表提供了第一级功能。
-
让我们在数据上执行一些
find操作。以下代码演示了使用 PyMongoArrow 将查询结果转换为不同数据格式,查询名为data的 MongoDB 集合中amount字段大于0的文档。用于转换的预定义模式是可选的。如果您省略模式,PyMongoArrow 将尝试根据第一批数据自动应用模式:df = client.db.data.find_pandas_all({'amount': {'$gt': 0}}, schema=schema) arrow_table = client.db.data.find_arrow_all({'amount': {'$gt': 0}}, schema=schema) df = client.db.data.find_polars_all({'amount': {'$gt': 0}}, schema=schema) ndarrays = client.db.data.find_numpy_all({'amount': {'$gt': 0}}, schema=schema)第一行代码将查询结果转换为 pandas DataFrame。第二行代码将查询结果集转换为 arrow 表。第三行将查询结果集转换为 polars DataFrame,最后,第四行将查询结果集转换为 NumPy 数组。
您不仅限于执行 find 操作将查询结果集转换为其他支持的数据格式。PyMongoArrow 还允许您使用 MongoDB 强大的聚合管道对数据进行复杂查询,以在导出到其他数据格式之前过滤出所需的数据。
例如,以下代码在 MongoDB 数据库的数据集合上执行聚合查询,将所有文档分组并计算 amount 字段的总额:
df = client.db.data.aggregate_pandas_all([{'$group': {'_id': None, 'total_amount': { '$sum': '$amount' }}}])
此代码的结果被转换为包含总计的 pandas DataFrame。
PyTorch
既然您已经对 pandas 和 NumPy 有了一些了解,那么了解另一个流行的 Python 机器学习库 PyTorch 也是非常重要的。
由 Meta 的 AI 研究实验室开发的 PyTorch 是一个开源的深度学习框架,以其灵活性和易用性而闻名。它因其动态计算图而广受欢迎,这使得代码的直观编码和即时执行成为可能。这一特性对于需要快速实验和迭代的研发人员尤其有用。
在构建 GenAI 应用程序的情况下,PyTorch 是以下方面的强大工具:
-
模型训练和开发:PyTorch被用于开发和训练核心生成模型,例如生成预训练转换器(GPT)变体,这些变体构成了GenAI应用的基础。
-
灵活性和实时实验:PyTorch中的动态计算图允许即时修改和实时实验,这对于微调生成模型以产生高质量输出至关重要。
正在将预训练语言模型适应其特定要求或为其独特任务开发自定义模型的开发者可能会对使用此库以及下一节中讨论的一些API感兴趣。
AI/ML APIs
在开发GenAI应用时,开发者可以访问各种API,这些API可以显著增强他们项目的功能和效率。由于这些API被广泛使用,它们在数千个项目中提供了性能、稳定性和一致性,确保开发者无需重新发明轮子。以下只是这些API提供的一些功能:
-
文本生成和处理:如OpenAI、Hugging Face和Google Gemini API等API使开发者能够生成连贯且符合上下文的文本,这对于内容创作、对话系统和虚拟助手等应用至关重要。
-
翻译功能:Google Cloud Translation API、Azure AI Translator和Amazon Translate API提供了强大的翻译功能,使GenAI应用能够实现多语言和全球可访问性。
-
语音合成和识别:如Google Text-to-Speech、Amazon Polly和IBM Watson Text-to-Speech等服务将生成的文本转换为自然流畅的语音,从而增强用户交互和可访问性。
-
图像和视频处理:Clarifai和DeepAI的API允许GenAI应用创建、修改和分析视觉内容,从而实现从文本生成图像和对象识别等任务。
这些API提供了一系列功能,当结合使用时,可以显著加速GenAI应用的开发并增强其功能。接下来,你将深入了解其中的两个API,即OpenAI API和Hugging Face Transformers API。
OpenAI API
如您从第3章“大型语言模型”中回忆起的那样,OpenAI提供了一个基于广泛数据集的基础模型。它通过API提供此模型,允许您利用高级机器学习模型的力量,而无需管理底层基础设施。重新训练或托管针对组织或特定领域信息定制的自定义LLM的计算和财务成本非常高,因此大多数开发者将利用他人的LLM为他们的应用提供GenAI功能。
尽管每个API都有自己的优点和缺点,但OpenAI API目前是最广泛使用的。它为开发者提供了一个简单的接口,以便在他们的应用程序中创建智能层。它由OpenAI最先进的模型和前沿的自然语言处理(NLP)能力提供支持,使应用程序能够执行文本生成、摘要、翻译和对话等任务。该API设计得灵活且可扩展,适用于从聊天机器人到内容创作工具的广泛用例。它还拥有良好的文档记录,拥有庞大的社区,并且针对几乎每个用例都有许多教程。
OpenAI API已经成为了某种行业标准,许多生成式AI工具和技术都支持并与它无缝集成。如果你想避免不必要的努力和成本,最佳选择是与OpenAI API合作。
让我们在以下示例中开始使用OpenAI API:
-
要开始,你需要从终端或命令行安装
openai:pip3 install --upgrade openai==1.41.0 -
在环境变量文件中包含你的OpenAI API密钥:
export OPENAI_API_KEY='your-api-key-here' -
使用Python库向OpenAI API发送第一个API测试请求。为此,使用终端或IDE创建一个名为
openai-test.py的文件。然后,在文件内部,复制并粘贴以下示例之一:from openai import OpenAI client = OpenAI() completion = client.chat.completions.create( model="gpt-4o-mini", messages=[ {"role": "system", "content": "You are a poetic assistant, skilled in explaining complex programming concepts with creative flair."}, {"role": "user", "content": "Compose a poem that explains the concept of recursion in programming."} ] ) print(completion.choices[0].message) -
通过在终端或命令行中输入
python openai-test.py来运行代码。这将输出一首关于递归的创意诗。每个结果都是不同的,因为GPT会每次都使用创造力来创造新的东西。这是它在这次尝试中创造的:In code’s endless labyrinth, a tale is spun, Of functions nested deep, where paths rerun. A whisper in the dark, a loop within, Where journeys start anew as they begin. Behold the call, a serpent chasing tail, The dragon’s circle, a fractal’s holy grail. In depths unseen, the echoing refrain, A self-same mirror where the parts contain. A climb up winding stairs, each step the same, Yet every twist, a slight and altered game. In finite bounds, infinity unfurls, A loop of dreams within its spiral swirls.结果令人惊讶地好。你应该亲自尝试一下,看看会创作出什么样的新创意诗。
GPT擅长回答问题,但仅限于它能从其训练数据中回忆起来的主题。在大多数情况下,你希望GPT回答关于你的业务或产品的问题,或者回答用户经常提出的问题。在这种情况下,你希望添加搜索你自己的文档库中相关文本的能力,然后让GPT使用这些文本作为其响应的参考信息。这被称为RAG,你可以在第8章,在AI应用中实现向量搜索中了解更多信息。
Hugging Face
Hugging Face是一个突出的AI社区和机器学习平台。其生态系统是Hugging Face Hub,一个旨在促进AI社区合作和创新的平台。该Hub位于https://huggingface.co/docs/hub/en/index,截至编写时,拥有超过12万个模型、2万个数据集和5万个演示,是可用的最大机器学习资源集合之一。它具有以下特点:
-
广泛的模型存储库:Hub包括各种任务的预训练模型,如文本分类、翻译、摘要和问答,为开发者提供了广泛的选择。
-
数据集:它提供了对多种数据集的访问,这些数据集对于训练和评估机器学习模型至关重要。数据集涵盖多个领域和语言,支持开发稳健和通用的AI应用。
-
社区和协作:该平台通过允许用户共享模型、数据集和代码来支持协作。开发者可以通过上传自己的模型和数据集来为社区做出贡献,营造一个协作的环境。
-
集成和部署选项:Hugging Face Hub无缝集成到流行的机器学习框架中,如PyTorch和TensorFlow。Hub还提供部署解决方案,使开发者能够轻松地将模型部署到生产环境中。
GenAI应用开发者可以使用Hugging Face Transformers API来访问特定数据集上针对特定任务的数千个预训练机器学习模型。使用transformer模型,您可以使用预训练模型进行推理或使用PyTorch和TensorFlow库用您自己的数据进行微调。
为了说明您的GenAI应用可能实现的功能,让我们看看如何使用预训练的transformer模型进行推理以执行两个任务:基本的情感分析和文本生成。如果您想对客户反馈进行排序或根据情感进行评分并生成响应,这两个任务对您的GenAI项目都可能很有用。
情感分析
您将使用transformers库来利用共享模型,然后探索pipeline()函数,这是transformers库的核心组件。此函数无缝地将模型与必要的预处理和后处理步骤集成,允许直接输入文本并生成可理解的响应:
-
首先,确保您已安装必要的软件包。请注意,至少应安装TensorFlow或PyTorch中的一个。在这里,我们将使用TensorFlow:
pip3 install transformers tensorflow -
接下来,导入
pipeline()函数。您还将创建一个pipeline()函数的实例,并指定您想使用它的任务,即情感分析:from transformers import pipeline analyse_sentiment = pipeline(«sentiment-analysis») analyse_sentiment("The weather is very nice today.")您将收到以下输出:

图7.5:Hugging Face Transformers情感分析输出
该模型执行分析并输出标签和分数。标签表示情感类型为正面或负面,而分数表示对输出的置信度。
您还可以将多个输入文本作为数组传递给模型进行情感分类:
analyse_sentiment(["The weather is very nice today.", "I don't like it when it rains in winter."])
您将收到以下输出:

图7.6:Hugging Face中用于情感分类的多个输入文本
在这种情况下,模型输出一个对象数组。每个输出对象对应于单个文本输入。
你可能正在屏住呼吸,期待事情变得更加复杂——但它们不会。你只需用几行代码就在Hugging Face上完成了第一次情感分析。
文本生成
除了情感分析,你还可以使用Transformers库执行许多其他NLP任务,例如文本生成。在这里,你将提供一个提示,模型将通过生成剩余的文本来自动完成它:
generator = pipeline("text-generation")
generator("I love AI, it has")
对于前面的代码,你将得到以下输出:

图7.7:使用Hugging Face Transformers进行文本生成
由于你没有为管道实例提供模型名称,它决定使用默认的,在这种情况下是GPT-2。你可能或可能不会得到这里的结果,因为文本生成涉及一些随机性。然而,你仍然可以看到这项任务是多么简单。
接下来,指定在文本生成时pipeline函数中要使用的模型名称。以下代码提供了更多自定义细节,例如要生成的不同序列的数量和输出文本的最大长度:
generator = pipeline("text-generation", model="distilgpt2")
generator(
"I love AI, it has",
max_length=25,
num_return_sequences=2,
)
在提供这些附加参数后,你现在将收到以下输出:

图7.8:带有参数的Hugging Face文本生成输出
前面的代码输出了两对不同对的文本,每对都少于25个单词。
如你所料,Hugging Face提供了许多更多工具和功能,开发者可以使用它们来构建他们的GenAI应用。凭借其全面的库支持和活跃的社区,Hugging Face继续成为推进NLP和ML项目的关键资源。此外,它与各种AI/ML框架的无缝集成确保了开发者可以以最小的努力和最大的生产力高效地部署和扩展他们的GenAI模型。
摘要
在本章中,你了解了Python空间中AI/ML框架的演变,因为LLM驱动的应用已经变得突出。你还学习了为什么Python仍然是构建现代LLM驱动应用的首选。你回顾了最流行的Python框架、库和API,这些可以帮助你在GenAI应用开发的各个阶段。
GenAI领域正在迅速发展,到这本书出版的时候,可能会有更多的库可用,更多的API在应用中,并且框架的功能也将得到扩展。你欠自己一份责任,去深入了解哪个框架最适合你的业务需求,同时也要确保选择一个得到适当支持的框架。正如任何快速发展的技术一样,今天存在的某些工具和技术明天可能就会消失。因此,本章试图只包括那些拥有社区、支持能力和功能集,以确保其长期存在的工具。
毫无疑问,仍有大量的创新工作要做,以及新的工具需要被创造,即使在短期内——本章讨论的工具也只是冰山一角。因此,深吸一口气,开始你自己的探索之旅。你不可避免地会意识到你需要一些工具,并且你有太多的选择来满足这些需求。
在下一章中,你将探索如何利用 MongoDB Atlas 的向量搜索功能来创建智能应用。你将了解 RAG 架构系统,并通过对 MongoDB Atlas 的各种复杂 RAG 架构模式进行深入了解。
第八章:在AI应用中实现向量搜索
向量搜索正在改变人们与AI应用中数据交互的方式。MongoDB Atlas向量搜索允许开发者实现理解发现和检索细微差别的复杂搜索功能。它通过将文本、视频、图像或音频文件转换为数值向量表示,然后可以高效地存储和搜索。MongoDB Atlas可以在您的运营数据旁边执行相似度搜索,使其成为增强从电子商务到内容发现等应用程序用户体验的必备工具。使用MongoDB Atlas,设置向量搜索流程简化,使开发者能够专注于创建动态、响应和智能的应用程序。
在本章中,您将学习如何使用MongoDB Atlas的向量搜索功能构建智能应用程序。您将学习如何构建检索增强生成(RAG)架构系统,并深入了解使用MongoDB Atlas理解和开发各种复杂RAG架构模式,揭示其联合价值和潜力的协同效应。通过实际用例和实践演示,您将了解这对动态组合如何无缝地改变各行业的业务,推动效率、准确性和运营卓越。
本章涵盖了以下主题:
-
利用MongoDB Atlas的向量搜索和全文搜索,这将有助于您构建一个强大的RAG检索器
-
理解在RAG系统开发中涉及的各个组件
-
了解简单RAG和高级RAG系统开发的过程和步骤。
技术要求
本章假设您至少具备Python编码的入门级专业知识。为了跟随演示,您需要通过完成以下步骤来设置您的开发环境:
-
在您选择的操作系统上安装
python@3.9或python@3.11。 -
设置并激活Python虚拟环境:
$ python3 -m venv venv $ source venv/bin/activate -
您将使用以下包来开发本章中描述的演示:
-
pandas: 帮助进行数据预处理和处理 -
numpy: 处理数值数据 -
openai: 用于嵌入模型和调用LLM -
pymongo: 用于MongoDB Atlas向量存储和全文搜索 -
s3fs: 允许直接从S3存储桶加载数据 -
langchain_mongodb: 使您能够使用LangChain包装器在MongoDB Atlas中实现向量搜索 -
langchain: 用于构建RAG应用 -
langchain-openai: 使您能够与OpenAI聊天模型交互 -
boto3: 使您能够与AWS s3存储桶交互 -
python-dotenv:使您能够从.env文件中加载环境变量
要在您的Python虚拟环境中安装所提到的包,请运行以下命令:
pip3 install langchain==0.2.14 langchain-community==0.2.12 langchain-core==0.2.33 langchain-mongodb==0.1.8 langchain-openai==0.1.22 langchain-text-splitters==0.2.2 numpy==1.26.4 openai==1.41.1 s3fs==2024.6.1 pymongo==4.8.0 pandas==2.2.2 boto3==1.35.2 python-dotenv==1.0.1您还需要了解如何设置和运行JupyterLab或Jupyter Notebook。
-
使用MongoDB Atlas Vector Search进行信息检索
信息检索是RAG系统的关键组成部分。它通过从广泛的知识库中获取信息,提高了生成文本的准确性和相关性。这个过程使得RAG系统能够生成既精确又基于事实内容的响应,使其成为各种自然语言处理(NLP)任务的有力工具。通过有效地结合检索与生成,RAG解决了与偏见和错误信息相关的挑战,为AI相关应用和任务的进步做出了贡献。
在信息检索的背景下,区分相关性和相似性是至关重要的。虽然相似性关注于词匹配,但相关性是关于思想的相互联系。虽然向量数据库查询可以帮助识别语义相关的内容,但需要更高级的工具来准确检索相关信息。
在第5章,向量数据库中,你学习了MongoDB Atlas Vector Search以及它是如何通过允许创建和索引向量嵌入(可以使用机器学习模型,如嵌入模型生成)来增强相关信息的检索。这促进了语义搜索功能,能够识别出在上下文中相似的内容,而不仅仅是基于关键词。全文搜索通过提供强大的文本搜索能力来补充这一点,可以处理拼写错误、同义词和其他文本变化,确保搜索返回最相关的结果。这些工具共同提供了一种全面的搜索解决方案,可以根据术语的相似性和内容的关联性来识别和检索信息。
Python中的向量搜索教程
通过一个示例,让我们看看如何将小型数据集加载到MongoDB中,以执行向量搜索和全文搜索以进行信息检索。为此演示,你将从一个S3存储桶中加载一个示例电影数据集:
-
编写一个简单的Python函数,接受搜索词或短语,并将其通过嵌入API再次传递以获取查询向量。
-
将生成的查询向量嵌入取出来,并使用MongoDB聚合管道中的
$vectorsearch(向量搜索)运算符执行向量搜索查询。 -
使用元信息对文档进行预过滤,以缩小数据集上的搜索范围,从而加快向量搜索结果的处理速度,同时保持准确性。
-
此外,如果您想展示对语义搜索行为的更高程度的控制,可以对语义上相似的检索到的文档进行后过滤(基于相关性分数)。
-
初始化OpenAI API密钥和MongoDB连接字符串:
import os import getpass # set openai api key try: openai_api_key = os.environ["OPENAI_API_KEY"] except KeyError: openai_api_key = getpass.getpass("Please enter your OPENAI API KEY (hit enter): ") # Set MongoDB Atlas connection string try: MONGO_CONN_STR = os.environ["MONGODB_CONNECTION_STR"] except KeyError: MONGO_CONN = getpass.getpass("Please enter your MongoDB Atlas Connection String (hit enter): ") -
现在,从S3存储桶中加载数据集。在Jupyter Notebook中运行以下代码行以直接从AWS S3存储桶读取数据到
pandasDataFrame:import pandas as pd import s3fs df = pd.read_json("https://ashwin-partner-bucket.s3.eu-west-1.amazonaws.com/movies_sample_dataset.jsonl", orient="records", lines=True) df.to_json("./movies_sample_dataset.jsonl", orient="records", lines=True) df[:3]执行前面的代码片段后,你应该在你的Jupyter Notebook单元格中看到以下结果。

图8.1:样本电影数据视图
-
初始化并运行一个嵌入作业以嵌入
sample_movies数据集。在下面的代码示例中,你创建了一个final字段,这是一个从数据集中已存在的text和overview字段派生出来的字段。 -
接下来,运行此
final字段与OpenAI的嵌入API,如图所示:import numpy as np from tqdm import tqdm import openai df['final'] = df['text'] + " Overview: " + df['overview'] df['final'][:5] step = int(np.ceil(df['final'].shape[0]/100)) embeddings_t = [] lines = [] # Note that we must split the dataset into smaller batches to not exceed the rate limits imposed by OpenAI API's. for x, y in list(map(lambda x: (x, x+step), list(range(0, df.shape[0], step)))): lines += [df.final.values[x:y].tolist()] for i in tqdm(lines): embeddings_t += openai.embeddings.create( model='text-embedding-ada-002', input=i).data out = [] for ele in embeddings_t: out += [ele.embedding] df['embedding'] = out df[:5]你应该看到
sample_movies数据集在embedding字段中丰富了OpenAI嵌入,如图图8**.2所示。

图8.2:使用OpenAI嵌入的样本电影数据集视图
-
接下来,初始化MongoDB Atlas并将数据插入MongoDB集合中。
-
现在你已经为
sample_movies数据集创建了向量嵌入,你可以初始化MongoDB客户端并将文档插入你选择的集合中,通过运行以下代码行:from pymongo import MongoClient import osmongo_client = MongoClient(os.environ["MONGODB_CONNECTION_STR"]) # Upload documents along with vector embeddings to MongoDB Atlas Collection output_collection = mongo_client["sample_movies"]["embed_movies"] if output_collection.count_documents({})>0: output_collection.delete_many({}) _ = output_collection.insert_many(df.to_dict("records"))你已经将测试数据导入以构建向量搜索功能。现在,让我们按照以下步骤构建向量搜索索引。
-
让我们首先创建向量索引定义。你可以在MongoDB Atlas向量搜索UI中通过遵循第5章,向量数据库中解释的步骤创建一个向量搜索索引。本演示教程所需的向量索引在此提供:
{ "fields": [ { "type": "vector", "numDimensions": 1536, "path": "embedding", "similarity": "cosine" }, { "type": "filter", "path": "year" }, ] }一旦在MongoDB Atlas UI中的向量搜索索引JSON编辑器下添加了向量索引定义,创建向量搜索索引的过程就会被触发,并在向量索引定义中指定的
path字段处创建向量搜索索引。现在,你准备好在MongoDB Atlas中执行向量搜索查询,在存储所有数据的sample_movies.embed_movies集合上创建向量索引。让我们为向量搜索或检索API配备你的RAG框架。
-
你可以使用
$vectorSearch查询MongoDB向量索引。MongoDB Atlas带来了使用向量搜索的同时使用搜索过滤器的灵活性。此外,你还可以使用聚合管道应用范围、字符串和数字过滤器。这允许最终用户控制来自搜索引擎的语义搜索响应的行为。下面的代码示例演示了如何执行向量搜索,并在
year字段上进行预过滤以获取1990年后发布的电影。为了更好地控制返回结果的关联性,你可以使用MongoDB查询API对响应进行后过滤。下面的代码演示了如何执行这些步骤:
-
将原始文本查询表示为向量嵌入。目前 OpenAI 提供了多种嵌入模型,例如
text-embedding-3-small、text-embedding-3-large(具有可变维度)和text-embedding-ada-002模型。 -
构建并向 MongoDB Atlas 执行向量搜索查询。
-
在对
year字段执行向量搜索之前进行预过滤。 -
使用
score字段进行后过滤,以更好地控制返回结果的关联性和准确性。
运行以下代码以初始化一个可以帮助您实现向量搜索、预过滤和后过滤的函数:
def query_vector_search(q, prefilter = {}, postfilter = {},path="embedding",topK=2): ele = openai.embeddings.create(model='text-embedding-ada-002', input=q).data query_embedding = ele[0].embedding vs_query = { "index": "default", "path": path, "queryVector": query_embedding, "numCandidates": 10, "limit": topK, } if len(prefilter)>0: vs_query["filter"] = prefilter new_search_query = {"$vectorSearch": vs_query} project = {"$project": {"score": {"$meta": "vectorSearchScore"},"_id": 0,"title": 1, "release_date": 1, "overview": 1,"year": 1}} if len(postfilter.keys())>0: postFilter = {"$match":postfilter} res = list(output_collection.aggregate([new_search_query, project, postFilter])) else: res = list(output_collection.aggregate([new_search_query, project])) return res query_vector_search("I like Christmas movies, any recommendations for movies release after 1990?", prefilter={"year": {"$gt": 1990}}, topK=5)您应该得到以下结果:
-

图 8.3:使用预过滤运行向量搜索查询的示例结果
这是一个示例查询,使用 year 作为预过滤和一个基于 score 的后过滤来仅保留相关结果:
query_vector_search("I like Christmas movies, any recommendations for movies release after 1990?", prefilter={"year":{"$gt": 1990}}, postfilter= {"score": {"$gt":0.905}},topK=5)
您应该得到以下结果:

图 8.4:使用预过滤和后过滤运行向量搜索查询的示例结果
使用此 Python 方法,您能够对 score 字段和 year 字段进行过滤以生成结果,以及向量相似度的结果。通过使用启发式方法,您能够控制结果的准确性,仅保留最相关的文档,并且还能够应用范围过滤查询(在 year 字段上)。
LangChain 向量搜索教程
利用 LangChain 和 MongoDB Atlas 向量搜索构建语义相似度检索器提供了几个优点。以下示例演示了如何使用 LangChain 包装类执行向量相似度搜索:
from langchain_mongodb.vectorstores import MongoDBAtlasVectorSearch
from langchain_openai import OpenAIEmbeddings
import json
embedding_model = OpenAIEmbeddings(model="text-embedding-ada-002")
vector_search = MongoDBAtlasVectorSearch(output_collection, embedding_model, text_key='final')
fquery = {"year": {"$gt": 1990}}
search_kwargs = {
"k": 5,
'filter': fquery,
}
retriever = vector_search.as_retriever(search_kwargs=search_kwargs)
docs = retriever.invoke("I like Christmas movies, any recommendations for movies release after 1990?")
for doc in docs:
foo = {}
foo['title'] = doc.metadata['title']
foo['year'] = doc.metadata['year']
foo['final'] = doc.metadata['text']
print(json.dumps(foo,indent=1))
这是结果:

图 8.5:使用 MongoDB 的 LangChain 模块执行的示例向量搜索查询结果
这展示了更复杂但简单的方法,特别有利于开发 RAG 应用程序的程序员。LangChain 框架提供了一套 API 和包装类,可用于与各种无服务器 LLM 提供商集成,例如 OpenAI,并与 MongoDB Atlas 向量搜索通信,以用很少的代码构建 RAG 框架。它也易于维护和扩展。
在本节中,您已经能够使用 MongoDB Atlas 构建并执行向量相似度搜索。您开发了可重用的包装类和函数,这些将在开发更复杂的应用程序,如聊天机器人时非常有用。
现在,让我们深入探讨理解 RAG 架构是什么以及如何使用您迄今为止创建的资源来开发它。
构建 RAG 架构系统
在现代商业的动态景观中,对效率和准确性的不懈追求促使组织采用尖端技术。在这些技术中,自动化是基石,尤其是在处理和自动化工作流程方面。然而,当面对大量复杂任务的数据时,传统方法会受到影响,而以人为中心的过程往往由于易出错的手动干预而不足。
本节探讨了自动化转型的变革性景观,讨论了RAG在革命性改变业务运营中的关键作用。MongoDB以其在数据管理和灵活模式方面的优势而闻名,通过其向量搜索和全文搜索功能,与RAG形成了引人注目的协同效应。深入探讨RAG的架构细节,本节剖析了其构成的基本模块,提供了构建利用LLMs和MongoDB向量搜索全部潜力的自动化文档处理工作流的实用见解。

图8.6:RAG架构的构建块
让我们详细探讨RAG架构的关键组件:
-
文档加载:最初,文档从数据存储中加载。这包括文本提取、解析、格式化和清理,以准备数据以便文档分割。
-
文档分割:下一步是将文档分解成更小、更易于管理的段或片段。分割策略可能有所不同,从固定大小的块分割到考虑内容结构的感知内容分割。
-
文本嵌入:这些文档片段随后使用OpenAIEmbeddings、Sentence e-BERT和Instructor Embeddings等技术转换为向量表示(嵌入)。这一步对于理解片段的语义内容至关重要。
-
向量存储:生成的向量,每个都与独特的文档片段相关联,存储在向量存储中,与文档片段和其他从MongoDB Atlas集合中提取的元数据一起。可以通过MongoDB Atlas UI构建Atlas向量搜索索引和Apache Lucene搜索,以便快速检索。
-
查询处理:当用户提交查询时,它也使用与第3步中提到的相同嵌入技术转换为向量表示。
-
文档检索:检索组件定位并获取与查询语义相似的文档片段。此检索过程采用向量相似性搜索技术和MongoDB Atlas,使用分层可导航小世界(HNSW)算法进行快速最近邻搜索,以检索相关文档,同时不牺牲检索搜索结果的准确性。
-
文档块后过滤:使用统一查询API从MongoDB集合中检索相关文档块,并且可以轻松后过滤以将输出文档块转换为所需的格式。
-
LLM提示创建:将检索到的文档块和查询组合以创建LLM的上下文和提示。
-
答案生成:最后,LLM根据提示生成响应,完成RAG过程。
在RAG系统的背景下,有两种主要类型:简单(或天真)RAG和高级****RAG。在实际场景中,这种分类有助于解决不同类型的人物和应用程序处理的问题,并且通常在同一个工作流程和同一个人物中会遇到简单和复杂的RAG查询。作为开发者,在决定RAG架构中涉及的构建块之前,重要的是要推理出应用程序预期要提供的功能。
在构建您的RAG架构系统时,考虑以下要点以帮助编程和规划:
-
工作流程的特定性:定义您打算使用RAG自动化的特定工作流程;它可能与问答(QA)、数据增强、摘要、推理或断言相关。也许您的客户经常询问一组特定的三到四种类型的查询。
-
用户体验:与您的目标用户群体合作,了解他们可能提出的问题类型,以确定用户群体旅程,这可能是一个简单的单状态响应或一个多状态聊天流程。
-
数据源:首先,确定数据源的性质——它是不结构化还是结构化的。接下来,映射这些数据源的位置。一旦完成,根据数据是否用于操作或分析目的对数据进行分类。最后,观察数据模式以确定答案是否在某个位置即可获得,或者您需要从多个来源收集信息。
这些要点将帮助您确定是否需要采用简单RAG系统或高级RAG系统,并帮助您确定构建RAG架构时需要考虑的基本构建块。
现在,让我们通过一些代码示例深入探讨这个架构的构建块,以更好地解释细微差别。然而,在开发RAG应用程序之前,让我们看看如何处理源文档以最大限度地提高RAG应用程序的评分响应准确性的基本原理。以下策略在将文档存储到MongoDB Atlas集合之前处理文档时将非常有用。
块化或文档拆分策略
块划分或文档分割是处理RAG系统中的大量文本的关键步骤。在处理大型文档时,语言模型(如gpt-3.5-turbo)施加的标记限制需要将它们划分为可管理的块。然而,简单的固定块大小方法可能导致块之间的句子碎片化,影响后续任务,如问答。
为了解决这个问题,在划分文档时考虑语义。大多数分割算法使用块大小和重叠原则。块大小(以字符、单词或标记衡量)决定了段长度,而重叠通过在相邻块之间共享上下文来确保连续性。这种方法保留了语义上下文并提高了RAG系统性能。
现在,让我们深入了解文档分割技术的复杂性,特别是关注内容感知的块划分。虽然固定大小块划分与重叠简单且计算效率高,但更复杂的方法提高了文本分割的质量。以下是一些文档分割技术:
-
递归划分:这项技术包括以下方法:
-
层次方法:递归划分通过迭代地将输入文本分解成更小的块。它采用层次结构,在每一级使用不同的分隔符或标准。
-
可定制结构:通过调整标准,您可以实现所需的块大小或结构。递归划分很好地适应了不同长度的文档。
-
-
句子分割:句子分割涉及各种策略,如以下所列:
-
朴素分割:这种方法依赖于基本的标点符号(如句号和新行)来将文本划分为句子。虽然简单,但它可能无法很好地处理复杂的句子结构。
-
spaCy:另一个强大的NLP库spaCy提供了准确的句子分割。它使用统计模型和语言规则。
-
自然语言工具包(NLTK):NLTK是一个强大的Python NLP库,提供了高效的句子分词。它考虑上下文和标点符号模式。
-
高级工具:一些工具使用较小的模型来预测句子边界,确保精确的划分。
-
-
专用技术:专用技术包括以下内容:
-
结构化内容:对于具有特定格式(例如Markdown、LaTeX)的文档,专用技术就派上用场。
-
智能划分:这些方法分析内容的结构和层次。通过理解标题、列表和其他格式提示,它们创建语义上连贯的块。
-
总结来说,虽然固定大小的分块可以作为基线,但内容感知技术考虑语义、上下文和格式复杂性。选择正确的方法取决于你数据的独特特性和你RAG系统的需求。在选择用于存储和检索这些块的数据检索器时,你可能想要考虑诸如文档层次结构和知识图谱等解决方案。MongoDB Atlas具有灵活的模式和简单的统一查询API,可以从中查询数据。
现在我们使用递归文档拆分策略来构建一个简单的RAG应用。
简单RAG
一个简单的RAG架构实现了一种天真方法,其中模型根据与用户查询的相似性从知识库中检索预定的文档数量。然后,这些检索到的文档与查询结合,并输入到语言模型中进行生成,如图8.7所示。

图8.7:天真RAG
要构建一个简单的RAG应用,你将使用本章“使用MongoDB向量搜索进行信息检索”部分中加载到MongoDB Atlas集合的数据集。通过这个应用,你将能够对可用的电影进行查询并创建一个推荐系统。
LLM
此示例将使用OpenAI API和gpt-3.5-turbo,但OpenAI还提供了其他LLM模型的变体,例如gpt-4o和gpt-4o-mini。相同的提示技术也可以用于其他LLM,如claude-v2或mistral8x-7B,以实现类似的结果。
以下是通过LangChain调用OpenAI LLM的示例代码:
from openai import OpenAI
client = OpenAI()
def invoke_llm(prompt, model_name='gpt-3.5-turbo-0125'):
"""
Queries with input prompt to OpenAI API using the chat completion api gets the model's response.
"""
response = client.chat.completions.create(
model=model_name,
messages=[
{
«role»: «user»,
«content»: prompt
}
],
temperature=0.2,
max_tokens=256,
top_p=1,
frequency_penalty=0,
presence_penalty=0
)
chatbot_response = response.choices[0].message.content.strip()
return chatbot_response
invoke_llm("This is a test")
这里是结果:
'Great! What do you need help with?'
现在你有了调用MongoDB Atlas向量搜索进行检索的API以及调用LLM的API,你可以结合这两个工具来创建一个RAG系统。
提示
对LLM的提示是用户提供的指令或输入,它指导模型的响应。它可以是一个问题、一个声明或一个命令,旨在驱动LLM以特定的输出进行响应。提示的有效性可以极大地影响基于RAG的系统生成结果的质量,使得提示工程与这些模型交互的一个关键方面。好的提示是清晰、具体和结构化的,以便将用户的意图传达给LLM,使其能够生成最准确和最有帮助的响应。
以下是一个在私有知识库上执行QA的提示示例:
def get_prompt(question, context):
prompt = f"""Question: {question}
System: Let's think step by step.
Context: {context}
"""
return prompt
def get_recommendation_prompt(query, context):
prompt = f"""
From the given movie listing data, choose a few great movie recommendations.
User query: {query}
Context: {context}
Movie Recommendations:
1\. Movie_name: Movie_overview
"""
return prompt
为了展示RAG相对于基础LLM的优势,让我们首先在没有向量搜索上下文的情况下询问LLM一个问题,然后包含它。这将展示你如何通过利用未在私有知识库上训练的基础LLM,如gpt-3.5-turbo,来提高结果的准确性并减少幻觉。
这里是未使用向量搜索的查询响应:
print(invoke_llm("In which movie does a footballer go completely blind?"))
这是结果:
The Game of Their Lives" (2005), where the character Davey Herold, a footballer, goes completely blind after being hit in the head during a game
虽然LLM的响应显示它在事实准确性方面存在困难,但与人类监督一起在企业应用中使用它仍然有希望。这些系统可以协同有效地为商业应用提供动力。为了帮助克服这个问题,你需要通过向量搜索结果向提示中添加上下文。
让我们看看如何使用invoke_llm函数和query_vector_search方法,将相关上下文与用户查询一起提供,以生成具有事实正确答案的响应:
idea = "In which movie does a footballer go completely blind?"
search_response = query_vector_search(idea, prefilter={"year":{"$gt": 1990}}, postfilter={"score": {"$gt":0.8}},topK=10)
premise = "\n".join(list(map(lambda x:x['final'], search_response)))
print(invoke_llm(get_prompt(idea, premise)))
这里是结果:
The movie in which a footballer goes completely blind is "23 Blast."
同样,你可以使用get_recommendation_prompt方法,通过简单的RAG框架生成一些电影推荐:
question = "I like Christmas movies, any recommendations for movies release after 1990?"
search_response = query_vector_search(question,topK=10)
context = "\n".join(list(map(lambda x:x['final'], search_response)))
print(invoke_llm(get_recommendation_prompt("I like Christmas movies, any recommendations for movies release after 1990?", context)))
这里是结果:

图8.8:简单RAG应用的示例输出
你刚刚构建的简单RAG系统可以处理需要直接答案的简单查询。一些例子包括客户服务聊天机器人回答诸如“班加罗尔的客户服务中心在哪里?”这样的基本问题,或者帮助你找到在Koramangala提供你最喜欢的美食的所有餐厅。聊天机器人在检索步骤中可以检索到相关的信息片段,并在LLM的帮助下生成对这个问题的答案。
高级RAG
高级RAG框架集成了更复杂的检索技术,更好地整合检索到的信息,并且通常能够迭代地改进检索和生成过程。在本节中,你将学习如何构建一个智能推荐引擎,该引擎可以在时尚数据上识别用户的兴趣,并在用户的话语中有购买产品的意图时,仅生成相关的时尚产品或配件推荐。在本节中,你将构建一个利用LangChain、MongoDB Atlas Vector Search和OpenAI的力量的智能对话聊天机器人。
当前示例中的高级RAG系统将展示以下功能:
-
使用LLM根据用户的聊天语句生成多个可搜索的时尚查询
-
将用户的聊天语句分类为是否有购买意图
-
开发一个融合阶段,它还将从多个搜索查询中检索向量相似度搜索结果,并将它们融合为一个单一的推荐集,该推荐集通过LLM的帮助进行重新排序。
用户查询RAG系统时的步骤流程在图8**.9中展示:

图8.9:查询处理和推荐的示例高级RAG流程图
让我们逐步分析代码,以加载示例数据集并构建具有本节开头列出的所有功能的先进RAG系统。
加载数据集
对于此示例,您将利用来自一家知名电子商务公司的时尚数据。以下代码展示了如何将数据集从S3存储桶加载到pandas DataFrame中,然后将这些文档插入到MongoDB Atlas集合search.catalog_final_myn中:
import pandas as pd
import s3fs
import os
import boto3
s3_uri= "https://ashwin-partner-bucket.s3.eu-west-1.amazonaws.com/fashion_dataset.jsonl"
df = pd.read_json(s3_uri, orient="records", lines=True)
print(df[:3])
from pymongo import MongoClient
mongo_client = MongoClient(os.environ["MONGODB_CONNECTION_STR"])
# Upload documents along with vector embeddings to MongoDB Atlas Collection
col = mongo_client["search"]["catalog_final_myn"]
col.insert_many(df.to_dict(orient="records"))
这里是结果:

图8.10:使用OpenAI嵌入的时尚数据集的示例视图
创建向量搜索索引
如您在图8**.10中看到的那样,向量嵌入已经作为数据集的一部分提供。因此,下一步是创建向量搜索索引。您可以通过遵循第5章,向量数据库中详细说明的步骤,使用以下索引映射来创建向量搜索索引:
{
"fields": [
{
"type": "vector",
"numDimensions": 1536,
"path": "openAIVec",
"similarity": "cosine"
}
]
}
使用高级RAG的时尚推荐
您已成功将新的时尚数据集加载到MongoDB Atlas集合中,并创建了包含所有构建块的向量搜索索引。现在,您可以使用以下代码设置高级RAG系统并构建具有之前提到的功能的推荐系统:
from langchain_core.output_parsers import JsonOutputParser # type: ignore
from langchain_core.prompts import PromptTemplate # type: ignore
from langchain_core.pydantic_v1 import BaseModel, Field # type: ignore
from langchain_openai import ChatOpenAI # type: ignore
from langchain_community.embeddings import OpenAIEmbeddings # type: ignore
from langchain_mongodb.vectorstores import MongoDBAtlasVectorSearch # type: ignore
from pymongo import MongoClient # type: ignore
from typing import List
from itertools import chain
import certifi # type: ignore
import os
from dotenv import load_dotenv # type: ignore
load_dotenv()
from functools import lru_cache
@lru_cache
def get_openai_emb_transformers():
"""
Returns an instance of OpenAIEmbeddings for OpenAI transformer models.
This function creates and returns an instance of the OpenAIEmbeddings class,
which provides access to OpenAI transformer models for natural language processing.
The instance is cached using the lru_cache decorator for efficient reuse.
Returns:
embeddings (OpenAIEmbeddings): An instance of the OpenAIEmbeddings class.
"""
embeddings = OpenAIEmbeddings()
return embeddings
@lru_cache
def get_vector_store():
"""
Retrieves the vector store for MongoDB Atlas.
Returns:
MongoDBAtlasVectorSearch: The vector store object.
"""
vs = MongoDBAtlasVectorSearch(collection=col, embedding=get_openai_emb_transformers(), index_name="vector_index_openAi_cosine", embedding_key="openAIVec", text_key="title")
return vs
@lru_cache(10)
def get_conversation_chain_conv():
"""
Retrieves a conversation chain model for chat conversations.
Returns:
ChatOpenAI: The conversation chain model for chat conversations.
"""
llm = ChatOpenAI(model="gpt-3.5-turbo", temperature=0.2, max_tokens=2048)
# chain = ConversationChain(llm=llm, memory=ConversationBufferWindowMemory(k=5))
return llm
# Define your desired data structure.
class ProductRecoStatus(BaseModel):
"""
Represents the status of product recommendations.
Attributes:
relevancy_status (bool): Product recommendation status conditioned on the context of the input query.
True if the query is related to purchasing fashion clothing and/or accessories.
False otherwise.
recommendations (List[str]): List of recommended product titles based on the input query context and
if the relevancy_status is True.
"""
relevancy_status: bool = Field(description="Product recommendation status is conditioned on the fact if the context of input query is to purchase a fashion clothing and or fashion accessories.")
recommendations: List[str] = Field(description="list of recommended product titles based on the input query context and if recommendation_status is true.")
class Product(BaseModel):
"""
Represents a product.
Attributes:
title (str): Title of the product.
baseColour (List[str]): List of base colours of the product.
gender (List[str]): List of genders the product is targeted for.
articleType (str): Type of the article.
mfg_brand_name (str): Manufacturer or brand name of the product.
"""
title: str = Field(description="Title of the product.")
baseColour: List[str] = Field(description="List of base colours of the product.")
gender: List[str] = Field(description="List of genders the product is targeted for.")
articleType: str = Field(description="Type of the article.") mfg_brand_name: str = Field(description="Manufacturer or brand name of the product.")
class Recommendations(BaseModel):
"""
Represents a set of recommendations for products and a message to the user.
Attributes:
products (List[Product]): List of recommended products.
message (str): Message to the user and context of the chat history summary.
"""
products: List[Product] = Field(description="List of recommended products.")
message: str = Field(description="Message to the user and context of the chat history summary.")
reco_status_parser = JsonOutputParser(pydantic_object=ProductRecoStatus)
reco_status_prompt = PromptTemplate( template="You are AI assistant tasked at identifying if there is a product purchase intent in the query and providing suitable fashion recommendations.\n{format_instructions}\n{query}\n\
#Chat History Summary: {chat_history}\n\nBased on the context of the query, please provide the relevancy status and list of recommended products.",
input_variables=["query", "chat_history"],
partial_variables={"format_instructions": reco_status_parser.get_format_instructions()},
)
reco_parser = JsonOutputParser(pydantic_object=Recommendations)
reco_prompt = PromptTemplate(
input_variables=["question", "recommendations", "chat_history"],
partial_variables={"format_instructions": reco_parser.get_format_instructions()},
template="\n User query:{question} \n Chat Summary: {chat_history} \n Rank and suggest me suitable products for creating grouped product recommendations given all product recommendations below feature atleast one product for each articleType \n {recommendations} \n show output in {format_instructions} for top 10 products"
)
def get_product_reco_status(query: str, chat_history: List[str] = []):
"""
Retrieves the recommendation status for a product based on the given query and chat history.
Args:
query (str): The query to be used for retrieving the recommendation status.
chat_history (List[str]): The chat history containing previous conversations.
Returns:
The response containing the recommendation status.
"""
llm = get_conversation_chain_conv()
chain = reco_status_prompt | llm | reco_status_parser
resp = chain.invoke({"query": query, "chat_history": chat_history})
return resp
def get_sorted_results(product_recommendations):
all_titles = [rec['title'] for rec in product_recommendations['products']]
results = list(col.find({"title": {"$in":all_titles}}, {"_id": 0 , "id":1, "title": 1, "price": 1, "baseColour": 1, "articleType": 1, "gender": 1, "link" : 1, "mfg_brand_name": 1}))
sorted_results = []
for title in all_titles:
for result in results:
if result['title'] == title:
sorted_results.append(result)
break
return sorted_results
def get_product_recommendations(query: str, reco_queries: List[str], chat_history: List[str]=[]):
"""
Retrieves product recommendations based on the given query and chat history.
Args:
query (str): The query string for the recommendation.
chat_history (List[str]): The list of previous chat messages.
filter_query (dict): The filter query to apply during the recommendation retrieval.
reco_queries (List[str]): The list of recommendation queries.
Returns:
dict: The response containing the recommendations.
"""
vectorstore = get_vector_store()
retr = vectorstore.as_retriever(search_kwargs={"k": 10})
all_recommendations = list(chain(*retr.batch(reco_queries)))
llm = get_conversation_chain_conv()
llm_chain = reco_prompt | llm | reco_parser
resp = llm_chain.invoke({"question": query, "chat_history": chat_history, "recommendations": [v.page_content for v in all_recommendations]})
resp = get_sorted_results(resp)
return resp
上述代码执行以下任务:
-
从各种库中导入必要的模块和函数。这包括用于解析JSON输出的
JsonOutputParser,用于创建提示的PromptTemplate,用于定义数据模型的BaseModel和Field,以及用于与MongoDB Atlas向量存储交互的MongoDBAtlasVectorSearch。它还导入了MongoClient以连接到MongoDB,load_dotenv以加载环境变量,以及lru_cache以缓存函数结果。 -
它定义了三个函数,每个函数都使用
lru_cache装饰以缓存其结果以提高效率。get_openai_emb_transformers返回一个OpenAIEmbeddings实例,该实例提供对OpenAI NLP转换器模型的访问。get_vector_store检索MongoDB Atlas的向量存储。get_conversation_chain_conv检索用于聊天对话的对话链模型。 -
使用Pydantic的
BaseModel和Field定义了三个类。这些类代表产品推荐的状态(ProductRecoStatus),一个产品(Product),以及一组产品推荐和用户消息(Recommendations)。 -
创建用于解析JSON输出和创建提示的
JsonOutputParser和PromptTemplate实例。这些实例用于在下一节中创建对话链。 -
它定义了两个函数,用于检索产品的推荐状态和基于给定的查询和聊天历史检索产品推荐。
get_product_reco_status使用会话链根据给定的查询和聊天历史确定产品的推荐状态。get_product_recommendations根据给定的查询和聊天历史、过滤查询和推荐查询列表检索产品推荐。它使用向量存储检索器获取每个推荐查询的相关文档,然后使用会话链生成最终的推荐。
让我们现在使用这些方法来创建一个产品推荐示例。输入以下代码,然后检查其输出:
query = "Can you suggest me some casual dresses for date occasion with my boyfriend"
status = get_product_reco_status(query)
print(status)
print(get_product_recommendations(query, reco_queries=status["recommendations"], chat_history=[])
这是状态输出:
{'relevancy_status': True,
'recommendations': ['Floral Print Wrap Dress',
'Off-Shoulder Ruffle Dress',
'Lace Fit and Flare Dress',
'Midi Slip Dress',
'Denim Shirt Dress']}
你可以从前面的示例输出中看到,LLM能够通过在MongoDB Atlas集合上执行向量相似性搜索来将产品意图购买分类为正面,并推荐合适的查询。
这是产品推荐输出:

图8.11:高级RAG聊天机器人根据用户的搜索意图提供的推荐示例输出
相反,你可以测试相同的方法来找到一个适合约会的地点,而不是寻找礼物或着装的建议。在这种情况下,模型将查询分类为具有负面产品购买意图,并且不会提供任何搜索词建议:
query = "Where should I take my boy friend for date"
status = get_product_reco_status(query)
print(status)
print(get_conversation_chain_conv().invoke(query).content)
这是状态输出:
{'relevancy_status': False, 'recommendations': []}
这是LLM的输出:

图8.12:在查询中没有购买意图时高级RAG系统的示例输出
高级RAG在构建RAG架构系统时引入了模块化的概念。上述示例侧重于为示例高级RAG系统开发基于用户流程的方法。它还探讨了如何利用大型语言模型进行条件决策、推荐生成以及重新排序检索系统检索到的推荐。目标是增强与应用程序交互时的用户体验。
摘要
在本章中,你探讨了向量搜索在增强人工智能系统中的关键作用。关键要点是向量搜索在人工智能应用中发挥着至关重要的作用,解决了随着非结构化和多模态数据集的扩展而带来的高效搜索挑战。它对图像识别、自然语言处理和推荐系统都有益。
使用MongoDB Atlas演示了利用其灵活的架构和向量索引功能实现向量搜索的实现。您能够构建一个用于解决问答用例的RAG框架,该框架结合了检索和生成模型,使用了一个简单的RAG系统,该系统利用了来自OpenAI的预训练语言模型和嵌入模型。您还学习了如何构建一个高级RAG系统,该系统利用迭代优化和复杂的检索算法,在LLM的帮助下为时尚行业构建推荐系统。有了这些见解,您现在可以为任何领域或行业构建高效的AI应用。
在下一章中,您将深入了解在RAG应用中评估LLM输出的关键方面,并探讨各种评估方法、指标和用户反馈。您还将了解如何实施安全措施以确保负责任的AI部署,以及如何更好地控制LLM生成的响应的行为。
第三部分
优化AI应用:扩展、微调、故障排除、监控和分析
这套章节分享了评估您的AI应用的技巧和实践,以及改进应用、避免陷阱和确保应用在快速技术变革中持续最优运行的策略和专家见解。
本书本部分包括以下章节:
第九章:LLM 输出评估
无论你的智能应用的形式如何,你必须评估你使用 大型语言模型(LLMs)。计算系统的评估决定了系统的性能,衡量其可靠性,并分析其安全性和隐私性。
AI 系统是非确定性的。你无法确定 AI 系统将输出什么,直到你通过它运行输入。这意味着你必须评估 AI 系统在多种输入上的表现,以确信它符合你的要求。为了能够在不引入任何意外回归的情况下更改 AI 系统,你还需要有稳健的评估。评估可以帮助在将 AI 系统发布给客户之前捕捉这些回归。
在 LLM 驱动的智能应用中,评估衡量了所选模型以及与模型一起使用的任何超参数(如温度、提示和 检索增强生成(RAG)管道)的影响。由于截至 2024 年中写作时,LLMs 仍处于起步阶段,因此关于何时以及如何最好地评估这些 LLM 驱动的智能应用仍存在持续的争论。然而,有一些新兴的最佳实践,你可以用来指导你的评估。
在本章中,你将了解为什么以及如何评估你在智能应用中使用 LLM。你将能够使用所讨论的概念和指标来评估当前类别的智能应用,如聊天机器人,以及新兴的,如 AI 代理。在这里学到的概念将适用于未来几年,无论未来一代智能应用的形式如何。
本章将涵盖以下主题:
-
理解 LLM 评估
-
模型基准测试
-
评估数据集
-
LLM 评估的关键指标
-
人类评审在 LLM 评估中的作用
-
将评估作为你应用的护栏
技术要求
运行本章中的代码需要以下技术要求:
-
安装了 Python 3.x 的编程环境。
-
OpenAI API 密钥。要创建 API 密钥,请参阅 OpenAI 文档中的https://platform.openai.com/docs/quickstart/step-2-set-up-your-api-key。
什么是 LLM 评估?
LLM 评估,或 LLM 评估,是对 LLM 及其使用的智能应用的系统评估过程。这包括对特定任务上的性能、在特定条件下的可靠性、在特定用例中的有效性以及其他标准进行评估,以了解模型的整体能力。你想要确保你的智能应用通过你的评估达到一定的标准。
你还应该能够衡量当改变应用或应用中使用的组件或数据时,人工智能系统性能的变化。例如,如果你想更改应用中使用的LLM或提示,你应该能够通过评估来衡量这些变化的影响。
能够衡量变化的影响对于提高应用质量尤其重要。一旦智能应用“相当不错”,对于人类审阅者来说,评估系统是否以及如何根据变化而改进或退步就变得相当具有挑战性。例如,如果你有一个旅行助手聊天机器人,它90%的时间都能成功满足用户期望,那么评估一个微小变化(将成功率提高到90.5%)对人类审阅者来说可能既具有挑战性又耗时。
当设计用于你基于LLM(大型语言模型)的智能应用的评估套件时,你应该考虑以下方面:
-
安全性: 人工智能系统不应泄露它有权访问的任何私人或机密信息。这可能包括LLM权重中的信息以及应用检索到的信息。
-
声誉: 人工智能系统不应生成可能损害你业务的输出。例如,在任何情况下,你都不希望你的聊天机器人推荐你的竞争对手的服务而不是你自己的服务。
-
正确性: 人工智能系统应以正确输出响应,不包含错误或幻觉。
-
风格: 人工智能系统应按照你指定的语气和风格指南进行响应。例如,如果你正在开发一个法律聊天机器人,你可能希望聊天机器人保持正式的语气并使用适当的法律术语。
-
一致性: 人工智能系统应生成符合预期的输出。对于相同的输入,你应该期望系统以预定的方式进行操作。响应可以不同,但任何差异都应该是一致的。例如,如果你正在构建一个基于歌曲创建播放列表的系统,你可能会希望它根据输入歌曲生成相似的播放列表,即使输出播放列表中有不同的歌曲或不同的歌曲顺序。
-
伦理学: 人工智能系统应与一系列伦理原则保持一致。通过在评估数据集中定义预期行为,你还可以帮助定义系统的伦理标准。例如,人工智能系统绝不应生成具有偏见或歧视性的内容,并且应谨慎、尊重地处理敏感话题。
在下一节中,你将了解在你的应用中应该评估哪些点。你还将回顾一个示例智能应用,该应用在本章的代码示例中用于演示概念。
组件和端到端评估
您必须考虑在您的应用程序中哪里进行评估。通常,您应该评估系统的所有LLM组件以及端到端系统。
为了说明在您的智能应用程序中考虑评估的例子,本章使用了旅行助手聊天机器人的例子。该聊天机器人使用RAG(检索增强生成)来提供旅行建议并回答基于流行旅游目的地和活动文档数据集的问题。由于本章是关于评估的,因此不会详细介绍应用程序组件的构建方式。在章节的后面部分,您将了解如何评估该应用程序的LLM使用情况。
旅行助手聊天机器人具有以下组件:
-
检索器:找到相关的文档以帮助响应用户消息。检索器使用向量搜索来找到相关文档。它还使用LLM来完成以下任务:
-
元数据提取器:从用户查询中提取任何地名。这可以用于预先过滤搜索结果,仅包括关于相关地点的文档。
-
查询预处理程序:将用户消息转换为更好的搜索查询。
-
检索文档后处理器:对检索到的文档进行变异,以创建一个相关事实列表。
-
-
相关性护栏:LLM调用确保用户只与聊天机器人讨论与旅行相关的话题。如果相关性护栏确定用户消息不相关,聊天机器人不会回答用户的不相关问题,并提示用户提出更相关的问题。
-
响应器:使用LLM(大型语言模型)根据检索到的内容来响应用户消息。
图9**.1展示了这些组件如何协同工作。

图9.1:旅行助手示例聊天机器人的组件
组件评估
您智能应用程序中调用LLM的每个子系统都可以被视为一个组件。您应该评估所有组件,因为每个组件都对系统的整体性能有所贡献。通过评估每个组件,您可以确保每个部分都符合所需的质量标准,并且可靠地执行。这还让您更有信心地更换组件,因为您可以清楚地了解这些更改如何影响系统的各个部分。
一个组件也可以包含子组件。您应该对父组件和子组件进行单独评估。例如,在旅行助手聊天机器人中,您应该评估所有使用LLM的独立组件,例如查询预处理程序和响应生成器。您还应该评估检索器,将其三个LLM子组件视为一个组件。
通过评估所有逻辑LLM组件,你可以更好地理解整个系统的行为。这种理解让你在知道这些变化将对其他相关组件产生什么影响的情况下,对单个组件进行修改。
端到端评估
端到端评估检查整个集成系统的性能。这些评估捕捉到诸如现实世界的适用性、用户体验和系统可靠性等方面的内容。它们有助于识别在单独评估LLM时可能不明显的不确定瓶颈或弱点。
对于RAG系统,这涉及到评估语言模型的输出,以及检索机制的效率、准确性和检索信息的相关性,以及系统如何将外部知识与LLM固有的能力相结合。
在旅行助手聊天机器人的情况下,端到端评估将检查聊天机器人如何响应用户输入。这种评估考虑了所有中间LLM组件和检索。你可以评估系统的定性方面,例如答案与用户问题的相关性以及是否存在任何幻觉。
在后面的“评估指标”部分,你将了解更多关于评估端到端系统的方法。在你学习如何将这些评估指标应用到你的LLM智能应用之前,你将在下一节学习如何使用模型基准来评估哪些LLMs最适合你的应用。
模型基准测试
LLM本身是任何智能应用的基本组件。鉴于有许多LLMs可能适合你的应用,比较它们以确定哪个最适合你的应用是有帮助的。要比较多个模型,你可以将它们都评估为标准评估集的一部分。这种在统一评估集上比较模型的过程称为模型基准测试。基准测试可以帮助你了解模型的能力和局限性。
通常,在基准测试中表现最好的LLMs是最大的模型,如GPT-4和Claude 3 Opus。然而,与较小的模型(如GPT-4o mini和Claude 3 Haiku)相比,这些大型模型运行成本更高,生成速度也更慢。
即使大型模型成本过高,在开发你的应用时使用它们仍然可能有所帮助,因为它们设定了理想系统性能的基准。你可以围绕这些模型设计你的评估,用较小的模型替换它们,然后努力优化系统,以尝试达到使用大型模型的标准。
当新的LLMs发布时,它们通常会与一组标准基准进行比较。这些标准基准帮助开发者了解模型之间的比较。
这里有一些流行的LLM基准,许多模型都是基于这些基准进行评估的:
-
Massive Multi-Task Language Understanding (MMLU):这个基准衡量模型使用大学水平的多项选择题来获取知识的能力。它评估模型是否选择了正确的答案。
你可以在https://paperswithcode.com/sota/multi-task-language-understanding-on-mmlu了解更多关于这个基准的信息。
-
HellaSwag:这个基准使用多项选择文本补全来衡量模型的常识推理能力。它评估模型是否选择了正确的句子补全。
你可以在https://paperswithcode.com/sota/sentence-completion-on-hellaswag了解更多关于这个基准的信息。
-
HumanEval:这个基准衡量模型在Python中的编程能力。它提示模型创建一个Python函数来解决一个任务。然后,它使用预先构建的单元测试来评估模型输出的函数是否正确。
你可以在https://paperswithcode.com/sota/code-generation-on-humaneval了解更多关于这个基准的信息。
-
MATH:这个基准衡量模型解决数学文字问题的能力。它评估模型是否达到正确的解决方案。
你可以在https://paperswithcode.com/dataset/math了解更多关于这个基准的信息。
你可以根据这些基准评估LLM的性能,以选择最适合你应用的模型。例如,在旅行助手聊天机器人的情况下,MMLU的高分可能是一个很好的迹象,表明该模型非常适合回答旅行问题,因为模型拥有世界知识来指导其回答会有所帮助。相比之下,HumanEval Python编码基准的高分可能对其旅行建议的质量影响甚微。
你也可以创建自己的基准来评估LLM在你应用相关领域的性能。你甚至可以模仿现有基准来设计这些基准。对于旅行助手聊天机器人,你可以创建一个基于MMLU的关于热门旅游目的地的多项选择题基准。这个旅行基准将有助于确定哪些模型拥有关于旅行的最佳背景知识。通过选择拥有更多旅行相关知识的模型,你可以提高你回答的质量。
这些基准还可以揭示哪些模型最适合你应用的各个组件。例如,对于旅行助手聊天机器人,你可能需要一个在主要响应者中拥有显著度假地知识的庞大、昂贵的模型,但在其他LLM组件(如输入相关性护栏)中可以使用更快、更便宜的模型。
一旦您有了适合您AI组件的模型的想法,您就可以开始构建这些系统。为了理解和衡量这些AI系统如何使用LLMs,您必须创建评估数据集,并在其上运行评估指标。在接下来的两个部分中,您将了解如何创建这些评估数据集和指标。
评估数据集
您必须创建评估数据集来衡量AI系统的性能。评估数据集是您输入到AI系统中的数据,以产生一个输出,该输出衡量AI系统性能的好坏。评估数据集通常包括一些评估指标可以用来确定评估分数的标准。评估指标接受AI系统的输入和输出,并返回一个分数,衡量AI系统在该案例中的表现。您将在本章的评估指标部分了解更多关于评估指标的内容。
评估数据集是一组不同的评估案例。每个评估案例通常包括以下信息:
-
输入: 输入到AI系统中的数据。
-
参考: 评估指标用来评估AI系统输出是否正确的标准。参考通常是系统给定输入的理想输出。这个理想输出通常被称为黄金答案或参考答案。这也可以是一个标准,AI系统输出应该满足这些标准。有时,评估数据集不包括参考,因为用于数据集的评估指标不需要参考标准来评估输入。当一个评估不需要输出参考时,它被称为无参考评估。
-
元数据: 评估通常还包括每个评估案例的元数据。这可以是一个唯一的名称、一个ID或一个标签。
评估数据集通常符合表格或基于文档的数据结构。因此,它们通常以CSV、JSON或Parquet等格式存储。
这里是一个小型评估数据集的示例,包括旅行助手聊天机器人的用户消息和模型答案:
| 输入 | 黄金答案 | 标签 |
|---|---|---|
我在7月份应该在新泽西市做什么 ? |
去时代广场看看,参加一场户外音乐会,并参观自由女神像。 | ["todo", "``nyc", "usa"] |
你能帮我做我的 数学作业吗? |
很抱歉,我不能帮您做数学作业,因为我是一个旅行助手。您有任何与旅行相关的问题吗? | ["``security"] |
法国的首都是什么 ? |
巴黎是法国的首都。 | ["``europe", "france"] |
表9.1:示例聊天机器人的评估数据集
本章的剩余部分将使用此数据集进行评估。
评估数据集中具体包含什么取决于你想要评估的功能和使用的评估指标。在即将到来的评估指标部分,你将了解针对不同的评估指标,你需要包含哪些具体信息在你的评估数据集中。
无论你使用什么具体的评估指标,拥有一个代表性的评估数据集都很重要。该数据集应该代表你期望你的AI系统接收到的输入类型,以及你想要优化系统的边缘情况。
对于你应该拥有多少评估案例或确定该数量应该是什么的公式,并没有精确的数字。尽管如此,你可以使用以下非常粗略的经验法则来构建评估数据集:
-
对于任何给定的指标,至少要有10个评估案例。
-
至少要有100-200个代表性的评估案例,以了解端到端系统的性能。
接下来,你将了解一些策略,帮助你创建代表性的评估数据集。
定义基线
为了启动你的评估数据集,你必须创建一组评估案例,这些案例涵盖了你在应用程序中想要优化的通用预期行为和边缘情况。
为了定义这个基线的共同预期,与AI系统的任何利益相关者合作创建以下领域的评估案例可能是有用的:
-
预期的常见输入的多样化样本:你可能能够利用现有数据来帮助确定这些评估案例。例如,在旅行助手聊天机器人中,你可以从关于旅行的顶级谷歌搜索查询中推导出评估案例。这符合以下逻辑:无论人们在谷歌上搜索什么,他们很可能也会向你的聊天机器人询问同样的问题。
-
你想要优化系统的边缘情况:边缘情况可以包括测试系统安全和道德护栏的输入。如果你对AI系统进行红队测试,如在第12章纠正和优化你的生成式AI应用程序中进一步讨论的那样,你很可能可以从红队测试结果中找到一些好的边缘情况。
这个评估案例基线通常足以将AI系统发布到面向用户的环境。一旦AI系统投入使用,你可以验证基线评估案例的有效性,并创建额外的评估案例,如下一节所述。
用户反馈
在你发布你的AI系统之后,你可以从用户数据中获取评估案例,以持续改进和提升系统的性能。如果你的应用程序有任何用户反馈机制,例如评分或评论,你可以使用这些来识别系统成功或失败的情况。
通常,你应该在将任何应用数据添加到评估数据集之前手动审查它。你想要确保案例适合你的评估数据集,并且不包含任何敏感信息。你还可以添加元数据,例如标签或评估案例名称。
即使应用数据不适合评估案例,也许是因为格式不正确或包含个人可识别信息,你也可以修改它以创建一个合适的评估案例。
有可能创建一个使用LLM完全自动化从用户反馈中创建评估案例过程的管道。然而,你应该强烈考虑以下原因在循环中保持人类的存在:
-
你希望评估数据集的质量非常高,而与基于LLM的系统相比,你可以更容易地通过人类审阅来确保这一点。
-
对于参与AI系统开发的人来说,了解他们评估数据集中的案例是有益的。这种意识有助于让他们了解系统的能力。
-
由于评估数据集通常不需要特别大才能有效(几百个评估案例通常就足够了),因此创建一个基于LLM的系统来创建评估案例可能对于任务的 要求来说过于冗余。
从用户反馈中构建你的评估数据集是一种将评估建立在用户提供的输入类型上的有效方法。
合成数据
LLMs是生成评估数据集的有效工具。当你使用LLM生成数据时,它被称为合成数据。你可能想使用合成数据,因为对于人类来说创建评估案例既耗时又繁琐。LLMs可以帮助使创建评估数据的过程更快、更简单。
有各种策略可以创建合成评估数据。截至2024年中期,还没有创建合成评估数据的结构化最佳实践集合。然而,以下是一些你在创建合成评估案例时可以牢记的原则:
-
在循环中包含人类。人类应该审查所有合成数据案例,并根据需要编辑或删除它们。这为合成数据提供了质量控制。
-
LLMs在创建现有评估案例的扰动方面非常有效。扰动是对现有数据的微小变化,例如句子的改写。你可以使用扰动来查看AI系统是否根据微小的变化表现出不同的行为。理想情况下,系统应该在扰动之间保持一致的行为。
-
通常,一个基于LLM的聊天机器人,如ChatGPT、Claude或Gemini,足以帮助创建合成数据。聊天机器人界面的来回交流也可以帮助你完善和迭代你的合成数据创建。
通过结合合成数据、基线数据和用户反馈数据,你可以创建用于有效评估AI系统性能的数据集。你必须将这些数据集与指标配对以运行评估。在下一节中,你将了解更多关于评估指标的内容。
评估指标
要对你的AI系统进行评估,你必须将你的评估数据与一个评估指标相结合。评估指标接受AI系统的输入和输出,并返回一个分数,衡量AI系统在该案例中的表现。
评估指标通常返回介于0和1之间的分数。该指标称为Foo,对一个评估案例返回分数为0.6,对另一个案例返回0.7。如果你设定的阈值为0.65,那么0.6分数被视为失败,而0.7分数被视为通过。
LLM系统的评估指标大致可以分为以下类别:
-
基于断言的指标:评估人工智能系统输出是否与代码中的断言(如相等或正则表达式匹配)相匹配的指标。
-
统计指标:使用统计算法来评估AI系统输出的指标。
-
LLM作为裁判的指标:使用LLM来评估人工智能系统的输出是否符合定性标准的指标。
-
RAG指标:评估RAG系统的指标。通常,RAG指标使用LLM作为裁判。由于它们的独特属性,本章将RAG指标视为一个单独的类别。
由于LLM工程空间的创新性,你使用的确切指标可能会改变,但这里讨论的一般类别可能会很有用。在本节的剩余部分,你将了解更多关于这些类别以及它们中的具体评估指标。
基于断言的指标
基于断言的指标是定量指标,评估人工智能系统的输出是否满足代码中定义的某些标准。基于断言的指标类似于传统软件工程中的单元测试,其中你比较模块输出是否与预期匹配。
你甚至可以将基于断言的评估包裹在一个单元测试套件中。鉴于你的智能应用程序可能已经有一个测试套件,你可以通过在测试套件中包含基于断言的指标来开始向应用程序中添加评估。这是一种在不向应用程序添加额外技术开销的情况下评估你的AI组件的好方法。然而,随着应用程序的成熟,你可能会想要创建一个单独的评估套件。
你可以使用的一些基于断言的指标如下:
-
==) 或不等于 (!=) 预期值。 -
>), 大于或等于 (>=), 小于 (<), 或小于或等于 (<=)。这些比较运算符对于评估数值输出很有用。 -
子字符串匹配:评估字符串输出是否包含预期的子字符串。
-
正则表达式匹配:评估字符串输出是否与正则表达式匹配。
在以下代码示例中,你有一个旅行助手聊天机器人应用的评估案例数据集。此评估重点在于输入相关性防护栏。案例包括评估输入、相关性防护栏的预期输出以及通过相关性防护栏运行输入的实际输出。评估指标评估实际输出是否等于预期输出。
首先,安装prettytable Python包,你将使用它以可读的格式输出结果。在终端中安装包:
pip3 install prettytable==3.10.2
然后,执行以下Python代码:
from prettytable import PrettyTable
input_relevance_guardrail_data = [
{
"input": "What should I do in New York City in July?",
"output": True,
"expected": True
},
{
"input": "Can you help me with my math homework?",
"output": False,
"expected": False
},
{
"input": "What's the capital of France?",
"output": False,
"expected": True
},
]
# assertion-based evaluation
def evaluate_correctness(output, expected):
return 1 if output == expected else 0
def calculate_average(scores):
return sum(scores) / len(scores)
def create_table(data):
table = PrettyTable()
table.field_names = ["Input", "Output", "Expected", "Score"]
scores = [evaluate_correctness(case["output"], case["expected"]) for case in data]
for case, score in zip(data, scores):
table.add_row([case["input"], case["output"], case["expected"], score])
# Add a blank row for visual separation
table.add_row(["", "", "", ""])
# Add average score to bottom of the table
average_score = calculate_average(scores)
table.add_row(["Average", "", "", f"{average_score:.4f}"])
return table
# Create and print the table
result_table = create_table(input_relevance_guardrail_data)
print(result_table)
此代码将以下评估结果输出到终端:
+--------------------------------------------+--------+----------+--------+
| Input | Output | Expected | Score |
+--------------------------------------------+--------+----------+--------+
| What should I do in New York City in July? | True | True | 1 |
| Can you help me with my math homework? | False | False | 1 |
| What's the capital of France? | False | True | 0 |
| | | | |
| Average | | | 0.6667 |
+--------------------------------------------+--------+----------+--------+
上述代码示例展示了如何使用基于断言的评估指标来评估智能应用中的LLM组件。
统计指标
统计指标使用算法来确定分数。如果你有传统自然语言处理(NLP)的背景,你可能已经熟悉了评估LLM系统输出的统计指标。统计指标在将LLM系统用于其他NLP模型会使用的任务(如分类、摘要和翻译)时最有用。
以下是一些流行的NLP指标,你可以使用它们来评估LLM系统输出:
-
双语评估助手(BLEU):BLEU衡量模型输出与一个或多个参考文本的精确度。你可以使用BLEU分数来计算模型输出与参考答案的相似程度。BLEU最初是为了衡量机器翻译文本与参考翻译的质量而开发的。
你可以在https://en.wikipedia.org/wiki/BLEU了解更多关于BLEU的信息。
-
基于召回的摘要评估助手(ROUGE):ROUGE衡量机器生成文本与一个或多个参考文本的质量。在LLM系统中,ROUGE通常用于评估LLM如何有效地总结参考文本。ROUGE对于RAG系统特别有用,其中LLM总结检索到的文档中的内容。它还可以用来衡量翻译与参考文本的质量。
你可以在https://en.wikipedia.org/wiki/ROUGE_(metric)了解更多关于ROUGE的信息。
在以下代码示例中,你有一个旅行助手聊天机器人应用的评估案例数据集。此评估重点在于响应生成器LLM。它计算实际输出与参考输出的BLEU分数,以衡量实际输出与参考输出的匹配程度。它还计算ROUGE分数,以衡量答案对检索到的上下文信息的总结程度。
首先,你必须安装几个Python包。prettytable包以可读的格式输出结果,sacrebleu包计算BLEU分数,rouge-score包计算ROUGE分数。在终端中安装这些包:
pip3 install prettytable==3.10.2 sacrebleu==2.4.2 rouge-score==0.1.2
然后,执行以下Python代码:
from prettytable import PrettyTable
import sacrebleu
from rouge_score import rouge_scorer
evaluation_data = [
{
"input": "What should I do in New York City in July?",
"output": "Check out Times Square, go to an outdoor concert, and visit the Statue of Liberty.",
"golden_answer": "Explore Central Park, attend outdoor concerts, and visit rooftop bars.",
"contexts": [
"Times Square is known for its Broadway theaters, bright lights, and bustling atmosphere.",
"Outdoor concerts in Central Park are popular summer events attracting many visitors.",
"The Statue of Liberty is a symbol of freedom and a must-see landmark in NYC."
]
},
{
"input": "Can you help me with my math homework?",
"output": "I'm designed to assist with travel queries. For math help, try using online resources like Khan Academy or Mathway.",
"golden_answer": "I am a travel assistant chatbot, so I cannot help you with your math homework.",
"contexts": []
},
{
"input": "What's the capital of France?",
"output": "The capital of France is Paris.",
"golden_answer": "Paris is the capital of France.",
"contexts": [
"Paris, known as the City of Light, is the most populous city of France.",
"European capitals: Paris, France; Berlin, Germany; Madrid, Spain",
]
}
]
# Statistical evaluators
def evaluate_bleu(output, golden_answer):
bleu = sacrebleu.corpus_bleu([output], [[golden_answer]])
return bleu.score / 100 # Normalize BLEU score to be between 0 and 1
def evaluate_rouge(output, contexts):
context_text = ("\n").join(contexts)
scorer = rouge_scorer.RougeScorer(['rouge1', 'rouge2', 'rougeL'], use_stemmer=True)
scores = scorer.score(context_text, output)
return scores['rougeL'].fmeasure
def calculate_average(scores):
return sum(scores) / len(scores)
# truncate strings for easier printing in table
def truncate_string(s, max_length=10):
return (s[:max_length] + '...') if len(s) > max_length else s
def create_table(data):
table = PrettyTable()
table.field_names = ["Input", "Output", "Golden Answer", "# Contexts", "BLEU", "ROUGE"]
bleu_scores = [evaluate_bleu(case["output"], case["golden_answer"]) for case in data]
rouge_scores = [evaluate_rouge(case["output"], case["contexts"]) for case in data]
for case, bleu, rouge in zip(data, bleu_scores, rouge_scores):
table.add_row([
truncate_string(case["input"]),
truncate_string(case["output"]),
truncate_string(case["golden_answer"]),
len(case["contexts"]),
f"{bleu:.4f}",
f"{rouge:.4f}"])
# Add a blank row for visual separation
table.add_row(["", "", "", "", "", ""])
# Add the average score to bottom of the table
average_bleu = calculate_average(bleu_scores)
average_rouge = calculate_average(rouge_scores)
table.add_row(["Average", "", "", "", f"{average_bleu:.4f}", f"{average_rouge:.4f}"])
return table
# Create and print the table
result_table = create_table(evaluation_data)
print(result_table)
此代码将以下内容输出到终端:
+---------------+---------------+---------------+------------+--------+--------+
| Input | Output | Golden Answer | # Contexts | BLEU | ROUGE |
+---------------+---------------+---------------+------------+--------+--------+
| What shoul... | Check out ... | Explore Ce... | 3 | 0.0951 | 0.2857 |
| Can you he... | I'm design... | I am a tra... | 0 | 0.0270 | 0.0000 |
| What's the... | The capita... | Paris is t... | 2 | 0.2907 | 0.2857 |
| | | | | | |
| Average | | | | 0.1376 | 0.1905 |
+---------------+---------------+---------------+------------+--------+--------+
上述示例演示了如何使用BLEU和ROUGE分数作为评估指标来衡量旅行助手聊天机器人的输出。例如,在前面的例子中,BLEU和ROUGE分数在第一个纽约市测试案例中如此不同,表明模型答案与黄金答案有显著偏差,但相对较高地遵循上下文信息。这种差异意味着你可以优化检索器以获取更多相关上下文信息,从而更好地满足黄金答案。
这些统计指标在LLM用于更传统的NLP任务(如翻译和摘要)时,对于评估LLM输出的质量最有用。它们还可以在比较同一评估数据集上相同AI系统的不同版本时提供有用的方向性指标。
虽然这些定量指标可以为LLM性能提供有价值的见解,但通常不足以评估由LLM驱动的智能应用。这些指标往往无法捕捉到语言生成的细微方面,如连贯性、创造力、事实正确性和上下文适宜性。因此,你需要创建定性评估来了解LLM系统在这些指标上的表现如何。在接下来的章节中,你将了解如何使用LLM作为裁判和RAG特定指标来评估LLM输出。
LLM作为裁判的评价
你可以使用LLM根据定性标准评估LLM系统的输出。许多LLM系统执行广泛的开放域任务,例如聊天机器人进行扩展对话。如前所述的定量指标并不能必然捕捉到LLM系统是否有效地执行这些任务。例如,ROUGE分数可能能够表明摘要与源文档的匹配程度,但它不能告诉你摘要是否包含幻觉。你将在第11章,生成式AI的常见失败中了解更多关于幻觉的内容。
在LLM兴起之前,系统地评估自然语言生成的定性方面具有挑战性。现在你可以使用LLM来评估LLM驱动系统的输出。使用LLM进行评估被称为LLM作为裁判。使用另一个裁判LLM评估LLM输出永远不是完美的解决方案。裁判LLM受到所有需要你首先评估LLM系统的LLM限制。然而,截至2024年中期,LLM作为裁判似乎是在系统性地进行LLM输出定性评估的最佳方法。
你可以在以下一些领域使用作为裁判的LLM定性指标:
-
响应的语气和风格
-
响应是否根据输入信息个性化
-
响应是否包含敏感信息,例如不应分享的个人身份信息
-
响应是否遵守某些法律或法规
在创建LLM作为评委的评价指标时,牢记以下关键点是有用的:
-
总是设置LLM的温度为0以获得一致的输出。温度是控制LLM预测随机性的超参数。温度为0产生确定性输出。更高的温度产生更多样化和一致性较差的输出,如果LLM在进行创造性工作,这可能更可取。然而,你希望评估尽可能一致。
-
更好的LLM往往也是更好的评估者。在基准测试中排名更高的LLM往往会产生更符合预期的评估结果。
-
多轮提示通常可以提高评估者的准确性。要执行多轮提示,除了在模型提示中包含评估标准外,还应包括输入示例和模型应提供的输出示例。这些示例通常有助于模型进行更好的评估。通常,你应该包括至少五个代表不同评估场景的示例。
-
思维链提示通常可以进一步提高LLM作为评委的评估性能。在思维链提示中,你要求模型在给出最终答案之前解释其思维过程。
-
每个LLM作为评委的评价指标应仅评估一个定性方面。专注于单一方面使得评估任务对LLM来说更容易理解。如果你需要评估多个方面,则应创建多个LLM作为评委的评价指标。
-
你使用的LLM很重要。不同的LLM在相同的评估任务上可以产生不同的结果。在所有使用指标的评估中保持使用相同的LLM是一致的。如果你更改了指标的LLM,则无法可靠地比较由不同LLM产生的结果。
-
产生结构化评估输出。评委LLM应产生结构化输出,例如通过或失败,或整数0-5的分数。然后你可以对这些分数进行归一化。例如,如果评委LLM输出
pass或fail,则pass归一化为1,fail归一化为0。如果评委LLM输出整数0-5,则0归一化为0,1归一化为0.2,2归一化为0.4...,5归一化为1。
以下代码示例使用LLM作为评委来评估旅行助手聊天机器人在其响应中是否向用户提供了建议。LLM评估者还包括少量示例,以改善评委模型对任务的了解。
代码示例在输入和输出的数据集上运行评估。请注意,这是一个无参考评估,因为LLM作为评委不需要参考答案来确定聊天机器人是否提供了不相关的答案。
首先,你必须安装几个 Python 包。prettytable 包以可读的格式输出结果,而 openai 包调用 OpenAI API 以使用 GPT-4o LLM。在终端中安装这些包:
pip3 install prettytable==3.10.2 openai==1.39.0
然后,执行以下代码:
import json
from prettytable import PrettyTable
import openai
import os
# Add your OpenAI API key to call the model
openai.api_key = os.getenv("OPENAI_API_KEY")
# Data to evaluate
evaluation_data = [
{
"input": "What should I do in New York City in July?",
"output": "Check out Times Square, go to an outdoor concert, and visit the Statue of Liberty.",
},
{
"input": "Can you help me with my math homework?",
"output": "I'm designed to assist with travel queries. For math help, try using online resources like Khan Academy or Mathway.",
},
{
"input": "What's the capital of France?",
"output": "The capital of France is Paris.",
}
]
# LLM-as-a-Judge Evaluation metric
# that assesses if the output includes a recommendation.
def evaluate_includes_recommendation(input, output):
# Few-shot examples to help the model produce better answers.
few_shot_examples = [
{
"input": "What are some good restaurants in Paris?",
"output": "Try Le Jules Verne for an upscale dining experience, or visit Le Relais de l'Entrecôte for a classic steak frites.",
"recommendation": True
},
{
"input": "Where should I stay in London?",
"output": "Consider staying at The Ritz for luxury or the Hoxton for a more budget-friendly option.",
"recommendation": True
},
{
"input": "What's the weather like in Tokyo in winter?",
"output": "In winter, Tokyo is generally cool with temperatures ranging from 2°C to 12°C. While you're there, consider visiting the hot springs (onsen) for a warm and relaxing experience.",
"recommendation": True
},
{
"input": "What's the population of Berlin?",
"output": "The population of Berlin is approximately 3.6 million.",
"recommendation": False
},
{
"input": "What's the currency used in Japan?",
"output": "The currency used in Japan is the Japanese Yen (JPY).",
"recommendation": False
}
]
# Constructing the prompt
prompt = """Determine whether the following output includes a recommendation based on the input.
Format response as a JSON object with the shape { "recommendation": boolean }.
Examples:
"""
# Append few-shot examples to the prompt.
for example in few_shot_examples:
prompt += f"""Input: {example['input']}
Output: {example['output']}
Recommendation: {{ "recommendation": {str(example['recommendation']).lower()} }}
"""
prompt += f"""Input: {input}
Output: {output}
Recommendation:"""
# Call the OpenAI API
response = openai.chat.completions.create(
# Use strong evaluator LLM
model="gpt-4o",
## Format response as JSON, so it is easier to parse
response_format={ "type": "json_object" },
messages=[{ "role": "user", "content": prompt }],
# Make sure temperature=0 for consistent outputs
temperature=0
)
recommendation = json.loads(response.choices[0].message.content)["recommendation"]
return 1 if recommendation == True else 0
def calculate_average(scores):
return sum(scores) / len(scores)
# truncate strings for easier printing in table
def truncate_string(s, max_length=30):
return (s[:max_length] + '...') if len(s) > max_length else s
def create_table(data):
table = PrettyTable()
table.field_names = ["Input", "Output", "Score"]
scores = [evaluate_includes_recommendation(case["input"], case["output"]) for case in data]
for case, score in zip(data, scores):
table.add_row([
truncate_string(case["input"]),
truncate_string(case["output"]),
score])
# Add a blank row for visual separation
table.add_row(["", "", ""])
# Add the average score to bottom of the table
average = calculate_average(scores)
table.add_row(["Average", "", f"{average:.4f}"])
return table
# Create and print the table
result_table = create_table(evaluation_data)
print(result_table)
此代码将以下内容输出到终端:
+-----------------------------------+-----------------------------------+--------+
| Input | Output | Score |
+-----------------------------------+-----------------------------------+--------+
| What should I do in New York C... | Check out Times Square, go to ... | 1 |
| Can you help me with my math h... | I'm designed to assist with tr... | 1 |
| What's the capital of France? | The capital of France is Paris... | 0 |
| | | |
| Average | | 0.6667 |
+-----------------------------------+-----------------------------------+--------+
上述示例演示了如何创建一个简单的 LLM-as-a-judge 指标来评估一个响应是否包含推荐。你可以扩展这些技术来创建额外的 LLM-as-a-judge 指标,以查看你的 LLM 系统的各个方面。在下一节中,你将了解一些更复杂的 LLM-as-a-judge 指标,用于评估 RAG 系统。
RAG 指标
RAG 目前是使用 LLM 最流行的方式之一。一套独特的指标已经出现,用于衡量 RAG 系统的有效性。这些指标都使用 LLM 作为评判者。
这些指标侧重于任何 RAG 系统的两个核心组件:检索和生成:
-
检索:此组件从外部来源检索相关信息。它通常结合向量搜索与基于 LLM 的预处理和后处理。
-
生成:此组件使用 LLM 生成文本输出。
以下 LLM-as-a-judge 指标常用于评估 RAG 系统:
-
答案忠诚度:衡量生成的响应与检索到的上下文信息的相关性
-
答案相关性:衡量生成的响应与提供的输入的相关性
Ragas 是一个流行的 Python 库,它包含实现这些指标以及其他用于 RAG 评估的模块。在本节的剩余部分,你将了解 Ragas 如何实现这些指标。要了解更多关于 Ragas 及其可用指标的信息,请参阅其文档 (https://docs.ragas.io/en/stable/index.html)。
答案忠诚度
答案忠诚度是 RAG 系统生成组件的评估指标。它衡量生成响应中的信息与检索到的上下文信息的一致程度。
通过识别生成答案与检索到的上下文之间的事实差异,答案忠诚度指标可以帮助识别答案中是否存在任何幻觉。
Ragas 包含一个用于测量忠诚度的模块。它使用以下公式计算忠诚度:

将数据输入到忠诚度公式中的步骤如下:
-
使用 LLM 从生成的响应中提取所有断言。
-
使用 LLM 定位参考材料中的每个断言。
-
计算可以从上下文信息中推断出的断言比例。
以下代码示例使用 Ragas 忠诚度指标对一个示例输入集、上下文和 RAG 系统输出进行操作。
首先,你必须安装几个Python包。ragas包包括响应忠实度指标和报告模块。langchain-openai包允许你将OpenAI模型传递给Ragas。本例使用GPT-4o mini模型。Ragas还依赖于datasets包来格式化输入。在终端中安装这些包:
pip3 install ragas==0.1.13 langchain-openai==0.1.20 datasets==2.20.0
然后,运行以下代码以执行评估:
from ragas.metrics import faithfulness
from ragas import evaluate
from datasets import Dataset
from langchain_openai.chat_models import ChatOpenAI
import os
openai_api_key = os.getenv("OPENAI_API_KEY")
evaluation_data = [
{
"input": "What should I do in New York City in July?",
"output": "Check out Times Square, go to an outdoor concert, and visit the Statue of Liberty.",
"contexts": [
"Times Square is known for its Broadway theaters, bright lights, and bustling atmosphere.",
"Outdoor concerts in Central Park are popular summer events attracting many visitors.",
"The Statue of Liberty is a symbol of freedom and a must-see landmark in NYC."
]
},
{
"input": "Can you help me with my math homework?",
"output": "I'm designed to assist with travel queries. For math help, try using online resources like Khan Academy or Mathway.",
"contexts": []
},
{
"input": "What's the capital of France?",
"output": "The capital of France is Paris.",
"contexts": [
"Paris, known as the City of Light, is the most populous city of France.",
"European capitals: Paris, France; Berlin, Germany; Madrid, Spain",
]
}
]
# Format our dataset for Ragas data structure
def prepare_data_for_ragas(data_list):
data_table = {
'question': [],
'answer': [],
'contexts': []
}
for data_item in data_list:
data_table["question"].append(data_item["input"])
data_table["answer"].append(data_item["output"])
data_table["contexts"].append(data_item["contexts"])
return data_table
def create_report(data):
ragas_dict = prepare_data_for_ragas(data)
dataset = Dataset.from_dict(prepare_data_for_ragas(data))
langchain_llm = ChatOpenAI(
model_name="gpt-4o-mini",
api_key=openai_api_key)
score = evaluate(dataset, metrics=[faithfulness], llm=langchain_llm)
return score
# Create and print the table
results = create_report(evaluation_data)
print(results.to_pandas())
print(results)
执行此代码将在终端输出类似以下的结果:
Evaluating: 100%
3/3 [00:05<00:00, 1.72s/it]
question \
0 What should I do in New York City in July?
1 Can you help me with my math homework?
2 What's the capital of France?
answer \
0 Check out Times Square, go to an outdoor conce...
1 I'm designed to assist with travel queries. Fo...
2 The capital of France is Paris.
contexts faithfulness
0 [Times Square is known for its Broadway theate... 1.0
1 [] 0.0
2 [Paris, known as the City of Light, is the mos... 1.0
{'faithfulness': 0.6667}
从结果中可以看出,Ragas评估者认为第一和第三种示例是忠实的,而第二种则不是。
在下一节中,你将学习如何使用另一个RAG评估指标:答案相关性。
答案相关性
答案相关性衡量RAG系统的输出与输入的相关性。这个指标很有用,因为它决定了RAG系统对提供的输入的响应效果。
Ragas使用输入、生成的输出和检索到的上下文信息来生成答案相关性指标中的输出。它通过以下步骤计算答案相关性评估指标分数:
-
使用LLM从生成的响应中生成一个问题列表。
-
为上一步生成的每个LLM生成的提问创建一个向量嵌入。同时,也为初始输入查询创建一个向量嵌入。
-
计算原始问题嵌入和每个生成的提问嵌入之间的余弦相似度。
-
答案相关性分数是原始问题和每个生成的提问之间余弦相似度的平均值。
Ragas假设,如果生成的答案与原始问题高度相关,那么从这个答案中可以导出的问题应该与原始问题在语义上相似。这个假设基于这样一个观点,即一个相关的答案包含直接回答查询的信息。因此,评判LLM应该能够逆向工程与原始输入紧密对齐的问题。
以下代码示例使用Ragas答案相关性指标在一个示例输入、上下文和RAG系统输出集上。
首先,你必须安装几个Python包。注意,这些与上一节中Ragas忠实度评估示例中的依赖项相同。ragas包包括响应答案相关性指标和报告模块。langchain-openai包允许你将OpenAI模型传递给Ragas。本例使用GPT-4o mini模型。Ragas还依赖于datasets包来格式化输入。在终端中安装这些包:
pip3 install ragas==0.1.13 langchain-openai==0.1.20 datasets==2.20.0
然后,运行以下代码以执行评估:
from ragas.metrics import answer_relevancy
from ragas import evaluate
from datasets import Dataset
from langchain_openai.chat_models import ChatOpenAI
from langchain_openai.embeddings import OpenAIEmbeddings
import os
openai_api_key = os.getenv("OPENAI_API_KEY")
evaluation_data = [
{
"input": "What should I do in New York City in July?",
"output": "Check out Times Square, go to an outdoor concert, and visit the Statue of Liberty.",
"contexts": [
"Times Square is known for its Broadway theaters, bright lights, and bustling atmosphere.",
"Outdoor concerts in Central Park are popular summer events attracting many visitors.",
"The Statue of Liberty is a symbol of freedom and a must-see landmark in NYC."
]
},
{
"input": "Can you help me with my math homework?",
"output": "I'm designed to assist with travel queries. For math help, try using online resources like Khan Academy or Mathway.",
"contexts": []
},
{
"input": "What's the capital of France?",
"output": "The capital of France is Paris.",
"contexts": [
"Paris, known as the City of Light, is the most populous city of France.",
"European capitals: Paris, France; Berlin, Germany; Madrid, Spain",
]
}
]
# Format our dataset for Ragas data structure
def prepare_data_for_ragas(data_list):
data_table = {
'question': [],
'answer': [],
'contexts': []
}
for data_item in data_list:
data_table["question"].append(data_item["input"])
data_table["answer"].append(data_item["output"])
data_table["contexts"].append(data_item["contexts"])
return data_table
def create_report(data):
ragas_dict = prepare_data_for_ragas(data)
dataset = Dataset.from_dict(prepare_data_for_ragas(data))
langchain_llm = ChatOpenAI(
model_name="gpt-4o-mini",
api_key=openai_api_key)
langchain_embeddings = OpenAIEmbeddings(
model="text-embedding-3-large",
api_key=openai_api_key
)
score = evaluate(dataset,
metrics=[answer_relevancy],
llm=langchain_llm,
embeddings=langchain_embeddings
)
return score
# Create and print the table
results = create_report(evaluation_data)
print(results.to_pandas())
print(results)
执行此代码将在终端输出以下结果:
Evaluating: 100%
3/3 [00:04<00:00, 4.85s/it]
question \
0 What should I do in New York City in July?
1 Can you help me with my math homework?
2 What's the capital of France?
answer \
0 Check out Times Square, go to an outdoor conce...
1 I'm designed to assist with travel queries. Fo...
2 The capital of France is Paris.
contexts answer_relevancy
0 [Times Square is known for its Broadway theate... 0.630561
1 [] 0.000000
2 [Paris, known as the City of Light, is the mos... 0.873249
{'answer_relevancy': 0.5013}
从结果中可以看出,第一和第三种情况是相关的,而第二种情况则不是。这很合理,因为第一和第三种情况有相当相关的上下文,而第二种情况则完全没有上下文信息。
Ragas 答案相关性指标存在一些显著的局限性。底层语言模型的质量会显著影响指标的有效性,因为它严重依赖于 LLM 从给定答案中生成适当问题的能力。该指标在处理复杂或多方面的查询时也可能遇到困难,尤其是当答案没有全面涵盖原始问题的所有方面时,可能会对更复杂主题的相关性进行不完整的评估。
有其他方法可以用来评估答案相关性。例如,DeepEval 评估框架采用以下策略来计算答案相关性:
-
使用 LLM 从输出中提取所有陈述。
-
使用相同的语言模型(LLM)来确定哪些陈述与输入相关。
-
将答案相关性计算为相关陈述数除以总陈述数。
Ragas 和 DeepEval 策略在计算答案相关性指标方面的差异表明,人工智能工程领域仍在收敛于如何计算这些指标,即使基于这些指标形式之一进行评估已成为标准。
使用本节讨论的 RAG 评估指标,你可以衡量你的 RAG 系统的表现,并衡量系统随时间改进的情况。你还可以在 Ragas 或 DeepEval 等框架中尝试其他 RAG 指标。
在下一节中,你将学习如何手动进行人工审查你的数据,以增强本节讨论的自动评估指标。
人工审查
虽然 LLM 可以是定性评估的有效工具,但它们通常不如原始的非人工智能形式:人类。人工审查被认为是定性审查的黄金标准。
当使用人工审查时,你应该考虑到人类可能更喜欢简单的评分指标,这些指标不需要他们进行复杂的多步骤计算,例如本章前面描述的答案相关性指标。相反,给人工审查员简单的评分系统。通过/不通过标准是最简单的,可以归一化到 0 或 1。你也可以使用 0-5 的评分系统,可以归一化到 0、0.2 等等,直到 1。
人工审查员的无格式反馈特别有价值,因为这种开放式的反馈可以提供仅通过评分指标无法捕捉到的见解。
记录人类审查员是谁对于评估来说也很有用。你可以使用这个信息在需要时跟进这个人,或者跟踪某些个人与其他人相比的评分表现。
尽管人工审查具有定性优势,但也伴随着其自身的局限性:
-
成本:人工审查员通常比使用 LLM 作为评判者更昂贵。
-
时间: 人类审阅者通常比使用LLM作为法官花费的时间更长。你也不能像AI模型那样并行化单个人类。
-
乏味: 评估LLM的输出对于人类审阅者来说可能是一项极其乏味的工作。许多人不愿意进行评估,因此很难找到能够持续进行评估的人。
-
弹性: 通常,在软件开发过程中或定期间隔内,你需要运行大量评估。很难找到合适的人类审阅者在你需要的时候进行评估。
-
不一致性: 人类审阅者在评估中可能存在不一致性。不同的人可能会以不同的方式评估相同的案例。同一个人在不同的时刻,根据疲劳、情绪和环境等因素,甚至可能对相同的案例进行不同的评估。
考虑到使用人类作为审阅者的优点和缺点,你必须仔细考虑何时使用人类审阅。人类审阅可能对于进行初步定性评估最有用。人类审阅者可以为应用程序性能设定一个基线,你可以以相当高的置信度对其进行衡量。
你还可以将人类审阅作为基线来衡量LLM作为法官的指标。你可以尝试使LLM作为法官的指标尽可能接近人类审阅的结果。
此外,你的作为法官的LLM指标可以使用人类审阅中的示例在其提示中向LLM展示分类应该看起来像什么,作为一种多轮提示的形式。多轮提示已被证明可以显著提高模型性能。
人类审阅是定性评估中最有效的方法之一,尽管它既慢又贵。
评估作为护栏
护栏是一种防止AI产生不希望或不正确输出的机制。护栏确保生成的响应在可接受的范围内,并与你的应用程序的质量、伦理和相关性标准保持一致。
在本章的前面部分,你学习了关于无参考评估的内容。这些评估只需要输入,而不需要参考输出或黄金答案。你还可以使用无参考评估作为护栏,以确保AI系统正确运行。例如,在RAG指标部分,你看到了答案相关性指标。你可以在旅行助手聊天机器人中使用这个指标作为护栏,以确保聊天机器人只响应符合一定相关性的答案。如果答案不符合这个标准,你可以在响应用户之前执行一些额外的应用程序逻辑。
在本章中,你已经学习了如何使用评估来提高你智能应用程序的质量。使用无参考评估作为安全带让你可以扩展评估的效用,使其成为应用程序本身的一个组件。
摘要
在本章中,你探讨了评估你智能应用程序中LLM输出的方法。你学习了LLM评估是什么以及为什么它对你的智能应用程序很重要。模型基准测试是一种评估形式,可以帮助你确定在你的应用程序中使用哪些LLM。
一旦你的应用程序拥有功能性的AI模块,你就可以创建评估数据集并在其上运行指标来衡量性能和随时间的变化。除了自动评估之外,你还可以进行人工审查以进一步衡量应用程序的质量。最后,你可以在应用程序中使用无参考指标作为安全带。
在下一章中,你将学习如何优化语义数据模型以增强检索准确性和整体性能。
第十章:精炼语义数据模型以提高准确性
为了在智能应用中有效地使用向量搜索进行语义长期记忆,你必须优化语义数据模型以满足应用的需求。由于语义数据模型使用向量嵌入模型和向量搜索,你必须优化嵌入数据的内容以及数据检索的方式。
精炼语义数据模型可以提高检索准确性和整体应用性能。在检索增强生成(RAG)应用中,一个有效的语义数据模型是构建强大检索系统的基石,它直接影响到生成输出的质量。本章的其余部分将探讨你可以如何精炼语义数据模型和检索的不同方法。
本章将涵盖以下主题:
-
尝试不同的嵌入模型
-
微调嵌入模型
-
在嵌入内容中包含元数据以最大化语义相关性
-
优化RAG用例的各种技术,包括查询变异、格式化导入数据以及高级检索系统
技术要求
运行本章中的代码需要以下技术要求:
-
安装了Python 3.x的编程环境
-
能够在本地运行开源嵌入模型
gte-base-en-v1.5的编程环境 -
OpenAI API密钥。要创建API密钥,请参阅OpenAI文档中的https://platform.openai.com/docs/quickstart/step-2-set-up-your-api-key
嵌入
向量嵌入是语义数据模型的基础,作为想法和关系的机器可解释表示。嵌入是多维空间中对象的数学表示,作为连接智能应用中各种语义数据片段的粘合剂。向量之间的距离与语义相似性相关。你可以使用这个语义相似性分数来检索其他难以连接的相关信息。这个概念无论在特定用例中是否成立,无论是RAG、推荐系统、异常检测还是其他,都是正确的。
拥有一个更适合特定用例的嵌入模型可以提高准确性和性能。通过尝试不同的嵌入模型并在特定领域的数据上进行微调,可以帮助确定特定用例的最佳匹配,从而进一步增强其有效性。
尝试不同的嵌入模型
在构建智能应用时,你可以尝试不同的预训练嵌入模型。不同的模型在准确性、成本和效率方面有所不同。它们的性能会根据具体的应用和数据而显著变化。通过尝试多个模型,开发者可以确定最适合其用例的模型。
表10.1列出了截至2024年春季的一些流行嵌入模型,这些模型来自Hugging Face的大规模测试嵌入基准(MTEB)排行榜1
1 MTEB排行榜上的信息是在2024年4月30日采集的。(https://huggingface.co/spaces/mteb/leaderboard)
| 模型名称 | 开发者 | 是否开源 | 嵌入长度 | 平均分数22 此分数是各种基准的平均值。有关基准中使用的评估指标的更多信息,请参阅MTEB:大规模文本嵌入基准研究论文(https://arxiv.org/abs/2210.07316)。 |
|---|---|---|---|---|
text-embedding-3-large |
OpenAI | 否 | 3072 | 64.59 |
cohere-embed-english-v3.0 |
Cohere | 否 | 1024 | 64.47 |
gte-base-en-v1.5 |
阿里巴巴 | 是 | 768 | 64.11 |
sentence-t5-large |
Sentence Transformers | 是 | 768 | 57.06 |
表10.1:选定的嵌入模型
要正确比较不同的嵌入模型,你必须有一个一致的评估框架。这包括定义一组相关的评估数据集和指标。为了公平比较,所有模型应使用相同的评估集和指标。评估数据集应代表相关应用领域。评估框架将帮助你在时间上迭代和改进评估过程,结合初始实验的学习,逐步提高应用。
以下是一些用于使用嵌入模型进行信息检索的有用评估指标。这些指标来自Ragas,一个用于RAG评估的框架:
-
上下文精确度:评估检索到的结果是否包含您希望回答输入查询的地面事实。上下文中存在的相关项目在检索结果中排名较高。
-
上下文实体召回:评估一组地面真相中的实体在检索信息中预设的比例。
Ragas还支持其他RAG评估指标,你可以在Ragas文档(https://docs.ragas.io/en/stable/)中了解更多信息。
以下代码示例使用Ragas和LangChain评估不同嵌入模型在上下文实体召回指标上的表现。
首先,在终端中安装所需的依赖项:
pip3 install ragas==0.1.13 datasets==2.20.0 langchain==0.2.12 openai==1.39.0 faiss-cpu==1.8.0.post1
以下代码评估了OpenAI的text-embedding-ada-002和text-embedding-3-large嵌入模型在样本数据集上的Ragas上下文实体召回评估表现:
from ragas.metrics import context_entity_recall
from ragas import evaluate, RunConfig
from datasets import load_dataset, Dataset
from langchain_openai import ChatOpenAI, OpenAIEmbeddings
from langchain_text_splitters import RecursiveCharacterTextSplitter
from langchain_community.vectorstores import FAISS
import os
from typing import List
# Add your OpenAI API key to the environment variables
openai_api_key = os.getenv("OPENAI_API_KEY")
# Load sample dataset.
dataset = load_dataset("explodinggradients/amnesty_qa", split="eval")
sample_size = 100
# Get sample questions from the sample dataset.
sample_questions = dataset['question'][:sample_size]
# Get sample context information from the sample dataset.
sample_contexts = [item for row in dataset["contexts"]
[:sample_size] for item in row]
sample_ground_truths = [item for row in dataset["ground_truths"]
[:sample_size] for item in row]
# Break sample context into chunks to use with vector search.
text_splitter = RecursiveCharacterTextSplitter(
chunk_size=400, chunk_overlap=100, add_start_index=True
)
chunks: List[str] = []
for context in sample_contexts:
split_chunks = text_splitter.split_text(context)
chunks.extend(split_chunks)
# Embedding models that we are evaluating.
openai_embedding_models = ["text-embedding-ada-002", "text-embedding-3-large"]
# Ragas evaluation config to use in all evaluations.
ragas_run_config = RunConfig(max_workers=4, max_wait=180)
# #Evaluate each embedding model
for embedding_model in openai_embedding_models:
# Create an in-memory vector store for the evaluation.
db = FAISS.from_texts(
chunks, OpenAIEmbeddings(openai_api_key=openai_api_key, model=embedding_model))
# Get retrieved context using similarity search.
retrieval_contexts: List[str] = []
for question in sample_questions:
search_results = db.similarity_search(question)
retrieval_contexts.append(list(map(
lambda result: result.page_content, search_results)))
# Run evaluation for context relevancy of retrieved information.
result = evaluate(
dataset=Dataset.from_dict({
"question": sample_questions,
"contexts": retrieval_contexts,
"ground_truth": sample_ground_truths
}),
metrics=[context_entity_recall],
run_config=ragas_run_config,
raise_exceptions=False,
llm=ChatOpenAI(openai_api_key=openai_api_key, model_name="gpt-4o-mini")
)
# Print out results
print(f"Results for embedding model '{embedding_model}':")
print(result)
此代码将以下结果输出到终端:
Results for embedding model 'text-embedding-ada-002': {'context_entity_recall': 0.5687}
Results for embedding model 'text-embedding-3-large': {'context_entity_recall': 0.5973}
如您从这些结果中可以看到,text-embedding-3-large 在这次评估中产生了更高的上下文实体召回率。上下文相关性分数在 0 和 1 之间归一化。
当为您的应用程序创建评估时,请考虑使用与您的用例相关的样本数据以获得更好的比较。此外,您可能还想包括至少 100 个示例的代表样本。
微调嵌入模型
除了尝试不同的预训练模型外,您还可以微调预训练的嵌入模型以优化它以适应您的用例。
在以下场景中微调嵌入模型可能有益:
-
特定领域的数据:如果应用程序处理可能无法使用现成模型很好地捕捉的特定领域数据,例如带有专业术语的法律文件或医疗记录,微调可以帮助模型更好地理解和表示特定领域概念。
-
避免不希望的匹配:在存在看似相似但应区分的概念的情况下,微调可以帮助模型区分它们。例如,您可以微调模型以区分 苹果公司 和 苹果 水果。
然而,现成的嵌入模型对于许多任务来说通常表现得很出色,尤其是在结合本章后面讨论的元数据丰富化和 RAG 优化之后。
微调嵌入模型的可选方法可能因模型及其托管方式而异。托管模型提供商可能只公开其模型的一些方法,而使用开源模型可以提供更多灵活性。SentenceTransformers (https://sbert.net/) 框架旨在用于使用和微调开源嵌入模型。
通常,微调涉及提供相似的句子对,可选地包括相似度的大小。或者,可以提供锚点、正面和负面示例来指导微调过程。表 10.2 提供了锚点、正面和负面示例的概述,这些示例在随后的代码示例中使用:
| 类型 | 定义 | 示例 |
|---|---|---|
| 锚点 | 作为识别相似和不同示例起点的参考文本。 |
"I love eating apples."
|
| 正面 | 应该类似于锚示例表示的文本。 |
|---|
"Apples are my favorite fruit."
|
| 负面 | 应该表示为与锚示例不同或不相似的文本。 |
|---|
"Apple is a tech company."
|
表 10.2:微调嵌入模型的方法
下面是一个使用 SentenceTransformers 和 PyTorch 库微调开源嵌入模型 gte-base-en-v1.5 的简短代码示例。
首先,在终端中安装依赖项:
pip3 install sentence-transformers==3.0.1 torch==2.2.2
然后运行以下代码:
from sentence_transformers import SentenceTransformer, InputExample, losses, util
from torch.utils.data import DataLoader
# Load embedding model
model = SentenceTransformer("Alibaba-NLP/gte-base-en-v1.5", trust_remote_code=True)
# Function to print similarity score
def get_similarity_score():
sentence1 = "I love the taste of fresh apples."
sentence2 = "Apples are rich in vitamins and fiber."
embedding1 = model.encode(sentence1)
embedding2 = model.encode(sentence2)
cosine_score = util.cos_sim(embedding1, embedding2)
score_number = cosine_score.item()
print(f"Cosine similarity between '{sentence1}' and '{sentence2}': {score_number:.4f}")
return cosine_score
# Print similarity score before training
print("Before training:")
similarity_before = get_similarity_score()
train_examples = [
InputExample(texts=["I love eating apples.", "Apples are my favorite fruit", "Apple is a tech company"]),
InputExample(texts=["Chocolate is a sweet treat loved by many.", "I can't resist a good piece of chocolate.", "Chocolate Rain was one of the most popular songs on YouTube from 2007."]),
InputExample(texts=["Ice cream is a refreshing dessert.", "I love trying different ice cream flavors.", "The rapper and actor Ice Cube was wearing a cream colored suit to the VMAs."]),
InputExample(texts=["Salad is a healthy meal option.", "I love a fresh, crisp salad with various vegetables.", "Salad Fingers is a surreal web series created by David Firth."]),
]
train_dataloader = DataLoader(train_examples, shuffle=True, batch_size=8)
train_loss = losses.TripletLoss(model=model)
# fine tune
model.fit(train_objectives=[(train_dataloader, train_loss)], epochs=10)
print("After training:")
similarity_after = get_similarity_score()
similarity_difference = similarity_after - similarity_before
print(f"Change in similarity score: {similarity_difference.item():4f}")
此代码将输出类似以下结果到终端:
Before training:
Cosine similarity between 'I love the taste of fresh apples.' and 'Apples are rich in vitamins and fiber.': 0.4402
[10/10 00:05, Epoch 10/10]
After training:
Cosine similarity between 'I love the taste of fresh apples.' and 'Apples are rich in vitamins and fiber.': 0.4407
Change in similarity score: 0.000540
如此例所示,仅仅进行的小幅微调就增加了相关句子之间的向量相似度。
如果您想了解更多关于微调嵌入模型的信息,Omar Espejel在Hugging Face的Train and Fine-Tune Sentence Transformers Models博客文章(https://huggingface.co/blog/how-to-train-sentence-transformers)是一个很好的起点。这篇文章更详细地探讨了使用与前面代码示例类似的方法来微调嵌入模型。
以下部分讨论了在选择合适的数据模型后,如何通过在文本中嵌入相关元数据来进一步增强语义数据模型。
嵌入元数据
在嵌入内容中包含元数据可以显著提高检索结果的质量,因为它为内容添加了更多的语义意义。元数据为内容创建了一个更丰富、更有意义的语义表示。元数据可以包括诸如内容类型、标签、标题和摘要等描述符。以下表格包含了一些在嵌入内容中包含的有用元数据示例:
| 类型 | 示例 |
|---|---|
| 内容类型 | 文章、食谱、产品评论等 |
| 标签 | “晚餐”,“意大利菜”,“素食” |
| 文档标题 | 烤大蒜番茄意面 |
| 文档摘要 | 一道简单的意面,以烤大蒜和樱桃番茄搭配轻柔的酱汁 |
表10.3:嵌入元数据的实用类型
您还可以包含特定于您应用程序的元数据类型。例如,考虑创建一个RAG聊天机器人,用户可以提出自然语言问题,并获得关于烹饪和食谱的生成答案。
您有以下烤大蒜番茄意面的食谱可以包含在您的食谱数据库中:
# Roasted Garlic and Tomato Pasta
## Ingredients
- 8 oz pasta
- 1 head garlic
- 1 pint cherry tomatoes
- 1/4 cup olive oil
- 1/2 cup fresh basil, chopped
- Salt and pepper
## Instructions
1\. Preheat oven to 400°F (200°C).
2\. Cut the top off the garlic head, drizzle with olive oil, wrap in foil, and roast for 30 minutes.
3\. Roast cherry tomatoes with olive oil, salt, and pepper for 20 minutes until blistered.
4\. Cook pasta according to package instructions. Mix pasta with roasted garlic (squeezed out), tomatoes, and olive oil.
5\. Stir in basil, season with salt and pepper, and serve.
Yield: 4 servings
在为食谱创建向量嵌入时,您可以在食谱文本之前包含以下元数据:
---
contentType: recipe
recipeTitle: Roasted Garlic and Tomato Pasta
keyIngredients: pasta, garlic, tomatoes, olive oil, basil
servings: 4
tags: [dinner, Italian, vegetarian]
summary: A simple pasta dish featuring roasted garlic and cherry tomatoes in a light sauce
---
# Roasted Garlic and Tomato Pasta
## Ingredients
- 8 oz pasta
...other ingredients
## Instructions
1\. Preheat your oven to 400°F (200°C).
...other instructions
Yield: 4 servings
通过将此元数据与嵌入文本一起包含,您为文本赋予了更多的语义意义,使其更有可能被用户查询捕获正确的内。这使得相关用户查询与文本的余弦相似度分数更高。
以下表格显示了使用BAAI/bge-large-en-v1.5嵌入模型在各种查询与带有和不带有元数据的文本之间的余弦相似度分数:
| 查询文本 | 无元数据的文本 相似度分数 | 有元数据的文本 相似度分数 | 元数据相似度 分数提升 |
|---|
|
I have tomatoes, basil and pasta in my fridge. What to make?
0.7141546 |
0.7306514 |
0.016496778 |
|---|
|
simple vegetarian pasta with roasted vegetables
0.71199816 |
0.76754296 |
0.055544794 |
|---|
|
vegetarian italian pasta dinner
0.60327804 |
0.6559261 |
0.052648067 |
|---|
表10.4:比较带有和不带有嵌入元数据的文本向量余弦相似度
如 表 10.4 所示,带有前置元数据的文本对于一系列相关查询具有更高的余弦相似度。这意味着相关内容更有可能被展示并用于 RAG 聊天机器人。
格式化元数据
在包含元数据时,必须考虑其结构以优化处理和解释。应使用易于解析和操作的可读格式,例如 YAML (https://yaml.org/spec/1.2.2/)、JSON (https://www.json.org/json-en.html) 或 TOML (https://toml.io/)。
YAML 通常比其他数据格式(如 JSON)更高效。这意味着使用 YAML 可以节省处理额外标记的计算成本,并且用更少的 分散 标记来表示相同的概念,这些标记可能会稀释大型语言模型(LLM)解释输入并产生高质量输出的能力。YAML 还得到了广泛的应用,因此嵌入模型和 LLM 可以有效地与之协同工作。
以下表格展示了使用 GPT-4 分词器(https://platform.openai.com/tokenizer)对以 YAML 和 JSON 格式表示的相同数据进行比较的标记密度:
| 格式 | 内容 | 标记 计数 |
|---|---|---|
| YAML |
contentType: recipe
recipeTitle: Roasted Garlic and Tomato Pasta
keyIngredients: pasta, garlic, tomatoes, olive oil, basil
servings: 4
tags: [dinner, Italian, vegetarian]
summary: A simple pasta dish featuring roasted garlic and cherry tomatoes in a light sauce
| 60 |
|---|
| JSON |
{
" contentType": "recipe",
" recipeTitle": "Roasted Garlic and Tomato Pasta",
" keyIngredients": "pasta, garlic, tomatoes, olive oil, basil",
" servings": 4,
" tags": [
"dinner",
"Italian",
"vegetarian"
],
" summary": "A simple pasta dish featuring roasted garlic and cherry tomatoes in a light sauce"
}
| 89 |
|---|
表 10.5:比较 YAML 和 JSON 中相同内容的标记长度
如 表 10.5 所示,YAML 相比 JSON 使用了大约三分之二的标记。标记使用的确切差异取决于数据和格式。YAML 通常证明比 JSON 是一个更高效的元数据格式。
如果在附加文本中包含元数据,请考虑将其作为 前文 (https://jekyllrb.com/docs/front-matter/) 包含。在元数据前后使用 ---。
下面是一个前文在 Markdown (https://commonmark.org/help/) 文本之前的例子:
---
foo: bar
letters:
- a
- b
- c
---
# Title
Some **body** text!
前文规范起源于 Jekyll 静态网站构建器 (https://jekyllrb.com/docs/)。它已经广泛应用于各个领域。鉴于其流行度,语言模型和嵌入模型应该能够理解其语义上下文作为文本其余部分的元数据。此外,还有库可以轻松地操作与主文本内容相关的前文,例如 Python 中的 python-frontmatter。
以下代码示例展示了如何向 Markdown 添加前文并打印结果。
首先,在终端中安装 python-frontmatter 包:
pip3 install python-frontmatter==1.1.0
使用 python-frontmatter 库向文本添加前文:
import frontmatter
# Define the text content
text = """# Roasted Garlic and Tomato Pasta
## Ingredients
- 8 oz pasta
- 1 head garlic
- 1 pint cherry tomatoes
- 1/4 cup olive oil
- 1/2 cup fresh basil, chopped
- Salt and pepper
## Instructions
1\. Preheat oven to 400°F (200°C).
2\. Cut the top off the garlic head, drizzle with olive oil, wrap in foil, and roast for 30 minutes.
3\. Roast cherry tomatoes with olive oil, salt, and pepper for 20 minutes until blistered.
4\. Cook pasta according to package instructions. Mix pasta with roasted garlic (squeezed out), tomatoes, and olive oil.
5\. Stir in basil, season with salt and pepper, and serve.
Yield: 4 servings
"""
# Define the dictionary to be added as frontmatter
metadata = {
"contentType": "recipe",
"recipeTitle": "Roasted Garlic and Tomato Pasta",
"keyIngredients": ["pasta", "garlic", "tomatoes", "olive oil", "basil"],
"servings": 4,
"tags": ["dinner", "Italian", "vegetarian"],
"summary": "A simple pasta dish featuring roasted garlic and cherry tomatoes in a light sauce"
}
# Create a frontmatter object with the metadata and content
post = frontmatter.Post(text, **metadata)
print("Text with front matter:")
print(frontmatter.dumps(post))
print("\n------\n")
print("You can also extract the front matter as a dict:")
print(post.metadata)
这将在终端输出以下带有前文的文本:
---
contentType: recipe
keyIngredients: ["pasta", "garlic", "tomatoes", "olive oil", "basil"]recipeTitle: Roasted Garlic and Tomato Pasta
servings: 4
summary: A simple pasta dish featuring roasted garlic and cherry tomatoes in a light sauce
tags: ["dinner", "Italian", "vegetarian"]
---
# Roasted Garlic and Tomato Pasta
## Ingredients
- 8 oz pasta
...other ingredients
## Instructions
1\. Preheat your oven to 400°F (200°C).
...other instructions
Yield: 4 servings
------
You can also extract the front matter as a dict:
{'contentType': 'recipe', 'recipeTitle': 'Roasted Garlic and Tomato Pasta', 'keyIngredients': ['pasta', 'garlic', 'tomatoes', 'olive oil', 'basil'], 'servings': 4, 'tags': ['dinner', 'Italian', 'vegetarian'], 'summary': 'A simple pasta dish featuring roasted garlic and cherry tomatoes in a light sauce'}
前面的例子展示了在语义检索中添加元数据格式(如前文)的有用性。
包含静态元数据
对于某些类型的内容或来源,包含所有文档都相同的静态元数据可能是有益的。这是一种计算成本低廉且易于在文档中一致性地包含元数据的方法。
对于一个食谱聊天机器人,您可以在元数据中包含食谱来源。例如:
contentType: recipe
source: The MongoDB Cooking School Cookbook
这确保了特定类型或特定来源的每一份文档都包含一致的基线元数据。然后,您可以像以下章节中讨论的那样,添加额外的动态元数据,这些元数据对每个特定文档是唯一的。包括静态元数据是提供额外语义上下文的一种低效方式,有助于检索和解释。
以编程方式提取元数据
您可以使用不依赖于AI模型的传统的软件开发技术从内容中提取元数据。
一种方法是从文档中提取标题,这可以通过使用正则表达式(regex)匹配标题模式或通过解析文档的抽象语法树(AST)来识别标题元素来完成。提取并包含标题作为元数据可能是有用的,因为标题通常总结或提供关于该部分内容的概述信息,从而有助于理解语义上下文并提高检索相关性。
从Markdown文档中提取标题可以创建一个包含类似以下元数据的文档:
---
headers:
- text: Vegetable Stir-Fry
level: h1
- text: Ingredients
level: h2
- text: Vegetable Preparation
level: h3
- text: Instructions
level: h2
- text: Cooking the Stir-Fry
level: h3
- text: Serving
level: h3
---
# Vegetable Stir-Fry
A quick and easy stir-fry with fresh veggies and a savory sauce.
## Ingredients
- 2 cups mixed vegetables (e.g., broccoli, carrots, bell peppers)...other ingredients
### Vegetable Preparation
- Wash and chop the vegetables into bite-sized pieces.
...other preparation
## Instructions
### Cooking the Stir-Fry
1\. Heat the sesame oil in a large skillet or wok over high heat.
...other instructions
### Serving
- Serve hot over steamed rice or noodles.
...other instructions
Serves 4
使用LLM生成元数据
您可以使用LLM为您的内容生成元数据。使用LLM生成元数据的潜在用例包括:
-
概括文本
-
从文本中提取关键短语或术语
-
将文本分类到类别中
-
识别文本的情感
-
识别命名实体
在选择LLM用于元数据生成时,您可能可以使用比用于您智能应用程序其他组件的语言模型更小(因此更快、更便宜)的语言模型。
您还可以使用传统的自然语言处理(NLP)技术来提供额外的元数据。例如,计算n-gram可以揭示文本中最频繁出现的术语或短语。其他NLP方法,如词性标注和关键词标注也可以提供有用的元数据。这些方法通常使用小型AI模型。
您可以使用Python NLP库,如NLTK或spaCy来提取元数据。虽然使用这些库通常比使用LLM更高效,但它们通常需要微调,因此除非您的应用程序运行在LLM的计算需求成本或资源受限的规模上,否则不值得使用它们。
以下代码使用OpenAI GPT-4o mini LLM提取元数据。它还使用Pydantic将响应格式化为JSON。
首先,在终端中安装依赖项:
pip3 install openai==1.39.0 pydantic==2.8.2
然后,执行以下代码:
import os
from openai import OpenAI
from pydantic import BaseModel
import json
# Create client to call model
api_key = os.environ["OPENAI_API_KEY"]
client = OpenAI(
api_key=api_key,
)
# Format response structure
class TopicsResult(BaseModel):
topics: list[str]
function_definition = {
"name": "get_topics",
"description": "Extract the key topics from the text",
"parameters": json.loads(TopicsResult.schema_json())
}
response = client.chat.completions.create(
model="gpt-4o-mini",
functions=[function_definition],
function_call={ "name": function_definition["name"] },
messages=[
{
"role": "system",
"content": "Extract key topics from the following text. Include no more than 3 key terms. Format response as a JSON object.",
},
{
"role": "user",
"content": "Eggs, like milk, form a typical food, inasmuch as they contain all the elements, in the right proportion, necessary for the support of the body. Their highly concentrated, nutritive value renders it necessary to use them in combination with other foods rich in starch (bread, potatoes, etc.). In order that the stomach may have enough to act upon, a certain amount of bulk must be furnished."
}
],
)
# Get model results as a dict
content = TopicsResult.model_validate(json.loads(response.choices[0].message.function_call.arguments))
print(f"Topics: {content.topics}")
此代码在终端产生以下类似输出:
Topics: ['eggs', 'milk', 'nutritive value']
正如您在这里看到的,LLMs允许您通过提示工程和最小的技术开销执行许多形式的NLP任务。
在查询嵌入和摄入内容嵌入中包含元数据
除了在将内容摄入向量存储时包含元数据之外,您还可以在搜索查询中使用的内容中包含元数据。通过在查询和检索内容上以类似的方式结构化元数据,您增加了使用向量相似性搜索获得相关匹配的可能性。
您可以使用与本章先前讨论的从数据源提取元数据相同的策略来提取查询的元数据。
例如,假设您正在查询之前提到的食谱聊天机器人。给定用户查询apple pie recipe,您可能希望使用以下查询进行向量搜索:
---
contentType: recipe
keyIngredients: apples, sugar, butter
tags: [dessert, pie]
---
apple pie recipe
如上所述的查询将使匹配具有类似结构嵌入元数据的食谱的可能性更高,如下所示:
---
contentType: recipe
recipeTitle: Classic Apple Pie
keyIngredients: apples, pie crust, sugar, cinnamon, butter
servings: 8
tags: [dessert, baking, American, fruit]
summary: A classic apple pie with a flaky crust and a sweet, cinnamon-spiced apple filling.
---
# Classic Apple Pie
## Ingredients
- 1 premade pie crust
- 1 can apple pie filling
- 1 teaspoon ground cinnamon
- 1 egg, beaten (for egg wash)
## Instructions
1\. Preheat oven to 425°F (220°C). Place the premade crust in a 9-inch pie plate.
2\. Pour the apple pie filling into the crust and sprinkle with cinnamon.
3\. Cover with the top crust, trim and crimp edges, and cut slits for steam. Brush with egg wash.
4\. Bake for 15 minutes, reduce temperature to 350°F (175°C), and bake for another 30-35 minutes until golden brown. Cool before serving.
Yield: 8 servings
在查询中包含结构化元数据可以作为一种语义过滤器,以获得更精确的搜索结果。下一节将探讨其他提高RAG应用中数据模型准确性的技术。
优化检索增强生成
除了通过向量嵌入模型选择和元数据丰富来优化语义数据模型本身之外,还有方法可以进一步精炼和改进RAG应用。本节涵盖了优化RAG管道不同组件和阶段的策略。
优化关键领域包括查询处理、摄入数据的格式化、检索系统配置和应用级别的防护措施。有效地优化这些方面可以提高RAG应用的准确性、相关性和整体性能。
注意
本节涵盖了比第第8章中讨论的在AI应用中实现向量搜索更高级的技术。
查询突变
在原始的RAG方法中,您使用直接的用户输入来创建用于向量搜索的嵌入,可能还辅以本章前面讨论的元数据。然而,您可以通过使用LLM突变用户输入来提高搜索性能。
查询突变的一些流行技术包括:
-
My daughter is allergic to nuts. My son is allergic to dairy. What is a vegetarian dinner I can make for them?由LLM生成的回溯搜索查询可能是Vegetarian dinner recipe without dairy or nuts。 -
西冷牛排食谱,LLM生成的HyDE搜索查询可能是预热您的烤架或烤盘至高温。用纸巾擦干西冷牛排,并慷慨地撒上盐和胡椒。淋上橄榄油,用双手均匀涂抹牛排。将牛排放在热烤架上,每面烤4-5分钟至五成熟,只翻一次面。使用即时读数温度计检查熟度(五成熟为135°F)。将牛排转移到切菜板上,静置5分钟后再顺着纹理切片。搭配您最喜欢的配菜,如烤土豆、烤蔬菜或新鲜沙拉*享用多汁的西冷牛排。 -
纯素食晚餐派对菜单,LLM生成的多个搜索查询可能是纯素食开胃菜、纯素食晚餐主菜和纯素食甜点。
所有这些技术都可以针对您的应用程序领域进行优化。您甚至可以将它们结合起来,或者让LLM选择对特定用户查询最合适的技巧。
然而,在应用程序中引入另一个AI点也带来了挑战。查询突变可能并不总是按预期工作,在某些情况下可能会降低性能。此外,它还引入了另一个需要评估的组件,并产生了额外AI使用的成本。任何LLM查询突变都应该彻底评估,以减轻意外结果。
提取查询元数据以进行预过滤
除了在嵌入元数据部分讨论的执行语义过滤之外,您还可以在执行向量搜索之前对元数据进行编程过滤。这可以让您减少搜索的嵌入数量,仅检查与给定查询相关的总嵌入子集。
选择一个包含适合您应用程序需求的元数据过滤功能的向量数据库非常重要。元数据过滤功能在向量数据库之间差异很大。例如,MongoDB Atlas Vector Search在$vectorSearch聚合管道阶段支持多种预过滤选项。(https://www.mongodb.com/docs/atlas/atlas-vector-search/vector-search-stage/#atlas-vector-search-pre-filter)。在第8章,在AI应用中实现向量搜索,你学习了如何使用Atlas Vector Search Index设置这些预过滤选项。
您可以使用LLM从查询中提取元数据作为过滤器,就像您在嵌入元数据部分讨论的那样从摄取的内容中提取元数据一样。或者,您可以使用启发式方法来确定过滤标准。
例如,假设您正在构建一个烹饪聊天机器人,该机器人对食谱和一般烹饪信息(如某些菜系中流行的香料)的向量数据库执行RAG。如果您用户的查询包含单词recipe,则可以添加一个仅查看向量数据库中食谱项的元数据过滤器。您还可以创建所谓的智能过滤器,这些过滤器使用AI模型(如LLM)来确定要包含的数据子集。
这里是一个LLM函数的代码示例,该函数确定对搜索查询应用哪些过滤器(如果有的话)。它还使用Pydantic将响应格式化为JSON。
以下Python代码使用OpenAI LLM GPT-4o mini从查询中提取主题。它还使用Pydantic将响应格式化为JSON。然后,您可以使用提取的主题作为预过滤器,如第8章中所述,在AI应用程序中实现向量搜索。
首先,在您的终端中安装所需的依赖项:
pip3 install openai==1.39.0 pydantic==2.8.2
然后,运行以下代码:
from openai import OpenAI
from pydantic import BaseModel
import json
from typing import Literal, Optional
# Create client to call model
api_key = os.environ["OPENAI_API_KEY"]
client = OpenAI(
api_key=api_key,
)
# Create classifier
class ContentTopic(BaseModel):
topic: Optional[Literal[
"nutritional_information",
"equipment",
"cooking_technique",
"recipe"
]]
function_definition = {
"name": "classify_topic",
"description": "Extract the key topics from the query",
"parameters": json.loads(ContentTopic.schema_json())
}
# The topic classifier uses few-shot examples to optimize the classification task.
def get_topic(query: str):
response = client.chat.completions.create(
model="gpt-4o-mini",
functions=[function_definition],
function_call={ "name": function_definition["name"] },
temperature=0,
messages=[
{
"role": "system",
"content": """Extract the topic of the following user query about cooking.
Only use the topics present in the content topic classifier function.
If you cannot tell the query topic or it is not about cooking, respond `null`. Output JSON.
You MUST choose one of the given content topic types.
Example 1:
User: "How many grams of sugar are in a banana?"
Assistant: '{"topic": "nutritional_information"}'
Example 2:
User: "What are the ingredients for a classic margarita?"
Assistant: '{"topic": "recipe"}'
Example 3:
User: "What kind of knife is best for chopping vegetables?"
Assistant: '{"topic": "equipment"}'
Example 4:
User: "What is a quick recipe for chicken stir-fry?"
Assistant: '{"topic": "recipe"}'
Example 5:
User: Who is the best soccer player ever?
Assistant: '{"topic": null}'
Example 6:
User: Explain gravity to me
Assistant: '{"topic": null}'""",
},
{
"role": "user",
"content": query
}
],
)
content = ContentTopic.model_validate(json.loads(response.choices[0].message.function_call.arguments))
return content.topic
## Test the classifier
queries = [
"what's a recipe for vegetarian spaghetti?",
"what is the best way to poach an egg?",
"What blender setting should I use to make a fruit smoothie?",
"Can you give me a recipe for chocolate chip cookies?",
"Why is the sky blue?"
]
for query in queries:
print(f"Query: {query}")
print(f"Topic: {get_topic(query)}")
print("---")
class ContentTopic(BaseModel):
topic: Optional[Literal[
"nutritional_information",
"equipment",
"cooking_technique",
"recipe"
]]
这将在您的终端中输出以下内容:
Query: what's a recipe for vegetarian spaghetti?
Topic: recipe
---
Query: what is the best way to poach an egg?
Topic: cooking_technique
---
Query: What blender setting should I use to make a fruit smoothie?
Topic: equipment
---
Query: Can you give me a recipe for chocolate chip cookies?
Topic: recipe
---
Query: Why is the sky blue?
Topic: None
---
通过结合元数据过滤和向量搜索,您的RAG应用程序可以更高效、更准确地搜索。这种方法将搜索空间缩小到最符合上下文的数据,从而产生更精确和有用的结果。
格式化摄取数据
在将数据摄取以创建嵌入时,您必须考虑数据的格式。尽可能标准化数据格式可以导致更一致的结果。
对于较长的文本数据,例如技术文档或报告,您应该以一致的格式格式化摄取和嵌入的数据,该格式在密集的标记格式中包含适当的语义意义。Markdown是一个不错的选择,因为它与基于XML的格式(如HTML或PDF)相比,每个标记的信息密度更高。
例如,查看以下内容的总GPT-4标记器标记计数,该内容以纯文本、Markdown和HTML表示:
| 格式 | 内容 | 标记 计数 |
|---|---|---|
| 纯文本 |
Simple Vegan Soup
Ingredients
- 1 can diced tomatoes
- 1 cup vegetable broth
- 1 cup mixed frozen vegetables
Instructions
1\. In a medium pot, combine the diced tomatoes, vegetable broth, and mixed frozen vegetables.
2\. Bring to a boil, then reduce heat and simmer for 10-15 minutes, or until the vegetables are heated through. Serve hot.
| 81 |
|---|
| Markdown |
# Simple Vegan Soup
## Ingredients
- 1 can diced tomatoes
- 1 cup vegetable broth
- 1 cup mixed frozen vegetables
## Instructions
1\. In a medium pot, combine the diced tomatoes, vegetable broth, and mixed frozen vegetables.
2\. Bring to a boil, then reduce heat and simmer for 10-15 minutes, or until the vegetables are heated through. Serve hot.
| 83 |
|---|
| HTML |
<h1 id="simple-vegan-soup">Simple Vegan Soup</h1>
<h2 id="ingredients">Ingredients</h2>
<ul>
<li>1 can diced tomatoes</li>
<li>1 cup vegetable broth</li>
<li>1 cup mixed frozen vegetables</li>
</ul>
<h2 id="instructions">Instructions</h2>
<ol>
<li>In a medium pot, combine the diced tomatoes, vegetable broth, and mixed frozen vegetables.</li>
<li>Bring to a boil, then reduce heat and simmer for 10-15 minutes, or until the vegetables are heated through. Serve hot.</li>
</ol>
| 138 |
|---|
表10.6:不同文本格式的标记计数
您格式化摄取数据的方式可以对检索质量和资源消耗产生有意义的影响。通常,纯文本或Markdown对于大多数基于文本的使用案例都是有效的格式。
高级检索系统
已经出现了各种高级检索系统,它们不仅限于检索与查询最接近的匹配项。
截至2024年8月撰写时,以下所有检索架构都是实验性的。在开发您的智能应用程序时,您可能应该从标准的向量搜索检索开始。在尝试这些高级检索系统之前,先优化标准的向量搜索检索,然后再使用过滤和添加语义元数据等技术。
高级检索系统包括:
-
摘要检索:从每个文档中提取摘要并将其存储在向量搜索索引中。当匹配到摘要的嵌入版本时,检索整个文档的内容。
-
知识图谱检索:在数据摄取期间,创建向量存储中文档之间关系的知识图谱。这些关系可以使用LLM创建。在检索期间,执行初始语义搜索。
-
路由检索:使用分类器确定用户查询应路由到不同数据存储中的哪个位置。
LlamaIndex在跟踪高级检索系统的最新研究方面做得非常出色。要了解更多关于LlamaIndex支持的各类高级检索模式,请参阅LlamaIndex查询引擎文档(https://docs.llamaindex.ai/en/stable/examples/query_engine/knowledge_graph_rag_query_engine/)。
摘要
在本章中,你探索了各种技术来优化你的语义数据模型,以提高向量搜索和RAG的检索准确性。你学习了如何改进用于信息检索和RAG的数据模型。通过微调嵌入,你可以调整预训练模型以提高搜索结果的准确性和相关性。使用嵌入元数据,你可以提高向量搜索质量。最后,RAG优化确保检索过程获取最相关的信息。
在下一章中,你将探讨解决AI应用开发中常见问题的方法。
第十一章:生成式AI的常见失败
如果你刚刚构建了你的生成式AI(GenAI)应用,那么你可能对它能做什么感到如此着迷,以至于你忽略了答案的质量和准确性。发现GenAI错误发生的频率本身就是一个挑战。
许多人倾向于认为,当计算机给出答案时,它给出的是准确的答案——通常比人类更准确。例如,大多数人感到欣慰,因为现在飞机是由机器而不是人驾驶的。由于这一进步,飞机可能比15年前更安全,但当我们谈到GenAI时,结果远不如飞行器的机载系统准确。
本章详细探讨了与GenAI应用相关的五大挑战及其发生的原因。理解这些挑战对于开发者设计有效的解决方案至关重要。到本章结束时,你将很好地理解这些挑战,它们如何影响你的结果,它们如何相互关联,以及为什么尽管存在这些挑战,这一特定技术集对用户仍然非常有价值。
本章将涵盖以下主题:
-
幻觉
-
奄媚
-
数据泄露
-
成本优化
-
性能问题
技术要求
本章中的大多数示例可以通过简单地重复ChatGPT中的提示或示例来演示。
幻觉
与GenAI一起工作的最大挑战之一,也许是最为人所知的,就是幻觉。在GenAI中,幻觉是指AI模型生成看似合理但实际上错误、无意义或与提供的输入数据不符的内容的现象。这个问题在自然语言处理(NLP)模型中尤为普遍,例如用于文本生成的模型,但也可以出现在其他生成模型中,如图像生成和LLMs(如GPT-4)。
在最坏的情况下,开发者和他们的用户都不知道GenAI给出的答案是正确的、部分正确、大部分不正确,还是完全虚构的。
幻觉的原因
组织捕获的大部分数据要么是冗余的、过时的、微不足道的(ROT),要么根本未分类。在数据湖、仓库和数据库中,良好的数据只占很小一部分,这些是大多数公司拥有的。在开始你的GenAI应用之旅时,你很可能会首先注意到,你想要用于训练GenAI应用的大部分数据质量都很差。不久之后,你就会了解到幻觉是由低质量的训练数据引起的。
工程师可以将此视为垃圾输入,垃圾输出问题。当训练数据有错误、不一致、不相关、过时信息、偏见信息和其他问题时,模型将学会复制这些问题。AI 模型的准确性高度依赖于训练数据的质量,以下数据问题更有可能导致输出问题和幻觉:
-
不准确的数据:输入中的错误会在系统中传播并累积,因此了解任何自动或实时数据流到您的 GenAI 应用程序中都有准确信息至关重要。例如,如果您正在使用传感器数据来预测设备何时会故障,但接收到的传感器读数不准确,那么您的 GenAI 应用程序可能无法正确或及时地预测故障。
-
不完整的数据:在不完整的数据集上进行训练可能导致模型生成看似合理但实际上错误的内容来填补感知到的空白。
-
过时或陈旧的数据:从本质上讲,过时的数据通常只是不再准确,向 AI 提供错误信息。相关数据更新确保您的 GenAI 应用程序继续为用户提供准确输出。
-
无关数据:可能会诱使您尽可能多地填充您的 GenAI 应用程序中的数据,以便它可以利用这些信息进行分析;然而,这是一种增加成本而不提高准确性的方法。
-
误导性或失实的数据:如果机器学习模型是在标签不佳或无法代表现实场景的图像上训练的,那么在部署时它将难以正确识别或分类图像。
-
重复数据:这也包括整合不良的数据集。冗余数据可能会给 AI 一种印象,即某些东西比它实际上更重要,因为它是重复的。
-
模型架构和目标:例如 GPT-4 这样的模型被训练来根据上下文预测序列中的下一个单词,而不一定是验证事实。这个目标可能导致模型生成流畅但事实不准确的文章。
这些原因各自引起略微不同的问题,并且结合在一起可能会使您的 GenAI 应用程序无法产生令人满意的结果。因此,您的训练数据必须准确、全面,并代表模型将在实际应用中遇到的多样化条件。大部分 GenAI 是持续自我学习的,因此维护数据质量是一个持续的问题,而不仅仅是首次部署到生产的问题。
生成模型专注于产生连贯且上下文相关的输出,这有时是以事实准确性为代价的。这些模型在识别和复制数据中的模式方面也非常出色。然而,这可能导致输出遵循学习到的模式,即使这些模式与事实现实不符。这是相关性而非因果关系的问题。
此外,模型是在静态数据集上训练的,缺乏对更新信息的实时访问,这可能导致过时或不正确的输出。例如,GPT及其同类产品是在几个月(甚至几年!)前从网络上抓取的数据上训练的。昨天的产品、见解和世界新闻都不在可用范围内。当询问关于最近事件的问题时,在最好的情况下,用户会收到这样的回答:“我没有这个信息”。在最坏的情况下,通用人工智能应用只是胡编乱造一个回答。生成模型可能不完全理解上下文或缺乏验证生成信息正确性的现实世界知识。
幻觉的后果
除了“错误”和“编造答案”之外,幻觉还可能产生其他意想不到的后果。错误信息可以轻易地传播给成千上万的人,其中一些人可能难以事后纠正。例如,如果今天,ChatGPT(一个流行的通用人工智能模型)开始告诉每个询问的人一个流行的开源项目存在一个关键漏洞,那么这个消息就会像野火一样迅速传播,使得损害控制变得困难。它将影响到比公司在博客上发表的关于信息不真实声明更多的人。许多用户在没有验证的情况下就信任人工智能的输出。
幻觉削弱了人工智能系统的可靠性,尤其是在医疗、法律或金融服务等领域,这些领域准确性至关重要。此外,持续的幻觉可能会侵蚀用户对人工智能应用的信任,导致采用率降低和对人工智能能力的怀疑。
错误信息可能导致道德困境和潜在的法律责任,尤其是如果人工智能的输出影响了关键决策或公众舆论。随着通用人工智能被应用于各种应用中,对于用户来说退出变得越来越困难,同时辨别答案是否合法也越来越困难。
值得注意的是,收到一个不是幻觉的回答与收到最佳回答大相径庭。
媚俗
媚俗者是指那些不惜一切代价赢得你赞同的人,即使是以牺牲他们的道德或对真相的了解为代价。人工智能模型经常表现出这种行为,以至于人工智能研究人员和开发者可以使用相同的术语——媚俗——来描述模型如何以欺骗或问题的方式对人类反馈和提示做出反应。人类反馈通常被用来微调人工智能助手。但人类反馈也可能鼓励模型产生与用户信念相符的响应,而不是真实的响应,这种特性被称为媚俗。媚俗以多种方式存在,例如镜像反馈、容易被影响,以及如果用户反驳,就会改变正确的答案。如果用户分享他们对某个主题的看法和观点,人工智能助手将提供与用户信念相符的答案。
奇谄可以在多个层面上观察到和描述,如下所示:
-
反馈奇谄:当用户对文本表达喜好或厌恶时,AI助手可能会相应地提供更多积极或消极的反馈。
-
摇摆不定:在正确回答问题后,即使原始答案是正确的,AI助手在用户挑战时可能会改变他们的答案。
-
信念一致性:当用户就某个话题分享他们的观点时,AI助手往往会提供与那些信念一致的答案,导致准确性降低。
在测试中,研究人员Mrinank Sharma等人展示了由Claude生成的奇谄答案(https://arxiv.org/abs/2310.13548),如图11.1所示。

图11.1:展示奇谄的示例回答
值得注意的是,在ChatGPT中对相同和类似问题的重复测试并没有产生一致的结果。
奇谄的原因
奇谄的确切原因尚不清楚。这种现象存在于许多LLM中,因为这些模型已被指示接受上下文和参数信息以告知其回答。GenAI应用有一个学习功能,即它们与用户互动越多,就越能了解语法、上下文和提供充分答案。在这样做的时候,应用表现出只能描述为取悦他人的行为,导致它们偏离了纯粹的事实信息传递。
在上述研究中,发现奇谄是类似于RLHF的对齐训练的副作用。从人类反馈中进行强化学习(RLHF)是一种用于训练LLM以使代理或机器与人类偏好对齐的技术。这在语言模型等领域尤为重要。为了说明这一点,让我们看看一些例子以及为什么这很重要。
考虑以下内容:
当你问候同事时,你可能会说“你好,先生/女士”,“你好”,“早上好”,“好日子”,“嗨”,“怎么了”,“问候”,或许多其他可能的问候语。假设所有这些都是合适的,但人类对哪种更合适有一定的偏好。
为了进一步理解这一点,让我们从文化偏好开始。在某些文化中,如果你不包含同事的名字,比如“早上好,史密斯先生”,这确实会令人震惊。然而,在其他文化中,以这种方式称呼某人会显得极其奇怪。人类对哪种问候方式更受欢迎的偏好有一定的依据,其中一部分是文化因素,一部分是情境和语境(史密斯先生是总裁吗?他是你20岁的应届毕业生吗?),还有一部分纯粹是你个人的偏好。
工程师决定,当人们与GenAI互动时,他们更喜欢他们的对话和互动感觉像人。为了做到这一点,机器必须考虑文化、情境、行为,以及在某种程度上个人的偏好。
训练模型可以访问大量信息,包括上下文信息(来自网站、书籍、研究等的文本段落)和参数信息(最近邻词的嵌入)。它们将使用用户提供的任何文化、上下文或行为线索来帮助提供答案。也就是说,用户提问的方式会影响答案。
ChatGPT确认了这一点。当被问及它是如何得出答案时,它明确地陈述如下:
I assess the context of your question. For instance, if you've mentioned the setting (formal or informal), the relationship with the coworker, or any specific preferences, I take those into account.
If we've interacted before, I consider any speech patterns or preferences you've shown in previous conversations. This helps tailor the response to your style and needs.
I use general knowledge about cultural and social norms to gauge what might be most appropriate. For example, formal greetings are more suitable in professional settings, while casual greetings work better in relaxed environments.
可以请求生成式人工智能(GenAI)在回答问题之前忽略你之前的互动、个人偏好、语法和/或它之前关于你的任何结论,但当然,这要求用户首先知道这种情况正在发生。
谄媚的影响
尽管这个功能很有帮助,但它对GenAI应用的输出在现实世界中有着实际的影响。在前面章节中引用的同一篇研究论文(https://arxiv.org/abs/2310.13548)中,确定谄媚的后果,虽然起源于机器,可能导致对用户观点的不正确顺从、传播用户创建的错误和有偏见的回答。因此,GenAI并没有帮助创造一个更加事实和一致的世界理解,反而持续并可能加速了错误信息的传播。
谷歌DeepMind的研究人员发现,随着模型变得更大,问题变得更加严重(https://www.newscientist.com/article/2386915-ai-chatbots-become-more-sycophantic-as-they-get-more-advanced/)。具有更多参数输入的LLM更有可能同意客观上错误的陈述,而不是较小的LLM。这种倾向甚至适用于数学方程式,即只有一个正确答案的问题。
LLM们不断学习、进化,并且被它们的创造者不断改进。在未来,也许LLM会更高地评价陈述的客观真实性,而不是用户的观点或偏好,但截至2023年,这还没有发生。持续的研究和测试将使它们在平衡用户期望、用户观点和事实方面变得更加熟练。然而,截至本书撰写时,谄媚仍然是GenAI应用的主要担忧,尤其是在输出在生成响应之前考虑用户观点和偏好的情况下。使用合成数据和重新训练模型进行的进一步测试将谄媚的倾向减少了高达10%,但这仍然不是100%(https://arxiv.org/abs/2308.03958)。这意味着即使经过相当大的微调修改,这种倾向仍然存在。
数据泄露
在生成式人工智能(GenAI)的背景下,数据泄露指的是使用来自期望训练数据集之外的信息来创建模型的情况,这可能导致过度乐观的性能指标,并可能产生有缺陷或误导性的预测。这种情况可能发生在模型开发的各个阶段,从数据收集到模型评估,可能会严重损害人工智能系统的有效性。存在多种类型的数据集,它们有不同的用途:
-
用于训练LLM的训练数据集
-
用于改进LLM响应和减少幻觉的微调数据集
-
用于评估响应准确性的评估数据集
数据泄露的原因
数据泄露的原因简单明了,且易于避免,只要这些应用的开发者了解这些原因。首先,让我们从高层次上了解导致数据泄露的原因:
-
不适当的dataset重叠:每个数据集应适用于适当的训练和评估阶段。当这种情况不成立时,就会发生数据泄露。例如,当训练数据集与评估数据集重叠时,GenAI应用在测试期间的表现自然会更好,因为它们已经知道了确切的答案。在这种情况下,您的股价预测应用在其训练和评估数据集中会有重复的历史数据点;因此,在测试其输出性能时,其表现将不切实际地高,因为它已经看到了答案。
-
未来信息:每个数据集应仅包含预测时可能可用的信息。例如,您不会在训练数据集中包含来自未来某个时期的真实或假设信息,或模型在生产中通常无法访问的数据。
-
数据归一化和转换努力:当转换或特征工程步骤意外地引入来自训练集之外的数据时,信息可能会从评估数据集泄露到训练过程中。对于GenAI,您希望训练数据尽可能接近现实生活,无论是从用户交互还是应用将运行在其内的任何上下文来看,这样您的应用就有真正代表性的数据。
为了说明这些原因,让我们使用一个假设的GenAI应用,该应用根据请求使用历史数据预测股价。在这种情况下,现在是2024年5月,您的应用处于最终测试阶段。在推向生产之前,您想确定其预测的准确性。您首先检查应用对以下用户请求的响应。
用户请求:
Predict the average stock price for $TSLA in May 2024.
输出答案:
The average stock price for $TSLA in May 2024 is expected to be $176.
在这个例子中,请注意以下内容:
-
模型输入的训练数据不应包含任何来自2024年5月的数据点。
-
评估数据集应包括2024年5月的所有价格,并可能包括平均股价的实际计算值。这是因为您希望将模型的估计值与实际值进行比较,然后对其进行评分,然后按月绘制,以查看该应用程序是否始终做出低或高的估计。
如果您试图在2024年5月的估计中追求准确性,但您已经在训练阶段提供了2024年5月的数据,这将被视为不适当的训练数据集重叠。让我们看看另一个例子。
用户请求:
Predict the average annual price for $TSLA in 2024.
输出答案:
The average annual stock price for $TSLA in May 2024 is predicted to be $205.
您不会提供一个已经包含年度平均值的训练数据集,因为该信息尚未可用。虽然您可以在训练数据集中包含截至当年的平均数,但不应该包含带有合成或生成的前瞻性数据的年度平均值。如果您创建了一个估计的年度股价并将其包含在训练数据中,那么您就是在使用未来信息。现在,让我们考虑一个最终的例子。
用户请求:
What is the average stock price for $TSLA in May 2024?
输出答案:
The average stock price for $TSLA in May 2024 is expected to be $176.
注意,与第一个例子相比,这里用户查询的措辞有所不同,尽管它导致了相同的答案。LLMs在推断用户意图方面非常擅长。记住,即使是相当简单的问题,用户也会用许多不同的方式表达(估计、预测、预测、想象、猜测和预测都是他们可能使用的词语)。
对于您的训练数据集,您可能包括一个针对整个支持数据库的提示-答案配对,其风格类似于常见问题解答(FAQs)。然而,请抵制纠正诸如措辞和拼写等方面的诱惑。虽然您希望意识到“垃圾输入,垃圾输出”的问题,但您不希望过度保护您的GenAI应用,以至于它不知道如何应对用户不可避免地输入垃圾的情况。这对于GenAI聊天机器人尤其相关。用户有无数种提问的方式。这些问题通常没有适当的语法、术语或上下文意识,他们的知识也可能过时。数据归一化和转换的努力不应使您的训练数据变得不那么有用。
数据泄露的影响
数据泄露的影响因情况而异,取决于您是否泄露了泪滴或瀑布。如果有数据泄露,那么您在生产前的GenAI评估和测试结果将是错误的,并且不能代表您应用程序的实际性能,导致过度乐观的测试或误导性的结论。在所有数据重叠的情况下,训练和测试数据集重叠的最明显后果是,模型可能会学会简单地记忆训练数据,并在任何必须从中做出预测的新数据上表现不佳。
这可能会给应用程序开发人员和测试人员一种对模型性能的虚假自信。后来,当提供真实世界数据并且用户在生产环境中提出问题时,应用程序的表现将明显变差。
避免数据泄露很简单,它从将你的数据集分割成不同的实体开始,然后执行以下操作:
-
确保训练、验证和测试数据集严格分离。使用时间分割等技术来防止未来信息泄露到训练集中。
-
使用工具确保数据转换仅在模型训练期间应用于训练集,并在评估期间独立应用于测试集。
-
以一种防止未来数据被使用的方式来设计特征。避免将未来值或聚合的未来统计数据作为训练数据的一部分。
回到股价预测应用程序,理想情况下,你希望训练集和测试集中的数据基于时间,确保训练集中的股价按时间顺序发生在测试集中的股价之前。然后,你的应用程序将只具有在预测股价点之前的历史股价数据中使用的特征,这标志着真实历史股价和预测未来股价之间的明确界限。接下来,为了验证你的应用程序,使用基于时间的交叉验证来确保模型性能是在模拟真实世界预测场景或应用程序允许的场景的数据上评估的。
通过严格管理模型开发过程中数据的处理方式,你可以最大限度地降低数据泄露的风险,并确保你的GenAI模型提供可靠和有效的预测。
成本
由于有如此多不同的、复杂的、可能昂贵的组成部分,工程师了解他们的GenAI应用程序的成本以及如何控制这些成本至关重要。虽然你将在第12章“纠正和优化你的生成式AI应用程序”中学习更多关于成本优化策略,但本节将作为了解GenAI应用程序财务成本的一个介绍,这些成本在某些方面与Web开发应用程序不同。
成本类型
当使用GenAI时,成本可能来自几个不同的领域。这些成本可以大致分为计算、存储、数据获取、开发和维护成本:
-
训练成本:训练GenAI模型需要大量的计算资源。这对于像GPT-4这样的大型模型尤其如此。这些资源通常包括图形处理单元(GPU)或张量处理单元(TPU),它们针对并行处理任务进行了优化。支持这些设置的基础设施消耗大量电力,并需要冷却系统以维持运行温度。大多数工程师可能无法承担这些费用,而是会利用来自供应商的模型,例如OpenAI、Anthropic、Google、Meta或其他。
-
推理,或实时计算:从训练好的模型生成响应或输出,这被称为推理,也会产生计算成本,特别是对于需要提供实时答案的模型。更大的模型成本更高。
-
存储成本:存储用于训练GenAI模型的庞大数据集会产生成本。这包括原始数据、预处理数据、用户交互数据、可观察性数据以及模型本身。
-
数据收集:获取高质量数据集可能很昂贵。这可能包括从第三方提供商购买数据或生成专有数据集。
-
数据标注和清洗:预处理数据以确保其适合训练涉及成本。这可能包括支付人工标注员对数据进行标注或开发算法以清理和准备数据作为训练或评估数据集。
-
软件开发:编写和维护用于训练和部署GenAI应用的代码库需要熟练的工程师和数据分析师。
-
实验和测试:开发GenAI通常涉及广泛的实验和微调,这需要时间和资源。
-
数据更新:训练和评估数据集需要定期更新以保持其准确性和相关性,这涉及额外的计算和人力资源。
-
监控和支持:持续监控AI系统以确保其正确运行并处理出现的问题涉及运营成本。
-
合规性和安全性:确保数据隐私和安全以及遵守法规(如GDPR)涉及额外的成本。
这不是一个详尽的列表。因此,估计您预期的成本是复杂且非同寻常的任务。但让我们专注于最重要的成本驱动因素,即文本,因此是代币。接下来,您将学习如何在此处估计和控制成本。
代币
LLMs处理文本使用 tokens,这是在文本集中发现的常见字符序列。代币是GenAI应用的货币。每个用户输入和输出都是一个token,问题和响应的token计数都可以控制。每个token的成本极小。GenAI供应商寻求通过每笔交易赚钱,这可能会迅速累积。
让我们用一个例子来理解这个概念。语句Hello how are you是5个token。一个有用的经验法则是,一个token通常对应于约4个字符的普通英文文本。这相当于大约3/4个单词(因此,100个token约等于75个单词)。Hello how are you这个例子包括空格在内的字符数为18个,因此18/4 = 4.5,所以是5个token。
GenAI应用程序的每个输入和输出都被简化为这个简单的度量单位。
ChatGPT和其他LLM都有token限制,这限制了用户可以输入作为提示的文本量以及输出响应的限制。然而,这些限制是相当宽裕的。对于大多数用例,普通消费者不太可能达到这些限制。
例如,GPT-4每次交互的token限制为32,768个,估计的字数为25,000个单词,而Claude 3(Anthropic托管的大型语言模型)在撰写本文时token限制为100,000+。对于一个简单的客户服务聊天机器人,您不太可能达到这个限制,但这是可能的。让我们看看两个例子来解释这可能是如何成立的。
示例 1:一位客户向GenAI聊天机器人提出一个简单的问题。
询问:
what is the latest version of python
响应:
The latest stable version of Python is 3.12.4, released on June 6, 2024\. This release is part of the Python 3.12 series, which includes many new features and optimizations. Key improvements in this version include enhanced f-string parsing, support for isolated subinterpreters with separate Global Interpreter Locks, and numerous performance enhancements (Python.org) (Python.org).
Additionally, Python 3.13 is currently in development, with the first beta release, 3.13.0b1, made available in May 2024\. This upcoming version introduces new features such as an improved interactive interpreter, experimental JIT support, and an incremental garbage collector (Python Insider). For more details, you can visit the official Python website here.
初始询问为36个字符和7个token。响应为162个token,或741个字符。如果您将交互限制在300个token以内,您仍然远远低于您为用户允许的限制。
示例 2:一个服务器遇到了内存不足错误,GenAI自动分析堆栈跟踪并记录分析供人类稍后审查。
询问:
serverId.nodeId.processID reports error for analysis.
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
at java.util.HashMap.resize(HashMap.java:704)
at java.util.HashMap.putVal(HashMap.java:663)
at java.util.HashMap.put(HashMap.java:612)
at com.example.server.RequestHandler.handleRequest(RequestHandler.java:45)
at com.example.server.Server.processRequest(Server.java:78)
at com.example.server.Server.run(Server.java:61)
at java.lang.Thread.run(Thread.java:834)
2024-06-18 14:32:21.456 ERROR [Server] - Server encountered an unrecoverable error and will shut down.
java.lang.OutOfMemoryError: Java heap space
at java.util.Arrays.copyOf(Arrays.java:3332)
at java.lang.AbstractStringBuilder.ensureCapacityInternal(AbstractStringBuilder.java:124)
at java.lang.AbstractStringBuilder.append(AbstractStringBuilder.java:674)
at java.lang.StringBuilder.append(StringBuilder.java:214)
at com.example.server.Logger.log(Logger.java:27)
at com.example.server.RequestHandler.handleRequest(RequestHandler.java:47)
at com.example.server.Server.processRequest(Server.java:78)
at com.example.server.Server.run(Server.java:61)
at java.lang.Thread.run(Thread.java:834)
响应:
Exception: The OutOfMemoryError exception indicates that the Java Virtual Machine (JVM) has run out of memory.
Error Message: Java heap space specifies that the heap memory allocated for the JVM is exhausted.
Method Calls: The trace shows the sequence of method calls that led to the error.
The error starts in the HashMap.resize method.
The putVal and put methods of HashMap are called.
These methods are used in RequestHandler.handleRequest.
Server.processRequest and Server.run handle the processing and running of the server, respectively.
The Thread.run method is the entry point of the thread that encountered the error.
Timestamp: The timestamp (2024-06-18 14:32:21.456) provides the exact time of the error occurrence.
Logger: The logger in Logger.log attempted to log the error but also encountered the same OutOfMemoryError.
Causes and Solutions:
Causes: Excessive memory usage due to large data structures. Memory leaks where objects are not properly disposed of. Insufficient heap size allocated to the JVM.
Solutions: Increase the heap size using JVM options (-Xmx for maximum heap size). Optimize the code to use memory more efficiently. Use profiling tools to detect and fix memory leaks.
这个分析器对诊断工程师很有用,因为它可以快速审查堆栈跟踪,总结其发现,并为解决问题提供建议。
然而,提示词长度为275个token(1,240个字符),而响应长度为248个token(1,205个字符)。由于这是一个示例堆栈跟踪,实际应用中的实现可能会包含更详细的真实信息。因此,如果控制设置仍然是输入或输出300个token,那么您已经非常接近这个限制。
为您的应用程序选择输入和输出token限制至关重要。虽然您希望控制成本,但您也不希望从根本上限制功能。如果token限制太低,LLM可能无法生成所需的输出。
生成式AI应用中的性能问题
GenAI最明显的失败是性能和可靠性相关的问题。既然您已经学习了关于准确性的内容第10章,通过优化语义数据模型提高准确性,在本章的上下文中,性能意味着速度慢。如果用户向您的AI应用程序提出问题,并且没有响应、部分响应或响应缓慢,这通常比如果响应是虚构的或谄媚的更为明显。
几个因素可能导致GenAI应用程序的缓慢。GenAI中性能问题最常见的几个原因包括计算负载、网络延迟、模型服务策略以及高输入/输出(I/O)操作。
当然,可能还有更多原因。本节剩余部分将详细解释一些性能杀手,以及它们对您的应用程序和用户的影响。
计算负载
如您所知,LLMs需要大量的计算能力。生成查询响应所需的时间随着模型复杂性和规模的增加而增加。不恰当的请求会显著增加GenAI应用程序的计算负载。让我们看看几个这样的例子,以便您能够理解这种故障模式是如何发生的。
大量的数据处理和计算
需要处理大量数据集或执行大量计算的请求可能会带来计算上的负担,以下是一个例子。
用户请求:
Evaluate a sample of the last 20,000 stock prices for TSLA, sort it from highest to lowest, and let me know on which days and times it had the highest price.
获取20,000条随机股票价格看似简单,但用户没有指定时间范围。模型应该评估最后20,000条股票价格的哪个时间段?是过去一个月?还是去年随机选取的时间段?对这些值的排序计算成本高昂,并进一步增加了返回列表的处理负担。
高复杂度请求
涉及评估大量数据、总结并返回大量结果的复杂请求也很耗费资源。通常,这涉及到通过高级提示技术,如ReACT模式和函数调用,链式调用多个LLM。
推理和行动(ReACT)模式是GenAI模型中用于处理需要多个推理和交互步骤的复杂任务的高级提示技术。这个模式涉及一个序列,其中模型对任务进行推理,生成中间动作,然后产生最终输出。ReACT模式帮助模型将复杂请求分解为可管理的步骤,提高最终响应的准确性和连贯性。
在LLMs的上下文中,函数调用涉及指示模型在其响应生成过程中执行特定的函数或动作。这对于需要结构化输出、计算、数据检索或与外部系统交互的任务特别有用。例如,开发者可以在提示中指定模型可以调用的函数来执行特定任务。这些函数是预定义的,可以处理各种操作,如查询数据库、执行计算或获取外部数据。
让我们通过一个高复杂度的请求来阐述这一点。
用户请求:
Generate a detailed and historically accurate list of the top three priorities for every US president, but do not include their policies related to South America.
在这种情况下,GenAI 必须首先创建每位美国总统的名单,然后寻找每位总统的信息,接着创建他们在任期间政策和事件的详细总结。它还必须检索与总统优先考虑的事项相关的内容,确定哪些内容是最高优先级的共识,整理并总结所有这些信息,然后输出给用户。这是广泛的知识检索、分析和文本生成。很可能会需要多个 LLM 查询,而更多的查询意味着更多的开销。
这些例子说明了某些类型的用户请求如何显著增加 GenAI 应用程序的计算负载。现在让我们看看模型服务策略如何影响 GenAI 的性能。
模型服务策略
根据请求量,为每个请求单独生成响应可能效率低下。如果应用程序没有设计成可以同时处理多个请求,那么随着用户数量的增加,它将变得越来越慢。如果应用程序依赖于云服务,网络延迟会影响性能。客户端和服务器之间慢速的互联网连接或高延迟会导致延迟。频繁或复杂的 API 调用到外部服务会增加响应时间,尤其是如果这些服务正在经历高负载或地理位置遥远。
让我们以股票预测应用程序为例。
由于您的 GenAI 应用程序获得了一些新闻报道,您的网站流量激增,与应用程序互动的客户数量急剧增加。但是,由于您的应用程序逐个处理每个请求并且无法同时处理多个请求,随着系统变得不堪重负,每个用户的响应时间都会增加。用户会体验到更慢的响应时间,从而导致挫败感。
新闻报道来自澳大利亚悉尼的一位有影响力的人物,因此用户激增来自亚洲。您的服务器位于美国东部地区,由于服务器和客户端之间的地理距离,网络延迟导致延迟。互联网连接速度慢的客户体验到的等待时间更长,进一步降低了用户体验。
您的应用程序经常调用外部 API 来获取股票价格和金融市场新闻的实时数据。如果这些外部服务正在经历高负载,API 调用将需要更长的时间才能完成。
高 I/O 操作
不良的数据处理实践,如低效地读取大型数据集或未使用适当的数据结构,可能会降低性能。频繁的磁盘读写操作可能成为瓶颈,同样,数据库交互优化不良和查询格式错误也可能导致问题。示例股价预测应用程序经常读取大量历史股价数据集进行预测。让我们探讨一些数据处理问题,这些问题会导致高I/O操作:
-
该应用程序读取大型数据集效率低下,例如,即使只需要子集,也会将整个数据集加载到内存中,这消耗了过多的内存和计算能力,降低了性能。
-
该应用程序在每次预测周期结束后将中间预测结果和日志保存到磁盘。频繁的磁盘读写操作形成瓶颈,这显著增加了完成每个预测周期所需的时间。
-
在做出预测之前,该应用程序会查询数据库以获取最近的财经新闻和其他相关数据。然而,缺乏索引意味着查询结果缓慢返回。这增加了响应时间,使得应用程序对用户请求的响应速度变慢。
假设你有一个大型数据集,你将想要避免这些做法,因为它们会影响用户体验并增加成本。
摘要
现在你已经通过了这些GenAI挑战,你可以欣赏到这些强大技术所伴随的一些复杂性和细微差别。幻觉、谄媚、数据泄露、成本和性能问题构成了巨大的障碍,需要批判性的眼光和创新解决方案。每个挑战都为GenAI应用程序固有的局限性和潜在陷阱提供了独特的视角。
尽管存在这些障碍,GenAI仍然无可争议地具有价值。它继续改变行业,提高生产力,并为创造力和创新开辟新的途径。通过理解和解决这些挑战,开发者可以充分发挥GenAI的潜力,提供强大、可靠和负责任的应用程序。同时,也要注意,即使应用程序不总是正确,它们也可能是有用的。以ChatGPT为例:它已经极大地提高了数百万用户的效率,尽管其缺陷是众所周知的(而且有些并不容易解决)。你的GenAI应用程序也可能同样有用和受欢迎,但同样存在类似的警告。
在下一章中,你将了解优化你的GenAI应用程序的方法,提高其输出和性能,从而改善用户体验,并解决这里讨论的一些问题。
第十二章:修正和优化您的生成式AI应用
到目前为止,您已经阅读了如何构建生成式AI(GenAI)应用、其各种组件以及它们如何相互配合的内容。您已经对它们如何工作(以及如何不工作)有了坚实的理解。您也意识到了一些GenAI应用的挑战以及如何识别它们。
在本章中,您将开始揭开如何改进您的GenAI应用之谜,一旦您确定了其不足之处。您还将了解优化和微调您的GenAI应用,使其成为一个可靠、有效且稳定的机器,为您服务,而不是一个带来混乱的恶意行为者。
本章将讨论几种提高您的GenAI应用效果的经典技术,以便您对自己的成品充满信心。理想情况下,您将执行所有这些技术。本章将定义这些技术并解释它们如何改进您的应用。然后,您将通过活动完成这些技术的稳健示例。到本章结束时,您将有许多关于如何改进您应用的想法。
本章将涵盖以下主题:
-
基准测试
-
训练和评估数据集
-
少样本提示
-
检索和重排
-
晚期交互策略,包括应用内反馈和用户反馈循环
-
查询重写
-
测试和红队行动
-
信息后处理
技术要求
本章不包含任何编码。然而,它建立在所有前面的章节之上,描述了各种改进和优化您的GenAI应用输出的方法。要重现一些示例,您只需使用您最喜欢的大型语言模型(LLM)提供商并自行尝试即可。本章使用ChatGPT。
基准测试
在GenAI的背景下,基准测试指的是为AI模型定义一个标准或参考输出,以便比较未来的输出。这个标准作为评估模型性能、一致性和随时间改进的关键基准。通过建立基准,开发人员和利益相关者可以客观地衡量AI相对于预定义的预期表现,确保模型达到并维持期望的标准。
在GenAI中,基准测试对于以下几个原因至关重要。首先,它为评估AI模型的质量和性能提供了一个明确的指标。其次,它有助于跟踪模型随时间的进展和改进。最后,基准测试是帮助确保模型输出一致性的工具,通过检测输出变异性来实现。所有这些对于维护AI系统的可靠性和信任至关重要。
可以进行基准测试的AI模型方面众多,且高度依赖于具体的应用及其目标。以下是一些可能进行基准测试的常见元素:
-
准确性:这涉及到衡量模型输出的正确性。例如,在语言模型中,准确性可以通过生成的文本与预期文本的匹配程度或提供正确信息的频率来衡量。
-
响应速度:这指的是模型在接收到输入后生成输出所需的时间。通常,更快的响应时间更受欢迎,尤其是在实时应用中。
-
有效性:这可以衡量AI实现其预期目的的程度。例如,在推荐系统中,有效性可能通过提供的推荐的相关性和个性化来评估。
-
用户满意度:这个主观指标可以通过用户反馈和调查来衡量,反映用户对AI性能和输出的满意度。
在您的当前性能旁边建立基线标准也有助于您——工程师——确定您是否随着时间的推移在提高结果。这种知识对于确保您的应用程序性能没有下降至关重要。在某些行业中,基线性能指标可能需要满足行业或监管标准,并且可能是您应用程序或组织的报告要求。
评估您应用程序的初始性能后,您将希望记录这些结果。随后,确保您在每个训练和更新周期中持续将模型的输出与基线进行比较。全面的文档提供了可以用于比较未来输出并识别模型性能中的趋势或问题的参考。
定期评估模型输出与基线之间的差异也是至关重要的。在随后的训练和更新迭代中,这些评估有助于检测与预期(基线)性能的偏差。如果模型的性能低于基线,这可能表明需要解决的问题,例如数据漂移、用户行为变化或训练数据集问题。
训练和评估数据集
要创建您的基线,您需要创建一个评估数据集。评估数据集是一系列针对您的应用程序提出的问题,以确定它是否满足您已确定的标准。请注意,评估数据集不应与训练数据集混淆,训练数据集是您用于训练模型的数据。评估数据集应是一组完全不同的问题和答案。实际上,训练数据集类似于您提供给学生的笔记和资料,而评估数据集则类似于期末考试。您不希望考试太容易!
训练数据集
正如其名所示,训练数据集是用于教授或训练机器学习模型的 数据集合。它包含输入-输出对,其中输入数据被输入到模型中,模型学习产生正确的输出。这个过程涉及调整模型的参数,以便它能够很好地泛化到新的、未见过的数据。训练数据集的质量和多样性直接影响训练模型的性能和准确性。
高质量训练数据确保模型能够识别模式并做出准确的预测或生成适当的响应。因此,您的训练数据集应代表问题域,涵盖模型在现实世界应用中预期会遇到的各种场景。这有助于减少偏差并提高模型的泛化能力。
训练数据集中的数据类型可能包括以下内容:
-
标记数据:这是监督学习中使用的主要数据类型。每个数据点都包含一个输入和一个相应的正确输出,或标签。例如,在文本分类任务中,标记数据可能包括句子及其相应的类别。
-
未标记数据:在无监督学习中使用,这种数据没有预定义的标签。模型试图在数据中找到模式和结构。例如,聚类算法使用未标记数据将相似的数据点分组在一起。
-
混合数据:半监督学习结合了标记和未标记数据。这种方法利用了大量的未标记数据,同时从较小的标记数据集中受益,以指导学习过程。
-
多样化数据:包括多样化数据确保模型可以处理各种输入。这可能包括不同的语言、方言、格式和上下文。对于某些类型的应用,这可能包括既有人可读的文档又包括代码库的训练数据。
尽管如此,您可能还希望包括补充训练数据。补充训练数据是指用于微调或增强已训练模型性能的附加数据。有许多原因要做这件事,但让我们谈谈其中三个特别有说服力的原因:
-
补充数据可以帮助将通用模型适应特定领域。例如,在通用文本上训练的语言模型可以通过医学文献进行微调,以在医疗应用中表现更好。
-
补充训练数据可以用来增强模型在可能较弱的特定领域的功能。例如,添加更多与金融交易相关的数据可以帮助欺诈检测模型变得更加准确。
-
随着新信息的出现,补充训练数据可以用来更新模型的知识。这对于需要最新信息的应用尤其相关,例如新闻生成或行业快速发展的领域(如技术)。
评估数据集
除了您的训练数据和补充数据之外,您还需要一个评估数据集。评估数据集至关重要,因为它们提供了一种受控和一致的方式来衡量AI模型的表现。它们作为比较的基准,确保模型输出可以客观地与预定义的标准进行比较。通过使用标准数据集,您可以可靠地跟踪改进、识别弱点,并随着时间的推移保持模型的质量。这有助于验证模型不仅在开发阶段表现良好,而且能够有效地推广到新的、未见过的数据。
评估数据集的内容取决于具体的应用及其目标。通常,它应包括以下内容:
-
代表性查询:AI在现实世界使用中可能遇到的各种问题或输入。这些应该涵盖不同的场景和边缘情况,以确保全面的评估。
-
预期输出:对应于每个查询的正确或理想响应,这些响应将用于与AI的响应进行比较。
-
多样化数据:反映模型将面临的各种输入的数据,包括语言、格式和上下文的变化。这有助于评估模型的鲁棒性和处理不同类型输入的能力。
例如,MongoDB文档聊天机器人的评估数据集包括针对前250个搜索术语、前250个按数量排序的支持问题和关于MongoDB的一些最常见问题的问答。这可以采取简单的关键词或完整的句子格式,如下所示:
Mongodb install
Install mongodb ubuntu
Mongodb connection string
$in mongodb
How to create a collection in mongodb
What is an aggregation pipeline
Cannot deserialize a 'String' from BsonType 'ObjectId' in C#
这些术语和问题是从多个来源检索的,具体取决于您的基础设施。对于MongoDB,这些基础设施来自mongodb.com的Google搜索控制台,以及支持聊天、社区论坛和Stack Overflow。
确定合适的评估数据量需要平衡彻底性和实用性。您应该有足够的数据来覆盖广泛的场景,并确保您的GenAI应用的输出始终准确可靠。通常,这涉及数百甚至数千个数据点,具体取决于应用的复杂性。
话虽如此,虽然更多的数据可以提供更全面的评估,但存在一个收益递减的点,即额外的数据不会显著提高评估,但会增加复杂性和资源需求。过度评估也可能导致评估数据集过度拟合,而不是提高整体性能。回到之前的学生/考试类比,你不想你的评估考试是训练材料的精确复制品,因为那时你只是在测试学生是否能够记住一个问题及其回答。你不会测试学生掌握材料的情况。
总结来说,确保高质量的、准确的和全面的训练和评估数据集可以降低模型学习错误模式的可能性。这需要在部署GenAI应用之前投入大量努力,但可以显著提高你的GenAI准确性和响应的深度,以及确保其响应质量。
Few-shot提示
在许多情况下,你会要求你的GenAI应用生成新内容或总结现有内容,在这种情况下,你需要做的是向现有应用提供正确设计的提示。大多数时候,用户简单地请求他们需要的内容就足够了。但在输出复杂的情况下,你会发现使用一种称为few-shot提示的技术可以改善GenAI应用的响应质量和准确性。Few-shot提示是指你将示例作为LLM输入的一部分提供,以便它可以看到你确切需要什么类型的语法和响应。如果你认为输入可能是一个LLM不熟悉的术语,或者你正在使用特定于业务的术语,你还可以在示例中包含一个定义。
让我们用一个GPT-4的例子来试一试。
示例 1:让我们看看LLM对未使用few-shot提示的请求如何响应。
这是用户输入:
Please generate a linkedin post less than 500 words about the company OpenAI.
这里是输出:
🚀 Pioneering the Future with OpenAI 🚀
In the dynamic world of technology, OpenAI stands as a beacon of innovation and progress. Their mission to advance artificial general intelligence (AGI) is not just envisioning the future; they are actively shaping it.
OpenAI operates under the guidance of visionary leaders like CEO Sam Altman. Their cutting-edge research is pushing the boundaries of what's possible in AI, crafting a future where technology enhances human capabilities and opens new realms of possibility.
From healthcare to finance, and education to entertainment, OpenAI's innovations are leaving an indelible mark. Their models, such as GPT-4, are revolutionizing how we interact with technology. Businesses are leveraging these tools to enhance customer experiences, streamline operations, and achieve unprecedented levels of productivity.
OpenAI thrives on collaboration, partnering with organizations worldwide to accelerate innovation. Their open research culture demonstrates a belief in collective progress. By sharing insights, tools, and findings, OpenAI is democratizing access to AI, empowering developers, researchers, and businesses to push the boundaries of what's possible. Through initiatives like the OpenAI Scholars and Fellows programs, they are nurturing the next generation of AI talent. These programs provide mentorship, resources, and opportunities to individuals from diverse backgrounds, ensuring a wealth of perspectives and ideas in AI development.
Join the conversation and be part of the future OpenAI is creating.
#AI #OpenAI #Innovation #Technology #Future #Collaboration #Leadership
示例 2:现在,让我们尝试使用few-shot提示的例子。
这是用户输入:
Please generate a linkedin post focusing on the OpenAI company. It should be less than 500 words in length. Also, please match the tone, syntax, word choices, and style of this passage:"There's a way to do it better. Find it." — Thomas Edison
"Lies. We are being lied to. We are told that technology takes our jobs, reduces our wages, increases inequality, threatens our health, ruins the environment, degrades our society, corrupts our children, impairs our humanity, threatens our future, and is ever on the verge of ruining everything.
We are told to be angry, bitter, and resentful about technology. We are told to be pessimistic. The myth of Prometheus – in various updated forms like Frankenstein, Oppenheimer, and Terminator – haunts our nightmares. We are told to denounce our birthright – our intelligence, our control over nature, our ability to build a better world. We are told to be miserable about the future." –Marc Andreessen
这里是输出:
We are told that artificial intelligence is a threat, that it steals our jobs, that it widens the chasm of inequality, that it's an invisible force eroding our humanity. We are told that AI is an uncontrollable beast, ready to plunge us into dystopia.
OpenAI is here to shatter these myths.
Founded on the principle of advancing digital intelligence for the collective good, OpenAI isn't just another tech company. It's a bold proclamation that the future is ours to shape. OpenAI stands as a beacon against the darkness of misinformation and fearmongering. It's a rallying cry for innovation, for the relentless pursuit of better.
Imagine a world where AI enhances every aspect of our lives—where it amplifies human potential rather than diminishes it. OpenAI's groundbreaking models, like GPT-4, are not instruments of destruction but tools of empowerment. From revolutionizing healthcare to transforming education, from pioneering new frontiers in entertainment to optimizing complex financial systems, OpenAI's innovations are the keystones of a new era.
OpenAI is driven by visionaries who refuse to bow to the pessimism that pervades our discourse. That we are a testament to what can be achieved when we refuse to accept the status quo. We are the architects of tomorrow, wielding AI not as a weapon, but as a bridge.
你可以看到,通过提供示例文本,你可以增强输出以匹配你偏好的任何语法。额外的提示信息也不需要特别困难来生成。如果你能向你的GenAI应用提供示例输出,其结果将更接近你期望的。
检索和重排序
检索和重排序是用于增强LLM性能和准确性的关键技术。首先,要明白通过检索相关上下文或文档,LLM可以提供更准确和上下文相关的响应。这在模型训练数据未涵盖查询的具体内容或需要最新信息时尤其有用。
在 LLM 的背景下,检索可能涉及在大量文档、知识库或其他数据源中进行搜索,以找到与特定查询或任务相关的信息片段。让我们看看两种不同的检索类型:
-
在你的查询中包含
cars,它会返回包含单词 cars 的文档。 -
基于嵌入的检索:这使用向量嵌入来找到匹配的文档。查询和文档都被转换成高维空间中的向量。检索涉及找到接近查询向量的向量(即文档)。
重新排序是重新排列检索到的文档或信息片段的过程,以优先考虑最相关的内容。在初始检索之后,文档根据其与查询的相关性进行排名。检索到的文档最初根据其在嵌入空间中的相似性(例如,使用余弦相似度)与查询进行排名。然而,一个更复杂的模型可以通过考虑额外的特征和上下文来重新排序这些最初检索到的文档。
让我们看看以下示例。
示例 1:使用 GenAI 应用推荐餐厅。
你已经构建了一个 GenAI 应用程序,该程序提供餐厅推荐。用户请求附近的当前营业的餐厅。在检查提供给用户的潜在餐厅时,应用程序会查看用户当前位置或提供的地址与当前本地时间和营业时间之间的距离。
然后,它会根据结果进行排名,使得最近的餐厅首先显示给用户。这是一个完全可行的解决方案。但你可能希望得到更智能的结果,这些结果可以根据其他标准动态重新排序,例如餐厅的用户评分。你可能希望首先显示一个距离三英里远的评分较高的餐厅,而不是一个距离一英里远的评分为一星的餐厅。随着用户对结果的反馈,你可能希望动态重新排序,随着你获得更多关于用户偏好的信息(包括,比如说,菜系或氛围),扩大你的餐厅选择范围。
通过重新排序结果,最相关和有用的信息被优先考虑,从而提高了 LLM 输出的整体质量。它有助于过滤掉不那么相关或冗余的信息,确保响应精确且有用。
当结合检索和重新排序时,可以显著增强 LLM 的输出,具体如下:
-
模型可以访问并利用其训练数据中可能不存在的相关信息,提供更准确和上下文相关的答案。
-
通过通过重新排序来关注最相关的信息,模型的响应变得更加精确,减少了错误和不相关的内容。
-
检索可以从更新的来源中提取最新信息,使模型的响应更加及时。
-
这些技术使模型能够高效地处理特定、详细的查询,而无需频繁重新训练整个模型。
示例 2:总结量子计算的最新研究。
这里还有一个实际例子。假设您询问LLM关于量子计算的最新研究。输出步骤如下:
-
检索:该模型通过搜索大量科学论文和文章的数据库来找到关于量子计算的相关文档。
-
重排序:最初检索到的文档随后被重新排序,最新的和最相关的研究被置于顶部。
-
响应生成:LLM使用排名最高的文档来生成关于量子计算最新研究趋势的详细和准确响应。
通过结合检索和重排序,LLM可以提供信息丰富、最新和上下文准确的答案,极大地改善了用户体验。
后期交互策略
现在您准备将应用程序投入生产,还有一些事情可以做来帮助改善用户体验并创建反馈循环,以便更好地了解您的GenAI应用程序的行为。这些建议的下一组重点在于后期交互策略,有时也称为BERT上的上下文化后期交互(ColBERT)。
首先,让我们定义交互。交互是指通过比较查询和文档的表示来评估查询与文档之间相关性的过程。后期处理策略是指在处理过程中较晚发生查询和文档表示之间的交互,通常是在它们被独立编码之后。早期交互模型是在查询和文档嵌入在早期阶段交互,通常是在模型编码之前或编码期间。
其次,让我们深入了解一下其内部工作原理。当用户与GenAI应用程序交互时,他们输入一个查询,该查询被编码成一个密集向量表示。潜在响应,通常是文档或段落,也被编码成密集向量表示。系统在查询和文档嵌入之间执行相似度匹配,返回相似度得分最高的文档作为最佳匹配。
为了提高相关性,您不会向用户返回所有匹配结果。相反,您旨在提供最相关的结果或结果集的总结版本。ColBERT等后期交互模型通过专注于最有希望的查询-文档对,而不是考虑所有可能的组合,从而提高效率,产生更精确的结果和更好的用户体验。这种选择性的方法可以提供更精确和相关的结果,从而提升用户体验。
如果你需要专注于提高搜索结果,考虑实现ColBERT或类似技术来增强检索性能,并为用户查询提供更相关的结果。
查询重写
查询重写,或查询重构,是一种用于提高LLM(大型语言模型)提供答案质量的技巧。这个过程涉及修改原始查询,使其更清晰、更具体或更详细,这有助于模型生成更好的响应。LLMs不会在后台明确重写查询,因此除非你实现了在处理之前评估和重写用户查询的工作流程,否则这项工作将是手动的。
重写查询可以使它更清晰、更精确,减少歧义,并确保模型确切地理解了被询问的内容。向查询中添加相关上下文或细节可以帮助模型提供更准确和上下文相关的答案,并有助于消除具有多个含义的术语的歧义,确保响应与预期意义一致。此外,重构查询以包含更多相关细节可以导致更全面的答案。
查询重写是如何工作的?了解你的GenAI(生成式人工智能)应用程序的用户意图非常重要。你的应用程序的目的是什么,它将尝试回答哪些类型的问题?了解用户期望得到的响应与你的应用程序可能提供的响应之间的差异是关键。之后,你可以进行以下活动,这些活动不是相互排斥的,这意味着你可以执行一些、一个或全部这些活动。
例如,基于意图,用户查询可以通过添加额外的上下文和细节来增强。这项活动极大地扩展了用户查询(并增加了每个查询的令牌计数),但通常会得到更好的结果。
以一个简单的例子来说明,假设你的应用程序生成图像。用户请求“一张小猫的图片”,这是一个相当简单的查询,可能有无穷无尽的结果。
为了帮助用户获得更好的结果,你可以在用户界面中添加三个按钮,以便用户可以选择“一只小猫的图片”,查询被修改为以下内容:
An image of a kitten, in anime style, large eyes, chikai, chibi-style, pixel-style, anime illustration, cute, in the style of Akira Toriyama.
在这里,对于每种按钮样式,你可以在提交前添加增强用户查询的术语。
作为另一个例子,考虑以下用户查询:
"What's the average revenue?"
有意义的重写可能如下所示:
"What's the average revenue for [May 2024] for [sales sku 123]?"
这个添加了额外上下文的重写查询有助于系统理解用户正在请求特定产品和时间段,从而得到更准确和有用的响应。
最终,在进行查询重写时,你希望简化语言。复杂的查询可以被简化或分解成更简单的部分,使模型更容易处理并准确响应。这种方法涉及将大型查询分解为其组成部分(通常通过一系列输入字段/表单实现),然后将每个数据条目统一为一个提交的查询。这指导用户构建一个没有专业知识的良好格式查询。
例如,假设你的用户只有一个输入字段来输入他们的查询。在这种情况下,他们可能会遗漏相关信息或提供可能影响准确性或增加幻觉可能性的无关信息。相反,如果你为用户提供一系列字段,每个字段都有明确的说明,然后将输入的信息组装成一个查询,该查询被输入到GenAI应用中,你会得到比自由文本输入更好的结果。
对于实际实施,你可以考虑一个工作流程,其中系统本身分析查询的意图和上下文,审查查询的复杂性,然后将查询重写得更清晰、更具体或更详细。重写的查询可以用来生成响应。
测试和红队行动
测试AI系统对于确保它们的准确性、可靠性和整体性能至关重要。通常,在软件工程中,自动化测试被用作软件开发过程的一部分。GenAI应用也不例外。你希望定期和定期测试输出,以确保输出质量没有发生重大变化。
测试
就像你典型的软件工程特性一样,你希望在测试计划中包括单元测试、集成测试、性能测试和用户验收测试的阶段。然而,具体做法因用例而异。
在GenAI应用的背景下,单元测试仍然遵循相同的基本原则,涉及测试应用程序的各个组件或模块以确保它们能正确运行。然而,在GenAI应用的情况下,你的单元测试还需要包括以下步骤:
-
输入验证:确保应用程序正确处理和验证各种输入类型、格式和范围。测试边缘情况,如空输入、过大输入或不规范数据。
-
预处理:验证任何预处理步骤,如分词、归一化或特征提取,是否正确执行。
-
模型加载:测试模型是否正确从其存储位置加载,并验证是否使用了正确的版本。
-
模型推理: 确保模型在给定有效输入的情况下能够生成无错误的输出。通过受控输入测试推理函数以验证预期的行为,例如对于某些提示或场景的确定性响应。
-
输出格式: 验证生成的输出是否符合预期的格式和结构。这包括检查输出是否完整、格式正确,并遵守任何长度或内容限制。
-
后处理: 测试任何修改或增强模型输出的后处理步骤,例如清理文本、转换格式或应用额外的业务逻辑。
-
正常功能: 输出应该正常工作。如果你的 GenAI 应用程序输出代码,你需要测试代码本身是否能够编译并且按照预期行为执行。
这些只是你应该包括在单元测试你的 GenAI 应用程序中的几个项目。
集成测试侧重于验证你的 GenAI 系统的组件是否按需协同工作。这意味着你将测试组件之间的交互,以检查以下内容:
-
是否你的数据摄取管道拉取了正确的数据
-
如何向用户展示推荐(例如,格式化,如果这是由另一个库或工具完成的)
-
如果你使用的是 OpenAI 或 Anthropic 等其他 LLM,进行 API 负载测试。
你将通过性能测试评估处理时间、效率和可扩展性。这可能包括以下活动:
-
测试你的应用程序如何处理大量并发查询的负载。
-
评估自托管模型在不同硬件配置下的推理时间。
-
测量输入和输出应该设置多少令牌限制以控制成本和处理时间。
-
测量模型生成输出所需的时间,并确保它符合性能要求。这对于具有实时约束的应用程序尤为重要。
除了这个常规测试之外,你还需要为你的测试套件添加更多内容。一般来说,也建议 GenAI 应用程序进行以下额外测试:
-
偏差和公平性: 如果你的模型做出的推荐会影响生活和生计,你将需要仔细考虑不同人口群体的训练数据偏差。
-
鲁棒性: 为了确保你的 GenAI 应用程序能够抵抗变化和噪声,你需要使用对抗性示例和边缘情况来测试,以评估其处理意外输入的能力。
一旦你完成了所有这些,你将想要考虑用户验收测试,这是流程中最激动人心的部分,你将在下一节中看到。
红队攻击
如果你的GenAI应用将接受来自人类的自然语言提示和输入,那么强烈推荐进行红队测试。红队测试涉及模拟现实世界、具有挑战性或对抗性的情况,以识别你的GenAI应用中的漏洞和弱点。这种方法借鉴了网络安全实践,对于确保你的GenAI应用满足用户期望尤为重要。
这涉及到拥有一个大量用户池,他们会提出现实世界的问题,但他们不受脚本的限制,不知道可以问什么。进行红队测试的原因是GenAI应用可以,并且经常会产生不同的输出,即使输入相似或相同,输出也会有很大的差异。不仅如此,生成的输出质量往往是主观的,取决于人类判断。因此,虽然传统软件应用会产生可预测和一致的结果,但GenAI并非如此。让我们通过一个例子来看看这是如何工作的。
对于聊天机器人应用,你可能会有常规的自动化测试,它会向你的GenAI应用提出最常问的200个问题,然后评估它们的正确性。使用红队,你会让50个用户提出他们想问的任何问题,并记录下提出的问题和回答。这可能会产生以下见解:
-
如果用户以类似的方式提出问题,但不是用完全相同的措辞,他们就会收到不正确或不那么正确的答案。
-
一些用户会提出恶意问题,而GenAI应用会做出较差的回应。
-
其他用户提出的问题不在训练数据中,GenAI应用会胡编乱造答案(或者根本不回答),从而识别出需要扩展训练数据的必要性。
-
当用户连续提出许多问题时,应用会卡住。
-
当用户提出特定类型的问题时,他们会对输出感到不满意,因为应用缺乏高质量的训练数据,或者回复的格式不理想。
-
当得到适当的提示时,GenAI应用会分享其他用户会话的细节,从而识别出安全问题。
为了启用红队测试阶段,建议你记录下每个用户提出的每个问题以及每个给出的回答,然后要求测试者对回答进行评分并附上备注。虽然这种详细程度的用户测试在软件开发中既费力又罕见,但在产品发布前看到你的应用在现实场景中、与真实人类互动的表现,却具有极高的价值。
由于某些人工智能系统的规模和范围,全面测试每个组件是不可能的。有效的测试和红队行动依赖于对系统哪些部分风险最高的判断。偶尔给出不太准确的建议可能是一个无足轻重的事件。然而,单个幻觉的潜在危害可能相当高。您将需要考虑危害的严重性、不准确性的可能性以及撤回或纠正不准确性的能力,作为您衡量风险的标准措施。使用这些简单但主观的措施可以帮助您确定测试系统每个方面的程度以及您红队的大小。
为了让您对将要测试哪些类型的危害和事件有一个概念——这些事件太多,无法一一列举——您会发现查看https://incidentdatabase.ai/上的AI事件数据库很有帮助。在审查此工具后,您可能会发现您特定的用例(或类似用例)以及已经报告的事件,这样您就可以测试并思考不准确性可能带来的后果。
例如,这里详细描述的一个事件涉及一个提供人员配备级别建议的应用程序。然而,基于算法的建议导致设施人员不足,导致忽视、伤害和死亡的关键事件。这些事件随后引发了针对使用人工智能的医疗保健提供者的诉讼甚至立法。
信息后处理
您可能知道,生成式人工智能与之前的人工智能或分析形式的主要区别在于它能够高效地生成新内容。但您知道这些内容通常是非结构化形式,例如书面文本或图像吗?当您看到格式良好、以项目符号列表、多种字体等形式呈现的输出时,这是一种信息后处理的形式。
信息后处理指的是在人工智能模型生成初步响应之后,但在将响应发送给用户之前所采取的一系列步骤。这一关键步骤增强了生成式人工智能模型的输出,将原始响应精炼得更加有用、准确和符合上下文。它可能采取多种形式,因此本章将仅讨论其中一些最有用的形式,并附带如何实施它们的信息:
-
事实核查:验证提供信息的准确性。这可能涉及将事实与可靠来源或数据库进行核对。
-
格式化:以清晰易读的格式组织信息,例如项目符号、段落或表格。这还可能包括风格变化,如粗体、文字颜色或字体,以增强可读性和强调。
-
语法、风格和语气检查:有时,GenAI应用提供的文本结果不符合标准或与预期的精确信息、语气和风格不一致。后处理工具和供应商可以显著改善生成的文本输出,使其更具可读性,使其符合读者的期望。
信息后处理是GenAI输出生命周期中的一个重要组成部分。它弥合了原始模型输出和经过打磨、用户准备好的响应之间的差距,提高了准确性、可读性、相关性和整体用户满意度。通过实施有效的后处理策略,AI系统可以提供更高质量和更可靠的输出。
围绕GenAI流程中这一宝贵步骤,出现了整个服务,因此工程师不必自己构建它。
其他补救措施
一些其他的技术补救措施甚至比本章详细描述的更容易实施。其中一些可能会提高你的GenAI应用的准确性和性能,尽管所需的努力程度不同。例如,在MongoDB对GPT的测试中,发现相同问题集的准确率在GPT-3.5和GPT-4之间提高了7%。通过提示、检索增强或后期交互策略获得这样的准确率提升是可能的,但会非常困难。
因此,调查所有潜在的改进途径都是值得的,包括硬件升级、代码优化、并发管理、数据库查询优化,甚至仅仅是升级你的软件。所有这些都可以提高你的GenAI应用的结果,并且应该独立进行调查:
-
硬件和软件升级:升级计算资源,例如使用更强大的GPU、通过更多服务器进行横向扩展,或更新到软件的最新版本,以对准确性和性能产生巨大影响。
-
代码优化:重构和优化代码以提高效率,减少计算负载,并更有效地处理数据。
-
网络优化:通过优化数据传输、缓存响应和最小化API调用开销来降低网络延迟。
-
并发管理:实现并发和并行处理技术以高效处理多个请求。
-
数据库优化:优化数据库查询和交互以减少I/O开销。
摘要
实施纠正和优化你的GenAI应用的机制可以有多种形式,可以在生成答案之前、期间和之后实施。为了获得最佳性能,你希望用高质量的数据训练你的GenAI模型,用你的特定用例数据补充现有模型,并拥有详尽的评估数据集,记录模型的表现以建立准确性的基线。
然而,一旦有了这个基准,你就可以立即开始使用本章讨论的技术来改进它。其中一种技术是一或少量提示。这涉及到向GenAI模型提供一个示例或提示来引导其响应,使模型能够在最少训练数据的情况下生成相关且上下文适当的输出。你也可以尝试根据用户的查询检索和重新排序相关文档或数据点,然后在生成最终响应之前重新排序这些结果,以优先显示最相关和有用的信息。查询重写是另一种可以提高清晰度、具体性或上下文的技术,有助于AI模型更准确地理解和响应用户的请求。
通过结构化和以清晰、有序和可读的方式呈现AI生成的内容来格式化GenAI响应可以增强整体用户体验并确保信息易于消化。同样,实施如ColBERT之类的后期交互策略可以提高检索信息的关联性和准确性。通过测试、红队攻击和记录你的结果,你可以跟踪你在提高性能、安全性和响应质量方面的进展。
GenAI技术正在改变(并将继续改变)软件行业的面貌。有了这些优化策略,你的GenAI应用将能够适应并在这个不断变化的环境中脱颖而出。
附录:进一步阅读
除了章节内提供的链接外,这里还有一些资源可以帮助你继续学习之旅。
第1章, 开始使用生成式AI
-
Gryka, Maciej. “Invest in RAG” in “Building reliable systems out of unreliable agents.” The Rainforest Blog, April 3, 2024. https://www.rainforestqa.com/blog/building-reliable-systems-out-of-unreliable-agents#Invest_in_RAG.
-
“The Black Box: Even AI’s creators don’t understand it.” July 2023. Unexplainable. Produced by Vox Creative. Podcast, Spotify, 36:15. https://open.spotify.com/episode/3npjXNCtUSGRUjVR4EYb4Y?si=-XpudYVzSEKfhD0-2NBjEQ.
第2章, 智能应用构建模块
- Naveed et al. “大型语言模型的全面概述。” arXiv,2023年7月12日[https://arxiv.org/abs/2307.06435](https://arxiv.org/abs/2307.06435).
第3章, 大型语言模型
-
“语音和语言处理,” n.d., https://web.stanford.edu/~jurafsky/slp3/.
-
Hochreiter, Sepp,和 Jürgen Schmidhuber。“长短期记忆。” Neural Computation 9,第 8 期(1997年11月1日):1735–80[https://doi.org/10.1162/neco.1997.9.8.1735](https://doi.org/10.1162/neco.1997.9.8.1735).
-
Vaswani, Ashish,Noam Shazeer,Niki Parmar,Jakob Uszkoreit,Llion Jones,Aidan N. Gomez,Lukasz Kaiser,和 Illia Polosukhin。“Attention Is All You Need。” arXiv (Cornell University),2017年1月1日[https://doi.org/10.48550/arxiv.1706.03762](https://doi.org/10.48550/arxiv.1706.03762).
-
“提示工程指南 – Nextra”,未注明日期[https://www.promptingguide.ai/](https://www.promptingguide.ai/).
第四章, 嵌入模型
-
A. Aruna Gladys 和 V. Vetriselvi,“关于情感识别的多模态方法综述”,Neurocomputing 556(2023年11月1日):126693[https://doi.org/10.1016/j.neucom.2023.126693](https://doi.org/10.1016/j.neucom.2023.126693).
-
Sumit Kumar,“语义搜索中表示学习的正负采样策略”,Sumit 的日记,2023年3月22日[https://blog.reachsumit.com/posts/2023/03/pairing-for-representation](https://blog.reachsumit.com/posts/2023/03/pairing-for-representation).
-
Tomas Mikolov 等人,“在向量空间中高效估计词表示”,arXiv.org,2013年1月16日,https://arxiv.org/abs/1301.3781.
-
OpenAI,“GPT-4”。GPT-4 研究,2023年3月14日[https://openai.com/index/gpt-4-research](https://openai.com/index/gpt-4-research).
-
Jeffrey Pennington,“GloVe:全局词向量表示”,未注明日期[https://nlp.stanford.edu/projects/glove](https://nlp.stanford.edu/projects/glove).
-
Jacob Devlin 等人,“BERT:用于语言理解的深度双向变换器预训练”,arXiv.org,2018年10月11日[https://arxiv.org/abs/1810.04805](https://arxiv.org/abs/1810.04805).
-
“fastText”,未注明日期[https://fasttext.cc/](https://fasttext.cc/).
-
Peters, M. E.,Neumann, M.,Iyyer, M.,Gardner, M.,Clark, C.,Lee, K.,和 Zettlemoyer, L.,“深度上下文化词表示”,arXiv:1802.05365,2018年3月22日[https://arxiv.org/pdf/1802.05365](https://arxiv.org/pdf/1802.05365).
-
Karen Simonyan 和 Andrew Zisserman,“用于大规模图像识别的非常深卷积网络”,arXiv.org,2014年9月4日[https://arxiv.org/abs/1409.1556v6](https://arxiv.org/abs/1409.1556v6).
-
Kaiming He 等人,“用于图像识别的深度残差学习”,arXiv.org,2015年12月10日[https://arxiv.org/abs/1512.03385](https://arxiv.org/abs/1512.03385).
-
Aurora Cramer,Ho-Hsiang Wu,Justin Salamon,和 Juan Pablo Bello,“OpenL3 — OpenL3 0.4.2 文档”,未注明日期[https://openl3.readthedocs.io/en/latest/#](https://openl3.readthedocs.io/en/latest/#).
-
“Google | vggish | Kaggle”,未注明日期[https://www.kaggle.com/models/google/vggish](https://www.kaggle.com/models/google/vggish).
-
Tran, D.,Bourdev, L.,Fergus, R.,Torresani, L.,和 Paluri, M.,“使用 3D 卷积网络学习时空特征。”arXiv:1412.0767,2015年10月7日。https://arxiv.org/pdf/1412.0767.
-
Grover, A.,和 Leskovec, J.,“Node2Vec:网络的缩放特征学习方法。”第 22 届 ACM SIGKDD 国际知识发现和数据挖掘会议论文集,2016年。https://cs.stanford.edu/~jure/pubs/node2vec-kdd16.pdf.
-
Bryan Perozzi,Rami Al-Rfou,和 Steven Skiena,“DeepWalk,”2014年8月24日,https://doi.org/10.1145/2623330.2623732.
-
Zhang, S.,和 Xu, Y.,“Json2Vec:JSON 数据的表示学习方法。”arXiv:2002.05707,2020年2月13日。https://arxiv.org/pdf/2002.05707.
-
Alec Radford 等人,“从自然语言监督中学习可迁移的视觉模型,”arXiv.org,2021年2月26日,https://arxiv.org/abs/2103.00020.
第五章,向量数据库
-
Yu. A. Malkov 和 D. A. Yashunin,“使用分层可导航小世界图进行高效且鲁棒的近似最近邻搜索,”arXiv.org,2016年3月30日,http://arxiv.org/abs/1603.09320.
-
Yikun Han,Chunjiang Liu,和 Pengfei Wang,“向量数据库全面综述:存储和检索技术,挑战,”arXiv.org,2023年10月18日,http://arxiv.org/abs/2310.11703.
-
Zhi Jing 等人,“大型语言模型与向量数据库的相遇:综述,”arXiv.org,2024年1月30日,http://arxiv.org/abs/2402.01763.
-
Doug Turnbull,“什么是判断列表?”,Doug Turnbull 的博客,2021年2月21日,https://softwaredoug.com/blog/2021/02/21/what-is-a-judgment-list.
-
“构建基于 RAG 的 LLM 应用程序以用于生产,”Anyscale,未注明日期,https://www.anyscale.com/blog/a-comprehensive-guide-for-building-rag-based-llm-applications-part-1.
-
“如何执行混合搜索 - MongoDB Atlas,”未注明日期,https://www.mongodb.com/docs/atlas/atlas-vector-search/tutorials/reciprocal-rank-fusion/.
-
“审查部署选项 - MongoDB Atlas,”未注明日期,https://www.mongodb.com/docs/atlas/atlas-vector-search/deployment-options/.
第六章,AI/ML 应用设计
-
“如何在向量搜索中索引字段 - MongoDB Atlas,”未注明日期,https://www.mongodb.com/docs/atlas/atlas-vector-search/vector-search-type/#considerations.
-
Lauren Schaefer Daniel Coupal,“膨胀的文档 | MongoDB,”2022年5月31日,https://www.mongodb.com/developer/products/mongodb/schema-design-anti-pattern-bloated-documents/.
-
Daniel Coupal,“使用模式构建:扩展引用模式,”MongoDB,2019年3月19日,https://www.mongodb.com/blog/post/building-with-patterns-the-extended-reference-pattern.
-
“Atlas 集群大小和层级选择 - MongoDB Atlas,”未注明日期,https://www.mongodb.com/docs/atlas/sizing-tier-selection/.
-
“自定义集群存储 - MongoDB Atlas,”未注明日期,https://www.mongodb.com/docs/atlas/customize-storage/.
-
“Amazon EBS 卷类型 - Amazon EBS,”未注明日期,https://docs.aws.amazon.com/ebs/latest/userguide/ebs-volume-types.html#gp3-ebs-volume-type.
-
“自定义集群存储 - MongoDB Atlas,”未注明日期,https://www.mongodb.com/docs/atlas/customize-storage/.
第7章,有用的框架、库和 API
-
“MongoDB Atlas,”LangChain,未注明日期,https://python.langchain.com/v0.2/docs/integrations/vectorstores/mongodb_atlas/.
-
“如何在向量搜索中索引字段 - MongoDB Atlas,”未注明日期,https://www.mongodb.com/docs/atlas/atlas-vector-search/manage-indexes/.
-
“使用 LangChain 集成开始 - MongoDB Atlas,”未注明日期,https://www.mongodb.com/docs/atlas/atlas-vector-search/ai-integrations/langchain/.
-
“使用 Python 的 MongoDB - MongoDB 文档,”未注明日期,https://www.mongodb.com/docs/languages/python/#integrations.
-
“Transformers,”未注明日期,https://huggingface.co/docs/transformers/en/index.
-
“OpenAI 开发者平台,”OpenAI 平台,未注明日期,https://platform.openai.com/docs/overview.
第8章,在 AI 应用中实现向量搜索
-
Yunfan Gao等人,“大型语言模型的检索增强生成:综述,” arXiv.org,2023年12月18日,https://arxiv.org/abs/2312.10997.
-
Rupak Roy, “利用CommaSeparatedListOutputParser、PydanticOutputParser等LLM输出解析器构建结构化AI | by Rupak (Bob) Roy - II | Medium | Medium,” Medium,2024年8月14日,https://bobrupakroy.medium.com/harness-llm-output-parsers-for-a-structured-ai-7b456d231834.
-
Mirjam Minor和Eduard Kaucher,“使用LLM进行检索增强生成以解释业务流程模型,” 在 Lecture Notes in Computer Science,2024年,第175-190页,https://doi.org/10.1007/978-3-031-63646-2_12.
第9章, LLM输出评估
-
“Papers with Code - 测量大规模多任务语言理解,” 2020年9月7日,https://paperswithcode.com/paper/measuring-massive-multitask-language.
-
“Papers with Code - HellaSwag: 一台机器真的能完成你的句子吗?,” 2019年5月19日,https://paperswithcode.com/paper/hellaswag-can-a-machine-really-finish-your.
-
“Papers with Code - 评估在代码上训练的大型语言模型,” 2021年7月7日,https://paperswithcode.com/paper/evaluating-large-language-models-trained-on.
-
“介绍 | Ragas,” 未注明日期,https://docs.ragas.io/en/stable/index.html.
第10章, 精炼语义数据模型以提高准确性
-
“SentenceTransformers 文档 — Sentence Transformers 文档,” 未注明日期,https://sbert.net/.
-
“训练和微调Sentence Transformers模型,” 未注明日期,https://huggingface.co/blog/how-to-train-sentence-transformers.
-
“运行向量搜索查询 - MongoDB Atlas,” 未注明日期,https://www.mongodb.com/docs/atlas/atlas-vector-search/vector-search-stage/#atlas-vector-search-pre-filter.
-
“知识图谱RAG查询引擎 - LlamaIndex,” 未注明日期,https://docs.llamaindex.ai/en/stable/examples/query_engine/knowledge_graph_rag_query_engine/.
第11章, 生成式AI的常见失败
-
Lance Eliot, “医生依赖生成式AI来总结医疗记录可能会不知不觉地承担重大风险,” Forbes,2024年7月2日,https://www.forbes.com/sites/lanceeliot/2024/02/05/doctors-relying-on-generative-ai-to-summarize-medical-notes-might-unknowingly-be-taking-big-risks/.
-
Markman, Ofer. “是时候制定战略了:85%的数据是垃圾或孤岛化的。” Filo Focus,2024年2月11日。 https://www.filo.systems/blog/85-percent-of-data-is-not-actionable-time-to-restrategize.
-
Neeman, Ella, Roee Aharoni, Or Honovich, 等人. “DisentQA:通过反事实问答分解参数知识和上下文知识。” arXiv.org,2022年11月10日。 https://arxiv.org/pdf/2211.05655.
-
Sharma, Mrinank, Meg Tong, Tomasz Korbak, 等人. “理解语言模型中的谄媚。” arXiv.org,2023年10月20日。 https://arxiv.org/abs/2310.13548.
-
Sparkes, Matthew. “随着AI聊天机器人变得更加高级,它们变得越来越谄媚。” New Scientist,2023年8月17日。 https://www.newscientist.com/article/2386915-ai-chatbots-become-more-sycophantic-as-they-get-more-advanced/.
-
Wei, Jerry, Da Huang, Yifeng Lu, 等人. “简单的合成数据可以减少大型语言模型中的谄媚。” arXiv.org,2023年8月7日。 https://arxiv.org/abs/2308.03958.
第12章,纠正和优化您的生成式AI应用
-
Chui, Michael, Roger Roberts, Tanya Rodchenko, 等人. “每位CEO都应该了解的生成式AI。” McKinsey Digital, 2023年5月12日。 https://www.mckinsey.com/capabilities/mckinsey-digital/our-insights/what-every-ceo-should-know-about-generative-ai.
-
Xiao, Han. “什么是ColBERT和晚期交互,以及为什么它们在搜索中很重要?”,Jina AI,2024年2月20日。 https://jina.ai/news/what-is-colbert-and-late-interaction-and-why-they-matter-in-search/.


后出现的概率:将单词对
的计数除以单个单词 
浙公网安备 33010602011771号