Merge branch 'datawhalechina:main' into trans01

This commit is contained in:
Yi Jiang
2024-04-24 00:31:40 +08:00
committed by GitHub
13 changed files with 808 additions and 0 deletions
+1
View File
@@ -0,0 +1 @@
+426
View File
@@ -0,0 +1,426 @@
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# 1 理解大语言模型"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"+ 大型语言模型(LLMs)背后基本概念的高层次解释、\n",
"+ 深入了解从 ChatGPT 衍生出类似 LLM 的 Transformer 架构\n",
"+ 从头开始构建LLM的计划"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**这本书涵盖**"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"大型语言模型(LLM),如 OpenAI 的 ChatGPT 所提供的模型,是近些年发展的深度神经网络模型。它们为自然语言处理(NLP)带来了新的时代。在大型语言模型出现之前,传统方法在垃圾邮件分类和简单模式识别等分类任务中表现出色,这些任务可以用手工规则或更简单的模型来捕捉。然而,在需要复杂理解和生成能力的语言任务中,如解析详细指令、进行上下文分析或创建连贯且与上下文相适应的原始文本时,这些方法通常表现不佳。例如,前几代语言模型无法根据关键字列表编写电子邮件————而这是当代的 LLM 能够轻松完成的任务。"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"语言模型具有理解、生成和解释人类语言的卓越能力。不过,有必要澄清的是,当我们说语言模型 \"能理解 \"时,我们指的是它们能以看起来连贯且与上下文相关的方式处理和生成文本,而不是说它们拥有类似人类的意识或理解能力。"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"深度学习是以神经网络为核心的机器学习和人工智能(AI)的一个子集,在深度学习的推动下,LLM 在大量文本数据的基础上进行训练。与以前的方法相比,这使得 LLM 能够捕捉到更深层次的语境信息和人类语言的微妙之处。因此,LLM 在文本翻译、情感分析、问题解答等各种 NLP 任务中的性能都有显著提高。"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"当代 LLM 与早期 NLP 模型之间的另一个重要区别是,后者通常是为特定任务而设计的;尽管早期的 NLP 模型在狭窄的应用领域表现出色,但 LLM 则在广泛的 NLP 任务中表现出更强的泛化能力。"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"LLM 的成功可归功于其背后的 Transformer 架构,该架构支撑了许多LLM,以及 LLM 训练所用到的海量数据,这使得它们能够捕捉各种语言细微差别、语境和模式,这些内容如果手动编码将会非常具有挑战性。"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"这种转向基于 Transformer 架构实施模型并使用大型训练数据集来训练 LLM 的做法,已经根本性地改变了自然语言处理 NLP,为理解和与人类语言互动提供了更强大的工具。"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"从本章开始,我们为实现本书的主要目标奠定基础:通过在代码中逐步实现基于 Transoformer 架构的类ChatGPT的LLM,从而理解 LLM。"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 1.1 什么是LLM"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"LLM 即大型语言模型,是一种神经网络,旨在理解、生成和响应人类文本。这些模型是在海量文本数据上训练出来的深度神经网络,有时包含互联网上全部公开文本的很大一部分。"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"大型语言模型中的 \"大型 \"指的是该模型的参数规模以及训练该模型的庞大数据集。此类模型通常有数百亿甚至数千亿个参数,这些参数是网络中可调整的权重,在训练过程中进行优化,以预测序列中的下一个单词。下一个单词的预测是合理的,因为它利用了语言固有的顺序性来训练理解文本中上下文、结构和关系的模型。然而,这只是一项非常简单的任务,因此能产生如此强大的模型令许多研究人员感到惊讶。我们将在后面的章节中逐步讨论和实现下一个单词的训练过程。"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"LLM 利用一种称为 Transformer 的架构(在 1.4 节中详细介绍),这种架构允许 LLM 在进行预测时选择性地关注输入的不同部分,这使得 LLM 特别擅长处理人类语言的细微差别和复杂性。"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"由于 LLM 能够生成文本,因此 LLM 也常被称为生成式人工智能(Artificial Intelligence, AI)的一种形式,通常缩写为生成式 AI 或 GenAI。如图 1.1 所示,人工智能涵盖了更广泛的领域,即创造能够执行需要类似人类智能的任务(包括理解语言、识别模式和做出决策)的机器,并包括机器学习和深度学习等子领域。"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"图 1.1 正如对不同领域之间关系的分层描述所示,LLM 代表了深度学习技术的一种特定应用,它利用了深度学习技术处理和生成类似人类文本的能力。深度学习是机器学习的一个专门分支,侧重于使用多层神经网络。机器学习和深度学习都是旨在实现算法的领域,这些算法使计算机能够从数据中学习,并执行通常需要人类智能才能完成的任务。"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"![image.png](../img/Figure%201.1.png)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"用于实现人工智能的算法是机器学习领域的重点。具体来说,机器学习涉及开发能够从数据中学习并基于数据做出预测或决策的算法,而无需显式编程。为了说明这一点,可以把垃圾邮件过滤器想象成机器学习的实际应用。与手动编写识别垃圾邮件的规则不同,一个机器学习算法被喂食了标记为垃圾邮件和合法邮件的电子邮件示例。通过最小化其在训练数据集上的预测误差,该模型学习识别表明垃圾邮件的模式和特征,使其能够将新邮件分类为垃圾邮件或合法邮件。"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"如图 1.1 所示,深度学习是机器学习的一个子集,主要利用具有三层或更多层的神经网络(也称为深度神经网络)对数据中的复杂模式和抽象概念进行建模。与深度学习相比,传统的机器学习需要人工提取特征。这意味着人类专家需要为模型识别和选择最相关的特征。"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"虽然如今人工智能领域主要是机器学习和深度学习,但也包括其他方法,例如使用基于规则的系统、遗传算法、专家系统、模糊逻辑或符号推理。"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"回到垃圾邮件分类的例子,在传统的机器学习中,人类专家可能会手动从电子邮件文本中提取特征,如某些触发词(\"奖品\"、\"中奖\"、\"免费\")的频率、感叹号的数量、所有大写单词的使用或可疑链接的存在。基于这些专家定义的特征创建的数据集将用于训练模型。与传统的机器学习相比,深度学习不需要人工特征提取。这意味着人类专家不需要为深度学习模型识别和选择最相关的特征。(不过,在传统机器学习和深度学习的垃圾邮件分类中,仍然需要收集标签,如垃圾邮件或非垃圾邮件,这些标签需要由专家或用户收集)。"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"接下来的部分将介绍 LLM 目前可以解决的一些问题、LLM 所面临的挑战,以及我们将在本书中实现的一般 LLM 架构。"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 1.2 LLM的应用领域"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"由于 LLM 具备解析和理解非结构化文本数据的高级能力,因此在各个领域都有广泛的应用。如今,LLM 已被用于机器翻译、生成新文本(见图 1.2)、情感分析、文本摘要以及许多其他任务。最近,LLM 还被用于内容创作,如撰写小说、文章甚至计算机代码。"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"图 1.2 LLM 界面实现了用户与人工智能系统之间的自然语言交流。这张截图显示的是 ChatGPT 根据用户的要求写了一首诗。"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"![Figure 1.2](../img/Figure%201.2.png)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"LLM 还可以支持复杂的聊天机器人和虚拟助手,如 OpenAI 的 ChatGPT 或谷歌的 Gemini(原名 Bard),它们可以回答用户的询问,并增强谷歌搜索或微软必应等传统搜索引擎的功能。"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"此外,LLM 还可用于从医学或法律等专业领域的大量文本中进行有效的知识检索。这包括筛选文档、总结冗长段落和回答技术问题。"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"简而言之,LLM对于自动化几乎任何涉及解析和生成文本的任务都极具价值。它们的应用几乎是无限的,随着我们不断创新和探索使用这些模型的新方法,很明显,LLM有可能重塑我们与技术的关系,使其更具对话性、直观性和易用。\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"在本书中,我们将重点关注从零开始理解LLM的工作原理,并编写一个能够生成文本的LLM。我们还将学习让 LLM 执行查询的技术,包括回答问题、总结文本、将文本翻译成不同语言等。换句话说,在本书中,我们将通过一步步构建 ChatGPT 等复杂的 LLM 助手,了解其工作原理。"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 1.3 构建和使用LLM的各个阶段"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"我们为什么要构建自己的 LLM?从头开始编码 LLM 是了解其机制和局限性的绝佳练习。此外,它还能让我们掌握必要的知识,以便根据自己特定领域的数据集或任务对现有的开源 LLM 架构进行预训练或微调。"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"研究表明,在模型性能方面,定制的 LLM(为特定任务或领域量身定制的 LLM)可能优于通用 LLM(如 ChatGPT 提供的 LLM),后者专为各种应用而设计。这方面的例子包括专门用于金融领域的 BloombergGPT 和为医学问题解答量身定制的 LLM(详情请参见附录 B 中的 \"进一步阅读和参考文献 \"部分)。"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"创建 LLM 的一般过程包括预训练和微调。预训练 \"中的 \"预 \"指的是初始阶段,在这个阶段,像 LLM 这样的模型会在一个大型、多样化的数据集上接受训练,以形成对语言的广泛理解。然后,这种预训练模型可作为基础资源,通过微调进一步完善。微调过程是指在针对特定任务或领域的较窄数据集上对模型进行专门训练。图 1.3 描述了这种由预训练和微调组成的两阶段训练方法。"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"图 1.3 LLM 的预训练包括在大型文本数据集上预测下一个单词。然后,可以使用较小的标注数据集对预训练 LLM 进行微调。"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"![Figure 1.3](../img/Figure%201.3.png)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"如图 1.3 所示,创建 LLM 的第一步是在大量文本数据(有时称为原始文本)上进行训练。这里的 \"原始 \"是指这些数据只是普通文本,没有任何标签信息[1]。(可以进行过滤,如去除格式化字符或未知语言的文档)。"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"LLM 的第一个训练阶段也称为预训练,即创建一个初始的预训练 LLM,通常称为基础或基本模型。这种模型的一个典型例子是 GPT-3 模型(ChatGPT 中提供的原始模型的前身)。该模型能够完成文本,即完成用户提供的半写句子。它还具有有限的 \"少样本 \"能力,这意味着它能够仅基于几个示例而不是需要大量的训练数据来学习执行新任务。下一节 \"针对不同任务使用Transormer \"将进一步说明这一点。"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"通过在大型文本数据集上的训练获得预训练 LLM 后,我们可以在标注数据上进一步训练 LLM,也就是所谓的微调。"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"微调LLM的两个最受欢迎的类别包括指令微调(instructionfinetuning)和分类任务微调(finetuning for classification tasks)。 在指令微调中,标注数据集由指令和答案对组成,例如翻译文本的查询以及正确的翻译文本。在分类微调中,标注数据集由文本和相关类标签组成,例如与垃圾邮件和非垃圾邮件标签相关的电子邮件。"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"在这本书中,我们将涵盖预训练和微调LLM的代码实现,并在预训练基础LLM后,更深入地探讨指令微调和分类微调的具体内容。"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 1.4 使用LLM执行不同任务\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"大多数现代 LLM 都依赖于 transformer 架构,这是 2017 年论文《Attention Is All You Need》中提出的一种深度神经网络架构。要了解 LLM,我们必须简要回顾一下最初的 transformer,它最初是为机器翻译而开发的,用于将英文文本翻译成德文和法文。图 1.4 是 Transformer 架构的简化版本。"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"图 1.4 原始transformer架构的简化图示,这是一种用于语言翻译的深度学习模型。transformer由两部分组成:编码器负责处理输入文本,并生成文本的嵌入表示(一种数字表示,可捕捉不同维度的许多不同因素),解码器可利用该表示逐字生成翻译文本。请注意,该图显示的是翻译过程的最后阶段,解码器只需根据原始输入文本(\"This is an example\")和部分翻译的句子(\"Das ist ein\")生成最终单词(\"Beispiel\")即可完成翻译。"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"![Figure 1.4](../img/Figure%201.4.png)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"图 1.4 所示的Transformer架构由编码器和解码器两个子模块组成。编码器模块处理输入文本,并将其编码为一系列数字表示或向量,以捕捉输入的上下文信息。然后,解码器模块接收这些编码矢量,并从中生成输出文本。例如,在翻译任务中,编码器将源语言的文本编码成向量,解码器对这些向量进行解码,生成目标语言的文本。编码器和解码器都由许多层组成,通过所谓的自我注意机制连接起来。关于输入是如何预处理和编码的,您可能有很多问题。这些问题将在随后的章节中逐步解决。"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Transformer 和 LLM 的一个关键组成部分是自注意力机制(未在图中显示),它允许模型权衡序列中不同词或标记的相对重要性。这种机制使模型能够捕捉输入数据中的长程依赖关系和上下文关系,从而增强其生成连贯且与上下文相关的输出的能力。然而,由于其复杂性,我们将把解释工作推迟到第 3 章,在那里我们将逐步讨论和实施这一机制。此外,我们还将在第2章“处理文本数据”中讨论和实施数据预处理步骤,以创建模型输入。"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"转换器和LLMs的一个关键组成部分是自注意力机制(未在图中显示),它允许模型相对于序列中的其他单词或标记来权衡不同单词或标记的重要性。这种机制使模型能够捕捉输入数据中的长距离依赖关系和上下文关系,从而增强其生成连贯且上下文相关输出的能力。然而,由于其复杂性,我们将推迟到第3章进行解释,在那里我们将逐步讨论和实施它。此外,我们还将在第2章“处理文本数据”中讨论和实施数据预处理步骤,以创建模型输入。\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Transformer 架构后来的变体,如所谓的 BERTbidirectional encoder representations from transformers 的简称)和各种 GPT 模型(generative pretrained transformers 的简称),都是在这一概念的基础上发展起来的,以适应不同任务的需要。(参考文献见附录 B)。"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"BERT 建立在原始 transformer 的编码器子模块基础上,其训练方法与 GPT 不同。GPT 专为生成任务而设计,而 BERT 及其变体则专注于屏蔽词预测,即模型预测给定句子中的屏蔽词或隐藏词,如图 1.5 所示。这种独特的训练策略使 BERT 在文本分类任务(包括情感预测和文档分类)中具有优势。在本文撰写过程中,Twitter 使用 BERT 检测有毒内容,这就是 BERT 功能的一个应用。"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"图 1.5 Transformer 编码器和解码器子模块的直观示意图。左侧的编码器部分展示了类似 BERT 的 LLM,这些 LLM 专注于屏蔽词预测,主要用于文本分类等任务。右边的解码器部分展示的是 GPT 类 LLM,设计用于生成任务和生成连贯的文本序列。"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"![Figure 1.5](../img/Figure%201.5.png)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"而 GPT 则侧重于原始 transformer 架构的解码器部分,专为需要生成文本的任务而设计。这包括机器翻译、文本摘要、小说写作、编写计算机代码等。我们将在本章的其余章节中更详细地讨论 GPT 架构,并在本书中从头开始实现它。"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"GPT 模型主要是为执行文本补全任务而设计和训练的,它们也显示出了非凡的多功能性。这些模型善于执行零样本学习和少量样本学习任务。零样本学习指的是在没有任何先前具体实例的情况下,对完全未见过的任务进行泛化的能力。另一方面,少量样本学习涉及从用户提供的极少量实例中学习,如图 1.6 所示。"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"![Figure 1.6](../img/Figure%201.6.png)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**Transformer 与 LLM**"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"如今的 LLM 基于上一节介绍的 Transformer 架构。因此,在文献中,Transformer 和 LLM 通常是同义词。但请注意,并非所有的 Transformer 都是 LLM,因为 Transformer 也可用于计算机视觉。同样,并非所有 LLM 都是 Transformer,因为也有基于递归和卷积架构的大型语言模型。这些替代方法背后的主要动机是提高 LLM 的计算效率。不过,这些替代 LLM 架构能否与基于 Transformer 的 LLM 相抗衡,以及它们是否会被实际采用,还有待观察。(有兴趣的读者可以在本章结尾的 \"进一步阅读 \"部分找到描述这些架构的文献参考)。"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.9.6"
}
},
"nbformat": 4,
"nbformat_minor": 2
}
+380
View File
@@ -0,0 +1,380 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "e63ef76e",
"metadata": {},
"source": [
"# 5.2 训练一个LLM"
]
},
{
"cell_type": "markdown",
"id": "c1d79d88",
"metadata": {},
"source": [
"​\t\t在本节中,我们最终实现了用于预训练 LLM 的代码,即我们的 GPTModel。为此,我们专注于一个简单的训练循环,如图 5.11 所示,以保持代码简洁易读。但是,有兴趣的读者可以在附录 D,向训练循环添加花里胡哨中了解更高级的技术,包括学习速率预热、余弦退火和梯度削波。"
]
},
{
"cell_type": "markdown",
"id": "16882c7e",
"metadata": {},
"source": [
"图 5.11 在 PyTorch 中训练深度神经网络的典型训练循环由几个步骤组成,在训练集中的批次上迭代多个时期。在每个循环中,我们计算每个训练集批次的损失以确定损失梯度,我们用它来更新模型权重,以便将训练集损失降至最低。"
]
},
{
"cell_type": "markdown",
"id": "9791e0ca",
"metadata": {},
"source": [
"![image-20240422143154243](../img/image-5-11.png)"
]
},
{
"cell_type": "markdown",
"id": "becf954d",
"metadata": {},
"source": [
"\t\t图 5.11 中的流程图描述了一个典型的 PyTorch 神经网络训练工作流程,我们用它来训练 LLM。它概述了八个步骤,从迭代每个时期开始,处理批处理,重置和计算梯度,更新权重,最后是监控步骤,如打印损失和生成文本样本。如果您对使用 PyTorch 训练深度神经网络比较陌生,并且不熟悉其中任何一个步骤,请考虑阅读附录 A,PyTorch 简介中的 A.5 至 A.8 部分。"
]
},
{
"cell_type": "markdown",
"id": "a5e6748c",
"metadata": {},
"source": [
"​\t\t在代码中,我们可以通过以下train_model_simple函数实现此训练流程:"
]
},
{
"cell_type": "markdown",
"id": "c29235aa",
"metadata": {},
"source": [
"**Listing 5.3 预训练 LLM 的主要功能**"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "ee1deba6",
"metadata": {},
"outputs": [],
"source": [
"def train_model_simple(model, train_loader, val_loader, optimizer, device, num_epochs,\n",
"eval_freq, eval_iter, start_context):\n",
" \n",
"\ttrain_losses, val_losses, track_tokens_seen = [], [], [] #A\n",
"\ttokens_seen, global_step = 0, -1\n",
"\tfor epoch in range(num_epochs): #B\n",
" model.train()\n",
" for input_batch, target_batch in train_loader:\n",
" optimizer.zero_grad() #C\n",
" loss = calc_loss_batch(input_batch, target_batch, model, device)\n",
" loss.backward() #D\n",
" optimizer.step() #E\n",
" tokens_seen += input_batch.numel()\n",
" global_step += 1\n",
" if global_step % eval_freq == 0: #F\n",
" train_loss, val_loss = evaluate_model(\n",
" model, train_loader, val_loader, device, eval_iter)\n",
" train_losses.append(train_loss)\n",
" val_losses.append(val_loss)\n",
" track_tokens_seen.append(tokens_seen)\n",
" print(f\"Ep {epoch+1} (Step {global_step:06d}): \"\n",
" f\"Train loss {train_loss:.3f}, Val loss {val_loss:.3f}\")\n",
" generate_and_print_sample( #G\n",
" \tmodel, train_loader.dataset.tokenizer, device, start_context\n",
" )\n",
"\treturn train_losses, val_losses, track_tokens_seen"
]
},
{
"cell_type": "markdown",
"id": "1a5cf34a",
"metadata": {},
"source": [
"\t\t请注意,我们刚刚创建的 train_model_simple 函数使用了两个尚未定义的函数:evaluate_model 和 generate_and_print_sample。\n",
"\n",
"\t\tevaluate_model 函数对应于图 5.11 中的步骤 7。它会在每次模型更新后打印训练和验证集损失,以便我们可以评估训练是否改进了模型。\n",
"\n",
"\t\t更具体地说,evaluate_model函数计算训练和验证集的损失,同时确保模型处于评估模式,在计算训练和验证集的损失时禁用梯度跟踪和辍学:"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "42a2623b",
"metadata": {},
"outputs": [],
"source": [
"def evaluate_model(model, train_loader, val_loader, device, eval_iter):\n",
" model.eval() #A\n",
" with torch.no_grad(): #B\n",
" train_loss = calc_loss_loader(train_loader, model, device, num_batches=eval_iter)\n",
" val_loss = calc_loss_loader(val_loader, model, device, num_batches=eval_iter)\n",
" model.train()\n",
" return train_loss, val_loss"
]
},
{
"cell_type": "markdown",
"id": "dc84e85a",
"metadata": {},
"source": [
"\t\t与 evaluate_model 类似,generate_and_print_sample 函数是一个方便函数,我们用它来跟踪模型在训练过程中是否改进。具体而言,generate_and_print_sample 函数将文本片段 start_context) 作为输入,将其转换为令牌 ID,并将其提供给 LLM,以使用我们之前使用的 generate_text_simple 函数生成文本示例:"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "599d2e5a",
"metadata": {},
"outputs": [],
"source": [
"def generate_and_print_sample(model, tokenizer, device, start_context):\n",
" model.eval()\n",
" context_size = model.pos_emb.weight.shape[0]\n",
" encoded = text_to_token_ids(start_context, tokenizer).to(device)\n",
" with torch.no_grad():\n",
" token_ids = generate_text_simple(\n",
" model=model, idx=encoded,\n",
" max_new_tokens=50, context_size=context_size\n",
" )\n",
" decoded_text = token_ids_to_text(token_ids, tokenizer)\n",
" print(decoded_text.replace(\"\\n\", \" \")) # Compact print format\n",
" model.train()"
]
},
{
"cell_type": "markdown",
"id": "5122ad4c",
"metadata": {},
"source": [
"\t\t虽然 evaluate_model 函数为我们提供了模型训练进度的数字估计,但这个generate_and_print_sample文本函数提供了模型生成的具体文本示例,用于判断其在训练期间的能力。"
]
},
{
"cell_type": "markdown",
"id": "2feb5ef2",
"metadata": {},
"source": [
"**AdamW**"
]
},
{
"cell_type": "markdown",
"id": "3e324e55",
"metadata": {},
"source": [
"\t\tAdam 优化器是训练深度神经网络的热门选择。但是,在我们的训练循环中,我们选择了 AdamW 优化器。AdamW 是 Adam 的一个变体,它改进了权重衰减方法,旨在通过惩罚更大的权重来最大限度地降低模型复杂性并防止过度拟合。这种调整使 AdamW 能够实现更有效的正则化和更好的泛化,因此经常用于 LLM 的训练。"
]
},
{
"cell_type": "markdown",
"id": "cfea4934",
"metadata": {},
"source": [
"\t\t让我们通过使用 AdamW 优化器和我们之前定义的 train_model_simple 函数训练 10 个 epoch 的 GPTModel 实例来了解这一切。"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "57ca345c",
"metadata": {},
"outputs": [],
"source": [
"torch.manual_seed(123)\n",
"model = GPTModel(GPT_CONFIG_124M)\n",
"model.to(device)\n",
"optimizer = torch.optim.AdamW(model.parameters(), lr=0.0004, weight_decay=0.1) #A\n",
"num_epochs = 10\n",
"train_losses, val_losses, tokens_seen = train_model_simple(\n",
" model, train_loader, val_loader, optimizer, device,\n",
" num_epochs=num_epochs, eval_freq=5, eval_iter=1,\n",
" start_context=\"Every effort moves you\"\n",
")"
]
},
{
"cell_type": "markdown",
"id": "1b89adb6",
"metadata": {},
"source": [
"\t\t执行 training_model_simple 功能将启动训练过程,在 MacBook Air 或类似笔记本电脑上大约需要 5 分钟才能完成。在此执行过程中打印的输出如下:"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "35a1d65a",
"metadata": {},
"outputs": [],
"source": [
"Ep 1 (Step 000000): Train loss 9.781, Val loss 9.933\n",
"Ep 1 (Step 000005): Train loss 8.111, Val loss 8.339\n",
"Every effort moves you,,,,,,,,,,,,.\n",
"Ep 2 (Step 000010): Train loss 6.661, Val loss 7.048\n",
"Ep 2 (Step 000015): Train loss 5.961, Val loss 6.616\n",
"Every effort moves you, and, and, and, and, and, and, and, and, and, and, and, and, and, and, and, and, and, and, and, and, and, and,, and, and,\n",
"[...] #A\n",
"Ep 9 (Step 000080): Train loss 0.541, Val loss 6.393\n",
"Every effort moves you?\" \"Yes--quite insensible to the irony. She wanted him vindicated--and by me!\" He laughed again, and threw back the window-curtains, I had the donkey. \"There were days when I\n",
"Ep 10 (Step 000085): Train loss 0.391, Val loss 6.452\n",
"Every effort moves you know,\" was one of the axioms he laid down"
]
},
{
"cell_type": "markdown",
"id": "3fd848ba",
"metadata": {},
"source": [
"​\t\t正如我们所看到的,根据训练期间打印的结果,训练损失急剧改善,从值 9.558 开始,收敛到 0.762。该模型的语言技能有了很大的提高。在开始时,模型只能将逗号附加到开始上下文中(“Every effort moves you,,,,,,,,,,,,”)或重复单词“and”。在训练结束时,它可以生成语法正确的文本。"
]
},
{
"cell_type": "markdown",
"id": "3264949e",
"metadata": {},
"source": [
"​\t\t与训练集损失类似,我们可以看到验证损失从高处开始 (9.856),并在训练期间减少。但是,它永远不会变得像训练集损失那么小,并且在第 10 个纪元之后保持在 6.372。"
]
},
{
"cell_type": "markdown",
"id": "24d42eaa",
"metadata": {},
"source": [
"​\t\t在更详细地讨论验证损失之前,让我们创建一个简单的图,并排显示训练集和验证集损失:"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "1380f03f",
"metadata": {},
"outputs": [],
"source": [
"import matplotlib.pyplot as plt\n",
"def plot_losses(epochs_seen, tokens_seen, train_losses, val_losses):\n",
" fig, ax1 = plt.subplots(figsize=(5, 3))\n",
" ax1.plot(epochs_seen, train_losses, label=\"Training loss\")\n",
" ax1.plot(epochs_seen, val_losses, linestyle=\"-.\", label=\"Validation loss\")\n",
" ax1.set_xlabel(\"Epochs\")\n",
" ax1.set_ylabel(\"Loss\")\n",
" ax1.legend(loc=\"upper right\")\n",
" ax2 = ax1.twiny() #A\n",
" ax2.plot(tokens_seen, train_losses, alpha=0) #B\n",
" ax2.set_xlabel(\"Tokens seen\")\n",
" fig.tight_layout()\n",
" plt.show()\n",
"epochs_tensor = torch.linspace(0, num_epochs, len(train_losses))\n",
"plot_losses(epochs_tensor, tokens_seen, train_losses, val_losses)"
]
},
{
"cell_type": "markdown",
"id": "c7aa2922",
"metadata": {},
"source": [
"​\t\t得到的训练和验证损失图如图 5.12 所示。"
]
},
{
"cell_type": "markdown",
"id": "8840557b",
"metadata": {},
"source": [
"图 5.12 在训练开始时,我们观察到训练集和验证集的损失都急剧减少,这表明模型正在学习。但是,训练集损失在第二个时期之后继续减少,而验证损失停滞不前。这表明模型仍在学习,但它与第 2 期之后的训练集过度拟合。"
]
},
{
"cell_type": "markdown",
"id": "06408771",
"metadata": {},
"source": [
"![image-20240422144030197](..\\img\\image-5-12.png)"
]
},
{
"cell_type": "markdown",
"id": "7a6cf1c9",
"metadata": {},
"source": [
"\t\t如图 5.12 所示,在第一个时期,训练和验证损失都开始改善。然而,损失开始分化超过第二个时代。这种背离以及验证损失远大于训练损失的事实表明模型对训练数据过度拟合。我们可以通过搜索生成的文本片段来确认模型逐字记住了训练数据,例如“The Verdict”文本文件中的“对讽刺非常不敏感”。"
]
},
{
"cell_type": "markdown",
"id": "1ac40e02",
"metadata": {},
"source": [
"​\t\t这种记忆是意料之中的,因为我们正在使用一个非常非常小的训练数据集,并为多个时期训练模型。通常,通常只针对一个时期在更大的数据集上训练模型。"
]
},
{
"cell_type": "markdown",
"id": "157c5d36",
"metadata": {},
"source": [
"​\t\t如前所述,感兴趣的读者可以尝试在古腾堡计划的 60,000 本公共领域书籍上训练模型,其中不会发生这种过度拟合;详见附录B。"
]
},
{
"cell_type": "markdown",
"id": "3e4a2943",
"metadata": {},
"source": [
"\t\t在下一节中,如图 5.13 所示,我们将探讨 LLM 采用的采样方法来减轻记忆效应,从而生成更新颖的文本。"
]
},
{
"cell_type": "markdown",
"id": "a9604871",
"metadata": {},
"source": [
"图 5.13 我们的模型在实现训练函数后可以生成连贯的文本。但是,它经常逐字记住训练集中的段落。以下部分介绍生成更多样化输出文本的策略。"
]
},
{
"cell_type": "markdown",
"id": "6937bae6",
"metadata": {},
"source": [
"![image-20240422144152449](../img/image-5-13.png)"
]
},
{
"cell_type": "markdown",
"id": "d963ca66",
"metadata": {},
"source": [
"\t\t如图 5.13 所示,下一节将介绍 LLM 的文本生成策略,以减少训练数据记忆并提高 LLM 生成文本的原创性,然后我们介绍权重加载以及保存和加载来自 OpenAI 的 GPT 模型的预训练权重。"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.10.9"
}
},
"nbformat": 4,
"nbformat_minor": 5
}
+1
View File
@@ -0,0 +1 @@
Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 88 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 129 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 110 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 208 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 183 KiB