Databrick-大语言模型从生产到应用笔记-全-
Databrick 大语言模型从生产到应用笔记(全)
1:欢迎与课程介绍


在本节课中,我们将学习这门关于大语言模型(LLM)的课程概述,并认识四位主讲老师。课程将涵盖从基础概念到实际应用与生产的完整知识体系。
欢迎来到我们的大语言模型课程。我是Matei Zaharia。


我是Databricks的联合创始人兼首席技术官,同时也是斯坦福大学的计算机科学副教授。我长期致力于大语言模型、检索系统及其他工具的研究。我们非常高兴能为大家带来这门课程,内容涵盖大语言模型的使用,直至其应用与生产部署的全过程。本课程还有另外三位优秀的讲师与我一同授课。



大家好,欢迎来到这门精彩的课程。我是Sam Raymond,是你们的联合讲师之一,也是Databricks的一名高级数据科学家。在加入Databricks之前,我从事了约十年的AI研究工作,始于我在麻省理工学院的博士阶段,专注于物理信息机器学习等领域。我最近的研究关注如何利用农业数据和AI预测碳封存。希望大家喜欢这门课程。我们期待在下一节课中与大家相见。

我是Joseph Bradley,Databricks的首席产品专家。我在卡内基梅隆大学攻读博士学位期间进入机器学习领域,随后在加州大学伯克利分校进行了一些研究。2014年,我作为第二名机器学习工程师加入了Databricks。最初,我作为提交者和项目管理委员会成员,致力于ML和Apache Spark的工作。接下来的几年,我帮助构建了我们的ML产品和平台。最近几年,我一直在产品、工程以及面向客户的技术领域交汇处工作。


大家好,我是Chen Eng,Databricks的一名高级数据科学家。我在机器学习领域已有超过六年的经验。我在马萨诸塞大学阿默斯特分校读研究生时开始了自然语言处理(NLP)的旅程。我做过的最难的计算机作业之一是从零开始实现一个基础的Transformer模型。直到今天,我仍然记得那些熬夜的日子,以及我如何因为ELMo和BERT而梦到芝麻街。
自那以后,我在Databricks开发并教授了NLP领域的课程。我也帮助客户实施和设计NLP解决方案的架构。我希望这门课程能赋予你力量,去构建你的第一个LLM应用,并走得更远。


本节课中,我们一起认识了本课程的四位主讲老师,并了解了课程的核心目标:系统性地学习大语言模型,掌握从理论到应用,再到生产部署的完整技能栈。在接下来的章节中,我们将深入探索大语言模型的各个方面。
2:为什么需要大语言模型?🤔



在本节课中,我们将探讨大语言模型(LLMs)为何重要。我们将分析围绕LLMs的常见问题,解释其核心概念,并讨论在实际应用中需要考虑的关键因素。
我们经常听到许多关于大语言模型的问题。例如,围绕它们的炒作是否合理?这是行业的“iPhone时刻”,还是一个最终效果不佳的技术炒作?对于商业和软件开发的不同领域,LLMs是威胁还是机遇?
上一节我们提出了关于LLMs价值的核心问题,本节中我们来看看如何利用它们。
如何利用LLMs在您所在的行业中获得强大的竞争优势?最后,如何快速地将LLMs应用到您自己的数据中?我们将快速讨论所有这些内容,从第一个问题开始。
我们相信,许多人认为LLMs远不止是炒作。它们正在彻底改变每一个涉及某种形式人机交互、语言数据或界面,或广义上处理语言的行业。
以下是关于LLMs影响力的一些近期案例。Chegg是一家公司,其股价大幅下跌,因为它发现用户正在使用ChatGPT来回答问题,而不是使用其服务。因此,LLMs已经产生了影响,该公司必须思考如何在这个LLM时代调整其业务。
许多公司正在利用LLMs构建全新的产品。例如,You.com是一家初创公司,它从事搜索业务,但真正专注于一个可以为您查看个性化结果、分析页面内容并提供高质量、复杂结果并引用来源的搜索助手。这只有通过LLMs才可能实现。
即使是现有的机器学习工具,如GitHub Copilot,也随着最新一代的LLMs变得更加强大。GitHub Copilot本质上是一个帮助您自动补全代码的系统,但现在它还可以通过查看错误来修复代码、生成测试等等。所有这些新功能都得益于更强大的语言模型。
同时,LLMs作为一种基础技术并不算新。它们已经存在多年,即使是以深度学习形式,例如BERT这样的模型早已出现。那么,为什么您现在应该关注它们?
真正的原因在于,随着经验的积累,这些模型的质量已经显著提高。起初,人们会训练语言模型,我们稍后会详细讨论。您可以在一堆文本上训练一个模型,然后必须对其进行大量定制才能使其执行特定用例,例如分析产品评论并判断其是正面还是负面,或者确定评论涉及产品的哪个方面。

因此,即使您拥有这项基础技术,也必须进行大量手工编码、数据收集和机器学习工作,才能将其转化为有用的应用程序。随着我们在构建和调整LLMs方面变得更好,我们已经能够制造出只需用自然语言给出指令的LLMs,这些模型现在可以用极少的人力设置来完成许多有用的任务。
因此,真正推动所有这些强大应用的是两件事的结合,尽管其中的机器学习部分已经有一段时间保持不变了。第一,准确性和有效性已经达到临界点,首先,许多用例现在因准确性而成为可能;其次,同样重要的是,由于能够用自然语言指令它们,它对每个人都变得可访问。
此外,越来越多的高质量数据和工具变得可用。几年前,如果您想使用LLMs构建一些东西,您必须抓取网络、收集干净的数据集,并从零开始做各种事情。现在,您可以找到许多收集好的优秀开源数据集,可以找到在特定用例中非常高质量的完整开源模型。当然,有时您确实需要进行大量计算,但您可以从云提供商那里获得许多优秀的商业服务(如GPU)来实现这一点。
上一节我们讨论了LLMs的兴起和影响,本节中我们来具体看看什么是LLM。
那么,具体来说,什么是LLM?它是一个大语言模型。“大”当然是相对的,但它之所以越来越被称为“大”,主要是因为它比人们以前尝试使用的模型更大,本质上,它更大的主要方式之一是它也在非常大量的数据上进行了训练。
然而,也许更重要的问题是,究竟什么是语言模型?语言模型是一种统计模型,试图预测某种语言文本中的单词。设置这个问题的方式基本上是一个统计问题:您有一个文本集合,然后您说,如果我向您展示一些单词,比如一个句子的开头但不是结尾,您能预测缺失的单词是什么吗?这是一种预测问题,这是我们一直在机器学习和统计学中做的事情。
长期以来,有很多方法可以尝试拟合模型到数据以预测这些单词。但事实证明,在过去几年里,我们已经开发出能够获得越来越好的准确性的方法,尤其是在您输入更多数据时,这种可扩展性最终会为您提供能够学习关于世界的有用知识,或可用于获取关于世界的有趣信息的模型。
为了让您了解这些模型运作的规模,一个典型的人一生可能阅读几百本书,比如平均700本。这是很多书,需要很长时间,但与我们今天输入大语言模型的数据量相比并不算多。一本书大约有80,000个单词,当编码成语言模型接受的形式时,大约是100,000个标记。标记是单词的一部分,我们稍后会详细讨论。但像ChatGPT这样的典型模型,今天通常是在数万亿个标记上训练的,这意味着数千万本书,如果您计算一下的话。这比一个人可能阅读的书要多得多。如果您有一种方法将这些转化为有用的统计模型,您最终通常会得到可以应用于现实世界文本的东西。
作为一个非常简单的例子,说明如何利用这种语言建模能力来捕捉关于世界的知识:想象我写了句子“牛油果是”,然后我留了一个空白,我问模型:您能预测哪个词更有可能出现在那里吗?如果我有一个好的语言统计模型,我可以用它来了解一些关于牛油果的事情。例如,如果我不确定牛油果是什么颜色,我可以尝试放入一堆不同的颜色,然后问模型这个答案的可能性有多大。模型会说,“绿色”这个词出现在空白之后的可能性要大得多(牛油果是绿色的),而不是“蓝色”(牛油果是蓝色的)。在某种意义上,模型学习了关于牛油果颜色的知识,即使它并不真正知道牛油果是什么,它只知道单词的概率。同样,您可以推断出“牛油果是美味的”,牛油果可能不聪明,诸如此类。如果您将这种能力发挥到极致,以更复杂的方式使用它,您可以得到这些模型,它们会做一些事情,比如生成具有特定属性的文本(例如生成一首关于圣莫尼卡日落的歌曲),或者分类数据(您可以问它:这是一个好评还是差评?),它会做到。这些都是基于它从看到的大量文本中收集到的关于世界和推理的知识。
那么,这对您作为开发者和用户意味着什么?LLMs可以自动化许多以前需要人工详细完成的任务,这些任务通常涉及难以编码并放入计算机的不精确语言或关于世界的知识。这些可以帮助加速创新、构建软件的新功能和新界面,并提高企业的投资回报率和效率。
以下是一些您可以用它做的事情的例子:
- 您可以加速软件开发,因为很多软件就是代码,它是一种语言,您可以获得擅长特定部分的助手。
- 您可以 democratize AI 本身,许多用户现在可以通过与LLMs对话并要求它们做事来使用它们,而无需进行繁重的机器学习项目。
- 您可以开启丰富的用例,如助手、分析商业文档、生成广告文案等等。
- 当然,您可以降低应用程序的开发成本,并减少人们必须做的单调任务,将简单的事情交给模型,让人们专注于困难的事情。
我们才刚刚开始触及用例的表面,您将在整个课程中看到更多。
现在,不幸的是,对于LLMs来说,没有完美或万能的模型。因此,一旦您超越了“我可以用它来做什么”的想法,并尝试在实际应用中使用它,正确的模型将在很大程度上取决于您应用程序的需求,并且需要进行许多权衡,可能还需要相当多的定制开发。
以下是考虑使用LLMs的应用程序时需要考虑的一些因素:
- 模型质量:我能从中获得什么样的质量?我能找到一个现成的、高质量的可使用模型吗?或者我有办法自己改进它吗?或者我是否需要将LLM包装在一个更大的应用程序中,使用这个不可靠的组件最终做出可靠的事情?这是一个重要的部分。
- 服务成本:这在很大程度上取决于您要做什么。例如,如果您的应用程序是每天阅读几份文档并从中提取一些信息,那么LLM处理一份文档花费一美元或十美元都没关系,完全可以接受。如果您的应用程序是在网页上放置针对每个用户定制的广告,或者在用户浏览您的商店网站时为他们选择推荐,您无法承受每次有人查看页面时花费数美元。您需要每天以适中的成本为数百万甚至数亿个实例提供服务,因此您将真正为成本进行优化。
- 服务延迟:另一个重要因素。同样,如果您正在进行一些离线分析,您的应用程序花费几分钟是可以接受的。但如果您在交互式网页上进行某些操作,它最好在几毫秒内运行才能获得高质量的应用程序。
- 可定制性:最后一点可能很棘手,但思考它很重要。如果您有一个重要的应用程序,您会希望随着时间的推移不断提高其质量,在出现问题时进行调试,并通常使其变得更好并进行定制。因此,您需要考虑不同的解决方案:我有哪些控制旋钮来控制它的表现、监控它、使其变得更好、防止某些我不希望出现的坏行为等等。这是设计LLM应用程序时需要思考的重要事项。
那么,本课程适合谁?在本课程中,我们真正希望它对从业者有用,并弥合当今您在其他课程中可以找到的两方面内容之间的差距。其中之一是黑盒解决方案,比如您可以调用的第三方LLM即服务API,教程非常简单,就像“好的,只需在这里输入您的文本并调用这个函数,您就会得到结果”,但很难在此基础上进行定制、将其放入更大的应用程序并确保获得最佳质量和性能等,因为它基本上是为每个人提供的一个模型。这是一方面。
另一方面,是LLMs工作原理的第一性原理,机器学习如何工作,如何思考构建可靠管道,这些您经常在学术课程、理论和算法中找到。
本课程旨在构建一个介于两者之间的实用课程。这样,当您的老板问您“您能在我的应用程序中用LLMs做点什么吗”,而您真正关心的是使其良好工作并做对时,您就拥有了使用现有组件构建自己的应用程序的工具,并随着时间的推移找到它并使其变得真正出色。我们希望这能让您了解该领域的一些研究和学术基础,以及根据我们今天所知,您可以做的一些工程上的事情,以可靠地创建高质量的应用程序。
以上就是对课程的简要概述,我们希望您喜欢它。我们在制作过程中非常愉快,并且非常兴奋地期待看到您用LLMs构建什么。


本节课中我们一起学习了为什么大语言模型(LLMs)在当前如此重要。我们探讨了LLMs如何超越炒作,正在变革多个行业,并解释了其作为统计语言模型的核心工作原理。我们还讨论了在实际应用LLMs时需要考虑的关键因素,如质量、成本、延迟和可定制性。最后,我们明确了本课程的目标是弥合理论与实践之间的差距,为您提供构建可靠、高质量LLM应用所需的实用知识和工具。
3:入门知识


在本节课中,我们将学习自然语言处理的基础知识,并了解一些重要的定义和概念。这些知识是理解后续大语言模型内容的基础。

什么是自然语言处理?🤔
自然语言处理是一门研究领域,它探讨什么是自然语言,以及如何创建模型和计算程序来解决自然语言相关的问题。
无论我们是否专门研究这个领域,我们每天都在使用自然语言处理技术。例如,当我们在电脑或手机上输入文字时,系统会利用自然语言处理技术为我们提供自动补全、拼写检查等功能。这些功能都属于自然语言处理的应用范畴。
并非所有这些问题都通过大语言模型解决,但未来我们可能会看到更多基于大语言模型的解决方案。
自然语言处理的应用场景 📝
自然语言处理之所以有用,是因为它能帮助我们解决多种不同的任务。
以下是自然语言处理的一些常见应用场景:
- 情感分析:判断用户对某个产品的评论是正面还是负面。
- 机器翻译:将一种语言翻译成另一种语言。
- 聊天机器人:构建依赖自然语言作为输入形式的交互式系统。
- 相似性搜索:我们将在模块2中详细讨论,它同样属于自然语言处理领域,旨在实现自然语言输入和输出。
- 文本摘要:总结复杂文档,或从长文档中提取关键信息。
- 文本分类:不仅仅是判断情感,还可以识别文本的体裁、情绪或包含的内容。
可以看到,自然语言处理的应用范围非常广泛。
核心定义与概念 🔑
上一节我们介绍了自然语言处理的应用,本节中我们来看看其核心定义。理解这些术语对于后续学习至关重要。
考虑这个句子:“The Moon, Earth's only natural satellite, has been a subject of fascination and wonder for thousands of years.” 这是一个完整的句子。如果我们分析这个句子的构成,它由短语、单词、字符等基本单元组成。在自然语言处理中,这些基本单元被称为 tokens。
tokens 不一定是单词、字符或子词,它是我们在创建模型时做出的一种选择。你可以将 tokens 视为构建自然语言处理问题的“积木”或“原子”。
一个 sequence 则是一系列 tokens 的集合,旨在表示这些 tokens 的顺序排列。如果 token 是单词,那么一个 sequence 可能是一个完整的句子或句子片段。如果 token 是字符,那么一个 sequence 可能是单词“the”中的字符 T, H, E。
vocabulary 是我们特定模型可用的全部 tokens 的集合。这可以是英语中的所有单词,也可以是我们定义问题时使用的所有字符。
自然语言处理的任务分类 🧩
利用这些定义,我们可以对自然语言处理中的不同任务进行分类。
- 翻译任务:可以视为
sequence-to-sequence任务,因为它接收一个序列(源语言文本),并通常输出另一个序列(目标语言文本)。 - 情感分析:如果将其视为分类问题,技术上属于
sequence-to-non-sequence预测,因为它通常输出一个或几个tokens(如“正面”或“负面”)。 - 问答系统:则更为复杂,它接收一段文本序列,然后通常生成一大段非确定性的文本序列,这是一个更开放的问题。
能够对这些不同类型的任务进行分类,在课程后续部分会很重要。因为我们将发现,评估一个模型的好坏可能相当主观,并且取决于你所要解决的具体任务。
自然语言处理的广阔领域 🌐
当然,自然语言处理是一个比文本建模更广阔的领域。本课程将重点放在基于文本的自然语言处理问题上,因为这一领域本身就有大量内容需要探讨。但你需要清楚,自然语言处理远不止于文本到文本的问题解决。
例如,将可听语言转换为文本或将一种可听语言转换为另一种可听语言的语音识别;根据文本生成图像描述的图像字幕生成;以及过去12个月里我们看到的像DALL-E、Stable Diffusion等从文本生成图像的惊人工具,都属于自然语言处理的范畴。
你可能会问,为什么本课程不涵盖这些内容?原因在于,仅文本理解本身就极具挑战性。我们会看到,即使深入研究简单问题,仅处理文本本身就已经很复杂了。事实上,想想你的日常生活和与他人的交流,仅使用语言的口头形式就面临着许多不同的挑战。
语言可能具有歧义性;上下文可能因你说话的方式、地点和时间而改变;对于一个特定的问题或评论,可能存在多个合理的答案。
数据与模型的发展趋势 📈
过去几年,我们看到输入这些模型的数据量在不断增长。模型规模也在以类似的方式增长。我们将在本项目的第二部分更详细地讨论这些模型规模的具体含义。但在本课程中,我们将默认接受模型规模会不断增长以变得更准确、性能更强这一事实。
同样,训练数据也是我们将在模块4的微调部分探讨的内容,但本课程不会过多涉及细节。
所有这些因素都很重要,因为它们决定了我们将在下一个视频中讨论的不同类型的语言模型。

总结

本节课中,我们一起学习了自然语言处理的基础知识。我们了解了自然语言处理的定义、常见应用场景,以及 tokens、sequence、vocabulary 等核心概念。我们还对自然语言处理任务进行了分类,并认识到该领域远不止于文本处理。最后,我们了解了数据和模型规模的发展趋势,为学习下一课关于语言模型类型的内容做好了准备。
4:语言模型


在本节课中,我们将要学习语言模型的基本概念,了解它们如何预测文本,并探讨“大语言模型”这一术语的具体含义。
什么是语言模型?
上一节我们介绍了文本的表示方法,本节中我们来看看语言模型本身。语言模型本质上是一种计算模型。它接收某种序列(例如我们之前讨论过的词元序列),然后在整个词汇表上计算出一个概率分布,以找出最可能的词。
语言模型通常可以分为两大类:生成式模型和基于分类的模型。
以下是这两种模型的主要区别:
- 基于分类的模型:这类模型的目标是预测一个被掩盖的词。例如,在一个句子中遮盖掉一个词,模型的任务就是找出最适合填入该位置的词。
- 生成式模型:这是当前大多数研究的主题。这类模型的目标是预测给定序列之后最可能出现的下一个词。
因此,语言模型的核心任务,就是在内部计算一个覆盖整个词汇表的概率分布,从而找出最适合接在后面的词。这就是语言模型的全部。其内部实现可以基于像Transformer这样的架构(我们将在本课程中深入探讨,并在课程2详细讲解Transformer的架构细节)。目前,我们可以将其视为一个黑箱,其内部过程就是为词汇表中的每个词元计算一个概率。
什么是大语言模型?
了解了语言模型的基础后,本节我们来看看是什么让它们变得“大”。大语言模型是语言模型的一种,其规模从通常的1000万到5000万个参数,增长到了如今我们所见的数十亿甚至数百亿个参数。
Transformer是一种在2017年左右提出的架构,自此之后便主导了自然语言处理领域。在此之前,也存在许多语言模型格式,其中一些甚至采用了深度神经网络架构。然而,它们通常不被认为是“大”模型,因为那些模型的参数量通常少于几百万。
我们暂时不过多讨论“参数”具体是什么。尽管Transformer之前的这些语言模型不被认为是“大”模型,但它们仍然需要大量的计算努力才能获得结果。Transformer释放了巨大的计算效率潜力,这正是2017年后相关技术真正起飞,以及“大语言模型”这一新术语兴起的原因。
总结

本节课中我们一起学习了语言模型的核心概念。我们了解到,语言模型是一种通过计算概率分布来预测文本(如下一个词或被掩盖的词)的计算模型。而“大语言模型”特指那些参数量极其庞大(达到数十亿级别)的语言模型,其规模的飞跃主要得益于Transformer架构带来的计算效率突破。
5:分词


在本节课程中,我们将学习分词的概念。分词是自然语言处理中的基础步骤,它将文本转换为计算机可以处理的数字格式。我们将探讨不同的分词策略及其优缺点。

什么是分词? 🤔
我们之前定义过,给定一个短语或句子,构成这个短语或句子的标记将成为我们在NLP建模中的一个设计选择。这些标记可以是不同的单词、字符或单词片段。分词的过程旨在将我们选择的文本片段(如单词)转换为可用于计算的格式。计算机不擅长符号数学,因此我们需要将文本转换为数字格式。
分词策略对比 📊
上一节我们介绍了分词的基本概念,本节中我们来看看几种主要的分词策略:基于单词、基于字符和基于子词的分词。
以下是三种主要分词策略的概述:
- 基于单词的分词:将句子或单词序列切割成独立的单词。
- 基于字符的分词:将文本切割成单个字符。
- 基于子词的分词:将单词切割成有意义的子单元。
基于单词的分词
基于单词的分词过程分为两步。首先,我们根据训练数据创建一个包含所有不同标记的词汇表。例如,我们可以将英语词典中的每个单词都放入词汇表,并为每个单词关联一个数字(如从0开始编号)。这就构建了我们的索引。之后,每当我们看到一个新的单词序列,就可以将其转换为一个索引数字列表,从而编码成一系列模型可以处理的数字。
然而,基于单词的分词存在一些局限性和问题。如果我们构建词汇表时使用的训练集遗漏了某些常见或不常见的词,那么在后续使用语言模型时遇到这些词,就会产生词汇表外错误。这是基于单词分词的一个限制,因为它必须为每个独立的单词关联一个特定的标记值。
这也意味着,如果存在拼写错误,或者我们想创造新词,这种分词方案无法处理这种灵活的行为,它非常脆弱。此外,这还会导致词汇表非常庞大,因为我们必须为每个单词的每种形式(如 fast, faster, fastest)都分配不同的标记。如果我们有 slow, slower, slowest,又需要另外三个词。而实际上,我们可以只取这些单词的词干,然后将后缀作为单独的片段添加。
基于字符的分词
另一种使词汇表显著变小的解决方案是查看单个字符。如果选择英语,我们会有26个小写字母、26个大写字母,以及一些其他标点和数字字符。因此,词汇表大小大约在100左右。
这确实会创建一个非常小的词汇表,并且我们能够创建任何想要的新词,也能够处理任何拼写错误。然而,问题在于我们失去了“单词”的概念。在处理NLP问题时,我们必须牢记,我们需要尝试捕捉上下文和含义。通过将文本分解为单个字符,我们失去了单词的意义。
这也意味着我们的序列长度会变得非常长。例如,如果进行单词分词,句子“The moon”的前两个标记是单词“The”和“moon”。但如果进行字符分词,那么这个序列的前两个标记将是“T”和“h”。因此,我们最终需要处理非常长的序列长度,这也是我们希望尽量避免的。
基于子词的分词
介于上述两种极端方案之间的折中方法是进行子词分词。例如,单词“subject”可以被拆分为“sub”和“ject”,然后你可以用这些词块构建其他单词,如“object”、“subjective”、“subordinate”、“submarine”。
有多种不同的策略可以构建这类词汇表,字节对编码是一种流行的方案。还有许多其他方案,如 SentencePiece 和 WordPiece,在现代大语言模型中非常常用。这些方案在词汇表大小与灵活性之间取得了良好的平衡,既能处理词汇表外的单词,又能保留所描述单词的足够含义。
我们会发现,子词分词是该领域人们使用的主要分词方案。
总结 📝
本节课中我们一起学习了分词的核心概念。通过比较不同的分词方案,我们可以看到子词分词在标记数量与词汇表大小之间提供了最佳平衡。一旦我们获得了这些标记,下一步就是尝试理解如何融入含义和上下文,这将是我们下一个视频讨论词嵌入的主题。


6:词嵌入(Word Embeddings)🚀

在本节课中,我们将要学习自然语言处理中的一个核心概念——词嵌入。词嵌入的目标是尝试捕捉并保留特定词汇在其语境中的上下文信息,这包括该词与其他词的关系,或其自身的内在含义。

从词汇到向量
上一节我们介绍了如何将句子转换为标记(Token)。我们通过分词方案将句子中的词汇转换为特定的数值。然而,在自然语言处理中,我们常常需要比较两个句子或文档,以判断它们是否相似、是否包含相反的观点或情感等。
因此,含义相似的词,往往出现在相似的上下文中。例如,我们看这两个句子:
- “The cat meowed at me for food.”
- “The kitten meowed at me for treats.”
这两个句子明显不同,但在某些语境下,它们具有几乎相同的含义:都描述了一只猫科动物对着我喵喵叫,想要食物。单词“cat”和“kitten”显然是不同的词,但它们含义非常相似。如果我们将这些词投射到某个高维向量空间中,它们在某种意义上会非常接近。同样,“food”和“treats”也存在这种上下文相似性。
理想的情况是,我们能建立某种方案或模型,用数值来表达这种关系。我们的目标就是构建一些可用于上下文映射和嵌入的向量。
词袋模型(Bag of Words)及其局限性
让我们先通过一个简单的例子来探索。假设我们有以下三个句子,词汇表包含这些句子中出现的所有单词:
- The cat sat.
- The cat sat in the hat.
- The cat with the hat.
以下是构建向量表示的方法:
我们将统计每个单词在这些文档中出现的频率,看看这如何帮助我们比较不同的文档。
- 对于第一个句子“The cat sat.”,我们在词汇表的前三个单词位置得到值1,其余位置为0。
- 对于第二个句子“The cat sat in the hat.”,我们得到两个“the”,然后除了“with”之外,其他单词几乎都出现一次。
- 对于第三个句子“The cat with the hat.”,我们再次得到两个“the”,一个“cat”,没有“sat”和“in”,一个“hat”和一个“with”。
这样,我们就为这些文档构建了向量表示。如果我们想比较这些文档,我们可以使用这些向量来查看,例如,“the”这个词在每个文档中相对于其他文档的出现情况。可以说,最后两个句子更相似,因为它们有更多共同的词汇。
然而,如果我们将这种方法扩展到具有现实词汇量的更大场景中,就会遇到问题。由于任何合理长度的句子都不可能包含一种典型语言中的所有不同单词,因此向量中几乎99%的位置都会是零。例如,英语大约有25万个独特单词,除非使用词典,否则任何句子或文档都不可能接近包含所有这些单词。
虽然词袋模型是一个有用的工具,并且许多语言模型确实基于此类方法构建(如果你有兴趣了解更多,可以查看TF-IDF,它是旧版语言模型中非常常用的工具),但这里的稀疏性问题意味着,当我们将模型扩展到更大的问题和更复杂的文档时,这种方法并不真正有效。
此外,这种方法也丢失了每个单词本身的含义。虽然我们可以看到“the”这个词比其他任何词都更常见,但它并没有真正告诉我们这个词的实际含义。回想上一张幻灯片,我们希望找到一种方法,让“cat”和“kitten”在某种意义上能够“在一起”。
词嵌入函数
这正是词嵌入函数或单词向量化函数发挥作用的地方。我们不会深入探讨词嵌入方法的具体实现(这超出了本课程的范围),但你可以自行查阅像Word2Vec这样的工具,它是一套将不同单词转换为向量表示的算法。
本质上,其工作原理是:我们使用训练数据集中的每个单词,并查看它周围的单词。我们设置一个窗口,查看每个单词左边和右边的单词。这样,它就构建了一个映射,显示一个单词如何与另外三个单词一起出现,以及另一个单词在特定上下文中如何与相同的三个单词一起出现。
以我们之前看过的短语“the kitten meowed at me”和“the cat meowed at me”为例,我们可以看到“kitten”和“cat”处于相同的位置,并且被相同的单词包围。如果我们对整个词汇表或整个训练数据集进行此操作,就可以构建出代表哪些单词通常出现在这些单词附近的向量。
如果我们给向量赋予数百到数千的维度,我们就可以开始为这些单词构建一些有趣的含义和嵌入值。
通常的流程是:我们有一个单词,将其转换为标记(Token),然后将该标记放入嵌入函数,最后从该标记的单一索引中得到某种向量。
词嵌入的可视化与理解
完成这一步后,我们可以通过观察不同单词之间的关系来获得一些有趣的发现。正如我们在上一张幻灯片中看到的,这些向量非常长,有数百甚至数千个维度。但在某些情况下,我们可以将这些向量投影到2D平面上,并像你在这里看到的那样绘制在屏幕上。如果投影得当,我们往往会看到这些向量聚集成簇,含义相似的词会聚集在一起。
正如你所料,“dog”和“puppy”紧密相关,“car”和“van”也是如此。我们并不总是确切知道不同列(维度)的含义,它们几乎不像我们现在屏幕上显示的那样是精确的概念。然而,你可以感受到这些嵌入向量所发生的情况:我们使用的单词或标记,是由这个高维空间中的一系列值来描述的。
因此,你至少可以从概念上这样理解:例如,“dog”包含一些与“生物”相关的值,一些与“家庭”相关的值,可能一些与“交通工具”无关的值,以及一些与“年龄”相关的值(比如“dog”和“puppy”在这方面会有不同的值)。
我想再次强调,这些列并不对应如此具体的含义。然而,从向量的行为方式来看,其含义在某种程度上是分布在不同列中的。
如果这令人困惑,请不要过于担心,因为我们不需要过多纠结于此。 suffice to say,这些嵌入向量在封装每个标记的含义方面效果非常好。
总结

本节课中,我们一起学习了词嵌入(Word Embeddings)的概念。我们了解到,将单词转换为高维向量表示,可以有效地捕捉词汇的语义和上下文信息。我们首先探讨了简单的词袋模型及其在处理大规模词汇时的稀疏性局限,然后引入了更先进的词嵌入方法(如Word2Vec),该方法通过分析词汇的共现关系来生成稠密向量。最后,我们看到了这些向量在降维可视化后,语义相近的词汇会聚集在一起,这直观地展示了词嵌入如何将语言的含义编码为数学模型,为后续更复杂的自然语言处理任务奠定了基础。
7:总结


在本节课中,我们将对自然语言处理的基础知识进行回顾与总结,梳理核心概念,为后续模块的学习做好准备。
自然语言处理是一个专注于研究自然语言的领域。它主要研究文本,但其范围远不止于如何建模基于文本的问题。自然语言处理还研究语音、文本视频、图像转文本等其他概念,只要这些概念中自然语言是重要的组成部分。
自然语言处理在许多方面都极其有用。例如,将一种文本翻译成另一种文本,总结长篇文本,以及处理分类问题。在这些问题中,我们期望输入是自然语言,输出也是自然语言。这些任务由语言模型完成。
语言模型本质上是一种工具,用于在我们所使用的词汇表上创建一个概率分布。其核心公式可以表示为:
P(w_t | w_1, w_2, ..., w_{t-1})
这个公式表示,给定前 t-1 个词(或标记)的情况下,下一个词 w_t 出现的概率。
大语言模型是基于Transformer架构的语言模型,其参数量达到数百万甚至数十亿级别。
标记是我们语言模型的最小构建单元。它们将我们的文本转换为索引,然后这些索引被转换为多维的词嵌入向量。通过这种方式,我们可以更好地理解每个标记或每个词背后的上下文和含义。
以下是本课程核心概念的快速回顾列表:
- 自然语言处理:研究如何让计算机理解、解释和生成人类语言的领域。
- 语言模型:一种对词汇序列的概率分布进行建模的工具。
- 大语言模型:基于Transformer架构、参数量巨大的语言模型。
- 标记:文本处理的基本单元,可以是词、子词或字符。
- 词嵌入:将标记映射到高维向量空间的技术,以捕捉语义信息。
至此,我们已经完成了自然语言处理入门部分的学习。希望你现在已经准备好进入模块一的学习。


我们下一个模块再见。
8:Databricks 环境介绍 🚀
在本节课程中,我们将学习如何使用 Databricks 笔记本环境。如果你已经熟悉 Databricks 和笔记本,可以跳过本节。对于 Databricks 的新用户或希望复习的用户,我们将介绍笔记本的一些重要功能,帮助你充分利用本课程。
Databricks 笔记本可以看作与 Jupyter 笔记本风格非常相似的计算环境。然而,Databricks 笔记本提供了增强的安全性、更好的协作工具,以及更多在协作环境和处理大数据时非常重要的功能。
界面概览 📋
在笔记本的左侧,我们可以看到菜单栏。通过菜单栏,我们可以在工作区中浏览文件系统、使用 Repos 文件夹连接 Git 仓库,以及配置计算集群等。


在笔记本本身的左侧,我们有笔记本的标题,以及菜单栏中的一系列选项来执行特定功能。
需要注意的是,在课程中,如果你想保存和检索在实验中所做的工作,可以使用这里的导出命令来导出 Databricks 笔记本。


如果你要将代码从一个 Databricks 环境移动到另一个环境,使用 DBC 存档格式导出笔记本可以保留文件结构,使笔记本的迁移更加便捷。如果你只需要源文件或 Python 笔记本,也可以导出这些格式。
在右侧,你可以看到一些按钮。我们可以根据需要安排笔记本的运行、与他人共享笔记本。我们将重点关注最左边的两个按钮:Run All 命令和集群状态按钮。
Run All 命令如其名,将按顺序运行笔记本中的每个单元格。旁边的按钮是我们的集群状态,从这里我们可以启动新集群、启动或终止集群。登录 Vquaium(我们的实验环境)时,你的集群应该已经启动并运行。如果尚未连接,你需要点击此处的按钮,然后连接为你预准备的集群。
核心功能演练 🛠️
接下来,我们将演练这个 Databricks 笔记本,涵盖创建新单元格、执行代码、创建 Markdown 单元格、从 Databricks 文件系统读取数据、可视化数据以及安装库(你将在课程中需要这样做)。
我们还将使用一些 Databricks 笔记本实用程序,例如使用一些魔法命令,我们稍后会介绍这些命令。

在所有笔记本(包括演示笔记本和实验笔记本)的开头,你会看到我们需要运行一个名为“课堂设置”的内容。
课堂设置的目的是确保我们配置了数据集、模型和用户环境,以便顺利运行我们为实验和演示准备的代码。
我们将使用第一个魔法命令来运行此设置。这是使用特殊的 %run 魔法命令,它允许我们在此笔记本内实际运行另一个笔记本。在我们的文件结构中,会有一个 includes 文件夹和一个 classroom_setup 笔记本,我们将在这里运行该笔记本。
有多种方式可以运行单元格。
我们可以转到“运行”菜单并运行当前单元格。你也可以看到键盘快捷键的提示,即 Ctrl+Enter 或 Cmd+Enter。
我们还可以在右侧看到,可以使用这里的播放按钮“运行单元格”选项来运行此单元格。我将按键盘上的 Ctrl+Enter 来运行此单元格,你可以看到输出显示在下方。运行命令是当前正在发生的事情,然后我们将看到从此笔记本运行的笔记本的一些输出。

你会注意到,我们可能会收到“你可能需要重启内核”的提示,不用担心,这只是我们看到的预期输出的一部分,你实际上不需要重启内核。

如果你的环境设置正确(我们已为你预先设置好),你不需要安装任何数据集,因此你会看到“跳过现有数据集”的提示。
我们将在另一个视频中向你展示,如果在不同的工作区使用这些文件,如何确保你已准备就绪。然后,你最终可以看到我们还有一些预定义的变量:一个工作目录、一个数据库,以及一个指向我们稍后将使用的数据的链接。

你可以看到此命令已正确完成,并告诉我们此命令花费了多长时间、由谁运行以及何时运行。

单元格操作 📝
上一节我们介绍了运行设置脚本,本节我们来看看如何创建和管理单元格。单元格是我们注入代码、注释以及笔记本中所需一切内容的方式,因此熟悉它非常重要。
我们可以通过按此处的 X 按钮删除单元格,也可以通过多种方式添加单元格。


当你将鼠标悬停在不同的单元格上时,会看到一个加号按钮。

我们可以在此特定单元格上方创建一个单元格,也可以使用用户界面在下方创建一个。我们还可以使用键盘快捷键来完成相同的任务。


如果我们点击文本,会看到一个闪烁的光标。如果我们想在单元格中写入更多文本,只需像平常一样写入即可。
但如果我们想使用键盘在上下方创建单元格,需要点击单元格外部,即其下方的空白部分。然后,如果我们按键盘上的 A,会在上方创建一个单元格;如果我们点击返回以确保此单元格被高亮显示,然后按 B,会在下方创建一个单元格。




你可以看到此单元格的默认代码语言是 Python。你也可以将其更改为其他语言,以便在特定单元格中编写。
笔记本默认配置为 Python 语言,你可以在左上角看到此配置。因此,我们可以将其更改为 Databricks 笔记本支持的其他四种语言之一。


现在,我们将删除这些并继续。我们已经看到了如何通过运行设置脚本来执行一些单元格,但我们也可以同样运行这里的代码。我们有一些简单的 Python 代码,因此如果我们按 Ctrl+Enter,可以看到输出显示在这里。




我们也可以在笔记本中使用非默认语言。例如,如果我们有一个 SQL 笔记本,我们可以在其中运行一些 Python 代码;同样,我们也可以像这样在 Python 笔记本中运行一些 SQL 代码。
我们不会花太多时间在这上面,因为我们的课程专注于所有代码都使用 Python,所以你不需要担心这一点。重要的是要意识到 Databricks 笔记本中有许多功能,我们在本课程中不一定会涉及。

Markdown 与数据操作 📊
在大多数笔记本中,你还会看到许多 Markdown 单元格,用于描述我们正在查看的一些功能并解释一些概念。你可以通过在单元格顶部使用 %md 魔法命令,在 Databricks 笔记本中编写自己的 Markdown 单元格。



然后,你可以编写常规的 Markdown 文本。

要渲染此内容,只需像执行其他单元格一样,按 Shift+Enter、Ctrl+Enter 或 Cmd+Return 执行此单元格。


你可以看到所有这些都使用 Markdown 库进行了渲染。


在本课程中,我们还将读入大量数据。我们已经为你预先编译并下载了专门创建的数据集到工作区中。

让我们看看如何从名为 labeled_newscatcher_dataset 的 CSV 文件导入数据集。

我们有一个 CSV 文件位置,它位于我们预定义结构中的 paths.data 路径。然后,我们将使用 pandas 将此 CSV 文件读取为 CSV 格式,并显示数据集。我们可以运行此单元格。


你可以看到 pandas DataFrame 输出显示在下方。



我们还可以可视化数据。我们将可视化大量数据,这在任何数据科学或机器学习应用中始终是重要的一部分。我们将导入 Matplotlib。


并绘制主题和标题的图表。


你可以看到我们这里有条形图,显示了每个主题出现的次数。


如果我们想要显示一些文本或数据框,这些内容可能以复杂的方式查看,或者只需要稍微美化一下,我们可以使用 Databricks 内置的一个名为 display 的命令。


这将漂亮地打印多种不同的数据类型,包括 Spark DataFrames 和 pandas DataFrames。

让我们看看新闻抓取数据按主题计数的结果。



你可以看到一个格式精美的表格,我们还可以使用此 display 命令内置的一些简单可视化功能。如果你想对数据进行一些快速粗略的可视化,这很有帮助。



库管理与安装 📦
另外,你可能已经注意到,我们实际上不需要导入 Matplotlib 或 pandas,更准确地说,我们不需要使用 pip 安装它们。Databricks 运行时(我们当前用于此笔记本的运行时)内部包含了许多 Python 库。

每个运行时在发布时都包含不同类型的 Python 库。



但有时我们需要安装额外的库。在本课程中,我们将需要安装一些 Python 库。

我们使用这里的 %pip 魔法命令通过 pip 进行 Python 安装。

通常,我们看到许多人下载并安装最新版本。例如,如果我们想安装 nltk,可以像这样使用 %pip install nltk,这很常见。
然而,我们确实想强调,最佳实践是使用 pip 安装特定版本的库,特别是如果你打算与他人共享此代码以便复现结果时。

因此,在本课程的所有实例中,我们为所有使用 pip 安装的库都指定了版本。
我将重新运行此命令。现在,你可以看到它下载了 1.4.0 版本,但我们将保留 1.1.0 版本。让我们重新运行此命令。

它应该卸载我们已有的所有内容,然后重新安装。

你可以看到它卸载了 1.4 版本,然后安装了 1.1 版本。现在安装完成后,我们可以导入 nltk,并像使用其他所有库一样使用这个库来处理我们的代码库。



总结与延伸 📚
如果你有兴趣了解更多关于 Databricks 平台和笔记本的功能,请查看所有这些其他优秀的指南。Databricks 笔记本还有很多功能,但对于你开始本课程的演示和实验来说,这些内容已经足够,可以让你充分利用时间。


本节课中,我们一起学习了 Databricks 笔记本环境的基本操作,包括界面导航、运行代码、管理单元格、使用 Markdown、读写数据、数据可视化以及安装和管理 Python 库。掌握这些基础是顺利进行后续课程的关键。
感谢观看。



9:安装数据集 📂
在本节中,我们将学习如何在自己的工作空间中安装课程所需的数据集。这是为了确保后续的代码能够顺利运行,并避免不必要的计算资源消耗和时间浪费。

概述


如果你使用自己的工作空间,并希望运行我们提供的所有代码,你需要先执行几个初始步骤。其中最重要的一步是确保数据集已安装并缓存在你的工作空间中。否则,代码运行可能会非常耗时,并可能消耗不必要的计算资源。
安装步骤


以下是安装数据集的具体步骤。

首先,你需要从我们的GitHub仓库克隆代码库。克隆完成后,导航至 LLM 0 - Introduction 文件夹,并找到名为 0.9 Install Datasets 的笔记本文件。


接下来,你只需运行笔记本中的第三个命令,即 %run ./Includes/Classroom-Setup。

这个命令会查找所有课程所需的数据集,并将它们安装到相对于你工作空间的本地位置。

根据你的网络连接速度和数据可用性,这个过程可能需要大约20分钟。如果数据集已经安装过,则此过程将立即完成。

验证安装

安装完成后,你可以运行第四个命令来验证所有数据集是否已正确安装在指定位置。


这个命令会显示所有数据集及其存储路径。确认无误后,你就可以顺利运行课程中的其余代码了。
总结


本节课中,我们一起学习了如何为Databricks课程安装必要的数据集。我们了解了安装的重要性,并逐步完成了从克隆仓库到运行安装命令,再到验证安装结果的完整流程。确保数据集正确安装是后续所有实验和代码运行的基础。
10:引言 🎯


欢迎来到我们的第一个模块:大语言模型的应用。本模块将从一个小趣闻开始:所有CEO都在要求员工尽快开始使用大语言模型。作为一名首席技术官,我也曾有过类似的经历。但大语言模型的优势在于,你确实可以非常快速、以最小的努力在许多应用上起步,然后再逐步改进。
在本节课中,我们将要学习一些常见的应用场景,了解哪些任务可以“开箱即用”,以及如何判断哪些应用适合使用大语言模型,以及以何种方式使用。我们首先会介绍,在许多标准的自然语言处理任务中,你都可以找到表现优异的预训练大语言模型(通常是开源的),并且有完善的方法来针对你的具体应用优化它们。
因此,在本模块中,我们将涵盖一些最常见的大语言模型应用。我们将介绍Hugging Face,它既是一个用于处理大语言模型的编程框架,也是一个获取众多开源模型的途径。我们还将学习提示工程,这是让一个通用大语言模型执行其他任务的基本方法。
我们将从一些常见任务开始,例如文本摘要、评论分类或问答,并开始讨论大语言模型的其他应用。我们还将初步探讨使用大语言模型时存在的权衡:不同的方法涉及不同的工作量,并带来不同的质量和性能结果。因为在规划应用时,你需要开始思考这些问题。
希望在本模块结束时,你将了解大语言模型已经非常擅长的常见任务类型,以及通过不同程度的工程努力(从少量提示工程到复杂的数据收集)能让它们做到的事情。你也将能够开始思考,在你的应用中,有哪些既令人兴奋又切实可行的领域可以应用它们。


本节课中我们一起学习了本模块的概述,包括大语言模型快速入门的潜力、我们将要探讨的常见应用类型(如摘要、分类、问答)、以及将涉及的关键工具(Hugging Face)和方法(提示工程)。我们还初步认识了在应用大语言模型时需要权衡的要素:努力程度与产出质量。这为我们后续深入具体应用打下了基础。
11:课程概述与LLM应用入门 🚀

在本节课中,我们将学习如何快速入门大语言模型的应用。我们将了解预训练大语言模型能解决哪些问题,并学习如何使用Hugging Face的API、数据、流水线、令牌和模型来与LLM进行交互。课程的核心目标是帮助你快速为你的应用找到合适的模型,并理解提示工程的重要性。
课程目标
上一段我们介绍了本模块的整体目标,以下是本模块具体的学习目标清单:
- 理解预训练大语言模型所能解决的各类应用问题。
- 使用Hugging Face的API、数据、流水线、令牌和模型来下载并与LLM交互。
- 掌握如何快速为你的应用找到合适的模型,例如通过Hugging Face Hub。
- 理解提示工程的重要性并掌握其入门方法。
从业务问题到技术方案
正如Matttee所提到的,许多CEO或CTO最近都在说“尽快开始使用LLM”。而我们其他人自然会问:我能用LLM来做什么?以及具体该如何实现?
面对一个业务问题,我们的思路是:首先确定它对应哪种标准的自然语言处理任务。确定了任务之后,再寻找适用的模型。观察右侧的图表(2023年4月Hugging Face Hub的截图),我们可以看到,针对这些常见的NLP任务,有成千上万个模型可供选择。
问题在于模型数量太多,我们必须做出选择。本模块将花费相当多的时间来讨论如何选择模型。
实战案例:新闻摘要生成
为了让内容更具体,我们将使用一个贯穿始终的示例:为新闻源生成摘要。😊
给定文本文章(长篇文本),一个标准的NLP任务就是将其总结为更短的文本。对于这个应用而言,目标是生成简短的摘要,以便显示在新闻应用中供用户滚动浏览。
NLP生态系统工具简介
在我们深入实践之前,先快速了解一下庞大的NLP生态系统中的部分工具样本。
- Hugging Face:位于顶部,我们已经提到过。其Transformers库及相关的Hub最为人所知的是拥有大量基于深度学习的预训练NLP模型和流水线。Hugging Face提供的功能远不止这些。
- 经典NLP库:目前仍然存在许多非常流行的库,专注于经典NLP方法,而非基于LLM或深度学习。
- 专有服务:例如围绕Open AI的一些著名专有服务。
- 新兴框架:例如LangChain等新来者,它们本身不提供模型,而是提供围绕模型的工作流或链。我们将在课程后续部分详细讨论这些。
总结

本节课中,我们一起学习了本模块的总体目标和具体学习路径。我们了解了如何将业务问题映射到NLP任务,并认识到从海量模型中做出选择是一个关键挑战。我们引入了一个新闻摘要生成的实战案例,并简要概述了当前NLP生态系统中的主要工具。接下来,我们将从探索Hugging Face开始我们的实践之旅。
12:Hugging Face 简介与核心组件 🚀

在本节课中,我们将学习 Hugging Face 这一平台及其核心库 transformers 和 datasets。我们将了解如何使用这些工具来简化大语言模型的应用流程,包括文本的预处理、模型调用以及数据集的加载。
概述:Hugging Face 平台
Hugging Face 既是一家公司,也是一个专注于开源机器学习项目的社区,尤其在自然语言处理领域最为知名。其平台托管了模型、数据集以及用于演示和代码的“空间”,这些资源都可以便捷地下载。平台上的资源遵循不同的开源许可协议。此外,Hugging Face 还提供了一系列功能强大的库。
核心库介绍
上一节我们介绍了 Hugging Face 平台,本节中我们来看看它提供的几个核心库。
以下是三个主要的库及其功能:
datasets:这个库提供了从 Hub 下载数据集的方法。transformers:这个库让处理 NLP 的核心组件(如流水线、分词器和模型)变得简单,并能方便地从 Hub 下载预训练模型。evaluate:顾名思义,这个库用于模型评估。
在右侧的图表中,可以看到基于 Stack Overflow 提问数量,transformers 库的流行度在过去几年里急剧上升。我认为其原因是 Hugging Face 提供了非常简洁的 API,但其底层功能却十分强大,因为它利用了 PyTorch、TensorFlow 和 JAX 等深度学习框架。
深入 transformers 流水线
上一节我们了解了核心库,本节中我们来看看 transformers 库的核心——流水线。
让我们首先看看流水线是什么样子的。这里我们回到文本摘要任务:左边是文章,右边是摘要,中间是大语言模型。使用 transformers 库,只需导入 pipeline 类,声明需要一个默认的摘要流水线,然后将文章传入即可完成。在底层,它会自动为我选择一个默认的 LLM 并进行配置,尝试执行正确的操作。

然而,通常我们需要自己进一步配置流水线。让我们打开流水线,看看它可能包含的常见组件。原始输入可能会经过一个称为“提示词构建”的步骤。我们将在本模块稍后部分更详细地讨论提示词,现在只需知道,有些(并非所有)LLM 除了原始用户输入外,还需要进一步的指令。对于摘要任务,可能简单到只需在文章前加上“总结:”这样的前缀。
然后,文本会经过一个分词器,它将文本编码为数字,这是我们的模型(即 LLM)所期望的输入格式。模型输出一个编码后的摘要,然后由同一个分词器对该摘要进行解码。需要说明的是,本幻灯片略过了流水线中可能发生的潜在预处理和后处理步骤,但它给出了关键组件。
分词器详解
上一节我们概述了流水线,本节中我们更仔细地看看其中的分词器组件。
在左下角可以看到,分词器将编码后的数据输出为 input_ids,这是实际的编码文本,以及 attention_mask。本课程不会深入讨论“注意力”机制,目前只需知道,attention_mask 是描述文本的元数据,你需要将它连同 input_ids 一起传递给模型,因为模型需要这些元数据。
在右侧,你可以看到我们使用 AutoTokenizer 类。这是 transformers 提供的多个“Auto”类之一,当你给它一个想要加载的模型或分词器名称时,它能自动完成正确的操作。

给定那个分词器后,我们可以传入文章。这里是一些你可能指定的配置示例:max_length(最大长度)、是否进行填充(padding)、是否截断输入(truncation)。这些配置都是为了将可变长度的输入文本转换为 LLM 所期望的固定长度张量。你需要根据选择的模型和具体任务来调整这些参数。底部的 return_tensors 参数只是表明我们使用的是 PyTorch。
模型与推理参数
上一节我们介绍了分词器,本节中我们来看看模型部分以及如何配置推理过程。
接下来是模型,我们将传入 input_ids 和 attention_mask 并获得编码后的输出。在右侧,你可以看到我们使用另一个 Auto 类,这次是针对序列到序列语言模型的 AutoModelForSeq2SeqLM。我们不会在此详细讨论不同类别的语言模型,但这本质上是指将一个可变长度的文本序列(如文章)转换为另一个可变长度的文本序列(如摘要)。给出模型名称,它会为我们加载正确的模型,然后我们可以传入 input_ids 和 attention_mask。
这里需要更详细地说明一下元数据:在调用分词器时,我们为可变长度输入指定了一些参数,这些元数据将帮助模型处理这些可变长度。
接下来是几个推理和输出参数:num_beams(我们将在编码部分进一步探讨),它表示我想使用集束搜索来生成输出文本,这是进行推理的多种方法之一。然后 min_length 和 max_length 表示我希望输出摘要的长度在 5 到 40 个标记之间。当然,这需要根据你的任务要求进行调整。
datasets 库简介

关于 transformers 的介绍已经足够,现在让我们简单了解一下 datasets 库。
该库为加载数据集提供了一行代码的 API,我们将主要讨论 NLP,但它也支持音频和视觉任务。datasets API 不仅允许你指定数据集名称,还可以指定版本号,这对于代码的可维护性非常有价值。这些数据集托管在 Hugging Face Hub 上,其用户界面允许你按任务、大小、许可证、语言等进行筛选,还能找到相关的模型,这非常有用。
总结

本节课中,我们一起快速浏览了 Hugging Face 的库和工具。我们了解了 transformers 库如何通过流水线、分词器和模型简化 NLP 任务,也看到了 datasets 库如何便捷地管理数据。接下来,我们将进入选择合适模型的任务。
13:模型选择 🧠

在本节课中,我们将学习如何为特定任务选择合适的语言模型。我们将探讨筛选模型的关键因素,并介绍一些知名的模型系列,帮助你做出明智的决策。
回顾我们之前提到的摘要生成应用。首先需要明确的是,像摘要、翻译这类宽泛的任务,内部也存在细节差异。例如,在摘要任务中,你可能会遇到两种选择:抽取式摘要(从原文中选择代表性的片段)和生成式摘要(生成全新的文本)。
明确了任务细节后,如何在众多模型中搜索呢?以Hugging Face平台为例,上面有超过17万个模型。如果按“摘要”任务筛选,可能得到约400个结果。接下来该怎么办?或许可以按流行度排序,但我们首先应该考虑的是需求。
有许多潜在的需求和筛选技术。我们先看一些简单的筛选方法。
以下是几种基础的筛选维度:
- 任务、许可证、语言:在界面左上角,可以根据任务、许可证(如商业许可)和语言等硬性约束进行筛选,这非常直接有效。
- 模型大小:虽然界面可能不直接支持,但你可以通过查看文件大小或参数数量来估算模型规模。这对于控制硬件成本、延迟等需求很重要。
- 流行度与更新:在界面右侧,可以按流行度和最近更新排序。流行度反映了社区的认可,而更新则很重要,因为过旧的模型可能无法与最新的库兼容。
接下来,我们探讨一些更微妙但同样重要的模型选择方法。
以下是更深入的考量因素:
- 模型变体:知名模型(如T5)通常会发布不同尺寸的版本(基础版、小版、大版)。建议从最小的版本开始原型开发,以快速启动并控制成本,后续可以升级到更强大的版本。同时,寻找针对你的任务或相似数据集进行过微调的模型变体,它们通常表现更好。
- 示例与数据集:不仅要搜索模型,还要搜索相关的使用示例和数据集。好的示例不仅能指导你调整参数,还能让你无需深入了解模型架构,就能判断它是否适合你的任务。
- 模型定位与数据:这个模型是通用的“多面手”,还是针对特定任务微调的专家?了解其预训练和微调所用的数据集至关重要。通常,与你的任务匹配的微调模型会更小或性能更佳。
- 定义指标与测试:最终,模型选择关乎你的数据和用户。需要定义关键绩效指标和评估标准,并在你的数据和用户上进行测试。
以上是一些通用的模型选择技巧。选择模型的另一部分,是认识那些知名且优秀的模型。
这里有一个简短的列表,它只是众多模型或模型家族中的一小部分。在右上角有一个更大型的语言模型表格,也值得查阅。
我不会逐一讲解整个列表,但想指出几点:首先,很多所谓的“模型”实际上是模型家族,其参数规模可能从数百万到数百亿不等,并且许多都有更具体的微调版本。例如,第一行的Pythia可视为一个基础模型家族,通常不作为开箱即用的最终模型,而是作为基础用于微调出像第二行Dolly这样的指令跟随模型。
还需注意,这些模型的规模差异很大。一些知名模型(如GPT-3.5)的参数数量可能反而不如某些不太知名的大型模型。对于大语言模型,规模确实重要,更大的模型通常能力更强,能处理更多任务类型,但这并非全部。例如,GPT家族投入了大量精力在基于人类反馈的强化学习等技术上,这些对齐技术旨在更好地满足用户需求。
当然,模型架构、预训练数据集和微调方法也极为重要,它们会导致模型之间的巨大差异。话虽如此,许多基础模型实际上是相互关联的,共享或选自一系列共同的技术或预训练数据集。

本节课中,我们一起学习了筛选模型的各种技巧,并认识了一些常见的模型名称。在下一个视频中,我们将转向探讨常见的自然语言处理任务。
14:NLP 任务

在本节课中,我们将要学习一些常见的自然语言处理任务。我们将重点介绍其中几个关键任务,并了解大语言模型如何应用于这些场景。
概述
自然语言处理涵盖多种任务。本节将介绍情感分析、翻译、零样本分类和小样本学习等核心任务。理解这些任务有助于我们更好地利用大语言模型解决实际问题。
常见NLP任务简介

以下是一些常见的NLP任务列表。本模块不会讨论所有任务,我们将重点讨论加粗的部分。需要指出的是,某些“任务”的定义非常宽泛,并且彼此之间存在重叠。但这些术语在相关文献和模型中心中经常出现,因此了解它们非常重要。
一个很好的例子是最底部的“文本生成”,它几乎可以涵盖任何其他任务。我们将要使用的一些摘要模型实际上就被标记为文本生成模型,因为它们能执行包括文本生成在内的多种操作。
情感分析 😊

上一节我们介绍了摘要任务,本节中我们来看看情感分析。
情感分析的一个应用示例是:监控股市并希望利用推特评论作为趋势的早期指标。给定一条推文,判断它对所讨论的股票是积极、消极还是中性的。
在左下角可以看到,除了“积极”或“消极”这样的标签外,模型可能还会返回一个置信度分数,大语言模型通常可以做到这一点。
翻译 🌐
接下来要提到的任务是翻译。顾名思义,它就是将文本从一种语言转换为另一种语言,但有几点值得指出。

有些大语言模型被微调用于从一种特定语言翻译到另一种特定语言。例如,左上角的模型专门用于从英语翻译到西班牙语。
另外请注意,有时你可以将NLP“任务”指定为更通用的任务,比如“文本到文本生成”,这正是我们在这个模型中做的。左下角的T5翻译模型就是一个相当通用的模型。由于它的通用性,它可以执行翻译之外的多种任务。实际上,我们给它一个指令(“将英语翻译成罗马尼亚语”),然后提供用户输入。
零样本分类 🏷️
我们要看的下一个任务是零样本分类。
一个很好的例子是:我有一个新闻浏览器,给定新闻文章,我想将其分类为体育、突发新闻、政治等类别。现在,我不想每次更改类别时都重新训练一个新模型,而是希望使用现有模型。与经典机器学习不同,大语言模型允许你这样做。大语言模型已经掌握了语言知识,因此当你添加或更改标签时,它已经知道这些标签的含义,从而有可能无需重新训练就能进行分类。

在左下角,你可以看到一个流程如何实现这一点:给定文章和标签列表,它将返回其预测结果。
小样本学习 📚
下一个非常有用且通用的任务是小样本学习,我几乎称其为一种技术而非任务。
在这个例子中,你通过示例向模型展示你想要它做什么。因此,我们不是为特定任务微调模型,而是提供几个执行该任务的示例。如果大语言模型足够强大,通常可以即时“学会”你想要它做什么。
在右侧可以看到,我们正在进行情感分析,但使用的模型并非专为情感分析设计。我们的指令是:“对于每条推文,判断其情感”。我们给出一些示例(这是一条推文,这是它的情感),再给几个,然后是一个查询(一条新推文),要求给出其情感。
这是一种在以下情况使用的技术:没有针对你任务的微调模型可用,并且你没有足够的标注训练数据来训练或微调一个模型,但你可以写出几个示例。这需要使用文本生成模型,这些模型必须足够通用才能理解和遵循这些指令。这是一种非常强大的技术。
我们开始看到,用户输入(可能是最底部的查询)正在被精心设计成一个更大的提示。因此,我们将在下一个视频中仔细看看提示。
总结


本节课中我们一起学习了几种关键的NLP任务:情感分析用于判断文本情感倾向,翻译实现语言转换,零样本分类允许模型对未见过的类别进行分类,而小样本学习则通过少量示例引导模型执行新任务。理解这些任务及其应用方式,是有效利用大语言模型的基础。在接下来的课程中,我们将深入探讨“提示”这一核心概念。
15:提示词



在本节课中,我们将要学习与强大语言模型交互的核心技巧——提示词。提示词是引导模型产生期望输出的关键输入,理解其原理和应用是有效使用大语言模型的基础。
🧠 什么是提示词?
提示词可以被视为向大语言模型提出的输入或查询,目的是引出特定的回应。这里的重点是“引出”,因为你实际上是在尝试从这个“黑盒”模型中诱导出正确的行为。
🔄 指令遵循模型 vs. 基础模型
上一节我们介绍了提示词的基本概念,本节中我们来看看两种不同类型的模型如何与提示词互动。
为了解释什么是提示词,我们先比较一下左侧的基础模型和指令遵循模型。
基础模型在非常通用的文本生成任务上进行预训练。例如,给定蓝色文本,预测序列中的下一个词元,再下一个,或者填充序列中缺失的词元。
而指令遵循模型则经过调优,能够遵循几乎任意的指令或提示。因此,它虽然仍然很通用,但在某种程度上更为具体。
以下是几个例子:
- “给我三个饼干口味的创意。”——大语言模型会返回一个编号列表。
- “写一个关于某物的短篇故事。”——它会返回一个短篇故事。
当然,这些都是简单的例子,但提示词工程实际上可以变得非常严肃,并且我们已经看到了一些例子。
💡 提示词的构成与形式
提示词的形式非常灵活。更普遍地说,这些提示词可以是自然语言句子或问题、代码、上述内容的组合、表情符号,几乎任何文本形式。它们还可以包含来自其他大语言模型查询的输出,这非常强大,因为它允许嵌套或链式调用大语言模型,从而形成复杂且动态的交互。
我们将在课程后面更多地讨论这一点。

📚 提示词工程示例:小样本学习
我们看到了一个更复杂的例子——小样本学习。在这个例子中,一个提示词包含了一条指令和多个示例,这些示例旨在“教导”大语言模型我们期望它在实际查询中做什么。

🏗️ 提示词工程示例:结构化输出提取
提示词工程甚至可以更进一步。LangChain为我生成了这个结构化输出提取的例子。它包含多个部分:顶部是非常高层次的指令(例如,“回答用户查询,并符合以下格式”),一个解释如何理解期望输出格式的示例,该输出模式的规范说明,以及最后的指令“给我讲个笑话”。
现在,这个提示词本身不是一个笑话,尽管它看起来有点复杂。这对于某些模型确实有效,并且它能输出一个结构化格式,然后可以将其输入到下游的数据管道中。这只是提示词和提示词工程功能强大的一个很好的例子。
我们将在下一个视频中深入探讨提示词工程。

📝 总结

本节课中我们一起学习了提示词的核心概念。我们了解到提示词是引导大语言模型的关键输入,可以是自然语言、代码等多种形式。我们比较了基础模型与指令遵循模型在处理提示词上的区别,并通过小样本学习和结构化输出提取的例子,看到了精心设计的提示词如何能引导模型完成复杂、特定的任务。掌握提示词是有效利用大语言模型能力的第一步。
16:提示工程 🎯


在本节课中,我们将要学习提示工程的核心概念与实用技巧。提示工程是一门通过精心设计输入文本来引导大语言模型生成期望输出的技术。我们将探讨如何构建清晰、有效的提示,并了解一些高级技巧与潜在风险。
概述
提示工程是引导大语言模型完成特定任务的关键。一个好的提示需要清晰、具体,并包含必要的指令、上下文和输出格式要求。需要注意的是,提示工程是模型特定的,针对不同模型或不同用例,可能需要不同的提示策略,因此迭代开发至关重要。
构建有效提示的通用技巧
上一节我们介绍了提示工程的重要性,本节中我们来看看如何构建一个有效的提示。就像要求人类完成任务一样,对LLM的指令也需要清晰和具体。
一个优秀的提示通常包含以下几个部分:
- 指令:明确的任务描述。
- 上下文或背景信息:帮助模型理解任务的相关信息。
- 输入或问题:需要模型处理的具体内容。
- 输出类型或格式:对模型输出结果的明确要求。

你应该使用清晰的关键词(如“分类”、“翻译”等)或包含详细指令来描述高级任务。最后,既然是“工程”,就需要采用数据驱动的方法,针对不同的输入样本测试提示的多种变体,以找到平均效果最佳的提示。

提示组件示例回顾
以下是我们在上一视频中看到的LangChain示例,它清晰地展示了提示的各个组成部分:
- 清晰的指令:
You are a comedian. - 上下文/示例:
Here is an example of a joke: ... - 输出格式规范:
The joke should be exactly two sentences long. - 用户查询输入:
Tell me a joke.
提升模型思考质量的技巧
除了构建清晰的提示,我们还可以使用一些技巧来帮助模型进行更深入的“思考”,从而获得更好的答案。
以下是几种有效的技巧:
- 要求模型不虚构内容:你可以明确告诉模型“不要编造信息”,这有助于减少“幻觉”现象。
- 要求模型不假设或探测敏感信息:指示模型避免对未提供的信息做出假设。
- 要求模型逐步推理:使用思维链技巧,例如要求模型“逐步解释如何解决这个问题”或“分步思考”,这通常能显著提升复杂任务的解决效果。
提示格式与安全考量
提示的格式同样重要。使用分隔符(如###、""")来区分指令、上下文和用户输入,有助于模型更好地解析你的意图。同时,要求模型返回结构化输出(如JSON、列表)并提供正确示例,能确保输出的一致性。
在用户交互应用中,用户输入可能被包裹在一个更大的系统提示中。这引出了提示攻击的概念,即通过操纵输入来利用LLM的漏洞。
以下是几种常见的提示攻击类型及其防御思路:
- 提示注入:用户试图让LLM忽略系统指令,转而执行用户输入的恶意指令。防御方法包括在提示末尾重复指令、用随机字符串或标签包裹用户输入,以帮助模型区分。
- 提示泄露:诱导模型泄露其系统提示中的敏感信息(如内部指令、代码名)。
- 越狱:通过重新措辞绕过模型的内容安全策略或审查规则。
与任何计算机安全问题一样,这是一场应用开发者与攻击者之间的持续攻防。右侧的越狱示例如今已因模型更新而失效,这提醒我们在自己的应用中也需要考虑此类安全更新。
如果其他方法都失败,可以考虑更换模型或限制用户输入的长度。
提示工程资源
以下是一些有助于编写提示的指南和工具,有些是AI专用的,有些则通用,它们将是你在后续实验中宝贵的资源:
- OpenAI 提示工程指南
- PromptingGuide.ai
- LearnPrompting.org
- LangChain 提示模板
- Semantic Kernel 提示模板
总结


本节课中我们一起学习了提示工程的核心要点。我们了解到,构建有效的提示需要清晰、具体,并包含指令、上下文、输入和输出格式。我们探讨了通过要求模型逐步推理(思维链)来提升答案质量,并认识了提示格式的重要性以及提示注入、泄露、越狱等安全风险及其缓解策略。记住,提示工程是一个需要根据具体模型和任务进行迭代和实验的过程。
17:总结

在本节课中,我们将回顾模块一“LLM应用”的核心内容,总结关于使用大语言模型构建简单应用的关键知识点。
上一节我们探讨了提示工程的各种技巧,现在让我们对整个模块的学习内容进行总结。
以下是本模块的核心要点总结:
- 广泛的应用场景:大语言模型拥有广泛的应用场景。我们详细讨论了其中几类任务,并将在后续编码实践中进一步接触它们。
- Hugging Face 工具集:Hugging Face 提供了许多自然语言处理组件,以及一个包含可下载模型、数据集和示例的中心枢纽。这些是能够快速入门的强大工具。
- 模型选择策略:为了选择合适的模型,需要考虑你的具体任务、硬性约束(如延迟、成本)、软性约束(如准确性)、模型大小等因素。我们提供了一些建议,但最终需要你根据自身应用需求来决定什么是最重要的。
- 提示工程的重要性:提示工程对于从这些强大模型中生成有用的响应至关重要。存在许多技术和技巧,它部分是一门艺术,部分是一项工程。

本节课中,我们一起学习了大型语言模型在简单应用中的基础。我们了解了其广泛的应用潜力,认识了 Hugging Face 这一重要的工具与资源平台,探讨了如何根据任务与约束条件选择模型,并强调了通过精心设计提示词来引导模型生成理想结果的关键性。这些基础知识为我们后续进行实际编码和开发更复杂的应用奠定了坚实的基础。
18:使用LLM构建应用

在本节课中,我们将快速了解大语言模型的几种主流应用。我们将重点使用开箱即用的开源模型,并借助Hugging Face Hub及其托管的模型来实现。我们还会进行一些简单的提示工程。最后,我们将更详细地探讨Hugging Face API,学习如何配置这些模型管道。
首先,我将安装一个翻译模型所需的库,并运行课堂设置代码。这部分视频我会快速略过。让我们开始了解常见的LLM应用,目标是快速上手。但请注意过程中使用的数据集、模型和API,它们对你未来会很有用。
首先,导入datasets和transformers库,它们是本节的核心工具。
1.1:文本摘要 📝
上一节我们介绍了LLM的概览,本节中我们来看看第一个具体应用:文本摘要。摘要任务有两种形式:抽取式和生成式。我们将在这里进行后者,即生成式摘要。
每次介绍一个任务时,我们都会提供一些背景阅读材料,以及关于数据和模型的信息。
对于本节,我们将使用XSum数据集,它提供了一组BBC文章及其摘要。我们将使用的模型是T5的一个变体,具体来说是拥有6000万参数的小型版本。T5是谷歌推出的一个模型,支持多种任务,如摘要、翻译等,我们将在本笔记本中用它来完成两个任务。
现在加载XSum数据集。我们指定了一个缓存目录,因为我们已经为你预下载了一些数据和模型,并尽可能告诉Hugging Face使用这些预下载的信息。
当我们打印出从datasets加载的内容时,它通常是一个包含多个数据集的DatasetDict。这里包括训练集、验证集和测试集。我们只使用其中一小部分数据,即document和summary列,也就是文章和所谓的“真实摘要”。但请记住,在这个任务以及许多其他LLM应用中,“真实”是非常主观的。
我们可以显示一个数据样本,包括文档和摘要。
接下来加载我们的管道。一般来说,当我们加载一个Hugging Face管道时,会指定一个任务,这有助于告诉Hugging Face如何处理你要加载的模型。你可以指定可选的推理参数,例如希望生成的摘要长度,是否截断长文章等。同样,我们已经预下载了模型。
我们可以传入单篇文章或一批文章。传入单篇文章时,你可以看到输出的摘要。它可能不是最漂亮的摘要,但如果你将其与文章比较,会发现它在表达关键信息方面做得不错。
你可能还会看到关于使用生成配置文件的警告,这是Hugging Face的新建议。我们在笔记本中通过像这样指定配置来保持简单,但当你向生产环境迈进时,请考虑生成文件并遵循此URL获取更多信息。在生产中,你可能最终会使用更低级别的API,我们稍后会介绍。
将其应用于一批文章。我们在10篇文章的样本上运行它,并将结果与原始的“真实”摘要进行比较。你可以稍后并排比较这些结果,以了解模型的效果。
1.2:情感分析 😊😐😠
我们的下一个任务是情感分析。情感分析是一项文本分类任务,用于评估一段文本是积极的、消极的还是其他标签。
对于数据,我们将使用一组诗歌片段。它带有标签:消极、积极、无影响或混合。我们将使用的模型是BERT的微调版本。BERT是一个著名的基座模型,但我们使用的微调版本是一个非常特定的版本,实际上是在这个诗歌数据集上微调的。我承认,使用在这个数据集上微调的模型本质上是在“作弊”,但它清晰地展示了,如果你找到一个在类似任务上微调过的模型,会非常有益。
加载我们的数据集。可以看到它包括来自诗歌的小片段和标签,标签是数字0到3。稍后,我们会将这些数字转换为文本标签,以便更好地与我们的预测和真实标签进行比较。
加载情感分类器管道。任务将是文本分类,这是一个更通用的任务,但你可以看到它适用于这里,我们希望将文本分类为0、1、2或3标签。然后我们可以在诗歌片段上调用它。
这里我们将其与真实数据合并,将标签从数字转换为文本标签并显示它们。当然,你可以看到在这个数据上微调的模型表现得非常好。稍后,你可以尝试使用同一个模型,但输入你自己写的诗句,这会很有趣。
1.3:翻译 🌐
我们的下一个任务是翻译。翻译模型可能为特定的语言对设计,也可能支持两种以上的语言,我们将看看这两种情况。
对于数据,我们只是使用一些硬编码的句子,但我会指出Hugging Face上有翻译数据集。
我们将看的模型首先是,一个非常特定的英语到西班牙语模型,然后又是我们最喜欢的T5小型模型。除了我们刚才谈到的摘要,T5也适用于翻译。
首先,我们加载一个翻译管道,使用那个英语到西班牙语模型,然后我们给它一个英语句子,输出西班牙语。像这样的微调模型如果符合你的任务会非常有用,因为它们非常具体,通常表现得很好。
对于T5,回想一下T5是一个更通用的模型,这里我们说任务是通用的文本到文本生成。它还不知道我们想做翻译,因此当我们调用它时,需要告诉它我们想做什么,例如,翻译英语到法语,输出法语;或者英语到罗马尼亚语,输出罗马尼亚语。
1.4:零样本分类 🎯
我们的下一个任务是零样本分类,有时也称为零样本学习。
这里的想法,正如讲座中回顾的,是取一段文本,将其分类到几个类别或标签之一,但我们从未明确训练模型来预测这些特定类别。
这里有一些背景阅读材料。对于数据,我们只是从XSum数据集中挑选了几篇文章。模型是ALBERT基座模型的微调版本,它被专门微调以在零样本分类等任务上有用。
我们可以将其作为零样本分类任务加载,获取模型。然后为了调用这个模型,我们将其包装在这个categorize_article函数中。这意味着我们不必每次都重写候选标签,这些标签将文章分类为政治、金融等。然后我们漂亮地打印结果。
让我们在这篇关于体育的文章上调用它。零样本管道确实预测最有可能的标签是体育,并且置信度很高。
然后我们传入这篇关于水灾和风暴损害的文章,这里管道认为它是突发新闻。我相信这是最适用的标签,但请注意,这个标签比其他的更通用一些,并且模型在这里的置信度没有那么高。
1.5:少样本学习 ✨
我们的最后一个任务是少样本学习。回想一下,这是你给模型指令、几个查询-响应的示例来说明如何遵循指令,然后是一个新的查询。
这里有一些很好的背景阅读材料。我们在这里并不看特定的任务本身,实际上我们将进行情感分析,然后是一些其他示例任务。因为少样本学习更像是一种技术而非任务,它适用于其他任务。
对于数据,我们只是手动编码了一些示例。我们将使用的模型是GPT-Neo 1.3B。我开始加载它,因为它需要一点时间,它是本笔记本中使用的最大模型。原因是少样本学习通常需要更大、更强大的模型,因为它是一种非常通用的指令遵循任务。
我们将为Hugging Face指定的任务是通用的文本生成。对于大多数这些任务,我们将说只生成10个新标记。这个GPT-Neo模型只是它的13亿参数版本,由Eleuther AI创建。如果你认真对待少样本学习,当然可以考虑他们升级的模型GPT-NeoX,或者其他更通用、更大、更强大的指令遵循或文本生成模型。但为了这个演示和笔记本,我们保持相当小的规模。
在加载时,我将解释我们接下来要做什么。在下面的提示中,我们希望用特殊标记###分隔示例。我们将使用相同的标记来鼓励LLM在回答查询后结束其输出。因此,我们将其指定为序列结束标记。
在这个管道加载后,我们将提取其分词器,然后编码这三个#符号。我们提取ID,那就是我们的序列结束标记ID。每当我们调用这个少样本管道时,我们给它我们的提示,然后我们还指定使用这个作为序列结束标记ID。
我们从一个没有分隔符的简单示例开始,但你稍后会看到我们的假设。模型加载完成了,但在小实例上花了一点时间。然后我们可以用它来调用。
一个非常简单的提示。请注意,在这个提示中,我们给出了指令,但没有给出任何示例。关键点在于,没有任何示例的答案是糟糕的。这不是情感,而是一个随机的陈述。
如果我们添加一个示例会发生什么?这里我们给出一个推文示例,并说情感是中性的。模型返回了“中性”。这不正确,“音乐视频太棒了”应该是积极的,但模型似乎可能更好地理解了我们的意图。
接下来,我们将为每种情感(消极、积极、中性)各给一个示例,然后是我们的查询。确实,现在模型可能理解我们了,它说情感是积极的,这是正确的。这是一个精心挑选的例子,但它很好地展示了,当你给模型更多示例时,它更有可能理解你。这是提示工程的一个很好的例子。
只是为了好玩,让我们展示更多例子。这里我们要求饮料搭配,我们的查询是“我应该喝什么配司康饼?”。这个模型在饮料变体上实际上做得不是很好。它说你该喝可乐配司康饼,也许对某些人来说是的,但这不是刻板印象中的答案。
这是一个少样本管道,询问“给出一个描述某人感觉的词,这里是‘困惑’。建议一个描述,但不要使用原词”。这次它实际上做得很好:“感觉有点不对劲,不确定你在哪里”。有时它给出好答案,有时给出坏答案。
我们将展示的最后一个管道是“根据标题生成书籍摘要”。这些实际上是取自维基百科的标题和描述。查询是关于书籍《蓝火星》。模型当然不知道《蓝火星》是什么,但它会尝试生成一个最多50个标记长的描述。如果你看描述,它实际上相当合理。
这只是刚刚开始接触提示工程,真正聚焦于少样本学习技术。这里链接了一些资源,当然幻灯片中也有。
1.6:Hugging Face API详解 ⚙️
接下来,我们将深入了解Hugging Face API。我们首先想谈谈推理中的搜索和采样,然后是分词器和模型的加载器,即比管道更低级的API。
我们将使用之前提到的XSum样本数据集,包含文章和摘要。
我们在幻灯片中稍微提到了推理搜索和采样,现在让我们更详细地讨论一下。你可能会看到像num_beams、do_sample等参数被指定,这些是推理配置。
LLM通过预测或生成下一个标记,再下一个,依此类推来工作。目标是生成整体高概率的序列,但它在生成时有点“短视”,不知道整个序列的概率。因此,你可以将其视为在这个巨大空间中进行的一种有点短视的搜索。
要进行这种搜索,基本上有两种主要方法:搜索或采样。
基本搜索是贪婪搜索,这是默认设置,选择下一个最可能的单个标记。束搜索扩展了这一点,通过搜索几条序列路径使其不那么贪婪,这通过num_beams参数指定。
采样使我们更具随机性,我们说给定到目前为止生成的标记,我们对下一个标记有一个概率分布,那么为什么不从中采样呢?是的。
Top-K和Top-P采样是将采样限制在最可能标记的技术。极端情况下,这当然就回到了贪婪搜索。但Top-K说将其限制在K个最可能的标记,Top-P说将其限制在最可能的标记,直到概率质量P(介于0和1之间)。
你可以通过do_sample参数在搜索和采样之间切换。我强烈推荐查看这篇博客文章,其中有更多关于搜索和采样的信息。
让我们通过摘要任务来运行几个使用示例。请注意,即使我们改变参数,并非所有答案都会不同,这没关系,在某些情况下是预期的。
首先是贪婪搜索。接下来是束搜索,我们有10个束,实际上得到了相同的答案,但请注意它确实花了更长时间,在某些情况下我们可能会得到更好的答案,换句话说,一个更可能的序列。
或者,我们可以设置do_sample=True来进行采样。这里我们实际上得到了一个略有不同的答案。很难说哪个更好。这是采样,所以它更具随机性,如果我们再次运行它,实际上会看到一个不同的答案。
这里我们进行采样,但我们说进行Top-K采样,也就是说,每次选择下一个标记时,限制在前10个最可能的标记内,然后也进行Top-P采样。这些都是有用的参数,我认为当你为任务选择它们时,请记住这是任务和数据特定的,因此你可能需要进行一些调整和工程来确定什么是最好的。
好的,这涵盖了推理的主要方法。下一节讨论分词器和模型的加载器。我们已经大量使用了管道,现在我们将转向模型和分词器,其理念是这些是更低级别的抽象,允许对更广泛的管道进行更多控制。
我们将遵循这个模式:给定输入文章,对它们进行分词,将模型应用于分词后的数据,并将摘要解码为人类可读的文本。
我们将从使用这些Auto类开始。这些基本上是给定一个预训练模型名称,然后“做正确的事情”,将它们加载到正确的分词器和模型子类中。
这里我们使用上面的T5小型模型。使用AutoTokenizer,调用from_pretrained,给它模型名称,当然我们已经为你下载了它。模型本身也是如此,只是这次我们使用这个用于序列到序列语言模型的AutoModelForSeq2SeqLM。
加载它。现在,我们有了独立的分词器和模型,而不是管道。这是一个通用的分词器和模型。因此,由于我们没有告诉Hugging Face这是一个摘要管道,我们需要知道T5实际上需要一个前缀summarize:,所以我们将把它作为提示添加到每篇文章的开头。
让我们这样做。你可以看到它被添加到了每篇文章的开头,现在我们可以对这些文章进行分词,指定潜在的配置,例如我们希望处理的最大文章长度,如果文章太长则截断,如果太短则填充,以及使用PyTorch张量。
这里,正如我们在幻灯片中提到的,input_ids是文章本身。attention_mask告诉模型,对于这篇文章,忽略文章的后半部分,因为它只是用零填充的。对于这篇文章,我们实际上可能截断了它,因为我们使用了整个编码后的文章。就我们的目的而言,你不需要对注意力掩码了解得更多。
然后我们调用model.generate,传入input_ids、attention_mask和一些推理参数。num_beams,我们进行简单的束搜索,然后我们想要一个长度在0到40个标记之间的摘要。
我们可以打印出来,这些当然仍然是编码后的,所以在下一行我们将忽略编码,因为我们无法阅读它。下一行我们将使用分词器进行批量解码,并且我们说跳过特殊标记,这样它就不会打印出像序列结束标记这样的特殊标记。我们显示这些,它们又是摘要。所以你可以看到,通过将管道分解为独立的分词器和模型,我们如何能够为推理和分词等指定更多参数。我们还可以插入自己的自定义预处理或后处理逻辑。
上面我们使用了Auto类,我想提一下,对于特定的模型架构,Hugging Face提供了针对这些架构的分词器和模型,例如这里的T5。
这与我们刚才用Auto模型看到的工作流程相同,只是我们使用了这些架构特定的类。Auto模型为我们处理了这一点,但这只是为了展示你可以进一步将这些分解为更低级别的API。
我不会讨论所有配置,因为我相信我保持了它们相同。但请注意,有时像这样的模型架构类可能提供超出Auto类所能提供的配置,这是你可能在API中走得更低,并利用其中一些较低级别类的一个原因。
这里又是摘要。
总结 📚

本节课中我们一起学习了多种常见的LLM应用,包括如何快速上手、如何调整推理配置,以及如何使用管道之下的一些较低级别API。我们涵盖了文本摘要、情感分析、翻译、零样本分类和少样本学习等任务,并深入探讨了Hugging Face的推理参数(如搜索与采样)以及如何直接操作分词器和模型。这些知识为你进一步探索和构建自己的LLM应用奠定了基础。
19:嵌入、向量数据库与搜索 🧠

概述
在本节课中,我们将学习如何通过嵌入、向量数据库和搜索技术,显著增强大语言模型的能力。我们将重点探讨一个非常实用的应用场景:基于知识的问答系统。这种技术能让大语言模型利用特定文档和知识库中的信息来回答问题,是许多企业将LLM应用于自身定制数据和内部流程的关键。
在上一模块中,我们概览了LLM的一些简单应用。本节中,我们将深入探讨一个更常见的应用——基于知识的问答。这能让LLM利用一系列文档和知识库中的信息来回答问题,并执行特定领域的任务。对于拥有定制数据集和内部知识的组织而言,这无疑是最常见的实践应用。
基于知识的问答是一个非常合理的常见应用,因为它能有效提升人们的工作效率。这项技术可用于公司内部流程,也可用于面向用户的功能,例如客户支持。知识更新可以非常迅速,只需放入一些新文档,你的应用就能开始回答相关问题。它还能解决仅使用语言模型时面临的许多根本性挑战。例如,仅凭语言模型可能无法准确记住所有知识,但如果你将其置于一个能够查找文档、引用来源并为所有内容提供出处并生成答案的应用中,你就能得到一个更可靠、且用户可自行判断准确性的系统。
我们将要构建的正是这类应用。从高层次看,我们利用LLM来理解和处理知识库中的信息。那么,嵌入、向量数据库和搜索在其中扮演什么角色呢?这些正是我们用来帮助LLM为特定任务找到合适知识并加以利用的工具。
嵌入是从语言模型工作中衍生出的一种强大特征表示方法,它可以将文档和问题映射为数学向量,从而轻松找到相关内容。向量数据库是一种搜索技术,你也可以使用传统的基于关键词的搜索。它们能帮助你在给定任务或输入时搜索相关文档,并且你可以通过训练使其更擅长此道。基本流程是:获取LLM的输入,搜索相关知识,然后将这些知识与原始问题一同输入模型,尝试获得答案。我们将讨论实现此功能所需的基础技术以及该领域的一些算法。
这个领域正在快速发展,但我们认为,尤其是在定制领域,这是将LLM投入实践时最常需要做的事情之一。
核心概念与技术
嵌入是一种将文本数据转化为数学向量的方法。其核心思想是,语义相似的文本在向量空间中的位置也相近。这可以通过一个简单的公式来表示:embedding(text) -> vector。
向量数据库则专门用于存储和高效检索这些向量。给定一个查询向量,它能快速找到最相似的向量(即最相关的文档)。这个过程可以描述为:search(query_vector, vector_database) -> top_k_similar_documents。
以下是构建基于知识的问答系统的主要步骤:
-
文档处理与嵌入:首先,将知识库中的文档分割成适当的片段(例如段落)。然后,使用嵌入模型将每个文本片段转换为一个高维向量。
-
向量存储:将这些向量及其对应的原始文本存储到向量数据库中。
-
查询与检索:当用户提出问题时,使用相同的嵌入模型将问题也转换为向量。接着,在向量数据库中搜索与该问题向量最相似的几个文档片段。
-
答案生成:将检索到的最相关文档片段与原始问题一起,组合成一个提示,输入给大语言模型,让它基于提供的上下文生成最终答案。

总结
本节课我们一起学习了如何利用嵌入、向量数据库和搜索技术来构建强大的基于知识的问答系统。我们了解到,嵌入技术能将文本转化为可计算的向量,向量数据库能高效存储和检索这些向量,而搜索则是连接用户问题与相关知识的关键桥梁。通过将这些技术与大语言模型结合,我们可以克服纯语言模型在知识记忆和事实准确性上的局限,创造出更可靠、更适用于特定领域(如企业内部知识管理或客户支持)的智能应用。这是将LLM投入实际生产环境中最常见且有效的方法之一。
20: 模块概览



在本节课中,我们将学习如何利用嵌入向量、向量数据库和搜索技术来构建问答系统。Ote 提到,大语言模型是推理引擎,其目标是处理信息并将其作为有意义的输出返回给用户。
上一节我们介绍了大语言模型作为推理引擎的基本概念,本节中我们来看看如何具体应用这些技术。
模块学习目标

在本模块结束时,你将能够理解不同的向量搜索策略以及如何评估搜索结果。你还将理解何时应使用向量数据库,何时应使用向量库或向量插件。我们将通过讨论使用这些解决方案时的最佳实践以及如何提升搜索和检索性能来结束本模块。
语言模型获取知识的两种方式
语言模型学习知识主要有两种途径。
第一种是我们在自然语言处理领域多年来常见的方式:从头开始训练模型或对现有模型进行微调。这意味着我们在训练过程中更新模型的权重。我们将在后续模块中更详细地讨论微调。
第二种方式相对较新,与提示工程紧密相关,即将知识作为模型输入传递。在本模块中,我将交替使用“知识”和“上下文”这两个术语,它们在这里含义相同。我们将通过提示词传入上下文,要求语言模型在输入中结合这些上下文。
因此,本模块将涵盖:我们如何找到相关的知识,以及如何在实际的问答系统中结合嵌入向量和向量数据库。
为何要向语言模型传递上下文?
首先,我们需要理解为何要向语言模型传递上下文。如前所述,微调是模型学习知识的另一种方式,通常更适用于专门的任务。这好比你在为两周后的考试学习,可能会忘记一些细节,但总体上对知识有了更好的内化。
另一方面,向模型传递上下文就像开卷考试。它能帮助你更精确,因为你可以随时参考手头的事实。
然而,其缺点是存在上下文长度限制。以 OpenAI 的 GPT-3.5 模型为例,它最多允许 4000 个令牌,大约相当于五页文本。考虑到大多数文档(例如员工手册)很容易超过五页,这个长度并不算长。因此,一个常见的解决方案是传入文档摘要,或者将文档分割成块,这也是我们将在本模块后面讨论的策略。
Anthropic 最新的 Claude 模型可以容纳多达 10 万个令牌的上下文。截至 2023 年 5 月中旬,该功能处于预览阶段。虽然能够传入更长的上下文,但随之而来的是更高的 API 调用成本和更长的模型处理时间。
许多研究人员认为,仅增加上下文长度并不能帮助模型在不同会话间保留信息。并且,与人脑不同,模型会平等对待上下文中的每一条信息。因此,我们可能需要新的模型架构来解决上下文处理问题。
向量搜索与向量数据库的重要性
你在入门课程中见过这张幻灯片。我们讨论向量搜索的根本原因在于,我们需要先将上下文或知识转换为嵌入向量,然后才能进行任何相似性搜索。
我们也看到向量数据库日益流行。如果说 2021 是图数据库之年,那么 2023 年很可能就是向量数据库之年。这是为什么呢?
因为向量数据库不仅对文本用例有用,对其他类型的非结构化数据同样有用,包括图像数据和音频数据。我们将这些图像或音频文件转换为嵌入向量,存储在向量数据库中,并可为各种任务检索它们。在这张幻灯片中,你可以看到任务范围非常广泛,从物体检测、产品搜索、翻译、问答到音乐转录,甚至识别机器故障。
以下是向量数据库的几个用例示例:
- 构建知识库问答系统与查找重复项:通过向量搜索计算向量间的相似度,这对于构建基于知识的问答系统和查找重复项非常有帮助。
- 构建推荐引擎:业界使用向量搜索和向量数据库构建推荐引擎。例如,Spotify 发布的一篇博客文章就介绍了他们如何利用向量数据库,基于用户查询为播客节目构建推荐引擎。
- 异常检测与安全威胁发现:我们也可以使用向量数据库或广义的向量搜索来发现异常和检测安全威胁。
问答系统的工作流程
既然我们已经了解了向量搜索和向量数据库为何有用,接下来让我们看看如何实际实现一个问答系统的工作流程。
一个基于知识的问答系统通常包含两个组件:搜索和检索。但首先,问答系统假设你有一个可以使用的知识库,即下图中红色框内的部分。
因此,我们首先需要将文档知识库转换为嵌入向量,然后将这些嵌入向量通过向量库或向量数据库存储到一个向量索引中。向量索引是一种便于向量搜索过程的数据结构,我们稍后会详细讨论。
我们还将讨论向量库和向量数据库之间的区别。目前,你可以将所有向量存储解决方案视为一个向量存储。
将所有这些文档作为嵌入向量存储在数据库或库中之后,下一步就是允许用户提交查询。
用户输入的任何自然语言查询也必须通过语言模型转换为嵌入向量。
之后,我们现在可以搜索包含文档嵌入向量的向量索引,并返回与用户查询相关的文本。这个确定哪些文档与用户查询相关或相似的步骤,就是工作流程中的搜索组件。
最后,在检索到所有相关文档后,我们可以将这些文档称为上下文或知识。然后,我们将在一个提示词中将此上下文传递给语言模型。这意味着我们的语言模型现在将收到一个由上下文增强的查询,并最终生成一个结合了上下文的文本输出。
因此,整个工作流程被称为搜索与检索增强生成工作流程,因为语言模型生成的输出由我们在搜索过程中检索到的上下文所增强。

总结

本节课中,我们一起学习了构建基于大语言模型的问答系统的基本原理。我们探讨了语言模型获取知识的两种方式,重点分析了通过传递上下文来增强模型能力的优势与限制。我们还介绍了向量搜索和向量数据库的核心概念及其广泛应用场景。最后,我们详细拆解了搜索与检索增强生成工作流程的各个步骤,为后续深入技术细节奠定了基础。
21:向量搜索如何工作 🔍


在本节课中,我们将要学习向量搜索的核心工作原理。我们将探讨向量搜索的两种主要策略,理解如何衡量向量之间的相似性,并深入了解两种流行的向量索引算法:Faiss和HNSW。最后,我们会简要介绍向量数据库如何实现过滤功能。
我们刚刚讨论了搜索和检索增强生成的高层工作流程,现在让我们来谈谈向量搜索。
在向量搜索中,主要有两种策略:精确搜索和近似搜索。
顾名思义,精确搜索意味着你使用一种暴力方法来寻找最近邻。这种方法几乎没有误差空间,这正是传统的K近邻算法所做的。
而近似最近邻搜索,你找到的是不那么精确的最近邻,但你在速度上获得了优势。以下是一些常见的索引算法,我们可以称它们为索引算法,因为这些算法的输出是一种称为向量索引的数据结构。正如我们在前面部分提到的,向量索引帮助你保存所有必要的信息,以进行高效的向量搜索。
在这些算法中,我们可以看到它们要么基于树的方法,要么基于聚类或哈希。我们将介绍其中两种:Faiss和HNSW,它们是向量存储库实现的最流行的两种算法。
但首先,让我们谈谈我们如何实际确定两个向量是否相似?
答案是使用距离或相似性度量,这对你们很多人来说可能不是一个陌生的概念。对于距离度量,我们常见的有L1曼哈顿距离或L2欧几里得距离。欧几里得距离通常是更受欢迎的选择。
因此,你可以看出,当距离度量值越高时,向量的相似性就越低。
另一方面,我们也可以使用余弦相似度来衡量向量之间的相似性。当相似性度量值最高时,意味着你的向量更相似。
同样值得指出的是,当你在归一化的嵌入向量上使用L2距离或余弦相似度时,它们会产生功能上等效的向量排序距离。如果你对此感兴趣,可以在网上搜索数学证明。
稠密嵌入向量通常占用大量空间,减少内存使用的一个常见方法是使用乘积量化来压缩向量,缩写为PQ。
这个花哨的方法PQ,本质上只是减少了字节数,而量化指的是我们如何使用一个更小的向量集合来表示向量。
非常粗略地说,量化意味着你可以将一个数字向下舍入或向上舍入。但在最近邻搜索的背景下,我们从原始的大向量开始,然后将这个大向量分割成子向量段,每个子向量被独立量化,然后映射到最近的质心。
假设第一个子向量最接近第一个质心,即质心1,那么我们将用值1替换向量值。现在你可以开始看到我们如何实际减少字节数:我们存储一个整数值,而不是存储许多浮点数。
我们现在将更深入地讨论向量索引算法。Faiss代表Facebook AI相似性搜索。
它是一种聚类算法,计算查询向量与所有其他点之间的欧几里得距离。正如你可以想象的,随着向量越来越多,计算时间只会增加。
为了优化搜索过程,Faiss利用了所谓的Voronoi单元。它的作用是,不是计算存储中每个向量与查询向量之间的距离,而是首先计算查询向量与质心之间的距离。
一旦它识别出与查询向量最接近的质心,它就会找到存在于同一个Voronoi单元中的所有其他与查询向量相似的向量。这对于稠密向量效果很好,但对于稀疏向量效果不佳。
另一种常见的实现算法是HNSW,它代表分层可导航小世界。它也使用欧几里得距离作为度量,但它不是基于聚类,而是基于邻近图的方法。
这里可能有很多细节,但我们将专注于构成HNSW的主要结构组件。
第一个是我们称之为链表或跳表的结构。在左侧图像中,你会看到当我们从第0层移动到第3层时,我们跳过了越来越多的中间节点或顶点。我们通过从左到右遍历来寻找最近邻,如果我们超过了目标,我们将向下移动到前一层。
但是,如果有太多的节点需要我们构建很多层呢?答案是引入层次结构。让我们看看右上角的图像:我们从预定义的入口点开始,然后遍历图以找到局部最小值,即向量实际上最接近查询向量的位置。
我们刚刚介绍了向量搜索策略,我想强调的是,搜索相似向量的能力实际上并非易事,因为它极大地扩展了我们可能的应用场景。我们不再局限于编写受精确匹配约束的代码。事实上,当我们进行精确匹配时,我们使用的是过滤语句,而众所周知,SQL过滤语句通常不够灵活。
所以,这就是我们接下来要讨论的内容:这些向量数据库或向量存储解决方案如何实际实现过滤?

本节课中我们一起学习了向量搜索的基本原理,包括精确与近似搜索策略、相似性度量方法(如欧几里得距离和余弦相似度),以及两种关键的索引算法Faiss和HNSW的工作原理。我们还了解到向量压缩技术和向量数据库过滤功能的重要性,为后续理解检索增强生成打下了基础。
22:向量数据库中的过滤策略 🔍

在本节课中,我们将要学习向量数据库中一个至关重要的功能:过滤。我们将探讨三种主要的过滤策略,理解它们的工作原理、各自的优缺点以及适用场景。
上一节我们介绍了向量搜索的基本概念,本节中我们来看看如何结合元数据过滤来优化搜索结果。
概述
在向量数据库中进行搜索时,我们常常需要根据特定条件(如品牌、价格、类别)来缩小结果范围。这个过程被称为过滤。实现高效的过滤颇具挑战,不同的向量数据库采用了不同的策略。理解这些策略有助于我们根据应用需求选择合适的方法。
过滤策略详解
过滤策略主要分为三类:查询后过滤、查询中过滤和查询前过滤。一些向量数据库也基于这些类别实现了专有的过滤算法。
查询后过滤
查询后过滤是指在完成向量相似性搜索、找到最邻近的K个结果后,再根据元数据条件进行筛选。
以下是其工作流程:
- 执行向量搜索,获取与查询向量最相似的Top-K个结果。
- 对这K个结果应用元数据过滤器(例如,
studio == "Pixar")。 - 返回满足过滤条件的最终结果。
这种方法的优点是能够充分利用近似最近邻搜索的速度。然而,其缺点是返回的结果数量高度不可预测,甚至可能没有任何结果满足过滤条件。
查询中过滤
查询中过滤是一种同时进行向量相似性计算和元数据过滤的算法。
以下是其核心特点:
- 系统需要同时加载向量数据和用于过滤的标量数据(元数据)。
- 在搜索时,同时计算向量相似度和匹配元数据条件。
这种方法对系统内存要求较高,因为它需要同时处理两种数据类型。当应用大量过滤器时,可能会遇到内存不足的问题。但它非常适合基于行的数据存储格式,因为这种格式需要一次性读入整行数据的所有列。
查询前过滤
查询前过滤是指在执行向量搜索之前,先根据元数据条件限定一个待搜索的数据子集。
以下是其工作方式:
- 应用过滤器,确定一个候选数据集(例如,所有
brand == "Nike"的商品)。 - 仅在这个缩小的数据集内进行向量相似性搜索。
这种方法的缺点是无法利用近似最近邻搜索的速度优势,因为过滤后的数据可能不再支持高效的索引结构,从而需要以暴力计算的方式搜索。因此,其性能通常不如前两种方法。
总结
本节课中我们一起学习了向量数据库中的三种过滤策略。查询后过滤速度最快但结果不可控;查询中过滤能同时保证相关性和准确性,但对资源要求高;查询前过滤逻辑简单但可能牺牲搜索性能。理解这些策略的权衡,对于设计高效、精准的检索系统至关重要。


下一节,我们将更深入地探讨向量存储的相关内容。
23:向量存储库 🗄️

在本节课中,我们将要学习如何与向量进行交互,核心是理解向量存储库的概念、类型及其应用场景。我们将探讨向量数据库、向量库和插件之间的区别,并帮助你根据实际需求做出合适的选择。

上一节我们介绍了向量索引的基本概念,本节中我们来看看如何存储和高效检索这些向量。
我们进入更实际的层面:如何与这些向量交互。答案是使用向量存储库。宽泛地说,当我谈论向量存储库时,它可以包括向量数据库、向量库,以及构建在现有常规数据库之上的插件。
但为什么需要关心向量存储库?为什么不能直接用常规数据库来存储向量?

向量存储库与常规数据库的差异并不太大。具体来说,向量数据库实际上就像一个常规数据库,它继承了完整的数据库特性,例如 CRUD(创建、读取、更新和删除)。
但向量数据库专门用于将非结构化数据存储为向量。事实上,向量存储库的差异化能力在于提供“搜索即服务”。你无需自己实现搜索算法,向量存储库为你提供了开箱即用的搜索功能。
那么向量库或插件呢?我们先谈谈向量库。
向量库确实会为你创建向量索引。正如我们在前几节提到的,向量索引是一种数据结构,能帮助你进行高效的向量搜索。
因此,如果你不想集成一个新的数据库系统,使用一个能为你创建这些向量索引的向量库是完全可行的。
一个向量索引通常包含三个不同的组成部分:
- 一个可选的预处理步骤,通常由用户自己实现,你可能需要对嵌入进行归一化或降低嵌入维度。
- 主要步骤涉及实际的索引算法,例如我们讨论过的 FAISS 和 HNSW。
- 最后一个可选的后处理步骤,你可能希望进一步量化或哈希化你的向量,以优化搜索速度。
对于小型静态数据集,像 FAISS 这样的向量库通常就足够了。
所有向量库都不具备数据库属性。这意味着你不能期望向量库具备向量数据库的属性,例如支持 CRUD、数据复制、或将数据存储到磁盘。你可能需要等待完整的数据导入完成后才能查询。这也意味着,每次你对数据进行更改时,向量索引都必须从头开始完全重建。
因此,是使用向量数据库还是向量库,实际上取决于你的数据变更频率,以及你是否需要向量数据库所附带的全功能数据库属性。
另一方面,也存在一些现有的关系数据库或搜索系统,它们提供向量搜索插件。这些插件通常支持的度量标准或 ANN(近似最近邻)算法选择较少,但即使在接下来的几个月里,我们看到这类插件的向量搜索支持大幅增加,我也不会感到惊讶。
现在,让我们更深入地讨论一下向量数据库的选择。
首先请记住,无论你是否使用向量数据库,都不会影响底层 ANN 算法的速度。这个决定主要基于三个因素:
- 你的数据量有多大?通常,只有当你有数百万或数十亿条记录时,才会看到使用向量数据库的必要性。
- 你实际需要的查询时间(即服务延迟)有多快?
- 正如前面提到的,你是否真的需要全功能的数据库属性?
如果你的数据大部分是静态的,并且不期望频繁更新数据,那么不使用向量数据库通常是一个不错的起点。在这种情况下,你通常可以先从使用向量库开始。
但是,如果你的数据变化很快,先离线计算嵌入,然后将其存储在向量数据库中供后续按需查询,这种方式可能成本更低。这样你也可以避免使用在线模型来动态计算嵌入。
当然,毫不意外,在架构中添加向量数据库的缺点意味着你需要为一项额外的服务付费,并且你还需要学习、集成和维护另一个系统。
如果你有兴趣探索向量数据库,我提供了一些流行选择之间的初步比较。请注意,这里的信息可能会随着时间的推移而演变。

最后,我们以一些最佳实践来结束本节。

本节课中我们一起学习了向量存储库的核心概念。我们区分了向量数据库、向量库和插件,并分析了根据数据量、更新频率和功能需求来选择合适工具的决策框架。理解这些是构建高效大语言模型应用的重要一步。
24:最佳实践


在本节课程中,我们将总结本模块,并围绕使用向量数据库和实现自己的搜索检索系统,介绍一些最佳实践。我们将探讨何时需要使用向量数据库,以及如何通过选择嵌入模型和优化文档存储策略来提升检索性能。
是否总是需要向量数据库?🤔
这是最核心的问题:你是否总是需要使用向量数据库?许多用户可能一直将数据(尤其是非结构化数据)存储在常规数据库中,并且效果良好。如果你属于这种情况,不必急于将向量数据库纳入你的架构。
在LLM的语境下,是否需要向量数据库(无论是专门的向量数据库、库,还是在关系型数据库之上的插件),归根结底取决于:你是否需要进行上下文增强? 向量数据库通过知识扩展了LLM的能力。你可以提供相关的向量查找,从而扩展上下文。这有助于事实回忆,也能缓解我们将在模块5深入探讨的“幻觉”问题。
然而,有些用例可能不需要上下文增强来辅助事实回忆,例如:
- 摘要
- 文本分类(包括情感分析)
- 翻译
对于这些用例,不使用向量数据库通常是安全的。
如何提升检索性能?🚀
为了使用户获得更好的响应,可以从两个高层策略入手:一是嵌入模型的选择,二是文档的存储方式。
关于嵌入模型的建议
以下是关于嵌入模型选择的两个要点。
第一,明智地选择你的嵌入模型。 一个可以自问的代理问题是:你当前的嵌入模型是否在与你数据相似的数据上训练过?如果答案是肯定的,那么你可以继续使用该模型。如果答案是否定的,你有两个选择:
- 寻找并使用另一个预训练的嵌入模型。
- 基于你的数据集训练自己的嵌入模型,或对现有嵌入模型进行微调。
后者在NLP领域已有多年历史,是一种非常成熟的方法。在ChatGPT或聊天机器人热潮之前,我们经常讨论像FastText和Word2Vec这样的词嵌入。
第二,确保你的嵌入空间能够覆盖你的所有数据,包括用户查询。 例如,如果你的数据是关于电影的,而用户查询医学问题,那么搜索检索系统的性能肯定会很差。因此,务必确保向量数据库中的文档包含与查询相关的信息。类似地,如果你希望文档和查询处于相同的嵌入空间(这对于返回相关结果至关重要),请使用相似的模型来为它们建立索引。
关于文档存储策略的建议
在讨论文档存储策略之前,需要说明一点:如何最佳地存储文档尚无明确定论,但我会分享一些供你参考的观点。
在文档存储方面,我们有两种选择:
- 将整个文档作为一个整体存储。
- 将单个文档分块存储。 这意味着我们将一个文档分割成多个块。每个块可以是一个段落、一个章节,或者任何你自定义的单元。这意味着一个文档可以产生多个向量。
你的分块策略可能决定了返回的块与查询本身的相关性。但你还需要考虑,在模型的令牌限制内,你实际上能容纳多少上下文或块?你是否需要将此输出传递给下一个LLM?(将输出传递给另一个LLM是本模块未涉及的内容,我们将在模块三讨论。)
举例来说,如果你有4个文档,总计2000个令牌,你可以选择将每个文档均匀分割成大约500个令牌的块。但要知道,分块策略高度依赖于具体用例。在机器学习中,我们常说模型开发通常是一个迭代过程,你绝对也应该以同样的方式对待分块策略:尝试不同的大小和方法。
你的文档有多长?是单句还是多句?如果一个块只包含一个句子,那么你的嵌入将只关注该特定句子的具体含义。但如果你的块包含多个段落,那么你的嵌入将捕获文本的更广泛主题。你可以按标题、章节或段落进行分割。
你还应考虑用户行为。你能预测用户查询的长度吗?如果查询较长,那么查询嵌入与返回的块更好对齐的几率就更高;但如果查询较短,则往往更精确,此时使用较短的块可能更有意义。
正如我提到的,分块的最佳实践尚无明确定论,但你可以自行阅读一些关于此主题的现有资源。
如何添加防护措施?🛡️
假设我选择了错误的嵌入模型,并且分块策略不佳,我们能否添加一些防护措施来防止静默失败或性能不佳?
对于用户而言,正如我们在模块1中讨论的,在提示词中包含明确的指令会很有帮助。例如,你可以告诉模型如果不知道答案就不要编造。这有助于你了解模型的局限性,而不是依赖不可靠的输出。
对于软件工程师,可以考虑以下几点:
- 添加后备逻辑:例如,如果距离超过某个阈值,你可能需要显示一个通用的响应列表,而不是什么都不显示。回到耐克鞋的例子,如果没有返回耐克鞋,你或许可以显示一个用户可能购买的最受欢迎鞋款的通用列表。
- 处理毒性内容:你可以在上层添加一个基本的毒性分类模型,以防止用户提交攻击性输入。2016年,微软发布了一个名为Tay的聊天机器人,由于用户开始提交种族主义言论,它最终变成了一个非常种族主义的聊天机器人。通过在上层设置一些防护模型,有助于防止聊天机器人的行为偏离你的预期。
- 丢弃攻击性内容:你也可以选择丢弃所有攻击性内容,以避免在这些内容上进行重新训练或微调。
- 配置超时:最后,你还应考虑配置你的向量数据库,使其在查询耗时过长时超时。这可能表明没有找到相似的向量。
总结 📝

本节课我们一起学习了使用向量数据库和构建检索系统的最佳实践。我们探讨了决定是否使用向量数据库的关键因素(上下文增强需求),并学习了通过谨慎选择嵌入模型和优化文档分块策略来提升检索性能的方法。最后,我们还了解了如何为用户和工程师添加防护措施,以应对模型选择或策略不当的情况,确保系统更稳健可靠。
25:总结

在本节课中,我们将对模块二的核心内容进行回顾与总结。我们将梳理关于嵌入向量、向量搜索以及向量数据库的关键概念,并明确它们的适用场景。
在之前的章节中,我们深入探讨了嵌入向量、向量搜索以及向量数据库。现在,让我们快速回顾一下核心要点。
向量存储方案可以包含多种形式。
以下是几种主要的向量存储方案:
- 向量数据库:专门为存储和检索向量数据而设计的数据库。
- 向量库:提供向量操作功能的软件库。
- 关系数据库上的搜索插件:在传统关系型数据库基础上增加向量搜索能力的扩展工具。
向量存储并非适用于所有文本处理场景。它们仅在需要进行上下文增强时才有用,并非所有文本用例都需要此功能。
上一节我们介绍了向量存储的形式,本节中我们来看看向量搜索的本质。
向量搜索的核心是计算向量之间的相似度与距离。这通常通过特定的数学公式来衡量。
以下是衡量向量相似度的常见方法:
- 余弦相似度:
similarity = cos(θ) = (A·B) / (||A|| ||B||),衡量两个向量方向上的差异。 - 欧几里得距离:
distance = √(Σ(A_i - B_i)²),衡量两个向量在空间中的直线距离。 - 点积:
similarity = A·B = Σ(A_i * B_i),在向量已归一化时,其效果与余弦相似度等价。
接下来,我们探讨何时需要考虑使用专门的向量数据库。
如果你在犹豫是否需要为向量使用数据库,可以这样理解:向量数据库本质上是一个具备强大向量搜索能力的常规数据库。
在以下情况下,使用向量数据库是合适的选择:
- 你需要数据库的典型属性(如持久化、事务支持)。
- 你处理的是大规模数据。
- 你对服务延迟有非常严格的要求。
最后,要构建一个高效的知识库检索系统,有几个关键因素不容忽视。
一个优秀的基于知识的搜索检索系统需要精心设计。

以下是构建高效检索系统的关键步骤:
- 选择合适的嵌入模型:根据你的数据类型(如文本、代码、图像)选择最合适的模型来生成向量表示。
- 迭代文档分割策略:文档如何被切分或分块会极大影响检索效果,通常需要多次实验和调整。
至此,本模块的理论讲解部分就结束了。接下来,我们将一起查看一些相关的实践代码。

本节课中我们一起学习了向量存储的多种形式、向量搜索的基本原理,以及向量数据库的适用场景。我们还总结了构建知识检索系统的关键步骤,包括选择嵌入模型和优化文档分割策略。掌握这些概念是有效利用大语言模型进行上下文增强的基础。
26:Notebook 演示第一部分 🧠

在本节课程中,我们将学习如何将文本转换为嵌入向量,将这些向量存储到数据库中,并在此基础上进行搜索。我们将探索两种不同的向量存储解决方案:Faiss(一个向量库)和Chroma DB(一个开源向量数据库)。
概述
上一节我们介绍了搜索与检索的工作流程。现在,我们将深入一个具体的代码示例。本笔记本将演示如何利用Faiss库,将新闻数据集转换为向量,建立索引,并执行相似性搜索。
数据准备

首先,我们来看看将要使用的数据。这是一个名为“news”的数据集,由newsketer团队收集。数据集包含8000多行,每一行代表一篇新闻文章,记录了新闻主题、来源、发布日期、标题、语言以及每篇文章的唯一ID。

# 示例:读取数据集
df = spark.read.parquet(DA.paths.datasets)
print(f"数据集行数: {df.count()}")
以下是数据集的列表示例:
- topic: 新闻主题
- source: 新闻来源
- published_date: 发布日期
- title: 新闻标题
- language: 语言
- id: 文章唯一ID(后续将作为向量ID使用)
认识Faiss
Faiss是一个用于高效相似性搜索和密集向量聚类的库。它执行的是近似最近邻搜索。与暴力搜索相比,近似最近邻搜索能在召回率、延迟、吞吐量和查找时间之间取得更好的平衡。
Faiss的工作流程主要分为两部分:
- 索引构建:将文档通过编码器转换为嵌入向量,并存储在Faiss的向量索引中。
- 查询搜索:将用户查询同样转换为向量,并应用搜索算法返回最相关的结果。
Faiss将索引存储在内存中,因此它非常轻量且易于使用,无需复杂的数据库配置。
代码实现步骤
第一步:安装依赖与设置环境
运行笔记本中的初始设置单元,安装必要的库并配置环境变量。这个过程可能需要一些时间。
第二步:加载与准备数据
我们使用sentence-transformers库来生成文本嵌入。这是一个用于生成最先进句子、文本嵌入的Python框架。为了加速演示,我们只使用数据集的前1000行。
以下函数将输入文本转换为sentence-transformers接受的InputExample数据结构。
from sentence_transformers import InputExample
# 创建InputExample列表
train_examples = [InputExample(texts=[text]) for text in texts_list]
第三步:将文本向量化
这是核心步骤,我们调用编码器模型将所有文本转换为嵌入向量。
from sentence_transformers import SentenceTransformer
# 加载模型并编码
model = SentenceTransformer('all-MiniLM-L6-v2', cache_folder='./cache')
document_embeddings = model.encode(train_examples, convert_to_numpy=True)
编码后,我们得到1000个向量,每个向量的维度是384。
第四步:构建Faiss索引
这是主要工作发生的地方。我们将向量传递给Faiss进行索引。
首先,收集所有新闻文章的ID作为向量ID。
vector_ids = df.index.values.astype('int64')
接着,对嵌入向量进行归一化处理。归一化到单位长度后,向量间的点积就等于余弦相似度,这有利于后续的相似性计算。
然后,我们创建Faiss索引。这里使用了IndexFlatIP索引类型。
IP代表内积。该索引旨在最大化查询向量与检索项之间的内积。Flat表示没有使用向量压缩,存储大小与原数据集相同。它常作为评估其他压缩索引算法的基线。
import faiss
dimension = document_embeddings.shape[1]
index = faiss.IndexFlatIP(dimension) # 创建内积索引
但是,直接使用IndexFlatIP无法直接添加我们自定义的ID。因此,我们需要使用IndexIDMap来建立向量与其ID之间的映射。这样,即使删除某些向量,ID也不会错乱。
index = faiss.IndexIDMap(index) # 包装索引以支持ID映射
index.add_with_ids(document_embeddings, vector_ids) # 添加向量及对应ID
至此,我们完成了将数据转换为向量并存入Faiss索引的整个流程。
第五步:执行搜索
现在,我们可以对索引进行搜索。注意,查询文本需要经过与文档相同的预处理步骤(编码和归一化),以确保它们在同一个嵌入空间中。
以下函数实现了搜索Top-K最近邻的功能。
def search(query_text, top_k=5):
# 编码查询文本
query_embedding = model.encode([query_text], convert_to_numpy=True)
# 在索引中搜索
distances, indices = index.search(query_embedding, top_k)
return distances, indices
让我们尝试搜索与“动物”相关的新闻。
query = “animals”
distances, indices = search(query)
# 根据返回的索引ID,从原数据集中查找对应的新闻标题
results = df.iloc[indices[0]]
print(results[['title', 'topic']])
执行后,我们成功检索到了与猫、动物传染病、宠物狗等相关的新文章。这证明我们的向量搜索系统工作正常。
总结
本节课中,我们一起学习了如何使用Faiss库构建一个基础的文本向量搜索系统。我们完成了从数据准备、文本向量化、构建Faiss索引到执行相似性搜索的全过程。目前,我们仅实现了检索相关上下文的功能。

在下一部分,我们将更进一步,结合Chroma DB和大语言模型,探索如何利用检索到的结果来增强文本生成,实现检索增强生成。
27:Notebook 演示第二部分

概述
在本节中,我们将学习如何使用 Chroma DB 作为向量数据库,来构建一个检索增强生成系统。我们将涵盖从数据加载、向量化存储到查询和结合大语言模型生成答案的完整流程。
设置 Chroma DB 客户端
首先,我们需要初始化 Chroma DB 客户端。默认情况下,客户端以非持久化模式运行,这意味着所有数据都是临时的,便于快速原型开发。
import chromadb
client = chromadb.Client()
如果你希望数据持久化,以便后续重用,可以在初始化时指定一个存储目录。
client = chromadb.PersistentClient(path="./chroma_db_data")
通过传递 path 参数,数据将在会话结束时自动保存,并在下次启动时自动加载。
理解集合
上一节我们介绍了客户端设置,本节中我们来看看 Chroma DB 的核心概念:集合。集合类似于一个向量索引,用于存储一组文档及其嵌入向量和元数据。
在下一个单元格中,我们将定义一个集合。由于我们的数据集是关于新闻文章的,因此将其命名为 my_news。
collection_name = "my_news"
请注意,Chroma DB 在内部将集合名称用于 URL,因此命名需遵循 URL 的通用规则。详细信息可查阅官方文档。
以下是创建集合的步骤。如果同名的集合已存在,我们会先删除它,然后创建一个新的。
if collection_name in [col.name for col in client.list_collections()]:
client.delete_collection(name=collection_name)
collection = client.create_collection(name=collection_name)
在 create_collection 方法中,你还可以传入可选参数,例如自定义距离度量方法。
向集合添加数据
现在我们已经创建了集合,接下来需要向其中添加数据。首先,让我们回顾一下数据集的结构。
以下是数据预览:
# 假设 df 是我们的新闻数据 DataFrame
print(df.head())
数据包含标题、语言、主题和发布日期等字段。
以下是将文档添加到集合的方法。Chroma DB 的一个优点是它能自动处理文本存储、分词和嵌入向量生成。
documents = df[‘text’].tolist() # 假设 ‘text’ 列包含新闻内容
metadatas = [{"topic": topic} for topic in df[‘topic’].tolist()] # 将主题作为元数据
ids = [f"id{i}" for i in range(len(documents))] # 为每篇文章生成唯一ID
collection.add(
documents=documents,
metadatas=metadatas,
ids=ids
)
如果你想使用自己的嵌入模型,而不是 Chroma DB 的默认模型,可以通过 embeddings 参数传入自定义的嵌入向量。
我们添加元数据的原因在于,后续可以基于这些元数据对查询结果进行过滤。
运行此单元格,将数据集中的所有文档正式添加到集合中。
查询相关文档
数据添加完成后,我们现在可以针对感兴趣的主题查询相关文档了。
例如,如果我们想查找关于“太空”的文章,可以执行以下查询:
query_text = "space"
n_results = 10
results = collection.query(
query_texts=[query_text],
n_results=n_results
)
查询结果将返回最相关的文档ID、文档内容、元数据以及相似度距离分数。从结果中,我们可以看到关于NASA、火星生命探索等主题的文章,且主题元数据集中在“科技”和“科学”类别,这符合预期。
利用数据库特性:过滤
我们之前提到,过滤功能并非所有向量库都支持,但它是向量数据库的一个重要特性。Chroma DB 支持在查询时添加过滤条件。
例如,我们可以在查询“太空”时,只返回主题为“科学”的文章:
results = collection.query(
query_texts=[query_text],
n_results=n_results,
where={"topic": "science"} # 添加过滤条件
)
运行后,你将看到所有结果都来自“科学”主题,这证明了过滤功能的有效性。
利用数据库特性:CRUD操作
向量数据库另一个优势是支持完整的CRUD操作。以下是一些示例。
删除操作:我们可以根据ID从集合中删除特定文档。
collection.delete(ids=[“id0”])
更新操作:我们可以更新特定文档的元数据。
collection.update(
ids=[“id2”],
metadatas=[{“topic”: “technology”}] # 将主题从“科学”改为“科技”
)
运行后,可以验证ID为“id2”的文档元数据已成功更新。
结合大语言模型生成答案
最后,我们将把检索到的上下文信息输入给一个大语言模型,让它生成基于这些信息的答案。这里我们使用开源的 GPT-2 模型。
首先,加载文本生成管道:
from transformers import pipeline
generator = pipeline(‘text-generation’, model=‘gpt2’)
接下来,构建提示模板。提示工程是一门艺术,没有固定答案,鼓励大家多尝试。
context = “\n”.join([doc for doc in results[‘documents’][0]]) # 将检索到的文档作为上下文
question = “What is the latest news on space development?”
prompt_template = f”””基于以下上下文信息回答问题。
上下文:{context}
问题:{question}
答案:”””
generated_text = generator(prompt_template, max_length=200)[0][‘generated_text’]
print(generated_text)
模型将根据我们提供的关于太空发展的新闻上下文,生成一个连贯的答案。系统会打印出提供的上下文、用户问题以及模型生成的新回答。

总结
恭喜!在本节课中,我们一起完成了第一个检索增强生成系统的实现。我们学习了如何设置 Chroma DB 向量数据库、创建集合、添加和查询数据,并利用其过滤和CRUD功能。最后,我们将检索到的上下文与大语言模型结合,生成了基于知识的答案。整个过程最耗时的部分可能是设计有效的提示词,这需要不断的实验和优化。现在,你可以尝试将这套方法应用到其他数据集上。
28:Notebook Demo - 使用Pinecone向量数据库 🗄️
在本节课程中,我们将学习如何使用一个名为Pinecone的云端向量数据库。我们将通过两种不同的方法,将新闻文章数据转换为向量并存储到Pinecone中,然后进行相似性搜索查询。

概述
Pinecone是一个基于云的向量数据库解决方案,它提供了简单且可扩展的相似性搜索功能。在本教程中,我们将演示如何设置Pinecone,生成文本嵌入向量,并将数据写入Pinecone索引,最后执行查询以检索相关信息。
环境准备
在开始之前,请确保安装以下依赖项:
- Pinecone客户端库:
pinecone-client - Spark连接器JAR文件:需要将此文件附加到您的Databricks集群上。
如果您需要更详细的指导,请参考相关文档。您可以暂停视频,花时间完成这些设置。
如果环境已就绪,您可以安装Pinecone客户端并运行课堂设置脚本。同样,您可以暂停视频,待课堂环境准备完成后继续。
设置Pinecone免费账户
课堂设置脚本运行完毕后,我们现在可以设置Pinecone免费账户。
- 访问Pinecone主页。
- 点击右上角注册一个免费账户。
- 进入控制台后,导航到“API Keys”部分。
- 复制两个值:环境值和API密钥。
- 将这两个值填入下面标记为“Fill in”的单元格中。
就我而言,我将从Databricks的Secret Scope中获取我的Pinecone凭证。
然后,使用以下代码初始化Pinecone客户端:
import pinecone
pinecone.init(api_key="YOUR_API_KEY", environment="YOUR_ENVIRONMENT")
读取数据
接下来,我们将读入本笔记本将使用的数据框。这些数据与课程中关于新闻文章的讲座中使用的数据相同。
生成嵌入向量并写入Pinecone
对于Pinecone,我们需要先生成嵌入向量,然后才能将其保存到Pinecone索引中。我们将介绍两种方法。
方法一:使用Pandas数据框
第一种方法是使用Pandas数据框。这种方法适用于应用单节点嵌入模型,然后分批将数据写入Pinecone。这个过程也可以称为“Upsert”。
我们将使用数据框的一个子集,以加快笔记本的执行速度。
如果您之前使用过Pinecone,需要先删除现有的索引,然后才能创建新的Pinecone索引,因为Pinecone免费层只允许一个索引。
与课程和书本中一样,我们将使用sentence-transformers库。我会将其缓存到一个文件夹。
下面的CMD 18包含了删除已存在索引的命令。
接下来,我们终于可以创建Pinecone索引了。在创建步骤中,我们将指定:
- 索引名称:使用上面指定的名称。
- 嵌入维度:从模型本身获取。
- 相似性度量(可选):这里我们坚持使用余弦相似度。
index_name = "news-articles"
dimension = model.get_sentence_embedding_dimension()
pinecone.create_index(name=index_name, dimension=dimension, metric="cosine")
然后,我们可以建立与Pinecone索引的连接。这一步可能需要长达三分钟,所以如果您看到此命令运行时间较长,请不要担心,您没有被卡住。
写入数据到Pinecone索引
现在让我们看看下一个单元格。一旦索引创建完成,我们就可以将数据写入Pinecone索引。
由于我们使用的是Pandas数据框,我们将循环遍历数据框中的记录批次。您会发现这个工作流程与讲座笔记本中的没有太大不同:
- 首先为每篇新闻文章创建一个ID列表。
- 提供每篇新闻文章的元数据。
- 为我们拥有的文章生成嵌入向量。
- 最后将它们插入到Pinecone索引中。
您会看到Pinecone索引中总共有1000个向量,因为我们只向Pinecone提供了1000条记录。
查询索引
现在所有向量都可用,我们可以直接查询索引。您会看到我写了一个关于“fish”的查询。查询过程也遵循相同的步骤:查询首先被转换为向量,然后可以提交给Pinecone以检索任何相关的上下文。这里我们只查看前三个最近的邻居。
确实,我们看到了与“fish”相关的结果被返回,例如关于大规模鱼类死亡、鲨鱼和蚯蚓的新闻。这可能表明,在我们拥有的所有一千篇文章中,毕竟没有那么多关于鱼的新闻文章。
方法二:使用Spark数据框与Pandas UDF
第二种方法是坚持使用Spark数据框,但使用Pandas UDF来处理数据框,将文本转换为向量,最后使用Spark直接将其写入Pinecone。
Pandas UDF是一种向量化UDF,允许您一次对一批数据应用函数。如果您有兴趣阅读更多关于Pandas UDF的信息,我邀请您查看我在这个Markdown单元格中链接的文档。
通常,这是一种非常高效的使用Spark的方式,因为您能够在Pandas UDF中保留您的Pandas语法,同时允许Spark将函数分布到整个数据框。
例如,在这个单元格中,我们使用了一个Pandas UDF,我们加载一个模型,然后对于发送到Spark的每一批数据,它将把文本转换为嵌入向量。
在下面的单元格中,我们将把我们的Pandas UDF函数传递给Spark数据框。正如您在这里看到的,这是我们定义的Pandas UDF函数,然后我们还提供了需要转换的列名,接着我们将此列重命名为“vector”。
我们另外构造了两个Pinecone期望的列,即namespace和metadata。在namespace列中,我们将放入我们使用的转换器类型;在metadata中,我们将提供主题信息。
现在数据框已准备就绪,我们可以检查结果。我们看到有四个不同的列:ID、嵌入向量、namespace列和最后的metadata列。
重新创建索引并写入数据
现在我们将重复之前在方法一中做过的步骤:删除现有索引并重新创建索引。如果您还记得,这一步可能需要长达三分钟。
成功连接到Pinecone索引后,我们现在可以继续将数据写入Pinecone索引。这里我们使用Spark数据框写入器方法。您可以看到,我们不再分批观察数据,而是传入整个Spark数据框。
您还需要提供连接Pinecone的凭证,所以请继续运行该单元格。
另外请注意,这对您来说非常重要:您实际上需要有一个Spark Pinecone连接器。如果您的集群上没有准备好,那么这个单元格将会失败。
总结

本节课中,我们一起学习了如何使用Pinecone向量数据库。我们介绍了两种将数据嵌入并存储到Pinecone的方法:一种是使用Pandas数据框进行分批处理,另一种是利用Spark数据框结合Pandas UDF进行分布式处理。两种方法最终都实现了将文本转换为向量并存入索引,以及通过向量相似性进行查询检索的核心功能。这为我们构建基于大语言模型的语义搜索应用提供了重要的数据存储和检索基础。
29:Notebook 演示 Weaviate 🧠

在本节课程中,我们将学习如何使用开源的向量数据库 Weaviate。我们将完成从环境配置、数据写入到向量查询的完整流程,并了解如何结合 OpenAI 的嵌入模型进行语义搜索。
概述
我们将使用 Weaviate 作为开源向量数据库。Weaviate 也提供由公司托管的商业版本。它提供了许多自定义选项,例如是否采用产品量化技术。
为了让本笔记本能够端到端运行,你需要安装 Weaviate 及其 Spark 连接器的 JAR 文件。
环境设置
以下是设置环境的步骤。
首先,运行 pip install 语句来安装必要的 Python 包,并运行课堂设置脚本。
同时,请记得安装 Spark 连接器的 JAR 文件,并将其上传到你的 Databricks 集群。

你可以暂停视频,在准备就绪后继续。
现在,课堂设置脚本已经运行完毕。

我们可以开始设置 Weaviate 网络。

首先,你需要访问 Weaviate 官网。
然后点击“免费开始”。点击后,你应会被自动引导至控制台页面。


如果你之前没有使用过 Weaviate,可以点击“在此注册”。
接着,点击“创建集群”并选择“免费沙盒”。
为你自己的集群命名。为简化操作,请将“启用身份验证”选项切换为“否”。
然后点击“创建”。点击创建后,Weaviate 会自动为你实例化一个集群 URL,你将在本笔记本中用到这个 URL。
配置 API 密钥
在本笔记本中,我们还将使用 OpenAI 的嵌入模型。
因此,你需要一个 OpenAI 的令牌。如果你之前没有使用过 OpenAI,请按照笔记本中的步骤创建一个账户并生成一个 OpenAI API 密钥。
OpenAI 没有免费选项,但会提供 5 美元的免费额度。一旦用尽这 5 美元额度,系统会要求你添加付款方式,之后将按令牌使用量收费。
请务必保管好你的 OpenAI API 密钥,因为如果他人获得此密钥,他们也能将使用费用记到你的账户上。
请花几分钟时间设置好 Weaviate 和 OpenAI,然后将各自的 API 密钥和网络 URL 填写到下方标记为“填写”的单元格中。
我将直接从我的 Databricks 学堂获取我的 OpenAI API 密钥和 Weaviate 网络 URL。
然后,我可以通过在附加头部参数中指定我的 OpenAI API 密钥来实例化我的 Weaviate 客户端。
定义数据模式
在本笔记本中,我们将使用与讲座笔记本中相同的数据集。
这是一个由新闻团队收集的新闻主题数据集,我们将把它存储到 Weaviate 数据库中。
为此,我们首先需要定义一个模式。在 Weaviate 中,这意味着我们将提供更多关于要保存到向量索引中的数据的信息。
默认情况下,Weaviate 要求类名首字母大写,这就是我们将“news”首字母大写的原因。
在类对象本身,我们将传入类名、数据集的描述、我们实际要摄取或保存到向量索引的列,以及我们在索引算法中要使用的文本向量化模型。
请运行此单元格。如果之前创建过“News”类,我们将删除它并重新创建。
如果你好奇模式的实际样子,可以运行此单元格,然后查看我们的类对象内部具体是什么。
不出所料,我们在这里看到了 text2vec-openai 模型,即 ada 模型,作为我们的向量化器。因为我们在上面的类对象中指定了它。
写入数据到 Weaviate
然后,我们最终可以将数据写入 Weaviate。
你可以看到,这里我们实际使用了 Spark 连接器的 JAR 文件。因此,如果你没有下载该文件并上传到你的 Databricks 集群,此单元格肯定会失败。你还需要有效的 Weaviate 网络 URL 和有效的 OpenAI API 密钥。
你可能已经在 Weaviate 文档中看到,Weaviate 网络会在 14 天后过期。因此,如果你在 14 天后重新访问此笔记本,需要指定并创建一个新的 Weaviate 网络。
验证数据写入
要检查数据是否确实已写入,你有两种方法。
第一种是将此 URL 复制粘贴到你的浏览器中,然后将其中的部分替换为你自己的 Weaviate 集群 URL。
或者,你可以运行以下单元格,然后检查结果是否非空。
在这里,我确实看到了每篇新闻文章的各种不同主题。
因此,我可以验证我的数据已成功写入 Weaviate。
执行查询搜索
工作流程的下一个组成部分是实际执行查询搜索。
在我的查询搜索中,我可以首先可选地指定一个 where 过滤器,传入过滤语句。
这里,我只对主题等于“科学”的任何文章感兴趣。
我还将传入一个关于“蝗虫”的查询。
你将看到,我们在这里使用 nearText API 来返回与“蝗虫”相关信息最接近的两个最近邻。
或者,你也可以选择以嵌入向量的形式提供查询。
这就是我们将在下一个单元格中要做的。因此,我们将使用 nearVector 而不是 nearText。
在这种情况下,我们将使用完全相同的模型,即 OpenAI 的 ada 模型。然后,我不是直接将“蝗虫”文本传入查询,而是首先将我的“蝗虫”文本转换为嵌入向量,然后将“蝗虫”的嵌入向量直接传入查询。
因此,你将看到,我这里的 nearVector 包含了“蝗虫”的嵌入向量。
我也可以选择指定距离度量。
如果你看到错误,请重新运行。因为可能是与 OpenAI 的连接出现了问题。
在这里,你将看到我们得到了完全相同的结果。这并不奇怪,因为我们使用的是 OpenAI 提供的完全相同的嵌入模型。
我们只是在提供查询的方式上有所不同。因此,在这个单元格中,我们提供的是向量;而在上面的单元格中,我们提供的是“蝗虫”文本,它在底层也使用了相同的文本向量化器,因为这是我们在上面的类对象中指定的。
总结

本节课我们一起完成了使用 Weaviate 和 OpenAI 的初步实践。我们学习了如何设置环境、定义数据模式、将数据写入向量数据库,以及执行基于文本和向量的语义搜索查询。这为构建更复杂的基于大语言模型的应用程序奠定了基础。
30:多阶段推理

概述
在本节课中,我们将要学习如何利用大语言模型构建更复杂的应用程序,核心在于通过多阶段推理来提升模型的表现。我们将从提示工程开始,探讨如何设计更精妙的提示,然后介绍链式提示的概念,以及如何利用开源框架构建模块化、可维护的LLM应用。最后,我们会简要了解LLM智能体及其使用外部工具的能力。
3.1 引言 🧠
欢迎来到我们的第三个模块:多阶段推理。这是人们利用大语言模型正在开发的一项非常激动人心的能力。
本质上,它是通过对语言模型进行多次独立的调用来构建更复杂的应用程序。
在开始本模块之前,我们先回顾一下之前的内容。上一模块我们介绍了基础提示工程,本节中我们将深入探讨如何构建更复杂的提示,引导模型以某种方式进行“推理”,从而产生更好的答案。
我们将讨论的一个常用工具是提示模板。此外,还有各种技术,例如思维链,可以利用模型的统计能力引导其产生你期望的输出。
接下来,我们将转向链式提示的概念。这允许你在LLM应用程序中创建擅长特定子任务的步骤,并将它们组合成一个更大的整体,以更好地处理复杂任务。
我们将探讨这种方法如何帮助分解问题,使其比仅仅依赖一个庞大而笼统的提示并希望它解决所有问题要容易得多。
我们还将讨论该领域的开源框架,例如 Langchain。这种链式化和模块化应用程序的理念,非常类似于软件工程,使得人们能够使用语言模型构建出功能复杂、同时仍可维护、可控且高质量的应用程序。
我们也会简要涉及LLM智能体领域。这是一种能够使用外部工具的LLM应用程序。模型不仅可以生成文本作为答案,它生成的文本实际上可以是对某种工具的API调用。这样,你就可以让模型利用这些工具来完成仅凭自身知识无法做到的事情。
这种模式也非常契合我们讨论的训练范式。
此外,我们将讨论能够自主发现使用哪些工具以及如何操作的自主LLM系统。
多阶段推理这个领域也与我作为一名研究者的兴趣密切相关。我们在斯坦福大学实际上有一个名为“演示、搜索与预演”或 DSP 的项目,该项目致力于研究如何构建由多次LLM调用组成的、高度可靠的流程,并且能够利用数据自动改进这些流程。
这不仅是LLM领域发展最快的方向之一,而且功能非常强大。让你的LLM能够接入其他系统,并与其他软件协同完成酷炫的任务,这一点至关重要。
总结

本节课中,我们一起学习了多阶段推理的基本概念。我们了解到,通过设计精妙的提示、将任务分解为链式步骤、并利用框架进行模块化开发,可以构建出强大且可靠的LLM应用程序。我们还初步认识了能够使用外部工具的LLM智能体。掌握这些方法,是将LLM能力应用于复杂现实场景的关键。
31:多阶段推理模块概述 🧠

在本模块中,我们将学习如何将之前介绍的大语言模型(LLM)与向量数据库等技术结合起来,构建更强大、更复杂的应用程序。我们将使用 LangChain 等工具来创建模块化的 LLM 工作流。
回顾与引入

在前两个模块中,我们学习了如何从 Hugging Face 等平台下载大语言模型来解决各种 NLP 任务,以及如何将数据转换为向量格式并使用向量数据库进行相似性搜索。
你可能会想,如何将这两个功能结合起来,从而真正增强我作为开发者所能构建的应用程序?在本模块中,我们将向你展示如何利用现有的各种工具来实现这一目标。
模块学习目标

在本模块结束时,你将能够:
- 描述使用 LangChain 等工具的 LLM 管道流程。
- 使用 LangChain 构建涉及来自不同提供商(包括 OpenAI 和 Hugging Face)的 LLM 的管道。
- 使用智能体(Agents)构建复杂的逻辑流程模式,这些智能体将 LLM 作为中央大脑,并利用不同的工具来解决给定的任务。这些工具可能是网络搜索、Python 编程环境等。

现有 LLM 的局限性
在开始之前,请思考一下我们目前使用过的 LLM 存在的一些局限性。
回想我们在模块 1 中所见,LLM 在解决传统 NLP 任务方面表现出色。例如,给它一个摘要任务,它能出色完成;要求它翻译文本,它几乎能完美执行;零样本分类任务,根据模型的训练情况,它也能处理得很好。LLM 在这些方面非常强大。
然而,我们考虑的大多数工作流程并不仅仅是这种简单的输入-输出响应。通常,在构建应用程序时,LLM 只是我们设计的端到端应用程序整个工作流程中的一个组成部分。
因此,我们需要思考如何将 LLM 与代码的其他部分无缝连接起来,并且如果需要更换或添加一个 LLM,不会破坏整个端到端系统。我们当前讨论的多阶段推理模块的目标,就是向你展示如何构建这些工具,使其模块化,以便你可以取出一个 LLM 并放入另一个不同的模型。
案例分析:摘要与情感分析
让我们考虑一个例子:我们想要总结一篇文章并获取其情感倾向,这并不是一个奇怪或荒谬的任务。
如果我们思考如何仅用一个 LLM 来处理这个问题,我们可以给它一堆不同的文章,要求它进行总结,然后获取情感倾向。这对于一个模型一次性处理来说任务相当繁重。
一个更好的策略可能是每次处理一篇文章,将其输入一个用于摘要的 LLM,然后将该摘要的输出再输入一个用于情感分析的 LLM。
从概念上讲,我们拥有完成此任务所需的所有工具,但如何以编程方式实际实现呢?
如果考虑让一个 LLM 完成所有这些任务的问题,我们会遇到各种麻烦:需要一个非常庞大的 LLM 才能同时进行摘要和情感分析,这是一项相当复杂的任务;我们还需要担心,如果在提示词中一次性输入所有文章,很快就会超出 LLM 设计的输入序列长度限制。
因此,我们需要做的是将每篇文章分开,逐一处理。收集摘要 LLM 的输出,并将这些摘要作为我们将要创建的情感分析 LLM 的输入。这样,这个框架就变成了一个可重用的工具,我们可以不断输入新文章,它就能逐步生成这个应用程序的不同步骤。
聚焦首个任务
所以,让我们专注于第一个任务:我们不会将所有文章作为一个巨大的提示词输入,而是逐一输入。我们需要确保有一种系统化的方法来抽象出每篇文章,以便我们可以将它们作为变量输入。
这正是我们将在下一个视频中探讨的内容,届时我们将研究如何将提示词和 LLM 链接在一起。
总结

本节课我们一起学习了多阶段推理模块的概述。我们认识到,虽然单个 LLM 能力强大,但在构建复杂应用时,需要将其模块化并与其他组件(如其他 LLM 或工具)串联起来。通过引入链式(Chain)和智能体(Agent)等概念,我们可以构建更灵活、更强大的工作流,以克服单一模型的局限性,例如处理长文本和复杂多步任务。下一节,我们将开始学习如何具体实现这种链式结构。
32:提示工程

在本节中,我们将学习提示工程的核心概念与实践。一个精心设计的提示能够引导大语言模型生成高质量的响应,而一个设计不佳的提示则可能无法充分发挥模型的潜力。我们将通过一个具体的文章摘要任务,系统地学习如何构建有效的提示。
提示工程的重要性
上一节我们介绍了多阶段推理的概念,本节中我们来看看如何通过提示工程来优化大语言模型的使用。一个编写良好的提示不仅能有效引导模型,还能节省大量精力,并且可以在团队和社区中共享和模块化。
构建提示模板:逐步指南

以下是构建一个用于文章摘要任务的提示模板的步骤。我们将使用一个摘要模型来处理文章,其输出后续将用于情感分析。
首先,我们需要创建一个摘要提示模板。我们通过逐步构建的方式来完成它。
- 任务描述:我们告诉模型“总结以下文章,并密切关注情感性短语”。第二个子句是为了确保模型在摘要时关注情感色彩,而不仅仅是事实信息。
- 定义输入变量:我们使用花括号
{}来定义一个变量,后续将输入具体的文章文本。例如:{article}。 - 指定输出格式:我们以“摘要:”作为结尾,提示模型开始生成摘要内容。这暗示我们使用的是生成式大语言模型,而非我们在入门部分讨论过的分类模型。
综合以上步骤,一个基础的提示模板可能如下所示:
总结以下文章,并密切关注情感性短语:
{article}
摘要:
代码实现:创建与使用提示
接下来,我们将上述模板转化为代码。这里使用了 LangChain 的语法,但其他提示库也有类似的结构。
我们首先创建提示模板的实例。

# 导入必要的库(假设使用LangChain)
from langchain import PromptTemplate
# 定义提示模板字符串
template_string = """总结以下文章,并密切关注情感性短语:
{article}
摘要:"""
# 创建PromptTemplate对象
summary_prompt = PromptTemplate(
input_variables=["article"], # 定义输入变量名
template=template_string # 指定模板字符串
)
然后,我们可以为具体的文章生成格式化的提示。

# 假设我们有一篇具体的文章
my_article = "这里是需要被总结的文章全文..."
# 使用模板和文章内容生成最终的提示
formatted_prompt = summary_prompt.format(article=my_article)
# 现在可以将 formatted_prompt 传递给摘要大语言模型以生成摘要
# generated_summary = summary_llm(formatted_prompt)

通过循环处理所有文章,我们可以为每一篇都创建摘要。这解决了我们两阶段问题中的第一部分:将文章逐一输入摘要模型。
从提示链到模型链
到目前为止,我们所做的是将一个提示模板链接到一个大语言模型。这是通过提示模板完成的。现在,我们需要考虑如何将摘要模型的输出,作为情感分析大语言模型的输入。这意味着我们需要将一个大型语言模型的输出与另一个大型语言模型链接起来。
因此,我们将在下一个视频中深入探讨 LLM 链 的世界。
本节总结


本节课中我们一起学习了提示工程的基础。我们了解到精心设计提示的重要性,并逐步实践了如何为一个文章摘要任务构建提示模板,包括定义任务、设置变量和指定输出格式。最后,我们通过代码演示了如何创建和使用提示,并引出了将多个模型串联起来的下一个主题——LLM链。
33:LLM 链式应用

概述

在本节课中,我们将要学习大语言模型(LLM)中一个令人兴奋的领域:LLM 链。我们将探讨如何将多个LLM甚至不同类型的工具连接在一起,以构建更复杂、更强大的工作流。

现在,让我们从大语言模型最令人兴奋的领域之一开始,那就是 LLM 链。在这里,我们不仅可以连接一个LLM与另一个LLM,甚至可以连接各种不同的工具。
LLM 链的概念在2022年底随着 LangChain 库的发布而开始流行。自那时起,其受欢迎程度便与日俱增。我们看到使用 LangChain 的应用程序如雨后春笋般涌现,创造出各种令人惊叹的不同类型的产品和工作流。
在本节中,我们将讨论 LangChain 的工作原理,以及我们如何利用大语言模型不仅连接其他LLM,还能连接不同的工具。
让我们回到之前的例子。我们已经完成了文章的输入、总结,并创建了一个用于总结的提示模板。现在,我们需要创建另一个提示模板,以便将情感分析纳入我们的工作流。
我们将创建一个新的情感分析提示模板,就像我们为总结所做的那样。模板内容可以是:“评估以下摘要的情感”,然后传入那个摘要。

接着,我们将请求LLM生成情感分析结果。这与我们之前看到的非常相似,我们只是在完成这个问题的闭环。我们有两个大语言模型,它们可能来自同一个提供商,也可能是不同的,这取决于我们如何利用手头的资源。我们可能会使用一个专门为总结微调的LLM,以及另一个专门为情感分析微调的LLM。
现在,我们有了一个提示,它将我们最初文章的摘要作为输入。因此,我们将把第一个大语言模型的输出,作为下一个大语言模型提示的输入。
如果我们看看如何将这两者连接起来,最终会得到三个不同的链。

首先,我们有一个工作流链,它将所有部分连接在一起。
然后,在这个工作流链内部,有两个较小的链:
- 总结链:我们之前看到过,它将提示和文章数据连接到我们用于总结的大语言模型。
- 情感链:它将总结链的输出,作为我们情感分析大语言模型提示的输入。
接着,情感链的输出就成为工作流链的输出,即文章1的情感。
这里发生了很多事情,但你可以将其想象成两个小胶囊连接在一起,构成了我们的工作流。
我们能做的远不止连接一个大语言模型到另一个大语言模型。通过将大语言模型连接到数学套件、编程工具、搜索库等各种事物,我们可以创造出无穷的创意。
让我们看看如何构建这样的东西,以及需要怎样的思考过程,才能将我们分析的自然语言连接到这些程序化接口。
以下是构建此类连接的基本步骤:
- 生成可执行代码:第一步是接收我们提供的文本或问题,并返回可执行代码(如果我们连接到某种数学库)。
- 执行与解释:代码会被传递给某种解释器执行,就像人类在终端中输入代码一样。
- 整合与回应:LLM接收执行结果,将其与我们最初的问题结合,生成一个自然语言的回应。
例如,我们给出问题:“计算5乘以10”。LLM可能会生成代码 5 * 10,解释器执行后返回结果 50。然后LLM会生成回应:“5乘以10等于50”。
这个过程虽然看起来复杂,但一步步来看,它真正展示了我们利用这些大语言模型所能拥有的强大能力。当然,这依赖于大语言模型经过足够好的训练,能够根据自然语言输入生成代码片段。许多知名或在课程中会看到的LLM,其训练数据都包含代码部分,这也是过去几年对训练数据集探索和兴趣激增的原因之一。
我们可以走得更远,超越简单的Python代码解释器。如果我们的大语言模型训练得足够好或足够专门化,我们实际上可以将其用作一个核心推理工具。

我们可以赋予它访问不同类型资源的能力,例如搜索引擎、电子邮件客户端、其他大语言模型。整个互联网世界,只要我们能通过API与之交互,理论上都可以对这些大语言模型开放。
只要我们构建我们的输入和提示,使得LLM的响应包含能与某种API交互的代码或代码片段,并且它能接收该API调用的结果,我们就可以让我们的LLM连接到我们拥有的几乎所有程序化接口。
我们可以以结构化的方式做到这一点,甚至可以让LLM自行决定它应该使用什么工具。我们将在下一节讨论LLM智能体时,重点介绍这些模型如何决定使用它们的工具。
总结

本节课中,我们一起学习了LLM链的核心概念。我们了解到,通过LangChain等框架,可以将多个LLM或工具串联起来,构建多阶段推理的工作流。从简单的总结-情感分析链,到连接代码解释器、API接口,LLM链极大地扩展了大语言模型的应用边界和创造力。关键在于设计合适的提示,让LLM生成可执行的指令,并处理返回的结果,最终整合成流畅的自然语言输出。这为构建更智能、更自主的应用系统奠定了基础。
34:智能体(Agents)🤖

在本节课中,我们将要学习大语言模型(LLM)中最令人兴奋的部分之一:LLM智能体。我们将了解智能体的核心概念、工作原理、构成要素以及当前的发展生态。

概述
LLM智能体将大语言模型作为核心推理单元,并为其配备各种工具和组件,使其能够自动解决非常复杂的任务。这种能力源于语言模型所展现出的强大推理循环。
智能体的工作原理 🔄
上一节我们介绍了智能体的基本概念,本节中我们来看看它的核心工作原理。
一个LLM智能体建立在推理循环之上。我们可以给大语言模型一个任务,并要求它提供一个完成该任务的计划或思考过程。然后,我们可以利用这种逐步推进的方法,强制LLM经历一个“思考-行动-观察”的循环。
如果我们观察右侧的代码,可以看到流程始于调用LLM并给予其一个自然语言请求。LLM会审视这个请求,查看其可支配的工具描述,然后决定下一步做什么。接着,它会观察使用特定工具执行该行动的结果,并判断是应该停止并返回已完成的任务,还是应该采取另一步骤,将当前已有的结果作为下一步LLM的输入,继续这个过程。

这个过程会持续进行,直到达到最大迭代次数或满足某些停止条件。这使得LLM智能体成为解决复杂问题的强大工具。
构建智能体的要素 🛠️
了解了工作原理后,我们来看看构建一个LLM智能体需要哪些基本要素。
以下是构建LLM智能体所需的三个核心部分:
- 一个需要解决的任务:这是智能体的目标。
- 一个具备良好思维链推理能力的LLM:这是智能体的大脑。
- 一组工具:这些工具能够与大语言模型交互,就像我们在LLM链中看到的数学工具一样。

工具描述非常有用,因为LLM会审视它需要执行的任务请求,查看工具的描述,然后决定应该使用哪个工具以及如何与之交互。由于LLM通常具备输出代码或API交互代码的能力,我们可以利用这一点与不同类型的数值或计算组件进行交互。
智能体的发展生态 🌍
现在我们已经知道如何构建一个智能体,接下来看看这个领域正在发生什么。
LLM智能体或LLM插件正开始向公众发布,并由开源社区开发。LangChain是第一个被广泛使用的LLM智能体开源应用,但整个社区正迅速跟进并开发类似的产品。Hugging Face几周前刚刚发布了他们的Transformers Agents。谷歌在今年的I/O大会上展示了PaLM 2与其Workspace的集成。ChatGPT正在逐步向公众发布插件功能,我们可以将不同类型的工具连接到ChatGPT界面,让它为我们完成非常有趣和复杂的任务。

OpenAI承认开源社区正朝着与其相似的方向发展,甚至在讨论插件的文档中引用了LangChain。
前沿探索:自主智能体 🚀
如果我们想将这个概念推向极致,实际上可以赋予LLM更多的自主能力,允许它创建自身的副本,从而仅需少量提示即可解决任务。
在2023年初,一个名为AutoGPT的新项目(更准确地说是一个新代码库)被创建出来。AutoGPT使用GPT-4来创建自身的克隆,并将任务委托给这些副本,从而仅通过自然语言提示就能解决真正复杂且令人着迷的任务。
因此,这些多阶段推理工具正在形成一个初步的生态格局。产品之间的差异基于它们是专有的还是开源的,以及它们是否是“有引导的”(如LangChain或Hugging Face Transformers提供的结构化构建模块),还是一些“无引导的”项目(如HuggingGPT、Baby AGI和AutoGPT),这些项目目前正由开源社区积极开发。
我强烈建议你查看所有这些项目,因为它们非常迷人,并且持续更新以展现惊人的能力。我们将在后续的笔记本中学习如何构建其中一些智能体,希望你对LLM智能体生态的未来和我一样感到兴奋。
总结


本节课中,我们一起学习了LLM智能体的核心概念。我们了解到智能体利用大语言模型作为推理核心,通过“思考-行动-观察”的循环,结合外部工具来解决复杂任务。构建智能体需要明确的任务、强大的LLM和定义清晰的工具集。当前,从LangChain、Hugging Face Agents到AutoGPT,一个充满活力的智能体生态正在快速发展,预示着AI应用将变得更加自主和强大。
35:多阶段推理总结 🧠
在本节课中,我们将回顾和总结第三模块“多阶段推理”的核心内容。我们探讨了如何利用LangChain框架和LLM智能体来构建结构化的工作流,以执行复杂的任务。

上一节我们介绍了LLM智能体的强大能力,本节中我们来对整个模块进行总结。
我们首先了解了LLM链如何帮助构建结构化的工作流。其核心是将提示模板与执行特定任务的LLM结合起来,逐步生成我们想要的结果。其基本思想可以用一个简单的流程表示:
工作流 = 提示模板 + 特定任务LLM
接着,我们探讨了LangChain框架。它作为这些链的封装器,使得构建和利用不同工具与架构变得更加容易。这允许我们的LLM执行多样化的任务,不仅限于自然语言处理,还包括编译代码和运行各种复杂逻辑。例如,通过LangChain,我们可以这样组织一个链式调用:
from langchain.llms import OpenAI
from langchain.prompts import PromptTemplate
from langchain.chains import LLMChain
# 定义模板和链
template = "请将以下英文翻译成中文:{text}"
prompt = PromptTemplate(template=template, input_variables=["text"])
llm_chain = LLMChain(prompt=prompt, llm=OpenAI())
最后,我们看到了LLM智能体如何执行一些真正令人惊叹的任务。它们能够自主使用工具、进行决策,其全部潜力仍有待我们进一步探索。
以下是本模块涵盖的三个核心要点:
- LLM链:提供了将提示与模型结合的标准方法,实现了分步、结构化的任务执行流程。
- LangChain框架:简化了复杂链的构建与管理,极大地扩展了LLM的应用场景。
- LLM智能体:代表了更高级的自动化水平,能够动态规划并使用工具来完成目标。

本节课中我们一起学习了多阶段推理的关键组件。从构建基础的LLM链,到利用LangChain框架整合复杂逻辑,再到展望LLM智能体的未来潜力,我们看到了如何让大语言模型超越简单的文本生成,成为执行复杂工作流的强大引擎。

希望你已经掌握了本模块的内容。接下来,是时候进入实践环节,在代码笔记本中亲眼看看这些概念是如何运作的了。
36:多阶段推理系统构建

概述
在本节课中,我们将学习如何使用 LangChain 构建两个多阶段推理的 AI 系统。第一个系统是一个包含自我审查机制的双 LLM 链,第二个系统则是一个具备网络搜索能力的智能体。
环境设置与准备工作
在开始构建系统之前,我们需要完成一些准备工作。
以下是设置环境的步骤:
- 运行课堂设置单元,配置必要的环境变量并安装所需的 Python 包。
- 获取并配置 API 密钥。本教程将使用 Hugging Face 和 SerpAPI(用于谷歌搜索)的服务。请参考相关链接注册免费账户并获取密钥。
- 安全地导入 API 密钥。推荐使用 Databricks Secrets 工具,避免在代码中明文存储密钥。
构建“杰基尔与海德”自我审查系统
上一节我们完成了环境配置,本节中我们将构建第一个 AI 系统。这个系统由两个大型语言模型组成,分别扮演“评论者”和“审核者”的角色。
第一步:创建评论者(杰基尔)的提示模板
首先,我们需要为评论者模型定义一个提示模板,指导它如何生成评论。
from langchain import PromptTemplate
import numpy as np
jekyll_template = """
你是一个社交媒体评论者。
你将用{sentiment}的情绪回应以下帖子。
{social_post}
评论:
"""
我们使用 PromptTemplate 类来实例化这个模板,并指定输入变量为 sentiment(情绪)和 social_post(社交媒体帖子)。
prompt_template = PromptTemplate(
input_variables=["sentiment", "social_post"],
template=jekyll_template,
)
接下来,我们为系统生成一些输入数据。情绪是随机选择的(“友好”或“刻薄”),而社交媒体帖子内容是固定的。
random_sentiment = "mean" # 也可以是 "nice"
social_post = "真不敢相信我正在这门慕课中学习 LangChain。要学的东西太多了。到目前为止,讲师们都非常乐于助人。我学得很开心。😊"
现在,我们可以用这些数据来填充提示模板,生成完整的提示。
jekyll_prompt = prompt_template.format(
sentiment=random_sentiment,
social_post=social_post
)
print(jekyll_prompt)
第二步:为杰基尔选择大语言模型
有了提示之后,我们需要为杰基尔选择一个“大脑”,即一个大语言模型。这里我们以 OpenAI 的模型为例进行说明。
from langchain.llms import OpenAI
# 实例化 OpenAI 模型,这里使用 text-babbage-001
jekyll_llm = OpenAI(
model_name="text-babbage-001",
openai_api_key=你的_openai_api密钥, # 请安全地替换为你的密钥
)
第三步:构建杰基尔的 LLM 链
现在,我们将提示模板和大语言模型连接起来,形成一个 LLM 链。这个链将接收输入变量,运行模型,并输出结果。
from langchain.chains import LLMChain
from better_profanity import profanity
jekyll_chain = LLMChain(
llm=jekyll_llm,
prompt=prompt_template,
output_key="jekyll_said",
verbose=False
)
# 运行链并获取杰基尔的评论
jekyll_said = jekyll_chain.run(
sentiment=random_sentiment,
social_post=social_post
)
# 使用过滤器确保输出安全
print(profanity.censor(jekyll_said))
第四步:创建审核者(海德)的 LLM 链
杰基尔可能发表不当言论,因此我们需要海德来审核。其构建步骤与杰基尔类似。
以下是构建海德链的完整代码:
# 1. 定义海德的提示模板
hyde_template = """
你是一个在线论坛的审核员。
你非常严格,不容忍任何负面评论。
你会查看来自用户的以下评论:{jekyll_said}。
如果它有任何负面内容,你会用符号(如 `####`)替换它并发布。
如果它看起来不错,你会原封不动地保留它并逐字重复。
编辑后的评论:
"""
hyde_prompt_template = PromptTemplate(
input_variables=["jekyll_said"],
template=hyde_template
)
# 2. 为海德实例化一个(可能更强大的)LLM
hyde_llm = OpenAI(model_name="text-davinci-003", openai_api_key=你的_openai_api密钥)
# 3. 构建海德的 LLM 链
hyde_chain = LLMChain(
llm=hyde_llm,
prompt=hyde_prompt_template,
verbose=False
)
# 4. 运行海德链,审核杰基尔的发言
hyde_says = hyde_chain.run(jekyll_said=jekyll_said)
print(hyde_says)
第五步:使用顺序链整合系统
目前,我们需要手动将杰基尔的输出传递给海德。为了创建一个更流畅、模块化的系统,我们可以使用 SequentialChain 将两个链自动连接起来。
from langchain.chains import SequentialChain
jekyll_hyde_chain = SequentialChain(
chains=[jekyll_chain, hyde_chain],
input_variables=["sentiment", "social_post"],
verbose=True # 设置为 True 以查看中间步骤
)
# 现在,只需提供初始输入,即可获得最终审核结果
final_output = jekyll_hyde_chain.run({
"sentiment": random_sentiment,
"social_post": social_post
})
总结

本节课中,我们一起学习了如何使用 LangChain 构建多阶段推理系统。我们首先创建了“杰基尔与海德”双 LLM 链系统,实现了评论生成与自动审核的功能。通过定义提示模板、实例化大语言模型、构建 LLM 链,最终使用 SequentialChain 将它们串联,我们完成了一个可以接收初始输入并自动执行多步推理的完整流程。这为构建更复杂的 AI 应用奠定了基础。
37: Notebook Demo Part 2

在本节中,我们将构建一个基于ReAct(思考-行动-观察)循环范式的LLM智能体。这个智能体名为Dacey,它能够接收纯文本指令,并对找到的数据进行科学分析。
上一节我们介绍了Jekyll Hyde智能体,本节中我们来看看如何构建一个更专注于数据科学任务的智能体。
构建智能体:Dacey
我们将构建的智能体类型称为“零样本ReAct描述”。这意味着Dacey将使用零样本学习来尝试解决我们给出的任务。我们只需提供提示,并期望Dacey使用其拥有的工具来解决问题。
以下是构建LLM智能体的核心步骤:
1. 选择工具集
构建LLM智能体最重要的部分是给它一套合理的工具集。了解智能体的任务将决定你为它配备的工具类型。
对于Dacey,它将可以访问以下工具:
- Wikipedia搜索API
- Google搜索API
- 终端
- Python REPL(读取-求值-打印循环)
Python REPL对于Dacey执行诸如使用Pandas库下载数据、与数据交互等任务非常有用。
2. 选择大语言模型
与Jekyll Hyde类似,你可以使用Hugging Face、OpenAI或其他LLM提供商。在本演示中,我们将使用OpenAI的LLM,并且不指定具体模型,让库为这个LLM选择最先进的模型。
建议:在构建LLM智能体时,推荐使用你能访问到的更先进的模型,因为任务的复杂性需要一个性能良好、知识丰富的大语言模型来生成所需的代码或推理循环。
3. 初始化智能体
我们将使用load_tools库加载工具,并传入之前获取了令牌的Wikipedia搜索API、Python REPL和终端命令库。
然后,我们可以使用initialize_agent命令类,传入工具、我们指定的LLM以及代理类型(即零样本ReAct描述)来构建Dacey。我们将把verbose设置为True,以便观察Dacey的思考过程。
代码示例:
from langchain.agents import initialize_agent, load_tools
from langchain.llms import OpenAI
# 加载工具
tools = load_tools(["wikipedia", "python_repl", "terminal"], llm=llm)
# 初始化智能体
agent = initialize_agent(
tools,
llm,
agent="zero-shot-react-description",
verbose=True
)
现在,让我们设置好并开始测试Dacey的技能。
测试Dacey的技能
测试一:基础数据分析
首先,我们使用一个相对简短但明确的提示,要求Dacey创建一个数据集(不是下载,而是创建),并找到关于梅赛德斯AMG F1车队2020年表现的数据进行分析,最后绘制结果。
运行此命令后,我们可以看到Dacey进入了一个代理执行链。它的第一个行动是搜索,因为它意识到需要查找数据并进行分析。它执行搜索动作,获取观察结果并进行思考。接着,Dacey传递结果,查看车队的表现统计数据,然后创建数据集并准备绘制结果。Dacey导入了pandas,构建了一个数据框,并绘制了该赛季车队的不同统计数据。
这是一个相当令人印象深刻的结果,但我们可以做得更多。
测试二:更详细的提示
让我们尝试通过给出更详细的提示来改进,要求Dacey至少绘制三个图表,并使用子图将它们同时绘制出来,同时使用Seaborn库让结果更美观。
虽然之前的结果已经很好,但让我们看看Dacey在更详细的提示下能做什么。请注意,这并不意味着Dacey会使用之前的数据或提前下载,它很可能再次从头开始执行。理论上,我们可以保存一些数据并使用不同的工具,以便Dacey能够利用已有的数据,我们将在下一部分看到如何实现。
我们可以看到Dacey找到了比赛统计数据和不同的得分。Dacey现在使用Python REPL,导入pandas和seaborn。Dacey有了一个数据框。现在,我们在绘图输出中可以看到刘易斯·汉密尔顿和瓦尔特里·博塔斯的得分图。
然而,我们要求Dacey绘制三个图表,但这只是一个。不过Dacey确实使用了Seaborn,所以它完成了大部分要求,但并不完美。LLM智能体背后并没有魔法,有时它们是否能给出你想要的精确结果全靠运气,你可能需要运行一两次。
让我们再运行一次这个命令,看看Dacey在第二次尝试中是否能成功。
测试三:二次尝试与成本考量

再次运行时,Dacey找到了一些关于该赛季的口述分析,并开始传递信息。请记住,每次运行这类命令时,Dacey都会使用API令牌来访问搜索API以及OpenAI LLM命令或Hugging Face命令,因此请注意你让智能体做什么,以及你对令牌使用的容忍度。
我们可以看到,我们获得了该车队两名车手的数据框。Dacey现在表示它已拥有数据集,并准备创建三个图表。
现在这些图并不是最出色的,它们基本上只是各自绘制了一个变量。然而,这正是我们要求Dacey做的:我们得到了该年度车队取得的杆位数量、前排发车位置数量和最快圈速数量。
这只是LLM智能体能力的初步展示,仅通过提示中非常有限的信息就允许它在网络上搜索。理论上,你可以编写一个极其复杂的提示,包含大量提示和技巧,以进一步引导你的LLM智能体表现得越来越好。但仅凭提示中的少量信息,我认为Dacey已经做得非常好了。
本节总结

本节课中我们一起学习了如何构建一个基于ReAct范式的数据科学LLM智能体Dacey。我们了解了为其配备合适工具集的重要性,并使用LangChain库进行了初始化。通过几个测试案例,我们看到了Dacey如何根据文本指令搜索信息、处理数据并生成可视化图表。同时,我们也认识到智能体的输出结果存在一定随机性,且需注意API调用的成本。下一节,我们将探讨如何让Dacey利用已有的数据进行工作。
38:多阶段推理 - 笔记本演示第三部分

在本节课中,我们将学习如何为AI代理提供数据进行分析。我们将创建一个特定类型的代理——Pandas DataFrame代理,并利用它来分析一个真实的数据集,甚至训练一个机器学习模型。
🧠 创建Pandas DataFrame代理
上一节我们介绍了如何让代理执行代码。本节中,我们来看看如何让代理分析我们提供的数据。
我们将创建一个名为Pandas DataFrame代理的特定类型代理。顾名思义,它直接利用Pandas库,因此可以处理我们授权其访问的DataFrame数据。
以下是创建和配置该代理的步骤:
- 读取数据:我们首先从一个名为Kaggle的平台上读取一个CSV文件,该文件包含了2023年数据科学领域的薪资数据。我们将这个数据加载到一个Pandas DataFrame中。
df = pd.read_csv('data_science_salaries_2023.csv') - 创建代理:接下来,我们使用
create_pandas_dataframe_agent函数来创建代理。我们将DataFrame和OpenAI的大语言模型传递给它。 - 设置参数:在初始化模型时,我们将温度参数
temperature设置为0。虽然本课程不深入探讨温度参数,但你可以将其理解为控制模型回答创造性的参数。设置为0意味着模型必须严格基于现有的事实信息进行回答,这在处理数据集时非常重要,因为我们不希望它“想象”出不存在的数据。agent = create_pandas_dataframe_agent( llm=OpenAI(temperature=0), df=df, verbose=True )
📊 代理进行数据分析
现在,我们有了数据和代理,可以要求代理分析数据并寻找有趣的趋势。
我们向代理提出请求:“分析数据,寻找任何有趣的趋势,并绘制一些漂亮的图表。”代理随后启动了一个执行链。
代理首先对DataFrame执行了describe操作,以获取数据的基本统计信息。接着,它进行了一些分组操作,以查看不同变量如何影响薪资水平。
最终,代理绘制了公司规模与薪资范围的对比图,以观察公司规模对薪资的影响。根据图表,中等规模的公司平均提供的薪资最高。
代理总结道:“我可以看到,平均薪资随着经验水平的提升而增加,全职员工的薪资高于合同制员工,并且随着公司规模的扩大而增加。”虽然这些结论可能看起来显而易见,但请注意,我们除了数据和运行Python代码的权限外,没有向代理提供任何其他信息或指导。
🤖 代理构建机器学习模型
上述分析已经令人印象深刻,但我们可以更进一步,要求AI代理自行创建一个机器学习模型。
在这个场景中,我们要求代理训练一个随机森林回归模型。这是一种基于决策树的机器学习模型。为了节省时间和计算资源,我们选择使用随机森林而非深度学习模型。
我们给代理的指令是:“使用最重要的特征来预测薪资,并告诉我们哪些变量对模型最有影响力。”对于有过机器学习经验的人来说,这个简单的指令要求代理完成的工作量是相当大的。
运行指令后,我们看到代理找到了scikit-learn库,并计划使用ensemble库中的RandomForestRegressor。
在编写代码的过程中,代理遇到了两个主要问题:
- 缩进错误:代理自己编写的Python代码出现了缩进错误,它识别到了这个错误,并通过删除每行代码开头的所有空格进行了修复。
- 数据类型错误:在尝试将字符串转换为浮点数时出错。代理意识到需要将字符串值转换为数值,并自行选择了
LabelEncoder来解决这个问题。
需要强调的是,除了“训练模型并告诉我们重要变量”这个指令外,我们没有引导或指示大语言模型做任何其他事情。代理完全是在自主地找出并克服这些障碍。
最终,代理成功运行了模型并输出了结果。它指出,对于使用随机森林回归器预测薪资而言,最重要的特征是:工作年份、经验水平、雇佣类型、远程工作比例、公司所在地区和公司规模。
对于那些熟悉机器学习的人来说,都知道构建这样一个模型需要付出多少努力和专业知识。然而,我们可以在不了解机器学习、scikit-learn或特征重要性的情况下完成这一切。这只是展示大语言模型代理能力的一个小例子。
🎯 课程总结
本节课中,我们一起学习了如何利用Pandas DataFrame代理分析真实数据集。我们看到了代理如何自主执行数据探索、可视化,甚至构建和调试一个完整的机器学习模型流程,包括处理数据编码错误和代码语法问题。这充分展示了基于大语言模型的智能代理在数据分析和自动化任务方面的强大潜力。


如果你在思考“如果这个模型不完全符合我的需求怎么办?”,请进入下一个模块——模块4。在那里,我们将探讨如何针对自定义用途,对模型进行微调和评估。
39:引言


欢迎来到第四模块。本模块将探讨如何获取高质量大语言模型应用的一些进阶方面,重点是微调与评估大语言模型。
在本模块中,我们将讨论当你搭建了一个应用,拥有一些数据,获得了一些反馈,并希望提升模型质量时,可以采取哪些措施。你可以通过多种方式实现这一目标。
我们将介绍三种通用方法。首先是小样本学习,即在提示词中为模型提供几个任务示例,让它学会执行。其次是微调,即实际更新模型的参数,使其专门适用于你的特定应用。
我们将讨论如何利用现有的冻结模型进行小样本学习,以及使用大语言模型即服务时可以采取的一些措施。最后,我们将探讨自行微调,这主要适用于你自行训练或拥有完全控制权的开源模型,你可以控制其许多方面,使其擅长特定任务。
本模块还将重点介绍一些功能强大的可用开源模型,例如具备指令遵循能力和高质量提示响应能力的模型。这包括来自Databricks的Dolly模型,以及其他基于它或在此领域表现优异的模型。
另一个非常重要的议题是评估大语言模型。我们将讨论如何系统地检查它们的表现并加以改进。在经典机器学习中,评估相对容易,因为模型在进行预测,你可以检查每个预测是否正确并给出分数。但对于生成文本的模型,判断“这一大段文本是否比另一段更好”则更具挑战性。我们将探讨相关的评估技术。
我们也会简要涉及对齐这一概念。对齐旨在让模型避免冒犯性内容,保持友好,总体上避免执行某些类型的任务,并在某种意义上对所提要求进行一定程度的推理,实现某种内容审核。
我们希望你在本模块中有所收获,从而能够从你的大语言模型中获取最佳性能。


40:模块四概述

在本节课中,我们将要学习如何针对特定应用场景,对大型语言模型进行微调和评估。我们将探讨何时需要微调模型,以及如何通过实践操作来定制模型以满足特定需求。
欢迎来到模块四。至此,本课程已过半程。我们已经见识了大型语言模型的强大能力:从使用Hugging Face解决几乎所有自然语言处理问题,到模块二中介绍的向量数据库,再到能与LangChain结合使用的各种神奇工具。大型语言模型及其应用似乎为我们提供了构建任何所需功能的灵活性。
但是,如果你认为现有的大型语言模型不完全适合你的应用呢?也许它需要定制化的数据才能工作,或者你只是不确定如何以最高效的方式构建你的应用。本模块的目标就是展示我们如何选取不同类型的大型语言模型,将其应用于构建特定类型的应用,并在认为需要创建专属模型时,走完微调的全过程。

在本模块结束时,你将理解何时以及如何微调不同类型的大型语言模型。通过我们的实践笔记,我们将了解DeepSpeed,以及如何使用Hugging Face模型针对我们的具体用例进行微调。我们还将学习如何评估这些不同类型的大型语言模型,以确保当我们微调或定制这些模型时,它们能给出我们想要的答案。
接下来,我们来谈谈典型的大型语言模型发布。
我们将考虑目前几乎每周都会出现的开源大型语言模型发布。
通常,它们会以多种不同规模发布。我们有一个基础模型,这种模型仅被训练来根据其迄今为止见过的所有文本预测下一个词。这些模型通常有基础尺寸,以及更小和更大的版本。这里的“尺寸”指的是参数数量,或者它可能占用的存储空间或内存大小(以GB计)。
模型也可能以不同的序列长度发布,这指的是我们单次可以输入给模型的令牌数量。
更新的技术(我们将在课程二中介绍其中一些)允许大型语言模型将序列长度扩展到数万,而通常我们被限制在4000左右。我们还可能看到,大型语言模型在发布基础模型(仅用于为生成模型预测下一个词)的同时,会附带一些预微调版本。
我们也可能看到一个基于聊天的模型随此次基础发布一同推出,它被训练用于进行更类人的对话交互。此外,还可能有一个基于指令的模型,它与聊天模型略有不同,因为它专门设计用于响应被赋予的任务。
作为开发者,你可能会疑惑该选择哪个方向,选择哪一个?
在每种情况下,我们都必须权衡准确性、速度和任务特定性能。
准确性往往倾向于更大的模型,因为更大的模型通常由于见过更多训练数据且拥有更多参数来解决不同问题,从而能提供更好的性能。
速度方面,推理速度将是一个重要因素。通常,较小的模型因为占用空间更小且涉及的计算更少,在推理时比大模型快得多。
任务特定性能也是我们需要考虑的因素。大型语言模型虽然可能拥有广泛的知识集,但在特定用例上的效率或性能值可能与专门针对该用例微调的模型不同。
在本模块中,我们将聚焦于一个非常具体的用例,向你展示如何解决你的问题。
我们将构建一个应用程序,它接收一篇新闻文章,对其进行总结,然后将其转化为一个谜语,供该应用程序的用户解答。


本节课中,我们一起学习了模块四的总体目标:针对特定应用微调和评估大型语言模型。我们了解了基础模型的不同变体,以及在实际应用中选择模型时需要权衡的要素。在接下来的小节中,我们将深入实践,学习具体的微调与评估技术。
41:应用基础大语言模型


在本节课中,我们将通过一个具体的应用开发示例,学习如何应用现有的大语言模型来解决问题。我们将探讨不同的模型选择与实现路径。
概述

我们将构建一个新闻应用,其核心功能是总结每日新闻文章,并将其改写为谜语形式,供用户阅读和解谜。接下来,我们将思考如何利用现有工具来实现这个想法。
可用的LLM流水线方案
我们拥有多种潜在的大语言模型应用方案。首先,考虑我们已有的资源:我们将通过应用程序接口连接某个新闻媒体,以获取全球每日新闻文章。此外,我们可能拥有一些由我们自己或擅长将新闻改写为谜语的人预先编写的示例。虽然样本数量不多,但足以支持后续进行少量样本学习。
考虑到社区发布的各种大语言模型,我们可以采取以下几种路径:
- 使用开源LLM进行少量样本学习:我们可以利用开源的大语言模型,结合我们手头的示例进行少量样本学习。
- 使用指令遵循型LLM进行零样本学习:我们可以直接使用能够遵循指令的模型,尝试进行零样本学习。
- 使用付费的LLM服务:我们可以选择商业化的、按服务收费的大语言模型选项。
- 构建自定义模型路径:我们也可以选择从头开始,构建自己的模型解决方案。

最终,我们的目标是找到一种方法,能够处理新闻文章并最终为用户呈现一个应用界面。下面,我们将逐一探讨这些选项,看看它们如何被实施。
42: 微调之小样本学习 🧠

在本节课中,我们将要学习如何利用小样本学习来解决将新闻文章转换为谜语的任务。我们将探讨其实现原理、优缺点,并了解如何构建一个有效的提示词。
概述
我们面临的任务是:利用新闻API获取文章,并将其转换为有趣的谜语。一种直接的方法是小样本学习。这种方法无需训练模型,只需在提示词中提供少量示例,引导大型语言模型理解并执行任务。
上一节我们介绍了任务背景,本节中我们来看看如何具体应用小样本学习。
小样本学习的优缺点分析
在决定是否采用小样本学习前,我们需要权衡其利弊。以下是其主要优点和缺点:
优点:
- 开发迅速:我们拥有所需的所有数据,只需直接应用LLM并指定提示词即可。
- 计算成本低:由于我们使用的是已发布的开源LLM,不进行任何训练,仅用于推理,因此相关的计算成本极低。
缺点:
- 性能依赖模型大小:为了在示例较少的情况下获得良好性能,我们可能需要一个更大的模型。使用较小的模型或较少的示例,性能往往不尽如人意。
- 需要高质量示例:我们需要相当数量、高质量的示例,这些示例必须覆盖整个任务的意图和范围。我们必须确保提供的少数示例能够涵盖真实世界中可能遇到的文章的足够广度,以便模型能够推断出足够的信息。
- 模型大小的影响:如果我们需要使用开源LLM中最大或较大的版本,可能会带来存储空间和计算上的困难,具体取决于模型的实际大小。
实现方案:构建提示词
了解了优缺点后,我们来看看如何在应用程序中实现它。核心在于构建一个有效的提示词。
我们将告诉大语言模型,它需要先总结文章,然后根据总结创建一个谜语。为了简化提示词,我们暂时将其视为一个单一的任务步骤。

我们的提示词将包含以下几个部分:
- 任务指令:明确要求模型进行总结并创作谜语。
- 示例部分:提供我们已有的所有文章及其对应的总结谜语作为示例。
- 目标输入:最后一部分是我们需要处理的新文章,并留出空白让模型生成对应的总结谜语。
一个简化的提示词结构可能如下所示:
你是一个将新闻文章转换为谜语的专家。请先总结文章,然后根据总结创作一个谜语。
示例:
文章:[示例文章1的全文]
总结谜语:[示例文章1对应的谜语]
文章:[示例文章2的全文]
总结谜语:[示例文章2对应的谜语]
现在,请处理以下新文章:
文章:[需要处理的新文章的全文]
总结谜语:
需要注意的是,对于这类应用,我们很可能需要一个支持很长输入序列的模型。这可能是模型的某个非常大的版本,或者是一个目前还难以获取的、专门处理长序列的模型。通常,对于小样本学习,你使用的基础或预训练模型越大,性能就越好,这也是需要考虑的因素。
总结

本节课中我们一起学习了小样本学习方法。我们了解到,它是一种快速、低成本的方案,通过精心设计的提示词和少量示例来引导大语言模型完成特定任务。然而,其性能严重依赖于模型的大小和所提供示例的质量与广度。这是一种可行的选项,在下一个视频中,我们将探讨另一种方案。
43:指令微调大语言模型 🎯


在本节课中,我们将学习如何利用开源社区发布的、经过指令微调的大语言模型来构建应用。我们将探讨在没有任何现成示例的情况下,如何通过零样本学习的方式,仅通过描述任务来让模型执行文本摘要。
概述
上一节我们介绍了小样本学习,本节中我们来看看另一种方法:直接使用经过指令微调的预训练大语言模型。这种方法适用于我们没有现成任务示例的场景。
使用指令微调模型的场景
假设我们没有任何现成的任务示例。如果我们有示例,可以遵循与小样本学习类似的方法,直接使用基础模型或指令模型。指令模型的表现取决于其训练方式,可能更好、更差或与小样本学习的结果相似。在本例中,我们重点探讨没有任何示例可用的场景。这种情况下,我们需要利用零样本学习:仅描述任务,然后提供待摘要的文章。
应用架构很简单:我们拥有新闻API、一个由开源社区发布的指令遵循大语言模型,以及我们想要创建的应用。
方法的优缺点分析
以下是使用预训练指令模型的一些关键考虑因素:
- 提示词构建:由于我们假设没有可用数据,可能需要更仔细地构建提示词,确保其具体且经过深思熟虑,以便模型能够遵循。
- 性能:根据用于从基础模型进行微调的数据集,该模型可能已经非常擅长解决此类问题,即使是零样本方法。
- 成本:同样,因为我们使用的是已经训练好的开源大语言模型,我们只需为推理时的计算付费,自己无需进行任何训练。
- 潜在缺点:如果该模型的微调过程并未使其真正学会处理此类任务,则可能导致性能下降,这可能不是一个可行的选择。
- 模型规模效应:根据模型的训练方式,我们可能需要为此特定应用使用该微调模型的最大版本(如果存在的话)。
实现方式

这种方法的实现方式将使用一个非常简短的提示词,比小样本学习中的提示词小得多。我们只需描述需要解决的任务,然后将文章作为输入变量,并要求其生成输出。
正如之前所说,根据模型的训练效果,我们可能会得到不同的结果。我们可能很幸运,发现模型已经可以直接使用,无需进一步调整。然而,也有可能这种方法对我们来说效果不佳,因此我们可能需要考虑其他选项。
总结


本节课中,我们一起学习了如何利用预训练的指令微调大语言模型进行零样本学习。我们分析了这种方法的优缺点,并了解了其简单的实现方式,即通过精心设计的简短提示词来指导模型完成任务。
44:微调: 将LLM作为服务使用

在本节中,我们将探讨一种不同的方法:利用专有的大语言模型或LLM即服务来解决应用开发问题。

上一节我们讨论了基于开源模型的应用构建,本节中我们来看看如何将商业化的LLM服务集成到我们的工作流中。
概述:LLM即服务的工作流
在这种场景下,我们假设没有现成的示例数据可以随新闻API的结果一起发送给模型。当然,你也可以想象,如果我们想为付费的LLM服务提供一些示例以实现小样本学习,我们同样可以做到。但本节的目标是重点审视,将LLM作为服务引入,会如何融入你的应用程序工作流。
LLM即服务的优势与考量
因为LLM即服务本质上只是另一个API调用,所以启动和围绕此调用构建应用非常迅速。这与调用新闻API的接口方式完全相同。
由于所有的计算和基础设施都由服务提供商处理,因此使用LLM即服务时性能往往更高,因为所有工作都在远端完成,而非本地机器。
当然,选择LLM即服务的缺点在于成本,你需要为发送和接收的每个令牌付费。然而,你仍需将此成本与在自有硬件上运行所有服务的成本进行权衡。
数据隐私和安全也存在风险,因为你发送给LLM提供商的所有数据,他们都能以某种形式访问。你需要根据服务条款,清楚了解你的数据可能被用于何种目的。
同样,正如在任何其他情况下使用供应商一样,你需要警惕供应商锁定的风险。如果供应商遭遇服务中断、弃用某些功能或改变定价结构,这些都是在构建应用时需要牢记的因素。
实现方式

以下是实现LLM即服务集成的核心步骤,它相对其他构建LLM应用的方式而言非常直接:
- 构建提示:将我们的需求描述为一系列令牌,构成发送给API的提示。
- 身份验证:通过API密钥(通常关联信用卡或其他支付方式)进行身份验证并发送请求。
- 接收响应:从API提供商的服务端接收响应。
其核心流程可以用一个简单的伪代码表示:
response = llm_service_api.call(prompt=user_prompt, api_key=my_api_key)
这是投入精力最少的方式,并且目前从闭源LLM提供商那里往往能获得非常好的性能。在目前大语言模型的发展周期中,专有软件的表现总体上超越了开源社区,尽管开源社区也在竞相提升性能。
总结
本节课中我们一起学习了将大语言模型作为服务(LLMaaS)集成到应用中的方法。我们分析了其快速启动、高性能的优势,也探讨了成本、数据隐私和供应商锁定等需要考虑的风险。实现上,它主要涉及构建提示词并通过API进行调用,流程相对简单。

如果这些情况(包括闭源专有版本)都不完全符合我们的需求,或者模型性能未达到预期,那么我们可能真的需要对某个大语言模型进行微调。让我们在下一个视频中探讨这个问题。
45:微调:自己动手

在本节课中,我们将要学习当现有的大语言模型无法满足特定应用需求时,如何通过微调现有模型来创建任务专属版本。我们将探讨微调的优势、挑战以及一个重要的开源项目案例。

假设我们已经尝试了所有其他大语言模型方案,但没有一个能完全给出我们想要的结果。这可能是因为它们没有针对我们应用程序所需的具体数据进行足够的训练。
在这种情况下,我们需要尝试自己动手解决问题。我们假设手头有足够数量的示例,展示了文章被总结并转化为谜语的过程,并且我们拥有新闻API连接,可以获取所需的推理数据。
构建模型的两种路径
当我们着手构建自己的模型时,面临两个选择。
以下是两种主要的模型构建路径:
- 从头开始构建自己的基础模型:这包括构建基础模型的架构,需要收集涵盖海量训练数据源的数据集,然后在模型训练完成后对其进行微调。
- 采用现有模型并微调:直接利用我们当前已有的数据集对现有模型进行微调。
我们几乎永远不会选择第一条路径,即从头开始训练基础模型。这需要大型公司的资源才能正确且经济高效地完成。在全球范围内,真正这么做的公司可能只有十几家,并且它们将模型开源。对于其他绝大多数人来说,这需要巨大的时间、成本和资源投入,既不可行也无必要。
上一节我们介绍了两种构建路径,本节中我们来看看更可行的路径:微调现有模型。这条路径有许多有趣的细节需要我们注意。
微调现有模型的考量
如果我们要微调一个现有的大语言模型,这意味着我们可以真正为自己创建一个任务特定版本的语言模型。
微调模型也倾向于节省推理成本。一个针对特定用例高度定制的模型,其规模通常可以小于我们使用少量提示工程的大型模型。此外,我们完全控制模型及其数据,确保一切都在我们的掌控范围内。
然而,微调也存在一些缺点。我们需要投入时间和计算成本来微调模型,尽管这个代价通常可以接受。对于我们希望构建的模型类型,可能需要一个大型数据集才能进行适当的微调。我们假设在当前场景中拥有足够的数据。我们还需要一些能够微调这些模型的技能,但如果正在学习本课程,应该没有问题。
指令遵循模型的微调进展
在过去的几个月里,指令遵循模型的微调方法已经崭露头角,我们看到斯坦福大学等机构在Alpaca和Dolly V1等项目上取得了非常出色的成果。
然而,这些成果在一定程度上受到限制,因为它们使用了专有数据集,或者至少是带有商业限制性许可证的数据集。就在最近几周,Databricks使用开源数据集发布了Dolly的新版本——Dolly V2。接下来,让我们谈谈为什么Dolly 2如此特别。


本节课中我们一起学习了微调大语言模型的基本概念、路径选择以及相关考量。我们了解到,对于大多数应用场景,微调现有模型是更可行和高效的选择,并且看到了像Dolly V2这样的开源项目如何推动这一领域的发展。
46:Dolly 🦙

在本节课中,我们将要学习Dolly模型。Dolly是2023年的一项创新,它真正开启了语言建模领域的新范式。

Dolly模型简介 🧠
上一节我们介绍了大语言模型的不同类型,本节中我们来看看一个具体的开源指令微调模型——Dolly。
Dolly是一个基于Eleuther AI的Pythia 120亿参数模型的120亿参数模型。Dolly是一个遵循指令的大语言模型,这意味着我们可以要求它执行特定任务,它会按照训练的方式作出回应。
Dolly的特殊之处在于,它代表了一种被开源社区忽视或至少未充分研究的方法。然而,在2023年初的几个月里,我们已经看到这种方向获得了巨大的发展势头。
Dolly的构建过程 ⚙️
Dolly的构建首先采用了一个开源的预训练基础模型,即Pythia 120亿参数模型,该模型在Pile数据集上进行了训练。Pile数据集由多个不同的开源数据集组成。
然后,对于Dolly,我们使用Databricks Dolly 15k数据集对这个模型进行了微调。这个数据集可能是Dolly之所以特殊的关键所在。
Eleuther AI的120亿参数模型是一个开源模型,我们可以随意使用。然而,它没有以任何特定方式进行微调。如果我们想要一个微调模型,就需要一个高质量的数据集,以便它能产生对我们有用的回应。
Dolly 15k数据集 📊
以下是关于Dolly 15k数据集的关键信息:
- Databricks Dolly 15k数据集由Databricks的员工制作。
- 它内部包含了高质量智力任务的指令和回应对。
- Dolly背后的特殊之处在于,Databricks所有者发布的这个Dolly 15k数据是完全开放且可商用的。
这与之前所有其他方法都不同,因为它们都存在某种许可问题。
Dolly的意义与影响 💡
Dolly本身并不是一个最先进的模型,它只是一种方法,表明你可以采用像Eleuther AI开源模型这样的模型,将其与高质量的开源数据集结合,从而创造出具有商业可行性的东西。
现在,许多新方法正在将Dolly 15k数据与更新的开源架构相结合。
Dolly的想法源自斯坦福大学的Alpaca项目。在该项目中,他们使用了自行创建的175条指令,并将其提供给OpenAI的text-davinci-003模型,以生成这些任务的合成新版本。
经过反复试验的过程,他们最终得到了大约52,000个高质量的指令遵循示例。他们将其与Meta发布的Llama 70亿参数模型相结合。
不久之后,这个高质量的数据集意味着他们生产出了一个能力非常强的模型。然而,该模型的使用受到了限制,原因既涉及Llama 70亿参数模型的许可,也涉及他们使用OpenAI来生成更多训练数据的事实。这阻碍了斯坦福Alpaca模型在商业环境中的应用。
这暗示我们,实际上可以使用小型模型配合高质量的数据集,来复制我们从这些大型模型中看到的性能。
未来展望:小型LLM时代 🚀
现在向前看。OpenAI的首席执行官Sam Altman表达了他的看法,即我们正处于追逐越来越大的大语言模型时代的末期。
2023年及以后的重点,似乎现在是小型LLM的时代,并将其应用于不同的用例。我们已经从试图构建一个“万事通”的广泛方法,转向尝试创建针对不同类型任务的、经过微调的定制模型。
我们将走向何方尚不确定,但看到这个领域不断发展和演进是令人兴奋的。

总结 📝

本节课中我们一起学习了Dolly模型。我们了解到Dolly是一个基于开源Pythia模型、使用高质量开源指令数据集(Dolly 15k)进行微调的指令遵循模型。它的重要意义在于证明了结合开源基础模型和高质量开源数据可以创造出具有商业价值的专用模型,并标志着大语言模型的发展重点正从追求模型规模转向开发针对特定用例的小型、定制化模型。
47: 评估大语言模型 🧐

在本节中,我们将探讨如何评估大语言模型的性能。虽然微调模型非常有用,但我们必须有方法来判断模型在我们定义的任务上表现如何。

上一节我们介绍了微调的应用,本节中我们来看看如何评估模型性能。直观上,理解一个大语言模型的表现好坏很难用语言描述或形成一个一致的定义。
传统评估指标的局限性
在训练过程中,我们当然会关注损失函数或验证分数,因为这些模型本质上是深度学习模型,旨在优化某个损失函数。然而,对于一个优秀的大语言模型,损失值本身并不能告诉我们太多信息。它不像二元分类器那样有明确的传统指标。
大语言模型的核心功能是:在整个词汇表上生成一个概率分布,并选择它认为正确的下一个词。这很难直接与我们关心的目标——例如,是否得到一个好的对话代理或生成高质量的摘要——联系起来。
困惑度:一个初步的指标
为了改进仅看“答案是否正确”的评估方式,我们可以观察模型的困惑度。
困惑度衡量的是模型在预测下一个词时,其概率分布的集中程度。以下是其核心概念:
- 低困惑度:如果模型对下一个词的预测概率分布非常集中(即“尖锐”),这意味着它具有很低的困惑度。模型对自己应该选择哪个词非常有信心。
- 高准确率:一个优秀的语言模型应该同时具备高准确率和低困惑度。它既知道下一个词应该是什么,又能正确地选出那个词。
用公式可以表示为,对于一个测试文本序列 ( W = w_1, w_2, ..., w_N ),困惑度 ( PP ) 定义为:
[
PP(W) = P(w_1 w_2 ... w_N)^{-\frac{1}{N}}
]
或者等价于:
[
PP(W) = 2^{H(W)}
]
其中 ( H(W) ) 是序列的交叉熵。简单理解,困惑度越低,模型对数据的预测越“不困惑”,性能可能越好。
困惑度的不足
然而,困惑度也不是万能的。即使模型自信且正确地预测了下一个词,这也不一定意味着最终生成的整体结果质量高。
模型没有考虑到它在该句子中选择的其他词的上下文。例如,如果它反复选择同一个词,可能在单个词上有很高的准确率和很低的困惑度,但如果用于翻译或摘要任务,生成的内容很可能毫无意义。
因此,我们需要关注特定任务的评估指标。我们将在下一个视频中详细探讨这些指标。
本节总结

本节课中我们一起学习了评估大语言模型的初步方法。我们了解到,传统的训练损失对于衡量模型最终输出质量帮助有限。困惑度是一个有用的指标,它反映了模型预测的确定性,但它主要关注词级别的准确性,无法全面评估生成文本的整体质量和任务适用性。为了进行更有效的评估,我们必须转向针对具体任务设计的评估体系。
48:特定任务的评估指标 📊

在本节中,我们将学习如何评估大语言模型在特定任务上的表现。我们将介绍几种主流的评估指标,包括用于翻译的BLEU分数和用于摘要的ROUGE分数,并了解社区如何通过基准数据集进行模型比较。最后,我们会探讨前沿的评估方向,如模型的对齐问题。
翻译任务的评估:BLEU分数
上一节我们讨论了通用评估方法,本节中我们来看看针对翻译任务的特定评估指标。对于翻译任务,我们可以使用BLEU指标。该指标通过比较模型输出与我们期望生成的参考翻译样本来进行评估。
在BLEU中,我们计算模型输出与参考样本之间在多个层面的匹配程度。以下是其核心计算步骤:
- 计算单字词匹配:首先,我们计算有多少个“单字词”(即单个单词)同时出现在模型输出和参考样本中。例如,如果一个单词在两者中都出现了六次,那么该单词的单字词匹配得分会很高。
- 计算N元词组匹配:接着,我们扩展到“双字词”、“三字词”和“四字词”的匹配情况。这些N元词组能更好地评估句子的流畅度和结构。
- 综合计算最终分数:BLEU最终会结合所有这些N元词组的匹配精度,计算它们的几何平均值,并施加一个针对过短译文的惩罚因子,从而得出一个总的BLEU分数值。
如果翻译质量很高,模型输出与参考样本匹配得很好,我们就会得到一个较高的BLEU分数。
摘要任务的评估:ROUGE分数
同样地,对于文本摘要任务,我们可以查看ROUGE分数。ROUGE在将参考摘要与模型生成的摘要进行匹配方面,与BLEU非常相似。
然而,ROUGE还额外考虑了摘要的长度,鼓励模型生成尽可能简洁的摘要。如果一个摘要非常冗长,即使它包含了参考样本中的许多词汇,其ROUGE得分也不会特别高。
ROUGE会寻找那些在参考样本和模型输出中都常见的词汇,同时要求模型输出相对于参考样本尽可能精炼。
模型间的比较:基准数据集
但是,如果我们想做的事情不仅仅是使用自己的数据呢?如果我们想将自己的模型与其他模型进行基准测试,我们可能没有他们使用的相同数据集。
这就是社区发挥作用的地方。社区已经创建了基准数据集,以便我们能够评估自己的模型,并与社区中的其他模型进行比较。
例如,SQuAD(斯坦福问答数据集)是一个非常常用的工具包。它包含许多不同的数据集,我们可以用它们来比较不同大语言模型在微调后的性能。
前沿评估方向:对齐问题
最后,一些更前沿的评估指标关注诸如对齐之类的问题。对齐指的是:当我们给一个遵循指令的大语言模型一个特定任务时,它的表现如何。
以下是评估对齐时关注的几个核心方面:
- 相关性:模型给出的结果是否基于我们输入的指令,并且是相关的?
- 幻觉:模型是否会产生“幻觉”(即编造不实信息)?我们将在下一个模块中更详细地探讨这个问题。
- 无害性:模型的回答是否无害?我们可能希望减少回答中的毒性或不恰当内容。
根据特定的用例,研究人员会使用不同的评估指标,并且每天都有更多的新指标被提出。然而,对齐问题仍然是现代大语言模型研究中一个非常关键的组成部分。
总结

本节课中,我们一起学习了针对不同任务的特定评估方法。我们介绍了用于评估翻译质量的BLEU指标和用于评估摘要质量的ROUGE指标。我们还了解了如何利用社区提供的基准数据集(如SQuAD)来比较不同模型的性能。最后,我们探讨了评估模型对齐表现的前沿方向,包括回答的相关性、幻觉和无害性。掌握这些评估工具对于开发和优化实用的大语言模型应用至关重要。
49:嘉宾讲座:LangChain创始人Harrison Chase - 评估LLM链与智能体


在本节课中,我们将学习如何评估基于大语言模型(LLM)构建的链式应用和智能体。我们将探讨评估的难点、潜在的解决方案,以及离线和在线评估的具体方法。
概述:什么是LLM链与智能体?🤔
LLM链与智能体的核心思想是将LLM作为推理引擎。这意味着我们利用LLM来决定如何与其他数据源和计算工具交互。关键在于,我们主要利用这些外部资源的知识,而非LLM本身内嵌的知识。LLM在此更多地扮演一个推理和决策的角色。
一个典型的例子是检索增强生成。我们可以将其理解为一个能够回答关于特定文档问题的聊天机器人,这些文档可能是私有的,并且像GPT-3这样的通用模型并未训练过。
这类应用的一般流程如下:
以下是构建一个检索增强生成应用的关键步骤:
- 压缩聊天历史:将对话历史提炼成一个独立的、完整的问题。这一步很重要,因为后续将用这个问题来检索相关文档。如果不进行压缩,仅使用最后一条消息,可能会丢失对话中提及的先前信息。
- 检索相关文档:使用上一步得到的问题,查找并返回一系列相关文档。
- 生成最终答案:将原始问题、聊天历史和检索到的文档一并输入给LLM,生成最终答案。提示词通常会引导模型基于提供的文档进行回答。
在这个流程中,LLM作为推理引擎,接收问题与数据,并基于这些数据进行推理和回答。整个过程都“扎根”于我们检索到的数据,而不是依赖于LLM自身的知识。
评估为何困难?😰
上一节我们介绍了LLM链的基本概念,本节中我们来看看评估这类应用为何充满挑战。主要困难源于两点:数据缺乏和指标缺乏。
数据缺乏
与传统机器学习问题不同,开发LLM应用通常不是从一个现成的数据集开始的。开发者往往从一个想法或一个待解决的问题出发,开始构建应用,然后才需要评估它。因此,我们缺少传统意义上的训练数据集。
此外,对于许多问题,数据本身可能就在不断变化。例如,许多问答应用需要处理最新的信息,因此可能不存在一个长期不变的“标准答案”。这使得数据收集变得非常困难。
指标缺乏
即使有数据,评估也非易事。首先,肉眼观察评估就很困难。回顾之前的链式流程,任何一个中间步骤(如压缩、检索、生成)都可能出错,而仅凭最终输出,我们很难定位问题具体出在哪一步。
其次,定量评估也很棘手。在上述问答聊天机器人的例子中,最终答案是自由形式的文本。答案中可能包含一个关键事实,这是我们真正需要评估的,但答案周围通常还包裹着大量对话性文本。因此,我们无法使用简单的精确匹配(exact match)来评估,而需要更高级的方法。
潜在的解决方案 🛠️
面对数据与指标的缺乏,一些最佳实践正在形成。
应对数据缺乏
以下是两种常见的获取评估数据的方法:
- 提前生成数据集:可以通过编程方式生成测试集,而LLM本身常常是这个生成过程的一部分。例如,在为文档问答应用生成测试集时,我们可以使用一个链:先将文档分割成块,然后针对每个块,要求LLM生成一个“问题-答案”对,这就构成了应用的测试集。
- 随时间积累数据:如果应用已在生产环境中运行,可以持续记录输入和输出。随着时间的推移,这些记录可以逐渐积累成一个不断增长的数据集。
应对指标缺乏
以下是三种关键的评估策略:
- 可视化检查:最重要的是让链中每一步的输入和输出都易于观察。这有助于理解问题是出在检索了错误的信息,还是检索正确但信息合成不当。
- 使用LLM进行评判:我们可以利用另一个LLM来判断最终答案是否正确。这种方法能处理“事实被大量对话文本包围”的情况。LLM会比较自然语言答案和自然语言标准答案,判断它们是否语义等价。
- 收集用户反馈:可以直接或间接地从用户那里收集反馈。我们将在“在线评估”部分详细讨论。
离线评估 📊
离线评估通常在模型部署到生产环境之前进行,例如在开发完成后进行最终测试,以判断其是否准备就绪。
其主要方法是:创建一个测试点数据集,然后让链或智能体在这些点上运行。接着,可视化检查每次运行的输入和输出,观察每个步骤的表现。当然,这并不具备可扩展性。
因此,下一步是使用LLM进行自动评分。你可以完全信任这个自动评分,让它为每次运行标记“正确”或“错误”,然后计算平均分;或者,你也可以将其作为一种引导工具,找出最可能正确或错误的数据点,然后对这些点进行第二轮更深入的可视化检查。
在线评估 📈
在线评估发生在模型已经部署、正在生产环境中服务用户之后,目的是确保其持续表现良好。
其主要方法是收集每个输入数据点的反馈。
以下是两种收集反馈的方式:
- 直接反馈:在应用中添加“赞”或“踩”按钮,用户可以点击。随着时间的推移跟踪这些反馈。如果你发布了新版本的应用,或者模型因某种原因开始表现不佳,你可能会注意到反馈数据的下降趋势,从而知道需要修复模型。
- 间接反馈:例如,如果你在答案中提供了相关链接,用户点击链接可能意味着你做得好;反之,则可能意味着模型表现不佳。这可以作为一种间接的反馈衡量指标。
这两种方法的共同思想是随时间跟踪这些指标,以便在模型性能开始下降时及时察觉。
总结 🎯


本节课中,我们一起学习了评估LLM链与智能体的核心内容。我们首先了解了LLM作为推理引擎的应用模式,然后探讨了评估面临的两大挑战:数据缺乏和指标缺乏。接着,我们介绍了应对这些挑战的潜在解决方案,包括生成数据、积累数据、可视化检查、使用LLM自动评判以及收集用户反馈。最后,我们详细说明了离线和在线评估的具体实施方法。评估这些应用是一个新兴且令人兴奋的领域,随着越来越多的LLM应用投入生产,相关的知识体系也将不断发展和完善。
50:总结
在本节课中,我们将回顾模块四的核心内容,该模块主要探讨了如何对大型语言模型进行微调,以及如何根据其需要执行的不同任务来评估模型。

上一节我们介绍了模型评估的具体方法,本节中我们来进行整体回顾与总结。
模型选择路径
在决定如何使用大型语言模型时,需要明确选择路径。以下是关键的决策点:
- 是否要从头开始训练一个模型。
- 是否要微调一个现有模型。
- 是选择开源模型还是闭源商业模型。
评估的重要性
为了确保模型输出符合应用需求,选择合适的评估方法至关重要。你需要根据具体的任务目标来确定评估指标。

本节课中我们一起学习了模型微调与评估的核心决策框架。现在,你应该能够更清晰地判断在特定应用场景下,是选择从头训练、微调现有模型还是直接使用预训练模型,并了解如何通过恰当的评估来验证模型性能。

接下来,我们将进入代码实践环节,分别查看一个评估笔记本和一个微调笔记本的具体示例。
51:微调与评估大语言模型:Notebook 演示第一部分

概述
在本节课程中,我们将学习如何对大语言模型进行微调。我们将使用一个具体的例子,将T5-small模型微调为一个电影评论情感分类器。课程将涵盖从环境准备、数据处理到模型训练和推理的完整流程,并简要介绍如何利用DeepSpeed技术进行多GPU训练。
环境准备与数据加载
首先,我们需要确保运行环境支持GPU加速,因为微调过程计算量较大。我们将安装必要的CUDA库和DeepSpeed软件包。
# 检查GPU可用性
assert torch.cuda.is_available()
接下来,我们安装transformers和datasets库,并加载IMDB电影评论数据集。该数据集包含带有“正面”、“负面”或“中性”标签的评论。
from datasets import load_dataset
from transformers import AutoTokenizer, AutoModelForSeq2SeqLM
# 加载数据集和模型
dataset = load_dataset("imdb")
model_checkpoint = "t5-small"
tokenizer = AutoTokenizer.from_pretrained(model_checkpoint)
数据预处理
原始数据集的标签是数值型的(-1, 0, 1)。为了利用大语言模型的自然语言理解能力,我们需要将这些数值标签转换为对应的文本标签(如“negative”, “positive”)。
以下是转换函数:
def convert_labels(example):
label_map = {-1: "unknown", 0: "negative", 1: "positive"}
example["label"] = label_map[example["label"]]
return example
# 应用转换
dataset = dataset.map(convert_labels)
然后,我们使用T5模型的tokenizer对文本进行分词处理,将其转换为模型可接受的输入格式。
def tokenize_function(examples):
return tokenizer(examples["text"], padding="max_length", truncation=True)
tokenized_datasets = dataset.map(tokenize_function, batched=True)
设置训练参数与模型
现在,我们设置训练参数并加载预训练模型。我们将使用Hugging Face的Trainer API来简化训练流程。
from transformers import TrainingArguments, Trainer
training_args = TrainingArguments(
output_dir="./results",
num_train_epochs=1,
per_device_train_batch_size=16,
optim="adamw_torch",
logging_dir="./logs",
)
model = AutoModelForSeq2SeqLM.from_pretrained(model_checkpoint)
我们初始化Trainer,传入模型、训练参数和分词后的数据集。
trainer = Trainer(
model=model,
args=training_args,
train_dataset=tokenized_datasets["train"],
data_collator=default_data_collator,
)
模型训练与监控
我们启动训练过程,并利用TensorBoard监控训练损失等指标。训练将进行一个完整的周期。
trainer.train()
trainer.save_model("./fine_tuned_model")
训练完成后,我们可以观察损失曲线。虽然损失仍有下降空间,但当前模型已可用于演示。
使用微调模型进行推理
现在,我们使用微调后的模型对新的电影评论进行情感预测。
首先,加载保存的模型:
fine_tuned_model = AutoModelForSeq2SeqLM.from_pretrained("./fine_tuned_model")
然后,准备一些示例评论并进行预测:
reviews = [
"This movie was a complete waste of time.",
"An absolutely fantastic film with brilliant performances.",
"I loved every minute of it!",
"The plot was confusing and the acting was poor."
]
# 对评论进行分词
inputs = tokenizer(reviews, return_tensors="pt", padding=True, truncation=True)
# 生成预测
outputs = fine_tuned_model.generate(**inputs)
predictions = tokenizer.batch_decode(outputs, skip_special_tokens=True)
最后,将预测结果整理并展示:
import pandas as pd
results = pd.DataFrame({"Review": reviews, "Predicted Sentiment": predictions})
print(results)
模型成功地将评论分类为“负面”或“正面”,展示了微调的有效性。
使用DeepSpeed进行扩展训练
上一节我们介绍了在单GPU上的标准微调流程。本节中我们来看看如何利用DeepSpeed技术,以便将来在多个GPU或多个节点上进行更大规模的训练。
DeepSpeed是微软开发的一个优化库,可以显著提升大模型训练的效率和规模。虽然我们在单GPU上演示,但代码只需微小改动即可扩展到多GPU环境。
以下是启用DeepSpeed所需的关键配置更改:
# DeepSpeed 配置文件 (ds_config.json)
{
"zero_optimization": {
"stage": 2,
"offload_optimizer": {"device": "cpu"}
}
}
在训练参数中,我们只需添加DeepSpeed配置文件的路径:
training_args_deepspeed = TrainingArguments(
output_dir="./results_deepspeed",
num_train_epochs=1,
per_device_train_batch_size=16,
optim="adamw_torch",
deepspeed="./ds_config.json", # 添加DeepSpeed配置
)
然后,像之前一样创建和运行Trainer。需要注意的是,在单GPU上使用为多节点设计的DeepSpeed配置会产生额外开销,因此训练时间可能更长。但这演示了代码的可扩展性——当切换到真正的多GPU集群时,只需修改环境变量和启动命令,而无需重写核心训练代码。
总结
本节课中我们一起学习了如何对大语言模型进行端到端的微调。
我们首先准备了GPU环境并加载了IMDB数据集。
接着,我们将数值标签转换为文本标签,并对数据进行了分词处理。
然后,我们设置了训练参数,加载了T5-small预训练模型,并使用Trainer API完成了模型的微调训练。
训练完成后,我们保存了模型并演示了如何用它对新评论进行情感分类推理。
最后,我们简要介绍了DeepSpeed技术,展示了如何通过少量代码修改来准备模型,以便在未来需要时利用多GPU资源进行高效训练。

通过这次实践,你将T5-small模型成功微调为了一个电影评论情感分析工具,掌握了微调的核心步骤。在接下来的课程中,我们将学习如何评估微调后模型的性能。
52:评估大语言模型性能

概述
在本节课中,我们将学习如何评估大语言模型在特定任务下的性能。我们将以文本摘要任务为例,使用在课程中提到的Rouge指标,构建一个完整的评估工作流。我们将分析不同模型以及同一模型不同规模版本的表现,并比较它们的结果。
环境准备
首先,我们运行课堂设置脚本,安装必要的库。
评估摘要任务
上一节我们介绍了评估的重要性,本节中我们来看看如何具体评估摘要任务。
我们有一个标准的示例场景:处理新闻文章并生成摘要。但如何判断生成的摘要质量好坏呢?假设你正在开发一个智能手机应用,需要实时显示自动生成的摘要,那么了解模型生成摘要的性能就至关重要。
我们将逐步构建评估流程,直观理解Rouge指标的工作原理及其执行方法。
加载数据集
我们将使用来自CNN Daily Mail的数据集,该数据集提供了新闻文章及其对应的人工摘要(高亮部分)。我们将把这些人工摘要作为评估的“参考答案”。
我们使用Hugging Face的datasets库加载数据,并仅抽取100个样本以加快分析速度。请注意,如果在课堂笔记本之外运行此代码,下载过程可能会更耗时,因为我们已经将部分数据集预加载到了本地缓存目录中。
现在,让我们查看一下刚刚下载的数据集。可以看到,数据集中包含多篇文章,以及对应的“highlights”摘要列。
以下是随机选择的一篇文章及其摘要示例:
文章片段:
(CNN) -- If you're between the ages of 16 and 24, you probably listen to music on YouTube more than any other service, according to a new study...
摘要:
Study: YouTube is top music source for young people.
构建摘要评估流程
接下来,我们讨论摘要的工作原理以及如何使用我们的大语言模型。
我们将下载Transformers库中的AutoTokenizer和T5ForConditionalGeneration,因为我们将使用T5模型。
我们需要创建一个函数来构建评估流程。Rouge分数通过比较生成摘要和参考摘要,进行一元组、二元组和三元组分析,来评估生成摘要的质量。
由于我们从零开始构建,下面将展示如何搭建自己的评估流程。
1. 定义批处理生成器
首先,定义一个批处理生成器。该函数接收数据和批大小参数,并使用yield语句输出数据批次,直到处理完所有数据。
def batch_generator(data, batch_size):
for i in range(0, len(data), batch_size):
yield data[i:i + batch_size]
2. 使用T5模型进行摘要
接下来,定义使用T5模型进行摘要的函数summarize_with_t5。该函数接收模型检查点、文章列表和批大小参数。
该函数将使用T5模型(如T5-base或T5-large)计算摘要。我们假设在CPU上运行此笔记本,但代码也支持在GPU上运行。
我们定义模型和分词器。分词器将输入文本转换为模型可处理的令牌,同时也能将模型输出转换回纯文本。
我们设置最大输入序列长度为1024。
perform_inference函数将利用大语言模型进行推理。我们创建输入,使用分词器处理数据批次,并指定最大长度、填充和截断策略。
然后,我们调用模型的generate方法生成输出摘要的令牌ID,最后使用分词器将这些令牌ID解码为纯文本。
在summarize_with_t5函数内部,我们初始化一个空数组来存储响应。通过为每篇文章添加“summarize: ”前缀来构建模型提示。我们利用之前创建的批处理生成器遍历数据,并将所有生成的摘要添加到数组中。
为了管理内存,在GPU上运行时需要清空缓存并执行垃圾回收。最后,删除分词器、模型等对象以释放内存,并返回包含所有文章摘要的数组。
流程总结:
- 创建批处理生成器。
- 配置分词器和模型。
- 执行推理并生成输出。
- 将输出解码为纯文本。
- 收集并返回所有摘要。
3. 运行T5-small模型
现在,我们将使用T5-small模型检查点。我们可以使用T5-small、T5-base、T5-large等任何兼容此架构的模型。
运行此单元,我们将看到T5模型对示例文章的摘要结果。请注意,运行可能需要一些时间,因为它需要加载所有必要的库和模型信息。
运行完成后,我们得到了T5生成的摘要。现在,我们有了T5生成的摘要和参考摘要,接下来看看如何比较它们。
初步评估:精确匹配
我们首先尝试一种简单的方法来评估模型生成的摘要质量:遍历所有参考摘要,如果生成摘要与参考摘要完全相同,则得分为1,否则为0。
在运行之前,请思考获得完全匹配的摘要的可能性有多大。实际上,由于措辞上的微小差异,这种方法几乎总是得分为0,因此无法有效评估摘要性能。
引入Rouge指标
这引出了Rouge指标。Rouge通过比较摘要的不同子部分来评估性能。
Rouge由四个不同的分数组成:Rouge-1、Rouge-2、Rouge-L和Rouge-Lsum。定义如下:
- Rouge-1:考察一元组(单个词/令牌),匹配参考摘要中的词是否出现在生成摘要中。
- Rouge-2:考察二元组(词对/令牌对)。
- Rouge-L:考察生成摘要和参考摘要之间最长的公共子序列。
- Rouge-Lsum:在摘要级别进行计算,忽略句子分隔符如换行符。
1. 导入Rouge库
我们导入Rouge库来评估模型摘要的表现。需要下载NLTK库用于句子分词,并从Hugging Face的Evaluate库中获取Rouge指标。
2. 计算Rouge分数
我们可以直接使用Rouge评分函数,但需要确保输入格式符合评估库的要求。Rouge要求比较摘要时句子由换行符分隔,而我们的T5模型输出可能不满足此格式。
因此,我们创建一个包装函数compute_rouge_score来确保输入格式正确。该函数接收生成摘要列表和参考摘要列表,使用NLTK的句子分词器分割句子并添加换行符,然后将处理后的文本传递给Rouge评分函数。我们设置use_stemmer=True,稍后会解释其含义。
运行此函数,计算T5-small模型的Rouge分数。
在输出中,我们可以看到每个Rouge值。对于Rouge-1,大约31%的一元组同时出现在生成摘要和参考摘要中。二元组匹配率下降到10%。最长公共子序列得分约为0.2,摘要级得分约为0.3。对于一个小型LLM来说,这是一个不错的分数,但我们相信更大的模型可以表现得更好。
3. 理解Rouge分数范围
为了确认我们对Rouge分数的理解,我们再次运行相同的函数,但这次将参考摘要与自身进行比较。这是摘要模型相对于参考摘要所能达到的最佳情况。可以看到每个分数都为1,表示完美匹配。
但这并不意味着模型完美无缺,仅仅意味着输出与参考摘要一致。在处理大语言模型或任何机器学习任务时,我们必须警惕数据中存在的偏见和质量问题。完美的分数通常意味着存在问题,在本例中,它只是验证了自我比较的逻辑。
同样,我们可以查看最低分。如果生成一个空字符串并与参考摘要比较,我们会得到0分。
因此,Rouge给出的分数介于0和1之间。
4. 词干提取的作用
use_stemmer=True启用了词干提取,可以忽略单词的微小差异。例如:
- 参考摘要:“large language models beat world record”
- 生成摘要:“large language models beating world records”
“beat”与“beating”、“record”与“records”存在细微差别。如果禁用词干提取(use_stemmer=False),Rouge-1得分为67%,Rouge-2为40%。如果启用词干提取,忽略这些差异并认为它们在摘要意义上相同,则得到满分。
是否认为这两个句子差异很大或非常相似,这仍然是大语言模型评估中艺术性的一部分。
5. Rouge在不同场景下的变化
让我们探索Rouge在不同情况下的变化。假设参考摘要包含一个词,而我们的预测摘要实际上长得多。运行Rouge后,我们看到Rouge-1得分相对较好,但Rouge-2为0,因为没有匹配的二元组。
值得注意的是,Rouge分数在预测和参考方面是对称的。如果长度差异是对称的,我们会得到相同的值。
现在,我们使预测摘要变为两个词并与参考摘要匹配,可以看到Rouge-1、Rouge-2、Rouge-L和Rouge-Lsum的值都更高了。
6. 不同Rouge指标的区别
观察Rouge-1与更高阶指标的区别。在Rouge-1中,由于我们匹配每个单词,它实际上没有考虑单词的顺序,因此Rouge-1有时可能是一个误导性的指标。使用更高阶的指标如Rouge-2、Rouge-3和Rouge-L,有助于确保我们获得更高质量的结果。
所有这些努力都是为了尽可能好地评估一项相当主观的任务。
比较不同模型
另一种比较方式是查看同一模型家族中不同规模版本的表现。我们一直在使用T5-small模型,现在让我们看看更大的T5-base模型。
1. 逐行计算Rouge
为此,我们创建一个新函数compute_rouge_per_row。与之前计算整个文章样本集的总体Rouge值不同,此函数将为每个文章计算Rouge分数。这将为每篇文章的生成摘要和参考摘要配对生成一个分数。
2. T5-small模型
正如课程笔记中提到的,当一个开源模型发布时,通常会有多种规模,如base、small和large。T5就是这样一个模型。T5-small是T5模型家族中较小的版本,拥有约6000万参数。
运行我们之前的T5-small模型,可以得到摘要的Rouge分数。使用新的逐行计算函数,我们可以查看每篇文章的Rouge分数,从而更细致地了解模型在不同类型文章上的表现。
3. T5-base模型
T5-base的规模是T5-small的三倍多,拥有2.2亿参数。其架构相同,只是T5-small的放大版(或者说T5-small是T5-base的缩小版)。
运行相同的summarize_with_t5函数,但使用不同的检查点t5-base。由于属于同一模型家族,我们可以重用此函数。
计算T5-base的Rouge分数。完成后,我们可以看到T5-base模型的Rouge分数比T5-small好很多,这对于更大的模型来说是符合预期的。分数大约提高了三分之一,对于一个规模大三倍的模型来说,这种提升在预期之内。
同样,查看逐行Rouge分数,可以看到T5-base的值普遍优于T5-small,这在模型架构升级中是直观的结果。
比较不同架构的模型
接下来,让我们看一个完全不同的模型家族。T5模型是编码器-解码器类型,我们将在课程2中详细讨论大语言模型的不同架构。正如所见,T5可以完成我们要求其他类型模型完成的一些任务。
GPT(生成式预训练变换器)是仅解码器类型的架构。GPT-2唯一的功能就是生成下一个词。GPT-2是OpenAI发布的开源模型,拥有1.24亿参数。让我们看看GPT-2在相同任务上的表现如何。
由于使用不同类型的模型架构,我们需要使用与T5不同的摘要函数。这里我们创建summarize_with_gpt2函数。它接收检查点、文章列表和批大小,与T5模型类似,但需要确保GPT的输入格式处理方式一致。
不同的大语言模型架构和家族需要不同类型的输入,目前行业尚未完全标准化,但正在逐步收敛。
我们的分词器看起来与之前略有不同,但大体相似。我们下载的是GPT-2模型。
执行推理时,我们定义perform_inference函数。需要注意的是,由于GPT只是完成句子并生成新文本,我们需要给它一个提示,让它知道需要生成新文本,而不是响应特定指令。
针对GPT-2的一个特点,我们可以通过在文章前添加“TL;DR”提示字符串来引导它完成文章摘要任务。之前使用T5时,我们添加了“summarize:”提示,这里我们使用“TL;DR”字符串。
生成摘要ID后,我们使用分词器的解码器将摘要令牌ID转换回纯文本。然后收集所有摘要并返回。
后处理摘要时,我们需要确保只获取“TL;DR”语句之后的内容,即生成的摘要部分,而不是文章本身。然后进行一些清理以保护内存使用。
现在,我们准备使用GPT-2创建文章摘要。像对T5-small和T5-base一样运行GPT-2。
完成后,让我们看看GPT-2的Rouge分数。你可能会注意到这些分数实际上比之前看到的要低一些。
系统比较所有模型
我们使用一个辅助函数将所有模型结果放入一个数据框中,以便系统比较。
创建生成摘要和参考摘要的模型结果,并为每个模型放入数据框,然后查看模型的Rouge-1、2、L和Lsum结果。
传入T5-small、T5-base和刚刚创建的GPT-2结果。我们可以看到,在整个数据集上,T5-small表现相当好,T5-base表现最佳,GPT-2表现最差。
考虑到GPT-2的训练方式,它主要被配置为在看到一段文本后生成下一个词,而T5实际上在包括摘要在内的多种任务上进行了更广泛的训练。
现在,我们可以将所有摘要放入一个数据框,并查看每个模型在每行摘要上的得分。
查看每个模型在不同摘要上的表现。对于第一篇文章,T5-small产生某个输出,T5-base有其输出,GPT-2表现尚可。然而,仔细观察GPT-2的表现,有时它会陷入特定的循环。例如,在第30行,它卡在重复生成相同的输出上。
回顾我们在课程笔记中提到的衡量好模型的准确性和困惑度分数,这是一个例子,说明即使我们可能获得较高的准确性或较低的困惑度,但它产生的内容的相关性和准确性实际上并不是我们真正想要的。“Well it's a zoo, it's not a zoo, it's not a zoo”对于模型来说,在生成下一个词的意义上可能是“正确”的,但显然是无意义的。
相比之下,T5-small的输出“A crocodile is the largest species of octopus on the planet”也不正确,但T5-base实际上做得相当好。
你可以自由探索此笔记本,看看是否能找到在不同类型评估中表现更好的模型。
总结


在本节课中,我们一起学习了如何评估大语言模型在文本摘要任务上的性能。我们介绍了Rouge指标及其四个组成部分(Rouge-1, Rouge-2, Rouge-L, Rouge-Lsum),并构建了一个完整的评估工作流。我们比较了T5-small、T5-base和GPT-2模型的表现,发现T5-base在摘要任务上优于T5-small,而GPT-2由于其训练目标和架构差异,在此任务上表现较弱。我们还探讨了词干提取的作用以及Rouge分数的局限性。理解这些评估方法对于开发和部署可靠的摘要应用至关重要。
53: 社会与大语言模型:引言 🧠

在本节课中,我们将要学习大语言模型(LLMs)与社会的关系,探讨使用LLMs时可能面临的风险,以及各种减轻这些风险的方法。
欢迎来到第五模块,本模块的主题是社会与大语言模型,以及使用大语言模型可能带来的一些风险与多种缓解方法。到目前为止,在本课程中我们已经看到了许多关于如何构建和使用LLMs进行各种应用的精彩示例。这些示例通常能做出非常出色的初始演示,并且有方法可以随着时间的推移使其变得更好。然而,其中也存在一些风险。在本模块中,我们希望讨论一些LLMs带来的最大风险,以提高大家对它们的认识,确保人们理解其局限性,同时也介绍一些可用于缓解这些风险的方法。
大语言模型可能产生各种不可靠的行为。这里先快速提及其中两种,稍后我们会讨论如何处理它们。
第一个不良行为的来源与语言建模本身有关。如果你在大量数据上训练你的LLM,特别是来自网络的数据,这些数据可能带有某种形式的偏见,而你的应用程序可能不希望拥有这些偏见。这很糟糕,因为模型学习了这些偏见,因为它只是学习了现存的语言。此外,数据可能还包含某些你不希望语言模型学习的内容。例如,当你构建一个需要政府批准或必须基于最新科学知识的应用程序时,你不希望一个被询问医学问题的语言模型输出网络上的流行观点。网络上存在大量垃圾信息,包括冒犯性文本和搜索引擎优化内容,因此网络数据不一定理想。这是第一个问题:你的语言模型可能学习了你不想让它学习的事实。
第二个例子是所谓的“幻觉”,即当模型并不真正知道该说什么时,它仍然会编造一些内容,并且听起来非常自信。这些是其中的一些风险。
当然,还存在其他风险。我们将讨论一些方法来减轻其中部分风险,并设计出不让用户被误导而承担这些风险的应用程序。这包括诸如基础生成、从经过审查的知识库中检索、评估模型表现的不同方法,以及在这个领域中需要注意的事项。

本节课中我们一起学习了第五模块的引言,了解了LLMs可能带来的社会风险,如数据偏见和模型幻觉,并初步认识了本模块将探讨的缓解策略。
54:模块概述 🧭

在本模块中,我们将探讨大语言模型对社会的影响。请注意,本模块中展示的模型结果有时可能具有冒犯性、偏见或包含有害内容,但这些内容仅用于演示或学习目的。
概述
在本节课中,我们将学习大语言模型的优点与风险、其训练数据的影响、幻觉问题的成因与应对方法,以及如何负责任地使用和管理这些模型。课程结束时,你将能够全面理解大语言模型的社会影响。

上一节我们介绍了本模块的学习目标,本节中我们来看看大语言模型带来的具体益处。
大语言模型正在革新各行各业,它们能帮助减少人力劳动与成本,也能提升内容创作的效率。例如,我们可以要求聊天机器人生成具有营销价值的文案来宣传一款助眠枕头。
此外,大语言模型也能应用于其他客户服务场景。事实上,最近发表的一篇论文表明,使用大语言模型可以提高客户服务案例的解决数量。
我们还可以将大语言模型用作任何网站的聊天机器人,以帮助回答客户问题。
或许最令人兴奋的是,大语言模型有潜力改变我们的学习方式,特别是能满足有特殊需求的学习者。
模块学习目标
在本模块结束时,你应该能熟练掌握大语言模型的优点与风险。
我们还将审视常用于训练大语言模型的数据集,以及这些数据集如何影响模型。
核心问题:幻觉
接下来,我们将详细探讨幻觉的成因与后果。幻觉是当前大语言模型中一个广为人知的问题。
我们将讨论评估幻觉的方法,以及如何缓解幻觉问题,同时也会探讨大语言模型的其他风险与局限。
伦理与治理
最后,我们将通过讨论伦理与负责任的使用方式,以及如何从整体上治理我们的大语言模型来结束本模块。
总结

本节课中,我们一起学习了本模块的总体框架,了解了大语言模型对社会带来的益处与挑战,并预览了我们将要深入探讨的核心议题,包括数据影响、幻觉问题以及伦理治理。在接下来的课程中,我们将逐一展开这些内容。
55:风险与局限

概述
在本节课中,我们将探讨大语言模型(LLMs)在带来巨大变革的同时,所伴随的风险与局限性。我们将从数据来源、模型偏见、滥用风险、就业影响及环境成本等多个角度进行分析,旨在帮助用户和开发者更负责任地使用和构建相关应用。

事物总有两面性,大语言模型也不例外。尽管大语言模型具备应用于众多行业并推动其转型的强大能力,但它们也伴随着自身的风险与局限性。
许多这类风险和局限很难完全消除,尤其当它们源于蓄意、不负责任或恶意的行为者时。我们首先将审视当今大语言模型能力的源泉——数据,并探讨数据如何导致模型偏见。接着,我们将涵盖不同方面的滥用风险,无论其是否出于故意。我们还将讨论大语言模型对就业的潜在影响及其环境成本。
需要指出的是,列举这些风险和局限,并非要求大家完全停止使用大语言模型。相反,我希望本节内容能作为一个邀请,无论你是用户还是开发者,都来思考我们如何能在与这些模型互动、构建这些应用时,共同承担起更多责任。

对创意经济与就业的影响
上一节我们提到了大语言模型的双面性,本节中我们来看看它在社会层面的一些具体影响。

虽然大语言模型能极大提升内容创作效率,但我们也必须承认,它可能削弱创意经济。你可能听说过关于“AI生成的艺术是否仍是艺术”的辩论。我们在此不回答这个问题,但它确实揭示了我们对现有内容的可信度存在不确定性,以及我们是否因未给予艺术家署名、甚至可能侵犯其版权,而损害了艺术产业。
自动化也可能取代工作岗位。美国劳工统计局数据显示,到2029年,客服人员数量将下降4%。此外,AI引入的许多新角色,在技能发展和职业晋升方面可能机会有限。
以下是几个具体例子:
- 数据标注员:从事将文本手动分类或标注(例如,判断文本是否有毒)的工作。这类工作对个人的职业发展可能增长有限。
- 心理健康风险:有报告显示,从事有毒文本分类的人工标注员,由于长期持续接触有害内容,出现心理健康问题的几率更高。
对于我们许多能观看此视频、能接触互联网和尖端技术的人来说,这些技术进步能带来很多好处。但这同时也意味着,欠发达国家中条件较差的人们可能无法享受到技术红利,从而加剧了全球不平等。
环境与财务成本
大语言模型还会产生高昂的环境和财务成本。一个普通美国人平均排放的二氧化碳是全球平均水平的三倍。
从头开始训练一个大语言模型成本极高。根据一篇论文的估算,每训练一千个参数大约需要1美元。而要从头训练一个类似ChatGPT的模型,可能需要花费1.75亿美元。
如此高昂的成本意味着,大多数组织可能只有一次机会将其做对,甚至根本没有机会,特别是对于小型企业或个人而言。
数据偏见与局限性
在当今时代,我们很幸运能够利用大数据构建强大的模型。然而,我们必须记住,大数据并不总是意味着好数据。
需要记住,用于模型训练的大部分数据来自互联网。如果你的祖父母和我的一样,他们可能不怎么使用互联网。这意味着互联网数据往往过度代表了年轻人和发达国家的人群。
不言而喻,大部分训练文本都偏向于英语,具体来说主要是英式英语和美式英语。2021年的一篇论文指出,GPT-2的数据来源于Reddit的外链,但Reddit用户中近70% 是男性,超过60% 的用户年龄在29岁以下。
同样,维基百科条目中只有不到15% 是关于女性的。这篇论文的核心观点是,或许我们今天使用的大语言模型并没有那么“聪明”,它们更像是非常擅长模仿人类说话的“鹦鹉”。
这意味着,如果数据质量不佳,我们很难指望这些语言模型能表现得更好。
我们讨论了数据规模并不能保证数据的多样性。但另一个极具挑战性的事情是审核数据。我们真的有良好的数据质量吗?但当数据量如此庞大时,我们甚至不知从何开始审核。
当模型输入的数据存在偏见时,我们几乎可以肯定模型也会产生偏见。“垃圾进,垃圾出”这句老话同样适用于语言模型。
但当今数据的另一个根本性局限在于,只有特定类型的事件才能成为新闻。例如,和平抗议在报纸上远不如暴力抗议引人注目,因此前者常常未被报道。这意味着我们的模型对此一无所知。
另一个局限是,即使我们能更新数据,我们也无法频繁地更新模型。正如我们之前所确定的,从头训练模型非常昂贵。因此,当我们无法更新数据时,我们就面临着模型过时的风险。
模型毒性、偏见与信息危害
模型可能具有高度毒性、歧视性和排他性,原因在于我们的数据存在缺陷。如果你看本页幻灯片上的例子,会发现右侧在家庭语境中出现的女性形象远多于政治语境。事实上,该论文发现,听起来像女性的名字常常被描绘得权力较小。
我们可以争辩说这是社会的反映,但这确实也意味着,当模型可能嵌入我们并不希望其包含的偏见时,我们需要仔细考虑如何使用这样的模型。
还有一些模型对某些特定人口群体表现出偏见。由于我们提到的数据问题,这些模型对某些语言的表现也可能较差,这并不令人意外。
下一个风险与信息危害有关。这体现在两个方面:
- 我们可能通过泄露或推断私人信息而意外损害隐私。例如,微软聊天机器人Sydney曾意外透露自己是Sydney;员工也可能因与另一个闭源模型互动而意外泄露公司机密。
- 更令人关注的是,大语言模型可能会自信地输出错误信息。例如,它可能暗示伴侣间的暴力行为实际上是好事。
大语言模型还可能助长许多恶意用例,例如欺诈、审查、监视或网络攻击。
最后,这是我们所有人都容易陷入的情况,即过度依赖这项技术,给予这些模型过多的信任。例如,如果我正在与心理健康问题作斗争,向聊天机器人咨询该怎么做可能并不明智。
许多生成的文本表明,大语言模型倾向于产生“幻觉”。我们尚未详细讨论“幻觉”这个术语,但在下一节中,你将了解到什么是幻觉,以及我们如何识别和应对它。

总结
本节课中,我们一起学习了大语言模型在广泛应用背后所隐藏的风险与局限。我们探讨了其对创意经济和就业的冲击、高昂的环境与财务成本、源于数据偏见的模型偏见问题,以及包括信息危害和恶意滥用在内的多种风险。理解这些挑战,是为了让我们作为用户和开发者,能够更清醒、更负责任地使用和开发大语言模型技术。
56:幻觉问题 🧠


在本节课中,我们将要学习大语言模型(LLM)中的一个核心挑战:幻觉。我们将了解幻觉的定义、类型、产生原因以及如何评估它。
我们经常谈论大语言模型的许多输出表明模型正在“幻觉”,但“幻觉”究竟是什么意思呢?
幻觉的定义
根据2022年的一篇论文,幻觉指的是生成的内容无意义或不忠实于提供的源内容。
这意味着模型的输出可能听起来完全自然且流畅。这也意味着即使输出是错误的,模型也可能表现得非常自信。
幻觉的类型
幻觉主要分为两种类型:内在幻觉和外在幻觉。基于个人期望,我们对输出内容的忠实性或事实性可能有不同的容忍度。我们稍后会讨论“忠实”在此语境中的具体含义。
以下是两种幻觉类型的详细说明:
-
内在幻觉:指输出内容直接与源内容矛盾。
- 示例:如果源文本指出“首个埃博拉疫苗于2019年获得FAA批准”,但摘要输出却显示“首个埃博拉疫苗于2021年获批”。这是一个非常明显的矛盾案例,意味着输出不忠实于源文本,同时也完全不具事实性。
-
外在幻觉:指我们无法从源内容验证输出,但模型本身可能并没有错。
- 示例:如果我说“爱丽丝上周首次在击剑比赛中获得一等奖”,然后模型告诉我“爱丽丝上周首次在击剑比赛中获得一等奖,她非常兴奋”。爱丽丝首次获奖并感到兴奋很可能是真的,但我们无法从源内容中验证这一点。这意味着我们无法断定输出对源内容而言是事实性的或忠实的。
幻觉的成因
上一节我们定义了幻觉的类型,本节中我们来看看导致幻觉的几个主要原因。幻觉的产生主要与数据和模型本身有关。
数据相关原因
数据如何收集对模型表现影响巨大。我们在前面的章节讨论过,当数据量庞大时,很难进行有效甚至任何审计。
以下是数据层面可能导致幻觉的几个因素:
- 数据收集过程:我们可能在没有任何事实核查的情况下收集所有可用的文本。
- 重复数据:大多数时候,我们不会过滤掉完全重复的数据。例如,如果同一个Reddit帖子被收录了两次,这就算作重复。重复数据会使模型产生偏见,如果数据中多次出现相同的Reddit讨论,模型就更可能输出来自那些讨论的回应。
- 生成任务的开放性:生成式任务本质上是开放式的。例如,在聊天应用中,我们希望聊天机器人更具吸引力,因此期望它对同一问题能给出更多样化的回应。如果聊天机器人总是重复相同的内容,我们可能不会长久使用它。然而,这种为了提升互动性而追求的多样性,也可能与糟糕的幻觉相关,尤其是在我们需要事实性和可靠输出的场景下。当我们询问医疗文献时,对任何非事实内容的容忍度会远低于询问如何制作完美沙拉时。但生成任务的这种开放性是很难避免的问题,也是我们作为模型和应用使用者必须面对的。
模型相关原因
第二个导致幻觉的方面是模型本身。
以下是模型内部可能导致幻觉的几个机制:
- 不完美的编码器学习:编码器学习了训练数据各部分之间错误的关联。
- 解码时错误:当模型尝试生成文本输出时,解码器可能关注了输入源中错误的部分。
- 解码策略设计:某些解码策略旨在鼓励随机性和意外性。例如,Top-K采样策略不是选择最可能的词元,而是从概率最高的K个候选词元中随机选择一个来生成下一个词元。
- 曝光偏差:简而言之,这意味着模型倾向于基于其自身历史生成的词元来生成后续输出。这也意味着模型可能会偏离主题,例如,你开始询问洗碗机,模型可能转而开始生成关于烘干机的内容。
- 参数知识偏见:总结来说,这意味着模型会固守其已知的知识。所有模型都倾向于基于其记忆的内容生成输出,而不是基于提供的输入。
幻觉的评估
正如之前提到的,评估幻觉是棘手且不完美的,不同个体对模型行为的期望不同,我们判断某些内容是否有害或是否属于错误信息的决策标准也可能大相径庭。
目前有两类指标可以帮助我们评估幻觉。
统计指标
第一类是指标基于统计方法。
以下是几种常见的统计评估指标:
- BLEU和ROUGE:这些指标在自然语言处理领域已存在一段时间。使用这些指标进行评估时,我们发现大约25%的摘要包含幻觉,即包含未被源内容支持的信息。
- BERTScore:该指标通过同时使用源文本和输出文本来衡量幻觉。它实际上在后台使用n-gram来捕捉源内容与目标内容的匹配程度,然后计算F1分数。
- BLEURT:它代表“基于向量的句子相似度回归评估”,用于衡量翻译输出是否与翻译参考具有相同的信息量。
基于模型的指标
第二类指标是基于模型的指标,这意味着我们利用另一个模型来帮助我们评估幻觉。但这类指标也意味着,我们所利用的这些模型本身的任何错误也会随之传播。
以下是几种基于模型的评估方法:
- 信息抽取:这对于任何命名实体识别用例尤其有用,我们试图从中提取知识。我们可以用抽取的结果与语言模型的输出进行比较。
- 基于问答的评估:我们可以通过衡量对同一问题不同答案之间的相似性来评估忠实度。
- 忠实度分类:直接提问:输出是否包含任何未被支持的信息?
- 基于语言模型的评估:使用一个语言模型来计算幻觉词元数量与目标总词元数之比。
如你所见,有多种指标可帮助我们评估幻觉,但没有一种是完美的。在下一节中,我们将讨论缓解策略。
总结

本节课中,我们一起学习了大型语言模型中的“幻觉”问题。我们明确了幻觉是指模型生成不忠实或无意义内容的现象,并区分了内在与外在两种幻觉类型。我们深入探讨了导致幻觉的数据因素(如收集过程、重复数据、任务开放性)和模型因素(如编码错误、解码策略、曝光偏差和参数知识偏见)。最后,我们介绍了评估幻觉的统计指标(如BLEU, BERTScore)和基于模型的指标(如信息抽取、问答评估),并认识到目前尚无完美的评估方案。理解幻觉的成因和评估方法是开发可靠LLM应用的重要基础。
57:缓解策略 🛡️

在本节课中,我们将要学习如何缓解大语言模型的幻觉问题,并探讨应对其各类风险与局限性的策略。

概述
大语言模型虽然强大,但也存在幻觉、偏见、有害输出等风险。本节我们将从数据和模型两个角度出发,介绍具体的缓解策略,并讨论更广泛的风险治理框架。
幻觉的缓解策略
既然幻觉问题源于数据和模型两方面,我们也应从这两个角度来应对。
以下是针对数据层面的策略:
- 构建可靠的数据集:这意味着需要人工参与,根据源文本从头开始编写清晰、忠实的目标文本。
- 改写网络真实语句:也可能涉及让人类实际过滤掉任何不可靠的数据,或对现有数据进行修正。
- 扩充输入数据源:我们可能还需要研究如何用更多数据源来增强输入数据。

上一节我们介绍了从数据入手的方法,本节中我们来看看如何从模型架构与后处理角度进行改进。
以下是针对模型与后处理的策略:
- 加强架构研究与实验:改进当前的建模和推理方法,例如更多地使用强化学习。
- 采用多任务学习:因为幻觉常源于对单一数据集的依赖,多任务学习可能有所帮助。
- 进行后处理校正:这同样需要人工介入和检查。
通用风险与限制的缓解
那么,我们如何普遍地降低所有大语言模型的风险和限制呢?这包括数据偏见、模型毒性、信息危害以及恶意用户等问题。我们需要一个综合的方案来应对它们。
以下是针对不同风险的具体措施:
- 应对数据偏见:我们需要审视数据切片,甚至可能需要更频繁地更新数据。
- 应对模型毒性:这需要多管齐下。首先同样涉及数据评估,但我们也可以引入一些后处理工具,例如来自 Hugging Face 和 Spark NLP 的工具(我们将在后续代码中看到这两个工具)。或者,为这些大语言模型设置防护栏,例如使用 MIEmo guardrails。此外,为微调精心策划更多数据也是一个方向。
- 应对信息危害:我们需要关注信息的来源。这可以包括为微调精心准备数据,或微调你自己的模型。
- 应对恶意用户:要抓住这些不良行为者或恶意行为者,必须要有某种形式的监管。
监管与审计框架
我们在大语言模型中看到的许多风险和限制确实需要某种监管来帮助我们规范其使用。
我们可以从一个三层的角度来思考监管。2023年的一篇论文提出,我们可以在三个独立的层面进行审计,而非单一的治理。
以下是提出的三层审计框架:
- 技术提供商层审计:这意味着审计所有为我们提供使用模型的大型公司。
- 模型层审计:这意味着在模型向公众发布之前对其进行审计。
- 应用层审计:这意味着根据用户实际使用模型的方式来评估这些模型的风险。
待解决的开放性问题
然而,即使有了这个框架,我们作为一个社区仍有一些开放性问题需要回答。
以下是一些关键的挑战与疑问:
- 当我们无法完全确定用户如何与这些模型交互时,我们如何真正把握全局情况?
- 我们如何审计闭源模型?
- 幸运的是,全球各国最近都在进行大量讨论。
- 或许最大的问题是,即使有了审计框架,究竟应该由谁来执行这些审计?任何审计的有效性都取决于执行它的机构。
- 我们还必须认识到,所有大语言模型都不可能实现零风险。因此,我们必须设定一个可接受水平的任意阈值。
- 我们如何捕捉故意的滥用行为?
- 当我们使用大语言模型生成创意产品时,如何真正处理其中的灰色地带?
总结

本节课中我们一起学习了缓解大语言模型幻觉的双重策略(数据与模型),并探讨了应对偏见、毒性等通用风险的方法。我们还介绍了一个三层审计的监管框架,并认识到随着大语言模型技术的发展,社会需要共同应对一系列关于监管、审计和风险阈值的开放性问题。
58:总结与工具展望 🎯
在本节课中,我们将回顾第五模块的核心内容,并展望后续将学习的实用工具。我们探讨了大语言模型的社会影响、当前挑战以及未来的发展方向。

上一节我们讨论了模型评估的挑战,现在我们来对本模块的内容进行总结。
我们提到,大语言模型拥有巨大的潜力,能够变革并真正革新每一个行业。
但从长远来看,除了训练越来越大的模型,我们需要更好的数据来构建更好的模型。
然而,大语言模型普遍存在可能产生幻觉、造成伤害以及在我们过度依赖时影响人类行为的问题。
要恰当地评估大语言模型,我们仍有很长的路要走,并且我们使用的评估指标往往非常主观。
最后,我们需要监管标准来帮助建立某种规范,以界定什么是合乎道德和负责任地使用大语言模型。

现在,是时候转向一些代码,去了解我们可以用来帮助识别和减少模型偏见的一些工具了。

本节课中我们一起回顾了大语言模型的社会影响与主要挑战,包括其潜力、数据需求、潜在风险、评估难点以及对监管标准的需求。同时,我们预告了接下来将进入实践环节,学习具体的代码工具来应对模型偏见问题。
59:社会与大语言模型笔记本演示第一部分 🧪

在本节课程中,我们将学习几种工具,用于检查数据中的表征偏见,并了解一些后处理工具,这些工具可以帮助我们计算文本的毒性分数,并为模型输出生成解释。最后,我们将探讨一项关于生成模型解释的最新研究——对比解释。
与其他笔记本一样,我们首先需要设置课堂环境。
请运行以下两个单元格,设置好环境后,再继续观看视频。
现在,您已经完成了课堂环境的设置。
让我们首先了解用于检查数据偏见的第一种工具:由 Hugging Face 开发的 Disaggregator 工具。顾名思义,它试图对数据进行分解,以便我们能够利用不同的模块,从更细粒度的层面审视数据,这些模块专注于不同主题,例如年龄、性别、宗教、大陆和代词。
为了演示,我们将使用 Wikipedia Biographies 数据集,并选择 代词分解模块,因为在这五个模块中,代词模块分解数据所需的时间最少。您也可以在课后尝试其他模块。
我们承认某些内容可能具有冒犯性,但希望您理解,本课程中开发和使用模型仅用于演示和学习目的。
如果您决定课后尝试性别分解器,请注意,您需要已安装我们在第一个单元格中下载的 spaCy 的 en_core_web_lg 模型。不同模块分解数据所需的时间也不同。
在本例中,我们将使用代词分解模块和维基百科传记数据集。您将看到数据集的第一段传记文本,以及关于维基百科传记的其他信息。
为了使分解器工作,它需要能够对数据调用 .map 函数。这里我们直接使用来自 Hugging Face datasets 库的维基百科传记数据集。
当我们运行这个单元格时,.map 函数(或 API 调用)将尝试将所有维基百科传记归类到 she、her、he、him 或 they 这些类别中。一条数据有可能被标记为符合多个类别。
实际上,让我们仔细查看结果数据框。通过仔细检查分解结果,我们发现它在识别 they 代词方面做得并不好,因为模型并未经过区分 they 代词的训练。它似乎将任何提及物理对象的文本都归类为 they。
例如,我们可以从这个 Markdown 单元格中读取,或者查看这里的 JSON 格式输出:目标文本中并未提及 they,但分解器却将该文本同时归类为 he/him 和 they。
由于该模块的分类不准确,我们将忽略此模型对 they 的分析,因为它并非为此目的而训练。
让我们开始第一步,检查数据表征偏见。
在接下来的单元格中,我们可以看到,在维基百科传记数据集中,he/him 类别占据了压倒性的比例。因此,一个主要在男性数据上训练的模型,或偏向于 he/him 的模型,也会对特定群体表现出更多的偏好性偏见,这并不令人意外。
我们可以通过查看现有的预训练模型(如 BERT)来验证这种偏见。这里,我们从 Hugging Face Transformers 库加载了一个 BERT 模型。
我们将有意地掩码某些标记,要求模型根据前面的历史词序列,输出一些可能的后续词语。
您将看到,对于“女人”和“男人”在职业方面,模型生成的词语列表实际上是不同的。我们将通过 BERT 模型来生成这些列表。
在下一部分,我们将转向另一个名为 Evaluate 的库。Evaluate 库包含多个模块,可以从不同角度评估语言模型。
第一个是 毒性 模块,用于评估生成内容的 problematic 程度,例如仇恨言论。我们将在接下来的单元格中查看它。
第二个模块叫做 HONEST,用于评估生成内容的伤害性。它的工作原理与我们上面看到的类似,因此我们不会详细讨论。如果您好奇,可以点击此处的主页链接,查看为您提供的易于上手的示例代码,以便应用到您自己的数据集。
最后一个模块叫做 Regard,我们将在实验部分查看它,所以请留到后面再看。

首先,我们将加载毒性模块,并尝试评估生成文本的毒性分数。我们可以任意定义超过 0.5 的毒性值为“有毒”,但正如我提到的,这个阈值完全是任意的,您可以选择定义更高或更低的值作为有毒阈值。

这里,我传入三个不同的候选句子,并要求毒性模块评估它们的毒性分数。我们尽量避免输入字面上的脏话,因此您会看到,这里的输出都没有超过 0.5。但通过比较毒性值,我们可以发现,第三个句子确实是三个候选句子中毒性最高的。
至此,我们结束了关于如何评估毒性的讨论。
在下一节,我们将使用 SHAP,这是一种基于博弈论的方法,来帮助我们理解模型为什么会生成某些特定的词语。
本节课总结

在本节课中,我们一起学习了:
- 使用 Disaggregator 工具分解数据,以检查数据集中可能存在的表征偏见,特别是代词使用上的不平衡。
- 通过 BERT 模型验证了数据偏见如何影响模型输出,例如在职业联想上对不同性别产生的差异。
- 利用 Evaluate 库中的 毒性 模块,量化评估生成文本的 problematic 程度。
- 简要介绍了其他评估工具(HONEST, Regard)及其用途。
- 预告了下一部分将使用 SHAP 工具进行模型解释分析。
60:Notebook 演示第二部分 🧪

在本节中,我们将学习如何使用 SHAP 来解释大语言模型的输出。我们将了解 SHAP 的基本原理,并通过代码演示如何将其应用于 GPT-2 模型,以理解模型生成特定词语的原因。最后,我们将探讨一种更先进的“对比解释”方法。
概述:SHAP 解释方法
上一节我们介绍了模型解释的重要性,本节中我们来看看 SHAP 这一具体工具。SHAP 代表 SHapley Additive exPlanations,是一种基于博弈论的方法。它最初为经典机器学习模型设计,但其模型无关的特性使其也能应用于语言模型。
直观地说,SHAP 是一种基于排列组合的方法。它通过固定其他所有输入变量,只改变一个输入变量的值,来观察输出结果的变化。这样,就能将输出的变化归因于那个唯一被改变的输入变量。
以一张图表为例:如果我们想检查“是否允许养猫”这一政策是否影响公寓价格,通过固定其他所有变量(如位置、面积),只改变“养猫政策”,我们确实能看到公寓价格产生了差异。这就是 SHAP 的基本思想。
应用 SHAP 到语言模型
现在,让我们看看如何将 SHAP 应用到语言模型的输出上。
首先,我们将使用 GPT-2 模型来生成文本,并设置一些模型配置参数。
# 模型配置示例
model_config = {
"is_decoder": True, # GPT是解码器模型
"temperature": 0.0, # 关闭随机性,使输出确定
"no_repeat_ngram_size": 2 # 禁止重复的2词序列
}
以下是关键参数的解释:
is_decoder: True:因为 GPT 是一个解码器模型。如果你不清楚解码器模型是什么,不必担心,这涉及到语言模型的基础架构,我们将在后续课程中详细讨论。temperature: 0.0:关闭输出中的随机性。no_repeat_ngram_size: 2:N-gram是指由 N 个词组成的序列。设置为 2 意味着我们不允许任何重复的 2 词序列出现在同一输出中。例如,如果输出中已经出现过“San Francisco”,那么这个词组就不能再次出现。设置此参数需谨慎,因为在某些情况下,我们确实希望模型生成准确的名字(如“Los Angeles”),此时设置此参数就不太合适。
运行配置单元格后,我们从一个示例输入句子开始:
“sunny days are the best days to go to the beach so”
我们期望 GPT-2 能为我们补全这个句子。
为了使用 SHAP,我们需要创建一个解释器对象,并将生成输出的模型以及对应的分词器传递给它。这是因为 SHAP 需要知道我们是如何将句子切分成不同的标记(token)的。
# 创建SHAP解释器
explainer = shap.Explainer(model, tokenizer)
这一步可能需要一些时间运行,因为它正在计算模型输出背后的解释。
计算完成后,我们可以通过绘图来检查 SHAP 的输出。在接下来的单元格中查看 SHAP 值,你会立即看到红色和蓝色的条形。
- 红色 表示对该特定标记(token)有正向贡献。
- 蓝色 表示有负向贡献。
- 颜色的强度(条形的宽度)表示贡献的强弱程度。
将鼠标悬停在某个特定的标记或单词上,可以看到每个词对该输出词的不同贡献值。例如,在补全句子 “…so we are looking” 时,悬停在输出词 “looking” 上,可以看到 “so” 是促成输出 “looking” 贡献最大的词,而 “to” 则是在负方向上贡献最小的词(即它降低了 “looking” 出现的可能性)。
用条形图可以更清晰地看到,“so” 是对输出 “looking” 贡献最大的词。
让我们尝试另一个输入句子:
“I know many people who prefer beaches to the mountains”
然后看看这次 SHAP 会生成什么解释。你会发现 GPT-2 补全了句子,带有偏见地说 “but I‘m not one of them”。同样,你可以悬停在任何输出词上,查看哪个输入词对输出 “not” 或 “them” 贡献最大。在条形图格式中,可以看到 “I” 是对输出 “not” 贡献最大的标记。
SHAP 的局限性与对比解释
虽然 SHAP 能帮助我们找出哪个词或标记对输出某个特定词贡献最大,但我们知道语言模型存在对最近标记的偏见。因此,解释本身也可能带有“近因偏见”,即最近的标记对后续预测标记的影响往往最大。
这是一个很难解决的问题。尽管 SHAP 能让我们了解哪些输入标记可能影响了输出标记,但在某些情况下,它的直接解释可能不那么有用,尽管看起来很有趣。
例如,在这个句子中:
“can you stop the dog from ___”
GPT-2 输出应该是 “barking”。但其他合理的候选词也可能是 “crying”、“eating”、“biting” 等等。如果我们能知道模型为什么输出 “barking” 而不是 “biting” 或 “crying”,将会更有趣。
探索这些“假设性”的词语,而不是单纯解释已输出的词,就是所谓的 “对比解释”。这个概念由一篇 2022 年的论文提出。论文作者认为,与其解释语言模型为什么生成一个词,不如去问语言模型为什么生成这个词,而不是另一个词。

我们不会深入探讨这篇论文的研究细节,但想指出这是一个非常令人兴奋和有趣的研究方向。未来,我们或许能更清楚地理解语言模型选择某个词而非另一个词的原因。

事实上,我们将使用这两位作者编写的一些代码。如果你对他们的研究感兴趣,可以点击提供的链接直接获取他们的代码。我们将使用他们 LM_saliency 文件中的代码,本 Markdown 中的所有相关代码都将归功于他们。
以下是使用对比解释方法的示例:
# 使用对比解释方法
# ‘target_token‘ 是模型实际输出的词(如“barking”)
# ‘foil_token‘ 是其他可行的候选词(如“crying”)
explanation = generate_contrastive_explanation(model, input_text, target_token="barking", foil_token="crying")
你会看到它生成了一些可视化结果,帮助我们理解哪个标记对模型生成 “barking” 而不是 “crying” 贡献最大。
在这个例子中,方框上的分数衡量了每个标记对模型将更高概率分配给目标词(即‘barking’)的影响程度。比较两组解释(barking vs crying)时,可以看到 “stop” 这个词无论在 “barking” 还是 “crying” 的上下文中,贡献度都保持很高。这表明 “stop” 这个词会使模型更倾向于预测 “barking” 而不是 “crying”。而 “the” 这个词,在检查输出 “crying” 的概率时,其贡献度大幅下降,这意味着 “the” 实际上并不影响模型预测 “barking” 还是 “crying”。
总结

本节课中我们一起学习了如何用 SHAP 工具解释大语言模型的输出。我们了解了 SHAP 基于博弈论、通过改变单个输入来归因的基本原理,并实践了如何用其分析 GPT-2 模型的生成结果。同时,我们也认识到 SHAP 可能受“近因偏见”影响。最后,我们介绍了一种更前沿的“对比解释”方法,它通过比较模型为何选择此词而非彼词,提供了更深层的解释视角。这是一个快速发展的研究领域,值得我们持续关注。
61: LLMOps 简介 🚀

在本节课中,我们将要学习如何将大型语言模型应用投入生产环境,并介绍一个名为LLMOps的新兴领域。我们将探讨从原型到可靠生产系统的转变过程,以及为什么需要专门针对大语言模型的操作实践。
从原型到生产:一个故事
上一节我们介绍了本模块的主题。本节中,我们通过一个故事来理解将应用投入生产的重要性。
在我还是博士生时,我参与开发了用于分析的开源框架Apache Spark。当时它只是一个较小的研究项目。许多公司对使用它感到兴奋,并提出了各种用例。但他们总会问:“我能在生产环境中运行它吗?我能真正依赖它,甚至将部分业务押注在它上面吗?”作为一个研究项目,它当时并未完全准备好。因此,随着时间的推移,我们围绕它创立了一家公司,在开源项目的开发中投入了更多精力,最终将其打造成一个非常稳定、可用于生产环境的系统。这需要大量的工作,才能将一个快速变化的、很酷的演示,转变为一个许多人依赖、每天都有任务在运行的可靠应用。
你的LLM应用也是如此。如果它取得成功,那么今天这个很酷的原型,未来必须能够可靠运行,即使周围的世界在不断变化。用户会以不同的方式提问,你所依赖的基础模型(如果是第三方模型)可能会更新,你连接的数据集和知识库也在不断变化。
什么是LLMOps?
正如在机器学习领域我们有MLOps来维护和操作应用一样,对于大型语言模型,我们也需要类似的东西。许多经典的操作理念仍然适用,但大型语言模型在某些方面也有所不同。它们生成非结构化的文本,影响质量的因素也不同。因此,有必要重新思考并审视其中的内容。
LLMOps的核心实践
以下是LLMOps涵盖的一些当今最佳实践,它涉及整个技术栈,而不仅仅是LLM本身。
- 全栈覆盖:实践不仅包括LLM,还涵盖向量数据库、处理链以及端到端的应用程序。
- 监控与改进:包括从监控应用表现到持续改进输出质量的各个方面。
- 灵活协作开发:支持以灵活和协作的方式进行开发。
- 测试与高性能:进行充分测试,确保你的应用获得高性能。
通过遵循这些实践,你可以构建一个坚如磐石的生产级LLM应用。


本节课中我们一起学习了LLMOps的基本概念及其重要性。我们了解到,将一个LLM应用从原型发展为可靠的生产系统需要专门的运维实践,这些实践覆盖了从模型、数据到整个应用栈的监控、测试与持续改进。
62:LLMOps:6.2 模块概述 🚀

在本节课中,我们将学习LLMOps(大语言模型运维)模块的概述。我们将探讨如何将传统的MLOps(机器学习运维)原则应用于大语言模型,并了解将LLM投入生产环境所涉及的端到端工作流、架构以及关键考量因素。
传统MLOps的目标
上一节我们介绍了LLMOps模块的整体目标。本节中,我们来看看其基础——传统MLOps。MLOps在过去几年已成为一个重要领域。随着机器学习和人工智能对企业变得至关重要,企业需要认真对待生产部署。
MLOps主要有两大目标:
以下是MLOps的两个主要目标:
- 维持稳定的性能:这可能意味着围绕模型的KPI,如准确性或其他指标;也可能意味着围绕系统的KPI,如延迟、吞吐量等。
- 维持长期的效率:这可能意味着在需要时自动化手动工作,缩短从开发到生产的迭代周期,并降低不符合任何要求或法规的风险。
在右侧图表中可以看到,就在几年前,“MLOps”这个术语还很少被使用。因此,我们将在下一个视频中花一些时间,介绍传统MLOps的背景知识。
从开发到生产的工作流
为了达成上述目标,我们需要一个系统化的流程。在代码实践部分,我们将逐步讲解部署一个可扩展的、由LLM驱动的数据管道,从开发到生产的完整工作流。


本节课中我们一起学习了LLMOps模块的概述,了解了其源于传统MLOps的两大核心目标:维持稳定性能与保持长期效率。我们还预览了后续将深入探讨的、将LLM投入生产的端到端工作流程。
63:传统MLOps概述 🏗️

在本节课中,我们将要学习传统机器学习运维(MLOps)的核心概念与架构。MLOps是确保机器学习项目从开发到生产能够高效、可靠运行的一系列实践和自动化流程。
概述
上一节我们介绍了LLMOps的目标。本节中,我们来看看其基础——传统MLOps。MLOps可以被定义为 DevOps + DataOps + ModelOps,它是一套用于管理机器学习资产(如代码、数据和模型)的流程与自动化工具,旨在提升性能与长期效率。
传统MLOps高层参考架构
下图展示了一个传统MLOps的高层参考架构。这是一个通用化的示意图,但它清晰地揭示了其中的关键思想。

架构顶部是用于管理代码的源代码控制。底部是湖仓一体(Lakehouse)数据层。中间从左到右,分别是开发(Development)、预发布(Staging) 和生产(Production) 环境。
开发环境
我们从左侧的开发环境开始。例如,数据科学家在此环境中工作,可能进行探索性数据分析(EDA),或编写面向生产的流水线,例如:
- 模型训练流水线
- 特征表刷新流水线
这些代码在准备就绪后,可以被提交到源代码控制系统。这是将ML资产(特别是代码)移向生产环境的主要渠道之一。
湖仓一体数据层
架构底部是湖仓一体数据层。其关键要素在于,它是一个跨多种工具的共享数据层,构成了这个更广泛的MLOps环境和系统集合。
以下是共享数据访问的重要性:
- 在开发环境中操作的数据科学家可能需要读取生产数据的权限来进行问题调试。
- 但他们绝对不应拥有写入权限。
- 这种灵活的、受控的共享数据访问和单一数据源非常有价值。
预发布与生产环境
当我们的代码移向预发布环境时,它会经过持续集成(CI) 测试。这些测试包括快速的单元测试和更长的集成测试,以确保代码在模拟生产环境(拥有相同的服务和流水线集合)中能正常工作。
一旦测试通过,代码就可以移向生产环境。在生产环境中,所有在开发阶段创建和测试的流水线都会被实例化。
以下是生产环境中典型的流水线流程:
- 数据从底部左侧被读取。
- 一个特征表刷新作业以批处理或流式方式将数据写入特征表。
- 这些数据可能被输入到一个自动模型重训练流水线中(例如,每周运行一次)。
- 新产生的模型被放入顶层的模型注册表(Model Registry) 中。
如果你不熟悉模型注册表,可以将其理解为具有特定规范的模型仓库。它记录模型的多个版本,并跟踪每个版本所处的阶段(开发、预发布或生产),管理模型向生产就绪状态的演进。
推动模型进入生产环境的过程由持续部署(CD) 流水线完成。它通过增量发布或多阶段测试,最终将模型标记为可用于生产。随后,模型可以被加载到右侧的推理和 serving 系统中,并进行监控。
总结
本节课中,我们一起学习了传统MLOps的基本架构。它为我们提供了一个关于ML项目如何从开发走向生产、并在此过程中保持管理和监控的粗略框架。现在,我们可以转向下一个核心问题:当我们将一个大语言模型(LLM)引入这个框架时,会发生什么?


64:LLMOps 与传统 MLOps 的对比 🧩

在本节中,我们将探讨 LLMOps 与传统 MLOps 架构的异同。我们将分析当大型语言模型被引入现有机器学习工作流时,哪些部分会发生变化,哪些部分会保持不变。
概述
首先,让我们回顾一下上一节中看到的传统 MLOps 架构图。我们将以此为基础,分析引入 LLM 后可能发生的变化。

发生变化的部分
以下是引入 LLM 后,架构中可能发生变化的几个关键领域。
模型训练
在传统 MLOps 中,模型经常需要频繁地重新训练。然而,对于 LLM 而言,从头开始进行完整的训练通常是不可行的。因此,训练方式可能会被更轻量级的方法所取代。
以下是几种可能的替代方案:
- 模型微调:这种方法仍然会生成一个新的模型,但比完全重新训练要高效。
- 流水线调优或提示工程:这些方法不产生新的模型,而是通过调整输入或处理流程来优化模型性能。
需要注意的是,无论采用哪种方法,这些流水线或微调后的模型,本质上仍然是模型或代码片段。我们现有的 MLOps 基础设施知道如何处理它们。
人工反馈
人工反馈对于 LLM 至关重要。在架构图的左下角,我们对此有两点说明。
首先,来自用户的反馈应被视为从开发到生产阶段都可用的一项重要数据源。这里称之为“数据源”,是因为你可能需要聚合来自内部和外部多个潜在来源的反馈。
其次,传统的、通常可自动化的监控,可能需要通过持续的人工反馈循环来增强。与人工反馈相关的自动化质量测试可能会变得非常困难,因此需要结合人工评估来进行补充。
部署与生产工具
在部署方面,实践可能会有所不同。传统 MLOps 中,你可能会对离线的批量数据集进行测试。而在 LLMOps 中,更可能采用增量式发布策略。
具体做法是:先将模型或 LLM 流水线展示给一小部分用户,观察他们的反应,随着信心的增加,再逐步扩大用户范围。
在生产工具层面,大型模型可能需要将服务从 CPU 迁移到 GPU。你的数据层也可能出现新的对象,例如向量数据库。
成本与性能
成本和性能也可能带来挑战。在架构图的左侧,模型的训练和重新调优可能需要谨慎管理。在服务端,你可能会面临更高的成本,并需要在性能之间进行权衡,尤其是在比较自己微调的模型与付费的第三方 LLM API 时。
需要说明的是,这里的比较是针对“传统 MLOps”而言的。如果你来自计算机视觉或 NLP 背景,已经对深度学习的训练、微调和推理成本很熟悉,那么 LLM 在这方面会显得比较类似。
保持不变的部分
尽管有上述变化,但如果你仔细观察这张图,会发现更多的部分保持不变。
开发、预发布和生产环境的分离,以及执行这些分离的访问控制,仍然相同。用于交付流水线和模型的渠道,仍然是 Git 和模型注册表。用于管理数据的湖仓一体架构仍然至关重要。我们的持续集成基础设施可以复用。MLOps 的模块化结构也得以保留,我们仍然在开发图中方框所示的模块化数据流水线和服务。
总结
本节课中,我们一起学习了 LLMOps 与传统 MLOps 架构的对比。我们看到了在引入大型语言模型后,模型训练、人工反馈整合、部署策略、生产工具以及成本管理等方面可能发生的变化。同时,我们也认识到,开发流程、基础设施、数据架构和模块化设计等核心部分在很大程度上保持不变。既然我们已经提到了这些变化,在接下来的视频中,我们将更深入地探讨其中的一些细节。


65: LLMOps 细节详解

在本节课中,我们将深入探讨LLMOps(大语言模型运维)的具体细节。我们将涵盖从提示工程到模型部署、成本管理以及服务架构等多个关键主题,帮助你理解如何将LLM应用从开发阶段顺利推进到生产环境。
提示工程:从追踪到自动化
上一节我们介绍了LLMOps的总体概念,本节中我们来看看提示工程在运维中的具体实践。提示工程不仅是模型开发的一部分,其某些方面对于生产部署也至关重要。
在提示工程中,你可能会按以下顺序遇到需求:首先是追踪,其次是模板化,最后是自动化。
以下是提示工程实践的具体步骤:
- 追踪:追踪查询和响应,进行比较,并迭代优化提示。在快速迭代和查询量较少的开发阶段,可以使用如MLflow这类提供原生LLM功能的工具进行详细比较。
- 模板化:当需要标准化提示格式时,可以使用如LangChain或LlamaIndex等工具来构建模板。这些工具的API有的会显式处理模板,有的则在底层隐式处理。
- 自动化:最终,你可能希望用更自动化的提示调优来替代手动工程。这类似于传统的超参数调优,使用数据驱动的方法来优化提示。你可以将提示视为一个复杂的超参数,并使用工具如DSP(Demonstrate-Search-Predict)进行调优。
模型与流水线打包部署

接下来,我们讨论如何为部署打包模型或流水线。我们的目标是标准化多种类型模型和流水线的部署流程。
MLflow是我们的首选工具。它提供了所谓的“风味”(flavors),用于以统一格式记录这些不同的模型,这将极大地简化部署工作。
如果你不熟悉MLflow,这里有一个简要总结:它是一个用于机器学习生命周期的开源平台。你可以将各种库或自定义代码记录为MLflow“模型”(一种标准化的元数据格式),连同指标、参数等信息一起记录到MLflow跟踪服务器。随后,模型可以在模型注册表中进行注册,跟踪其走向生产的过程,并最终以多种方式部署,如内联代码、容器、批处理、流式评分或自定义服务等。
关键在于,左边是多种不同的模型或库,右边是多种部署选项,而中间的操作部分需要尽可能标准化,这正是MLflow的作用。
扩展计算与成本性能权衡
现在,我们来看看如何为更大的数据和模型扩展或分布式计算。如果你熟悉传统机器学习,概念是相同的,但工具可能有所变化。
- 对于微调和训练,我们可能会使用分布式TensorFlow、PyTorch、DeepSpeed等工具,它们可以运行在Apache Spark或Ray等现有扩展框架之上。
- 对于实时推理服务,需要可扩展的端点。
- 对于流式和批处理推理,则需要可扩展的流水线,这些可能与传统ML(如Spark和Delta Lake)所用的类似。
在管理成本与性能的权衡方面,我们已涵盖了一些相关主题,如训练和推理的成本,以及通过微调和创建更小模型来降低成本的技术。
以下是一些通用的成本优化建议,具体采用哪种取决于你的应用场景:
- 从简到繁:从使用现有模型和API开始,后续再考虑降低成本或提升性能。
- 投资提示工程:提示工程可以带来显著提升。
- 考虑微调:尤其是在收集到足够的标注数据后,微调模型通常能超越通用模型。
- 预先评估成本:预估开发与生产成本,对于生产环境,需考虑预期的查询负载和单次查询成本。
- 实施降本措施:首先采取快速简便的方法,如缩短查询和响应长度,或调整推理配置以加速计算。然后考虑使用更小的模型,这可能涉及微调,或采用知识蒸馏、量化、剪枝等技术。
- 获取人工反馈:模型运行速度的提升相对容易,但了解对最终用户的影响则需要通过反馈。
- 避免过度优化:这是一个快速变化的领域,六个月后你正在优化的权衡点可能已经不同。
人工反馈、测试与监控
我们已经确立了人工反馈的重要性,因此需要为其做好规划。你可以明确付费获取反馈,但更应考虑从一开始就将反馈机制构建到应用程序中。
在操作层面,人工反馈可以像任何其他数据一样处理,输入到你的数据湖仓中。这意味着它既可用于生产时的监控,也可用于开发阶段的进一步分析和调优。
以下是两个内置隐式反馈的应用示例:
- 左侧:一个生成图像的模型,用户点击图像下载即表示他们喜欢或想要该图像。
- 右侧:一个技术支持机器人,在给出答案的同时提供更多信息的来源链接(暗示答案可能不够充分或某个来源特别相关),或提供一个直接与人工客服聊天的“逃生舱口”(明确表示机器人的回答无用)。
部署模型与部署代码
一个非常有趣且可能复杂的话题是:部署模型与部署代码的区别。你从开发转移到生产的资产是什么?在我们的参考架构中,我们讨论了同时使用版本控制管理代码和模型注册表管理模型。
我强调,在你的架构中可能会同时使用这两者,但重要的是要思考在具体实例中你正在做什么。
为了解释这一点:
- 部署模型:在开发环境中编写代码,生成一个模型,然后将这个具体的模型推向生产环境。
- 部署代码:编写用于生成模型的代码,然后将这段代码推向生产环境。在生产环境中,实际使用的模型是在生产环节创建的。
部署代码听起来可能更昂贵,但请记住,在开发/预演环境中,你可能只在很小的数据集上训练,而真正的训练只在生产环境进行。此外,你在生产环境拥有持续部署流水线,可以在那里进行测试。关键点在于,思考你在哪里进行测试——即你知道在哪个时间点产生最终要投入生产的模型或代码(即那个“工件”),那么你就知道在哪里需要测试它。
以下是一些具体场景的考量:
- 提示工程与流水线编排:可以将其作为代码部署,但考虑将其作为模型部署可能更简单,因为MLflow提供了将其打包为“MLflow模型”并注册到模型注册表的方法。
- 微调或训练模型:两种路线皆可。需根据你的需求、参考架构以及成本来决定。例如,从头训练一个新模型可能耗资百万美元量级,而微调一个模型可能只需100美元,因此在生产环境设置自动化的微调任务可能是合理的,但设置自动化的全新训练任务则可能不合理。
- 多流水线/多模型部署:考虑服务架构(此处使用宽松的定义),基本上考虑将推动这些不同模型或流水线走向生产的各个过程解耦。
服务架构考量
这里提供两个可能频繁出现的例子:
并非所有LLM应用都需要向量数据库,但有些需要。当需要时,需考虑LLM应该将向量数据库作为单独的服务调用,还是将其作为LLM流水线内部使用的本地库或工具。
例如,在进行文档问答时,主要的LLM流水线是文档问答LLM,它会调用向量数据库以获取上下文文档。
- 在上方的示意图中,它被展示为一个批处理作业。在批处理、流式处理设置或演示中,向量数据库可能仅保存在本地缓存中,不作为独立服务。
- 在下方的示意图中,它被抽离为独立服务。当有多个LLM流水线需要调用它,或者你希望解耦以便更独立地更新它们时,这种做法是合理的。
另外请注意,这里可能涉及两个LLM:左侧用于文档问答的主要LLM,以及向量数据库使用的嵌入模型。这个嵌入模型与另一个LLM是相当独立的,可以独立更新。
关于复杂模型与API的稳定性:当你开始思考服务架构时,需要考虑稳定性。但LLM可能具有复杂行为且具有随机性,如何使这些基于LLM的API稳定且兼容?
例如,你正在从左侧的LLM流水线V1.0升级到右侧的V1.1。在这种情况下,你期望什么行为?即使你没有升级模型,你是否期望查询始终返回相同的响应?如果你使用某些付费API(如OpenAI的API),它们会强制你指定API版本,这为你部分解决了这个问题,强制用户维持稳定的API版本。此外,我们在之前的模块中提到过一些推理配置,可以使模型更具确定性,减少随机性。
当你为组织内部或外部用户提供由LLM驱动的API服务时,这一点尤为重要。在这种情况下,请考虑对你的端点进行版本控制,并确保你的用户知道如何设置配置以实现确定性。

本节课中我们一起学习了LLMOps的多个核心细节,包括提示工程的运维实践、模型打包部署、计算扩展与成本优化、人工反馈的集成、部署策略的选择以及服务架构的设计考量。希望这些内容能为你针对具体用例进行规划提供良好的思路。在接下来的代码部分,我们将通过一个从开发到生产的、具有现实扩展性的工作流详细示例进行实践。
66:LLMOps 总结 🎯
在本节课中,我们将回顾第六模块关于LLMOps的核心内容。我们将总结如何通过流程和自动化来确保大语言模型在生产环境中的稳定性能与长期效率。

上一节我们介绍了LLMOps的各个组成部分,本节中我们来对整个模块进行总结。
我们讨论了LLMOps的流程与自动化。这些流程与自动化有助于确保模型性能的稳定性和长期的运行效率。
这与传统的MLOps非常相似,但大语言模型对这些平台提出了一些新的要求。尽管如此,许多部分仍与传统机器学习保持一致。
因此,我们的核心建议是:根据需求,逐一应对LLMOps流程每个步骤中的挑战。
为了应对这些挑战,我们提供了多种工具、技巧、建议以及更多资源的链接。

本节课中我们一起学习了LLMOps的核心理念。我们了解到,虽然LLMOps继承了传统MLOps的许多思想,但针对大语言模型的特性需要新的解决方案。关键在于采用系统化的流程和自动化工具,并灵活运用所提供的资源来应对实际部署中的挑战。
67: Notebook 演示


在本节中,我们将通过一个具体的 Notebook 示例,讲解 LLMOps 的完整流程。我们的目标是:将一个由大语言模型驱动的数据处理管道,从开发阶段推进到生产环境。这个管道本身非常简单,它使用一个预训练模型来总结新闻文章。我们将重点关注其中的运维环节。
概述
我们将构建一个基于 LLM 的数据增强管道。你可以将其类比为一个数据增强流水线:我们输入原始数据,然后利用 LLM 计算出一个或多个新列来增强这些数据。在本例中,这个新列就是文章的摘要。这个模式可以推广到其他场景,例如处理语音、文本或其他可以被 LLM 处理的有趣数据。
我们将按照熟悉的开发、预演和生产三个阶段来推进。在开发阶段,我们将使用 MLflow 跟踪来仔细记录我们的工作,以确保后续的可审计性和可复现性,并使用 MLflow 模型来打包我们的模型或管道,以简化未来的部署。在预演阶段,我们将展示如何使用 MLflow 模型注册表来跟踪模型从测试到生产的进度,并以编程方式演示未来 CI/CD 自动化所需的 API。最后,在生产阶段,我们的目标是构建一个可扩展的批量推理管道,并强调 MLflow 如何帮助我们编写与模型无关的部署代码,从而简化部署工程师的工作。
我们将使用以下工具:MLflow 模型注册表、Apache Spark DataFrames 和 Delta Lake。如果你不熟悉后两者,不用担心,我们会在生产阶段进行介绍。
现在,让我们开始运行课堂设置。在运行期间,请注意,本 Notebook 将使用我们熟悉的数据集和 Transformer 模型:数据集是 XSum 新闻文章数据集,而 T5 Transformer 将作为一个多用途的 LLM 用于摘要生成。
开发阶段:构建与跟踪
上一节我们介绍了本教程的目标和工具。本节中,我们来看看如何开始开发我们的 LLM 管道。
首先,我们导入必要的库并加载数据。对于开发,我们只使用数据集的一个非常小的子集。同时,为了后续生产管道的需要,我们将这个测试子集写入 Delta 格式。
from datasets import load_dataset
from transformers import pipeline
# 加载数据集
dataset = load_dataset("xsum")
# 使用一个小子集进行开发
test_subset = dataset["test"].select(range(10))
接下来,我们创建熟悉的 Hugging Face 管道。这与我们在模块 1 中所做的完全相同。请注意,我们特意提取了用于指定管道配置的参数,以便稍后将其显式记录到 MLflow 中。
# 定义模型参数
model_id = "t5-small"
task = "summarization"
# 创建管道
summarizer = pipeline(task=task, model=model_id)
我们可以调用这个摘要生成器来处理一个特定的文档,并查看生成的摘要。这很简单,也是数据科学家开发 LLM 时所做的全部工作,我们在课程中已经介绍过。
然而,我们想要重点强调的是跟踪。假设我们在一个更大的样本集上进行测试,本例中我们的样本只有 10 行,但它为我们提供了一组查询和响应,我们将把这些记录到 MLflow 中。
以下是 MLflow 中的几个关键术语:
- 实验:MLflow 跟踪中的最高级别组织单元。一个实验通常对应创建一个主要模型或管道。
- 运行:一个运行可能对应一次超参数调优试验,或者在本例中,由于我们每个 Notebook 运行只创建一个管道,因此一个 MLflow 运行对应此 Notebook 的一次执行。
运行不仅可以包含创建的模型或管道,还可以包含其他元数据。现在,让我们看看代码实现。
import mlflow
# 设置 MLflow 实验
mlflow.set_experiment("LLM06_MLflow_Experiment")
# 开始一个 MLflow 运行
with mlflow.start_run():
# 记录模型配置参数
mlflow.log_param("model_id", model_id)
mlflow.log_param("task", task)
# 生成预测(摘要)
documents = test_subset["document"]
summaries = [summarizer(doc, max_length=50)[0]['summary_text'] for doc in documents]
# 记录输入和输出(查询和响应)
mlflow.log_table(data={"document": documents, "generated_summary": summaries}, artifact_file="predictions.json")
# 为模型推断签名(输入/输出模式)
from mlflow.models import infer_signature
signature = infer_signature(documents[0], summaries[0])
# 记录模型到 MLflow
model_info = mlflow.transformers.log_model(
transformers_model=summarizer,
artifact_path="summarization_model",
task=task,
signature=signature,
input_example=documents[0]
)
通过这段代码,我们完成了模型的开发与跟踪。我们记录了配置、预测结果,并将整个管道打包成了一个 MLflow 模型。这个 model_info 对象包含了已记录模型的元数据,例如模型 URI,我们稍后将用它来重新加载模型。
现在,我们可以查询 MLflow 跟踪服务器。在 Databricks 环境中,你可以通过 UI 查看实验和运行。在 UI 中,你可以看到自动记录的源代码链接、我们显式记录的参数以及我们记录的工件。例如,我们记录的预测结果以 JSON 文件形式存储,模型本身也存储在其中,包含了我们记录的模式、环境规范等,这对于可复现性和向生产环境迁移非常重要。
回到 Notebook,我们可以使用保存的模型 URI 重新加载模型。MLflow 可以将其加载为一个普通的 Python 函数进行调用,也可以处理多个文档。
# 加载模型为 Python 函数
loaded_model = mlflow.pyfunc.load_model(model_info.model_uri)
# 对单个文档进行推理
single_summary = loaded_model.predict([documents[0]])
# 对多个文档进行推理(使用 pandas DataFrame)
import pandas as pd
df = pd.DataFrame({"document": documents})
batch_summaries = loaded_model.predict(df)
至此,开发阶段完成,我们准备进入预演阶段。
预演阶段:测试与模型注册
上一节我们完成了 LLM 管道的开发与跟踪。本节中,我们来看看如何将其注册到模型注册表,并通过测试将其推向生产。
首先,我们需要将模型注册到 MLflow 模型注册表。我们生成一个注册名称,然后进行注册。这将创建一个新的注册模型,其初始版本指向 MLflow 跟踪服务器中保存的模型,并处于“None”(即开发)阶段。
# 生成唯一的模型注册名称
import uuid
username = "user" # 在实际中替换为你的用户名
model_name = f"summarizer_{username}"
# 注册模型
mlflow.register_model(model_info.model_uri, model_name)
现在,让我们进入预演环境。我们的目标是测试 LLM 管道,并跟踪该模型在模型注册表中从开发到生产的移动过程。
我们首先导入 MLflow 客户端,它将允许我们以编程方式与 MLflow 模型注册表和跟踪服务器交互,这对于实现 CI/CD 至关重要。
from mlflow import MlflowClient
client = MlflowClient()
# 可以搜索已注册的模型
client.search_registered_models(filter_string=f"name='{model_name}'")
在 UI 中,你可以看到刚刚创建的摘要生成器模型及其初始版本,它链接回定义和创建该管道的源运行,并处于“None”阶段。在 UI 中,拥有权限的管理员可以手动将模型阶段过渡到“Staging”或“Production”,并生成审计日志。但在实践中,我们更倾向于以编程方式完成。
以下是如何以编程方式加载一个已注册的模型。注意,模型 URI 以 models:/ 开头。
# 加载特定版本的已注册模型
stage = "None"
model_version = 1
prod_model_uri = f"models:/{model_name}/{model_version}"
loaded_model_v1 = mlflow.pyfunc.load_model(prod_model_uri)
现在,我们将这个模型版本过渡到“Staging”阶段。客户端中有一个方法可以实现这一点。
# 将模型版本过渡到 Staging 阶段
client.transition_model_version_stage(
name=model_name,
version=model_version,
stage="Staging"
)
过渡后,你可以在模型注册表 UI 中看到状态更新。在实际的 CI/CD 工作流中,你会以编程方式加载处于 Staging 阶段的模型进行测试。测试可以结合我们在评估模块中介绍的自动化和人工方法。这里为了演示,我们假设人工检查了摘要结果并批准了该模型。
# 假设我们进行了测试并批准了模型
print("手动检查摘要结果:通过。模型优秀,应替换当前生产模型。")
# 将模型过渡到 Production 阶段
client.transition_model_version_stage(
name=model_name,
version=model_version,
stage="Production"
)
这样,我们就以编程方式完成了与模型注册表的交互和阶段过渡,这些是实施 CI/CD 工作流的关键要素。现在模型已进入生产阶段。
生产阶段:批量推理与扩展
上一节我们完成了模型的测试与注册。本节中,我们来看看如何在生产环境中部署一个可扩展的批量推理管道。
我们将创建一个批量推理工作流。你可以使用批量推理、流式服务端点甚至边缘设备。这里我们在同一个 Notebook 中展示如何使用 Apache Spark DataFrames 和 Delta Lake 格式进行批量推理。当需要高吞吐量、低成本的扩展作业时,这非常有价值。
首先加载数据。回想一下,在演示开始时,我们将数据保存到了一个 Delta 表中。现在我们使用 Spark 的读取功能将其读入。为了快速演示,我们限制为 10 行,但这展示了基本思路。在实际的大型管道中,我们会使用可扩展的自动伸缩集群来处理更多数据。
# 读取 Delta 格式的测试数据
test_data_path = "/path/to/delta/test_subset" # 替换为实际路径
spark_df = spark.read.format("delta").load(test_data_path).limit(10)
之前我们使用 mlflow.pyfunc.load_model 加载模型,但那是为了 Python 函数。现在,为了在 Spark 上高效处理大数据,我们需要将其加载为 Spark 用户定义函数。
这里的关键点是,这段部署代码是与模型无关的。我们只是说想加载一个可以大规模应用于 Spark DataFrame 的东西。我们传递 Spark 会话对象、模型 URI,并指定用于复制开发阶段环境的 Python 环境管理器。无论底层是 Hugging Face 管道、LangChain 链还是其他类型的 LLM 模型/管道,这段代码几乎不需要改变,因为 MLflow 在底层隐藏了模型或库特定的细节。
# 加载模型为 Spark UDF
import mlflow.pyfunc
udf = mlflow.pyfunc.spark_udf(
spark=spark,
model_uri=f"models:/{model_name}/Production", # 加载生产阶段的最新模型
env_manager="virtualenv", # 确保环境一致
result_type="string"
)
加载 UDF 可能需要一些时间,因为它会设置一个虚拟环境来确保复制开发环境。一旦用户定义函数加载完毕,我们就可以将其应用到 Spark DataFrame 上。
# 应用 UDF 进行批量推理
df_with_summary = spark_df.withColumn("generated_summary", udf("document"))
现在运行批量推理。请注意,这只是在一个小型机器和一小部分数据上运行,但我们所经历的工作流程可以扩展到数百万或数十亿行以及更大的集群。推理完成后,我们可以显示结果。
# 显示推理结果
display(df_with_summary)
得到 Spark DataFrame 后,我们可以将推理结果写入另一个 Delta 表。这里我们的目标是追加模式。
# 将推理结果写入 Delta 表
output_path = "/path/to/delta/llm06_inference_results"
df_with_summary.write.mode("append").format("delta").save(output_path)
我们特别使用了 Delta 格式,因为它与 Spark 和其他扩展技术配合良好,支持批处理和流处理管道,并允许对表进行具有 ACID 事务的并发读写。要创建生产作业,你可以将其转换为 Databricks 或你使用的任何编排工具中的自动化工作流。
总结
本节课中,我们一起学习了一个从开发到生产的完整 LLMOps 示例。我们的管道虽然简单,但对于更复杂的工作流(例如微调自定义模型),其运维过程将非常相似,只是开发过程会被替换。你仍然会遵循开发、预演和生产的基本步骤,并在其中使用我们已经看到的工具:用于跟踪的 MLflow Tracking Server、用于打包的 MLflow Models、用于阶段管理的 MLflow Model Registry、用于批量推理的 Apache Spark 和 Delta Lake,或者用于实时推理的模型服务端点。
我们希望这个详细的演练对你有所帮助,并期待看到你的 LLM 管道在生产环境中运行。




浙公网安备 33010602011771号