一篇写的非常好的文章,详细介绍了如何对大语言模型的质量进行评估。流程包括单元测试、人工评估、模型评估和 A/B 测试,强调快速迭代和数据管理的重要性。一个好的测试流程是模型进步的必要条件,如果没办法准确测试模型质量,就没办法帮助模型进步。
原文链接:https://hamel.dev/blog/posts/evals/
动机
五年前,当我领导团队创建 CodeSearchNet 时,我开始与语言模型合作。CodeSearchNet 是 GitHub CoPilot 的前身。从那时起,我见过许多成功和失败的构建大语言模型 (LLMs) 产品的方法。我发现,失败的产品几乎总是有一个共同的根本原因:未能创建健壮的评估系统。
我目前是一名独立顾问,帮助公司构建特定领域的人工智能产品。我希望公司通过仔细阅读这篇文章可以节省数千美元的咨询费用。尽管我喜欢赚钱,但我讨厌看到人们反复犯同样的错误。
这篇文章概述了我对构建基于大语言模型的人工智能产品的评估系统的看法。
快速迭代等于成功
与软件工程一样,人工智能的成功取决于你迭代的速度。你必须具备以下过程和工具:
- 评估质量(例如:测试)。
- 调试问题(例如:日志记录和检查数据)。
- 改变系统的行为(提示工程、微调、编写代码)。
许多人只专注于上述的第三点,这阻碍了他们将大语言模型产品改进到演示之外。做好所有三项活动可以创造一个良性循环,将优秀的人工智能产品与平庸的人工智能产品区分开来(下图可视化了这个循环)。
如果你简化评估过程,所有其他活动都会变得容易。这与软件工程中的测试非常相似,尽管需要前期投入,但从长远来看,测试会带来巨大的回报。
为了将这篇文章放在一个真实的情况下,我将通过一个案例研究来说明我们如何构建一个快速改进的系统。我将主要关注评估,因为这是最关键的组成部分。
案例研究:Lucy,一个房地产人工智能助手
Rechat 是一个软件即服务 (SaaS) 应用程序,允许房地产专业人士执行各种任务,如管理合同、搜索房源、构建创意资产、管理预约等。Rechat 的理念是,你可以在一个地方完成所有事情,而不必在许多不同的工具之间切换上下文。
Rechat 的人工智能助手 Lucy 是一个典型的人工智能产品:一个对话界面,无需点击、输入和导航软件。在 Lucy 的初始阶段,通过提示工程取得了快速进展。然而,随着 Lucy 的覆盖范围扩大,人工智能的性能达到了瓶颈期。这些症状包括:
- 解决一个失败模式导致其他失败模式的出现,类似于打地鼠游戏。
- 除了感觉检查之外,对人工智能系统在各项任务中的有效性缺乏可见性。
- 提示变得冗长且笨拙,试图涵盖许多边缘情况和示例。
问题:如何系统地改进人工智能?
为了突破这个瓶颈期,我们创建了一个以评估为中心的系统方法来改进 Lucy。下图说明了我们的方法。
这张图是我尽最大努力说明我改进人工智能系统的心智模型。在现实中,这个过程是非线性的,可以采取许多不同的形式,可能看起来像这张图,也可能不像。
我在下面的评估背景下讨论了这个系统的各个组成部分。
评估的类型
严格和系统的评估是整个系统中最重要的部分。这就是为什么 "评估和策划" 在图的中心用黄色突出显示。你应该把大部分时间花在使评估更加健壮和简化上。
有三个级别的评估需要考虑:
- 单元测试
- 模型和人工评估(这包括调试)
- A/B 测试
级别 3 的成本大于级别 2,级别 2 的成本大于级别 1。这决定了你执行它们的节奏和方式。例如,我经常在每次代码更改时运行 1 级评估,在设定的节奏上运行 2 级评估,只在重大产品更改后运行 3 级评估。在开始基于模型的测试之前,先完成大部分 1 级测试也很有帮助,因为它们需要更多的工作和时间来执行。
关于何时引入每个测试级别,没有严格的公式。你需要在快速获得用户反馈、管理用户感知和你的人工智能产品的目标之间取得平衡。这与你必须为产品做的平衡行为没有太大区别。
第1级: 单元测试
针对大语言模型 (Large Language Model, LLM) 的单元测试是一些断言语句 (类似于在 pytest 等单元测试框架中编写的断言)。与典型的单元测试不同,你希望将这些断言组织起来,用于单元测试之外的地方,例如数据清理和模型推理过程中的自动重试 (利用断言错误进行纠正)。重点在于,这些断言应该在你开发应用程序的过程中能够快速且低成本地运行,这样你就可以在每次代码变更时都执行它们。如果你在设计断言时遇到困难,你应该仔细检查你的日志和错误模式。此外,不要羞于使用 LLM 来帮助你集思广益,设计出更多的断言!
步骤1: 编写特定范围的测试
编写单元测试的最有效方法,是将你的 LLM 的功能范围划分为不同的特性和场景。所谓"特定范围的测试",就是针对某个特定特性或场景编写的测试。例如,Lucy 的一个特性是能够找到房地产房源,我们可以将其细分为以下几个场景:
特性: 房源查找器
这个待测试的特性是一个函数调用,用于响应用户查找房地产房源的请求。例如,"请找到位于加州圣何塞市的3个以上卧室、价格低于200万美元的房源"。
LLM 会将其转换为一个查询语句,在 CRM 系统中执行。然后,断言语句会验证返回的结果数量是否与预期一致。在我们的测试套件中,我们设计了三个用户输入,分别触发以下三个场景,并执行相应的断言 (这是一个为了说明问题而简化的例子):
场景 | 断言 |
---|---|
只有一个房源匹配用户查询 | 返回的房源列表长度等于1,即 len(listing_array) == 1 |
多个房源匹配用户查询 | 返回的房源列表长度大于1,即 len(listing_array) > 1 |
没有房源匹配用户查询 | 返回的房源列表长度等于0,即 len(listing_array) == 0 |
除了特定特性的测试,还有一些通用的测试。例如,下面是一个通用测试的代码,用于确保输出结果中没有包含 UUID:
const noExposedUUID = message => {
// 删除双大括号内的所有文本
const sanitizedComment = message.comment.replace(/\\{\\{.*?\\}\\}/g, '')
// 搜索暴露的UUID
const regexp = /[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/ig
const matches = Array.from(sanitizedComment.matchAll(regexp))
expect(matches.length, 'Exposed UUIDs').to.equal(0, 'Exposed UUIDs found')
}
从 CRM 返回给 LLM 的结果中,包含了一些不应该展示给用户的字段,例如与某个条目关联的 UUID。我们在 LLM Prompt 中要求 LLM 不要包含 UUID。我们使用一个简单的正则表达式来断言 LLM 的响应中不包含 UUID。
Rechat 有数百个这样的单元测试。我们会根据通过人工评估和调试观察到的新问题,不断更新这些测试。不管是用户提出的挑战,还是产品本身的演进,都可能带来新的测试需求。在开发 AI 系统 (如改进 Prompt、优化 RAG 等) 的过程中,这些单元测试对于快速获得反馈至关重要。随着产品的成熟,许多人最终会超越单元测试,转向其他层次的评估,但单元测试这一步是绝对不能跳过的!
步骤2: 创建测试用例
要检验上述断言,你需要生成一些测试用例或输入,以触发所有你想要测试的场景。我经常利用 LLM 来自动生成这些输入。例如,下面是 Rechat 用来为"创建和检索联系人"特性生成测试输入的一个 Prompt:
请编写50条不同的指令,模拟房地产经纪人给助手创建 CRM 联系人的场景。联系人信息可以包括姓名、电话、邮箱、伴侣姓名、生日、标签、公司、地址和职位。
对于每一条创建联系人的指令,请再生成一条用于查找该联系人的指令。
一条用于查找该联系人的指令。
最终结果请以 JSON 代码块的形式给出,其中每个元素都是一个包含两条指令的数组,例如:
jsonCopy code[
[
"为 John 创建联系人 ([email protected])",
"John Smith 的邮箱地址是多少?"
]
]
使用上面的 Prompt,我们可以生成如下测试用例:
jsonCopy code[
[
"为 John Smith 创建联系人 ([email protected]),电话号码为 123-456-7890,地址为 123 Apple St.",
"John Smith 的邮箱地址是多少?"
],
[
"添加 Emily Johnson,电话 987-654-3210,邮箱 [email protected],公司 ABC Inc.",
"Emily Johnson 的电话号码是多少?"
],
[
"为 Tom Williams 创建联系人,生日为 1985年10月20日,公司为 XYZ Ltd,职位为经理。",
"Tom Williams 的职位是什么?"
],
[
"为 Susan Brown 添加联系人,伴侣名字为 James Brown,邮箱为 [email protected]。",
"Susan Brown 的伴侣叫什么名字?"
],
// ...
]
对于上述每个测试用例,我们先执行第一条指令来创建联系人,再执行第二条指令来查询该联系人的信息。如果 CRM 返回的查询结果数量不是正好1个,就说明在创建或查询联系人的过程中出现了问题。我们还可以运行一些通用的断言,比如验证响应中没有包含 UUID 等。
随着对系统的人工评估和调试,你必须不断更新这些测试用例。关键是要让测试用例尽可能地覆盖实际用户的各种交互场景,同时又要有一定的挑战性。
其实你并不需要等到有了真实的用户数据才开始测试。你完全可以基于自己对用户使用场景的合理猜测,来生成一些模拟数据。你也可以先让一小部分用户试用你的产品,再根据他们的实际使用情况来优化你的数据模拟策略。设计良好的测试用例和断言的一个标志是,模型很难通过它们。这些模型不能轻易通过的场景,恰恰提示了一些可以通过微调等技术加以解决的问题。
与传统的单元测试不同,LLM 测试的通过率并不一定要做到100%。测试的通过率本质上是一个产品层面的权衡,取决于你能够容忍的错误率。
步骤3: 定期执行测试并追踪结果
编排和执行第1级测试的方式有很多。Rechat 一直在使用 CI 基础设施 (例如 GitHub Actions、GitLab Pipelines 等) 来运行这些测试。不过,针对工作流这一环节的工具还处于早期阶段,而且发展非常迅速。
我的建议是,选择能与你现有技术栈无缝集成的测试方案,尽量减少集成的复杂度。除了实际运行测试本身,你还需要追踪测试结果随时间的变化情况,以便观察系统是否在不断改进。如果你使用 CI,最好能把指标数据和测试/Prompt 的版本信息一起保存在 CI 系统之外,以便后续分析。
我建议从简单的方案开始,利用现成的分析系统来可视化测试结果。例如,Rechat 就使用 Metabase 来跟踪 LLM 测试结果随时间的变化。下图是 Rechat 在 Metabase 中构建的一个仪表盘:
这个图表展示了我们在修复前(左侧)和修复后(右侧),Lucy 模型的一个特定错误的出现频率。
级别 2:人工评估和模型评估
在你构建了扎实的级别 1 测试基础后,就可以开始尝试其他形式的验证了,这些验证无法仅靠断言来完成。在开展人工评估和模型评估之前,你需要先记录下评估过程的跟踪数据(traces)。
记录跟踪数据
跟踪数据是软件工程领域一个由来已久的概念,它是一系列事件的日志记录,比如用户的会话记录,或者请求在分布式系统中的流转情况。换句话说,跟踪数据就是日志记录的逻辑分组。在大语言模型(LLMs)的语境下,跟踪通常指的是你与 LLM 进行对话的过程。比如一次用户发言,后面是 AI 的回复,再后面又是用户的发言,这就是一个跟踪数据的例子。
现在已经有越来越多的工具可以用来记录 LLM 的跟踪数据了。[2] Rechat 使用的是 LangSmith,它可以记录对话的跟踪数据,并以人类可读的方式呈现,还提供了一个交互式的操作界面来调试和优化提示语(prompt)。有时候,记录跟踪数据需要你在代码里做一些埋点。就像 Rechat 使用 LangChain 时,LangChain 会自动将跟踪事件记录到 LangSmith。下面是一个示例截图:
我很喜欢 LangSmith,它不要求你必须使用 LangChain,而且使用起来非常直观方便。搜索、过滤、阅读跟踪数据是任何跟踪工具的基本功能。我发现有些工具连这些基本功能都没有很好地实现!
查看跟踪数据
在查看数据的过程中,你要尽量减少阻碍。这就意味着要以针对具体领域的方式呈现跟踪数据。我经常发现,最好是自己开发数据查看和标注工具,这样就可以把所有需要的信息都集中在一个界面上了。就拿 Lucy 的案例来说,我们需要查看很多不同的信息来源(比如跟踪日志、客户关系管理系统等),才能弄清楚 AI 究竟做了什么。这种阻碍查看数据的情况一定要避免。在 Rechat 的案例中,我们添加了以下信息:
- 正在评估的工具(功能)和场景
- 跟踪数据是由模拟输入还是真实用户输入产生的
- 在不同的工具和场景组合之间切换的过滤器
- 指向当前记录在客户关系管理系统(CRM)和跟踪日志中的链接
我为处理过的每个问题都开发了这个工具的不同版本。有时候我甚至需要内嵌另一个应用,以便查看用户的实际交互情况。下面是我们为评估 Rechat 的跟踪数据而开发的工具截图:
另一个专门针对 Lucy 的设计选择是,我们注意到很多错误都出在 LLM 最终输出的一些小细节上(比如格式、内容等)。所以我们决定让人工标注人员可以编辑最终输出,以便我们策划和修正数据,为后续的微调(fine-tuning)做准备。
这些工具可以用 Gradio、Streamlit、Panel、Shiny 等轻量级前端框架快速开发,一天之内就能搞定。上面展示的那个工具就是用 Python 版的 Shiny 开发的。此外还有像 Lilac 这样的工具,它用 AI 来实现语义化的数据搜索和筛选,在调试问题、寻找相似数据时非常好用。
我通常从给样本数据打标签开始,判断它们是好还是不好。我发现打分或者用更细致的等级来评价数据,要比二元分类更麻烦。虽然有一些先进的技术可以提高人工评估的效率或准确性(比如主动学习、一致性投票等),但我建议还是从简单的开始。最后要强调的是,和单元测试一样,你要系统地管理和分析人工评估的结果,以便判断系统性能是否在持续改进。
正如后面会提到的,这些标注好的样本数据可以用来衡量系统的整体质量,验证自动评估的效果,以及为微调筛选优质的合成数据。
要检查多少数据?
我经常被问到应该检查多少数据。在项目初期,你要尽可能多地检查数据。我通常会至少把所有测试用例生成的跟踪数据和用户生成的跟踪数据都过一遍。永远不要停止查看数据,There ain't no such thing as a free lunch. 不过随着项目的推进,你可以逐渐采样查看,减轻一些工作量。
利用 LLM 进行自动评估
很多工具提供商都声称他们的产品可以消除人工检查数据的必要。但我认为定期抽查一部分跟踪数据,由人工进行评估,还是很有必要的。我经常发现,判断一个输出是否正确,多少有些主观性,你必须让模型和人类保持一致。
你要持续跟踪模型评估和人工评估的相关性,据此来判断可以在多大程度上依赖自动评估。此外,通过收集标注人员的反馈,了解他们判断的理由,你可以用提示工程(prompt engineering)或者微调的方式迭代改进评估模型,让它和人类更加一致。不过我个人更倾向于用提示工程的方式来调教评估模型。
我喜欢用 Excel 这种低技术含量的方式来迭代评估模型,让它和人工评估对齐。比如,我每隔几天就会给同事 Phillip 发一个下面这样的电子表格,让他为一个自然语言查询生成器的不同使用场景打分。这个电子表格包含以下信息:
- model response:LLM 生成的预测结果
- model critique:一个(通常更强大的)LLM 对原始 LLM 预测结果的评价
- model outcome:评价模型给出的二元标签,判定原始预测是"好"还是"不好"
然后 Phillip 填写他自己对相同信息的判断,包括他的评价(critique)、结果(outcome)以及每一组 25-50 个样本应该如何修改(这些列的名称前缀是"phillip_"):
通过这些数据,我可以随着时间推移,迭代改进评价模型的提示,让它和 Phillip 的判断越来越接近。这个过程用一个电子表格就可以很方便地跟踪:
这张截图展示了我们记录对齐模型评估和人工评估的尝试。
关于基于模型的评估,有一些总体的建议:
- 在条件允许的情况下,使用尽可能强大的模型。要很好地评价一个输出,通常需要更高级的推理能力。相对于生产环境,在评估时你通常可以选择更慢但更强大的模型。
- 基于模型的评估本身也是你要解决的大问题中的一个子问题。你需要维护一个小型的评估系统来追踪它的效果。我有时会在评估阶段微调模型(但我尽量避免这样做)。
- 在让评估模型和人类判断对齐后,你要持续做一些常规测试,监控模型和人类的一致性。
我最喜欢打造优质评估模型的一点是,它给出的评价意见可以用来生成高质量的合成数据,这个我稍后会讲到。
级别 3:A/B Testing
最后,进行 A/B 测试总是一个好主意,以确保你的 AI 产品能够驱动你期望的用户行为或结果。与其他类型的产品相比,大语言模型 (Large Language Model, LLM) 的 A/B 测试并没有太大不同。如果你想了解更多关于 A/B 测试的信息,我建议阅读 Eppo 的博客 (由我过去一起工作的优秀同事创建)。
在你充分准备好并确信你的 AI 产品适合向真实用户展示之前,可以暂时推迟这个阶段。这种评估通常只适用于更成熟的产品。
评估 RAG
除了评估整个系统,你还可以评估 AI 的子组件,比如 RAG (Retrieval-Augmented Generation)。评估 RAG 超出了本文的范围,但你可以在 Jason Liu 的文章中了解更多关于这个主题的信息。
免费解锁超能力的评估系统
除了能够快速迭代之外,评估系统还可以解锁微调 (fine-tuning) 和调试的能力,这可以把你的 AI 产品提升到一个新的水平。
微调
Rechat 通过微调解决了许多仅通过 prompt engineering 无法解决的失败模式。微调最适合学习语法、风格和规则,而像 RAG 这样的技术则为模型提供上下文或最新的事实。
微调工作的 99% 涉及组装高质量的数据,覆盖你的 AI 产品的各个方面。然而,如果你有一个像 Rechat 这样可靠的评估系统,你就已经拥有了一个强大的数据生成和管理引擎!我将在以后的文章中更详细地阐述微调的过程。
数据合成和管理
为了说明一旦你有了评估系统,数据管理和合成几乎是免费获得的,让我们考虑前面提到的房源查找器的情况,假设你想为其创建额外的微调数据。首先,你可以使用 LLM 生成合成数据,提示如下:
想象一下,如果 Zillow 能够解析自然语言。想出 50 种不同的方式,让用户可以在那里搜索房源。使用真实的城市和社区名称。
你可以使用以下参数:
<由于保密原因而省略>
输出应该是一个 JSON 代码块数组。示例:
[
"纽约市 50 万美元以下的房屋"
]
这与生成测试用例的练习几乎相同!然后,你可以使用 1 级和 2 级测试来过滤掉不符合断言或评论模型认为错误的不良数据。你还可以使用现有的人工评估工具来查看跟踪记录,以管理微调数据集。
调试
当你收到投诉或看到与你的 AI 产品相关的错误时,你应该能够快速调试这个问题。如果你有一个强大的评估系统,你应该已经拥有:
- 一个可以搜索和过滤的跟踪记录数据库。
- 一组可以帮助你标记错误和不良行为的机制 (断言、测试等)。
- 日志搜索和导航工具,可以帮助你找到错误的根本原因。例如,错误可能是 RAG、代码中的 bug 或模型性能不佳。
- 能够快速响应错误并测试解决方案的有效性。
简而言之,评估和调试所需的基础设施之间有着非常大的重叠。
结论
评估系统创造了一个良性循环,使你能够非常快速地迭代。这几乎总是人们在构建 AI 产品时会遇到的困难。我希望这篇文章能让你对如何构建评估系统有一个直观的认识。要记住的一些关键要点:
- 消除查看数据的所有障碍。
- 保持简单。不要购买花哨的 LLM 工具。首先使用你现有的工具。
- 如果你没有查看大量数据,那你就做错了。
- 不要依赖通用的评估框架来衡量你的 AI 的质量。相反,要针对你的具体问题创建特定的评估系统。
- 编写大量测试并频繁更新。
LLM 可用于解锁评估系统的创建。例如使用 LLM 来:
- 生成测试用例并编写断言
- 生成合成数据
- 评论和标注数据等
为调试和微调复用你的评估基础设施。
如果你觉得这篇文章有帮助,或者有任何问题,我很乐意听取你的意见。