“有些问题看起来易于构想和展示原型,但将其发展成实际产品却极为艰难。比如自动驾驶:展示一辆汽车在街区自动行驶很简单,但要把这一技术转化为成熟的产品却需要十年时间。” – Karpathy
本文旨在探讨如何将大语言模型(LLMs)有效地融入系统和产品中。我们将汇集学术研究、行业资源及实践者的经验,提炼出一些关键的观点和做法。
我们总结出了七种关键策略。这些策略既考虑了如何在提高性能与降低成本/风险之间取得平衡,又着眼于如何在接近数据源与接近用户之间找到合适的定位。
- 评估(Evals):用于衡量性能
- 检索增强生成(RAG):用于增加最新的外部知识
- 微调(Fine-tuning):用于提高特定任务的效果
- 缓存(Caching):用于减少响应时间和成本
- 安全防护(Guardrails):用于保证输出的质量
- 防御性用户体验(Defensive UX):用于预见并优雅地处理可能出现的错误
- 收集用户反馈:用于构建我们的数据驱动力
(另可查阅这个附加资料,了解如何将大语言模型 (LLM) 的模式应用于识别潜在问题。)
大语言模型的模式分析:从数据处理到用户体验,从防守策略到攻击方法(了解这些模式之间的关联)
Evals: 测量性能的评估方法
评估是一系列测量工具,用于确定模型在特定任务上的表现。这些工具包括基准数据和评价指标。正如HackerNews 上的一条评论所指出的:
评估对团队而言的重要性,是区分迅速推出低质量产品和认真开发高质量产品的团队的关键。
为何进行评估?
评估帮助我们量化系统或产品的表现,并识别任何性能退化。(系统或产品可能由大语言模型、提示模板、检索到的上下文以及各种参数(比如响应度设置)等多个部分组成。)有效的评估能帮助我们在更广泛的层面上衡量系统变化。没有评估,我们就像盲人摸象,或者只能靠肉眼检查大语言模型的输出来判断改动的效果。
关于评估的更多信息
在语言模型领域,有众多的评价基准。其中一些值得关注的包括:
- MMLU: 这是一组包括基础数学、美国历史、计算机科学、法律等多个领域的 57 个任务。要想在这些任务中表现出色,模型需要具备丰富的世界知识和解决问题的能力。
- EleutherAI Eval: 这是一个统一的测试框架,它能够通过零样本或少样本的方式,在 200 个任务上测试模型的性能。这个框架包含了大量的评估工具,如 BigBench、MMLU 等。
- HELM: HELM 提供了对大语言模型 (Large Language Models) 的全面评估,而不是仅限于特定任务和指标。它涵盖了多个领域,评估指标包括准确度、校准性、鲁棒性、公平性、偏见、毒性等。评估任务涵盖了问答、信息检索、摘要编写、文本分类等多个方面。
- AlpacaEval: 这是一个自动化评估框架,用于衡量强大的大语言模型(例如 GPT-4)对比参考模型的输出偏好频率。它的评估指标包括胜率、偏见、响应时间、成本、变异性等,并且已通过 2 万次人类评注进行了高度验证。
我们可以将这些指标分为两大类:依赖上下文的和独立于上下文的。
- 依赖上下文的:这类指标考虑到了上下文因素。它们通常为特定任务设计,如果要应用到其他任务上,可能需要进行适当的调整。
- 独立于上下文的:这类指标在评估生成输出时不涉及上下文因素,仅仅是将输出内容与提供的标准参考答案进行比较。由于它们与特定任务无关,因此更容易适用于广泛的任务。
为了更深入地理解这些指标及其可能存在的不足,我们接下来将探讨一些常用的评估方法,如 BLEU、ROUGE、BERTScore 和 MoverScore。
BLEU(双语评估替代者) 是一种以精准度为基础的评估方法:它通过计算生成输出中与参考资料相匹配的 n-gram 数量,并将其与输出中的总词数进行比较。这种方法主要应用于机器翻译领域,由于其成本效益高,至今仍广受欢迎。
首先,我们需要计算不同 nn 值的精准度:
这里的 Countclip(n-gram)Count_{clip}(text{n-gram}) 是指限制 n-gram 在任何一个对应的参考句子中出现次数的最大值。
在计算了不同长度 nn 的精确度之后,最终的 BLEU-N 评分是通过计算所有 precision_nprecision_n 分数的几何平均数得到的。
但是,由于精确度仅基于 n-gram 并未考虑输出文本的长度,因此即使输出中只包含一个常见单词(如停用词)的 unigram 也可能获得完美的精确度。这可能会误导评分系统,使得简短输出以提高 BLEU 分数。为了避免这种情况,加入了简洁性惩罚,以对过短的句子进行惩罚。
因此,最终的 BLEU-N 公式为:
ROUGE(回顾导向的概要评估工具):与 BLEU 相比,ROUGE 重点在于召回率。它主要计算参考文本中出现在输出中的词语数量,这种方法通常用于自动文本摘要的评估。
ROUGE 有多个变种,其中 ROUGE-N 与 BLEU 最为相似,因为它也关注输出和参考文本之间匹配的 n-gram 数量。
ROUGE-N 是一个测量指标,它通过对比参考文献中的 n-gram(词序列)与生成文本中匹配的 n-gram 数量来进行评分。其公式可以表示为:
除此之外,还有其他的变体,例如:
- ROUGE-L:这个指标通过测量输出文本和参考文本之间的最长公共子序列(LCS)来评估它们的相似度。简单来说,它会找出两者中连续相同的词序列有多长。
- ROUGE-S:这个指标关注的是输出和参考文本中的“跳跃二元组”,即使中间隔着其他词,这对词的顺序仍然保持一致。
另一种评价方法是**BERTScore**,它使用余弦相似度来比较生成文本和参考文本中的词或词序列。BERTScore 主要包括三个部分:
- 召回率:即参考文本中的词与生成文本中最匹配的词之间的平均相似度。
- 精确度:即生成文本中的词与参考文本中最匹配的词之间的平均相似度。
- F1:召回率和精确度的调和平均值。
其公式如下:
BERTScore 的优势在于它能考虑到词语的同义替换和不同的表述方式,这是传统的如 BLEU 和 ROUGE 这样基于精确匹配的评价标准所不能做到的。因此,BERTScore 在图像描述和机器翻译等领域展现出了更好的相关性。
MoverScore 运用了上下文相关的嵌入技术来衡量生成文本与参考文本之间词汇的距离。与 BERTScore 不同的是,后者基于一对一的匹配(即“硬对齐”),而 MoverScore 则支持多对一的匹配(即“软对齐”),提供了更为灵活的比对方式。
BERTScore(左)与 MoverScore(右;来源)
MoverScore 的特点在于能够将一个序列中的词语与另一个序列中语义相近的词语进行匹配。它通过解决一个特定的优化问题,寻找把一段文本变换成另一段文本所需的最小改动。其核心思想是量化词汇移动的距离,以此来转换两个序列。
然而,使用这些传统的基准和指标也存在一些问题。
首先,这些指标与人类的判断之间往往关联性不高。比如,BLEU 和 ROUGE 等指标在评估语言流畅性方面与人类的评价常常呈现出负相关。同时,在衡量内容充分性方面,它们与人类评分之间的相关性也只是中等或较低。特别是在需要创造性和多样性的任务中,BLEU 和 ROUGE 的相关性更是相对较低。
其次,这些指标在适应不同类型任务上的表现并不理想。把一个任务的评估标准直接应用到另一个任务上,并不总是合适的。例如,BLEU 和 ROUGE 这类基于 n-gram 重叠的精确匹配指标,并不适合用于抽象性摘要或对话等任务。在对话任务中,可能会有多种可能的回应,而这些回应与参考文本之间可能完全没有 n-gram 重叠,但这并不意味着回应就不合适。
第三,这些指标的可重复性较差。即使是同一个指标,在不同的研究中也常常出现较大的差异,这可能是由于人类评价收集的方法或指标参数设置的不同所导致。例如,一项针对ROUGE 得分的研究显示,在 2000 多项研究中,这些得分很难重现,难以比较,且常常由于使用了未经验证或错误的 ROUGE 实现而导致结果不准确。
使用 ROUGE 进行模型评估的各个维度(来源)
即便在像 MMLU 这样的最新基准测试中,同一模型在不同的评价方法下也会得到显著不同的分数。Huggingface 对比了原版 MMLU 实施与 HELM 和 EleutherAI 的实施方式,并发现同一测试案例在不同供应商的输入语句或问题上存在差异。
MMLU 不同实施方式中,相同问题的不同输入语句 (来源)
此外,这三种基准测试的评价方法也截然不同:
- 原版 MMLU:只对答案的预测概率进行比较(选项 A、B、C、D)。
- HELM:根据模型预测下一个词汇(token)的概率来选择,即使这个词汇并不是给定选项之一。
- EleutherAI:为每个答案计算完整答案序列(例如,一个字母后跟答案文本)的概率,然后选出概率最高的答案。
MMLU 不同实施方式中,相同问题的不同评价方法 (来源)
因此,即使是对于相同的评估,模型的绝对分数和排名也可能因不同的评估实现而产生大幅波动。这意味着,除非评估实施的细节(如输入语句和词汇切分)完全一致,否则即使是同一评估,模型的性能指标也无法真正进行比较。同样地,QLoRA 的作者发现 MMLU 对微小变化过于敏感,并得出结论:不应该使用、报告或信任 MMLU 的分数。
除了上述常规的评估方式外,一个新兴的趋势是使用高效的大语言模型 (LLM) 作为不依赖参考标准的评估工具,用以评价其他大语言模型的生成效果。这表明,在评估过程中,我们可能不再需要依赖人类判断或标准答案。
G-Eval 是一个结合大语言模型 (LLMs) 的创新框架,它利用思维链 (Chain-of-Though, CoT) 和填表模式来评估大语言模型的输出效果。首先,该框架会向大语言模型介绍特定任务并提供评估标准,然后指导它构建出一系列的评估步骤思维链。例如,在评估新闻摘要的连贯性时,G-Eval 会将引导语、思维链、新闻原文及其摘要结合起来,并让大语言模型基于此输出一个 1 至 5 的评分。最后,G-Eval 利用大语言模型输出的每个词的概率来调整这些分数,并通过加权总和的方式得出最终评价结果。
G-Eval 概览(来源)
在实际应用中,他们发现 GPT-4 作为评估工具,与人类评价的相关性非常高(达到了 0.514),这一表现超越了以往所有方法。GPT-4 在诸如连贯性、一致性、流畅性和相关性等方面的表现,也都优于传统的评估指标。在话题聊天这一领域,GPT-4 在自然性、连贯性、吸引力和基础性等多个标准上,都表现得比传统评估方法如 ROUGE-L、BLEU-4 和 BERTScore 要好。
另一篇论文 Vicuna 也采取了类似的评估方法。 在这项研究中,研究者首先定义了八个类别(包括写作、角色扮演、信息提取、推理、数学、编程、STEM 和人文/社会科学),并为每个类别设计了 10 个问题。接着,他们从包括 LLaMA、Alpaca、ChatGPT、Bard 和 Vicuna 在内的五个聊天机器人中获取答案。最后,他们请 GPT-4 根据答案的有用性、相关性、准确性和详细程度来评定其质量。
这项研究发现,GPT-4 不仅能提供一致的评分,而且还能详细解释这些评分背后的原因。在单一答案评分模式下,GPT-4 与人类评估者的一致性高达 85%,这比人类之间的一致性(81%)还要高。这意味着 GPT-4 在评价方面与人类评估者的观点非常接近,显示了其在理解和评估复杂内容方面的强大能力。
在 QLoRA 的研究中,研究者同样使用了大语言模型来评价另一个大语言模型的输出。 在这个项目中,他们让 GPT-4 评估不同模型在 Vicuna 基准测试中相对于 gpt-3.5-turbo 的表现。具体来说,GPT-4 被要求根据 gpt-3.5-turbo 和另一个模型的回应给出 10 分制的评分,并解释其评价依据。此外,他们还通过直接比较不同模型的性能,将评价简化为包括平局在内的三类评分系统。
为了检验自动评估方法的有效性,研究人员在 Vicuna 基准测试上进行了人类评判的收集。他们通过 Mechanical Turk 平台招募了两位评估者对 gpt-3.5-turbo 进行评估,并另外招募了三位评估者进行双边比较。研究结果显示,人类评估者与 GPT-4 对于模型的排名有较高的一致性,其一致性程度可通过一个专业统计指标——斯皮尔曼等级相关系数(0.55)来反映。这一发现额外指出,基于大语言模型 (LLM) 的自动化评估方法,可能是一种与人类评估旗鼓相当且更具成本效益的替代方案。
如何应用评估工具?
构建有效的评估体系是 任何基于大语言模型(LLM)的系统或产品(包括传统机器学习系统)的首要步骤。
遗憾的是,像 BLEU 和 ROUGE 这样的传统评价指标对于复杂任务(如抽象总结或对话)并不适用。此外,我们注意到,像 MMLU 这样的基准测试(以及像 ROUGE 这样的指标)对它们的实施和测量方式非常敏感。坦白说,如果您的 LLM 系统不是为了应对学校考试,那么使用 MMLU 进行评估似乎并不太合适。
因此,我们应该从收集一系列特定任务的评估工具开始(即,定义任务提示、上下文、以及期望的输出作为参考)。这些评估将引导我们进行提示设计、模型选择、模型微调等。随着我们系统的更新,我们可以通过这些评估快速检测性能的提升或下降。可以将其视为评估驱动开发(EDD)的方法。
除了评估数据集外,我们还需要有效的评估指标。这些指标能帮助我们将性能变化汇总为一个单一的、可比较的数字。如果我们能简化问题,就可以选择更易于计算和解读的指标。
最简单的任务可能是分类:如果我们用大语言模型处理分类任务(比如,毒性检测、文档分类)或无对话的提取式问答,我们可以依赖于标准的分类指标,如召回率、精确度、PRAUC 等。如果我们的任务没有唯一正确答案,但我们有参考资料(如机器翻译、提取式总结),我们可以依赖于基于匹配(BLEU、ROUGE)或语义相似性(BERTScore、MoverScore)的参考指标。
然而,对于更开放式的任务,如抽象总结、对话等,这些指标可能不适用。收集人类判断可能既耗时又昂贵。因此,我们可能倾向于使用强大的大语言模型(LLM)进行自动化评估。
与人类评判相比,大语言模型的判断通常更加一致(因为其偏见更系统),尽管它们可能更有偏见。但我们既然已经意识到这些偏见,就可以采取措施来适当地减轻它们的影响。
- 位置偏好:大语言模型 (LLM) 更倾向于选择排在第一个位置的回答。为了解决这个问题,我们可以对同一组回答进行两次评价,并交换它们的顺序。如果在两种顺序下同一回答都被优先选择,我们认为这是有效的;如果不是,就视为平等对待。
- 冗词偏好:大语言模型 (LLM) 有时会偏向于那些更长、更啰嗦的回答,即使更简短的回答更清晰、质量更高。为此,我们可以尝试让比较中的回答长度相似,从而减少这种偏差。
- 自我倾向偏好:大语言模型 (LLM) 略微偏爱自己的答案。例如,研究表明 GPT-4 对自己的答案有 10% 更高的偏好率,而 Claude-v1 则高出 25%。为了避免这种偏好,评估时不应使用相同的大语言模型。
还有一个小技巧:与其让大语言模型直接打分评估,不如给它一个参考标准,让它进行比较。这样做有助于减少评估过程中的误差。
最后,有时候最佳的评估方法其实是人类的直觉评估,或者说是“氛围感知”。这与同名但命名不佳的代码评估基准 HumanEval 不同。正如 Latent Space 播客中 MosaicML 的一期(第 34 分钟)所提到的:
基于氛围感知的评估非常重要。我们的一种评估方式是准备了一系列提示,观察模型在训练过程中答案的变化。坦白说,我认为现有的评估指标并没有真正反映我们所关心的。例如,我们使用的一个提示是“为 3 岁和 7 岁的孩子推荐游戏”,通过观察训练过程中答案的变化,我们可以获得很多有价值的信息。 — Jonathan Frankle
此外,关于抽象性总结的评估,可以参考这篇 深入探讨的文章。它详细介绍了参考、上下文和偏好基准,还讨论了如何检测幻觉现象。
检索增强生成:加入额外知识
检索增强生成(Retrieval-Augmented Generation, RAG)通过从基础模型之外获取相关数据,并将这些数据融入输入,从而为模型提供更丰富的上下文,以改善输出结果。这种方法不仅增加了信息的丰富性,而且有助于提高模型生成内容的准确性和相关性。
为什么选择 RAG?
RAG 通过依赖检索到的相关内容,帮助减少模型在生成信息时的误导或错误,从而使得信息更加真实可靠。而且,相比于不断地对大语言模型(LLM)进行预训练,更新检索索引的成本要低得多。这种成本效益使得我们能够更轻松地通过 RAG 让大语言模型接触到最新的数据。此外,如果我们需要更新或删除一些带有偏见或有害的文档,通过更新检索索引来实现这一点要比调整大语言模型的生成策略或指令更为直接简单。
总的来说,RAG 利用了信息检索领域的一些成熟且简单的技术,以此来增强大语言模型的内容生成能力。在最近的一项红杉调查中,有 88% 的受访者认为在他们的技术体系中,检索功能将扮演一个关键角色。
更多关于 RAG
在深入探讨 RAG 之前,先了解一下文本嵌入技术会很有帮助。(如果你对此已有所了解,可以跳过这部分。)
文本嵌入是一种将任意长度的文本压缩成固定大小数字向量的高级文本数据表现形式。这通常通过分析大量文本资料(比如维基百科)来学习得到。可以将其视为一种文本的“通用编码”,在这种编码中,相似的内容彼此靠近,不同的内容则相隔较远。
一个优秀的文本嵌入模型,应该能在例如检索相似内容等“后续应用”中表现出色。例如,Huggingface 的 Massive Text Embedding Benchmark (MTEB) 就是对各种模型在分类、聚类、检索、摘要等多种任务上的表现进行评分的一个平台。
需要注意的是,我们虽然主要讨论的是文本嵌入,但实际上嵌入技术可以应用于多种形态。比如,CLIP 就是一种多模态技术,它能够将图像和文本嵌入到同一个空间中,这使我们能够找到与特定文本最为匹配的图像。此外,我们还可以通过分析用户的行为(如点击、购买)来嵌入商品信息,或者是嵌入图形关系数据。
RAG 的基础是开放域问答系统。早期的一个 Meta 研究 发现,通过 TF-IDF 方法检索到的相关文档,作为上下文输入给语言模型(如 BERT),能够提高开放域问答任务的效果。研究中,他们将每个问题转换成一个填空题,并让语言模型预测缺失的词汇。
紧接着,密集段落检索 (Dense Passage Retrieval, DPR) 的研究表明,相较于传统的稀疏向量空间(如 TF-IDF),使用密集嵌入技术进行文档检索,能够在类似 Lucene BM25 的强基准测试中取得更高的准确率(前 5 最高准确度,65.2% 对比 42.9%)。这一发现也突显了在端到端问答精确度提升中,更高效的文档检索技术的重要性。
为了掌握 DPR 技术的嵌入方法,研究人员对两个独立的基于 BERT 技术的编码器进行了针对性调整,主要是处理已有的问题与答案对。这里的“文章编码器”负责把文本段落转化为向量,而“查询编码器”则把问题转化为向量。之后,查询编码器会找出与提出的问题最匹配的 kk 个文本段落。
编码器的训练目的是使得点积相似度能有效地对结果进行排序。他们通过调整损失函数,即对正确答案段落的负对数似然进行优化,从而提高 DPR 嵌入的效果。这样做的目的是为了让问题及其相关段落在向量空间中尽可能地靠近。
在推理阶段,研究人员会先用文章编码器处理所有的文本段落,并在 FAISS 系统中建立索引。当有新问题提出时,他们利用查询编码器计算出问题的向量表达,然后通过类似搜索引擎的方式快速找出最匹配的 kk 个段落,并将这些信息传递给 BERT 语言模型,由它来给出问题的答案。
检索增强生成 (RAG) 是一种新的处理模式,它揭示了传统预训练大语言模型的局限性,比如无法更新或修正其记忆库、在生成的内容中缺乏深入解释、以及有时会产生错误的信息。
为了克服这些问题,研究人员提出了 RAG 模式,即半参数模型。这里,密集向量检索充当非参数部分,而预训练的大语言模型则是参数部分。他们采用之前的 DPR 编码器作为检索工具,并以此建立一个文档的索引库。至于大语言模型部分,则使用了 BART 模型,这是一个具有 4 亿参数的序列到序列(seq2seq)模型。
在实际应用中,RAG 模式将用户的输入和检索到的文档结合在一起,然后由大语言模型根据这些信息生成答案。具体来说,模型会考虑原始输入、检索到的文档以及之前生成的 token,来决定接下来生成的每一个 token。
关于生成答案的方法,RAG 提出了两种不同的策略。在第一种策略,即 RAG-Sequence 中,模型会使用检索到的同一篇文档来生成整个答案序列。对于检索到的每一个文档,模型都会生成一个相应的答案。然后,通过计算每个答案序列的概率,并将这些概率加权求和,从而确定最终的答案。简单来说,就是在多个可能的答案中选择一个最可能正确的答案。
另一方面,RAG-Token 能够基于各自不同的文档生成各个 Token。在给定的 kk 个检索文档中,生成器会为每个文档的下一个输出 Token 制定一个分布,然后将这些分布合并(即汇总所有单个 Token 的分布)。接着,此过程会对随后的 Token 重复进行。这意味着在每个 Token 的生成过程中,系统都可以基于原始输入以及之前生成的 Token,检索一组新的 kk 个相关文档。因此,不同文档在检索概率上可能会有所不同,并在下一个生成的 Token 上发挥不同的影响。
Fusion-in-Decoder (FiD) 同样运用了生成模型结合检索的方法来处理开放域的问答问题。FiD 支持两种检索方法:BM25(采用默认参数的 Lucene)和 DPR。FiD 之名源于其特点,即仅在解码器中对检索到的文档进行信息融合。
Fusion-in-Decoder 概览(来源)
对于每一个检索到的段落,系统会将其标题和内容与问题连接起来。这些信息对在编码器中被单独处理。同时,系统还会在它们对应的部分之前加入特殊的 Token,如 question:
、title:
和 context:
。解码器会处理这些检索到的段落的拼接内容。
由于它在编码器中独立处理各个段落,因此可以处理大量的段落。这是因为系统一次只需对一个上下文进行自我关注(self-attention)。因此,与如 RAG-Token 等其他方法相比,FiD 在处理检索到的段落数量上的计算增长是线性的(而不是呈二次方增长),这使得它在扩展性上优于其他方法。在解码阶段,解码器会共同处理这些编码过的段落,从而能够更有效地整合多个检索到的段落中的上下文信息。
Retrieval-Enhanced Transformer (RETRO) 采取了类似的方式,它结合了固定的 BERT 检索器、可变编码器和分块交叉关注机制来生成输出。RETRO 的特别之处在于,它在整个预训练阶段都进行文档检索,而不仅仅是在推理阶段。此外,它根据输入的各个部分来检索相关文档。这种方法在生成过程中允许更精细、重复的检索,而不是每个查询只进行一次检索。
对于每个输入的数据块(C_uC_u),系统会检索到kk个相关的数据块RET(C_u)RET(C_u)并对它们进行处理。处理的结果是这些相关数据块的编码信息Ej∗uE^{j}*{u},这里的Ej∗u=Encoder(RET(C_u)j,H_u)∈Rr×d_0E^{j}*{u} = text{Encoder}(text{RET}(C_{u})^{j}, H_{u}) in mathbb{R}^{r times d_{0}}表示通过编码器处理后,每个数据块的编码都与中间激活状态H_uH_u和数据块C_uC_u的激活状态相关,这一过程涉及到交叉关注机制。简单来说,就是通过关注输入数据块的激活状态来决定如何编码检索到的数据块。这些编码信息Ej_uE^{j}_{u}接下来将用于生成下一个数据块。
RETRO 概览(来源)
在检索过程中,RETRO 将输入的文本序列切分成每块 64 个标记。接着,系统会寻找与前一个数据块内容相似的文本,为当前的数据块提供上下文背景。检索索引由两部分连续的标记块组成,分别是邻近块(64 个标记)和延续块(64 个标记),前者用于生成搜索关键字,后者则是原文档中的后续内容。
这一检索过程基于 BERT 嵌入,通过计算欧几里得距离来找出近似的kk个最近邻居。这种方法与通常使用的余弦相似度或点积相似度不同。RETRO 使用的检索索引建立在 SCaNN 技术上,能够在短短 10 毫秒内处理高达 2 万亿标记的数据库。
他们还展示了如何对现有的基础模型进行 RETRO 化改造。通过锁定原有模型的训练权重,仅对块间的交叉关注和邻域编码器参数进行训练(对于一个 70 亿参数的模型来说,这部分不到总权重的 10%),可以在只使用 600 万训练序列(预训练序列的 3%)的情况下,为传统的变换器模型增加检索功能。这种 RETRO 化的模型不仅超过了基础模型的性能,甚至接近于从头开始训练的 RETRO 模型的表现。
通过 RETRO 化预训练模型的表现(来源)
联网增强的大语言模型 提出了一个创新想法:使用普通的搜索引擎来提升大语言模型(LLM)的能力。首先,他们用谷歌搜索找到相关的文档。这些文档通常很长(平均有 2,056 个词),因此他们将其分割成每段六句话的段落。接着,他们用一种称为 TF-IDF 的技术处理问题和段落,并利用一种叫做余弦相似度的方法,为每个查询找出最相关的段落。
联网增强大语言模型概览(来源)
这些被检索到的段落用于通过少样本(few-shot)提示方法来训练 LLM。研究人员采用了传统的 15-shot 提示方法,这是一种在封闭书籍问答系统中常用的技术,只提供问题和答案。他们对此方法进行了扩展,加入了证据段落,这样每个训练实例包含一个证据段落、一个问题和一个答案。
对于生成答案的模型,他们使用了一个名为 Gopher 的模型,这个模型有 2800 亿参数,是在 3000 亿个词上训练的。对于每个问题,他们从 50 个检索到的段落中为每一个生成四个候选答案。最后,他们用几种方法估算答案的可能性来选择最佳答案,包括直接推断、RAG、噪声信道推断和专家产品(Product-of-Experts,简称 PoE)。其中,PoE 的表现是最为突出的。
RAG 这种技术不仅被用于问答任务,还被应用于代码生成等非问答任务。CodeT5+ 本身可以作为一个独立的生成器,但当与 RAG 结合使用时,在代码生成方面的表现显著超过了其他类似模型。
为了评估 RAG 在代码生成中的作用,研究人员在三种不同的设置下测试了模型:
- 基于检索:预测时直接选取排名最高的代码样本。
- 仅生成:仅基于解码器生成代码。
- 检索增强:在通过解码器生成代码之前,将排名最高的代码样本加入到编码器的输入中。
CodeT5+ 中的 RAG 概览(来源)
作为一个具体例子,研究人员展示了检索到的代码提供了重要的上下文信息(例如,在 HTTP 请求中使用 urllib3
),这些信息引导生成过程产生更准确的预测。相比之下,仅生成模式下的输出虽然捕捉到了“下载”和“压缩”的概念,但结果却是不正确的。
如果我们无法获取到查询与段落配对的相关性评判,该怎么办呢? 在没有这些评判的情况下,我们将无法训练那些能够将查询和文档嵌入到同一空间的双编码器,在这个空间继续上文:
…在这个空间里,我们利用内积来表示查询和文档之间的相关性。一种名为 假设性文档嵌入(HyDE) 的方法为我们提供了解决方案。
HyDE 的运作方式是这样的:首先,它会用一个大语言模型,比如 InstructGPT,来根据一个查询生成一个假设性的文档。接着,一个无监督的编码器,例如 Contriver,会把这个文档转化成一个嵌入向量。最后,通过计算这个 假设性 文档与语料库中文档的内积,来找出与之最为相似的 真实 文档。
这个方法的核心在于,编码器的密集瓶颈部分作为一种有损压缩器,通过嵌入过程排除了那些多余和非事实的细节。这样一来,相关性建模的问题就从一个表示学习的任务转变成了一个生成任务,这对于理解和操作复杂数据提供了一种新的视角。
如何应用 RAG
从我在 Obsidian-Copilot 的实践经验来看,我发现将传统搜索索引与基于嵌入(embedding)的搜索相结合,效果要优于单独使用任一种方法。在那里,我用基于语义的搜索(e5-small-v2
)来补充经典的检索方式(通过 OpenSearch 的 BM25)。
为何不单纯使用基于嵌入的搜索呢?尽管在很多情况下它表现出色,但在某些场合它的效果并不理想,比如:
- 搜索特定人物或物体的名称(例如,Eugene,Kaptir 2.0)
- 查找缩写词或短语(例如,RAG,RLHF)
- 寻找特定的 ID(如
gpt-3.5-turbo
,titan-xlarge-v1.01
)
然而,关键词搜索也有其局限。它仅能处理简单的词频,并不能捕捉到语义或相关联的信息。所以,它在处理同义词或上位词(即更广义的词语)方面表现不佳。这就是为什么将传统搜索与基于语义的搜索结合使用会更加有效。
另外,使用常规的搜索索引时,我们可以利用元数据来精确筛选结果。例如,我们可以通过日期过滤器来优先选择更新的文档,或者将搜索范围限定在特定时间段内。在电子商务领域,根据平均评级或产品类别进行筛选也很有帮助。最后,元数据对于之后的排名也非常有用,例如优先展示被引用更多的文档,或根据销量提升产品的排名。
说到嵌入技术,目前流行的方法是使用 text-embedding-ada-002
。其优点包括易于通过 API 使用,以及无需自行维护嵌入基础设施或托管嵌入模型。然而,根据个人经验和他人的经验,有更适合用于检索的其他选择。
原始的嵌入技术包括 Word2vec 和 fastText。FastText 是一个开源的轻量级库,允许用户利用预训练的嵌入或训练新的嵌入模型。它提供了 157 种语言的预训练嵌入,并且即使不使用 GPU 也能快速运行。对于早期阶段的概念验证,FastText 是我的首选工具。
同样值得关注的基线技术是 sentence-transformers。它简化了为句子、段落甚至图像计算嵌入的过程。这个工具基于强大的变压器模型,如 BERT 和 RoBERTa,支持 100 多种语言,适用于多种语言环境。
最近,教学模型在性能上展现出了前所未有的先进性能(SOTA)。在训练期间,这些模型会在文本前添加任务描述。因此,当嵌入新文本时,我们只需要简单地描述任务,就能获得与任务相关的嵌入。(其实,这和为嵌入模型进行指令微调(IMHO)没什么太大区别。)
以 E5 模型家族为例。对于开放式问答和信息检索任务,我们只需在索引中的文档前加上 passage:
标签,查询时则加上 query:
标签。如果任务是双向对等的(比如语义相似性判断、释义查找),或者我们希望使用嵌入作为分类、聚类等任务的特征,那么我们仅需使用 query:
标签。
Instructor 模型进一步扩展了这一概念,允许用户自定义添加的提示语:“Represent the domain
task_type
for the task_objective
:”例如,“Represent the Wikipedia document for retrieval:”。(领域和任务目标是可选项)。这一做法将提示调整的概念带入了文本嵌入领域。
最后,截至 8 月 1 日,在 MTEB 排行榜 上排名第一的嵌入模型是阿里巴巴 DAMO 学院的 GTE 模型家族。排名最高的模型大小是排名第二的 e5-large-v2
的一半(0.67GB 对比 1.34GB)。排名第二的是 gte-base
,模型大小仅为 0.22GB,嵌入维度为 768。(感谢 Nirant 的信息。)
为了实现大规模、低延迟的文档检索,我们采用了近似最近邻(ANN)技术。这种方法专注于提升检索速度,并返回大致的而非精确的最相似的前 kk 个邻居,这种方式牺牲了一些准确度以换取显著的速度提升。
ANN 嵌入索引是一种有效进行 ANN 搜索的数据结构。它们通过在嵌入空间中建立分区,使我们能够快速定位到查询向量所在的特定区域。其中一些流行的技术包括:
- 局部敏感哈希(LSH):其主要思想是设计一种哈希函数,让相似的项目更可能被分配到同一个哈希桶里。这样,我们只需检查相关的哈希桶,就能高效地进行近似最近邻(ANN)搜索。
- Facebook AI 相似性搜索(FAISS):该方法结合了量化和索引技术,以实现高效的信息检索。它既支持 CPU 又支持 GPU,而且由于其高效的内存利用,能够处理高达数十亿的向量数据。
- 分层可导航的小世界(HNSW):这种方法受到了“六度分隔”理念的启发,通过构建一个分层的图结构来展现小世界现象。在这个结构中,大部分节点只需通过少量跳转就能从任一节点到达。HNSW 利用这种结构从较大范围的近似搜索开始,逐步在较低层次细化搜索范围。
- 可扩展最近邻(ScaNN):它采用了两阶段过程。首先,通过粗略量化来缩小搜索范围。接着,在这个缩小后的范围内进行更精细的搜索。就召回率与响应时间的平衡来看,我认为 ScaNN 是我所见过的最优选择。
在评估近似最近邻(ANN)索引时,我们需要考虑的一些关键因素包括:
- 召回率:它与精确最近邻搜索的效果相比如何?
- 响应时间/吞吐量:它每秒能处理多少次查询?
- 内存占用:运行一个索引需要占用多少内存?
- 新项目添加的便利性:在不需要重新索引所有文档的情况下(如 LSH),是否可以方便地添加新项目,或者是否需要重新构建索引(如 ScaNN)?
没有任何一个框架在所有方面都是最优的。因此,在进行性能测试之前,首先应明确你的功能性和非功能性需求。就我个人经验而言,我认为在召回率与响应时间的权衡方面,ScaNN 表现卓越(具体基准测试结果可见 这里)。
微调:专门优化以提升特定任务性能
微调是一个将已经通过大数据集训练好的模型进一步针对特定任务进行优化的过程。其目标是利用模型在预训练期间已经学到的知识,并应用于某个具体任务,这通常涉及到一个相对较小、专为该任务设计的数据集。
“微调”这个术语的用途非常广泛,可以指涉多种不同的概念,比如:
- 继续预训练:用特定领域的数据对基础模型进行同样的预训练流程(比如预测下一个词或掩蔽语言建模)。
- 指令式微调:对预训练好的基础模型进行微调,使其能根据指令 – 输出对的例子来遵循指令、回答问题、成为虚拟伴侣等。
- 单任务微调:为了专注于特定的狭窄任务(如检测有害内容或进行文本摘要)而对预训练模型进行细致调整,这与 BERT 和 T5 模型的应用相似。
- 结合人类反馈的强化学习(RLHF):这种方法将指令式微调和强化学习相结合。首先收集人类的偏好选择(例如成对比较),然后用这些数据来训练一个奖励模型。之后,利用这个奖励模型通过强化学习技术(如近端策略优化 PPO)来进一步优化指令式大语言模型。
本文将主要探讨单任务微调和指令式微调这两个方面。
为什么选择微调?
对开放的大语言模型(LLM)进行微调,正成为一个日益受欢迎的选择,用以替代依赖第三方云端的大语言模型,原因有以下几点。
性能与控制: 通过微调,我们能够提升标准基础模型的性能,甚至可能超越第三方的大语言模型。这种方式还能让我们更好地控制大语言模型的行为,打造出更加稳健的系统或产品。总体来说,微调使我们能开发出有别于仅使用第三方或公开大语言模型的独特产品。
模块化: 单任务微调允许我们运用众多小型模型,每个模型都专注于特定任务。借助这种模式,我们可以将系统分解为处理内容审核、信息提取、摘要撰写等特定任务的独立模型。同时,由于每个模型只需专注于有限的任务范围,我们可以避开所谓的“对齐成本”问题,即在某一特定任务上微调模型可能会降低其在其他任务上的表现。
降低依赖: 通过自行微调和托管模型,我们能够减轻因处理专有数据(如个人身份信息、内部文件和代码)而可能面临的法律风险,这些数据原本可能需要通过外部 API 处理。此外,这种方式还可以避免使用第三方大语言模型时可能遇到的限制,如请求频率限制、高昂成本或过分严格的安全过滤。自行微调和托管大语言模型还确保了数据不会离开我们的网络,并能根据需要调整处理能力。
深入了解微调
为什么要对基础模型进行微调呢?简而言之,基础模型主要优化的是基于它们训练的语料库预测下一个词。因此,它们通常不擅长遵从指令或回答问题。当提出问题时,这些模型往往以提出更多问题来回应。所以,我们需要对它们进行指令性的微调,以使其学会恰当地回应。
但是,微调过程并非没有挑战。首先,我们需要大量的示范数据。举个例子,在InstructGPT 论文中,研究者们使用了 13,000 个指令 – 输出样本进行监督式微调(supervised fine-tuning),33,000 个输出比较用于奖励建模(reward modeling),以及 31,000 个无需人类标注的提示作为强化学习与人类反馈结合(RLHF)的输入。
此外,微调的过程伴随着一定的对齐成本——这意味着在某些关键任务上可能会有性能下降。(毕竟没有完美的解决方案。)同样的 InstructGPT 论文指出,相比 GPT-3 基础模型,RLHF 在公开的 NLP 任务(如 SQuAD、HellaSwag 和 2015 年 WMT 的法英翻译)上的表现有所下降。(一种解决方法是使用几个专门化、擅长狭窄任务的小模型。)
微调的概念与迁移学习(Transfer learning)相似。维基百科这样定义迁移学习:“迁移学习是机器学习中的一种技术,它将从一个任务中学到的知识重新应用到相关任务上,以提高后者的性能。”几年前,我利用迁移学习轻松地将在 ImageNet 上训练的 ResNet 模型应用到了时尚产品分类和图像搜索构建上。
ULMFit 是较早将迁移学习应用于文本领域的论文之一。他们提出了一种流程:首先是自监督预训练(在未标记的数据上),然后是微调(在标记的数据上)。他们采用了 AWS-LSTM(一种改进的 LSTM 模型,具有不同门控的丢弃功能)。
ULMFit 的概述图(来源)
在预训练阶段(即下一个词的预测),该模型在包含约 28.6 万篇维基百科文章和 103 百万词汇的 wikitext-103 上接受训练。接着,在目标任务的微调阶段,大语言模型 (LM) 根据特定任务领域的数据进行调整。最后,在分类器的微调阶段,模型通过增加两个额外的线性模块,并针对包括情感分析、问题分类和主题分类在内的目标分类任务进行进一步微调。
自从这种“先预训练后微调”的范式被提出以来,它极大地推动了语言模型的发展。例如,双向编码器表示来自变换器 (BERT; 仅编码器),就是先在遮蔽语言模型和下一句预测任务上,使用英文维基百科和书籍语料库进行预训练,然后针对单句分类、句对分类、单句标注和问答等特定任务进行微调。
对于 BERT,您可以在这里找到更多信息。
而**生成式预训练变换器 (GPT; 仅解码器)** 则是首先在书籍语料库上通过下一个词预测任务进行预训练,随后针对文本分类、文本蕴涵、相似度和问答等单一任务进行微调。GPT 的研究者们发现,将语言建模作为辅助目标包含进来,有助于模型在训练期间更好地泛化和更快地收敛。
更多关于 GPT 的信息,可以在这里查阅。
文本到文本转换变换器 (T5; 编码器 – 解码器) 则在 Colossal Clean Crawled Corpus (C4) 上进行了预训练,这是 2019 年 4 月 Common Crawl 的一个清洁版本。T5 采用了与 BERT 相同的去噪目标,即遮蔽语言建模。之后,它针对文本分类、抽象概括、问答和机器翻译等任务进行了微调。
关于 T5 的更多详细信息,可以参考这里的资源。
这些模型展示了先进的预训练和微调技术,通过这种方式,它们能够更有效地适应各种语言处理任务,为自然语言处理领域带来革命性的进展。
但与 ULMFIt、BERT 和 GPT 不同的是,T5 将下游任务仅以文本转换文本的方式处理。例如,在翻译任务中,输入的文本可能以 Translation English to German:
开头,而在摘要任务中,则可能以 Summarize:
或 TL;DR:
开头。这种前缀实际上变成了一种关键设置(也许是提示工程的首次尝试?)。这样的设计使得 T5 能够用同一个微调模型处理多种不同的任务。
InstructGPT 则在这个基础上进一步发展,将单任务微调拓展到了基于指令的微调。GPT-3 是其基础模型,它首先利用互联网上的大量数据(包括 Common Crawl、WebText、书籍和维基百科)进行预训练。接着,它通过对示例行为(比如指令和输出)进行监督学习进行微调。然后,它在比较数据集上训练了一个用于评估的模型。最后,通过 PPO 技术,把经过指令训练的模型与评估模型进行对比优化,重点放在了提高模型的对齐能力,而不仅仅是提高特定任务的表现。
InstructGPT 微调步骤的概览 (来源)
现在,我们从单独的微调模型转向不同的微调技术探讨。
软提示微调(Soft prompt tuning) 是一种在模型输入嵌入前面添加一个可训练张量的方法,实际上就是创建了一个可调整的软提示。这种软提示不同于固定的文本提示,因为它们可以通过反向传播学习,这意味着可以根据大量标注数据来进行微调,以融入更多的信息。
紧接着,我们有 前缀微调(prefix tuning)。这种方法不是在模型输入端添加软提示,而是在所有变压器(Transformer)模块的隐藏状态前添加可训练参数。在微调过程中,模型原有的参数保持不变,只更新前缀参数。
前缀微调的概览 (来源)
研究表明,尽管这种方法仅需更新模型约 0.1% 的参数,但它在性能上可以媲美完全微调。更重要的是,在数据量有限且需要向新领域拓展的情况下,它的表现甚至超过了完全微调。一个假设是,训练更少的参数有助于减少在较小目标数据集上的过度拟合现象。
也有一种被称为**适配器(adapter)**的技术。这种方法在每个 Transformer 块里加入两个额外的全连接网络层,分别放在注意力层和前馈网络层之后。通过这种方式,它可以在 GLUE 任务中,仅仅增加每个任务 3.6% 的参数量,就能达到几乎与完全微调同等的性能。
适配器技术简介(来源)
低秩适应(Low-Rank Adaptation,LoRA) 是另一种技术,它通过将适配器设计成两个简化矩阵的乘积来工作。这个想法源自于Aghajanyan 等人的研究,他们发现在适应特定任务时,预训练的语言模型可以通过较少的变量就学习有效,即使是在被简化的数学空间中。因此,LoRA 假设在调整权重时,也只需关注这些关键的少量变量。
LoRA 技术简介(来源)
与所谓的前缀调整方法相似,LoRA 在多个基准测试中表现优于全面微调,包括完全微调。LoRA 之所以有效,是因为它通过关注重要的少量变量,从而隐式地避免了模型的过度复杂化。与此相反,全面微调会更新所有权重,这可能导致模型过度适应特定数据。
QLoRA 则是在 LoRA 的基础上进一步发展。与在微调期间使用完整的 16 位模型不同,QLoRA 应用了一种 4 位的量化模型。这种模型通过一些创新手段,如使用 4 位的 NormalFloat 技术来减小模型体积,以及采用双重量化和分页优化器来节省内存并防止内存溢出错误,从而提高了内存使用效率。
QLoRA 技术简介(来源)
结果是,QLoRA 显著降低了模型微调时的内存需求。例如,对于一个 65B(65 billion parameters)的大型模型,QLoRA 将所需内存从超过 780GB 减少到更易于管理的 48GB,而且在运行时间和预测性能方面,与完全微调的 16 位模型相比,并没有降低。
(趣闻:在一次与 QLoRA 作者 Tim Dettmers 的聚会上,他幽默地表示双重量化是“一个看似愚蠢但却非常有效的想法。”嘿,有效就行。)
如何进行微调呢?
微调的第一步是收集示范数据和标签。这些数据可以用于一些简单的任务,比如文档分类、实体提取或摘要,也可以用于更复杂的任务,比如问答或对话。收集数据的方法有:
- 通过专家或众包的人工注释:这种方法虽然成本高且耗时,但通常能得到高质量的数据,尤其是在有详细指导原则的情况下。
- 利用用户反馈:这可以很简单,比如让用户选择描述一个产品的特性,对大语言模型(例如 ChatGPT)的回答点赞或点踩,或记录用户下载哪些图片(如 Midjourney)。
- 询问大型开放模型并获取许可较宽松的数据:通过巧妙设计提示,我们可以从一个大型模型(如 Falcon 40B Instruct)获取有用的示范数据,用于微调较小的模型。
- 重用开源数据:如果你的任务可以归类为自然语言推理(NLI),我们可以先用MNLI 数据训练模型来执行 NLI,然后再用内部数据进一步微调,以判定输入是蕴含、中性还是矛盾。
注意:一些大语言模型(LLM)的使用条款禁止使用它们的输出来开发其他模型。
- OpenAI 使用条款(第 2c 节,iii):您不得使用服务的输出来开发与 OpenAI 竞争的模型。
- LLaMA 2 社区许可协议(第 1b-v 节):您不得使用 Llama 材料或其产出改进任何其他大型语言模型(不包括 Llama 2 或其衍生作品)。
下一步是定义评估指标。我们在之前的章节中已经讨论过这个话题。
首先,选择一个预训练模型。 你可以从多个开放的、许可宽松的大语言模型中挑选。除了 Llama 2(因为它不适合全部商业用途)之外,Falcon-40B 是公认的最佳性能模型。但在实际应用中,我发现要微调和部署 Falcon-40B 很不方便,主要是因为它太庞大了。
相对而言,我更倾向于使用体积更小的模型,比如 Falcon-7B。如果我们能够将任务进一步简化和专化,像 BERT(340M 参数)、RoBERTA(355M 参数)和 BART(406M 参数)这样的模型在分类和自然语言推理任务中表现出色。此外,对于翻译、生成摘要、标题创作等任务,Flan-T5(770M 和 3B 两个版本)是一个可靠的基准。
在某些情况下,我们可能还需要更新模型的架构。比如,当预训练模型的架构不适合特定任务时,我们可能需要对 BERT 或 T5 的分类部分进行调整。一个小提示:对于简单的二元分类任务,自然语言推理(NLI)模型可以直接使用,其中“肯定”对应正面分类,“否定”对应负面分类,而“不确定”可以用来表示模糊不清的情况。
接下来,选择一种微调方法。 LoRA 和 QLoRA 是不错的起点。但如果你的微调工作更深入,比如需要在新的领域知识上继续预训练,那么你可能需要进行完整的微调。
最后,进行基本的超参数调整。 通常情况下,大多数研究论文都会关注学习率、批次大小和迭代次数(参考 LoRA, QLoRA)。如果使用 LoRA,我们可能需要调整其“等级”参数(尽管 QLoRA 的研究表明不同的等级和 alpha 值产生的效果相似)。其他的超参数还包括输入序列的长度、损失类型(例如对比损失与令牌匹配),以及数据比例(如预训练或演示数据的比例,或正负例之间的比例等)。
缓存:降低延迟和成本
缓存是一种技术,用于存储之前已经检索或计算过的数据。借助这种技术,我们可以更快地处理对同一数据的后续请求。在处理大语言模型 (LLM) 生成的领域中,一种流行的做法是基于输入请求的嵌入对 LLM 响应进行缓存。这样,当收到一个与之前语义相似的新请求时,我们就可以直接提供已缓存的响应。
对于某些从业者而言,这种做法听起来似乎是“潜在的灾难”。我也有同样的担忧。因此,我认为正确采用这种模式的关键,在于探索如何安全有效地进行缓存,而不仅仅是依赖于请求的语义相似性。
为何选择缓存?
缓存可以显著减少先前响应过的请求的延迟时间。除此之外,避免对相同输入反复进行响应计算,不仅能够减少大语言模型的请求次数,还有助于降低成本。而且,在某些用例中,几秒钟的延迟是无法接受的。因此,在这种情况下,提前计算并缓存响应可能成为唯一可行的解决方案。
关于缓存的更多信息
缓存是一种高速存储层,专门用来存储那些经常被访问的数据部分。这样,我们就能够通过缓存快速处理这些请求,而不是每次都要访问数据的主存储位置(比如搜索索引或关系型数据库)。总的来说,缓存能够让我们高效地重复利用之前获取或计算过的数据。想了解更多关于缓存和最佳实践的信息。
举个关于大语言模型 (LLM) 使用缓存的例子,比如 GPTCache。
GPTCache 的详细介绍(来源)
当我们接到一个新的请求时:
- 嵌入生成器:它会使用多种模型(如 OpenAI 的
text-embedding-ada-002
、FastText、句子转换器等)来处理这个请求。 - 相似度评估器:它通过向量存储来计算请求的相似度,并提供一个距离指标。这个向量存储可以是本地的(比如 FAISS 或 Hnswlib),也可以是基于云的。此外,它还可以通过模型来计算相似度。
- 缓存存储:如果请求与现有数据相似,就会从缓存中提取并提供响应。
- 大语言模型 (LLM):如果请求与现有数据不够相似,就会交给大语言模型处理,模型随后生成结果。最后,这个响应会被提供出来,并存入缓存,供未来使用。
Redis 也分享了一个相似的案例,提到有些团队会提前计算所有他们预计会接收到的查询请求。然后,他们设定一个相似度阈值,以判断哪些查询请求足够相似,值得使用缓存响应。
如何实施缓存策略?
首先,我们需要深入了解用户的请求习惯。这有助于我们精心设计缓存系统,确保其稳定可靠地运作。
举个例子,考虑一个与大语言模型(LLM)无关的情景:想象我们在为一家电商网站缓存商品价格。在顾客结账时,显示那个可能已经过时的缓存价格安全吗?答案可能是否定的,因为顾客在结账时看到的价格应与他们实际支付的金额一致。在这种情况下,由于需要保证价格的一致性,使用缓存并不合适。
现在,让我们把这个讨论转向大语言模型(LLM)的回应。想象一下,我们收到了一个请求,要求总结“碟中谍 2”,但这个请求在语义上与“碟中谍 3”非常相似。如果我们基于语义相似性来检索缓存,就可能返回错误的信息。
此外,我们还需要考虑缓存在特定使用模式下的有效性。衡量这一点的一个方法是查看缓存命中率,即直接从缓存中获取的请求所占的比例。如果请求模式完全随机,那么缓存就需要频繁更新,维护缓存的努力可能会抵消缓存的好处。相反,如果请求遵循某种规律,比如少数几种请求占了大多数流量(比如常见的搜索查询或产品浏览),那么使用缓存就是一个高效的策略。
除了语义相似性外,我们还可以基于其他因素来实施缓存,比如:
- **商品 ID:**当我们预先计算商品评论的摘要或为整个电影三部曲生成总结时,可以采用这种方法。
- **商品 ID 对:**例如,我们生成两部电影之间的比较。虽然这听起来计算量巨大(O(N2)O(N^2)),但实际上,只有少数几种组合会带来大量流量,比如流行系列或类型电影之间的比较。
- **限定输入:**比如电影类型、导演或主演等变量。例如,如果用户想找特定导演的电影,我们可以执行一个结构化查询,并通过大语言模型(LLM)以更加优雅的方式呈现结果。另一个例子是基于下拉选项生成代码——如果代码经过验证有效,我们就可以将其缓存,以便可靠地重复使用。
此外,缓存不只是即时发生的过程。 如 Redis 所示,我们可以在实际提供服务之前,离线或异步地预计算大语言模型(LLM)的输出。通过使用缓存,我们可以把生成内容的时间延迟(通常需要几秒钟)降低到缓存查询的时间(仅需毫秒级)。批量预计算也有助于减少与实时服务相比的成本。
尽管这里提到的方法可能不如基于自然语言输入的语义缓存那样灵活,但我认为它在提高效率和确保可靠性之间取得了良好的平衡。
安全防护:确保输出质量
在大语言模型(LLM)的使用中,安全防护措施用于检验 LLM 的输出,确保输出不仅听起来合理,而且在语法上正确、内容真实,并且避免包含有害信息。这还包括预防可能的对抗性输入问题。
为何重视安全防护?
首先,这些措施有助于确保模型的输出在实际应用中足够可靠和一致。例如,我们可能需要确保输出符合特定的 JSON 格式,以便于机器阅读,或者需要确保生成的代码可以被执行。安全防护可以协助完成这种语法上的检验。
其次,它们在维护 LLM 输出的质量上提供了额外的安全保障层。例如,为了确定生成的内容是否适合使用,我们可能需要检查输出是否无害,核实其事实真实性,或者确保其与给定的上下文保持一致。
关于防护措施
通过提示控制模型响应是一种方法。 例如,Anthropic 提出了一种提示设计,目的是引导模型生成符合 有益、无害、诚实(HHH)标准的回答。他们发现,相较于使用强化学习和人类反馈(RLHF)的微调方法,采用 HHH 提示进行 Python 微调能够显著提高性能。
HHH 提示示例(来源)
另一种更普遍的方法是对输出进行验证。 例如,Guardrails 包就是这样一个工具。它利用 Pydantic 风格的验证手段,允许用户对大语言模型(LLM)的输出设置结构、类型和质量标准。如果输出未通过验证,该工具可以采取纠正措施,如过滤掉问题输出或生成新的响应。
validators.py
文件中包含了大部分的验证逻辑。这些验证器大致可分为以下几类:
- 单一输出值验证:确保输出是预设选项之一、长度适中、数字在合理范围内,且为完整句子。
- 句法检查:验证生成的 URL 是否有效可达,检查 Python 和 SQL 代码是否无误。
- 语义检查:确认输出内容与参考文献一致,或摘要与原文高度匹配。这可以通过余弦相似度或模糊匹配技术实现。
- 安全检查:确保输出无不适当用语,且翻译质量良好。
Nvidia 的 NeMo-Guardrails 采用了类似的原则,但专门用于指导基于大语言模型的对话系统。它更注重语义防护措施而不是句法防护。这包括确保助手避免触及政治敏感话题,提供准确无误的信息,并能够识别并防范越狱尝试。
因此,NeMo 的方法与众不同:它不依赖于传统的确定性检测,如验证列表中是否存在特定值或检查代码的语法错误。相反,NeMo 主要利用另一个大语言模型 (LLM) 来验证输出结果,这一点受到了 SelfCheckGPT 的启发。
举个例子,在进行事实核查和避免错误信息生成时,他们让大语言模型自行检查其最新输出是否与给定的上下文一致。在事实核查方面,为了确认答案的真实性,他们会向大语言模型提问,根据从知识库检索到的文档,来判断答案是否准确。在避免错误信息生成方面,由于缺乏可用的知识库,他们让大语言模型生成多种可能的答案作为参考。这种方法的基本思路是,如果大语言模型提供的多个答案相互矛盾,那么原始的答案很可能是错误的。
内容审核的例子也采用了类似的方法:利用大语言模型来筛查回答中可能含有的有害或不道德内容。鉴于伦理问题和有害内容的复杂性,传统的启发式方法和机器学习技术往往难以应对。因此,需要依赖大语言模型来更深入地理解对话的意图和结构。
除了用于审核大语言模型输出的保护措施外,我们还可以 直接指导输出符合特定的语法结构。 例如,微软的 Guidance 项目。与通过提示来 强制实现 JSON 架构的 Guardrails 不同,Guidance 通过注入特定的 tokens 来确保遵循特定的结构。
我们可以把 Guidance 看作是针对大语言模型交互和输出的特定领域语言。它借鉴了 Handlebars——一种在网页应用中广泛使用的流行模板语言,该语言允许用户进行变量插值和逻辑控制。
然而,Guidance 与常规模板语言的不同之处在于它是线性执行的。这意味着它保持了生成 tokens 的顺序。因此,通过插入属于结构一部分的 tokens —— 而不是依赖大语言模型自行准确生成这些 tokens —— Guidance 能够指定特定的输出格式。在他们提供的示例中,他们展示了如何 生成始终有效的 JSON、生成具有多个键的复杂输出格式,确保大语言模型 扮演正确的角色,以及使 智能体能够互相交互。
他们还提出了一种名为 token healing 的概念,这是一个实用的特性,有助于避免因分词(tokenization)过程产生的细微错误。简而言之,这个功能会在提示语结束前的最后一个词元(token)回退一步,然后使第一个生成的词元必须以与提示语中最后一个词元相匹配的前缀开头。这样就无需在编写提示语时担心词元边界的问题。
如何实施大语言模型的安全防范措施?
在产业界,大语言模型(LLM)的安全防范概念还在起步阶段,但我们已经可以尝试一些既实用又有效的策略。
结构性指导: 尽可能提供明确的指导。这种方法能够直接控制输出结果,确保输出内容符合特定的结构或格式。
语法安全措施: 包括检查输出内容是否属于预设的分类选择范围内,或数值输出是否在合理区间。例如,当生成 SQL 语句时,这种措施可以确保语法无误,同时保证查询中的所有字段与数据库架构相匹配。对于编写代码(如 Python、JavaScript)也同样适用。
内容安全措施: 这是为了确保输出内容不包含有害或不适当的信息。简单的做法是,对输出内容进行检查,看是否包含《不雅词汇列表》中的词汇,或使用亵渎词汇检测模型。(常见的做法是对输出内容使用内容审核分类器。)对于更复杂或含蓄的内容,可以采用大语言模型进行评估。
语义及事实准确性检查: 确保输出内容在语义上与输入紧密相关。比如,我们根据电影的简介生成一段两句话的总结。我们可以检查这个总结在语义上是否与原始简介相似,或者让另一个大语言模型来判断这个总结是否准确地反映了提供的简介。
输入限制措施: 这类措施限制了模型所能响应的输入类型,有助于防止模型对不当或恶意的提示做出回应,从而避免产生有害内容。例如,如果您尝试让 Midjourney 生成不适宜内容,系统会显示错误信息。这种措施可以简单到比对特定字符串列表或运用内容审核分类器。
预防性用户体验设计:优雅应对错误与不准确情况
预防性用户体验设计是一种策略,它认识到在用户与机器学习或大语言模型(LLM)产品交互过程中可能出现问题,如信息不准确或误导。这种设计的目标是通过引导用户行为、预防错误使用和优雅地处理错误,来提前预防和控制这些问题。
为什么需要预防性用户体验设计?
机器学习和大语言模型(LLM)并不完美,有时会产生不准确的结果。它们对同样的输入在不同时间的反应也可能不同,比如搜索引擎因个性化而展现不同的搜索结果,或者大语言模型(LLM)在更加创新和自由的设定下生成各种不同的内容。这可能与追求一致界面和可预测行为的原则相悖。
预防性用户体验设计能够减轻这些问题,具体包括:
- 提升易用性:帮助用户理解机器学习/大语言模型(LLM)的功能和局限,使其更易于接近和使用。
- 增强信任感:当用户发现系统能够优雅地应对复杂场景并且不产生负面影响时,他们更倾向于信赖这些功能。
- 优化用户体验:通过针对不确定情形和错误设计系统和用户体验,预防性用户体验设计为用户提供了更流畅、更愉悦的使用体验。
更多关于防御性用户体验
想要深入了解防御性用户体验,我们可以参照微软、谷歌和苹果关于人工智能与人类互动的指南。
**微软的人工智能与人类互动指南**是基于对 168 个潜在指南的综合调研而来的。这些指南汇集了行业内外的资源、学术文献以及公开文章。经过筛选、合并相似内容、剔除过于笼统或具体、以及针对人工智能不太具体的内容,再通过一轮的专业评估,最终确定了 18 条关键指南。
用户在使用旅程中遇到的人工智能与人类互动指南(来源)
这些指南的特点是:每条都是简洁明了的行动准则,包含 3 到 10 个词,以动词开头。每条规则都附有一句话的简要解释,帮助理解可能存在的歧义。它们按照用户互动过程中的应用场景进行分类:
- 初始阶段:明确系统能做什么 (G1),明确系统的执行效果 (G2)
- 互动过程中:根据情境及时提供服务 (G3),减少社会偏见 (G6)
- 当出现错误:支持用户快速放弃 (G8),支持用户高效纠错 (G9)
- 时间推移中:从用户行为中学习 (G13),提供整体控制 (G17)
谷歌的人类 + AI 指南 源于谷歌产品团队的实践经验和学术研究。不同于微软围绕用户体验设计的指南,谷歌的指南更注重于开发者在开发过程中需要考虑的关键概念。
这里有 23 种模式,围绕产品开发过程中经常出现的问题进行分类,例如:
- 如何开始以人为本的人工智能项目:判断 AI 是否真正增值,早期投资于优质数据实践(比如评估)
- 如何向用户介绍新的 AI 功能:确保安全的探索空间,植根于用户熟悉的环境,分阶段实现自动化
- 如何帮助用户建立对产品的信任:设定合理的期望值,保持透明度,低风险时可增加自动化程度。
苹果公司的 人机交互指南 – 机器学习篇 与学术文献和用户研究所采用的自下而上的方法不同,它更多基于实践者的知识和经验。因此,该指南并不包含大量参考资料或数据点,而是着重于苹果长久以来的设计原则,从而呈现出与其他两个指南不同的独特视角。
这份文档主要讲述了如何将苹果的设计原则应用于整合了机器学习(ML)技术的产品中,更多强调的是用户界面(UI)方面而非模型功能。它首先建议开发者思考机器学习在其应用中的作用,并从用户体验的角度出发向后推理。这包括思考机器学习是否:
- 是核心还是辅助功能:例如,面部识别(Face ID)离不开机器学习,但即使没有快速输入(QuickType),键盘也能正常工作。
- 是主动还是被动:Siri 的建议功能是主动的,而自动纠错则是被动的。
- 是动态还是静态:推荐功能是动态变化的,而照片中的对象检测功能则是随着每个 iOS 版本的发布而改进。
接着,文档深入分析了几种模式,分为系统的输入和输出两部分。输入部分关注于明确的反馈、隐含的反馈、校准和纠错。这部分指导了 AI 产品如何设计来请求和处理用户的数据和互动。输出部分则关注于错误、多种选项、可信度、归因及限制。其目的是确保模型输出以一种易于理解且有用的方式呈现。
三个指南之间的差异颇具启发性。谷歌更多地强调训练数据和模型开发的考量,这可能源于其工程驱动的文化特性。微软则更注重于心理模型,这可能是人机交互(HCI)学术研究的影响。最后,苹果的方法主要围绕提供流畅的用户体验,这种重点很可能受到其文化价值观和原则的影响。
如何实施防御性的用户体验设计?
这里有一些基于前述指导原则的实用方法。(免责声明:本人非设计专家。)
设定合理的期望。 此原则是三大指导原则的共同点:
- 微软:清晰说明系统的能力和局限(帮助用户了解 AI 系统可能出错的频率)
- 谷歌:合理设定用户期望(向用户明确介绍你的 AI 驱动产品能做的和不能做的事情)
- 苹果:引导用户形成现实的期望(在市场宣传材料或功能的上下文中说明局限性)
这可能像在 Bard 的 AI 生成结果上方添加一个简明的免责声明,或者像 ChatGPT 在其主页上明确指出应用的限制那样简单。
以谷歌 Bard 的结果免责声明为例(注意:nrows
不是有效参数。)
通过清楚地说明我们产品的功能和局限,我们帮助用户正确设定对它的功能和效果的预期。虽然这可能短期内降低用户的信任,但长期来看,它有助于建立信任——用户不会过高估计我们的产品,从而避免之后的失望。
简化忽略流程。 这是微软第八条指南的明确内容:支持简化的忽略流程(让用户轻松忽略或摒弃不需要的 AI 系统服务)。
比如,当用户浏览我们的网站时,如果弹出一个聊天机器人询问是否需要帮助,用户应该可以轻松地关闭这个聊天机器人。这确保聊天机器人不会成为干扰,尤其是在屏幕较小的设备上。同样,GitHub Copilot 允许用户简单地继续打字来忽略其代码建议。虽然这可能短期内减少 AI 功能的使用,但它防止了功能变得讨厌,并可能在长期内提升客户满意度。
明确标明来源。 这一点在所有三个指南中都被提到:
- 微软:清楚地说明系统为何做出特定行为(让用户能够理解 AI 系统的行为原因)
- 谷歌:添加人类来源的背景信息(帮助用户借助第三方来源的信息来评估你的建议)
- 苹果:考虑使用来源标记来帮助用户区分不同的结果
引用正在成为设计中日益普及的元素。以 BingChat 为例,当我们进行查询时,它经常包含来自可信来源的引用,不仅展示了信息来源,也便于用户评判断来源质量。同样地,想象一下,我们用一个大语言模型 (LLM) 来解释用户为何喜欢某个产品。除了 LLM 生成的解释,我们还可以引用实际的用户评价或提到产品的评分。
专家和社区的观点也有助于增强用户的信任感。例如,在推荐徒步旅行路径时,提到某条路径受到相关社区的高度推荐,不仅丰富了推荐内容,也通过人际关系帮助用户建立信任。
社会认同的例证(来源)
此外,苹果的指南中包括了一些受欢迎的推荐语,如“因为您阅读了非小说类作品”、“您之前阅读过的作者的新作”。这些表述不仅让体验更加个性化,而且还提供了上下文,增强了用户的理解和信任。
重视用户的熟悉体验。 当我们向用户介绍新的 AI 产品或功能时,用他们熟悉的 UX 模式和功能作为指引非常有助益。这样可以让用户更容易集中注意力在主要任务上,从而在新产品中建立起客户的信任。我们应该避免过分展示新奇的“神奇”功能和不寻常的 UI 元素。
与此同时,随着 ChatGPT 的普及,基于聊天的功能也变得越来越流行,例如:通过聊天编辑文档、查询数据、购买杂货等。但我对聊天是否适合大多数用户体验表示怀疑,因为相比于点击文本和图片的熟悉方式,聊天需要更多的努力。
而且,用户付出的努力越多,他们的期望通常也就越高。Netflix 曾分享,用户对于通过搜索等明确操作得出的推荐有更高的期望。通常情况下,用户投入越多(例如,通过聊天或搜索),他们的期望也就越高。这与通过简单滑动或点击产品进行的低投入互动形成了鲜明对比。
因此,尽管聊天提供了更多的灵活性,但它同时也要求用户付出更多的努力。此外,使用聊天框作为交互界面并不直观,因为它缺乏告知用户如何调整输出的指示。综上所述,我认为坚持使用用户熟悉且操作限制明确的界面,会使得用户更容易地使用我们的产品;聊天功能应当作为一个次要或备选方案来考虑。
收集用户反馈:打造我们数据驱动的增长引擎
收集用户反馈能帮助我们深入了解用户的喜好。在大语言模型 (LLM) 产品的开发中,用户反馈对于建立评估标准、模型微调和设置安全边界至关重要。我们需要意识到,像预训练的语料库、专家设计的示例、以及用于奖励建模的用户偏好这样的数据,是大语言模型产品成功的关键因素之一。因此,当我们设计用户体验时,应特别重视收集用户的反馈信息。
用户的反馈既可以是直接表达的,也可以是间接体现的。直接反馈是用户针对我们产品的特定请求所给出的明确信息;而间接反馈则是我们通过观察用户的互动行为,不需要用户特意表达就能获取的信息。
收集用户反馈的重要性
用户反馈对于提升我们模型的性能至关重要。通过分析用户的喜好、不满和投诉,我们可以不断优化模型,更好地满足他们的需求。同时,这也使我们能够根据每个用户的特定喜好进行调整。以推荐系统为例,通过分析用户与内容的互动,我们可以逐渐掌握他们的喜好,进而提供更符合个人口味的推荐。
此外,建立有效的用户反馈机制还有助于我们全面评估系统的性能。尽管通过评估可以衡量模型或系统的表现,但用户的反馈直接反映了他们对产品的满意度和产品的实际效果。
如何收集用户反馈
让用户轻松提供反馈。 这个原则被所有三大指导方针所共鸣:
- 微软:倡导细致反馈(让用户在与 AI 系统交互时能够表达自己的偏好)
- 谷歌:允许用户提供反馈(给用户机会进行实时教学、反馈和纠错)
- 苹果:提供应用可以用来提升内容和用户体验的有效信息
ChatGPT 就是一个典型案例。用户可以对回应点赞或点踩,或者如果回应很差或无帮助,选择重新生成。这是关于人类偏好的宝贵反馈,有助于对大语言模型 (LLM) 进行微调。
Midjourney 也是一个极佳的例子。用户在生成图像后,可以选择生成新的图像集(表示不满意),或通过要求稍作变化来调整图像(表示满意),或者放大并下载图像(表示非常满意)。这样,Midjourney 能够收集到关于其生成输出的丰富对比数据。
同样重要的是考虑到隐性反馈。 隐性反馈是用户在使用产品过程中自然产生的信息。与我们从显性反馈中得到的具体回应不同,隐性反馈能够提供关于用户行为和偏好的丰富数据。
以类似 Copilot 的助手为例。用户通过完全采纳(表示非常认可)、采纳后略作修改(表示认可)或忽略建议(表示中立或不认可)来表明一个建议的效用。此外,用户可能会修改引发代码生成的注释,这表明最初的代码生成可能未能满足他们的需求。
ChatGPT 和 BingChat 这类聊天机器人也提供了另一个视角。考察它们的日常使用量如何随时间变化。如果一个产品能持续吸引用户,这通常意味着用户对它的喜爱。同时,平均对话长度也是一个值得关注的指标。这个指标的解读颇具挑战性:是因为对话内容引人入胜、富有成果,所以对话时间更长?还是因为用户需要更多时间来获取他们所需的信息?
机器学习中还有哪些常见模式?
除了前面提到的七种模式,机器学习还涉及一些与大语言模型 (LLM) 系统及产品息息相关的其他模式。包括以下几种:
- 数据助推轮:不断收集数据不仅可以提升模型性能,还能优化用户体验。这样一来,用户会更频繁地使用这些系统,进而提供更多数据,帮助我们评估和微调 (fine-tune) 模型,形成一个正向的循环。
- 任务分解:我们不必把复杂的任务全部交给大语言模型 (LLM)。通过简化和拆分任务,我们可以让大语言模型 (LLM) 专注于它擅长的部分,比如逻辑推理或流畅交流。例如 RAG,它不是单纯依赖大语言模型 (LLM) 基于内部知识进行检索和排序,而是结合外部知识,让大语言模型 (LLM) 发挥其推理能力。
- 模型监控:通过监控,我们能够了解 AI 系统究竟增加了多少价值,或者说它的不足之处。有个实例是,某公司尝试了两周的基于大语言模型 (LLM) 的客户支持解决方案,但最终放弃了——因为一项 A/B 测试显示,使用大语言模型 (LLM) 替代原有支持团队后,损失增加了十二倍!
(想了解更多关于机器学习代码和系统的设计模式,请点击阅读原文。)
同时,还有业内人士分享了他们的见解:
分工与任务分解——为不同的子任务设置明确的提示,并将它们串联起来,有助于提高注意力和可靠性(尽管会增加延迟)。我们在尝试规定严格的输出结构和灵活的响应内容时遇到了困难,所以我们将任务进行了分割 — Erick Enriquez
还需要考虑的几个方面包括:基于角色的访问控制——谁可以访问什么内容;安全性——如果我使用数据库结合大语言模型 (LLM),我该如何确保有合适的安全保障 — Krishna
为了实现一致性,我们将输出格式标准化为像 JSON 这样的格式;同时,通过将一些任务转移到更专业、可靠的模型上,我们增强了工具的功能。 — Paul Tune
在安全方面,我们采取了多种措施:减轻缓存被恶意数据污染的风险、验证输入数据的有效性、预防恶意代码注入到提示信息中、确保训练数据的安全来源、生成不易受安全漏洞影响的代码、防止恶意输入干扰 AI 智能体的处理请求,以及抵御服务拒绝攻击,比如增强大语言模型的抗压能力,等等。 — Anderson Darario
另一方面,关于用户体验和界面设计,我们鼓励用户就生成的答案提供反馈,无论是直接表达还是间接表达。一个间接的方法可以是类似于 Copilot 的“即时提示”功能,用户通过按下 TAB 键接受这些提示,就相当于给出了积极的反馈。 — Wen Yang
这是一份很棒的列表。我想补充的是,像自我检查采样、任务的分解和连贯性检查,以及将多个模型的输出进行整合等一致性检查措施,这些都是我们几乎每天都在使用的方法。 — Dan White
在构建分析工具时,确保安全性和可控性是非常重要的,特别是在大语言模型用作从自然语言到编程语言的转换工具时。 — m_voitko
结论
这篇文章是我至今写过的最长篇幅。如果您能读到这里,非常感谢!希望您能从这些模式的探讨中获益,并且理解接下来介绍的 2×2 模型。
我们正在构建基于大语言模型 (LLM) 的系统和产品的旅程中还只是起步阶段。您是否还发现了其他关键的模式或有用的资源?有哪些是您觉得特别有用或没那么有用的?我非常期待听到您的经验分享。欢迎联系我!