过去一年有关大模型应用构建的干货经验之战术篇
过去一年,大模型应用以前所未有之势从出现到繁荣。LLM应用从原型展示向生产落地演进。近日,Oreilly刊发了由 Eugene Yan, Bryan Bischof等人撰写的大模型总结性文章《What We Learned from a Year of Building with LLMs?》,由于作者们背景各不相同,有独立咨询顾问(Hamel Husain,Jason Liu),有研究人员(Shreya Shankar),还有科技公司AI团队领袖(Eugene Yan,Bryan Bischof),以及AI领域布道师(Charles Frye),这使得文章内容更为全面深入。他们从战术、运营和战略三个部分展开解读了自己过去一年有关LLM应用开发与运营的全方位经验。
一、战术篇
本部分探讨有关提升质量和可靠性的提示技巧、构建检索增强生成(RAG)、应用流程工程( flow engineering)以及评估和监控的最佳实践和常见陷阱,进而构建稳健的LLM应用。
提示技巧
在开发新应用时,建议首先考虑提示技术。提示的重要性往往容易被人低估或高估。它被低估是因为,正确的提示技巧若使用得当,可以带来意想不到的成效。而其被高估,则是因为,即便是基于提示的应用,也需要围绕提示进行大量的工程设计才能确保良好的运行。
尽可能利用基础的提示技术获取最大收益
一些提示技巧在各种模型和任务上一直有助于提升性能:n-shot 提示 + 上下文学习, chain-of-thought, 以及提供相关资源。
通过n-shot提示进行的上下文学习的思路是向LLM提供一些示例,利用这些示例展示了任务过程并将输出与用户期望对齐。
一些建议:
- 如果n太低,模型可能会过度依赖这些特定示例,损害其泛化能力。作为一个经验法则,目标是n ≥ 5。不要害怕高达几十个的n。
- 示例应代表预期输入分布。如果你正在构建一个电影摘要器,包括不同类型的样本,大致按照期望在实践中看到的比例。
- 不一定需要提供完整的输入-输出对。在许多情况下,仅仅提供所需输出的示例就足够了。
- 如果正在使用一个支持工具调用( tool use)的LLM,n-shot示例也应使用希望Agent使用的工具。
在chain-of-thought (CoT)提示中,鼓励大语言模型在返回最终回答之前解释它的思考过程。把它想象成为大语言模型提供一个草稿本,这样它就不必全部在内存中完成。原始方法是简单地添加短语“让我们一步一步来思考”作为指令的一部分。然而,我们发现让CoT更具体,通过额外的一两个句子增加具体性,通常能显著减少幻觉率。例如,当让一个LLM总结会议纪要时,可以明确相关步骤,例如:
- 首先,在草稿本上列出关键决策、后续事项及相关负责人。
- 然后,检查草稿本中的细节与记录是否事实一致。
- 最后,将关键点整合成简明的摘要。
最近,出现一些有关COT技术是否有效的质疑(Chain of Thoughtlessness? An Analysis of CoT in Planning(https://arxiv.org/abs/2405.04776)),以及使用COT 进行推理时究竟发生了什么,存在着明显的争论。但无论如何,这是一个值得尝试的技术。
提供相关资源是扩展模型知识库、减少幻觉和增加用户信任的强大机制。这通常通过检索增强生成 (RAG) 来完成,为模型提供它可以直接利用的文本片段是一种必不可少的技巧。提供相关资源时,仅仅包含它们是不够的;别忘了告诉模型优先使用它们,直接引用它们,有时还要提及哪些资源不足。这些都有助于将Agent基于知识库作出响应。
结构化输入和输出
构建结构化的输入和输出有助于模型更好地理解输入,以及可靠地输出以便于与下游系统集成。向输入添加序列化格式可以给模型提供更多线索以理解上下文中Token和附加元数据(如类型)的特定Token之间的关系,或将请求与模型训练数据中的类似示例关联。
例如,互联网上关于编写SQL的许多问题都是通过指定SQL schema开始的。因此,可能期望有效的Text-to-SQL提示应包括结构化的schema定义。
结构化输出具有类似的目的,但它也简化了整合到系统的下游组件中。Instructor 和Outlines 对于结构化输出很有帮助。(如果正在导入LLM API SDK,使用Instructor; 如果正在导入Huggingface以用于自托管模型,使用 Outlines。)结构化输入清晰地表达任务,并类似于训练数据的格式化,增加了提升输出更好的可能性。
当使用结构化输入时,要意识到每种LLM都有它们自己的偏好。Claude偏好xml,而GPT偏爱Markdown和JSON。使用XML,甚至可以通过提供一个响应标签来预填Claude的响应,就像这样。
messages=[
{
"role": "user",
"content": """Extract the <name>, <size>, <price>, and <color>
from this product description into your <response>.
<description>The SmartHome Mini
is a compact smart home assistant
available in black or white for only $49.99.
At just 5 inches wide, it lets you control
lights, thermostats, and other connected
devices via voice or app—no matter where you
place it in your home. This affordable little hub
brings convenient hands-free control to your
smart devices.
</description>"""
},
{
"role": "assistant",
"content": "<response><name>"
}
]
编写“单一职责”的小提示
在软件开发中,有一个常见的反模式“全能型对象”(God Object),也就是指那种几乎包揽所有功能的类或函数。在编写提示(Prompt)时,这一概念同样适用。最开始,编写提示往往是简明扼要的:写下几句指示语,举几个例子,接着事情就可以开展了。不过,随着我们试图提高程序性能,处理越来越多的特殊情况,提示中的复杂性也在不知不觉中增长。这意味着需要更多的指示语和更复杂的逻辑推理,还要加入许多例子。就这样,一段原本简短的提示竟变成了一个长达 2,000 Token 的庞然大物。更令人头痛的是,它对那些更常见、更简单的情况的处理效果还不如以前那样好了!
在努力让系统和代码保持简洁的同时,在创建提示方面也应该坚持这一原则。就以会议纪要为例,可以将它拆解成几个步骤:
1. 将关键决策、待办事项以及责任承担者提取出来,并整理成结构化的格式。
2. 核对提取出的信息,确保其与原始会议记录的内容保持一致。
3. 基于已结构化的信息,生成一个简练的摘要。
如此一来,就可将一个复杂的提示拆分成了多个简单、专注并且容易理解的小提示。分开处理这些提示之后,也就能够逐一迭代和评估它们。
优化上下文
重新审视必须向 Agent 提供多少上下文信息。学习米开朗基罗,不是堆砌,而是精简。RAG 被广泛用于整合所有可能相关的信息片段,但关键在于如何从中提炼出必要内容?
把发送给模型的最终提示——全部上下文、元提示(meta-prompting)以及 RAG 结果——单独放在一张白纸上阅读,能有效帮助重新审视上下文配置。这种方法会帮助发现了文中的冗余、自我矛盾及格式问题。
另一个重要的优化点在于上下文的结构安排。将文档以“袋子模型(bag-of-docs)”的形式呈现对人帮助不大,不应默认它对 Agent同样有效。应深思熟虑地组织上下文结构,明确突出不同部分之间的关系,尽可能地简化信息抽取过程。
信息检索/RAG
除了提示之外,另一个引导LLM的有效方法是在提示中提供知识。这会将LLM建立在提供的上下文中,然后用于上下文学习。这就是所谓的检索增强生成 (RAG)。实践者发现 RAG 在提供知识和提高输出方面非常有效,同时与微调相比,它需要更少的努力和成本。RAG 的效果取决于检索到的文档的相关性、密度和细节。
检索增强生成(RAG)的输出质量取决于检索到的文档的质量,而文档质量可以从以下几个方面考虑。
第一个也是最明显的指标是相关性。这通常通过排名指标量化,例如平均倒数排名 (MRR) 或归一化折现累积增益 (NDCG)。MRR 评估系统在排序列表中将第一个相关结果排名的效果,而 NDCG 则考虑所有结果的相关性及其位置。它们衡量系统在将相关文档排得更高,而将不相关文档排得更低方面的效果。例如,如果我们要检索用户摘要来生成电影评论摘要,我们会希望将特定电影的评论排得更高,同时排除其他电影的评论。
与传统的推荐系统类似,检索到的项目的排名将对 LLM 在下游任务中的表现产生重大影响。为了衡量这种影响,运行一个基于 RAG 的任务,但将检索到的项目打乱 - RAG 输出的表现如何?
其次,还需要考虑信息密度。如果两篇文档同样相关,应该优先考虑更简洁、包含更少无关细节的文档。回到电影例子,可以认为电影剧本和所有用户评论在广义上都是相关的。但是,最高评分的评论和编辑评论可能在信息密度方面更高。
最后,考虑文档中提供的细节程度。假设要构建一个 RAG 系统,从自然语言生成 SQL 查询。可以简单地提供包含列名的表格架构作为上下文。但是,如果包含列描述和一些代表性值呢?额外的细节可以帮助 LLM 更好地理解表格的语义,从而生成更准确的 SQL。
不要忘记关键字搜索;将其作为基准并用于混合搜索。
鉴于基于向量检索的 RAG 演示如此流行,人们很容易忘记或忽视信息检索领域数十年的研究和解决方案。
尽管如此,embedding无疑是一种强大的工具,但它并不是万能的。首先,虽然embedding擅长捕捉高层次的语义相似性,但在处理更具体的、基于关键字的查询时,比如用户搜索姓名(如 Ilya)、缩略语(如 RAG)或 ID(如 claude-3-sonnet)时,嵌入式可能会显得力不从心。基于关键字的搜索,如 BM25,就是为此而明确设计的。经过多年的基于关键字的搜索,用户可能已经认为这是理所当然的,如果他们期望检索的文档没有被返回,他们可能会感到沮丧。
其次,使用关键字检索可以更直观地了解检索到文档的原因--可以查看与查询相匹配的关键字。相比之下,基于嵌入的检索就不那么容易理解了。最后,由于 Lucene 和 OpenSearch 等系统经过数十年的优化和实战检验,关键词搜索的计算效率通常更高。
在大多数情况下,混合搜索的效果最好:关键词匹配用于明显的匹配,嵌入式搜索用于同义词、超义词和拼写错误,以及多模态搜索(如图像和文本)。Shortwave 分享了他们如何构建 RAG 管道(https://www.shortwave.com/blog/deep-dive-into-worlds-smartest-email-ai/),包括查询重写、关键词 + 嵌入检索和排序。
对于新知识,优先考虑 RAG 而不是微调
RAG 和微调都可以用来将新信息整合到LLM中,并提升特定任务的性能。那么,应该先尝试哪个呢?
最近的研究表明,RAG 可能更胜一筹。一项研究将 RAG 与无监督微调(也称为持续预训练)进行了比较,并在 MMLU 的一个子集和当前事件上评估了两种方法。他们发现,对于训练过程中遇到的知识以及全新的知识,RAG 的性能始终优于微调。在另一篇论文中,他们将 RAG 与农业数据集上的监督微调进行了比较。同样地,RAG 带来的性能提升比微调更大,尤其是对于 GPT-4(参见论文的表格 20)。
除了性能提升之外,RAG 还具有几个实际优势。首先,与持续预训练或微调相比,更新检索索引更容易,也更便宜!其次,如果检索索引中存在包含有毒或有偏见内容的问题文档,可以轻松地删除或修改这些文档。
此外,RAG 中的 R (检索)提供了对文档检索方式的更细粒度的控制。例如,如果我们为多个组织托管 RAG 系统,通过对检索索引进行分区,可以确保每个组织只能检索到自己索引中的文档。这可以确保不会无意中将一个组织的信息暴露给另一个组织。
长文本模型并不会让 RAG 过时
Gemini 1.5 提供了高达 1000 万个标记的上下文窗口,有些人开始质疑 RAG 的未来。
虽然长上下文对于分析多个文档或与 PDF 聊天等用例来说是一个改变游戏规则的功能,但关于 RAG 即将消亡的传言都被大大夸大了。
首先,即使上下文窗口有 1000 万个 token,仍然需要一种方法来选择信息输入模型。其次,除了狭窄的“大海捞针”评估之外,还没有看到令人信服的数据表明模型可以有效地推理如此大的上下文。因此,如果没有好的检索(和排序),可能会让模型被干扰因素所淹没,甚至可能会用完全不相关的信息填充上下文窗口。
最后是成本。Transformer 的推理成本随着上下文长度呈二次方增长(或在空间和时间上都呈线性增长)。仅仅因为存在一个模型可以在回答每个问题之前读取你组织的整个 Google Drive 内容,并不意味着这是一个好主意。可以类比一下如何使用 RAM:仍然从磁盘读取和写入,即使存在 RAM 达到数十 TB 的计算实例。
所以,不要急着把你的 RAG 扔进垃圾桶。即使上下文窗口的大小不断增大,这种模式仍然会很有用。
微调和优化工作流(Workflow)
提示引导LLM只是第一步。为了充分利用它们,需要跳出单一提示的局限,拥抱工作流。例如,如何将一个复杂的任务分解成多个更简单的任务?微调或缓存何时有助于提高性能并降低延迟/成本?下面分享一些经过验证的策略和真实世界的例子,帮助优化和构建可靠的 LLM 工作流。
逐步的多轮“流程”可以带来巨大的提升。我们已经知道,将一个大型提示分解成多个较小的提示,可以获得更好的结果。AlphaCodium 就是一个例子:通过从单一提示切换到多步骤工作流程,他们将 GPT-4 在 CodeContests 上的准确率(pass@5)从 19% 提高到 44%。详细阅读:微软、OpenAI大佬暗示LLM应用开发范式迁移:从Prompt Engineering到Flow Engineering。该工作流程包括:
- 反思问题
- 在公开测试集上进行推理
- 生成可能的方案
- 对可能的方案进行排序
- 生成合成测试集
- 在公开测试集和合成测试集中反复迭代方案。
目标明确的小任务最适合作为Agent或流程提示。虽然不强制要求每个Agent提示都请求结构化输出,但是,结构化输出对于与编排Agent与环境互动的任何系统的接口都有很大帮助。
一些尝试:
- 一个明确的规划步骤,尽可能严格规定。考虑提供预定义的计划供选择。
- 将原始的用户提示改写成Agent提示。注意,这个过程是有损失的!
- 以Chain、DAG 和状态机的Agent行为;不同的依赖关系和逻辑关系可能更适合不同的规模,也可能不那么适合。能否从不同的任务架构中优化性能?
- 规划验证;规划可以包括如何评估其他Agent响应的说明,以确保最终组装能很好地协同工作。
- 具有固定上游状态的提示工程--确保Agent提示是根据之前可能发生的情况的变体集合进行评估的。
优先采用确定性的工作流
虽然 Agent可以动态地响应用户请求和环境,但它们的非确定性本质使得它们难以部署。Agent执行的每个步骤都有失败的可能性,而从错误中恢复的可能性很小。因此,代理成功完成多步任务的可能性随着步骤数量的增加而呈指数级下降。结果,构建Agent的团队发现很难部署可靠的Agent。
一种很有前景的方法是让Agent系统生成确定性的计划,然后以结构化、可重复的方式执行这些计划。在第一步中,给定一个高级目标或提示,Agent会生成一个计划。然后,该计划以确定性的方式执行。这使得每个步骤都更加可预测和可靠。好处包括:
- 生成的计划可以作为少量样本,用于提示或微调。
- 确定性执行使系统更加可靠,因此更易于测试和调试。此外,故障可以追溯到计划中的特定步骤。
- 生成的计划可以表示为有向无环图(DAG),与静态提示相比,它更容易理解和适应新情况。
最成功的Agent构建者可能是那些在管理初级工程师方面经验丰富的人,因为生成计划的过程类似于我们如何指导和管理初级工程师。我们给初级工程师明确的目标和具体的计划,而不是含糊不清的开放式指示,我们也应该对我们的Agent做同样的事情。
最终,构建可靠、可工作的Agent的关键可能在于采用更结构化、确定性的方法,以及收集数据来改进提示和微调模型。没有这些,我们将构建出可能在某些时候工作得异常好的Agent,但平均而言,会让用户失望,从而导致用户保留率低。
除了温度(temperature)之外,如何获得更多样化的输出
假设你的任务需要 LLM 输出的多样性。也许你正在编写一个 LLM 管道,根据用户之前购买的商品列表来推荐商品。当你多次运行提示时,你可能会注意到生成的推荐结果过于相似——所以你可能会在 LLM 请求中增加温度参数。
简单来说,增加温度参数会使 LLM 响应更加多样化。在采样时,下一个标记的概率分布变得更加平坦,这意味着通常不太可能出现的标记会被更多地选中。尽管如此,在增加温度时,你可能会注意到与输出多样性相关的某些故障模式。例如,目录中一些可能很合适的商品可能永远不会被 LLM 输出。如果这些商品在训练时非常有可能遵循提示,那么同一批商品可能会在输出中过度表示。如果温度过高,你可能会得到引用不存在的商品(或胡言乱语!)的输出。
换句话说,增加温度并不能保证 LLM 会从你期望的概率分布(例如,均匀随机)中采样输出。尽管如此,我们还有其他技巧来增加输出多样性。最简单的方法是调整提示中的元素。例如,如果提示模板包含一个项目列表,例如历史购买记录,每次将这些项目插入提示时对其进行随机排序会产生显著影响。
此外,保留一个最近输出的简短列表有助于防止冗余。在推荐商品的例子中,通过指示 LLM 避免推荐来自这个最近列表的商品,或者通过拒绝并重新采样与最近建议相似的输出,可以进一步使响应多样化。另一个有效的策略是改变提示中使用的措辞。例如,加入类似“选择用户会经常使用并喜欢的商品”或“选择用户可能会推荐给朋友的商品”这样的短语可以转移焦点,从而影响推荐商品的多样性。
缓存作用被低估
缓存无需对相同输入重新计算响应,从而节省了成本,消除了生成延迟。此外,如果某个回复之前已被屏蔽,我们就可以提供经过审核的回复,从而降低提供有害或不适当内容的风险。
一种直接的缓存方法是为正在处理的项目使用唯一的 ID,例如我们正在总结新文章或产品评论。当收到请求时,我们可以检查缓存中是否已经存在摘要。如果存在,我们可以立即返回;如果不存在,我们就生成、保护并提供摘要,然后将其存储在缓存中,以备将来的请求。
对于更开放的查询,我们可以借鉴搜索领域的技术,后者也利用缓存来处理开放式输入。自动完成和拼写纠正等功能也有助于规范用户输入,从而提高缓存命中率。
何时微调
在某些任务中,即使是设计最巧妙的提示也会有不足之处。例如,即使经过大量的提示工程设计,系统可能仍然无法返回可靠、高质量的输出。如果是这样,就有必要针对特定任务对模型进行微调。
最佳实践:
- Honeycomb 的自然语言查询助手:最初,"programming manual "是在提示中提供的,同时还提供了 n 个示例供用户在上下文中学习。虽然效果不错,但对模型进行微调后,特定领域语言的语法和规则输出效果更好。地址:https://www.honeycomb.io/blog/introducing-query-assistant
然而,微调虽然有效,但代价也很大。需要对微调数据进行标注,对模型进行微调和评估,并最终对模型进行部署管理。因此,要考虑较高的前期成本是否值得。如果通过提示可以实现 90% 的目标,那么微调可能就不值得投入。不过,如果决定进行微调,为了降低收集人工标注样本的成本,可以在合成数据上生成并进行微调,或者在开源数据上进行微调。
评估与监督
评估 LLM 可能是一个雷区。LLM 的输入和输出是任意文本,我们为其设定的任务也多种多样。然而,严谨周到的评估至关重要--OpenAI 的技术领袖就从事评估工作并对个别评估提出反馈意见绝非偶然。
评估 LLM 应用程序需要多种定义和还原:它可以是简单的单元测试,也可以更像是可观测性,还可能只是数据科学。我们发现所有这些观点都很有用。在下面的章节中,我们将就构建评估 和监控pipeline的重要性提供一些经验。
从真实的输入/输出样本中创建一些基于断言的单元测试
创建单元测试(即断言),其中包含来自生产的输入和输出样本,并基于至少三个标准对输出进行预期。虽然三个标准可能看起来是任意的,但这是一个实用的开始数字;更少的标准可能表明你的任务没有得到充分定义或过于开放,就像一个通用聊天机器人。这些单元测试或断言应该由pipeline中的任何更改触发,无论它是编辑提示、通过 RAG 添加新上下文还是其他修改。这篇写作用例中给出了一个基于断言的测试示例(https://hamel.dev/blog/posts/evals/#step-1-write-scoped-tests)。
考虑从指定要包含或排除在所有响应中的短语或想法的断言开始。还要考虑检查以确保单词、项目或句子计数在一定范围内。对于其他类型的生成,断言看起来可能不同。执行评估(Execution-evaluation)是评估代码生成的一种强大方法,其中你运行生成的代码并确定运行时的状态是否足以满足用户请求。
举例来说,如果用户需要一个名为 foo 的新函数,那么在执行Agent生成的代码后,foo 应该是可调用的!执行评估所面临的一个挑战是,Agent代码离开运行时的形式经常与目标代码略有不同。将断言 "放宽 "到任何可行答案都能满足的绝对最弱假设是有效的。
最后,按照客户的预期使用产品(即 "dogfooding")可以深入了解真实世界数据中的失败类型。这种方法不仅有助于识别潜在的弱点,还能提供有用的生产样本,并将其转换为 evals。
LLM-as-Judge(LLM充当法官)可以(在一定程度上)发挥作用,但并不是银弹
LLM-as-Judge,即我们使用一个强大的 LLM 来评估其他 LLM 的输出,有些人对此持怀疑态度。(不过,如果实施得当,LLM-as-Judge 与人类判断之间的相关性还是不错的,至少可以帮助建立新提示或新技术的性能预设。具体来说,在进行配对比较(如对照组与治疗组)时,LLM-as-Judge 通常能正确地判断方向,尽管输赢的偏差可能比较大。
以下是一些建议,可以让LLM-as-Judge 发挥最大作用:
- 使用成对比较:与其要求 LLM 在Likert scale上对单项输出打分,不如向它提供两个选项,让它选择更好的那个。这往往会带来更稳定的结果。
- 控制位置偏差:提交选项的顺序会使 LLM 的决策出现偏差。为了减少这种偏差,可以将每个配对比较进行两次,每次交换配对的顺序。交换顺序后,确保正确的选项获胜!
- 允许并列:在某些情况下,两个选项可能同样好。因此,允许LLM宣布平局,这样就不必武断地选出胜者。
- 使用COT:在给出最终偏好之前,要求 LLM 解释其决定,可以提高评估的可靠性。另外,这还能让您使用较弱但较快的 LLM,并仍能获得类似的结果。由于pipeline的这一部分经常处于批处理模式,CoT 带来的额外延迟不成问题。
- 控制回答长度:LLM 往往偏向于较长的回答。为减少这种偏差,应确保成对的回答长度相似。
LLM-as-Judge 的一个特别强大的应用是根据回归结果检查新的提示策略。如果已经跟踪了一系列生产结果,有时可以使用新的提示策略重新运行这些生产示例,并使用 LLM-as-Judge 快速评估新策略可能存在的问题。
下面是一个简单而有效的 "LLM-as-Judge "迭代方法示例,我们只需记录 LLM 响应、法官评价(即 CoT)和最终结果。然后与利益相关者一起对其进行审查,以确定需要改进的地方。经过三次迭代,人类与 LLM 的一致性从 68% 提高到 94%!
不过,LLM-as-Judge 并非银弹。在语言的某些微妙方面,即使是最强大的模型也无法进行可靠的评估。此外,我们还发现传统的分类器和奖励模型可以达到比 LLM-as-Judge 更高的准确度,而且成本和延迟更低。在代码生成方面,LLM-as-Judge 可能比执行评估等更直接的评估策略更弱。
"实习生测试”评估产品
在评估一代产品时,我们喜欢使用以下 "实习生测试":如果您将语言模型的准确输入(包括上下文)作为一项任务交给相关专业的普通大学生,他们能成功吗?需要多长时间?
如果答案是否定的,因为LLM缺乏所需的知识,那么请考虑如何丰富背景知识。
如果答案是否定的,而且我们根本无法改进语境来解决这个问题,那么我们可能遇到了对这代LLM来说太难的任务。
如果答案是肯定的,但需要一段时间,我们可以尝试降低任务的复杂性。它可以分解吗?任务的某些方面是否可以更加模板化?
如果答案是肯定的,他们会很快得到答案,那么就该深入研究数据了。模型做错了什么?我们能找到失败的模式吗?试着让模型在做出反应之前或之后解释自己,以帮助你建立思维理论。
过分强调某些测试成绩会影响整体成绩
这方面的一个例子是 "大海捞针"(NIAH)评估。最初的评估有助于量化模型召回率随着上下文大小的增加而增加,以及召回率如何受针的位置影响。然而,它已经被过分强调,以至于在双子座 1.5 的报告中被列为图 1。评估涉及在重复保罗-格雷厄姆(Paul Graham)文章的长文档中插入一个特定短语("The special magic {city} number is: {number}"),然后提示模型回忆魔术数字(magic number)。
虽然有些模型可以达到近乎完美的记忆效果,但 NIAH 是否真正反映了实际应用中所需的推理和记忆能力,还是个问题。考虑一个更实际的场景:给定一个小时的会议纪要,LLM能否总结出关键决策和下一步步骤,并将每个项目正确归属于相关人员?这项任务更切合实际,它超越了死记硬背的范畴,还考虑了解析复杂讨论、识别相关信息和综合总结的能力。
下面是一个实用的 NIAH 评估实例。利用医患视频通话的记录,LLM 被问及病人的用药情况。其中还包括一个更具挑战性的 NIAH,即插入一个关于披萨配料的随机配料的短语,例如 "制作完美披萨所需的秘密配料是:浸泡过浓咖啡的红枣、柠檬、杏仁和杏仁油:浸泡过浓咖啡的红枣、柠檬和山羊奶酪"。药物任务的回忆率约为 80%,披萨任务的回忆率为 30%。
另外,过分强调 NIAH 评估可能会降低提取和总结任务的性能。因为这些 LLM 非常精细地关注每个句子,它们可能会开始将无关细节和干扰项视为重要内容,从而将其纳入最终输出(而它们本不应该这样做!)。
这也适用于其他评估和用例。例如,总结。强调事实的一致性可能会导致摘要不那么具体(因此不太可能与事实不一致),也可能不那么相关。反之,如果强调写作风格和口才,则可能导致更多花哨的营销型语言,从而造成事实不一致。
将标注简化为二元任务或成对比较
用Likert scale对模型输出提供开放式反馈或评分对认知能力要求很高。因此,收集到的数据会因人类评分者之间的差异而变得更加嘈杂,从而降低其实用性。更有效的方法是简化任务,减轻标注者的认知负担。二元分类和成对比较是两种行之有效的任务。
在二元分类中,标注者需要对模型的输出做出简单的 "是 "或 "否 "判断。他们可能会被问到生成的摘要是否与源文件的事实一致,或者建议的回复是否相关,或者是否含有毒性。与Likert scale相比,二元判定更为精确,评分者之间的一致性更高,吞吐量也更大。Doordash 就是这样设置标签队列的,通过一棵 "是 "与 "否 "的问题树来标记菜单项。
在成对比较中,标注者会看到一对模型响应,并询问哪个更好。对于人类来说,说出 "A 比 B 好 "比单独给 A 或 B 打分更容易,因此标注速度更快、更可靠(比Likert scale更可靠)。在一次 Llama2 会议上,Llama2 论文的作者托马斯-斯基亚洛姆(Thomas Scialom)证实,成对比较比收集监督下的微调数据(如书面回复)更快、更便宜。前者的成本为每单位 3.5 美元,而后者的成本为每单位 25 美元。
(无参考文献)评估(EVAL)和围栏(guardrails)可以互换使用
Guardrails 有助于捕捉不恰当或有害的内容,而 evals 则有助于衡量模型输出的质量和准确性。就无参考的 evals 而言,它们可以说是一枚硬币的两面。无参照评估是指不依赖于 "黄金 "参照(如人工撰写的答案)的评估,可以仅根据输入提示和模型的响应来评估输出的质量。
其中的一些例子是摘要评估,我们只需考虑输入文档,就能对摘要的事实一致性和相关性进行评估。如果摘要在这些指标上得分较低,我们就可以选择不向用户显示它,从而有效地将评估用作围栏。同样,无参考翻译 evals 可以在不需要人工翻译参考的情况下评估翻译质量,这同样允许我们将其用作围栏。
LLM 在不应该返回输出时也会返回输出
使用 LLM 时的一个主要挑战是,它们经常会在不应该产生输出的情况下产生输出。这可能导致无害但无意义的响应,或更严重的缺陷,如毒性或危险内容。例如,当被要求从文档中提取特定属性或元数据时,LLM 可能会自信地返回值,即使这些值实际上并不存在。另外,由于我们在上下文中提供了非英语文档,因此模型可能会以英语以外的语言做出响应。
虽然我们可以尝试提示 LLM 返回 "不适用 "或 "未知 "的响应,但这并非万无一失。即使有对数概率,它们也不是衡量输出质量的好指标。虽然日志概率表明了输出中出现标记的可能性,但并不一定反映了生成文本的正确性。相反,对于经过训练以响应查询并生成连贯响应的指令调整模型来说,对数概率可能没有得到很好的校准。因此,虽然高对数概率可能表明输出是流畅和连贯的,但并不意味着它是准确或相关的。
虽然谨慎的提示工程能在一定程度上起到帮助作用,但我们还应辅之以强大的防护机制,以检测和过滤/再生不受欢迎的输出。例如,OpenAI 提供了一个内容节制 API,可以识别不安全的回应,如仇恨言论、自残或性输出。同样,还有许多用于检测个人身份信息(PII)的软件包。这样做的好处之一是,guardrails 在很大程度上与用例无关,因此可以广泛应用于特定语言的所有输出。此外,通过精确检索,如果没有相关文档,我们的系统可以确定性地回答 "我不知道"。
由此得出的一个推论是,LLM 可能无法在预期时间内产生输出结果。发生这种情况的原因有很多,从直接的问题(如来自 API 提供商的长尾延迟)到更复杂的问题(如输出被内容审核过滤器阻止)。因此,必须持续记录输入和(可能缺乏的)输出,以便进行调试和监控。
幻觉是一个顽固的问题。
内容安全或 PII 缺陷备受关注,因此很少发生,而事实不一致则不同,它顽固存在,检测起来更具挑战性。它们更常见,发生率基线为 5%-10%,而根据我们从LLM的提供者那里了解到的情况,要将其控制在 2% 以下是很有挑战性的,即使是在摘要等简单任务中也是如此。
为了解决这个问题,我们可以将提示工程(生成的上游)和事实不一致防护(生成的下游)结合起来。在提示工程方面,CoT 等技术可以让 LLM 在最终返回输出之前解释其推理,从而帮助减少幻觉。然后,我们可以应用事实不一致围栏(https://eugeneyan.com/writing/finetuning/)来评估摘要的事实性,并过滤或重新生成幻觉。在某些情况下,幻觉可以被确定地检测出来。在使用来自 RAG 检索的资源时,如果输出是结构化的,并能识别出资源是什么,那么就应该能够手动验证它们来自输入上下文。
原文:https://www.oreilly.com/radar/what-we-learned-from-a-year-of-building-with-llms-part-i/
本文转载自 AI工程化,作者: ully