add third chapter

This commit is contained in:
skindhu
2024-10-31 13:05:17 +08:00
parent ce615bd123
commit 34d2e1289a
4 changed files with 0 additions and 0 deletions

View File

@@ -0,0 +1,247 @@
本章涵盖以下内容:
- **大语言模型LLMs背后基本概念的高级解释**
- **对大语言模型(如 ChatGPT 平台上使用的模型)所源自的 Transformer 架构的深入了解**
- **从零开始构建大语言模型的计划**
------
- [1.1 LLM 是什么?](#11-llm-是什么)
- [1.2 LLMs 的应用](#12-llms-的应用)
- [1.3 构建和使用 LLMs 的步骤](#13-构建和使用-llms-的步骤)
- [1.4 介绍 Transformer 架构](#14-介绍-transformer-架构)
- [1.5 利用大型数据集](#15-利用大型数据集)
- [1.6 深入剖析GPT架构](#16-深入剖析gpt架构)
- [1.7 构建大语言模型](#17-构建大语言模型)
- [1.8 本章总结](#18-本章总结)
------
大语言模型 (LLMs),如 OpenAI 的 ChatGPT是近年来发展起来的深度神经网络模型。这些模型为自然语言处理 (NLP) 开辟了一个新时代。在大语言模型出现之前,传统方法在电子邮件垃圾分类等分类任务中表现良好,但通常在需要复杂理解和生成能力的语言任务上表现不佳,例如解析详细指令、进行上下文分析,或生成连贯且符合上下文的原创文本。例如,早期的语言模型无法根据关键词列表撰写电子邮件,而这个任务对现代 LLMs 来说却非常简单。
LLMs 具备理解、生成和解释人类语言的卓越能力。然而,我们需要澄清的是,当我们说语言模型“理解”时,并不是说它们具有人类的意识或理解能力,而是指它们能够以看起来连贯且符合上下文的方式处理和生成文本。
得益于深度学习的进展,深度学习是机器学习和人工智能 (AI) 的一个子集主要关注神经网络LLMs 可以基于深度学习理论在海量文本数据上进行训练。这使得 LLMs 能够捕捉到比以往方法更深层的上下文信息和人类语言的细微差别。因此LLMs 在各种自然语言处理 (NLP) 任务中的表现得到了显著提升,包括文本翻译、情感分析、问答等。
当代 LLMs 与早期 NLP 模型之间的另一个重要区别在于,早期的 NLP 模型通常是为特定任务而设计的,例如文本分类、语言翻译等。虽然这些早期模型在其特定应用中表现出色,但 LLMs 在各种自然语言处理 (NLP) 任务中展现了更广泛的能力。
LLMs 的成功可以归因于支撑 LLMs 的 Transformer 架构,以及 LLMs 训练所用的海量数据。这使得它们能够捕捉到多种语言的细微差别、上下文和模式,而这些都是难以手动编码的。
这种转向基于 Transformer 架构的模型和大规模训练数据集来训练 LLMs已经从根本上改变了自然语言处理 (NLP) 领域,为理解和与人类语言互动提供了更强大的工具。
从本章开始,我们将奠定实现本书主要目标的基础:通过逐步在代码中实现一个基于 transformer 架构的类似 ChatGPT 的 LLM以帮助理解 LLMs。
## 1.1 LLM 是什么?
LLM大语言模型是一个旨在理解、生成和响应人类文本的神经网络。这些模型是深度神经网络在海量文本数据上训练基本涵盖了互联网上大部分公开可用的文本数据集。
“大语言模型”中的“大”指的是模型的参数规模和用于训练的庞大数据集。这类模型通常包含数十亿甚至数百亿的参数,这些参数是网络中的可调节权重,训练过程中通过优化来预测序列中的下一个单词。预测下一个单词是合理的,因为这利用了语言的序列特性,帮助模型理解文本中的上下文、结构和关系。然而,这只是一项非常简单的任务,因此许多研究人员对其能够产生如此强大的模型感到惊讶。我们将在后面的章节中逐步讨论并实现下一个单词的训练过程。
LLMs 采用了一种称为 Transformer 的架构(在第 1.4 节中将详细讨论),这使得它们在做预测时能够对输入的不同部分进行选择性关注,因此特别擅长处理人类语言的细微差别和复杂性。
由于 LLMs 能够生成文本,因此它们通常被称为一种生成式人工智能 (AI),常缩写为生成 AI 或 GenAI。如图 1.1 所示,人工智能涵盖了创造能执行类似人类智能任务的更广泛领域,包括理解语言、识别模式和做出决策,并包括机器学习和深度学习等子领域。
<img src="../Image/chapter1/figure1.1.png" width="75%" />
用于实现人工智能的算法是机器学习领域的核心。机器学习具体涉及开发可以从数据中学习并基于数据做出预测或决策的算法,而不需要明确的编程。举例来说,垃圾邮件过滤器就是机器学习的一个实际应用。与其手动编写规则来识别垃圾邮件,不如将标记为垃圾邮件和合法邮件的电子邮件示例输入给机器学习算法。通过最小化训练数据集上的预测误差,模型能够学习识别垃圾邮件的模式和特征,从而将新邮件分类为垃圾邮件或合法邮件。
如图 1.1 所示,深度学习是机器学习的一个子集,专注于使用三层或更多层的神经网络(即深度神经网络)来建模数据中的复杂模式和抽象。与深度学习不同,传统机器学习需要手动提取特征。这意味着人类专家需要识别并选择最相关的特征供模型使用。
虽然当前人工智能领域主要由机器学习和深度学习主导,但它也涵盖了其他方法,例如基于规则的系统、遗传算法、专家系统、模糊逻辑和符号推理。
回到垃圾邮件分类的例子,在传统机器学习中,人类专家会手动提取电子邮件文本中的特征,例如某些触发词的频率(“奖品”、“获胜”、“免费”)、感叹号的数量、全大写单词的使用,或者是否存在可疑链接。基于这些专家定义的特征创建的数据集随后用于训练模型。与传统机器学习不同,深度学习不需要手动提取特征,这意味着人类专家不需要为深度学习模型识别和选择最相关的特征。(不过,无论是在传统机器学习还是深度学习的垃圾邮件分类中,仍然需要收集标签,如垃圾邮件或非垃圾邮件,而这些标签需要由专家或用户进行收集。)
接下来的章节将介绍 LLMs 能解决的问题、LLMs 面临的挑战,以及我们将在本书中实现的通用 LLM 架构。
## 1.2 LLMs 的应用
由于具备解析和理解非结构化文本数据的高级能力LLMs 在多个领域有着广泛的应用。目前LLMs 被广泛用于机器翻译、新文本生成(见图 1.2、情感分析、文本摘要等多种任务。最近LLMs 还被用于内容创作,比如撰写小说、文章,甚至计算机代码。
<img src="../Image/chapter1/figure1.2.png" width="75%" />
LLMs 还可以支持复杂的聊天机器人和虚拟助手,例如 OpenAI 的 ChatGPT 或谷歌的 Gemini以前称为 Bard这些助手能够回答用户的问题并提升传统搜索引擎的功能如 Google Search 和 Microsoft Bing。
此外LLMs 还可以有效地从医学或法律等专业领域的大量文本中检索知识。这包括筛选文档、总结长段落以及回答技术性问题。
总之LLMs 在自动化几乎所有涉及文本解析和生成的任务中都是不可或缺的。它们的应用几乎是无限的随着我们不断创新和探索这些模型的新用法LLMs 显然有潜力重新定义我们与技术的关系,使其变得更加对话式、直观和易于访问。
在本书中,我们将重点了解 LLMs 的工作原理,从基础开始,编码一个能够生成文本的 LLM。我们还将学习使 LLMs 执行查询的技术,包括回答问题、总结文本、将文本翻译成不同语言等。换句话说,在本书中,我们将通过一步步构建的方式,学习复杂的 LLM 助手(如 ChatGPT的工作原理。
## 1.3 构建和使用 LLMs 的步骤
为什么我们应该构建自己的 LLMs从头开始编码一个 LLM 是理解其工作机制和局限性的绝佳练习。同时,这也使我们具备了对现有开源 LLM 架构进行预训练或微调的知识,以便将其应用于我们特定领域的数据集或任务。
研究表明,在建模性能方面,专为特定任务或领域定制的 LLMs 通常能超过通用的 LLMs比如 ChatGPT这些通用模型设计用于多种应用场景。例如BloombergGPT 是一个专门针对金融领域的模型,还有针对医学问答定制的 LLMs有关更多细节请参阅附录 B 的进一步阅读和参考文献部分)
使用定制的 LLMs 有多个优势,尤其是在数据隐私方面。例如,公司可能因为保密问题而不愿与像 OpenAI 这样的第三方 LLM 提供商分享敏感数据。此外,开发定制的 LLMs 可以直接在客户的设备上部署,比如笔记本电脑和智能手机,这是像 Apple 这样的公司当前正在探索的方向。这种针对LLMs的本地部署实现能够显著降低响应延迟和服务器相关的成本。同时定制的 LLMs 使开发者拥有完全的自主权,能够根据需要控制模型的更新和修改。
创建 LLM 的一般过程包括预训练和微调。术语 "pre" 在 "pretraining" 中指的是初始阶段,此时模型(如 LLM在一个大型且多样化的数据集上进行训练以便获得对语言的广泛理解。预训练模型随后作为基础资源可以通过微调进一步优化。微调是指模型在一个更针对特定任务或领域的数据集上进行专门训练。包含预训练和微调的这种两阶段训练方法在图 1.3 中进行了说明。
> [!TIP]
>
> **个人思考:** 预训练的数据集已经学习好了语言模型的基础能力包括语法、词汇、语言结构可以相对准确的预测下一个token。而微调则是利用特定领域的数据来让模型适应某些特定的任务。微调一般有两种方式
>
> + 全权重的微调,这种方式会在训练过程中对模型的所有预训练权重进行调整,但由于权重已经经过预训练,大多数情况下,微调只会对预训练权重进行微小调整,而不是大幅度改变。这种方式能够让模型保持原有的语言生成能力,同时使其在特定任务上表现得更好。
> + 冻结部分权重的微调,一般冻结低层(往往是学习到的基础语言特征),对高层的权重进行调整。这种微调方式常在需要加速训练,或者数据量较小,全权重微调可能导致过拟合的情况下使用。
<img src="../Image/chapter1/figure1.3.png" width="75%" />
如图 1.3 所示,创建 LLM 的第一步是用大量文本数据进行训练,这些数据一般被称为原始文本。这里的 "raw" 指的是这些数据只是普通文本,没有任何标注信息[^1] 。(可以进行过滤,比如去除格式字符或未知语言的文档。)
LLM 的第一阶段训练被称为预训练,旨在创建一个初始的预训练 LLM通常称为基础模型。GPT-3 模型是一个典型例子ChatGPT 中原始模型的前身)。该模型可以完成文本补全,即对用户写了一半的句子进行续写。同时,它还具有有限的少量示例学习能力,这意味着它可以在仅有少量示例的情况下学习执行新任务,而不需要大量的训练数据。下一节“介绍 transformer 架构”将对此进行进一步说明。
在从大型文本数据集上训练得到预训练的 LLM 后该LLM会学习预测文本中的下一个单词。我们可以在优质的标注数据上对 LLM 进行进一步训练,这个过程称为微调。
微调 LLMs 的两个最流行的类别是指令微调和分类任务微调。在指令微调中,标注数据集包含指令和答案对,例如用于翻译文本的查询及其正确翻译。在分类微调中,标注数据集由文本及其对应的类别标签组成,比如与垃圾邮件和非垃圾邮件标签相关的电子邮件。
在本书中,我们将介绍 LLM 的预训练和微调的代码实现,并将在预训练基础 LLM 后,深入探讨指令微调和分类微调的具体细节。
## 1.4 介绍 Transformer 架构
大多数现代 LLM 基于 transformer 架构,这是一种深度神经网络架构,首次在 2017 年的论文《Attention Is All You Need》中提出。为了理解 LLM我们需要简要回顾一下最初为机器翻译开发的原始 Transformer该架构用于将英文文本翻译成德文和法文。图 1.4 显示了 Transformer 架构的简化版本。
<img src="../Image/chapter1/figure1.4.png" width="75%" />
图 1.4 中的 Transformer 架构由两个子模块组成:编码器和解码器。编码器模块处理文本输入,将其编码为一系列数值表示或向量,以捕捉输入的上下文信息。然后,解码器模块利用这些编码向量生成输出文本。例如,在翻译任务中,编码器将源语言文本编码为向量,而解码器则将这些向量解码为目标语言的文本。编码器和解码器都由多个层通过自注意力机制相连。您可能会对输入的预处理和编码过程有许多疑问,这些将在后续章节的逐步实现中详细解答。
Transformers 和 LLMs 的一个关键组成部分是自注意力机制(图中未显示),它使模型能够相对地权衡序列中不同单词或标记的重要性。这个机制帮助模型捕捉输入数据中的远程依赖关系和上下文关系,从而提高生成连贯且与上下文相关的输出的能力。不过,由于自注意力机制的复杂性,我们将在第三章中逐步讨论和实现它。此外,第二章《处理文本数据》中,我们也将讨论并实现创建模型输入所需的数据预处理步骤。
> [!TIP]
>
> **个人思考:** 早期用于翻译任务的模型一般使用RNNRNN的核心是循环结构也就是会把当前的输出和之前的状态结合起来再输入到下一步。这样网络就可以记住前面输入的信息并把这些信息应用到后续的预测中。例如当你输入一段文字时RNN会记住前面的单词以帮助理解后面的单词。但这种机制也有一个非常明显的不足长距离依赖问题。虽然RNN能“记住”前面的信息但它对非常长的序列记忆能力有限。随着序列变长早期信息会逐渐被“遗忘”导致长距离依赖的问题。这就像你在听一长段话可能会逐渐忘记开头说的内容。而Transformer架构通过自注意力机制后面详细介绍实现机制实现能够关注序列中的任意位置而不需要经过层层传递。因此无论信息在序列中距离多远Transformer都能有效地捕捉和利用长距离的依赖关系。
Transformer 架构的后续变体,包括 BERT双向编码器表示来自 Transformers 的缩写)和各种 GPT 模型(生成预训练变换器的缩写),都是基于这一概念进行构建的,以适应不同的任务。(参考文献见附录 B。
BERT 是基于原始 Transformer 架构的编码器子模块,与 GPT 的训练方法有所不同。GPT 主要用于生成任务,而 BERT 及其变体则专注于掩码词预测,即模型在给定句子中预测被掩码或隐藏的词,如图 1.5 所示。这种独特的训练策略使得 BERT 在文本分类任务中具备优势包括情感预测和文档分类。作为应用实例截至目前Twitter 正在使用 BERT 来检测有害内容。
> [!TIP]
>
> **个人思考:** 为什么BERT适合用于文档分类或情感预测这主要是基于BERT的训练模式BERT也是基于Transformer架构但它采用的是 **masked language model (MLM)** 训练方式即在训练过程中它会随机遮掩输入句子中的一些词称为“masked”并让模型预测这些被遮掩的词。这种训练策略被称为**掩蔽词预测**。这一独特的训练方法使得 BERT 能够更好地理解句子的上下文因为它需要根据整句话的前后部分来预测被遮掩的词。这种双向bidirectional的训练使得 BERT 更适合处理需要全局上下文理解的任务,而文档分类或情感预测正是两种对于上下文语义理解要求非常高的场景。
<img src="../Image/chapter1/figure1.5.png" width="75%" />
另一方面GPT 专注于原始 Transformer 架构中的解码器部分,被设计用于需要生成文本的任务。这些任务包括机器翻译、文本摘要、小说创作和编写代码等。在本章接下来的部分,我们将更详细地讨论 GPT 架构,并在本书中从零开始实现它。
GPT 模型主要是为文本补全任务设计和训练的但它们在能力上展现出显著的多样性。这些模型擅长执行zero-shot 和few-shot 学习任务。zero-shot 学习指的是在没有先前具体示例的情况下能够处理完全未见过的任务。而few-shot 学习则是指模型可以从用户提供的极少量示例中进行学习,如图 1.6 所示。
<img src="../Image/chapter1/figure1.6.png" width="75%" />
> [!NOTE]
>
> **TRANSFORMERS 与 LLM**
>
> 如今的 LLM 大部分是基于上一节提到的 Transformer 架构来实现。因此在文献中Transformers 和 LLMs 常常被视为同义词。然而,值得注意的是,并非所有的 Transformers 都是 LLM因为它们也可以用于计算机视觉。同时并非所有的 LLM 都是基于 Transformers 的,市场上也有一些基于递归和卷积架构的大语言模型。这些替代方法的主要目的是提高 LLM 的计算效率。不过,这些替代架构能否与基于 Transformer 的 LLM 的能力相竞争以及它们是否会在实际中得到应用还需要进一步观察。为了简单起见本书将“LLM”一词用来指代类似于 GPT 的基于 Transformer 的 LLM。感兴趣的读者可以在本章末尾的进一步查找找到相关文献阅读。
## 1.5 利用大型数据集
流行的 GPT 和 BERT 类模型的大型训练数据集代表了丰富而全面的文本语料库,涵盖数十亿个单词,涉及各种主题以及自然语言和计算机语言。为了提供一个具体的例子,表 1.1 总结了用于预训练 GPT-3 的数据集,这个模型是第一版 ChatGPT 的基础。
<img src="../Image/chapter1/table1.1.png" width="75%" />
通过表1.1能得出的主要结论是,这个训练数据集的规模和多样性使得这些模型在各种任务中表现优异,包括不同语言的语法、语义和上下文信息,甚至还可以处理一些需要通用知识的任务。
> [!NOTE]
>
> **GPT-3 数据集细节**
>
> 表 1.1 展示了用于 GPT-3 的数据集。表中的“占比列(最后一列)”经过四舍五入误差调整后总和为 100%。尽管“token数量”这一列中的总计达到 5090 亿,但模型实际只在 3000 亿个token上进行训练。GPT-3 论文的作者没有解释为何模型没有在所有 5090 亿个token上进行训练。
>
> 对于上下文来说,考虑 CommonCrawl 数据集的规模,该数据集单独包含 4100 亿个token存储需要大约 570 GB。相比之下像 GPT-3 这样的模型后续版本,例如 Meta 的 LLaMA已经扩展了其训练范围加入了额外的数据来源比如 Arxiv 的研究论文92 GB和 StackExchange 的代码相关问答78 GB
>
> GPT-3 论文的作者没有公开训练数据集,但有一个与其相当的公开数据集名为 Dolma由 Soldaini 等人在 2024 年发布的《用于 LLM 预训练研究的三万亿token开放语料库》https://arxiv.org/abs/2402.00159)。不过,该数据集可能包括受版权保护的作品,其具体使用条款可能会根据预期的使用场景和国家有所不同。
这些模型的预训练特性使它们在后续任务中的微调变得非常灵活,因此它们也被称为基础模型或基模。预训练 LLM 需要消耗大量资源且成本非常高。例如GPT-3 的预训练费用估计为 460 万美元,通过云计算积分来计算[^2]。
好消息是已经有许多经过预训练的开源LLM可以作为通用工具来撰写、提取和编辑不在训练数据中的文本。此外LLM可以在相对较小的数据集上进行微调以减少所需的计算资源并提高特定任务的性能。
在本书中,我们将一步步实现预训练代码,并利用它来预训练一个 LLM。所有的计算都将在普通消费级硬件上进行。在实现预训练LLM的代码后我们将学习如何重用公开可用的模型权重并将这些权重加载到我们所实现的架构中这样在本书后续微调 LLM 时,我们就可以跳过昂贵的预训练阶段。
## 1.6 深入剖析GPT架构
在本章之前我们提到了GPT类模型、GPT-3和ChatGPT。现在让我们更深入地了解通用的GPT架构。首先GPT 是“生成预训练变换器”Generative Pretrained Transformer的缩写最初是在以下论文中提出的
+ 《通过生成预训练改善语言理解2018》是由 OpenAI 的 Radford 等人撰写的链接http://cdn.openai.com/researchcovers/language-unsupervised/language_understanding_paper.pdf
GPT-3 是该模型的增强版,具有更多参数,并在更大的数据集上进行训练。而在 ChatGPT 中提供的原始模型是通过在一个大型指令数据集上微调 GPT-3 而创建的,这一过程使用了 OpenAI 的 InstructGPT 论文中的方法,我们将在第 7 章“使用人类反馈进行微调以遵循指令”中详细介绍。如图 1.6 所示,这些模型在文本完成方面表现出色,并且还能够进行拼写纠正、分类和语言翻译等其他任务。考虑到 GPT 模型是在相对简单的下一个单词预测任务上进行预训练的,这一点确实非常惊人,如图 1.7 所示。
<img src="../Image/chapter1/figure1.7.png" width="75%" />
下一个单词预测任务是一种自监督学习的方法,这是一种自我标注的形式。这意味着我们不需要专门收集训练数据的标签,而是可以利用数据本身的结构:我们可以把句子或文档中的下一个单词作为模型需要预测的标签。由于下一个单词预测任务允许我们“动态”生成标签,因此我们可以利用大量未标记的文本数据集来训练 LLM这在第 1.5 节中也有讨论,即利用大型数据集。
与我们在 1.4 节讨论的原始 Transformer 架构相比,通用 GPT 架构相对简单。实际上,它仅包含解码器部分,而没有编码器,如图 1.8 所示。由于像 GPT 这样的解码器模型是通过逐字预测生成文本,因此它们被视为一种自回归模型。自回归模型会将之前的输出作为未来预测的输入。因此,在 GPT 中,每个新词的选择都是基于之前的文本序列,这样可以提高生成文本的连贯性。
像 GPT-3 这样的模型架构明显大于原始的 Transformer 模型。例如,原始的 Transformer 将编码器和解码器块重复了六次,而 GPT-3 具有 96 层 Transformer总共有 1750 亿个参数。
<img src="../Image/chapter1/figure1.8.png" width="75%" />
GPT-3 于 2020 年推出按照深度学习和大语言模型LLM开发的标准如今看来已经是很久以前了。然而像 Meta 的 Llama 模型这样的最新架构依然基于相同的基本原理,仅做了些许修改。因此,理解 GPT 的重要性依旧不减。本书将专注于实现 GPT 背后的核心架构,并提供有关其他 LLM 所采用的特定调整的参考。
最后,值得注意的是,原始的 Transformer 模型由编码器和解码器块组成,专为语言翻译设计,但 GPT 模型虽然架构更大且仅包含解码器,却主要用于下一个词预测,但同时也具备执行翻译任务的能力。这一能力最初让研究人员感到意外,因为它出现在一个主要针对下一个词预测任务的模型中,而这个任务并没有专门针对翻译。
模型能够执行未明确训练的任务被称为“涌现行为”。这种能力不是通过明确的训练获得的而是模型接触大量多语言数据和多样化上下文后自然而然涌现的结果。GPT 模型能够“学习”不同语言之间的翻译模式,并执行翻译任务,尽管它们并没有专门针对这些任务进行训练,这展示了这些大语言模型的优势和能力。我们可以在不需要为每个任务使用不同模型的情况下,完成多种任务。
## 1.7 构建大语言模型
在本章中我们为理解LLM打下了基础。在本书的其余部分我们将从零开始编码一个 LLM使用 GPT 的基本理念作为框架,并分为三个阶段进行,如图 1.9 所示。
<img src="../Image/chapter1/figure1.9.png" width="75%" />
首先,我们将学习基本的数据预处理步骤,并编写 LLM 核心的注意力机制代码。
接下来,在第二阶段,我们将学习如何编写代码并预训练一个类似 GPT 的 LLM能够生成新的文本。同时我们还会介绍评估 LLM 的基本原理,这对开发强大的 NLP自然语言处理系统至关重要。
请注意,从头开始预训练一个 LLM 是一项庞大的工程,对于类似 GPT 的模型,计算成本可能高达数千到数百万美元。因此,第二阶段的重点是进行教学目的的训练,使用小型数据集。此外,本书还将提供关于如何加载公开可用的模型权重的代码示例。
最后,在第三阶段,我们将使用一个预训练好的 LLM对其进行微调使其能够执行指令例如回答查询或进行文本分类——这些是在许多现实世界应用和研究中最常见的任务。
希望你能期待踏上这段激动人心的旅程!
## 1.8 本章总结
+ LLM 已经彻底改变了自然语言处理的领域之前自然语言处理主要依赖于显式的规则系统和较为简单的统计方法。LLM 的出现引入了新的深度学习驱动的方法,推动了对人类语言的理解、生成和翻译的进步。
+ 现代 LLM 的训练通常分为两个主要步骤:
+ 首先,它们在一个大型未标注的文本语料库中进行预训练,通过使用句子中下一个单词的预测作为“标签”。
+ 这些模型接下来会在一个较小的、有标签的目标数据集上进行微调,以遵循指令或执行分类任务。
+ LLM 基于Transformer架构。Transformer架构的核心理念是注意力机制这使得 LLM 在逐字生成输出时,能够选择性地访问整个输入序列。
+ 原始的Transformer架构由一个用于解析文本的编码器和一个用于生成文本的解码器组成。
+ 生成文本和执行指令的 LLM例如 GPT-3 和 ChatGPT仅实现解码器模块这使得架构更加简化。
+ 由数十亿个单词构成的大型数据集对预训练 LLM 至关重要。在本书中,我们将实现并在小型数据集上训练 LLM以便用于教学同时也会探讨如何加载公开可用的模型权重。
+ 类似 GPT 的模型的普遍预训练任务是预测句子中的下一个单词,但这些 LLM 显示出了“涌现”特性,例如具备分类、翻译或文本总结的能力。
+ 一旦 LLM 完成预训练,得到的基础模型就可以更高效地微调,以应对各种下游任务。
+ 在自定义数据集上微调过的 LLM 能够在特定任务上超越通用 LLM。
[^1]: 拥有机器学习背景的读者可能会注意到,传统机器学习模型和通过常规监督学习训练的深度神经网络通常需要标注数据。但在 LLM 的预训练阶段情况并非如此。在这一阶段LLM 采用自监督学习,模型可以从输入数据中自动生成标签。这个概念将在本章后面的内容中进一步讨论。
[^2]: GPT-3价值 4,600,000 美元的语言模型https://www.reddit.com/r/MachineLearning/comments/h0jwoz/d_gpt3_the_4600000_language_model/

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,340 @@
本章涵盖以下内容:
+ **探讨在神经网络中使用注意力机制的原因**
+ **介绍一个基本的自注意力框架,并逐步深入到改进的自注意力机制**
+ **实现一个因果注意力模块,使 LLM 能够一次生成一个token**
+ **使用 dropout 随机掩盖部分注意力权重,以减少过拟合**
在上一章中,你学习了如何准备输入文本以训练 LLM。这包括将文本拆分为单个单词和子词token这些token可以被编码为向量表示即所谓的嵌入以供 LLM 使用。
在本章中,我们将关注 LLM 架构中的重要组成部分,即注意力机制,如图 3.1 所示。
<img src="../Image/chapter3/figure3.1.png" width="75%" />
注意力机制是一个复杂的话题,因此我们将专门用一整章来讨论它。我们主要会将注意力机制独立来研究,关注其内部的工作原理。在下一章中,我们将编写环绕自注意力机制的 LLM 其他部分的代码,以便观察它的实际应用,并创建一个能够生成文本的模型。
本章中,我们将实现四种不同的注意力机制变体,如图 3.2 所示。
<img src="../Image/chapter3/figure3.2.png" width="75%" />
图 3.2 中展示的这些不同的注意力变体是逐步构建的,其目标是在本章末尾实现一个紧凑且高效的多头注意力机制实现,以便在下一章中可以将其整合到我们将编写的 LLM 架构中
## 3.1 长序列建模的问题
在深入了解自注意力机制之前(这是大语言模型的核心),让我们先探讨一下缺乏注意力机制的架构存在哪些问题(这些架构在大语言模型之前已经存在)。假设我们想要开发一个将一种语言翻译成另一种语言的翻译模型。如图 3.3 所示,我们无法简单地逐词翻译文本,因为源语言和目标语言的语法结构存在差异。
<img src="../Image/chapter3/figure3.3.png" width="75%" />
为了解决逐词翻译的局限性通常使用包含两个子模块的深度神经网络即所谓的编码器encoder和解码器decoder。编码器的任务是先读取并处理整个文本然后解码器生成翻译后的文本。
在第 1 章1.4 节,使用 LLM 进行不同任务)介绍 Transformer 架构时,我们已经简要讨论过编码器-解码器网络。在 Transformer 出现之前循环神经网络RNN是最流行的用于语言翻译的编码器-解码器架构。
**循环神经网络RNN**是一种神经网络类型,其中前一步的输出会作为当前步骤的输入,使其非常适合处理像文本这样的序列数据。如果您不熟悉 RNN 的工作原理,不必担心,您无需了解 RNN 的详细机制也可以参与这里的讨论;我们在这里的重点更多是编码器-解码器结构的总体概念。
在编码器-解码器 RNN 中,输入文本被输入到编码器中,编码器按顺序处理文本内容。在每个步骤中,编码器会更新其隐状态(即隐藏层的内部值),试图在最终的隐状态中捕捉整个输入句子的含义,如图 3.4 所示。然后,解码器使用该最终隐状态来开始逐词生成翻译句子。解码器在每一步也会更新其隐状态,用于携带生成下一个词所需的上下文信息。
<img src="../Image/chapter3/figure3.4.png" width="75%" />
尽管我们不需要深入了解这些编码器-解码器结构 RNN 的内部工作原理,但关键思想在于,编码器部分将整个输入文本处理为一个隐藏状态(记忆单元)。解码器随后使用该隐藏状态生成输出。您可以将这个隐藏状态视为一个嵌入向量,这是我们在第 2 章中讨论的概念。
编码器-解码器结构的 RNN 的一个重大问题和限制在于,**在解码阶段 RNN 无法直接访问编码器的早期隐藏状态**。因此,它只能依赖当前隐藏状态来封装所有相关信息。这种设计可能导致上下文信息的丢失,特别是在依赖关系较长的复杂句子中,这一问题尤为突出。
对于不熟悉 RNN 的读者,不必深入理解或学习这种架构,因为本书中不会使用它。本节的重点是,编码器-解码器 RNN 存在一个缺点,这一缺点促使了注意力机制的设计。
> [!TIP]
>
> **个人思考:** 虽然本书没有涉及对RNN的过多讨论但了解从RNN到注意力机制的技术变迁对于核心内容的理解至关重要。让我们通过一个具体的示例来理解这种技术变迁
>
> 1. **RNN的局限性**
>
> 假设我们有一个长句子“The cat, who was sitting on the windowsill, jumped down because it saw a bird flying outside the window.”
>
> 假设任务是预测句子最后的内容即要理解“it”指的是“the cat”而不是“the windowsill”或其他内容。对于 RNN 来说,这个任务是有难度的,原因如下:
>
> + **长距离依赖问题**:在 RNN 中每个新输入的词会被依次传递到下一个时间步。随着句子长度增加模型的隐状态会不断被更新但早期信息如“the cat”会在层层传播中逐渐消失。因此模型可能无法在“it”出现时有效地记住“the cat”是“it”的指代对象。
> + **梯度消失问题**RNN 在反向传播中的梯度会随着时间步的增加逐渐减小,这种“梯度消失”使得模型很难在长句中保持信息的准确传播,从而难以捕捉到长距离的语义关联。
>
> 2. **注意力机制的解决方法**
>
> 为了弥补 RNN 的这些不足,**注意力机制**被引入。它的关键思想是**在处理每个词时,不仅依赖于最后的隐藏状态,而是允许模型直接关注序列中的所有词**。这样,即使是较远的词也能在模型计算当前词的语义时直接参与。
>
> 在上例中注意力机制如何帮助模型理解“it”指代“the cat”呢
>
> + **注意力机制的工作原理**当模型处理“it”时注意力机制会将“it”与整个句子中的其他词进行相似度计算判断“it”应该关注哪些词。
> + 由于“the cat”与“it”在语义上更相关注意力机制会为“the cat”分配较高的权重而其他词如“windowsill”或“down”则获得较低的权重。
> + **信息的直接引用**通过注意力机制模型可以跳过中间步骤直接将“it”与“the cat”关联而不需要依赖所有的中间隐藏状态。
>
> 3. **示例中的注意力矩阵**
>
> 假设使用一个简单的注意力矩阵模型在处理“it”时给每个词的权重可能如下至于如何计算这些权重值后文会详细介绍
>
> | 词 | The | cat | who | was | sitting | ... | it | saw | bird | flying | ... | window |
> | -------- | ---- | ---- | ---- | ---- | ------- | ---- | ---- | ---- | ---- | ------ | ---- | ------ |
> | **权重** | 0.1 | 0.3 | 0.05 | 0.05 | 0.05 | ... | 0.4 | 0.05 | 0.02 | 0.01 | ... | 0.02 |
>
> 在这个注意力矩阵中,可以看到**“it”对“the cat”有较高的关注权重0.3),而对其他词的关注权重较低**。这种直接的关注能力让模型能够高效捕捉长距离依赖关系理解“it”与“the cat”的语义关联。
## 3.2 通过注意力机制捕捉数据依赖关系
在 Transformer 架构的大语言模型LLM出现之前通常会使用循环神经网络RNN来完成语言建模任务例如语言翻译。RNN 对于翻译短句表现良好,但在处理长文本时效果不佳,因为它们无法直接访问输入序列中的前面词语。
这一方法的一个主要缺陷在于RNN 必须将整个编码后的输入信息存储在一个隐藏状态中,然后再将其传递给解码器,如上一节的图 3.4 所示。
因此,研究人员在 2014 年为 RNN 开发了所谓的 Bahdanau 注意力机制(该机制以论文的第一作者命名)。该机制对编码器-解码器 RNN 进行了改进,使得解码器在每个解码步骤可以选择性地访问输入序列的不同部分,如图 3.5 所示。
<img src="../Image/chapter3/figure3.5.png" width="75%" />
有趣的是,仅仅三年后,研究人员发现构建用于自然语言处理的深度神经网络并不需要 RNN 结构,并提出了基于自注意力机制的原始 Transformer 架构(在第 1 章中讨论),其灵感来自 Bahdanau 提出的注意力机制。
自注意力机制是一种允许输入序列中的每个位置在计算序列表示时关注同一序列中所有位置的机制。自注意力机制是基于Transformer架构的当代大语言模型如GPT系列模型的关键组成部分。
本章将重点讲解并实现 GPT 类模型中使用的自注意力机制,如图 3.6 所示。在下一章中,我们将继续编码 LLM 的其它部分。
<img src="../Image/chapter3/figure3.6.png" width="75%" />
## 3.3 通过自注意力机制关注输入的不同部分
现在我们将深入了解自注意力机制的内部工作原理,并从零开始学习如何实现它。自注意力机制是基于 Transformer 架构的每一个大语言模型的核心。需要注意的是,这一部分内容可能需要大量的专注与投入(无双关含义),但一旦掌握了它的基本原理,你就攻克了本书及大语言模型实现中最困难的部分之一。
> [!NOTE]
>
> **“自我”在自注意力机制中的含义**
>
> 在自注意力机制中“self”指的是该机制通过关联同一输入序列中的不同位置来计算注意力权重的能力。它评估并学习输入内部各部分之间的关系和依赖性例如句子中的单词或图像中的像素。这与传统注意力机制不同传统机制关注的是两个不同序列间的关系例如序列到序列模型中注意力可能存在于输入序列和输出序列之间这一点在图 3.5 中有示例说明。
由于自注意力机制可能显得较为复杂,尤其是对于初次接触的读者,我们将在下一小节中首先介绍一个简化版的自注意力机制。随后,在第 3.4 节中我们将实现带有可训练权重的自注意力机制这种机制被用于大语言模型LLM中。
### 3.3.1 一种不含可训练权重的简单自注意力机制。
在本节中,我们实现了一个简化的自注意力机制版本,没有包含任何可训练的权重,如图 3.7 所示。本节的目标是先介绍自注意力机制中的一些关键概念,然后在 3.4 节引入可训练的权重。
<img src="../Image/chapter3/figure3.7.png" width="75%" />
图 3.7 显示了一个输入序列,记作 x由 T 个元素组成,表示为 x(1) 到 x(T)。该序列通常代表文本,例如一个句子,并且该文本已被转换为 token 嵌入,如第 2 章所述。
举例来说,假设输入文本为 “Your journey starts with one step”。在这个例子中序列中的每个元素`x(1)`)对应一个 `d` 维的嵌入向量,用于表示特定的 token例如 “Your”。在图 3.7 中,这些输入向量显示为 3 维的嵌入向量。
在自注意力机制中,我们的目标是为输入序列中的每个元素 x(i) 计算其对应的上下文向量 z(i) 。上下文向量可以被解释为一种增强的嵌入向量。
为了说明这个概念,我们聚焦于第二个输入元素 x(2) 的嵌入向量(对应于词 "journey")以及相应的上下文向量 z(2),如图 3.7 底部所示。这个增强的上下文向量 z(2) 是一个嵌入向量,包含了关于 x(2) 以及所有其他输入元素 x(1) 到 x(T) 的信息。
在自注意力机制中,上下文向量起着关键作用。它们的目的是通过整合序列中所有其他元素的信息(如同一个句子中的其他词),为输入序列中的每个元素创建丰富的表示,正如图 3.7 所示。这对大语言模型至关重要,因为模型需要理解句子中各个词之间的关系和关联性。之后,我们将添加可训练的权重,以帮助大语言模型学习构建这些上下文向量,使其与生成下一个词的任务相关。
在本节中,我们实现了一个简化的自注意力机制,以逐步计算这些权重和生成的上下文向量。
请考虑以下输入句子,该句子已经根据第 2 章的讨论转换为三维向量表示。为了便于说明和展示,我们选择了较小的嵌入维度,以确保句子在页面上可以无换行地展示。
```python
import torch
inputs = torch.tensor(
[[0.43, 0.15, 0.89], # Your (x^1)
[0.55, 0.87, 0.66], # journey (x^2)
[0.57, 0.85, 0.64], # starts (x^3)
[0.22, 0.58, 0.33], # with (x^4)
[0.77, 0.25, 0.10], # one (x^5)
[0.05, 0.80, 0.55]] # step (x^6)
```
实现自注意力机制的第一步是计算中间值 **ω**,即注意力得分,如图 3.8 所示。请注意,图 3.8 中前一层输入张量的值为截断版本,例如 0.87 因空间限制被截断为 0.8。在这个截断版本中,单词 "journey" 和 "starts" 的嵌入向量可能会由于随机因素而看起来相似。
<img src="../Image/chapter3/figure3.8.png" width="75%" />
图 3.8 说明了如何计算查询 token 与每个输入 token 之间的中间注意力得分。我们通过计算查询 token x(2)x(2)x(2) 与每个其他输入 token 的点积来确定这些得分。
```python
query = inputs[1] #A
attn_scores_2 = torch.empty(inputs.shape[0])
for i, x_i in enumerate(inputs):
attn_scores_2[i] = torch.dot(x_i, query)
print(attn_scores_2)
#A 第二个输入 token 用作查询向量
```
> [!TIP]
>
> **个人思考:** 这里对于注意力得分的计算描述的比较笼统仅仅说明了将当前的输入Token向量与其它Token的向量进行点积运算计算注意力得分实际上每个输入Token会先通过权重矩阵W分别计算出它的Q、K、V三个向量这三个向量的定义如下
>
> + **Q向量查询向量**:查询向量代表了这个词在寻找相关信息时提出的问题
> + **K向量键向量**:键向量代表了一个单词的特征,或者说是这个单词如何"展示"自己,以便其它单词可以与它进行匹配
> + **V向量值向量**:值向量携带的是这个单词的具体信息,也就是当一个单词被"注意到"时,它提供给关注者的内容
>
> **更通俗的理解:**想象我们在图书馆寻找一本书(`Q向量`),我们知道要找的主题(`Q向量`),于是查询目录(`K向量`),目录告诉我哪本书涉及这个主题,最终我找到这本书并阅读内容(`V向量`),获取了我需要的信息。
>
> 具体生成Q、K、V向量的方式主要通过线性变换
>
> ```python
> Q1 = W_Q * (E1 + Pos1)
> K1 = W_K * (E1 + Pos1)
> V1 = W_V * (E1 + Pos1)
> ```
>
> 依次类推为所有token生成`Q``K``V`向量,其中`W_Q``W_K`和`W_V`是Transformer训练出的权重每一层不同
>
> 针对每一个目标tokenTransformer会计算它的 `Q向量` 与其它所有的token的 `K向量` 的点积,以确定每个词对当前词的重要性(即注意力分数)
>
> 假如有句子“The cat drank the milk because it was hungry”
>
> 例如对于词 `cat` 的 `Q向量 Q_cat`,模型会计算:
>
> + `score_cat_the = Q_cat · K_the` --- 与`the`的语义相关度
> + `score_cat_drank = Q_cat · K_drank` --- 与 `drank` 的语义相关度
> + `score_cat_it = Q_cat · K_it` --- 与 `it` 的语义相关度
> + 依此类推,得到`cat`与句子中其它所有token的注意力分数 `[score_cat_the、score_cat_drank、socre_cat_it、……]`
计算得到的注意力得分如下:
```
tensor([0.9544, 1.4950, 1.4754, 0.8434, 0.7070, 1.0865])
```
> [!NOTE]
>
> **理解点积**
>
> 点积运算本质上是一种将两个向量按元素相乘后再求和的简洁方式,我们可以如下演示:
>
> ```python
> res = 0.
> for idx, element in enumerate(inputs[0]):
> res += inputs[0][idx] * query[idx]
> print(res)
> print(torch.dot(inputs[0], query))
> ```
>
> 输出结果确认,逐元素相乘的和与点积的结果相同。
>
> ```
> tensor(0.9544)
> tensor(0.9544)
> ```
>
> 除了将点积运算视为结合两个向量并产生标量结果的数学工具之外,点积也是一种相似度的衡量方法,因为它量化了两个向量的对齐程度:较高的点积值表示向量之间有更高的对齐程度或相似度。在自注意力机制的背景下,点积决定了序列中元素之间的关注程度:点积值越高,两个元素之间的相似度和注意力得分就越高。
如图 3.9 所示,接下来,我们对先前计算的每个注意力分数进行归一化。
<img src="../Image/chapter3/figure3.9.png" width="75%" />
图3.9中所示的归一化的主要目的是使注意力权重之和为 1。这种归一化是一种有助于解释和保持LLM训练稳定性的惯例。以下是一种实现此归一化步骤的简单方法
```python
attn_weights_2_tmp = attn_scores_2 / attn_scores_2.sum()
print("Attention weights:", attn_weights_2_tmp)
print("Sum:", attn_weights_2_tmp.sum())
```
如输出所示,现在注意力权重的总和为 1
```
Attention weights: tensor([0.1455, 0.2278, 0.2249, 0.1285, 0.1077, 0.1656])
Sum: tensor(1.0000)
```
在实践中,更常见且更推荐使用 softmax 函数来进行归一化。这种方法更擅长处理极端值,并且在训练过程中提供了更有利的梯度特性。以下是用于归一化注意力分数的 softmax 函数的基础实现。
```python
def softmax_naive(x):
return torch.exp(x) / torch.exp(x).sum(dim=0)
attn_weights_2_naive = softmax_naive(attn_scores_2)
print("Attention weights:", attn_weights_2_naive)
print("Sum:", attn_weights_2_naive.sum())
```
从输出中可以看到softmax 函数可以实现注意力权重的归一化,使它们的总和为 1
```
Attention weights: tensor([0.1385, 0.2379, 0.2333, 0.1240, 0.1082, 0.1581])
Sum: tensor(1.)
```
此外softmax 函数确保注意力权重始终为正值。这使得输出可以被解释为概率或相对重要性,其中较高的权重表示更重要。
注意,这种简单的 softmax 实现softmax_naive在处理较大或较小的输入值时可能会遇到数值不稳定性问题例如上溢或下溢。因此实际操作中建议使用 PyTorch 的 softmax 实现,它经过了充分的性能优化:
```python
attn_weights_2 = torch.softmax(attn_scores_2, dim=0)
print("Attention weights:", attn_weights_2)
print("Sum:", attn_weights_2.sum())
```
可以看到,它与我们之前实现的 `softmax_naive` 函数产生的结果相同。
```
Attention weights: tensor([0.1385, 0.2379, 0.2333, 0.1240, 0.1082, 0.1581])
Sum: tensor(1.)
```
> [!TIP]
>
> **个人思考:** 这里稍微延伸探讨一下`Softmax`, 它是一种常用的激活函数,尤其在神经网络的分类任务中被广泛使用。它的作用是将一个任意的实数向量转换为一个概率分布,且所有元素的概率之和为 1。下面通过例子来说明 softmax 的原理、好处,以及它在神经网络中的使用原因。
>
> 1. **Softmax 的原理**
>
> Softmax 函数的公式如下:
>
> $$ \text{softmax}\left(z_{i}\right)=\frac{e^{z_{i}}}{\sum_{j} e^{z_{j}}} $$
>
> 其中z<sub>i</sub>是输入的每个分数即未激活的原始值e 是自然对数的底。这个公式的作用是将输入向量中的每个元素转换为一个概率值,且所有值的和为 1。
>
> 2. **Softmax 的好处**
>
> + **归一化输出为概率**Softmax 将输出转换为 0 到 1 之间的概率,且所有类别的概率之和为 1方便解释结果。例如在分类任务中输出可以直接表示模型对各类别的信心。
> + **平滑和放大效果**Softmax 不仅能归一化,还具有平滑和放大效果。较大的输入值会被放大,较小的输入值会被抑制,从而增强模型对最优类别的区分。
> + **支持多分类问题**:与 sigmoid 不同Softmax 适用于多类别分类问题。它可以输出每个类别的概率,使得模型可以处理多分类任务。
>
> 3. **神经网络为什么喜欢使用 Softmax**
>
> 在神经网络中特别是分类模型如图像分类、文本分类Softmax 层通常用作最后一层输出。原因包括:
>
> + **便于优化**在分类任务中Softmax 输出的概率分布可与真实的标签概率进行比较,从而计算交叉熵损失。交叉熵损失的梯度较为稳定,便于模型的优化。
> + **概率解释**Softmax 输出可以解释为“模型对每个类别的信心”,使得输出直观可理解。
> + **与交叉熵的结合**Softmax 与交叉熵损失函数结合效果特别好,可以直接将模型预测的概率分布与真实标签比较,从而更快收敛,效果更好。
现在我们已经计算出了归一化的注意力权重,接下来可以执行图 3.10 所示的最后一步:通过将嵌入后的输入 token x<sup>(i)</sup> 与相应的注意力权重相乘,再将所得向量求和,来计算上下文向量 z<sup>(2)</sup>。
<img src="../Image/chapter3/figure3.10.png" width="75%" />
如图 3.10 所示,上下文向量 z<sup>(2) </sup> 是通过所有输入向量的加权和计算得到的。这一过程涉及将每个输入向量与其对应的注意力权重相乘:
```python
query = inputs[1] # 2nd input token is the query
context_vec_2 = torch.zeros(query.shape)
for i,x_i in enumerate(inputs):
context_vec_2 += attn_weights_2[i]*x_i
print(context_vec_2)
```
结算结果如下:
```
tensor([0.4419, 0.6515, 0.5683])
```
在接下来的部分我们将把串行计算上下文向量的过程优化为并行计算所有输入token的上下文向量。
### 3.2 为所有输入的 token 计算注意力权重
在前一节中,我们计算了第二个输入元素的注意力权重和上下文向量,如图 3.11 中的高亮行所示。现在,我们将扩展该计算,以对所有输入计算注意力权重和上下文向量。
<img src="../Image/chapter3/figure3.11.png" width="75%" />
我们沿用之前的三个步骤(如图 3.12 所示),只是对代码做了一些修改,以计算所有的上下文向量,而不仅仅是第二个上下文向量 z<sup>(2)</sup>。