反对LLM全能主义
许多人在用大型语言模型(LLM)构建真正的新事物,比如以前不可能实现的、狂野的互动小说体验。但如果你正在解决企业长期以来一直试图解决的同类自然语言处理(NLP)问题,使用它们的最佳方式是什么?
公司使用语言技术已有多年,但结果往往好坏参半。以某种智能方式处理文本或语音数据的需求是相当基础的。例如,在大多数热门网站中,文本通常要么是产品的重要组成部分(例如,网站发布新闻或评论),要么是使用模式(例如,用户相互书写文本),要么是输入(例如,新闻聚合器)。一般来说,以语言进行交易在经济活动中占很大比例。我们工作中很大一部分时间都在写作、阅读、说话和倾听。因此,在各种程序中,“处理语言”长期以来一直是一个理想的功能需求也就不足为奇了。2014年,我开始开发spaCy,以下是我当时解释该库动机的摘录:
计算机不理解文本。这很不幸,因为网络几乎完全由文本构成。我们想根据人们喜欢的其他文本来推荐文本。我们想缩短文本以在移动屏幕上显示。我们想对它进行聚合、链接、过滤、分类、生成和纠正。spaCy提供了一个实用函数库,帮助程序员构建此类产品。
如今,很难断言“计算机不理解文本”而不加上巨大的星号或限定条件。即使你对“理解”的构成有某种特定的哲学观点,毫无疑问,LLM能够构建和操纵意义表示,足以满足广泛的实用目的。它们仍然会犯各种错误,但相对少见的是,你会感觉它们根本没有将输入的文字连接起来以形成预期的含义。它们生成的文本也极其流畅。有时它可能非常自信地出错或与你的问题无关,但它几乎总是由真实、连贯的句子组成。这绝对是新现象。
然而,对于公司一直在处理的大多数NLP用例,LLM并不是直接的解决方案。它们非常有用,但如果你想交付可以随时间改进的可靠软件,你不能只是写个提示就完事。一旦你度过了原型阶段并希望交付你所能构建的最佳系统,对于非生成性任务——即存在一个你希望模型找到的特定正确答案的任务——监督学习通常能比上下文学习提供更好的效率、准确性和可靠性。围绕你的模型应用规则和逻辑进行数据转换或处理可以完全枚举的情况也极其重要。
例如,假设你想构建一个在线声誉管理系统。你希望从某中心或其他来源获取帖子流,识别提及你公司或产品的内容,并理解它们之间的共同主题。或许你也想以类似方式监控关键竞争对手的提及情况。你可能希望以多种方式查看数据。例如,你可以提取一些带噪声的指标,比如在仪表板中跟踪的总体“积极性”情感分数,同时你也可以对帖子进行更细致的聚类,并定期更详细地审查。
我不想低估LLM对此类用例的影响。你可以给LLM一组评论,让它总结文本或识别关键主题。而这其中的具体细节可以在运行时改变:你不必针对将要提出的、非常特定的总结或问题类型。这种生成性输出可能是一个彻底的改变者,最终交付数据科学项目通常过度承诺而未能兑现的“洞察力”。除了这些生成性组件,你还可以使用LLM来帮助系统的其他部分。但你应该这样做吗?
LLM足够新,变化也足够快,以至于关于如何最好地使用它们几乎没有共识。最终情况会稳定下来(或者也许通用人工智能终究会席卷我们所有人),我们将积累一些关于什么有效、什么无效的伤疤和智慧。与此同时,我想提出一些常识性的建议。
一种关于如何使用LLM的愿景,我称之为LLM全能主义。如果你有某项任务,你尝试尽可能直接地让LLM去做它。需要某种格式的数据?在提示中要求它。避免将你的任务分解为几个步骤,因为这会阻止LLM端到端地解决你的问题。它还引入了额外的调用,并可能在中间处理步骤中引入错误。当然,LLM确实有局限性,例如其知识的时效性,或者可以传递的上下文大小。因此,你确实必须设法规避这些问题,并使用向量数据库或其他技巧。但从根本上说,LLM全能主义的立场是你希望信任LLM来解决问题。你正在为技术持续改进、当前的痛点随时间不断减少做准备。
发现自己就是左边那个人,这感觉从来都不好。但是,我就在这里。
这种方法有两个大问题。一是,“规避系统限制”通常根本不可能。大多数系统需要比今天的LLM快得多,而根据当前的效率和硬件改进趋势,未来几年内仍将如此。用户在聊天应用中对延迟相当宽容,但在几乎所有其他类型的用户界面中,你无法为单个预测等待数秒钟。这太慢了。在我们的在线声誉管理示例中,你想连接到某种数据流,比如来自某中心或某机构的。你不能直接将其传递给LLM——成本太高了。
第二个问题是,LLM全能主义方法从根本上说不是模块化的。假设你做了一个明显的小妥协,使用一个单独的分类器来预过滤可能提及你公司的文本。你开发了一个与你使用的LLM模型配合良好的提示,并得到了相当不错的输出摘要。现在你收到了一个新请求。用户希望能够查看一些原始数据。为了满足这个需求,你的团队决定开发一个单独的视图,在其中显示提及列表以及一句上下文。
你应该如何着手?你可以设计一个单独的LLM提示,要求它以你被要求的第二种格式提取数据。然而,新提示不能保证识别出与你使用的第一个提示相同的提及集合——不可避免地会存在一些差异。这真的不理想。你希望能够将摘要链接到生成它们的评论组。如果句子视图不同,你就无法做到这一点。与其使用单独的提示,你可以尝试将信息添加到第一个提示中。但现在你输出整个句子,这大大增加了你生成的标记数量,这既更慢也更昂贵。而且你很难在这种新的、更复杂的输出格式下获得与以前相同的准确性。
什么是好的程序?它不仅在于如何高效准确地解决一组特定的需求,还在于它如何能够被可靠地理解、改变和改进。使用LLM全能主义方法编写的程序在这些标准下并不好。
与其抛弃我们学到的所有关于软件设计的知识,要求LLM一次性完成所有事情,我们可以将问题分解成若干部分,并将LLM视为系统中的另一个模块。当我们的需求改变或扩展时,我们不必回去改变整个东西。我们可以添加新的模块,或者重新排列我们已经满意的模块。
将任务分解为独立的模块也有助于你看到哪些部分真正需要LLM,哪些可以用另一种方法更简单可靠地完成。识别英语句子边界并非完全微不足道(你不想只用正则表达式),但这绝对不需要LLM来做。你只需调用spaCy或其他库。这将快得多,而且你不必担心LLM会因某些奇怪的输入而绊倒并返回完全意外的输出。
检测公司提及的任务也可能不需要使用LLM来完成。在初始原型设计中使用LLM当然是有意义的——这是LLM另一个不应被低估的巨大优势。快速原型设计极其重要。你可以高效地探索设计空间,并丢弃不值得进一步开发的想法。但你还需要能够超越原型阶段。一旦你找到一个值得改进的想法,你需要一种方法来实际改进它。
在改进任何统计组件之前,你需要能够评估它。对整个流水线进行某种评估很重要,如果你没有其他方法,你可以用它来判断对组件的某些更改是否让事情变得更好或更糟(这被称为“外部评估”)。但你也应该独立地评估你的组件(“内部评估”)。对于像提及检测器这样的组件,这意味着用正确的标签标注一些文本,将它们放在一边,并在每次更改后针对它们测试你的组件。对于生成性组件,你无法针对一组标注进行评估,但内部评估仍然是可能的,例如使用李克特量表或A/B测试。
内部评估类似于单元测试,而外部评估类似于集成测试。这两者你确实都需要。开始构建评估集时,你常常会发现你关于期望组件如何行为的想法比你意识到的要模糊得多。你需要一个清晰的组件规范来改进它,以及改进整个系统。否则,你将陷入局部最优:对某个组件的更改本身看起来似乎有意义,但总体上你会看到更糟的结果,因为之前的行为正在补偿其他地方的问题。像这样的系统非常难以改进。
一个好的经验法则是,你希望每个评估指标的有效数字对应十个数据点。因此,如果你想区分91%的准确率和90%的准确率,你希望至少有1000个标注数据点。你不想运行这样的实验:你的准确率数字显示有1%的改进,但实际上你只是从94/103变成了96/103。你最终会基于几乎纯粹的运气形成迷信。这不是改进的途径。你需要系统化。
一旦你标注了评估数据,通常最好继续为非生成性组件标注一些训练数据。监督学习在文本分类、实体识别和关系提取等任务上非常强大。如果你清楚组件应该做什么,并能相应地标注数据,通常你可以预期,使用为单个GPU配置、具有预训练表示的Transformer架构,只需几百个带标签的示例,就能获得比LLM更好的准确性。这实际上与LLM是相同的模型架构,只是尺寸更便利,并且配置为只执行一项任务。
以下是我认为目前LLM在NLP项目中应该如何使用的方式——我称之为LLM务实主义。
- 分解:将你希望应用程序用语言做的事情分解为一系列预测性和生成性步骤。
- 简化:保持步骤简单,不要要求那些你可以轻松确定性地完成的转换或格式化。
- 原型:使用LLM提示或现成解决方案处理所有预测性或生成性步骤,组装一个原型流水线。
- 测试:在尽可能真实的上下文中尝试流水线。
- 外部评估:设计某种外部评估。这里的成功是什么样的?节省的净劳动力?参与度?转化率?如果无法直接衡量系统的效用,你可以使用其他类型的指标,但应尽量使其有意义。如果假阴性比假阳性更重要,在你的外部评估指标中考虑这一点。
- 实验:尝试替代的流水线设计。尝试创建那些正确答案独立于你的用例的任务。优先选择文本分类而非实体识别,优先选择实体识别而非关系提取(标注更快,准确性更好)。
- 标注:选择一个预测性(相对于生成性)组件,花两到五个小时为其标注数据。
- 测量:使用你的评估数据测量LLM驱动组件的准确性。
- 辅助标注:使用LLM驱动组件帮助你创建训练数据,以训练你自己的模型。一种方法是简单地保存LLM驱动组件的预测,并相信它们足够好。如果LLM驱动组件的准确性似乎远超出你的需求,这是一个值得尝试的好方法。如果你需要比LLM提供的更好的准确性,你需要更正确的示例数据。一个好的方法是将LLM预测加载到标注工具中并进行修正。
- 训练:在你的新训练数据上训练一个监督模型,并使用之前使用的相同评估数据对其进行评估。
- 决策:为了决定是否应该标注更多训练数据,运行额外的实验,其中保留部分训练数据。例如,比较当你使用现有数据的100%、80%和50%时,你的准确性如何变化。这应该有助于你确定,如果你有120%或150%的数据,你的准确性可能会是什么样子。但是,请注意,如果你的训练集很小,你的准确性可能会有很大方差。尝试几种不同的随机种子,以了解仅因机会而导致的准确性变化有多大,从而帮助你正确看待结果。
- 重复:对任何其他预测性组件重复此过程。
在这整个过程结束时,你将拥有一个适合生产的流水线。它将比一系列LLM调用运行得更快、更准确,并且你将知道,无论输入什么文本,你的预测性组件将始终给你有效的输出。你将拥有不同步骤的评估,并且能够将错误归因于不同的组件。如果你需要改变系统的行为,你将能够在流水线的不同点加入新的规则或转换,而不必回去重新设计你的提示或重写你的响应解析逻辑。
其中一些步骤仍然比应有的难度大一点,特别是如果你以前没有接触过机器学习。这就是我对LLM寄予厚望的地方。LLM确实非常强大,它们可以使很多事情变得容易得多。它们可以帮助我们构建更好的系统,而这正是我们应该使用它们的方式。我称之为LLM全能主义的方法实际上缺乏雄心。它使用LLM轻松获得一个更差的系统——成本更高、运行时间更长、可靠性更差、可维护性更差。相反,我们应该使用LLM来帮助获得更好的系统。这意味着在开发过程中更多地依赖LLM,以打破知识壁垒、创建数据并改进我们的工作流程。但目标应该是在运行时尽可能少地调用LLM。让LLM训练它们自己的更便宜、更可靠的替代品。
附录1:付诸实践
有许多工具和库可以用来标注你自己的数据并训练你自己的NLP模型。HF Transformers和spaCy(我们的库)是其中最受欢迎的两个库。Transformers便于使用来自最新研究的各种模型,并且更接近底层的ML库(PyTorch)。spaCy具有更好的用于处理标注的数据结构,支持混合统计和基于规则操作的流水线,以及更多的框架特性,用于配置、扩展和项目工作流。我们最近发布了spaCy-llm,这是一个扩展,允许你将LLM驱动的组件添加到spaCy流水线中。你可以将LLM与其他组件混合使用,并利用spaCy的Doc、Span、Token和其他类来使用这些标注。
假设你想创建一个检测某些实体的流水线,然后你想获取实体所在的句子。以下是该流水线在Python中构建和使用的示例:
importspacy nlp=spacy.blank("en")nlp.add_pipe("sentencizer")nlp.add_pipe("llm",config={"task":{"@llm_tasks":"spacy.NER.v1","labels":"SAAS_PLATFORM,PROGRAMMING_LANGUAGE,OPEN_SOURCE_LIBRARY"},"model":{"@llm_models":"spacy.Davinci.v2",},},)doc=nlp("There's no PyTorch bindings for Go. We just use Microsoft Cognitive Services.")forentindoc.ents:print(ent.text,ent.label_,ent.sent)spaCy中的sentencizer组件使用规则检测句子边界,这里的llm组件配置为执行命名实体识别,并带有给定的标签。为其他一些常见的NLP任务提供了任务处理程序,但你也可以定义自己的函数来执行任意任务——你只需要向函数添加装饰器并遵守正确的签名。
句子和实体标注(在本例中通过doc.sents和doc.ents访问)都可以作为Span对象的序列访问,Span对象类似于Doc对象的带标签切片。你可以遍历span中的标记,获取其起始和结束字符偏移量,并根据流水线中的组件访问嵌入或计算相似度。组件可以分配多个重叠的Span标注层,并且你可以设计和分配扩展属性以方便地访问其他属性。
显然,我对spaCy的这些部分感到相当自豪,但它们与LLM有什么关系呢?你可以用类似这样的命令提示LLM:“这篇评论中有多少段落说了关于表演的坏话?他们经常提到哪些演员?”。对于一次性的个人任务,这绝对是神奇的。但如果你正在构建一个系统,并且希望为每篇评论计算和显示这些信息,那么仅仅将其视为单独的预测任务(标记名称、将其链接到知识库以及段落级别的演员情感),并利用数据结构让你灵活访问信息,是非常好的。spaCy中新的LLM支持现在允许你为这些预测任务插入LLM驱动的组件,这对于原型设计尤其有用。这个功能仍然相当新且处于实验阶段,但探索起来已经非常有趣。
你需要的另一件工具是某种数据标注解决方案。你可以直接在电子表格或文本编辑器中加载内容,但如果你反复这样做,值得使用更好的东西。我们开发了一款商业标注工具Prodigy,它强调可定制性和模型辅助的工作流程。Prodigy不是免费的,但每个许可证是一次性购买,并且非常适合本地工作流程。
Prodigy的一个关键设计思想是模型辅助:调用模型获取初始标注,并让你审查和修正它们。这与LLM配合得特别好,在过去六个月里我们一直在构建对其的支持。Prodigy v1.12将具有集成的LLM标注辅助支持,支持一系列后端,包括可以自己托管开源解决方案。Prodigy还支持生成性输出的A/B评估,这对LLM尤其适用。该功能也在v1.12中得到了扩展。例如,你可以设计一些不同的提示,并通过回答一系列A/B评估问题在它们之间进行竞赛,在你不知道哪个提示产生的情况下选择两个输出中哪一个更好。这使你能够基于可以记录并随后审查的决定,系统地执行提示工程。
附录2:监督学习与上下文学习的准确性
大型语言模型(LLM)可用于任意预测任务,通过构建一个描述任务的提示,给出要预测的标签,并可选地在提示中包含相对少量的示例。这种方法不涉及针对新任务对模型进行任何直接更新。然而,LLM似乎从其语言模型目标中学习到了一种延续模式(包括抽象模式)的通用能力。其机制仍在研究中,但例如可以参见某机构的关于归纳头的工作。
在监督学习(在语言模型背景下常称为微调)中,模型被提供一组带标签的示例对,并调整权重以使某个目标函数最小化。在现代NLP中,监督学习和语言模型预训练密切相关。关于语言的知识在任务之间具有通用性,因此以某种方式用这些知识初始化模型是可取的。语言模型预训练已被证明是对此要求的一个非常强大的通用答案。我认为关于这方面最好的阅读材料是这些发展相对较新时的文章。
某机构评估了GPT-3在各种配置下的上下文学习能力与监督学习的对比。关于SuperGLUE基准测试的第3.7节结果,与一般的NLP预测任务(如实体识别或文本分类)最直接相关。在他们的实验中,某机构用每个任务的32个示例提示GPT-3,发现他们能够达到与BERT基线相似的准确性。这些结果是首次大规模引入上下文学习作为一种具有竞争力的方法,它们确实令人印象深刻。然而,它们在发布时远低于当时的最高准确率,并且当前SuperGLUE排行榜上的最佳结果都涉及监督学习,而不仅仅是上下文学习。SuperGLUE基准测试套件的某些子任务具有非常小的训练集,在这些任务上上下文学习具有竞争力。据我所知,目前没有任何NLP基准测试有超过几百个训练样本可用,并且领先的系统完全依赖于上下文学习。
上下文学习的重点从来不是成为让模型执行某项特定任务的绝对最高准确率的方法。相反,它是一个令人印象深刻的折中方案:它极其样本高效(你不需要很多任务示例),并且你不必支付训练的前期计算成本。简而言之,上下文学习的优势是开销较低。但你的项目存在时间越长,这越不应该被视为主要优势。开销会被摊销掉。
最后,重要的是要认识到,SuperGLUE和其他标准的NLP基准测试是专门设计得相当具有挑战性的。简单的任务不能成为好的基准。这与NLP应用正好相反,在应用中我们希望任务尽可能简单。大多数实际任务不需要强大的推理能力或广泛的背景世界知识,而这些才是真正使LLM与较小模型区分开来的东西。相反,实际任务通常要求模型学习一套相当具体的策略,然后一致地应用它们。监督学习非常适合这个要求。
更多精彩内容 请关注我的个人公众号 公众号(办公AI智能小助手)或者 我的个人博客 https://blog.qife122.com/
对网络安全、黑客技术感兴趣的朋友可以关注我的安全公众号(网络安全技术点滴分享)