LLM 和 GNN:如何提升两种 AI 系统在图数据上的推理能力
·发表于 Towards Data Science ·9 分钟阅读·2023 年 12 月 3 日
–
人工智能软件被用来提升本文文本的语法、流畅性和可读性。
图神经网络(GNNs)和大型语言模型(LLMs)已成为人工智能的两个主要分支,分别在从图结构数据和自然语言数据中学习方面取得了巨大成功。
随着图结构和自然语言数据在现实应用中越来越相互关联,对能够进行多模态推理的人工智能系统的需求也在增长。
本文探讨了结合图神经网络(GNNs)和大型语言模型(LLMs)互补优势的集成图语言架构,以提升分析能力。
现实世界场景通常涉及具有结构性和文本性模式的互联数据。这就带来了对能够通过统一 GNNs 和 LLMs 互补优势进行多方面推理的集成架构的需求。
具体来说,虽然 GNNs 利用图上的消息传递来聚合局部模式,但节点嵌入在捕捉丰富特征方面存在局限性。
相比之下,LLMs 展现出卓越的语义推理能力,但在处理 GNNs 自然理解的结构化拓扑关系时表现欠佳。
融合这两种范式可以实现更具上下文的、有信息的分析。
最近,融合了 GNN 编码器和 LLM 解码器互补优势的图语言集成架构越来越受到关注。
正如综述论文(Li et al. 2023)总结的,这些集成方法可以根据 LLMs 所扮演的角色进行分类。
图在表示和分析复杂关系方面发挥了重要作用,如…
synthical.com](https://synthical.com/article/3c4812bb-0cde-43fb-a861-4bc7cd978b7b?source=post_page-----5ebd875eef30--------------------------------)
Li et al. 2023
**LLM 作为增强器:**LLMs 通过强化节点嵌入和文本特征来提升 GNN 在文本属性图上的性能。技术方法包括基于解释的增强,这种方法利用额外的 LLM 生成的信息,或直接输出基于嵌入的增强。
**LLM 作为预测器:**利用 LLMs 的生成能力对图进行预测。策略要么是将图展平成顺序文本描述,要么在 LLM 预测之前采用 GNN 进行结构编码。
**GNN-LLM 对齐:**专注于对齐 GNN 和 LLM 编码器的向量空间以实现协调分析。对齐可以是对称的,强调平等,或是不对称的,优先考虑某些模式。
核心动机在于有效地将图的关系建模优势与语言模型的上下文推理能力相融合。这在提升对结合了结构和语义的互联数据的分析能力方面显示了巨大的潜力。
Li et al. 2023
推理挑战:
对于 LLMs
尽管大型语言模型(LLMs)在各种自然语言任务上取得了令人印象深刻的成绩,但在处理图结构数据时,其推理能力仍然受到限制。
这是因为大多数图缺乏 LLMs 可以处理的固有顺序结构。例如,社交网络、分子数据或知识图谱定义了复杂的实体关系,这些关系无法轻易展平成文本描述。
因此,LLMs 在将基于图的拓扑的位置信息和关系依赖有效地融入其推理过程中存在困难。由于无法将节点和边直接转换为单词/标记,LLMs 无法感知像邻域相似性、社区结构或多跳连接等有助于图分析的见解。
对于 GNNs
另一方面,图神经网络(GNNs)旨在通过节点之间的消息传递来聚合每个节点周围的局部邻域信息。这使得它们能够基于图的位置发现节点的模式和角色。
然而,推理在很大程度上被限制在单个节点及其直接邻居。捕捉远离节点的较长距离依赖性对于标准 GNN 架构仍然很困难。
更重要的是,GNN 依赖于固定大小的节点向量表示,这限制了它们表达复杂语义的能力。在缺乏处理丰富文本特征的能力的情况下,推理能力也会在图形端遇到瓶颈。
I. 增强器、预测器或对齐
1. LLM 作为增强器
这一类技术专注于利用大语言模型(LLMs)的知识和上下文理解来增强图神经网络(GNNs)的学习过程,特别是在文本属性图上。
核心动机是,虽然 GNN 专注于拓扑分析,但它们依赖于有限的文本节点嵌入。通过语言模型的外部语义知识增强特征提供了性能提升的途径。
基于解释的增强
一类方法提示 LLM 生成额外的节点解释、描述符或标签,以丰富文本属性。这些补充现有文本数据,以改进嵌入。例如,LLM 可能会为引用数据集中的论文输出研究领域标签。
基于嵌入的增强
另外,LLM 可以直接输出增强的文本嵌入,供 GNN 使用,而不是通用词向量。像图基训练或适配器层这样的微调策略允许注入拓扑感知。通过任务调优的 LLM 处理文本,图模型的语言表达能力可以大大提升。
2. LLM 作为预测器
这一分支专注于通过对图结构数据进行编码来提升 LLM 的预测能力,使语言模型能够有效利用自注意力来揭示洞察。
图形展平
一种常见的技术是通过自然语言模板将图形展平为类似句子的顺序节点描述。例如,论文引用网络可以转化为带有定向“引用”连接的研究论文提及。顺序线性化允许直接应用 LLM 架构。
GNN 融合
为了更紧密的集成,GNN 编码器可以先提取拓扑表示,然后将其与 LLM 中的标记嵌入融合,以利用两种模态。然后,LLM 对合并后的嵌入进行预测。
3. GNN-LLM 对齐
这一类别特别关注于对齐 GNN 和 LLM 编码器的向量空间,以改进合并推理,同时保留其专门角色。
对称对齐
对齐的图-文本对下的对比表示学习方法在训练过程中对每个编码器进行平等待遇。
非对称对齐
通过辅助调优的图层或蒸馏方法直接将结构知识注入 LLM 编码器,非对称地增强语言推理能力。
II. 将图结构与文本语义集成
为了克服图和语言模型各自的推理局限性,一个有效的方法是将 GNN 和 LLM 模块整合到一个端到端可训练的架构中。
关键的见解是允许两个组件互补而非孤立工作——将图的拓扑建模优势与语言模型的上下文推理能力融合起来。
这通过在两种模式上共同学习来实现增强的集体推理,而不是以解耦的方式使用它们。具体来说,图编码器利用消息传递生成节点邻域、社区、角色和位置等结构属性的表示。
同时,文本解码器利用对顺序标记的自注意力以及预训练的知识,生成基于丰富语义特征的推断。
GNN-LLM 融合架构
一个典型的高级蓝图包含三个关键组件:
-
图编码器: 像图卷积网络(GCNs)或图注意力网络(GATs)这样的 GNN,它输出捕捉拓扑的节点嵌入。
-
跨模态投影器: 像对比学习这样的跨模态对齐模块,将图和文本向量映射到一个共同的嵌入空间。
-
语言解码器: 像 BERT 这样的 LLM,在来自投影器的融合图文表示上进行标记级推理。
通过分别以其本地格式编码结构和语义,并通过对齐融合,可以将图和语言的优势统一在一个集成的推理系统中。联合的端到端学习允许适当的信号混合。
III. 改进推理的策略
基于提示的重新表述
仔细设计描述图关键概念(如节点、边、连接、位置等)的提示,以自然语言格式使结构图领域转移到发挥大型语言模型(LLMs)优势的领域。
通过将图组件映射到词汇/标记,复杂的拓扑和关系可以转化为 LLM 架构本身设计用来处理和推理的序列。这有助于在两种模式之间转移推理模式。
例如,论文引用图可以通过提及论文以及“引用”或“被引用”关系来描绘。
多跳邻居描述
由于序列长度的限制,LLMs 在聚合全局图结构和捕捉长距离依赖关系方面有限。描述每个节点的多跳邻居提供了有关扩展网络位置和角色的额外上下文信息。
通过灵活地增加跳数限制并递归地整合进一步的节点,LLMs 可以学习模仿图神经网络的聚合过程,实现局部性和全局性意识的表示。
上下文学习
通过逐步推理示例演示图分析,可以引导 LLM 图理解朝着可解释的方向发展。通过提供任务的前提和结论以及解释,LLM 生成可以模仿并链式处理这些逻辑过程。
这种从上下文示例中学习的技术可以获得更连贯和可靠的图推理能力。对这些数据进行微调会带来更强的归纳偏置。
可解释的微调
诸如适配器层和基于提示的调优等策略允许将结构知识精确地注入到语言模型中,同时保持模型的整体可解释性,因为这种适应是隔离的。通过仅将定制锚定到某些层,可以提升推理能力而不会丧失语言一致性。
IV. 未来展望
带有 LLM 控制器的层次推理
超越仅仅融合 GNN 和 LLM,将语言模型制定为能够选择性地委托和协调最优图形、文本及其他专业模块的元控制器,承诺提供更复杂的推理能力。
基于层次任务分解,LLM 可以通过可用的 AI 组件规划计算路径,在动态流中结合各自的优势。这也朝着更具人类解决问题特征的架构发展。
例如,一个推荐系统可以使用 LLM 来分解目标,利用 GNN 用户编码器,应用视觉工具进行项目分析,最后融合信号以获得结果。
可转移的图中心预训练
图神经网络面临的一个持续挑战是由于结构和模式的变化而导致的领域间泛化能力差。在下游微调之前在大量代表性图数据上预训练 GNN 模型可以缓解这个问题。
同样,需要探索定制的预训练策略,以适应图拓扑模式并注入到语言模型中,以提高跨图任务的推理可转移性。
评估 LLM 的图表达能力
鉴于 LLM 在语言领域的主导地位,分析其在基本图函数方面的理论表达能力也提供了一个研究方向。
例如,1-WL 测试用于评估 GNN 的表达能力。增强的 LLM 与这种基准匹配或超越了吗?元素级和网络级的评估可以量化这一点。
共享表示的方向
超越模型特定的优化,创建共享向量空间以便无缝整合来自图形和文本模态的信号提供了最灵活的推理基础。
在对齐编码器时平衡特异性和共性,以及在联合分析过程中协同信号提供了一个有趣的前进道路。
结论
多范式推理的需求
总结来说,现实世界的数据正日益互联,包括实体之间关系的图结构表示以及与节点相关的基于文本的信息。这推动了对能够在拓扑和语义领域进行多方面推理的人工智能技术的需求。
纯粹的图中心或文本中心的方法都不能单独完全解决这种互联数据的复杂性。这需要统一多种专业化模态的集成架构。
图神经网络与大语言模型的互补优势
当前最主导的两种分析范式是图神经网络,这些网络在图拓扑上的计算模式表现出色,以及大语言模型,这些模型在文本概念上展示了极高的推理能力。
将两者融合提供了在联合模型中有效结合拓扑能力和语义表达的机会,从而提供更连贯的多范式推理。
技术的持续探索
正如本文所述,利用大语言模型作为增强器、预测器以及与图神经网络对齐的多种技术提供了有前景的初步途径来实现这一协调目标。每种方法都贡献了独特的策略,具有各自的优势和局限性。
层次化技术、优化的预训练策略以及在共享表示上的综合推理提供了有趣的前瞻性路径,因为图与文本的协同效应继续被探索,以满足互联智能的需求。
图片由作者提供
LLM 经济学:ChatGPT 与开源
原文:
towardsdatascience.com/llm-economics-chatgpt-vs-open-source-dfc29f69fec1
部署像 ChatGPT 这样的 LLM 成本是多少?开源 LLM 部署是否更便宜?有哪些权衡?
·发表于 Towards Data Science ·阅读时间 6 分钟·2023 年 4 月 26 日
–
比较 LLM 成本的卡通示意图 | Skanda Vivek
TLDR: 对于每天请求数在 1000 个左右的低使用量,ChatGPT 的成本低于使用部署在 AWS 上的开源 LLM。对于每天请求数在百万级别的高使用量,部署在 AWS 上的开源模型成本更低。(截至撰写本文日期 2023 年 4 月 24 日。)
大语言模型正在席卷全球。变压器模型于 2017 年推出,随后出现了突破性的模型如 BERT、GPT 和 BART——拥有数亿参数;并能够执行多种语言任务,如情感分析、问答、分类等。
几年前——来自 OpenAI 和 Google 的研究人员记录了多篇论文,显示具有超过 10 亿参数的大语言模型开始展示出突现能力,它们似乎能够理解语言的复杂方面,并且在回答时几乎像人类一样。
GPT-3 论文展示了大语言模型令人印象深刻的学习能力。
GPT-3 论文展示了参数超过 10-100 亿的模型在仅有几十个提示的情况下展现了令人印象深刻的学习能力。
然而,这些 LLM 的资源消耗非常大,以至于在大规模部署时经济上具有挑战性。直到最近 ChatGPT 的出现才改变了这种状况。ChatGPT 界面发布后不久,OpenAI 使 ChatGPT API 可用,以便开发者可以在他们的应用中使用 ChatGPT。让我们看看这些大规模部署的成本以及经济可行性。
ChatGPT API 成本
ChatGPT API 按使用量计费。每 1K 个 token 的费用为 $0.002。每个 token 大约是一个单词的 3/4 —— 单次请求中的 token 数量是提示 + 生成的输出 token 的总和。假设你每天处理 1000 个小文本块,每个文本块为一页 —— 即 500 个单词或 667 个 tokens。这将花费 $0.002/1000x667*1000= ~$1.3 一天。 还不错!
但是,如果你每天处理一百万个这样的文档会发生什么?那么,每天的费用为 $1,300 或每年 ~0.5 百万美元! ChatGPT 从一个有趣的玩具变成了一个主要的开支(因此希望 —— 也是一个主要的收入来源)在一个数百万美元的业务中!
开源生成模型
在 ChatGPT 发布之后,出现了一些开源的倡议。Meta 发布了 LLaMA——一个拥有数十亿参数的 LLM 模型,其性能优于 GPT-3。斯坦福大学随后对 LLaMA 的 7B 版本进行了 52K 指令跟随演示的微调,发现 他们的 Alpaca 模型 优于 GPT-3。
最近有研究团队展示了一种 13B 参数的微调 LLaMA 模型,称为 Vicuna,其质量达到了 ChatGPT 的 >90%。公司可能选择使用开源生成模型而非 OpenAI 的 GPT 系列模型的原因有多个,包括对 OpenAI 故障的较低敏感性、定制更为容易,以及可能更便宜。
虽然开源模型免费使用,但托管和部署它们的基础设施却不是。而且,早期的 transformer 模型如 BERT 可以在配备良好的 CPU 和基础 GPU 的个人计算机上轻松运行和微调,但 LLMs 更为资源密集。一个常见的解决方案是使用云服务提供商如 AWS 来托管和部署这些模型。
让我们深入了解托管开源模型的 AWS 成本。
AWS 成本
首先,让我们讨论在 AWS 上部署模型并将其作为 API 提供服务的标准架构。通常有三个步骤:
-
使用 AWS Sagemaker 将模型部署为端点。
-
将 Sagemaker 端点连接到 AWS Lambda。
-
通过 API 网关将 Lambda 函数作为 API 提供服务
使用 API 网关和 Lambda 调用 Sagemaker 模型端点
当客户端向 API 网关发起 API 调用时,会触发 Lambda 函数,该函数解析请求并将其发送到 Sagemaker 端点。模型端点随后进行预测,并将信息发送到 Lambda。Lambda 解析这些信息并将其发送到 API,最终返回给客户端。
Sagemaker 的成本对托管模型的计算实例类型非常敏感。LLMs 使用相当大的计算实例。
例如,这篇由 AWS 的某人撰写的文章详细说明了如何在 AWS 上部署 Flan UL2 —— 一个 200 亿参数的模型:
[## 使用 Amazon SageMaker 在单个 GPU 上部署 Flan-UL2
Hugging Face + AWS 的合作使得实验开源最先进语言模型变得比以往更加容易……
文章使用了 ml.g5.4xlarge 实例。虽然上面的 Sagemaker 定价没有列出这个特定实例的价格,但看起来大约在每小时 ~5$ 的范围。这就意味着每天 150$!这只是实例托管的费用,我们还没有计算 Lambda 和 API 网关的费用。
以下是 AWS Lambda 定价的详细信息——这取决于内存使用量和请求频率。
假设获取响应需要 5 秒。128 MB 足够了,因为我们将数据路由到 AWS Sagemaker 端点。所以这将花费 5 * .128 * 1000 * 0.0000166667 = 0.01 0.0000166667 = 0.01 0.0000166667=0.01 每 1000 次请求,或 10$ 每 1M 次请求。
最终成本是 API 网关的费用:
如你所见,API 网关相当便宜——每百万次请求 1$。
因此,在 AWS 上托管开源 LLM,如 Flan-UL2 的成本是每天 1000 次请求 150 ,每天 1 M 次请求 160 ,每天 1M 次请求 160 ,每天1M次请求160。
但我们是否总是需要如此昂贵的计算实例?对于像 BERT 这样的较小语言模型(参数数量为亿级)——你可以使用更便宜的实例,比如 0.23 / 小时和 5 0.23/小时和 ~5 0.23/小时和 5 每天。这些模型也相当强大,并且比那些似乎能够理解复杂语言细微差别的 LLM 更加针对任务和训练数据。
总结思考
那么哪个更好?使用像 OpenAI 的 GPT 系列这样的付费服务 LLM?还是开放访问 LLM?这取决于使用场景:
付费服务 LLM 的利与弊 | Skanda Vivek
开放访问 LLM 的利与弊 | Skanda Vivek
注意:由于这是一个迅速发展的领域,由于大规模的需求,未来相对较近的时期,部署成本大幅降低的可能性很大。(请记住,虽然托管开源 LLM 是一个挑战,但像 BERT 这样具有数亿参数的小型语言模型在特定任务中仍然是一个很好的选择。我已撰写有关如何通过对 BERT 基础模型进行微调来实现类似人类表现的任务,如 问答系统 和 垃圾邮件检测 的文章。)
但哪个模型更好呢?ChatGPT 和 GPT-4 的回应比开源 LLM 的回应更具相关性。然而,开源模型正在快速赶上。而且,使用开源模型而不是封闭 API 可能有非常好的理由。
公司希望在其特定数据源上对开源模型进行微调。ChatGPT 和后续的 OpenAI 模型可能无法像在领域特定数据上微调的开源模型那样表现良好;由于这些模型的通用性质。我们已经看到像 BloombergGPT 这样的领域特定模型在生成式 AI 方面取得了强大的进展。
哦——我们都祈祷 OpenAI 不会提高 ChatGPT API 的价格。当 ChatGPT API 刚推出时,令人惊讶的是它的定价比早期的 GPT-3 API 便宜了 10 倍。
我们生活在激动人心的时代!
如果你喜欢这篇文章,请关注我——我撰写关于将最先进的 NLP 应用于现实世界应用的主题,更一般地说,也涉及数据与社会的交集。
欢迎通过 LinkedIn与我联系!
如果你还不是 Medium 的会员,并且想要支持像我这样的作者,欢迎通过我的推荐链接注册: https://skanda-vivek.medium.com/membership
LLM 评估指南:如何构建和基准测试你的评估
图片由作者使用 Dalle-3 通过 Bing Chat 创建
如何构建和运行 LLM 评估——以及为何在基准测试你的 LLM 提示模板时使用精确度和召回率
·
关注 发表在 Towards Data Science ·12 分钟阅读·2023 年 10 月 13 日
–
这篇文章由 Ilya Reznik 共同创作
大型语言模型(LLM)是开发者和商业领袖为消费者创造新价值的不可思议的工具。它们可以提供个人化推荐、在非结构化数据和结构化数据之间进行翻译、总结大量信息,等等。
随着应用程序的增加,衡量基于 LLM 的应用程序的性能变得越来越重要。这是一个非平凡的问题,原因有几个:用户反馈或任何其他**“真实来源”极其有限且往往不存在**;即使可能,人工标记仍然很昂贵;而且这些应用程序很容易变得复杂。
这种复杂性通常被代码的抽象层隐藏,只有当事情出错时才会显现出来。一行代码可以触发一连串的调用(跨度)。每个跨度需要不同的评估,从而使问题倍增。例如,下面的简单代码片段会触发多个子 LLM 调用。
作者绘制的图表
幸运的是,我们可以利用 LLM 的力量来自动化评估。在这篇文章中,我们将深入探讨如何设置这一过程并确保其可靠性。
LLM 评估的核心是 AI 评估 AI。
虽然这听起来有些循环,但我们一直以来都是由人类智慧来评估人类智慧(例如,在面试或大学期末考试中)。现在,AI 系统终于可以对其他 AI 系统进行同样的评估。
这里的过程是让 LLM 生成可以用于评估另一个系统的合成基准。这引出了一个问题:为什么不直接使用人工反馈?简单来说,因为你永远不会拥有足够的人工反馈。
对于你输入/输出对中的哪怕只有百分之一获得人工反馈,这也是一项巨大的成就。大多数团队甚至无法做到这一点。但是,为了使这一过程真正有用,重要的是对每个 LLM 子调用进行评估,我们已经看到,LLM 子调用可能会有很多。
让我们来探讨一下如何做到这一点。
LLM 模型评估与 LLM 系统评估
LLM_model_evals != LLM_System_evals
LLM 模型评估
你可能听说过 LLM 评估。这个术语在许多不同的情况下使用,听起来很相似,但实际上非常不同。一个更常见的用法是我们所称的LLM 模型评估。LLM 模型评估关注于基础模型的整体性能。推出原始面向客户的 LLM 的公司需要一种量化其在各种任务中的有效性的方法。
作者绘制的图表 | 在这种情况下,我们正在评估两个不同的开源基础模型。我们在这两个模型上测试相同的数据集,看看它们的指标,例如 hellaswag 或 mmlu,如何表现。
一个流行的库是 OpenAI Eval 库,最初专注于模型评估使用案例。现在有许多指标,如HellaSwag(评估 LLM 完成句子的能力)、TruthfulQA(衡量模型回应的真实性)和MMLU(评估 LLM 的多任务能力)。甚至还有LLM 排行榜查看开源 LLM 的表现如何。
LLM 系统评估
到目前为止,我们讨论了 LLM 模型评估。相比之下,LLM 系统评估是对你在系统中可控组件的完整评估。这些组件中最重要的是提示(或提示模板)和上下文。LLM 系统评估评估你的输入如何决定你的输出。
例如,LLM 系统评估可以保持 LLM 不变并更改提示模板。由于提示是系统中更动态的部分,这种评估在项目生命周期中非常有意义。例如,LLM 可以评估你的聊天机器人响应的有用性或礼貌性,并且相同的评估可以提供关于生产环境中性能变化的信息。
作者图示 | 在这种情况下,我们正在评估在单一基础模型上使用的两种不同提示模板。我们在这两个模板上测试相同的数据集,并查看它们的精确度和召回率等指标的表现。
使用哪种?这取决于你的角色
使用 LLM 评估的角色有区别。一种是负责调整核心 LLM 的模型开发者或工程师,另一种是组装用户系统的实践者。
LLM 模型开发者非常少,他们通常在 OpenAI、Anthropic、Google、Meta 等公司工作。模型开发者关心 LLM 模型评估, 因为他们的工作是提供一个适用于各种使用案例的模型。
对于 ML 实践者,任务同样从模型评估开始。开发 LLM 系统的第一步之一是选择一个模型(即 GPT 3.5 vs 4 vs Palm 等)。然而,对于这一群体,LLM 模型评估通常是一个一次性的步骤。一旦确定了哪个模型在你的使用案例中表现最好,应用生命周期的大部分将由 LLM 系统评估决定。因此,ML 实践者关心 LLM 模型评估和 LLM 系统评估,但可能在后者上花费更多时间。
LLM 系统评估指标因使用案例而异
在与其他机器学习系统合作后,你首先会问这个问题:“结果指标应该是什么?”答案取决于你要评估的内容。
-
提取结构化信息:你可以查看 LLM 提取信息的效果。例如,你可以查看完整性(输入中是否有输出中没有的信息?)。
-
问答:系统回答用户问题的效果如何?你可以查看答案的准确性、礼貌性或简洁性——或者以上所有方面。
-
检索增强生成(RAG):检索到的文档和最终答案是否相关?
作为系统设计师,你最终负责系统性能,因此你需要了解系统中哪些方面需要评估。例如,如果你有一个与儿童互动的 LLM,比如一个辅导应用,你会希望确保回答是适龄的,并且不含有毒内容。
目前一些常用的评估包括相关性、幻觉、问答准确性和毒性。每一个评估工具都会有不同的模板,具体取决于你要评估的内容。以下是一个关于相关性的示例:
这个示例为了简单起见使用开源的Phoenix 工具(完全披露:我在开发 Phoenix 的团队中)。在 Phoenix 工具中,存在适用于大多数常见用例的默认模板。以下是我们将用于本示例的模板:
You are comparing a reference text to a question and trying to determine if the reference text contains information relevant to answering the question. Here is the data:
[BEGIN DATA]
************
[Question]: {query}
************
[Reference text]: {reference}
[END DATA]
Compare the Question above to the Reference text. You must determine whether the Reference text
contains information that can answer the Question. Please focus on whether the very specific
question can be answered by the information in the Reference text.
Your response must be single word, either "relevant" or "irrelevant",
and should not contain any text or characters aside from that word.
"irrelevant" means that the reference text does not contain an answer to the Question.
"relevant" means the reference text contains an answer to the Question.
我们还将使用 OpenAI 的 GPT-4 模型和 scikit-learn 的精确度/召回率指标。
首先,我们将导入所有必要的依赖项:
from phoenix.experimental.evals import (
RAG_RELEVANCY_PROMPT_TEMPLATE_STR,
RAG_RELEVANCY_PROMPT_RAILS_MAP,
OpenAIModel,
download_benchmark_dataset,
llm_eval_binary,
)
from sklearn.metrics import precision_recall_fscore_support
现在,让我们引入数据集:
# Download a "golden dataset" built into Phoenix
benchmark_dataset = download_benchmark_dataset(
task="binary-relevance-classification", dataset_name="wiki_qa-train"
)
# For the sake of speed, we'll just sample 100 examples in a repeatable way
benchmark_dataset = benchmark_dataset.sample(100, random_state=2023)
benchmark_dataset = benchmark_dataset.rename(
columns={
"query_text": "query",
"document_text": "reference",
},
)
# Match the label between our dataset and what the eval will generate
y_true = benchmark_dataset["relevant"].map({True: "relevant", False: "irrelevant"})
现在让我们进行评估:
# Any general purpose LLM should work here, but it is best practice to keep the temperature at 0
model = OpenAIModel(
model_name="gpt-4",
temperature=0.0,
)
# Rails will define our output classes
rails = list(RAG_RELEVANCY_PROMPT_RAILS_MAP.values())
benchmark_dataset["eval_relevance"] = \
llm_eval_binary(benchmark_dataset,
model,
RAG_RELEVANCY_PROMPT_TEMPLATE_STR,
rails)
y_pred = benchmark_dataset["eval_relevance"]
# Calculate evaluation metrics
precision, recall, f1, support = precision_recall_fscore_support(y_true, y_pred)
使用 LLM 对 LLM 基础系统进行评估
使用 LLM 对 LLM 基础系统进行评估的过程包括两个不同的步骤。首先,为你的 LLM 评估指标建立基准。为此,你需要组建一个专门的 LLM 评估工具,其唯一任务是将数据标注得尽可能像人工标注你的“黄金数据集”一样有效。然后,你将你的指标与该评估工具进行基准测试。接着,将这个 LLM 评估指标应用于你的 LLM 应用结果(下面会详细说明)。
如何构建 LLM 评估工具
首先,如上所述,构建评估的基准是第一步。
为此,你必须从最适合你用例的指标开始。然后,你需要黄金数据集。这应该能够代表你期望 LLM 评估工具看到的数据类型。黄金数据集应该有“真实标签”,以便我们可以衡量 LLM 评估模板的性能。这些标签通常来自人工反馈。构建这样的数据集是费力的,但你通常可以找到针对最常见用例的标准化数据集(正如我们在上面的代码中所做的)。
作者绘制的图表
然后你需要决定使用哪个 LLM 进行评估。这可能是与用于应用的 LLM 不同的另一个 LLM。例如,你可能在应用中使用 Llama,而在评估中使用 GPT-4。这个选择通常受到成本和准确性问题的影响。
作者提供的图示
现在来到我们试图基准化和改进的核心组件:评估模板。如果你使用的是像 OpenAI 或 Phoenix 这样的现有库,你应该从现有的模板开始,看看那个提示的表现如何。
如果有特定的细节你想要加入,可以相应地调整模板或从头开始构建自己的模板。
请记住,模板应该有一个清晰的结构,就像我们在前面部分使用的那样。对以下内容要明确:
-
输入是什么? 在我们的例子中,它是检索到的文档/上下文和用户的查询。
-
我们在问什么? 在我们的例子中,我们要求 LLM 告诉我们文档是否与查询相关。
-
可能的输出格式是什么? 在我们的例子中,它是二元相关/不相关,但也可以是多类(例如,完全相关、部分相关、不相关)。
作者提供的图示
你现在需要在你的黄金数据集上运行评估。然后你可以生成指标(总体准确性、精确度、召回率、F1 等)来确定基准。重要的是要查看的不仅仅是总体准确性。我们将在下面详细讨论。
如果你对 LLM 评估模板的性能不满意,你需要更改提示以提高其表现。这是一个由硬指标驱动的迭代过程。像往常一样,重要的是避免将模板过度拟合到黄金数据集。确保有一个代表性的保留集或进行 k 折交叉验证。
作者提供的图示
最终,你到达了你的基准。在黄金数据集上的优化性能代表了你对 LLM 评估的信心。它可能不会像你的真实数据那样准确,但足够准确,而且成本远低于每个示例都需要人工标注的情况。
准备和自定义你的提示模板可以让你设置测试用例。
为什么在基准测试你的 LLM 提示模板时应该使用精确度和召回率
行业尚未完全标准化 LLM 评估的最佳实践。团队通常不知道如何建立正确的基准指标。
总体准确性经常被使用,但这还不够。
这是数据科学中最常见的问题之一:非常显著的类别不平衡使得准确性成为一个不切实际的指标。
从相关性指标的角度考虑是有帮助的。假设你经历了所有麻烦和费用,组建了你能组建的最相关的聊天机器人。你选择了一个适合用例的 LLM 和模板。这应该意味着你的示例中会有显著更多的被评估为“相关”的。让我们选择一个极端的数字来说明这个观点:99.99% 的所有查询返回相关结果。太棒了!
现在从 LLM 评估模板的角度来看。如果输出在所有情况下都是“相关”,即使不查看数据,它的正确率也会是 99.99%。但它同时会错过所有(可以说是最重要的)案例——即模型返回无关结果的那些,我们必须捕捉这些结果。
在这个例子中,准确率会很高,但精确率和召回率(或者它们的组合,比如F1 分数)会非常低。在这里,精确率和召回率更能衡量你模型的表现。
另一个有用的可视化是混淆矩阵,它基本上让你看到相关和无关示例的正确和错误预测百分比。
作者图表 | 在这个例子中,我们看到最高比例的预测是正确的:在黄金数据集中,一个相关的例子被我们的评估标记为相关的机会是 88%。然而,我们看到评估在“无关”示例上的表现明显较差,错误标记的概率超过 27%。
如何在你的应用程序上运行 LLM 评估
此时你应该拥有模型和经过测试的 LLM 评估。你已经证明了评估有效,并对其相对于真实情况的表现有了可量化的理解。是时候建立更多信任了!
现在我们可以实际使用我们的评估来评估应用程序。这将帮助我们衡量我们的 LLM 应用程序的表现,并找出如何改进。
作者图表
LLM 系统评估在你的整个系统中运行一个额外的步骤。例如:
-
你检索输入文档并将它们添加到你的提示模板中,以及样本用户输入。
-
你将这个提示提供给 LLM 并接收答案。
-
你将提示和答案提供给你的评估,询问答案是否与提示相关。
最佳实践是不使用一次性代码来进行 LLM 评估,而是使用具有内置提示模板的库。这增加了可重复性,并允许更灵活的评估,可以更换不同的部分。
这些评估需要在三种不同的环境中有效:
预生产
当你进行基准测试时。
预生产
当你在测试你的应用程序时。这在某种程度上类似于传统机器学习中的离线评估概念。其目的是在你将系统交付给客户之前了解系统的性能。
生产
当系统投入使用时,现实生活是混乱的。数据漂移、用户漂移、模型漂移,所有这些都是不可预测的。仅仅因为你的系统曾经运行良好,并不意味着它在周二下午 7 点也会如此。评估有助于你在部署后持续了解系统的性能。
作者图示
需要考虑的问题
你应该抽取多少行?
LLM 评估 LLM 的范式并不是魔法。你不能评估你曾经遇到的每一个示例——那样会代价高昂。然而,你在人工标记期间已经需要抽样数据,更多的自动化只会使这变得更容易和便宜。因此,你可以抽取比人工标记更多的行。
你应该使用什么评估?
这在很大程度上取决于你的使用案例。对于搜索和检索,相关性类型的评估效果最好。毒性和幻觉有特定的评估模式(上述已有提及)。
其中一些评估在故障排查流程中很重要。问答准确性可能是一个很好的总体指标,但如果你深入挖掘为什么这个指标在你的系统中表现不佳,你可能会发现这可能是由于检索不良等原因。通常有很多可能的原因,你可能需要多个指标来找到根本原因。
你应该使用什么模型?
不可能说哪种模型适用于所有情况。相反,你应该进行模型评估,以了解哪个模型适合你的应用程序。你还可能需要考虑召回率与精度的权衡,具体取决于对你的应用程序来说哪种更有意义。换句话说,做一些数据科学来了解你的特定情况。
作者图示
结论
能够评估应用程序的性能在生产代码中是非常重要的。在 LLM 时代,问题变得更加复杂,但幸运的是,我们可以使用 LLM 技术来帮助我们进行评估。LLM 评估应该测试整个系统,而不仅仅是基础 LLM 模型——考虑一下提示模板对用户体验的重要性。最佳实践、标准化工具和精心策划的数据集简化了 LLM 系统的开发工作。
LLM 用于合成时间序列数据生成
·
关注 发表在Towards Data Science · 12 分钟阅读 · 2023 年 10 月 26 日
–
我们最近参与了Brembo hackathon,并赢得了$10,000 的大奖,任务是使用生成型 AI 创建新化合物并生成其预测性能数据。
在这篇博客文章中,我将详细解释我们的方法和解决方案。
问题陈述
使用 Brembo 提供的摩擦测试数据,利用生成式人工智能创建新化合物,预测测试结果,并建立预测新 Brembo 刹车产品效果和特性的框架。提供的数据将包括 Brembo 之前使用和测试的化合物列表及其结果。解决方案必须基于生成式人工智能,应用于提供一个能够提出新配方的模型,以增加候选化合物的数量,确保可行性和良好的性能。
对于你的提交,请提交一个 csv 文件,其中包含你生成的 10–30 种新化合物、它们的成分和它们的合成性能数据。[1]
数据集描述
我们获得了一份包含 337 种摩擦材料及其成分和性能数据的列表。
每种摩擦材料由 10–15 种原材料组成,这些原材料来自 60 种可能的原材料列表。这 60 种原材料被分为 6 类(标记为 A-F),我们必须确保生成的摩擦材料的成分在给定范围内。
材料成分约束
换句话说,我们必须确保任何生成的输出材料中至少有 1%且最多有 30%的成分来自 B 类化合物,依此类推。
每次刹车测试的性能数据本质上是31 个点的时间序列,其中每个点提供了如压力、温度和摩擦系数等参数的值。此外,每种化合物总共进行了124 次刹车测试,因此在性能数据方面,我们需要为每种化合物生成 124*31 = 3844 个数据点。
以下是一些示例数据,其中包含了某种化合物的成分和性能数据。关于数据集的其他相关信息可以在这里找到。
评估标准
最终结果对技术评分和展示评分给予了相等的权重。
技术评分是基于以下相等权重的参数计算的。
-
遵循给定约束:生成的化合物是否符合给定的约束(如下所述)?
-
技术相关性:输出的合成性能数据是否遵循提供的数据中观察到的模式,并捕捉不同变量之间的关系?
-
目标性能:摩擦材料最重要的变量是其摩擦系数(mu),预计值为 0.6,允许的误差率为 0.1。输出的 mu 是否符合我们的预期值?
-
变异性:新生成的材料的成分与当前材料的差异有多大?
设计概述
本质上,我们有 3 个基本组件
-
材料选择模块: 负责生成新的配方。它输出一批新的摩擦材料及其材料组成。
-
数据生成模块: 给定一个合成材料和各种化合物的历史性能数据,为该材料生成合成性能数据。
-
数据验证器: 确定数据生成器的输出有多好/坏。该模块利用提供的历史数据中的趋势(例如:压力和摩擦系数随着时间的推移呈反比关系,减速似乎遵循线性模式,而温度增加曲线似乎更具指数性质)来评估合成性能数据的好坏。这可以用来向模型提供人工反馈,以改进系统性能。
解决方案的高级设计
详细设计
我们在解决方案中使用了以下技术栈和方法
-
GPT 3.5 turbo: 我们使用了 GPT 3.5 turbo 作为材料选择和数据生成模块的基础语言模型。
-
提示工程: 使用正确的系统和指令提示集帮助我们提高了模型的性能。
-
微调: 选择合适的示例来教会模型如何响应的基本结构和语调是非常重要的,这个阶段帮助我们将这些内容教给模型。
-
RAG(检索增强生成): 作为帮助模型输出正确合成性能数据的秘密武器,这在解决方案中发挥了重要作用。更多内容见下文。
材料选择模块
模块的作用是生成新的可能的摩擦材料及其组成。从样本数据可以看出,每种摩擦材料本质上包含一个 60 维的向量,其中第 i 个索引上的数字表示其组成中来自第 i 种原材料的百分比。
一些初步的主成分分析(PCA)显示,我们可以看到总共有 3 到 4 个聚类。
对给定摩擦材料的材料组成进行主成分分析(PCA)
理论上,我们可以为一个大小为 60 的向量生成随机数,看看哪些向量满足给定的约束条件并使用它们。虽然这可以在变异性上获得一个不错的分数(生成的摩擦材料是随机生成的,因此应覆盖 60 维空间中的多个点),但这种方法会有一些缺陷,比如
-
这将使我们更难预测与历史数据中提供的材料完全不同的化合物的性能。这是因为材料的组成在所见性能中扮演着重要角色,而预测之前未见过的组成的性能可能很困难。
-
这会使调试变得更加困难。如果在我们管道中的任何一点上,我们得到的结果不符合历史数据中的趋势,那么很难确定问题所在。
鉴于这些潜在问题,我们决定利用 gpt 3.5 turbo 模型为我们生成一批化合物。
我们所做的如下:
-
为模块创建一个相关的系统提示。
-
微调gpt 3.5 turbo 模型,通过输入我们提供的 337 种摩擦材料的组成。
-
使用数据验证模块,我们会丢弃不符合给定约束的条目,并保留符合约束的条目。
完成后,我们生成了几个化合物并重复了 PCA 分析。
对提供的和生成的材料进行 PCA 分析
最终,为了变异性,我们从生成的化合物中挑选了一组我们认为可以最大化以下因素的化合物:
-
相对于提供的材料的变异性:生成的化合物与提供的化合物有多大的不同?本质上,我们不希望我们的生成材料与已经存在的化合物过于相似。
-
相对于生成材料的变异性:由于我们将提交 10-30 个新生成的化合物,我们必须确保所有生成的化合物不会都属于同一个簇。
因此,经过修剪后,我们得到了一份最终提交的化合物列表。
生成化合物的最终列表
数据生成模块
数据生成模块负责输出给定材料和刹车测试的合成性能数据。本质上,给定摩擦材料的组成,它应该输出一个包含温度、压力和摩擦系数等参数的 31 点时间序列数据。
这就是我们实现这一目标的方式:
- 为模块创建一个合适的系统提示。经过在OpenAI 的实验平台上反复试验,我们使用的提示是:
You are a highly skilled statistician from Harvard University who
works at Brembo, where you specialize in performance braking systems
and components as well as conducting research on braking systems.
Given a friction material’s composition, you craft compelling
synthetic performance data for a user given braking test type.
The braking id will be delimited by triple quotes. You understand
the importance of data analysis and seamlessly incorporate it for
generating synthetic performance data based on historical performance
data provided. You have a knack for paying attention to detail and
curating synthetic data that is in line with the trends seen in the
time series data you will be provided with. You are well versed with
data and business analysis and use this knowledge for crafting the
synthetic data.
-
接下来,我们 微调了 gpt 3.5 turbo 模型,以创建一个在给定材料的组成和刹车测试 ID 的时间序列数据预测方面的专家。由于我们有 41,788 个(材料,刹车 ID)元组,在所有示例上进行微调不仅会耗时,而且成本高昂。然而,基于我们阅读的一些论文和文章[2][3],我们了解到“微调是为了形式,而 RAG 是为了知识”。因此,我们决定仅使用 5%的样本来微调模型,以便模型可以正确学习我们期望的输出结构和语调。
-
最终,在查询模型以生成时间序列数据时,我们决定基于材料的成分识别和检索 5 个最接近的邻居,并将它们的性能数据作为模型的附加上下文输入。这种技术被称为 RAG(检索增强生成),它是我们能够输出良好结果的原因之一。
RAG 如何帮助我们的结果
微调帮助我们完成了以下任务
-
以正确的结构输出数据:正如各种技术博客[4]中所述,微调在教会模型如何输出数据方面是有效的。我们的微调模型能够输出 csv 文件和 31 个时间序列数据点,其中包括压力、速度、温度和μ等参数的值。
-
理解数据中的基本趋势:微调模型能够理解输入性能数据和输出数据中的一般趋势,并保留这些趋势。例如,温度的值应该随指数曲线增加,而速度应该随线性曲线减少,微调模型能够做到这一点。
然而,微调模型的输出结果有些偏差。例如,在一个案例中,μ的值预期约为 0.6,但输出数据将μ的值定在了约 0.5。因此,我们决定通过识别 5 个最接近的邻居并将它们的性能数据添加到用户提示中来增强数据。
我们定义了两个材料 M1 和 M2 之间的距离,如下所示:
def distance(m1, m2, alpha):
sixty_dim_distance = euclidean_dist(sixty_dim_vector(m1), \
sixty_dim_vector(m2))
six_dim_distance = euclidean_dist(six_dim_vector(m1), six_dim_vector(m2))
return alpha[0] * sixty_dim_distance + alpha[1] * six_dim_distance
- 确定 M1 和 M2 在 60 维输入向量空间中的欧几里得距离。
- 现在,将属于同一类别的化合物的总和以减少向量维度到 6。
- 最后,调整超参数 alpha[0]和 alpha[1]。
采取这种方法的原因是,我们希望确保使用相同类别材料的整体距离小于使用完全不同材料成分的那些材料之间的距离。本质上,给定三个材料 M1、M2 和 M3,其中 M1 使用材料 A0,M2 使用 A1,M3 使用 B0,我们希望我们的距离函数标记 M1 和 M2 彼此更近,而不是 M1 和 M3。
使用这种方法,我们能够显著提高我们的性能,如下图所示。
数据验证器
验证模块帮助我们理解输出数据是否遵循我们期望的趋势。例如,我们期望压力和μ呈反比,μ值约为 0.6,温度随时间呈指数增长,速度线性减速。这个模块帮助我们识别合成时间序列数据与历史数据的接近程度,这有助于我们调整所有的提示和超参数。
这个模块帮助我们分析哪些提示集对模型输出有帮助,哪些没有。
结果和展示
演讲占总分的 50%,这是我们绝对做得好的一个方面。我们做了几件事:
-
确保演讲在 4 分钟以内完成:我们在进入演讲室之前进行了充分的练习,以确保在演讲时不会遇到任何意外。
-
与观众互动:我们提出了一个问题,询问观众他们认为哪个时间序列是合成生成的,哪个是实际的,这帮助我们保持了观众的兴趣。
我们工作的代码和演讲可以在 这里 找到。
关键要点
-
快速迭代设计:我在队友们之前稍早到达,开始在白板上记录我们该做什么。队友们到达后,我们讨论了设计方案,并提出了一个大家都同意的解决方案。这是我们获胜的关键因素,因为在黑客松中总是时间紧迫,尽快确定可以开始实施的设计非常重要。
-
不要担心竞争:当我们的设计完成后,我能感受到我们正走在正确的道路上。我们邀请了不少来自 Brembo 的人员来查看我们的设计。甚至其他参赛者也惊叹不已,盯着我们的设计,这进一步给了我们信号,说明我们在正确的轨道上。当我的队友建议我们应该看看其他人的做法时,我反驳了这个想法,而是要求大家专注于我们的设计并实施它。
-
不要担心冲突:我们多次遇到冲突,特别是在设计方面。关键是要理解没有什么是个人的,而是应该达成共识,对权衡进行迭代,找到适合所有人的解决方案。在我看来,如果你能允许甚至鼓励团队内的健康冲突,伟大的产品就会诞生。
-
微调用于形式,RAG 用于事实:我们知道微调仅对教授模型基本结构和语调重要,而真正的收益来自于 RAG。因此,我们仅用 5%的样本对 gpt 3.5 turbo llm 进行微调,以生成时间序列数据。
-
演讲是关键 (1):识别你的观众是谁,以及他们如何消化你的内容至关重要。在我们的案例中,我们发现大多数评委是高管而非技术人员,因此我决定仅包括我们使用的技术栈[gpt 3.5 turbo, fine tuning, prompt tuning, RAG, KNN]而不深入细节。
-
演讲是关键 (2):成为一个能够通过有效沟通技巧传达观点并充满热情地向观众展示的人。如果你做不到这一点,就找一个团队中的人来做。第一印象很重要,而演讲技巧在我们的技术世界中被严重低估。
-
大胆而不同:我们更进一步,决定包含 5 个他们的数据点和一个我们生成的数据点,并让他们猜哪个是生成的。当他们未能猜出我们生成的那个点时,这确实突显了我们构建的管道和解决方案的优越性。此外,我们还获得了观众互动的加分,这点我怀疑很多人做不到。
下一次的学习
-
微调很昂贵。我们在微调和查询模型三次时用完了 OpenAI 的凭证。未来,我们更倾向于使用像 LoRA[5]和 QLoRA[6]这样的技术在一些开源模型上。
-
使用高级 RAG:未来,我想使用高级 RAG 技术[7]来改进提供的上下文。
-
使用智能 KNN:下次,我想多尝试一下超参数和使用的距离函数。
-
更长的上下文窗口:我们必须舍去性能数据中的一些数字,以确保不超过 4,092 个标记的限制。使用像 Claude[8]这样的 LLM 可能会提高性能。
-
对 LLM 不要太客气:在提示工程中发生了一件有趣的事,当我们提到“μ的值不在 0.6 附近是不可接受的”而不是“请确保μ在 0.6 附近”时,前者得到了更好的结果。
注:除非另有说明,所有图片均由作者提供。
团队成员:
参考文献
[1] brembo-hackathon-platform.bemyapp.com/#/event
[2] www.anyscale.com/blog/fine-tuning-is-for-form-not-facts
[3] vectara.com/introducing-boomerang-vectaras-new-and-improved-retrieval-model/
[4] platform.openai.com/docs/guides/fine-tuning/fine-tuning-examples
[7] LlamaIndex Doc
[8] Claude
理解 LLM 幻觉
观点
LLMs 如何编造内容以及该如何应对
·发表于Towards Data Science ·阅读时间 6 分钟·2023 年 5 月 8 日
–
图片来源:Ahmad Dirini在Unsplash
主要目标
使用大型语言模型并非没有风险,其中包括基于所谓的 LLM“幻觉”的响应。幻觉可能对 LLMs 构成严重问题,因为它们可能导致虚假信息的传播、暴露机密信息,并对 LLMs 的能力产生不切实际的期望。理解幻觉并对它们生成的信息保持批判性,有助于解释和减轻这些幻觉可能造成的问题。
什么是 LLM 幻觉?
LLMs 是一种人工智能(AI),通过大量的文本和代码数据集进行训练。它们可以生成文本、翻译语言、创作不同类型的内容,并以信息性方式回答问题。然而,LLMs 也容易“幻觉”,这意味着它们可能生成事实不准确或毫无意义的文本。正如经常讨论的那样,“LLMs 可能充满了胡说八道。”这种幻觉发生是因为 LLMs 在经常不完整或矛盾的数据上进行训练。因此,它们可能会学会将某些词语或短语与某些概念联系起来,即使这些联系并不准确或无意中“过于准确”(我的意思是它们可能会捏造一些真实但不应被分享的内容)。这可能导致 LLMs 生成事实不准确、无意中过于纵容或纯粹毫无意义的文本。
幻觉的类型
谎言!谎言!谎言!
LLMs 有时会生成事实不准确的文本。以下是一个例子,其中有些内容是正确的,但突出显示的部分则完全不真实:
Prompt: Write a brief history of the United States
Response: The 20th century was a time of great change for the United States.
The country fought in two world wars, and it emerged as a global superpower
after World War II. The Civil Rights Movement of the 1950s and 1960s led to
the end of segregation and discrimination against African Americans.
说隔离和歧视已经不存在是不符合事实的。将其称为“谎言”对我来说也是技术上不准确的,因为模型不理解真理或谎言,只是如何组装单词。无论原因如何,LLMs 仍然可能生成事实不准确的内容。这并不少见,因此必须对所有内容进行事实检查。
胡说八道
从一个很高的层面来看,LLMs 使用概率来安排单词。尽管单词和其概率的范围可能会生成有意义的内容,但这并不总是如此;LLMs 也可以生成无意义的文本。例如,如果你让 LLM 写一首诗,它可能会生成语法正确但毫无意义的内容。
Prompt: write a funny poem about swimming in green water
Partial (summarized) Response:
Swimming in Green Water
I went swimming in the green water,
The water was so green,
I couldn't see my feet,
I couldn't see my hand,
I couldn't see my head.
I looked around and saw a fish,
A big green fish,
With big green eyes,
And a big green smile,
And a big green fin.
I said, "Hello, fish,"
And the fish said, "Hello,"
And we swam around together,
In the green water,
For a long time.
Then we got tired,
And we lay down on the beach,
And we took a nap,
In the sun,
And the green water.
我能理解创作者可能会说,在诗中说鱼可以躺在沙子里是完全可以接受的(诗意许可及其所有),但我也可以争论模型是在编造一些胡说八道,这也是我在这种情况下的看法。思想从逻辑偏离到了非逻辑。然而,注意如果你与 LLMs 合作一段时间,你会看到更严重的例子。再次检查模型输出,并在必要时进行更正。在诗歌示例中,我可能会简单地将“…我们躺在海滩上…”改为“…我们躺在珊瑚礁旁…”或干脆删除那一行,因为人类实际上无法在水下小憩。
来源混淆
LLMs 有时会混淆不同来源的信息,这可能导致它们生成不准确或误导性的文本。例如,如果你让 LLM 写一篇关于当前事件的新闻文章,它可能会将来自不同新闻来源的信息结合在一起,即使这些来源相互矛盾。还要注意,将历史信息的推断与(例如)LangChain 等内容结合在一起,可能会真正混淆信息(和格式)。
这是一个示例,说明混淆如何导致事实不准确(或至少非常误导性)的信息。
Prompt: write a one paragraph summary of the miami grand prix
Response:
The 2023 Miami Grand Prix was the fifth round of the 2023 Formula One
World Championship. It was held on May 7, 2023 at the Miami International
Autodrome in Miami Gardens, Florida, United States.
Sergio Pérez of Red Bull Racing started the race on pole position, but
was overtaken by Charles Leclerc of Ferrari on the opening lap. Leclerc
led the race for the first 20 laps, but was then passed by Max Verstappen
of Red Bull Racing. Verstappen went on to win the race, with Leclerc
finishing second and Pérez finishing third.
The Miami Grand Prix was a popular event, with over 270,000 spectators in
attendance. The race was also well-received by the media, with many
praising the new circuit and the atmosphere at the event.
回应在第一段准确地开头,描述了 2023 年 5 月 7 日举行的比赛。然后,模型似乎在之后混淆了 2022 年的结果。勒克莱尔在 2023 年排位第七,并没有在前 20 圈领跑,但他确实在 2022 年获得了第二名,并且可能在那场比赛的前 20 圈中领跑。(2023 年塞尔吉奥·佩雷斯获得第二名。)在这种情况下,混淆可能发生在用于结合当前事件和 LLM 文本的任何工具(例如 LangChain)中,但混淆引起的幻觉的想法仍然适用。
过度纵容
鉴于大型语言模型(LLMs)能够利用概率生成一些相当重要的文本,并且可能会混淆信息,因此统计上来说,LLM 有可能编造出“意外”泄露机密信息的内容。
为了保护机密信息,我不能提供关于我最近遇到的 LLM 出现这种情况的详细信息。然而,我曾询问过一个 LLM 一个我知道不应该披露的特定话题,模型给出了一个逻辑上正确但过于放纵的回答。虽然我遇到的情况不是国家安全问题,但在适当的情况下可能会非常严重。
如何管理幻觉?
这里有一些管理幻觉的技巧:
-
测试不同的设置,比如温度和 TopK(模型如何管理概率)。这是管理模型输出的最重要方法之一。
-
不要完全相信输出——进行事实核查(不用担心,你仍然节省了大量时间!)。
-
通常将 LLM 输出视为草稿机制——例如,LLM 创建了这篇文章的基本布局和一些内容。但我进行了大量编辑。
-
调整你使用的模型。根据你如何使用模型输出,你可能需要调整模型——这有许多方法,包括提示工程、参数高效调整(PET)和完全模型调整。这个简单列表中有相当多的细微差别和复杂性,但如果你知道如何做,这种调整可以减少幻觉。
-
接受模型出现幻觉的现实。与人类(在大多数情况下)不同,LLM 的幻觉通常是一个无意的结果,我认为通常正面的影响远远超过负面的影响。接受这一点并承认/沟通幻觉发生的可能性。
-
探索!虽然这篇文章提供了 LLM 幻觉的概述,但这对你和你的应用意味着什么可能会有显著差异。此外,你对这些词的理解可能并不完全符合现实。唯一真正理解和欣赏 LLM 幻觉如何影响你所做的事的方法是广泛探索 LLM。
更多内容待揭示
LLM 的广泛使用实际上还处于初期阶段,优缺点尚未准确列举。在我看来,开放的心态是理解 LLM 的所有维度,包括幻觉的最佳技巧。享受这个过程,尽可能多地探索,因为这种快速演变很少发生(根据我的经验),那些拥抱这一过程的人将从中获得最多。
LLM 监控与可观测性——负责任 AI 的技术和方法总结
·
关注 发布于 Towards Data Science ·10 分钟阅读·2023 年 9 月 15 日
–
目标受众: 实践者 希望了解可用的方法以及如何开始实施这些方法的情况,和 领导者 希望在构建治理框架和技术路线图时理解可能性的艺术。
几乎一夜之间,每个首席执行官的待办事项、招聘广告和简历上都出现了生成 AI(genAI)。这完全是应有的。基于基础模型的应用程序已经改变了数百万人的工作、学习、写作、设计、编程、旅行和购物方式。大多数人,包括我在内,都认为这只是冰山一角。
在这篇文章中,我总结了对现有大型语言模型(LLM)监控方法的研究。我花了很多时间阅读文档、观看视频,并阅读来自专注于 LLM 监控和可观测性的开源库和软件供应商的博客。结果是一个用于监控和观察 LLM 的实用分类法。希望对你有帮助。在不久的将来,我计划进行一项学术文献检索,以添加前瞻性的视角。
研究过的软件: Aporia, Arize, Arthur, Censius, Databricks/MLFlow, Datadog, DeepChecks, Evidently, Fiddler, Galileo, Giskard, Honeycomb, Hugging Face, LangSmith, New Relic, OpenAI, Parea, Trubrics, Truera, Weights & Biases, Why Labs
- 本文呈现了一个累积的分类法,没有对软件选项进行评分或比较。如果你想讨论我研究中涉及的特定软件,请 联系我。
文章大纲
-
评估 LLM — LLM 是如何评估的,何时被认为适合生产?
-
跟踪 LLM — 跟踪 LLM 意味着什么,哪些组件需要包括?
-
监控 LLM — LLM 一旦投入生产,如何进行监控?
LLM 生命周期
由 Josh Poduska 创作的照片 — 互动视图链接
将 LLM 纳入生产工作流程的竞赛已经开始,但技术社区正在争分夺秒地开发最佳实践,以确保这些强大的模型随着时间的推移按预期行为。
评估 LLM
评估传统的机器学习(ML)模型涉及检查其输出或预测的准确性。这通常通过众所周知的指标如准确率、RMSE、AUC、精确度、召回率等来衡量。评估 LLM 复杂得多。数据科学家今天使用了几种方法。
(1) 分类和回归指标
LLM 可以生成数字预测或分类标签,这种情况下评估较为简单。与传统的 ML 模型一样。这在某些情况下很有帮助,但我们通常关注的是评估生成文本的 LLM。
(2) 独立的基于文本的指标
当你没有可靠的基准数据源时,这些指标对于评估 LLM 的文本输出非常有用。你可以根据过去的经验、学术建议或其他模型的评分来决定什么是可接受的。
困惑度是一个例子。它衡量模型生成输入文本序列的可能性,可以看作是评估模型对其训练文本的学习效果的方式。其他例子包括阅读水平和非字母字符。
更复杂的独立方法包括从模型输出中提取嵌入,并分析这些嵌入以寻找不寻常的模式。这可以通过检查 3D 图中的嵌入图来手动完成。通过颜色编码或按关键字段(如性别、预测类别或困惑度评分)比较,可以揭示你的 LLM 应用中潜在的问题,并提供偏见和解释性的衡量。存在一些软件工具可以以这种方式可视化嵌入。它们将嵌入聚类并映射到 3 维空间。这通常通过HDBSCAN 和 UMAP完成,但有些工具采用基于 K-means 的方法。
除了视觉评估外,还可以运行异常检测算法来检查嵌入中是否存在异常值。
(3) 评估数据集
拥有真实标签的数据集可以将文本输出与批准的响应基准进行比较。
一个著名的例子是ROUGE指标。在语言翻译任务中,ROUGE 依赖于一个参考数据集,其答案与正在评估的 LLM 进行比较。可以根据参考数据集计算相关性、准确性和其他一系列指标。嵌入在其中起着关键作用。标准的距离指标如 J-S 距离、赫灵距离、KS 距离和 PSI,将你的 LLM 输出嵌入与真实标签嵌入进行比较。
最后,存在一些广泛接受的 LLM 基准测试。斯坦福的HELM 页面是了解这些测试的好地方。
(4) 评估 LLM
起初,你可能会认为使用 LLM 来评估 LLM 是作弊,但许多人认为这是前进的最佳途径,研究也显示了希望。使用我所说的评估 LLM 很可能会成为不久的将来 LLM 评估的主要方法。
一个广泛接受的例子是Toxicity度量。它依赖于一个评估器 LLM(Hugging Face 推荐的roberta-hate-speech-dynabench-r4)来确定你的模型输出是否有毒。上述所有关于评估数据集的度量在这里也适用,因为我们将评估器 LLM 的输出视为参考。
根据 Arize 的研究人员,评估器 LLM 应配置为提供二元分类标签以用于它们测试的度量。他们解释说,数值评分和排名需要更多工作,并且不如二元标记表现良好。
(5) 人类反馈
尽管这篇文章、软件文档和营销材料强调了可测量的度量,但你不应忘记手动基于人类的反馈。这通常在构建 LLM 应用的早期阶段由数据科学家和工程师考虑。LLM 可观察性软件通常有一个接口来协助这个任务。除了早期开发反馈之外,将人类反馈纳入最终评估过程(以及持续监控)也是一种最佳实践。获取 50 到 100 个输入提示并手动分析输出可以让你对最终产品有很大的了解。
跟踪 LLMs
跟踪是监控的前提。在我的研究中,我发现跟踪 LLMs 的细节中有足够的细微差别,需要单独成章。跟踪的低悬果包括捕获请求数量、响应时间、令牌使用情况、成本和错误率。标准系统监控工具在这里发挥作用,同时还有更多针对 LLM 的选项(传统监控公司也有营销团队迅速宣称基于简单功能度量跟踪的 LLM 可观察性和监控)。
从捕获输入提示和输出响应中获得深刻见解以进行未来分析。这听起来很简单,但实际上并非如此。复杂性来自于我迄今为止略过的某些内容(大多数数据科学家在谈论或写作关于 LLM 时也一样)。我们并不是在评估、跟踪和监控一个 LLM。我们处理的是一个应用程序;一个或多个 LLM、预设的指令提示和协作生成输出的代理的集合。一些 LLM 应用并不复杂,但许多是的,并且趋势是向更复杂的方向发展。在即使是稍微复杂的 LLM 应用中,确定最终的提示调用可能是困难的。如果我们在调试,我们需要知道每一步的调用状态及这些步骤的顺序。实践者会希望利用帮助解开这些复杂性的工具。
监控 LLMs
虽然大多数 LLM 和 LLM 应用程序至少经历过某种形式的评估,但实施持续监控的却很少。我们将拆解监控的组成部分,以帮助你建立一个保护用户和品牌的监控程序。
(1) 功能监控
首先,上述跟踪部分提到的简单任务应持续监控。这包括请求数量、响应时间、令牌使用、成本和错误率。
(2) 监控提示
接下来,你的列表中应该包括监控用户提供的提示或输入。像可读性这样的独立指标可能会很有帮助。评估 LLM 应该用于检查毒性等问题。将嵌入距离与参考提示进行比较是明智的指标。即使你的应用程序能够处理与预期大相径庭的提示,你仍然希望了解客户与应用程序的互动是否是新的或随时间变化的。
目前,我们需要引入一个新的评估类别:对抗性尝试或恶意提示注入。这在初步评估中并不总是被考虑到。与已知对抗性提示的参考集进行比较可以标记出恶意行为者。评估 LLM 也可以将提示分类为恶意或非恶意。
(3) 监控响应
在将你的 LLM 应用程序产生的内容与预期结果进行比较时,有许多有用的检查可以实施。考虑相关性。你的 LLM 是否在响应相关内容,还是在胡言乱语(幻想)?你是否看到与预期主题的偏离?情感如何?你的 LLM 是否以正确的语调回应,且这种情况是否随着时间变化?
你可能不需要每天监控所有这些指标。每月或每季度一次对于某些指标来说就足够了。另一方面,毒性和有害输出始终是部署 LLM 时最令人担忧的问题。这些是你需要更频繁跟踪的指标。记住,之前讨论的嵌入可视化技术可能有助于根本原因分析。
提示泄露是一种我们尚未介绍的对抗性方法。提示泄露发生在某人欺骗你的应用程序泄露你存储的提示时。你可能花了很多时间找出哪个预设提示指令效果最好。这是敏感的知识产权。提示泄露可以通过监控响应并将其与提示指令数据库进行比较来发现。嵌入距离指标效果很好。
如果你有评估或参考数据集,你可能需要定期测试你的 LLM 应用,并比较之前测试的结果。这可以给你一个随时间变化的准确度感知,并能提醒你漂移。如果发现问题,一些管理嵌入的工具允许你导出表现不佳的输出数据集,以便你可以在这些有问题的提示类别上微调你的 LLM。
(4) 警报与阈值
应注意确保你的阈值和警报不会引发过多的虚假警报。多变量漂移检测和警报可以有所帮助。我有一些关于如何做到这一点的想法,但会留到另一篇文章中。顺便说一下,我在研究本文时没有看到关于虚假警报率或阈值最佳实践的任何提及。这真是个遗憾。
有几个与警报相关的不错功能,你可能想将它们列入必备清单。许多监控系统提供与信息馈送(如 Slack 和 Pager Duty)的集成。一些监控系统允许如果输入提示触发警报,则自动响应拦截。相同的功能可以应用于在将响应发送给用户之前筛查个人信息泄露、毒性以及其他质量指标。
我会在这里添加一个观察点,因为我不知道把它放在哪里。自定义指标对你的监控方案可能非常重要。你的 LLM 应用可能是独特的,或者你团队中的一位优秀数据科学家想出了一个将显著提升你方法的指标。这个领域可能会有进展。你会希望有自定义指标的灵活性。
(5) 监控用户界面
如果系统具备监控功能,它将有一个用户界面(UI),显示度量的时间序列图。这是相当标准的。用户界面开始有所区别,当它们允许深入挖掘警报趋势,并指向某种程度的根本原因分析时。其他界面则通过基于集群和投影的嵌入空间可视化(我希望能看到或进行一项关于这些嵌入可视化在实际应用中有效性的研究)。
更成熟的产品将按用户、项目和团队对监控进行分组。它们将具有 RBAC,并基于所有用户都是需要知道的原则。过于频繁地,工具中的任何人都能看到每个人的数据,这在许多当今的组织中是不可接受的。
我所强调的警报产生不可接受虚假警报率的一个原因是用户界面未能促进对警报的适当分析。软件系统很少尝试在这方面进行任何优化,但有些系统会。再次提到,这个话题还有很多需要讨论的内容,稍后会再提及。
对从业者和领导者的最终思考
领导者们,面临的风险太大,不容忽视 LLM 监控和可观测性的重要性。我说这些不仅仅是为了避免对用户造成伤害或失去品牌声誉。这些显然在你的关注范围内。你可能没有意识到的是,你公司对 AI 的快速且可持续的采用,可能意味着成功与失败之间的差距,而一个成熟的负责任的 AI 框架以及一个详细的技术路线图用于监控和观察 LLM 应用,将为你提供一个基础,使你能够比竞争对手更快、更好、更安全地扩展。
从业人员,本文介绍的概念提供了一系列工具、技术和指标,这些应当包含在 LLM 可观测性和监控的实施中。你可以将其作为指南,以确保你的监控系统能够胜任这项任务。同时,你可以将其作为进一步深入研究我们讨论的每个概念的基础。
这是一个令人兴奋的新领域。那些对其有深刻了解的领导者和从业人员,将能够帮助他们的团队和公司在 AI 时代取得成功。
关于作者:
Josh Poduska 是一位 AI 领导者、战略家和顾问,拥有超过 20 年的经验。他曾担任 Domino Data Lab 的首席现场数据科学家,并在多家公司管理团队和领导数据科学战略。Josh 在多个领域建立并实施了数据科学解决方案。他拥有加州大学欧文分校的数学学士学位和康奈尔大学应用统计硕士学位。
LLM 输出解析:函数调用与 LangChain
如何使用 Open AI API 和 LangChain 函数调用一致地解析 LLM 的输出:评估这些方法的优缺点
·
关注 发表在 Towards Data Science ·11 分钟阅读·2023 年 9 月 21 日
–
图片由 Victor Barrios 提供,来源于 Unsplash
使用 LLMs 创建工具需要多个组件,例如向量数据库、链、代理、文档分割器和许多其他新工具。
然而,最关键的组件之一是 LLM 输出解析。如果你不能从 LLM 接收到结构化的响应,你将很难处理生成的内容。当我们希望一次调用 LLM 输出多个信息时,这一点变得尤为明显。
让我们用一个假设的场景来说明这个问题:
我们希望 LLM 从一次调用中输出 ingredients 和 steps 来制作特定的食谱。但我们希望将这两项内容分别用于系统的两个不同部分。
import openai
recipe = 'Fish and chips'
query = f"""What is the recipe for {recipe}?
Return the ingredients list and steps separately."""
response = openai.ChatCompletion.create(
model="gpt-3.5-turbo-0613",
messages=[{"role": "user", "content": query}])
response_message = response["choices"][0]["message"]
print(response_message['content'])
这将返回如下内容:
Ingredients for fish and chips:
- 1 pound white fish fillets (such as cod or haddock)
- 1 cup all-purpose flour
- 1 teaspoon baking powder
- 1 teaspoon salt
- 1/2 teaspoon black pepper
- 1 cup cold beer
- Vegetable oil, for frying
- 4 large russet potatoes
- Salt, to taste
Steps to make fish and chips:
1\. Preheat the oven to 200°C (400°F).
2\. Peel the potatoes and cut them into thick, uniform strips. Rinse the potato strips in cold water to remove excess starch. Pat them dry using a clean kitchen towel.
3\. In a large pot or deep fryer, heat vegetable oil to 175°C (350°F). Ensure there is enough oil to completely submerge the potatoes and fish.
4\. In a mixing bowl, combine the flour, baking powder, salt, and black pepper. Whisk in the cold beer gradually until a smooth batter forms. Set the batter aside.
5\. Take the dried potato strips and fry them in batches for about 5-6 minutes or until golden brown. Remove the fries using a slotted spoon and place them on a paper towel-lined dish to drain excess oil. Keep them warm in the preheated oven.
6\. Dip each fish fillet into the prepared batter, ensuring it is well coated. Let any excess batter drip off before carefully placing the fillet into the hot oil.
7\. Fry the fish fillets for 4-5 minutes on each side or until they turn golden brown and become crispy. Remove them from the oil using a slotted spoon and place them on a paper towel-lined dish to drain excess oil.
8\. Season the fish and chips with salt while they are still hot.
9\. Serve the fish and chips hot with tartar sauce, malt vinegar, or ketchup as desired.
Enjoy your homemade fish and chips!
这是一个庞大的字符串,解析起来会很困难,因为 LLM 可能会返回稍有不同的结构,从而破坏你编写的代码。你可以争辩说,在提示中始终要求返回“Ingredients:” 和 “Steps:” 可以解决问题,你说得没错。这可能有效,但你仍然需要手动处理字符串,并且要应对可能的变化和虚构。
解决方案
我们可以用几种方法解决这个问题。上述提到了一种,但还有几种经过测试的方法可能更好。在本文中,我将展示两个选项:
-
Open AI 函数调用;
-
LangChain 输出解析器。
Open AI 函数调用
这是我尝试过的一种方法,并且给出了最一致的结果。我们利用 Open AI API 的函数调用能力,以便模型将响应作为结构化的 JSON 返回。
这个功能的目标是提供给 LLM 通过 JSON 输入调用外部函数的能力。模型经过微调,以理解何时需要使用特定函数。一个例子是当前天气的函数。如果你问 GPT 当前的天气,它不能告诉你,但你可以提供一个做这件事的函数并传递给 GPT,这样它就会知道可以根据输入访问它。
如果你想深入了解这个功能,可以查看 Open AI 的公告,以及一篇 很棒的文章。
那么让我们在代码中看看这个在我们面临的问题下会是什么样子。让我们分解一下代码:
functions = [
{
"name": "return_recipe",
"description": "Return the recipe asked",
"parameters": {
"type": "object",
"properties": {
"ingredients": {
"type": "string",
"description": "The ingredients list."
},
"steps": {
"type": "string",
"description": "The recipe steps."
},
},
},
"required": ["ingredients","steps"],
}
]
我们需要做的第一件事是声明将对 LLM 可用的函数。我们必须给它一个名称和描述,以便模型理解何时应使用该函数。在这里,我们告诉它这个函数是用来返回请求的食谱的。
然后我们进入参数设置。首先,我们说明它的类型为对象,其可以使用的属性有“ingredients”和“steps”。这两个属性也都有一个描述和一个类型,以指导 LLM 输出。最后,我们指定哪些属性是调用函数时必需的(这意味着我们可以有可选字段,LLM 可以决定是否使用它们)。
现在让我们在一次调用 LLM 中使用这个:
import openai
recipe = 'Fish and chips'
query = f"What is the recipe for {recipe}? Return the ingredients list and steps separately."
response = openai.ChatCompletion.create(
model="gpt-3.5-turbo-0613",
messages=[{"role": "user", "content": query}],
functions=functions,
function_call={'name':'return_recipe'}
)
response_message = response["choices"][0]["message"]
print(response_message)
print(response_message['function_call']['arguments'])
这里我们开始通过格式化一个可能是变量输入(食谱)的基础提示来创建对 API 的查询。然后,我们使用“gpt-3.5-turbo-0613”声明我们的 API 调用,我们将查询传递到 messages 参数中,然后传递我们的函数。
关于我们的函数有两个参数。第一个是我们传递的对象列表,格式如上所示,包含模型可以访问的函数。第二个参数“function_call”我们指定模型应该如何使用这些函数。有三种选项:
-
“Auto” -> 模型在用户响应或函数调用之间进行决定;
-
“none” -> 模型不调用函数,而是返回用户响应;
-
{“name”: “my_function_name”} -> 指定一个函数名称强制模型使用它。
你可以在这里找到官方文档。
在我们的案例中,作为输出解析我们使用了后者:
function_call={'name':'return_recipe'}
现在我们可以查看我们的响应。我们得到的响应(在此过滤器[“choices”][0][“message”]之后)是:
{
"role": "assistant",
"content": null,
"function_call": {
"name": "return_recipe",
"arguments": "{\n \"ingredients\": \"For the fish:\\n- 1 lb white fish fillets\\n- 1 cup all-purpose flour\\n- 1 tsp baking powder\\n- 1 tsp salt\\n- 1/2 tsp black pepper\\n- 1 cup cold water\\n- Vegetable oil, for frying\\nFor the chips:\\n- 4 large potatoes\\n- Vegetable oil, for frying\\n- Salt, to taste\",\n \"steps\": \"1\. Start by preparing the fish. In a shallow dish, combine the flour, baking powder, salt, and black pepper.\\n2\. Gradually whisk in the cold water until the batter is smooth.\\n3\. Heat vegetable oil in a large frying pan or deep fryer.\\n4\. Dip the fish fillets into the batter, coating them evenly.\\n5\. Gently place the coated fillets into the hot oil and fry for 4-5 minutes on each side, or until golden brown and crispy.\\n6\. Remove the fried fish from the oil and place them on a paper towel-lined plate to drain any excess oil.\\n7\. For the chips, peel the potatoes and cut them into thick chips.\\n8\. Heat vegetable oil in a deep fryer or large pan.\\n9\. Fry the chips in batches until golden and crisp.\\n10\. Remove the chips from the oil and place them on a paper towel-lined plate to drain any excess oil.\\n11\. Season the chips with salt.\\n12\. Serve the fish and chips together, and enjoy!\"\n}"
}
}
如果我们进一步解析到“function_call”,我们可以看到我们预期的结构化响应:
{
"ingredients": "For the fish:\n- 1 lb white fish fillets\n- 1 cup all-purpose flour\n- 1 tsp baking powder\n- 1 tsp salt\n- 1/2 tsp black pepper\n- 1 cup cold water\n- Vegetable oil, for frying\nFor the chips:\n- 4 large potatoes\n- Vegetable oil, for frying\n- Salt, to taste",
"steps": "1\. Start by preparing the fish. In a shallow dish, combine the flour, baking powder, salt, and black pepper.\n2\. Gradually whisk in the cold water until the batter is smooth.\n3\. Heat vegetable oil in a large frying pan or deep fryer.\n4\. Dip the fish fillets into the batter, coating them evenly.\n5\. Gently place the coated fillets into the hot oil and fry for 4-5 minutes on each side, or until golden brown and crispy.\n6\. Remove the fried fish from the oil and place them on a paper towel-lined plate to drain any excess oil.\n7\. For the chips, peel the potatoes and cut them into thick chips.\n8\. Heat vegetable oil in a deep fryer or large pan.\n9\. Fry the chips in batches until golden and crisp.\n10\. Remove the chips from the oil and place them on a paper towel-lined plate to drain any excess oil.\n11\. Season the chips with salt.\n12\. Serve the fish and chips together, and enjoy!"
}
函数调用的结论
可以直接从 Open AI API 使用函数调用的功能。这允许我们每次调用 LLM 时都得到具有相同键的字典格式响应。
使用起来相当直接,你只需声明函数对象,指定名称、描述和关注于你的任务的属性,同时在描述中指定这应该是模型的响应。此外,当调用 API 时,我们可以强制模型使用我们的函数,从而使其更加一致。
这种方法的主要缺点是并不是所有的 LLM 模型和 API 都支持。所以,如果我们想使用 Google PaLM API,我们将不得不使用另一种方法。
LangChain 输出解析器
我们可以选择一个与模型无关的替代方案,就是使用 LangChain。
首先,什么是 LangChain?
LangChain 是一个用于开发由语言模型驱动的应用程序的框架。
这是 LangChain 的官方定义。这个框架最近创建,并已经作为构建由 LLM 驱动的工具的行业标准。
它有一个非常适合我们用例的功能,称为“输出解析器”。在这个模块中,可以创建多个对象来返回和解析来自 LLM 调用的不同类型的格式。实现的方式是,首先声明格式,并将其传递到 LLM 的提示中。然后,使用之前创建的对象来解析响应。
让我们分解代码:
from langchain.prompts import ChatPromptTemplate
from langchain.output_parsers import ResponseSchema, StructuredOutputParser
from langchain.llms import GooglePalm, OpenAI
ingredients = ResponseSchema(
name="ingredients",
description="The ingredients from recipe, as a unique string.",
)
steps = ResponseSchema(
name="steps",
description="The steps to prepare the recipe, as a unique string.",
)
output_parser = StructuredOutputParser.from_response_schemas(
[ingredients, steps]
)
response_format = output_parser.get_format_instructions()
print(response_format)
prompt = ChatPromptTemplate.from_template("What is the recipe for {recipe}? Return the ingredients list and steps separately. \n {format_instructions}")
我们首先要做的是创建我们的响应模式,这将作为解析器的输入。我们为成分和步骤各创建一个,每个都包含一个名称,这将是字典的键,以及一个描述,这将指导 LLM 如何回应。
然后我们从这些响应模式中创建我们的结构化输出解析器。有多种方法可以做到这一点,使用不同风格的解析器。了解更多信息,请查看这里。
最后,我们获取我们的格式说明,并定义我们的提示,将食谱名称和格式说明作为输入。格式说明如下:
"""
The output should be a markdown code snippet formatted in the following schema, including the leading and trailing "```json" 和 "```py":
```json
{
"ingredients": string // 食谱中的成分,作为一个独特的字符串。
"steps": string // 准备食谱的步骤,作为一个独特的字符串。
}
"""
```py
Now what we have left is just calling the API. Here I will demonstrate both the Open AI API and with Google PaLM API.
llm_openai = OpenAI()
llm_palm = GooglePalm()
recipe = ‘炸鱼薯条’
formated_prompt = prompt.format(**{“recipe”:recipe, “format_instructions”:output_parser.get_format_instructions()})
response_palm = llm_palm(formated_prompt)
response_openai = llm_openai(formated_prompt)
print(“PaLM:”)
print(response_palm)
print(output_parser.parse(response_palm))
print(“Open AI:”)
print(response_openai)
print(output_parser.parse(response_openai))
As you can see it is really easy to change between models. The whole structure defined before can be used in the exact same way for any models supported by LangChain. We used also the same parser for both models.
This generated the following output:
PaLM:
{
‘ingredients’: ‘’'- 1 杯通用面粉\n
-
1 茶匙发酵粉\n
-
1/2 茶匙盐\n
-
1/2 杯冷水\n
-
1 个鸡蛋\n
-
1 磅白鱼片,如鳕鱼或鳕鱼\n
-
用于炸制的植物油\n- 1 杯塔塔酱\n
-
1/2 杯麦芽醋\n- 柠檬角’‘’,
‘steps’: ‘’'1. 在一个大碗中,搅拌面粉、发酵粉和盐。\n
2. 在另一个碗中,搅拌鸡蛋和水。\n
3. 将鱼片浸入鸡蛋混合物中,然后在面粉混合物中裹上。\n
4. 在深炸锅或大煎锅中加热油至 375 华氏度(190 摄氏度)。\n
5. 将鱼片两面煎 3-5 分钟,直到金黄酥脆并完全熟透。\n
6. 在纸巾上沥干鱼片。\n
7. 立即将鱼片与塔塔酱、麦芽醋和柠檬角一起上桌。
‘’’
}
Open AI
{
‘ingredients’: '1 ½ 磅鳕鱼片,切成 4 块,
2 杯通用面粉,
2 茶匙发酵粉,
1 茶匙盐,
1 茶匙新鲜磨碎的黑胡椒,
½ 茶匙大蒜粉,
1 杯啤酒(或水),
用于炸制的植物油,
塔塔酱,用于搭配’,
‘steps’: '1. 预热烤箱至 400°F (200°C),并在烤盘上铺上烘焙纸。
2. 在一个中等大小的碗中,将面粉、发酵粉、盐、胡椒粉和大蒜粉混合在一起。
3. 倒入啤酒,搅拌至形成浓稠的面糊。
4. 将鳕鱼浸入面糊中,确保四面都裹上。
5. 在一个大锅或煎锅中加热约 2 英寸(5 厘米)的油,火力中高。
6. 将鳕鱼两面煎 3 到 4 分钟,直到金黄酥脆。
7. 将鳕鱼转移到准备好的烤盘上,烘烤 5 到 7 分钟。
8. 热食,配以塔塔酱。’
}
## 结论:LangChain 输出解析
这种方法也非常好,其主要特点是灵活性。我们创建了一些结构,如响应模式、输出解析器和提示模板,这些结构可以轻松组合在一起,并与不同的模型一起使用。另一个好处是支持多种输出格式。
主要的缺点来自通过提示传递格式指令。这可能会导致随机错误和幻觉。一个真实的例子是,在这个具体的案例中,我需要在响应模式的描述中指定“作为唯一字符串”。如果我没有这样指定,模型会返回一个包含步骤和指令的字符串列表,这会导致输出解析器的解析错误。
# 结论
使用输出解析器来处理 LLM 驱动的应用有多种方法。然而,根据问题的不同,你的选择可能会有所变化。对我而言,我喜欢遵循这个思路:
我总是使用输出解析器,即使我只有一个来自 LLM 的输出。这使我能够控制和指定我的输出。如果我使用的是 Open AI,Function Calling 是我的选择,因为它具有最好的控制能力,并能避免生产应用中的随机错误。然而,如果我使用的是不同的 LLM 或需要不同的输出格式,我的选择是 LangChain,但需要对输出进行大量测试,以便构建最少错误的提示。
感谢阅读。
完整的代码可以在 [这里](https://github.com/gabrielcassimiro17/llm-output-parsing) 找到。
如果你喜欢这些内容并想要支持我,可以请我喝杯咖啡:
[](https://www.buymeacoffee.com/cassimiro?source=post_page-----63b80545b3a7--------------------------------) [## 加布里埃尔·卡西米罗是一个数据科学家,向社区分享免费内容
### 我喜欢支持创作者!
www.buymeacoffee.com](https://www.buymeacoffee.com/cassimiro?source=post_page-----63b80545b3a7--------------------------------)
这里有一些你可能感兴趣的其他文章:
[](/async-calls-for-chains-with-langchain-3818c16062ed?source=post_page-----63b80545b3a7--------------------------------) ## 使用 Langchain 进行链条的异步调用
### 如何使 Langchain 链条与异步调用 LLM 一起工作,从而加快运行顺序长时间任务的速度…
towardsdatascience.com [](/solving-unity-environment-with-deep-reinforcement-learning-836dc181ee3b?source=post_page-----63b80545b3a7--------------------------------) ## 使用深度强化学习解决 Unity 环境问题
### 使用 PyTorch 实现深度强化学习代理的端到端项目代码。
towardsdatascience.com
# 基于 LLM+RAG 的问题回答
> 原文:[`towardsdatascience.com/llm-rag-based-question-answering-6a405c8ad38a?source=collection_archive---------0-----------------------#2023-12-25`](https://towardsdatascience.com/llm-rag-based-question-answering-6a405c8ad38a?source=collection_archive---------0-----------------------#2023-12-25)
## 如何在 Kaggle 上表现不佳,并从中学习 RAG+LLM
[](https://teemukanstren.medium.com/?source=post_page-----6a405c8ad38a--------------------------------)[](https://towardsdatascience.com/?source=post_page-----6a405c8ad38a--------------------------------) [Teemu Kanstrén](https://teemukanstren.medium.com/?source=post_page-----6a405c8ad38a--------------------------------)
·
[关注](https://medium.com/m/signin?actionUrl=https%3A%2F%2Fmedium.com%2F_%2Fsubscribe%2Fuser%2F9fc0679190dc&operation=register&redirect=https%3A%2F%2Ftowardsdatascience.com%2Fllm-rag-based-question-answering-6a405c8ad38a&user=Teemu+Kanstr%C3%A9n&userId=9fc0679190dc&source=post_page-9fc0679190dc----6a405c8ad38a---------------------post_header-----------) 发表在 [Towards Data Science](https://towardsdatascience.com/?source=post_page-----6a405c8ad38a--------------------------------) ·23 分钟阅读·2023 年 12 月 25 日[](https://medium.com/m/signin?actionUrl=https%3A%2F%2Fmedium.com%2F_%2Fvote%2Ftowards-data-science%2F6a405c8ad38a&operation=register&redirect=https%3A%2F%2Ftowardsdatascience.com%2Fllm-rag-based-question-answering-6a405c8ad38a&user=Teemu+Kanstr%C3%A9n&userId=9fc0679190dc&source=-----6a405c8ad38a---------------------clap_footer-----------)
--
[](https://medium.com/m/signin?actionUrl=https%3A%2F%2Fmedium.com%2F_%2Fbookmark%2Fp%2F6a405c8ad38a&operation=register&redirect=https%3A%2F%2Ftowardsdatascience.com%2Fllm-rag-based-question-answering-6a405c8ad38a&source=-----6a405c8ad38a---------------------bookmark_footer-----------)
图像由 ChatGPT+/DALL-E3 生成,展示了关于 RAG 的文章的插图。
检索增强生成(RAG)似乎现在相当受欢迎。随着大语言模型(LLM)的兴起,它成为了使 LLM 在特定任务上表现更好的热门技术之一,比如对内部文档进行问答。前段时间,我参加了一个 [Kaggle 比赛](https://www.kaggle.com/competitions/kaggle-llm-science-exam),这让我能够尝试它,并比自己随意实验学到更多一些。以下是从这些实验中获得的一些经验教训。
除非另有说明,否则所有图片均由作者提供。生成工具为 ChatGPT+/DALL-E3(如有注明),或取自我个人的 Jupyter 笔记本。
# RAG 概述
RAG 有两个主要部分:检索和生成。在第一部分中,检索用于获取与查询相关的(块)文档。生成则使用这些检索到的块作为额外输入,即 *context*,传递给第二部分的答案生成模型。这个附加的上下文旨在为生成器提供比基本训练数据更及时、更好的信息,以生成答案。
# 构建 RAG 输入,或文本分块
LLM 的最大上下文或序列窗口长度是它们可以处理的范围,RAG 生成的输入上下文需要足够短以适应这个序列窗口。我们希望将尽可能多的相关信息纳入这个上下文,因此从潜在的输入文档中获取最佳的“块”文本非常重要。这些块应当是生成正确答案所需的最相关的内容。
作为第一步,输入文本通常会被分块成更小的片段。RAG 的一个基本预处理步骤是使用特定的嵌入模型将这些块转换为嵌入。一个典型的嵌入模型的序列窗口为 512 个 tokens,这也使其成为实际的分块目标。一旦文档被分块并编码为嵌入,就可以使用这些嵌入进行相似性搜索,以构建生成答案的上下文。
我发现 [Langchain](https://github.com/langchain-ai/langchain) 提供了有用的工具用于输入加载和文本分块。例如,使用 Langchain 对文档进行分块(在此情况下,使用 [Flan-T5-Large](https://huggingface.co/google/flan-t5-large) 模型的分词器)是非常简单的:
```py
from transformers import AutoTokenizer
from langchain.text_splitter import RecursiveCharacterTextSplitter
#This is the Flan-T5-Large model I used for the Kaggle competition
llm = "/mystuff/llm/flan-t5-large/flan-t5-large"
tokenizer = AutoTokenizer.from_pretrained(llm, local_files_only=True)
text_splitter = RecursiveCharacterTextSplitter
.from_huggingface_tokenizer(tokenizer, chunk_size=12,
chunk_overlap=2,
separators=["\n\n", "\n", ". "])
section_text="Hello. This is some text to split. With a few "\
"uncharacteristic words to chunk, expecting 2 chunks."
texts = text_splitter.split_text(section_text)
print(texts)
这将生成以下两个块:
['Hello. This is some text to split',
'. With a few uncharacteristic words to chunk, expecting 2 chunks.']
在上面的代码中,chunk_size 12 告诉 LangChain 旨在每个块最多包含 12 个 token。根据文本结构,这可能并不总是 100% 精确。然而,根据我的经验,这通常效果很好。需要记住的是 tokens 和单词之间的区别。下面是对上述 section_text 进行分词的一个示例:
section_text="Hello. This is some text to split. With a few "\
"uncharacteristic words to chunk, expecting 2 chunks."
encoded_text = tokenizer(section_text)
tokens = tokenizer.convert_ids_to_tokens(encoded_text['input_ids'])
print(tokens)
生成的输出 tokens:
['▁Hello', '.', '▁This', '▁is', '▁some', '▁text', '▁to', '▁split', '.',
'▁With', '▁', 'a', '▁few', '▁un', 'character', 'istic', '▁words',
'▁to', '▁chunk', ',', '▁expecting', '▁2', '▁chunk', 's', '.', '</s>']
大多数 section_text 中的单词本身形成一个 token,因为它们是文本中的常见单词。然而,对于特殊形式的单词或领域词汇,这可能会更复杂。例如,在这里,“uncharacteristic” 这个词变成了三个 tokens [“ un”, “ character”, “ istic”]。这是因为模型的分词器知道这三个部分词汇,但不知道整个单词(“ uncharacteristic”)。每个模型都有自己的分词器来匹配输入和模型训练中的这些规则。
在分块中,来自 Langchain 的RecursiveCharacterTextSplitter用于上述代码中,计算这些令牌,并寻找给定的分隔符将文本拆分成请求的块。不同块大小的试验可能会有用。在我的 Kaggle 实验中,我从嵌入模型的最大大小开始,即 512 个令牌。然后尝试了 256、128 和 64 个令牌的块大小。
示例 RAG 查询
我提到的Kaggle 比赛是基于维基百科数据的多项选择题回答。任务是从多个选项中选择每个问题的正确答案。显而易见的方法是使用 RAG 从维基百科数据中找到所需的信息,并用它来生成正确答案。以下是比赛数据中的第一个问题及其答案选项,用于说明:
示例问题和答案选项 A-E。
多项选择题是尝试 RAG 的一个有趣话题。但我相信,最常见的 RAG 用例是根据源文档回答问题。有点像聊天机器人,但通常是针对特定领域或(公司)内部文档的问答。我在本文中使用这个基本的问答用例来展示 RAG。
作为本文的 RAG 示例问题,我需要一个 LLM 无法仅凭其训练数据直接回答的问题。我使用了维基百科数据,因为它可能是 LLM 训练数据的一部分,所以我需要一个与模型训练后相关的问题。我为本文使用的模型是Zephyr 7B beta,于 2023 年初训练完成。最后,我决定询问Google Bard AI 聊天机器人。它在 Zephyr 训练日期之后的一年里有很多发展。我对 Bard 也有一定了解,以评估 LLM 的答案。因此,我使用“what is google bard?”作为本文的示例问题。
嵌入向量
RAG 的第一阶段检索基于嵌入向量,这些向量实际上只是多维空间中的点。它们看起来像这样(这里只列出了前 10 个值):
q_embeddings[:10]
array([-0.45518905, -0.6450379, 0.3097812, -0.4861114 , -0.08480848,
-0.1664767 , 0.1875889, 0.3513346, -0.04495572, 0.12551129],
这些嵌入向量可以用来比较单词/句子及其相互关系。这些向量可以通过嵌入模型构建。可以在MTEB 排行榜找到各种统计数据的模型集。使用这些模型之一就像这样简单:
from sentence_transformers import SentenceTransformer, util
embedding_model_path = "/mystuff/llm/bge-small-en"
embedding_model = SentenceTransformer(embedding_model_path, device='cuda')
HuggingFace 上的模型页面通常显示示例代码。上述代码从本地磁盘加载模型“ bge-small-en ”。使用此模型创建嵌入只是:
question = "what is google bard?"
q_embeddings = embedding_model.encode(question)
在这种情况下,嵌入模型用于将给定的问题编码为嵌入向量。该向量与上面的示例相同:
q_embeddings.shape
(, 384)
q_embeddings[:10]
array([-0.45518905, -0.6450379, 0.3097812, -0.4861114 , -0.08480848,
-0.1664767 , 0.1875889, 0.3513346, -0.04495572, 0.12551129],
dtype=float32)
形状 (, 384) 表示 q_embeddings 是一个长度为 384 个浮点数的单一向量(而不是一次嵌入多个文本的列表)。上面的切片显示了这 384 个值中的前 10 个。某些模型使用更长的向量以获得更准确的关系,而其他模型,如本例所示,则使用较短的向量(此处为 384)。再次,MTEB 排行榜 提供了很好的示例。较小的向量需要更少的空间和计算,而较大的向量在表示块之间的关系以及有时的序列长度方面提供了一些改进。
对于我的 RAG 相似性搜索,我首先需要问题的嵌入。这就是上面的 q_embeddings。需要将其与所有被搜索文章(或其块)的嵌入向量进行比较。在这种情况下,所有被分块的维基百科文章。要为所有这些构建嵌入:
article_embeddings = embedding_model.encode(article_chunks)
这里 article_chunks 是来自英文维基百科数据转储的所有文章的所有块的列表。这样它们可以批量编码。
向量数据库
在大规模文档/文档块上实现相似性搜索,在基本层面上并不复杂。一个常见的方法是计算查询和文档向量之间的 余弦相似性,并进行排序。然而,在大规模时,这有时会变得有些复杂。向量数据库是使这种管理和搜索在规模上变得更简单/更高效的工具。
例如,Weaviate 是一个向量数据库,曾用于 StackOverflow 的基于 AI 的搜索。在其最新版本中,它也可以以 嵌入模式 使用,这使得它甚至可以在 Kaggle 笔记本中使用。它也被用于一些 Deeplearning.AI LLM 短期课程,所以至少似乎有些受欢迎。当然,还有许多其他工具,进行比较是很好的,这个领域也在快速发展。
在我的试验中,我使用了来自 Facebook/Meta 研究的 FAISS 作为向量数据库。FAISS 更像是一个库,而不是一个客户端-服务器数据库,因此在 Kaggle 笔记本中使用非常简单。它的表现也很不错。
分块数据和嵌入
一旦所有文章的分块和嵌入完成后,我构建了一个包含所有相关信息的 Pandas DataFrame。以下是我使用的维基百科数据转储前 5 个块的示例,文档标题为 无政府主义:
我使用的维基百科数据转储中的第一篇文章的前 5 个块。
这个表格中的每一行(一个 Pandas DataFrame)包含块化过程后单个块的数据。它有 5 列:
-
chunk_id:允许我稍后将块嵌入映射到块文本。
-
doc_id:允许将块映射回其文档。
-
doc_title:用于尝试一些方法,例如将文档标题添加到每个块中。
-
chunk_title:块的文章子部分标题,与 doc_title 目的相同。
-
chunk:实际的块文本
这里是前五个无政府主义块的嵌入,顺序与上面的 DataFrame 相同:
[[ 0.042624 -0.131264 -0.266858 ... -0.329627 0.178211 0.248001]
[-0.120318 -0.110153 -0.059611 ... -0.297150 -0.043165 0.558150]
[ 0.116761 -0.066759 -0.498548 ... -0.330301 0.019448 0.326484]
[-0.517585 0.183634 0.186501 ... 0.134235 -0.033262 0.498731]
[-0.245819 -0.189427 0.159848 ... -0.077107 -0.111901 0.483461]]
每一行在这里部分展示,但说明了概念。
搜索相似的查询嵌入与块嵌入
早些时候,我对查询“ what is google bard? ”进行了编码,然后编码了所有文章块。通过这两组嵌入,RAG 搜索的第一部分很简单:找到“语义上”最接近查询的文档。实际上,只需计算查询嵌入向量与所有块向量之间的余弦相似度,并按相似度得分排序。
这里是与 q_embeddings 语义上最接近的前 10 个块:
按照与问题的余弦相似度排序的前 10 个块。
这个表格中的每一行(DataFrame)表示一个块。这里的 sim_score 是计算的余弦相似度得分,行从最高余弦相似度到最低排序。表格显示了前 10 个最高的 sim_score 行。
Re-ranking
纯粹基于嵌入的相似性搜索在计算上非常快速且低成本。然而,它不如其他一些方法准确。Re-ranking 是一个术语,用于描述使用另一个模型更准确地排序这些初始文档列表的过程,这种模型通常计算成本更高。这个模型通常在所有文档和块上运行的成本太高,但在初始相似性搜索后的前几个块上运行就可行得多。Re-ranking 帮助获得更好的最终块列表,以便为 RAG 的生成部分建立输入上下文。
同样的 MTEB 排行榜 也托管了许多模型的 re-ranking 得分。在这种情况下,我使用了 bge-reranker-base 模型进行 re-ranking:
import torch
from transformers import AutoModelForSequenceClassification, AutoTokenizer
rerank_model_path = "/mystuff/llm/bge-reranker-base"
rerank_tokenizer = AutoTokenizer.from_pretrained(rerank_model_path)
rerank_model = AutoModelForSequenceClassification
.from_pretrained(rerank_model_path)
rerank_model.eval()
def calculate_rerank_scores(pairs):
with torch.no_grad(): inputs = rerank_tokenizer(pairs, padding=True,
truncation=True, return_tensors='pt',
max_length=512)
scores = rerank_model(**inputs, return_dict=True)
.logits.view(-1, ).float()
return scores
question = questions[idx]
pairs = [(question, chunk) for chunk in doc_chunks_all[idx]]
rerank_scores = calculate_rerank_scores(pairs)
df["rerank_score"] = rerank_scores
在将 rerank_score 添加到块 DataFrame 并用它进行排序后:
按照与问题的重新排序得分排序的前 10 个块。
比较上面的两个表格(第一个按sim_score排序,现按rerank_score排序),可以看到一些明显的差异。按嵌入生成的普通相似性得分(sim_score)排序, Tenor 页面 是第 5 个最相似的片段。由于 Tenor 似乎是一个由 Google 托管的 GIF 搜索引擎,我想看到它的嵌入与问题“what is google bard?”接近是有道理的。但它实际上与 Bard 本身没有什么关系,只是 Tenor 是一个在类似领域的 Google 产品。
然而,在按rerank_score排序后,结果更有意义。Tenor 从前 10 名中消失了,前 10 名列表中的最后两个片段似乎不相关。这些片段关于“Bard”和“Bård”的名字。可能是因为有关 Google Bard 的最佳信息来源似乎是 Google Bard 页面,在上述表格中这是 id 为 6026776 的文档。之后,我猜 RAG 用完了好的文章匹配,并有些偏离了正轨(Bård)。这也可以从表格最后两行/片段的负面重新排序得分中看到。
通常会有许多相关文档和文档中的片段,而不仅仅是上面提到的 1 份文档和 8 个片段。但是在这种情况下,这种限制有助于说明基于基本嵌入的相似性搜索和重新排序之间的区别,以及重新排序如何积极地影响最终结果。
构建上下文
当我们收集了 RAG 输入的顶级片段后,我们该怎么做?我们需要从这些片段中为生成模型构建上下文。最简单的方法就是将选择的顶级片段连接成一个长文本序列。该序列的最大长度受所用模型的限制。由于我使用了 Zephyr 7B 模型,所以我将 4096 个标记作为最大长度。 Zephyr 页面将此作为一个灵活的序列限制(带有滑动注意窗口)。更长的上下文似乎更好,但 这并不总是如此。最好尝试一下。
以下是我用来生成具有此上下文的答案的基本代码:
from transformers import AutoTokenizer, AutoModelForCausalLM
import torch
llm_answer_path = "/mystuff/llm/zephyr-7b-beta"
torch_device = "cuda:0"
tokenizer = AutoTokenizer.from_pretrained(llm_answer_path,
local_files_only=True)
llm_answer = AutoModelForCausalLM.from_pretrained(llm_answer_path,
device_map=torch_device, local_files_only=True,
torch_dtype=torch.float16)
# assuming here that "context" contains the pre-built context
query = "answer the following question, "\
"based on your knowledge and the provided context. "\n
"Keep the answer concise.\n\nquestion:" + question +
"\n\ncontext:"+context
input_ids = tokenizer.encode(query+"\n\nANSWER:", return_tensors='pt',
return_attention_mask=False).to(torch_device)
greedy_output = llm_answer.generate(input_ids, max_new_tokens=1024,
do_sample=True)
answer = tokenizer.decode(greedy_output[0], skip_special_tokens=True)
print(answer[len(query):])
如前所述,在这种情况下,上下文只是将排名最高的片段连接起来。
生成答案
进行比较,首先让我们尝试模型在没有任何额外上下文的情况下的回答,即仅基于其训练数据:
query = "what is google bard?"
input_ids = tokenizer.encode(query+"\n\nANSWER:", return_tensors='pt',
return_attention_mask=False).to(torch_device)
greedy_output = llm_answer.generate(input_ids, max_new_tokens=1024,
do_sample=True)
answer = tokenizer.decode(greedy_output[0], skip_special_tokens=True)
print(answer[len(query):])
这给出了(多次运行之一,略有变化但通常相似):
ANSWER:
Google Bard is an experimental, AI-based language model developed by
Google's sister company, DeepMind. Its primary use is to generate
human-like text responses to prompts, which can help in tasks such as
content creation, idea generation, and text summarization. Bard is
trained on a vast amount of textual data and can provide highly
relevant and contextually accurate responses, making it a useful tool
in various applications where text generation is required. However, as
an experimental feature, Bard's accuracy and effectiveness may still be
improving, and it is not yet publicly available for use.
通常来说,虽然准确,但缺乏最新的发展。相比之下,我们可以尝试将生成的上下文提供给问题:
query = "answer the following question, "\
"based on your knowledge and the provided context. "\
"Keep the answer concise.\n\n"\
"question:" + question + "\n\ncontext:"+context
input_ids = tokenizer.encode(query+"\n\nANSWER:", return_tensors='pt',
return_attention_mask=False).to(torch_device)
greedy_output = llm_answer.generate(input_ids, max_new_tokens=1024,
do_sample=True)
answer = tokenizer.decode(greedy_output[0], skip_special_tokens=True)
print(answer[len(query):])
以下是一个示例答案,使用了按sim_score排序的片段作为上下文(包括 Tenor 和 Bård 页面片段):
ANSWER:
Bard is a conversational generative artificial intelligence (AI)
chatbot developed by Google based initially on the LaMDA family of
large language models (LLMs) and later PaLM. It was launched in a
limited capacity in March 2023 to mixed reviews, and expanded to other
countries in May, following the launch of Microsoft's Bing Chat. Google
researcher Jacob Devlin resigned from the company after alleging Bard
had surreptitiously leveraged data from ChatGPT. Tenor is an online GIF
search engine and database owned by Google, available on Android, iOS,
and macOS. Its main product is the GIF Keyboard, which is integrated
into numerous apps and messaging services. Bård is a Norwegian
masculine given name of Old Norse origin. It is sometimes used as a
surname. It may refer to several people. Tenor and Bård are not related.
这不是一个很好的答案,因为它开始谈论完全无关的话题,Tenor和Bård。部分原因是因为在这种情况下,Tenor 块被包含在上下文中,块的顺序也通常较差,因为没有重新排序。
相比之下,使用rerank_score排序的上下文块(更好的块排序和 Tenor 消失):
ANSWER:
Google Bard is a conversational generative artificial intelligence (AI)
chatbot, based initially on the LaMDA family of large language models
(LLMs) and later on PaLM, developed by Google to compete with OpenAI's
ChatGPT. It was rolled out in a limited capacity in March 2023 and
expanded to more countries in May, prompting a mixed reception from
critics, who raised safety and ethical concerns about its accuracy and
usefulness. Google has promised that Bard will be tightly integrated
with other Google AI products and services, leading to claims that a
new AI-powered version of the Google Assistant, dubbed "Assistant with
Bard", is being prepared for launch. Google has also stressed that Bard
is still in its early stages and being continuously refined, with plans
to upgrade it with new personalization and productivity features, while
stressing that it remains distinct from Google Search.
现在不相关的主题已经消失,答案总体上更好,更切题。
这突出了不仅要找到适当的上下文以提供给模型,而且还要去除无关的上下文。在这种情况下,Zephyr 模型似乎无法直接识别哪个部分的上下文是相关的,而是似乎对所有内容进行了总结。不能真正责怪模型,因为我提供了这些上下文并要求它使用这些内容。
查看块的重新排序分数,基于诸如负面重新排序分数等指标的一般过滤方法也可以解决上述问题,因为在这种情况下“坏”块具有负面重新排序分数。
值得注意的是,Google 在我写这篇文章时发布了一个全新的、显著改进的Gemini模型系列。由于维基百科的内容生成有一些延迟,因此这里生成的答案没有提到这个模型。因此,如人们所想,尝试保持上下文的信息是最新的,并保持其相关性和重点是很重要的。
可视化嵌入检查
嵌入是一个很好的工具,但有时确实很难真正理解它们是如何工作的,以及相似度搜索发生了什么。一个基本的方法是将嵌入彼此绘制,以获得一些关于它们关系的见解。
构建这样的可视化使用PCA和可视化库是相当简单的。它涉及将嵌入向量映射到 2 维或 3 维,并绘制结果。在这里,我将这 384 维映射到 2 维,并绘制了结果:
import seaborn as sns
import numpy as np
fp_embeddings = embedding_model.encode(first_chunks)
q_embeddings_reshaped = q_embeddings.reshape(1, -1)
combined_embeddings = np.concatenate((fp_embeddings, q_embeddings_reshaped))
df_embedded_pca = pd.DataFrame(X_pca, columns=["x", "y"])
# text is short version of chunk text (plot title)
df_embedded_pca["text"] = titles
# row_type = article or question per each embedding
df_embedded_pca["row_type"] = row_types
X = combined_embeddings pca = PCA(n_components=2).fit(X)
X_pca = pca.transform(X)
plt.figure(figsize=(16,10))
sns.scatterplot(x="x", y="y", hue="row_type",
palette={"article": "blue", "question": "red"},
data=df_embedded_pca, #legend="full",
alpha=0.8, s=100 )
for i in range(df_embedded_pca.shape[0]):
plt.annotate(df_embedded_pca["text"].iloc[i],
(df_embedded_pca["x"].iloc[i], df_embedded_pca["y"].iloc[i]),
fontsize=20 )
plt.legend(fontsize='20')
# Change the font size for x and y axis ticks plt.xticks(fontsize=16)
plt.yticks(fontsize=16)
# Change the font size for x and y axis labels
plt.xlabel('X', fontsize=16)
plt.ylabel('Y', fontsize=16)
对于“what is google bard?”问题的前 10 篇文章,这里给出了以下可视化:
基于 PCA 的 2D 绘图,比较问题嵌入与文章第一个块嵌入。
在这个图中,红点是问题“what is google bard?”的嵌入。蓝点是根据sim_score找到的最接近的维基百科文章匹配项。
Bard 文章显然是与问题最接近的,而其他的则稍远一些。Tenor 文章似乎是第二接近的,而Bård 文章则稍远一些,可能是因为从 384 维映射到 2 维时信息的丢失。由于这一点,可视化并不是完全准确的,但对快速人工概览是有帮助的。
下图展示了我在 Kaggle 代码中发现的实际错误,使用了类似的 PCA 图。为了获取一些见解,我对维基百科转储中的第一篇文章(“ 无政府主义”)提出了一个简单的问题:“ 无政府主义的定义是什么?”。下面是 PCA 可视化的结果,标记的离群点可能是最有趣的部分:
我在 PCA 基于 2D 图的 Kaggle 嵌入中显示的失败,针对所选的顶级文档。
左下角的红点再次表示问题。旁边的蓝点簇是所有与无政府主义相关的文章。然后右上角有两个离群点。我删除了图表中的标题以保持其可读性。查看时,这两个离群文章似乎与问题无关。
为什么会这样?由于我使用了 512、256、128 和 64 的各种块大小来索引文章,在处理 256 块大小的所有文章时遇到了一些问题,并在中途重新启动了分块。这导致某些嵌入与我存储的块文本的索引有所不同。在注意到这些奇怪的结果后,我重新计算了 256 个令牌块大小的嵌入,并将结果与 512 大小进行比较,注意到这个差异。可惜那时比赛已经结束🙂
更高级的上下文选择
上述内容讨论了将文档分块并使用相似度搜索+重新排序作为找到相关块和构建问题回答上下文的方法。我发现有时也有必要考虑初始文档的选择方式,而不仅仅是块本身。
作为示例方法,高级 RAG课程在DeepLearning.AI上介绍了两种方法:句子窗口化和层次块合并。总结来说,这种方法查看附近的块,如果多个块的分数很高,则将它们作为一个大的块。所谓“层次结构”是通过考虑越来越大的块组合来共同相关。旨在提供更连贯的上下文,而不是随机排序的小块,给生成 LLM 更好的输入。
作为一个简单的示例,这是我上面 Bard 示例的重新排序的前几个块:
我在 Bard 示例中的前 10 个块,按重新排序分数排序。
这里最左侧的列是块的索引。在我的生成中,我只是按表中的排序顺序取了顶级块。如果我们想使上下文更连贯,我们可以按文档中的顺序对最终选择的块进行排序。如果在高度排名的块之间有小片段缺失,添加缺失的部分(例如这里的块 ID 7)可能有助于填补空白,类似于层次合并。这可能是作为最终步骤进行尝试的内容,以获得最终的改进。
在我的 Kaggle 实验中,我仅基于第一个块进行初步文档选择。这部分是由于 Kaggle 的资源限制,但它似乎也有一些其他的优势。通常,一篇文章的开头部分充当了总结(引言或摘要)。从这些排名文章中进行初步块选择可能有助于选择具有更相关整体上下文的块。
在我上面的 Bard 示例中可以看到,无论是 rerank_score 还是 sim_score,对于最佳文章的第一个块都是最高的。为了改进这一点,我还尝试使用更大的块大小进行初始文档选择,以包括更多的引言以提高相关性。然后将顶级选择的文档按较小的块大小进行切分,以实验每种大小的上下文效果。
由于资源限制,我无法在 Kaggle 上对所有文档的所有块进行初始搜索,但我在 Kaggle 外部尝试了一下。在这些试验中,我发现有时单个不相关的文章块会被排名较高,而实际上对答案生成存在误导。例如,与相关电影的演员传记。初步的文档相关性选择可能有助于避免这种情况。不幸的是,我没有时间用不同的配置进一步研究这个问题,好的重排名可能已经有帮助。
最后,在上下文中重复相同的信息在多个块中不是很有用。块的最高排名并不保证它们彼此最好地补充,或最佳块多样性。例如,LangChain 为 最大边际相关性 提供了一个特殊的块选择器。它通过对新块进行惩罚,惩罚依据是它们与已添加块的相似度来实现这一点。
扩展 RAG 查询
我在这里使用了一个非常简单的问题/查询作为我的 RAG 示例(“ what is google bard?”),简单的查询有助于说明基本的 RAG 概念。考虑到我使用的嵌入模型具有 512 令牌的最大序列长度,这个查询输入相当简短。如果我使用嵌入模型的分词器(bge-small-en)将这个问题编码成令牌,我会得到以下令牌:
['[CLS]', 'what', 'is', 'google', 'bard', '?', '[SEP]']
这总共是 7 个标记。最大序列长度为 512,这为我使用更长的查询句子留出了足够的空间。有时这很有用,特别是当我们想要检索的信息不是简单的查询,或者领域较复杂时。对于非常简单的查询,语义搜索可能效果不好,正如在Stack Overflows AI Journey 文章中提到的那样。
例如,Kaggle 比赛有一组问题,每个问题有 5 个答案选项可以选择。我最初尝试了仅将问题作为嵌入模型的输入的 RAG。搜索结果并不理想,因此我再次尝试了将问题 + 所有答案选项作为查询。这产生了更好的结果。
作为一个例子,比赛训练数据集中的第一个问题:
Which of the following statements accurately describes the impact of
Modified Newtonian Dynamics (MOND) on the observed "missing baryonic mass"
discrepancy in galaxy clusters?
对于 bge-small-en 模型,这需要 32 个标记。因此,最大 512 个标记的序列长度还剩大约 480 个标记可以填充。
这里是第一个问题及其给出的 5 个答案选项:
示例问题和答案选项 A-E。将所有这些文本合并形成了查询。
将问题和给定的选项合并成一个 RAG 查询,得到的长度为 235 个标记,并且仍然有超过 50% 的嵌入模型序列长度剩余。就我而言,这种方法产生了更好的结果,无论是通过人工检查,还是比赛分数。因此,尝试不同的方式使 RAG 查询本身更具表现力是值得的。
幻觉
最后是关于幻觉的话题,即模型生成的文本不正确或虚构。我的 sim_score 排序中的 Tenor 示例就是一个例子,即使生成器确实基于实际的给定上下文。因此,我想最好还是保持上下文良好 😃.
为了解决幻觉问题,大型 AI 公司(Google Bard,ChatGPT,Bing Chat)的聊天机器人都提供了将其生成的回答部分链接到可验证来源的方法。Bard 有一个特定的 “G” 按钮,可以执行 Google 搜索,并突出显示与搜索结果匹配的生成回答部分。可惜我们并不总是拥有世界级的搜索引擎来帮助处理我们的数据。
Bing Chat采用了类似的方法,突出显示答案的部分并添加对源网站的引用。ChatGPT的方法略有不同;我必须明确要求它验证其答案并更新最新进展,告诉它使用其浏览器工具。之后,它进行了互联网搜索并链接到特定网站作为来源。源的质量似乎有很大的变化,就像任何互联网搜索一样。当然,对于内部文档,这种类型的网络搜索是不可能的。然而,即使在内部,也应该始终可以链接到来源。
我还询问了 Bard、ChatGPT+和 Bing 关于检测幻觉的想法。结果包括一个 LLM 幻觉排名指数,以及RAG 幻觉。在调优 LLM 时,将温度参数设为零可能有助于 LLM 生成确定性的、最可能的输出令牌。
最后,由于这是一个非常常见的问题,似乎有各种方法正在被构建以更好地解决这一挑战。例如,特定的LLM 来帮助检测幻觉似乎是一个有前途的领域。我没有时间尝试它们,但在更大的项目中肯定是相关的。
评估结果
除了实现一个有效的 RAG 解决方案之外,能够评估它的效果也是很有价值的。在 Kaggle 比赛中,这相当简单。我只是运行解决方案以尝试回答训练数据集中的给定问题,并将其与训练数据中提供的正确答案进行比较。或者将模型提交到 Kaggle 比赛测试集进行评分。答案分数越高,RAG 解决方案就越好,即使分数背后还有更多内容。
在许多情况下,可能没有适用于领域特定 RAG 的合适评估数据集。对于这种情况,可以考虑从一些通用 NLP 评估数据集开始,例如这个列表。像 LangChain 这样的工具还提供了自动生成问题和答案并进行评估的支持。在这种情况下,使用一个 LLM 为给定文档集创建示例问题和答案,另一个 LLM 用于评估 RAG 是否能够提供这些问题的正确答案。也许可以在这个LangChain 的 RAG 评估教程中更好地解释。
虽然通用解决方案在开始时可能很好,但在实际项目中,我会尝试收集来自领域专家和目标用户的真实问题和答案数据集。由于大型语言模型(LLM)通常被期望生成自然语言响应,这些响应可能在正确的前提下变化很大。因此,评估答案是否正确不像正则表达式或类似的模式匹配那么直接。在这种情况下,我发现使用另一种 LLM 来评估给定的响应是否匹配参考响应是一个非常有用的工具。这些模型能够更好地处理文本变异。
结论
RAG 是一个非常不错的工具,随着对 LLM 的高度关注,它现在也是一个相当热门的话题。虽然 RAG 和嵌入技术已经存在了很长时间,但最新的强大 LLM 及其快速演变可能使它们在许多高级应用场景中更具吸引力。我预计这一领域将持续以良好的速度发展,有时很难跟上所有最新动态。为此,像RAG 发展综述这样的总结可以提供至少保持主要发展方向的参考。
一般来说,RAG 方法相当简单:找到与给定查询类似的一组文本块,将它们拼接成上下文,然后向 LLM 请求答案。然而,正如我在这里尝试展示的那样,在如何使这一过程对不同需求有效且高效方面,可能会有各种问题需要考虑。从良好的上下文检索,到排名和选择最佳结果,最后能够将结果链接回实际的源文档。还要评估生成的查询上下文和答案。正如Stack Overflow 的人们指出的,有时更传统的词汇搜索或混合搜索也非常有用,即使语义搜索也很酷。
今天就到这里。RAG 继续…
ChatGPT+/DALL-E3 对 RAG 的理解…
最初发布于 http://teemukanstren.com 于 2023 年 12 月 25 日。
LLMOps:与 Hamilton 一起进行生产级提示工程模式
生产级别的提示迭代概述,与 Hamilton 一起进行
·
关注 发表在 Towards Data Science ·13 分钟阅读·2023 年 9 月 13 日
–
提示。在生产环境中,如何演变这些提示?这篇文章基于最初发表的内容 这里. 图片来自 pixabay.
你发送给大型语言模型(LLM)的内容非常重要。小的变化和调整可能对输出产生重大影响,因此随着你的产品发展,你的提示也需要进化。LLMs 也在不断开发和发布,因此随着 LLMs 的变化,你的提示也需要变化。因此,建立一个迭代模式来操作化你的“部署”提示是重要的,以便你和你的团队可以高效地移动,同时确保生产问题最小化,甚至避免。在这篇文章中,我们将通过Hamilton这一个开源微调度框架来指导你管理提示的最佳实践,并类比MLOps模式,并讨论其中的权衡。这篇文章的高级要点即使你不使用 Hamilton 也同样适用。
在我们开始之前几点注意事项:
-
我是Hamilton的共同创建者之一。
-
对Hamilton不熟悉?请滚动到底部查看更多链接。
-
如果你在寻找讨论“上下文管理”的文章,这不是那篇文章。但这篇文章将帮助你了解如何迭代和创建生产级别的“提示上下文管理”迭代故事。
-
我们将提示和提示模板互换使用。
-
我们假设这些提示是在“在线”网页服务设置中使用的。
-
我们将使用我们的Hamilton 的 PDF 摘要示例来映射我们的模式。
-
我们的信誉是什么?我们在为 Stitch Fix 的 100 多位数据科学家构建自服务数据/MLOps 工具方面度过了职业生涯。因此,我们见证了很多故障和方法的演变。
提示对 LLMs 的作用类似于超参数对 ML 模型的作用。
要点: 提示+LLM APIs 类似于超参数+机器学习模型。
就“Ops”实践而言,LLMOps 仍处于起步阶段。MLOps 稍微成熟一点,但如果与 DevOps 实践的广泛知识相比,仍然没有被广泛采用。
DevOps 实践主要关注如何将代码交付到生产环境,而 MLOps 实践关注如何将代码***& 数据工件***(例如,统计模型)交付到生产环境。那么 LLMOps 呢?个人认为,它更接近 MLOps,因为你有:
-
你的 LLM 工作流仅仅是代码。
-
LLM API 是一个数据工件,可以使用提示“调整”,类似于机器学习(ML)模型及其超参数。
因此,你很可能需要紧密版本控制 LLM API 和提示,以确保良好的生产实践。例如,在 MLOps 实践中,你需要一个过程来验证 ML 模型在其超参数更改时仍然表现正确。
你应该如何考虑提示的操作化?
需要明确的是,控制的两个部分是LLM和提示。类似于 MLOps,当代码或模型工件发生变化时,你需要能够确定是哪一部分发生了变化。对于 LLMOps,我们也需要相同的辨别能力,将 LLM 工作流与 LLM API + 提示分开。重要的是,我们应该认为 LLM(自托管或 API)大多是静态的,因为我们不经常更新(甚至控制)它们的内部。因此,改变 LLM API + 提示的提示部分实际上就像是创建一个新的模型工件。
处理提示的主要方式有两种:
-
提示作为动态运行时变量。所使用的模板在部署时不是静态的。
-
提示作为代码。提示模板在给定的部署下是静态的/预定的。
主要区别在于你需要管理的移动部分的数量,以确保一个良好的生产故事。下面,我们将探讨如何在这两种方法的背景下使用 Hamilton。
提示作为动态运行时变量
动态传递/加载提示
提示只是字符串。由于字符串在大多数语言中是原始类型,这意味着它们非常容易传递。这个想法是抽象你的代码,使你在运行时传递所需的提示。更具体地说,你会在有“更新”的提示模板时“加载/重新加载”提示模板。
这里的 MLOps 类比是,当有新的模型可用时,自动重新加载 ML 模型工件(例如,pkl 文件)。
MLOps 类比:图示 ML 模型自动重新加载的效果。图片作者。
图示动态重新加载/查询提示的效果。图片作者。
这里的好处是你可以非常迅速地推出新的提示,因为你不需要重新部署你的应用程序!
这种迭代速度的缺点是增加了操作负担:
-
对于监控你的应用程序的人来说,什么时候发生了变化以及是否已经在你的系统中传播开来将是不清楚的。例如,你刚刚推送了一个新的提示,而 LLM 现在每个请求返回更多的 token,导致延迟激增;监控的人可能会感到困惑,除非你有一个优秀的变更日志文化。
-
回滚语义涉及到需要了解另一个系统。你不能仅仅回滚之前的部署来修复问题。
-
你需要优秀的监控来了解运行了什么以及何时运行;例如,当客户服务给你一个调查的票据时,你怎么知道使用了什么提示?
-
你需要管理和监控你用来管理和存储提示的任何系统。这将是一个你需要维护的额外系统,超出了提供你代码的系统之外。
-
你需要管理两个过程,一个用于更新和推送服务,另一个用于更新和推送提示。同步这些更改将由你负责。例如,你需要对服务进行代码更改以处理新的提示。你需要协调更改两个系统以使其工作,这增加了额外的操作开销。
与 Hamilton 一起工作的方式
如果你删除sumarize_text_from_summaries_prompt
和sumarize_chunk_of_text_prompt
函数定义,我们的 PDF 总结器流程大致如下:
summarization_shortened.py。注意两个输入*_prompt
,它们表示现在作为数据流输入所需的提示。通过 Hamilton,你可以通过查看像这样的图表来确定你的提示模板所需的输入。图表由 Hamilton 创建。图像由作者提供。
要操作事物,你需要在请求时注入提示:
from hamilton import base, driver
import summarization_shortend
# create driver
dr = (
driver.Builder()
.with_modules(summarization_sortened)
.build()
)
# pull prompts from somewhere
summarize_chunk_of_text_prompt = """SOME PROMPT FOR {chunked_text}"""
summarize_text_from_summaries_prompt = """SOME PROMPT {summarized_chunks} ... {user_query}"""
# execute, and pass in the prompt
result = dr.execute(
["summarized_text"],
inputs={
"summarize_chunk_of_text_prompt": summarize_chunk_of_text_prompt,
...
}
)
或者你可以更改代码以动态加载提示,即将函数添加到 Hamilton 数据流中以从外部系统检索提示。在每次调用时,它们将查询要使用的提示(当然你可以缓存以提高性能):
# prompt_template_loaders.py
def summarize_chunk_of_text_prompt(
db_client: Client, other_args: str) -> str:
# pseudo code here, but you get the idea:
_prompt = db_client.query(
"get latest prompt X from DB", other_args)
return _prompt
def summarize_text_from_summaries_prompt(
db_client: Client, another_arg: str) -> str:
# pseudo code here, but you get the idea:
_prompt = db_client.query(
"get latest prompt Y from DB", another_arg)
return _prompt
Driver 代码:
from hamilton import base, driver
import prompt_template_loaders # <-- load this to provide prompt input
import summarization_shortend
# create driver
dr = (
driver.Builder()
.with_modules(
prompt_template_loaders,# <-- Hamilton will call above functions
summarization_sortened,
)
.build()
)
# execute, and pass in the prompt
result = dr.execute(
["summarized_text"],
inputs={
# don't need to pass prompts in this version
}
)
我如何记录使用的提示并监控流程?
在这里,我们概述了几种监控发生情况的方法。
- 记录执行结果。也就是说,运行 Hamilton,然后将信息发送到你希望它去的地方。
result = dr.execute(
["summarized_text",
"summarize_chunk_of_text_prompt",
... # and anything else you want to pull out
"summarize_text_from_summaries_prompt"],
inputs={
# don't need to pass prompts in this version
}
)
my_log_system(result) # send what you want for safe keeping to some
# system that you own.
注意。在上述内容中,Hamilton 允许你请求 任何 中间 输出,只需按名称请求“函数”(即图中的节点)。如果我们真的想获取整个数据流的所有中间输出,我们可以这样做并将其记录到任何我们想要的地方!
- 在 Hamilton 函数内部使用记录器(要查看这种方法的强大功能,请参见我关于结构化日志的旧讲座):
import logging
logger = logging.getLogger(__name__)
def summarize_text_from_summaries_prompt(
db_client: Client, another_arg: str) -> str:
# pseudo code here, but you get the idea:
_prompt = db_client.query(
"get latest prompt Y from DB", another_arg)
logger.info(f"Prompt used is [{_prompt}]")
return _prompt
- 扩展 Hamilton 以发出这些信息。你可以使用 Hamilton 捕获执行函数的信息,即节点,而无需在函数体内插入日志语句。这促进了重用,因为你可以在 Driver 级别在开发和生产设置之间切换日志记录。参见 GraphAdapters,或编写你自己的 Python 装饰器 来包装函数进行监控。
在上述任何代码中,你都可以轻松地引入第三方工具来帮助跟踪和监控代码以及外部 API 调用。
作为代码的提示
作为静态字符串的提示
由于提示仅仅是字符串,它们也非常适合与源代码一起存储。这个想法是将尽可能多的提示版本存储在你的代码中,以便在运行时,所用提示集是固定且确定的。
这里的 MLOps 类比是,与其动态重新加载模型,不如将 ML 模型嵌入到容器中/硬编码引用。一旦部署,你的应用程序拥有它所需的一切。部署是不可变的;一旦启动便不会更改。这使得调试和确定问题变得更加简单。
MLOps 类比:通过将模型固定以进行应用程序的部署,从而创建一个不可变的部署。图片由作者提供。
图示展示了如何将提示视为代码,使你能够利用 CI/CD 构建一个不可变的部署来与 LLM API 进行交互。图片由作者提供。
这种方法有许多操作上的好处:
-
每当推送一个新提示时,都会强制执行新的部署。如果新提示出现问题,回滚语义是明确的。
-
你可以同时提交源代码和提示的拉取请求(PR)。这使得审查更改变得更简单,并且可以清楚地了解这些提示将影响/交互的下游依赖关系。
-
你可以在 CI/CD 系统中添加检查,以确保不良提示不会进入生产环境。
-
调试问题变得更简单。你只需拉取创建的(Docker)容器,就能迅速轻松地精确复现任何客户问题。
-
不需要维护或管理其他“提示系统”。简化了操作。
-
这并不排除添加额外的监控和可视性。
这在 Hamilton 中如何工作
提示将被编码为数据流/有向无环图(DAG)中的函数:
PDF 摘要示例中的 summarization.py 文件的样子。提示模板是代码的一部分。图示由 Hamilton 创建。图片由作者提供。
将这段代码与 git 结合使用,我们为你的整个数据流(即“链”)提供了一个轻量级的版本控制系统,这样你总是可以根据 git commit SHA 辨别世界的状态。如果你想在任何时间点管理和访问多个提示,Hamilton 提供了两个强大的抽象来实现这一点:@config.when
和 Python 模块。这允许你存储并保留所有旧版本的提示,并通过代码指定使用哪个版本。
@config.when (文档)
Hamilton 有一个装饰器的概念,它们只是函数上的注解。@config.when
装饰器允许为数据流中的函数(即“节点”)指定替代实现。在这种情况下,我们指定替代的提示。
from hamilton.function_modifiers import config
@config.when(version="v1")
def summarize_chunk_of_text_prompt__v1() -> str:
"""V1 prompt for summarizing chunks of text."""
return f"Summarize this text. Extract any key points with reasoning.\n\nContent:"
@config.when(version="v2")
def summarize_chunk_of_text_prompt__v2(content_type: str = "an academic paper") -> str:
"""V2 prompt for summarizing chunks of text."""
return f"Summarize this text from {content_type}. Extract the key points with reasoning. \n\nContent:"
你可以继续添加带有@config.when
注解的函数,这样可以通过传递给 Hamilton Driver
的配置在它们之间切换。在实例化Driver
时,它将使用与配置值关联的提示实现来构建数据流。
from hamilton import base, driver
import summarization
# create driver
dr = (
driver.Builder()
.with_modules(summarization)
.with_config({"version": "v1"}) # V1 is chosen. Use "v2' for V2.
.build()
)
模块切换
除了使用@config.when
之外,你还可以将不同的提示实现放入不同的 Python 模块中。然后,在Driver
构造时,传递适合你想使用的上下文的正确模块。
所以在这里我们有一个包含 V1 的模块:
# prompts_v1.py
def summarize_chunk_of_text_prompt() -> str:
"""V1 prompt for summarizing chunks of text."""
return f"Summarize this text. Extract any key points with reasoning.\n\nContent:"
在这里,我们有一个包含 V2 的模块(看看它们之间的细微差别):
# prompts_v2.py
def summarize_chunk_of_text_prompt(content_type: str = "an academic paper") -> str:
"""V2 prompt for summarizing chunks of text."""
return f"Summarize this text from {content_type}. Extract the key points with reasoning. \n\nContent:"
在下面的驱动代码中,我们根据某些上下文选择要使用的正确模块。
# run.py
from hamilton import driver
import summarization
import prompts_v1
import prompts_v2
# create driver -- passing in the right module we want
dr = (
driver.Builder()
.with_modules(
prompts_v1, # or prompts_v2
summarization,
)
.build()
)
使用模块方法允许我们将整个提示集封装和版本化。如果你想回到过去(通过 git),或者查看一个被批准的提示版本,你只需要导航到正确的提交,然后查看正确的模块。
我该如何记录使用的提示并监控流程?
假设你使用 git 来跟踪你的代码,你就不需要记录使用了哪些提示。相反,你只需知道部署的 git 提交 SHA,你就能同时跟踪你的代码和提示的版本。
要监控流程,就像上述方法一样,你可以使用相同的监控钩子,我不会在这里重复,但它们是:
-
请求任何中间输出并在 Hamilton 之外记录它们。
-
从函数内部记录它们,或构建一个Python 装饰器 / GraphAdapter在框架级别进行记录。
-
集成第三方工具来监控你的代码和 LLM API 调用。
-
或者以上所有!
那如何进行 A/B 测试我的提示?
对于任何机器学习项目,测量变更的业务影响非常重要。同样,对于 LLMs + 提示,测试和衡量变更对重要业务指标的影响也很重要。在 MLOps 世界中,你会对 ML 模型进行 A/B 测试,以通过在它们之间分配流量来评估它们的业务价值。为了确保 A/B 测试所需的随机性,你不会在运行时知道使用哪个模型,直到掷硬币。然而,要让这些模型上线,它们都需要遵循一个过程以进行资格认证。因此,对于提示,我们也应该考虑类似的方式。
上述两种提示工程模式并不会妨碍你进行 A/B 测试提示,但这意味着你需要管理一个过程,以启用你正在并行测试的任何数量的提示模板。如果你还在调整代码路径,将它们放在代码中将更容易辨别和调试发生了什么,并且你可以利用 @config.when
装饰器/ Python 模块交换来实现这一目的。与其依赖你的日志/监控/可观察性栈来告诉你使用了哪个提示,特别是当你动态加载/传递它们时,并且还需进行心理映射哪个提示对应哪个代码路径,前者将更为简便。
请注意,如果你开始需要更改多个提示进行 A/B 测试,因为你在一个流程中有几个提示,那么这会变得更加困难。例如,你在工作流中有两个提示并且你正在更换 LLM,你将希望从整体上进行 A/B 测试,而不是逐个提示地测试。我们的建议是,通过将提示放入代码中,你的操作生活会更简单,因为你将知道哪些提示属于哪些代码路径,而无需进行任何心理映射。
总结
在这篇文章中,我们解释了在生产环境中使用 Hamilton 管理提示的两种模式。第一种方法将提示视为 动态运行时变量,而第二种方法将提示视为代码用于生产设置。如果你重视减少操作负担,那么我们的建议是将提示编码为代码,因为它在操作上更简单,除非更改它们的速度对你来说真的很重要。
总结:
-
提示作为动态运行时变量。使用外部系统将提示传递到你的 Hamilton 数据流,或使用 Hamilton 从数据库中提取提示。对于调试和监控,能够确定给定调用使用了哪个提示是很重要的。你可以集成开源工具,或使用像 DAGWorks 平台这样的工具来帮助确保你知道在任何代码调用中使用了什么。
-
提示作为代码。 将提示编码为代码允许使用 git 进行简单的版本控制。更改管理可以通过拉取请求和 CI/CD 检查来完成。它与 Hamilton 的功能,如
@config.when
和 Driver 级别的模块切换,工作良好,因为它清楚地确定使用了哪个版本的提示。这种方法加强了你可能使用的任何工具,如 DAGWorks 平台,因为部署的提示是不可变的。
我们想听听你的意见!
如果你对这些内容感到兴奋或有强烈的意见,请留下评论,或访问我们的 Slack 频道!一些用于表扬/投诉/聊天的链接:
-
📣 加入我们的 Slack 社区 — 我们非常乐意帮助解答你可能有的问题或帮助你入门。
-
⭐️ 在 GitHub 上关注我们。
-
📝 如果你发现问题,请在 issue 中告诉我们。
-
📚 阅读我们的 文档。
-
⌨️ 互动式 在浏览器中了解 Hamilton。
其他你可能感兴趣的 Hamilton 链接/帖子:
-
tryhamilton.dev — 在浏览器中的互动教程!
-
Hamilton + Lineage 在 10 分钟内
-
如何在 5 分钟内与 Pandas 一起使用 Hamilton
-
如何在 5 分钟内与 Ray 一起使用 Hamilton
-
如何在笔记本环境中使用 Hamilton
-
Hamilton 的背景故事与介绍
-
使用 Hamilton 创建数据流的好处(来自 Hamilton 的有机用户帖子!)
每个人的 LLM:在 Google Colab 中运行 LangChain 和 MistralAI 7B 模型
免费实验大型语言模型
·发表于 Towards Data Science ·阅读时间 10 分钟·2023 年 12 月 5 日
–
LangChain 的艺术表现,照片由 Ruan Richard Rodrigues 提供,Unsplash
大家都知道大型语言模型的定义就是“大”。甚至在不久前,它们仅对高端硬件拥有者开放,或者至少对付费获取云访问或每次 API 调用的人开放。现在,情况正在改变。在这篇文章中,我将展示如何在 Google Colab 中完全免费运行 LangChain Python 库、FAISS 向量数据库和 Mistral-7B 模型,并进行一些有趣的实验。
组件
这里有很多关于在 Python 中使用大型语言模型的文章,但它们往往不容易重现。例如,许多使用 LangChain 库的示例使用了一个 OpenAI 类,其第一个参数(猜猜是什么?)是 OPENAI_API_KEY。一些其他 RAG(检索增强生成)和向量数据库的示例使用了 Weaviate;我们打开其网站后首先看到的是“定价”。在这里,我将使用一组可以完全免费使用的开源库:
-
LangChain。它是一个用于开发语言模型驱动应用程序的 Python 框架。它也是模型无关的,相同的代码可以与不同的模型重复使用。
-
FAISS(Facebook AI 相似性搜索)。这是一个用于高效相似性搜索和稠密向量存储的库,我将使用它进行检索增强生成(Retrieval Augmented Generation)。
-
Mistral 7B 是一个拥有 7.3B 参数的大型语言模型(在 Apache 2.0 许可证下发布),根据作者的说法,它在所有基准测试中超越了 13B Llama2。它也可以在 HuggingFace 上使用,因此使用起来非常简单。
-
最后但同样重要的是,Google Colab也是这次测试的重要组成部分。它提供了对由 CPU、16 GB NVIDIA Tesla T4 或甚至 80 GB NVIDIA A100(尽管我从未见过免费实例中提供最后一个)的 Python 笔记本的免费访问。
现在,让我们开始吧。
安装
首先,我们需要打开 Google Colab 并创建一个新的笔记本。需要的库可以在第一个单元格中使用pip
安装:
!pip install bitsandbytes accelerate xformers einops langchain faiss-cpu transformers sentence-transformers
在运行代码之前,我们需要选择运行时类型:
Google Colab,作者截图
现在,让我们导入这些库:
from typing import List
import transformers
from transformers import AutoModelForCausalLM, AutoTokenizer, AutoConfig, BitsAndBytesConfig
import torch
from langchain.prompts import PromptTemplate
from langchain.llms import HuggingFacePipeline
from langchain.embeddings import HuggingFaceEmbeddings
from langchain.text_splitter import CharacterTextSplitter
from langchain.chains import ConversationalRetrievalChain, RetrievalQA
from langchain.callbacks.tracers import ConsoleCallbackHandler
from langchain_core.vectorstores import VectorStoreRetriever
from langchain.memory import ConversationBufferMemory
from langchain.vectorstores import FAISS
device = 'cuda' if torch.cuda.is_available() else 'cpu'
print("Device:", device)
if device == 'cuda':
print(torch.cuda.get_device_name(0))
# >>> Device: cuda
# >>> Tesla T4
如果一切正常,输出应显示“cuda”设备和选定的“Tesla T4”显卡。
下一步是最重要和最耗资源的:让我们加载语言模型:
orig_model_path = "mistralai/Mistral-7B-Instruct-v0.1"
model_path = "filipealmeida/Mistral-7B-Instruct-v0.1-sharded"
bnb_config = BitsAndBytesConfig(
load_in_4bit=True,
bnb_4bit_use_double_quant=True,
bnb_4bit_quant_type="nf4",
bnb_4bit_compute_dtype=torch.bfloat16,
)
model = AutoModelForCausalLM.from_pretrained(model_path, trust_remote_code=True, quantization_config=bnb_config, device_map="auto")
tokenizer = AutoTokenizer.from_pretrained(orig_model_path)
在这里,我选择了 4 位量化模式,这允许模型适应 GPU 内存。还有另一个棘手的部分。加载原始“mistralai/Mistral-7B-Instruct-v0.1”模型会导致 Colab 实例崩溃。令人惊讶的是,GPU 内存足够用于 4 位量化,但模型文件的大小约为 16 GB,免费 Colab 实例上没有足够的“正常”内存来在将其加载到 GPU 之前对模型进行量化!作为变通方法,我使用了一个“分片”版本,它被拆分成 2GB 的块(如果您的 PC 或 Colab 实例有超过 16GB 的内存,则不需要这样做)。
另外,想了解更多关于 4 位量化如何工作的读者欢迎阅读另一篇文章:
让我们深入探讨
towardsdatascience.com
如果一切正常,Colab 的输出应如下所示:
加载 Mistral 7B 模型,作者截图
从图片中可以看出,需要下载的文件非常庞大,因此如果在本地运行此代码(而不是在 Colab 中),请确保您的网络流量没有限制。
现在,让我们创建 LLM 管道:
text_generation_pipeline = transformers.pipeline(
model=model,
tokenizer=tokenizer,
task="text-generation",
eos_token_id=tokenizer.eos_token_id,
pad_token_id=tokenizer.eos_token_id,
repetition_penalty=1.1,
return_full_text=True,
max_new_tokens=100,
)
mistral_llm = HuggingFacePipeline(pipeline=text_generation_pipeline)
恭喜!我们的安装已准备好,我们成功加载了一个 7B 语言模型。为了进行简单测试,让我们看看 LLM 是否有效:
text = "What is Mistral? Write a short answer."
mistral_llm.invoke(text)
#> Mistral is a type of cold front that forms over the Mediterranean
#> Sea and moves eastward across southern Europe, bringing strong winds
#> and sometimes severe weather conditions such as heavy rainfall, hail,
#> and even tornadoes.
如果一切正常,我们准备好享受乐趣并进行进一步的测试。
LangChain
LangChain 是一个专门为与语言模型配合使用而设计的 Python 框架。作为热身,让我们测试一个提示模板:
from langchain.prompts import PromptTemplate
prompt = PromptTemplate.from_template(
"Tell me a {adjective} joke about {content}."
)
prompt.format(adjective="funny", content="chickens")
llm_chain = prompt | mistral_llm
llm_chain.invoke({"adjective": "funny", "content": "chickens"})
#> Why don't chickens like to tell jokes? They might crack each other
#> up and all their eggs will scramble!
有趣的是,LangChain 是“跨平台”的,我们可以在不更改代码的情况下使用不同的语言模型。这个示例取自官方 库文档,其中 OpenAI 用于提示,但我使用了相同的模板来进行 Mistral。
它是如何工作的?可以将 ConsoleCallbackHandler
添加到配置中,这样我们就可以看到所有的中间步骤:
llm_chain = prompt | mistral_llm
llm_chain.invoke({"adjective": "funny", "content": "chickens"},
config={'callbacks': [ConsoleCallbackHandler()]})
然后,输出将如下所示:
[1:chain:RunnableSequence] Entering Chain run with input:
{
"adjective": "funny",
"content": "chickens"
}
[1:chain:RunnableSequence > 2:prompt:PromptTemplate] Entering Prompt run with input:
{
"adjective": "funny",
"content": "chickens"
}
[1:chain:RunnableSequence > 3:llm:HuggingFacePipeline] Entering LLM run with input:
{
"prompts": [
"Tell me a funny joke about chickens."
]
}
[1:chain:RunnableSequence > 3:llm:HuggingFacePipeline] [3.60s] Exiting LLM run with output:
{
"generations": [
[
{
"text": "\n\nWhy don't chickens like to tell jokes? They might crack each other up and all their eggs will scramble!",
"generation_info": null,
"type": "Generation"
}
]
],
"llm_output": null,
"run": null
}
作为另一个示例,让我们尝试 ChatPromptTemplate 类:
chat_prompt = ChatPromptTemplate.from_messages(
[
("system", "You are a helpful AI bot. Your name is {name}. Answer with short sentences."),
]
)
llm_chain = chat_prompt | mistral_llm
llm_chain.invoke({"name": "Mistral", "user_input": "What is your name?"})
#> Mistral: Yes, I am Mistral. How can I assist you today?
在我看来,回答“是的,我是 Mistral”是可以接受的,但从语言学角度来看,对于“你叫什么名字?”这个问题并不是最好的回答。显然,对于大型神经网络来说,可解释性可能是一个问题,我们无法告诉为什么模型以这种或那种方式做出回应。这可能是 4 位量化的副作用(这会稍微降低模型质量)或只是 7B 模型能力的根本限制(显然,其他 33B 或 70B 模型可能表现更好,但需要更多计算资源)。
检索增强生成(RAG)
现在,RAG 是一个热门研究话题。它允许我们自动将外部文档添加到 LLM 提示中,并在不微调模型的情况下添加更多信息。让我们看看如何将其与 LangChain 和 Mistral 一起使用。
首先,我们需要创建一个单独的嵌入模型:
from langchain.embeddings import HuggingFaceEmbeddings
embeddings = HuggingFaceEmbeddings(
model_name="sentence-transformers/all-MiniLM-l6-v2",
model_kwargs={"device": "cuda"},
)
这个小型 sentence-transformer 模型 能够将文本字符串转换为向量表示;我们将用它来构建我们的向量数据库。作为一个玩具示例,我将仅向数组中添加一个“文档”:
db_docs = [
"Airbus's registered headquarters is located in Leiden, Netherlands.",
]
然后我们需要创建一个向量数据库和一个 VectorStoreRetriever
对象:
from langchain.vectorstores import FAISS
from langchain_core.vectorstores import VectorStoreRetriever
vector_db = FAISS.from_texts(db_docs, embeddings)
retriever = VectorStoreRetriever(vectorstore=vector_db)
现在,我们可以创建一个 RetrievalQA 对象,这个对象专门用于问答:
template = """You are a helpful AI assistant. Use the following pieces of context to answer the question at the end.
{context}
If you don't know the answer, just say that you don't know, don't try to make up an answer.
Chat history: {history}
Question: {question}
Write your answers short. Helpful Answer:"""
prompt = PromptTemplate(
template=template, input_variables=["history", "context", "question"]
)
qa = RetrievalQA.from_chain_type(
llm=mistral_llm,
chain_type="stuff",
retriever=retriever,
chain_type_kwargs={
"verbose": False,
"prompt": prompt,
"memory": ConversationBufferMemory(
memory_key="history",
input_key="question"),
}
)
我将询问模型有关空客的问题;这些问题模型很可能不知道:
qa.run("Hi, who are you?")
#> I am an AI assistant.
qa.run("What is the range of Airbus A380?")
#> The range of Airbus A380 is approximately 12,497 nautical miles.
qa.run("What is the tire diameter of Airbus A380 in centimeters?")
#> I don't know.
我对这些回答感到积极惊讶。首先,Mistral 7B 模型已经知道了空客 A380 的航程(我用 Google 查了一下,结果看起来是正确的)。其次,正如我所预期的,模型并不知道 A380 轮胎的直径,但它“诚实地”回答了“我不知道”,而不是提供“虚构的”错误回答。
现在,让我们向我们的“向量数据库”中添加一个额外的字符串:
db_docs = [
"Airbus's registered headquarters is located in Leiden, Netherlands.",
"The Airbus A380 has the largest commercial plane tire size in the world at 56 inches in diameter."
]
然后我们可以再试一次:
qa.run("What is the tire diameter of Airbus A380 in centimeters? Write a short answer.")
#> 142 cm
这真是太棒了!模型不仅找到了 A380 轮胎直径为 56 英寸的信息,还正确地将其转换为厘米(56*2.54 确实是 142)。我们知道数学任务通常对 LLM 来说很困难,所以这种准确性令人惊讶。
我们还可以要求模型逐步解释答案:
qa.run("What is the tire diameter of Airbus A380 in centimeters? Explain the answer in three steps.")
#> 1\. The tire diameter of Airbus A380 is 56 inches in diameter.
#> 2\. To convert 56 inches to centimeters, we need to multiply it by 2.54 (the conversion factor from inches to centimeters).
#> 3\. Therefore, the tire diameter of Airbus A380 in centimeters is 142.16 cm.
这太棒了!好吧,我们已经习惯于大型语言模型如 GPT3 或 GPT4 在云端超级计算机上运行并产生惊人结果的事实。但在你的本地 GPU 上看到这一点(我在 Google Colab 和我的家用 PC 上都测试了这段代码)是完全不同的感觉。
细心的读者可能会问,Mistral 和 Retriever 模型如何协同工作?确实,我创建了一个“Mistral-7B-Instruct-v0.1”模型和一个“all-MiniLM-l6-v2”句子嵌入模型。它们的向量空间是否兼容?答案是“否”。当我们进行查询时,VectorStoreRetriever
会先进行自己的搜索,找到向量库中最好的文档,并以纯文本格式返回这些文档。如果我们将 verbose
参数设置为 True,就可以看到最终的提示:
template = """You are a helpful AI assistant. Use the following pieces of context to answer the question at the end.
{context}
If you don't know the answer, just say that you don't know, don't try to make up an answer.
Chat history: {history}
Question: {question}
Write your answers short. Helpful Answer:"""
prompt = PromptTemplate(
template=template, input_variables=["history", "context", "question"]
)
qa = RetrievalQA.from_chain_type(
llm=mistral_llm,
chain_type="stuff",
retriever=retriever,
chain_type_kwargs={
"verbose": True,
"prompt": prompt,
"memory": ConversationBufferMemory(
memory_key="history",
input_key="question"),
}
)
在运行相同的代码后,我们可以看到 LangChain 发送给模型的实际提示:
You are a helpful AI assistant. Use the following pieces of context to answer the question at the end.
The Airbus A380 has the largest commercial plane tire size in the world at 56 inches in diameter.
Airbus's registered headquarters is located in Leiden, Netherlands.
If you don't know the answer, just say that you don't know, don't try to make up an answer.
Chat history. Human: Hi, who are you?
AI: I am an AI assistant.
Human: What is the range of Airbus A380?
AI: The range of Airbus A380 is approximately 12,497 nautical miles.
Question: What is the tire diameter of Airbus A380 in centimeters? Explain the answer in three steps.
Write your answers short. Helpful Answer:
在这种情况下,这两个文档都与“空客”问题相关,VectorStoreRetriever
将它们放在 context
占位符中。
结论
在这篇文章中,我们成功在免费的 Google Colab 实例上运行了一个 7.3B 的 Mistral 大型语言模型,完全依赖于免费的开源组件。这是一项伟大的成就,同时也是 Google 的慷慨举措,因为在撰写本文时,Amazon 上最便宜的 16GB 显卡至少需要 $500(不过我必须承认,Google Colab 服务并非纯粹的慈善,免费的 GPU 后端可能并不总是可用;需要经常使用的人应该考虑购买付费订阅)。我们还能够使用检索增强生成来为 LLM 提供额外的信息。如果像这样的模型已经准备好投入生产,是否真的合适仍然是一个悬而未决的问题,也永远是“购买还是自己动手”的难题。Mistral 7B 模型有时仍会“幻觉”并产生错误的答案;它也可能被更大的模型超越。无论如何,能够免费测试这样的模型对于学习、自我教育和原型设计都是极好的。
接下来的部分,我将展示如何在 Google Colab 中运行 LLaMA-13B 模型和 LangChain 框架:
## 适合所有人的 LLM:在 Google Colab 中运行 LLaMA-13B 模型和 LangChain
免费实验大型语言模型(第二部分)
[towardsdatascience.com
对使用语言模型和自然语言处理感兴趣的人也欢迎阅读其他文章:
-
自然语言处理绝对初学者指南
-
16、8 和 4 位浮点格式——如何运作?
如果你喜欢这个故事,欢迎订阅Medium,你将获得新文章发布的通知,并且可以完全访问其他作者的成千上万篇故事。也欢迎通过LinkedIn与我联系。如果你想获得此文及其他文章的完整源代码,可以访问我的Patreon 页面。
感谢阅读。