🤭LLM 训练基础指南
参考文章👉原文
Author:cc
😇观前提示
本文没有解释 LLM 的意思,默认前面文章已经讲过它是什么了
请注意,阅读这篇文章并不能让你真的学会如何训练 LLM,而是让你对 LLM 是如何被制作出来的过程有一个整体的概念,这份概念在你之后尝试与 LLM 相关的工作时会派上大用场。
😇免责声明
如果你是一个新人,想要快速了解 LLM 训练过程,你可以先阅览本部分内容。
然而为了让大家都可以看懂,我做了很多抽象,举了很多例子,某些内容不太准确,这是必然的,最为准确的往往是课本上精确到少一个字都不行的概念,这是难以理解的。
所以本篇内容只适合新手理解使用,所以不免会损失一些精度。
你是否好奇大型语言模型(LLM)是如何通过不断学习来变得智能?
是否想学习如何训练一个 LLM 却对其了解不多?
这篇文章将带大家较为全面地了解 LLM 的核心原理、训练方法与优化技术。无论你是对 LLM 已有一定了解,还是刚刚开始探索,这篇文章都将为你揭开 LLM 的奥秘,带你进入一个智能语言模型的世界。
本文聚焦于概念解析,基本不涉及到具体实现。
一、LLM 的工作原理
想知道如何训练 LLM,我们首先需要理解 LLM 做了什么。
LLM 的工作过程
想象一下平时你使用 LLM 的情景。
比如与 ChatGPT 进行聊天。你先输入一段文字,它会根据这段输入,输出一段文字。如下:
这个工作过程可以简化为:
输入 -> 模型 -> 输出
仔细观察,你会发现,模型其实就像一个函数:它接收输入的文字,然后生成输出的回复。
不过像 chatgpt 这样能够有问必答的大语言模型,显然它的函数非常非常复杂,它的背后可能会有上亿个或数十亿的参数。
要理解模型的原理,其实就是理解这个函数到底在做什么,而它的核心可以用四个字概括:文字接龙。
LLM 其实是在做文字接龙
如果根据一句话生成一句话,可能会存在无数的可能性。但如果每次只生成一个字呢?
在 LLM 中,生成一句话被拆解成一连串的文字接龙,举一个例子:
当我输入 “什么是大语言模型?” 时,如下:
表面上它的工作流程是:
- “什么是大语言模型?” → 【函数】 → “大语言模型……”
但实际上它的工作流程是:
- “什么是大语言模型?” → 【函数】 → “大”
- 这一步中,“什么是大语言模型?” 被当作一个未完成的句子,LLM 预测下一个字可能是什么,然后输出:“大”。
- “什么是大语言模型?大” → 【函数】 → “语”
- 它会把前面的输出放到前面,补充为新的输入。重复上面的步骤。
- “什么是大语言模型?大语” → 【函数】 → “言”
- ……(略)……
怎么做文字接龙?
LLM 其实是在计算下一个词的可能性,并选择概率最大的那个字或词。
例如,如果您在 LLM 中输入:“How are”,它将计算下一个词的可能性:它可以将 60% 的概率分配给 “you”,20% 的概率 “things”,依此类推。
接着,它会选择概率最大的字,将这个字加至原来输入的末尾,即 “How are you”,作为新的输入继续文字接龙。
LLM 的本质
现在让我们从抽象的视角来看一看 LLM 的本质。
LLM 从大量的文本数据提取出其中规律与关联,这些模式将以概率的形式体现在模型中:模型学习特定的字后跟另一个字的概率有多高,以此类推。
我们可以将它理解为:一种文本的有损压缩形式。
实现过程
- 生成随机值的参数 —— 生成包含随机值和参数的张量(多维矩阵)
- 通过学习文本数据修改参数 —— 向这些张量输入大量的文本数据(达到 TB 级别!),使模型能够学习所有数据之间的关系并识别它们之间的模式,这些模式以概率的形式存储在我们随机初始化的张量中
如果用一个非常高级的定义来描述 LLMs,那就是 “将一种语言(如英语)的概率分布压缩到一系列矩阵中”。
上面讨论的随机初始化在很大程度上不适用于我们,因为它非常昂贵(我们谈论的是大型模型的数百万美元)。
接下来的内容将以 “对模型进行微调” 为重点 —— 也就是说,采用一个预先训练的模型并向其提供少量数据(通常为几 MB),以使其行为与您心目中的任何下游任务保持一致。
例如,如果需要编码助手模型,则可以根据编码示例对模型进行微调,依此类推。
LLM 的核心
我们在这里不会详细讨论这项架构的具体细节,因为这将涉及到所有促成其产生和发展的早期技术。
理解了 LLM 工作原理后,我们就可以进一步了解这些模型背后的技术架构。
与 LLM 联系最紧密的一个东西恐怕是 Transformer 架构了,它用于语言建模的最常见架构,使得我们能够训练具有令人难以置信的推理能力的大型语言模型 ,同时保持架构足够简单,新手也可以通过它训练 LLM。
而用于训练和构建 Transformer 模型最常用的语言是 Python。目前训练 LLM 最常用的库是 HuggingFace Transformers,它是如今几乎所有 LLM 训练的支柱。这个库提供了许多预训练的模型和工具,使得我们可以方便地训练自己的语言模型。
Transformer 架构
注意这并不是必须的。对于新手来说,上手实践可能会比啃一篇让人不易理解的论文更合适。如果想深入了解,可以看看论文。
《Attention Is All You Need》这篇论文介绍了 Transformer 架构,是一篇非常重要的论文,值得仔细阅读。然而,在阅读这篇论文之前,你可能需要先阅读一些内容,因为作者假设你已经具备神经网络的基础知识。建议按以下顺序阅读:
接下来,我们将具体介绍如何训练一个大型语言模型(LLM)。
二、如何训练一个 LLM?
刚刚我们提到,LLM 就像一个非常复杂的函数,里面含有可能上亿的参数。显然,这么多的参数是不能被人为确定的。因此我们需要机器来帮助我们确定合适的参数,找出这个函数。
你可以这样理解,当机器具备找一个函数的能力,其实就代表 “机器具备有学习的能力”。
而机器寻找这个复杂函数的过程,就是训练的过程,也是学习的过程。
实际情况的不同会影响最优的训练策略,因此有多种训练方法。
概括来说,基本上有三种训练 LLMs 方法:预训练、微调和 LoRA/Q-LoRA。
接下来将逐一介绍。
1.Pre-training 预训练
在我们让 LLM 去帮助解决具体的问题之前,我们需要先教它很多基础的知识,比如语言知识、复杂多层次的世界知识,为以后解决更复杂的问题打好基础。
这一步,LLM 将从大量的文本数据中学习到广泛的知识和模式。
抽象解释
简单来说,首先有一个有着大量的随机参数的原始模型。
然后我收集了大量的文本数据(全面并且足够多),作为训练数据集。
然后让原始模型学习训练数据集,并调整自己的参数。
这样模型就获得了非常非常多的知识,并且也能大致理解并流畅地生成文本。但是对于特定任务的处理等还很生疏。
下面的内容会更加深入,会略显复杂。
预训练的大致步骤如下:
数据收集:首先,需要收集大量文本数据数据集,通常数据量达到 TB 级。这些数据是训练模型以理解和生成文本的基础。
选择模型:根据特定的任务需求,选择或创建适当的模型架构。模型架构定义了数据如何在网络中流动和被处理。
分词器训练:训练一个分词器以正确处理数据。分词器负责将文本有效地编码和解码,这是理解和生成文本的关键步骤。
数据预处理:使用分词器的词汇表对收集到的数据集进行预处理,将原始文本转换为适合训练的格式。(这一步是必要的,因为计算机不能直接理解和处理自然语言文本,而只能处理数字。)包括以下几个步骤:
- 文本分割成令牌(分词,Tokenization):
- 这个过程涉及将连续的文本字符串分割成更小的单元,称为 “令牌”(tokens)。这些令牌通常是单词、数字或标点符号。
- 例如,句子 “我爱自然语言处理” 可能会被分割成令牌 “我”、“爱”、“自然语言” 和 “处理”。
- 将令牌转换为数字 ID:
- 为了让计算机能处理这些令牌,每个令牌都被映射到一个唯一的数字 ID。这个映射过程通常使用分词器内建的词汇表完成。
- 例如,如果词汇表是
{"我": 1, "爱": 2, "自然语言": 3, "处理": 4}
,那么上面的令牌将被转换为序列 [1, 2, 3, 4]。
- 添加特殊令牌和注意力掩码:
- 特殊令牌:在某些情况下,可能需要在令牌序列中插入特殊令牌。这些特殊令牌有多种用途,例如表示序列的开始(如
[CLS]
)和结束(如[SEP]
),或用于分隔不同的句子等。 - 注意力掩码:这是一种机制,用于在模型处理输入数据时指示哪些令牌是重要的,哪些应该被忽略。它通常用于控制模型在处理像填充令牌这样无关紧要的令牌时的行为。
- 特殊令牌:在某些情况下,可能需要在令牌序列中插入特殊令牌。这些特殊令牌有多种用途,例如表示序列的开始(如
- 文本分割成令牌(分词,Tokenization):
模型预训练
- 目标:在预训练期间,模型学习预测句子中的下一个单词或利用大量可用数据来填充缺失的单词。此过程涉及通过迭代训练过程优化模型的参数,该过程可最大限度地提高在给定上下文的情况下生成正确单词或单词序列的可能性。
- 方法:为了实现我们的目标,预训练阶段通常采用自监督学习技术的变体。
掩码语言建模(MLM):
步骤如下:
- 数据准备:文本数据中随机选取一定比例的令牌(如 15%),并隐藏这些令牌,即将这些令牌替换为一个特殊的掩码令牌(如
[MASK]
)。 - 模型训练:模型的输入是部分被隐藏的文本序列,任务是预测被掩码的令牌。模型必须利用未被掩码的上下文令牌来准确预测缺失的令牌。
- 参数优化:通过迭代的训练过程,模型的参数根据预测的准确性进行优化,使用如一些特定的损失函数来衡量预测令牌与实际令牌之间的差异,并据此调整参数。
通过以这种方式对大量数据进行训练,该模型逐渐对语言模式、语法和语义关系有了丰富的理解。
- 数据准备:文本数据中随机选取一定比例的令牌(如 15%),并隐藏这些令牌,即将这些令牌替换为一个特殊的掩码令牌(如
因果语言建模(CLM):
然而,当今最常用的方法是因果语言建模。
与 MLM 不同,因果语言建模侧重于在给定前一个上下文的情况下预测句子中的下一个单词。遵循如下步骤:
- 数据准备:数据不进行掩码处理,保留原始的文本序列。
- 模型训练:模型一次处理一个令牌,预测序列中下一个即将出现的令牌。这种模式使模型在每个时间步只依赖于先前的令牌,反映了自然语言的生成过程。
- 参数优化:模型通过预测每个令牌后的下一个令牌来训练,利用如一些特定的损失函数来衡量预测令牌与实际令牌之间的差异,并据此调整参数。
因此,因果语言建模更加适合于生成任务,因为它按照时间顺序处理和生成令牌,模拟人类说话或写作的自然流程。
预训练的结果与不足
这个初始预训练阶段旨在捕获一般语言知识,使模型成为熟练的语言编码器。但是,它缺乏关于特定任务或领域的具体知识。为了弥合这一差距,在预训练之后将进行随后的微调阶段。
2.Fine-tuning 微调
💡微调是把模型的整个 “脑袋” 稍微调整一下,让它更聪明或者更适合特定任务。
在最初的预训练阶段,模型学习到通用的语言知识。之后,通过微调,可以将模型的功能专门化,并在更窄、更特定于任务的数据集上优化其性能。
微调过程涉及几个关键步骤。
- 收集特定于任务的数据集:首先,收集特定于任务的数据集,该数据集由与所需任务相关的标记示例组成。例如,如果任务是 指令调优,则需要收集 指令 - 响应对 的数据集。微调的数据集通常比预训练的数据集要小得多。
- 初始化预训练模型:使用 预训练阶段学习到的参数 初始化模型。这意味着微调阶段不是从头开始,而是基于已经学到的通用语言知识进行调整。
- 训练模型:在特定于任务的数据集上训练模型,优化其参数以 最小化 特定于任务的损失,即模型预测结果与所需结果之间的差异。
- 基于梯度的优化算法:使用诸如随机梯度下降(stochastic gradient descent ,SGD)或 Adam 等基于梯度的优化算法来调整预训练模型的参数。通过在模型层中反向传播损失,计算梯度,使模型能够从错误中学习并相应地更新其参数。(后面的部分有梯度的内容~)
为了优化微调效果,可以采用其他技术,例如学习率调度(learning rate scheduling)、正则化方法(dropout or weight decay)或提前停止以防止过拟合( early stopping to prevent overfitting)。这些技术有助于优化模型的泛化,并防止模型过于紧密地记住训练数据集。
具体细节会在之后介绍。
3.Low-Rank Adaptation (LoRA) 低秩适应
微调的计算成本很高,需要数百 GB 的 VRAM 来训练数十亿个参数模型。为了解决这个具体问题,提出了一种新方法:低秩适应。
与使用 Adam 微调 OPT-175B 相比,LoRA 可以减少 10,000 倍的可训练参数数量和 3 倍以上的 GPU 内存需求。
Refer to the paper:LoRA: Low-Rank Adaptation of Large Language Models and the HuggingFace PEFT: Parameter-Efficient Fine-Tuning of Billion-Scale Models on Low-Resource Hardware
对于普通消费者来说,将内存需求降低 3 倍仍然不可行。于是引入了一种新的 LoRA 训练方法:量化低秩适应 (QLoRA)。
它利用 bitsandbytes 库对语言模型进行动态和近乎无损的量化,并将其应用于 LoRA 训练过程。这大大降低了内存需求 —— 能够在 2 个 NVIDIA RTX 3090 上训练多达 700 亿个参数的模型!相比之下,您通常需要超过 16 个 A100-80GB GPU 来微调该尺寸级别的模型,相关成本将是巨大的。
具体细节会在之后介绍,重点介绍微调和 LoRA/QLoRA 方法。
三、Fine-tuning 微调
💡训练大概需要多少算力?
如前所述,微调可能很昂贵,具体取决于您选择的模型大小。通常至少需要 6B/7B 参数
根据您的模型和数据集大小,内存要求会有所不同。您可以参考 EleutherAI 的 Transformer Math 101 博客文章,了解详细但易于理解的计算。
您将需要微调至少 7B 类的模型。一些流行的选项是 Llama-2 7B 和 Mistral 7B 等。此大小级别通常需要 160~192GB 范围内的内存。从本质上讲,您的选择可以归结为:
- 从云服务租用 GPU; 例如 Runpod、VastAI、Lambda Dare 和 Amazon AWS Sagemaker。在四个示例中,VastAI 是最便宜的(但也是最不可靠的),而 Amazon Sagemaker 是最昂贵的。我建议使用 Runpod 或 Lambdalabs。
- 使用 Google 的 TPU Research Cloud:您可以申请免费访问 Google TRC 计划,并可能获得多达 110 台 TPU 机器。请记住,TPU 在架构方面与 GPU 有很大不同;在承诺您的 30 天免费 TRC 计划之前,您需要先了解它们的工作原理。幸运的是,Google 通过 Google Colaboratory 免费提供对 TPU(尽管较弱的 TPU)的访问。还有一些库和指南用于在 TPU 上微调 LLMs。Mesh Transformers JAX 存储库提供了在 TRC 上微调 GPT-J 模型的指南,EasyLM 提供了一种在 TPU 和 GPU 上预训练、微调和评估语言模型的简便方法。
- 认识一个认识一个人的人。也许你的一个朋友认识一个可以使用超级计算机的人。那不是很酷吗?
1. 收集数据集
毫无疑问,数据集收集是微调过程中最重要的部分。质量和数量都很重要,但质量更重要。
首先,想想你希望微调后的模型能做什么。 写故事?角色扮演?为你写你的电子邮件?也许你想创建你的 AI 对象。
让我们假设你想训练一个类似 Pygmalion 的聊天和角色扮演模型。
接下来,你需要收集一个对话数据集,特别是包含互联网角色扮演风格的对话内容。收集这些数据可能会有些挑战性,你必须自己弄清楚:D
💡数据集结构
你的数据集的要求:
- 数据多样性:您不希望模型只执行一项非常具体的任务。在我们假设的用例中,我们正在训练一个聊天模型,但这并不意味着数据将只涉及一种特定类型的聊天 / RP。您将需要使训练样本多样化,包括各种方案,以便模型可以学习如何为各种类型的输入生成输出。
- 数据集大小:与 LoRA 或 Soft Prompts 相比,您需要相对大量的数据。当然,这与预训练数据集不在同一级别。根据经验,请确保至少有 10 MiB 的数据用于微调。过度训练模型非常困难,因此你可以堆叠更多数据。
- 数据集质量:数据质量非常重要。您希望数据集能够反映模型的结果。如果你给它喂垃圾,它会吐出垃圾。
2. 处理原始数据集
你现在可能有一堆文本数据。在继续之前,您需要将它们解析为适合预处理的格式。
如果您从网站抓取数据,则可能有 HTML 文件。在这种情况下,您的首要任务是从 HTML 元素中提取数据。
如果您从在线开放数据源获取数据集,则可能拥有 CSV 文件。
或者您从 SQL 中获取数据集,可能会更难一些。
具体的实现过程,可以去看后面的项目。
3. 降低 “噪声”
“最好的语言模型是随机的。”,这使得我们很难预测它们会如何生成(即使输入提示保持不变)。这有时会导致低质量和不良输出。
您需要确保清除数据集中不需要的元素。,如果您的数据源是合成的,即由 GPT-4/3 生成,这一点就显得尤为重要。
您可能希望过滤删除提及的一些短语,例如 “作为 AI 语言模型...”、“有害或令人反感的内容......”、“...... 由 OpenAI 训练......“等。ehartford 的这个脚本是此特定任务的良好过滤器。你也可以参考 https://github.com/AlpinDale/gptslop
仓库。
4. 开始训练运行
我们将使用 https://github.com/OpenAccess-AI-Collective/axolotl
进行微调,因为它使用简单且具有我们需要的所有功能。
如果您使用的是 RunPod 等云计算服务,具备所有必要的要求。
首先克隆存储库和安装要求:
git clone https://github.com/OpenAccess-AI-Collective/axolotl && cd axolotl
pip3 install packaging
pip3 install -e '.[flash-attn,deepspeed]'
2
3
这将安装 axolotl,然后我们就可以开始微调了。
axolotl 将所有训练选项都放在一个 yaml
文件中。 examples
目录中已经有一些示例配置,适用于各种不同的模型。
在此示例中,我们将使用 QLoRA 方法训练 Mistral 模型,这应该可以在单个 3090 GPU 上实现。要开始运行,只需执行以下命令:
**accelerate launch -m axolotl.cli.train examples/mistral/config.yml**
恭喜!你刚刚训练了 Mistral!示例配置使用一个非常小的数据集,应该需要很少的时间来训练。若要使用自定义数据集,需要将其正确格式化为 JSONL
文件。
axolotl 有许多不同的格式,你可以在这里找到例子。
然后, qlora.yml
您可以编辑文件并将其指向您的数据集
所有配置选项的完整说明都在这里,请确保单击展开按钮以查看所有选项!
现在你知道如何训练模型了。我们将在下面首先解释 LoRA 到底是什么,以及为什么它有效。
三、Low-Rank Adaptation (LoRA) 低秩适应
💡LoRA 是什么?
LoRA 就像是在预训练好的模型上加上一层专门的 “滤镜”,让它能在特定任务上表现得更好,就像在原有的画作上加一层滤镜,而不破坏原画。
“原有的画作”:在预训练阶段,模型通过大量的数据学习到了一般性的知识和模式,这些知识以权重的形式存储在模型的神经网络中。
“滤镜”:LoRA 在现有权重中引入成对的秩分解权重矩阵(称为更新矩阵)。LoRA 只专注于训练这些新添加的权重
LoRA 能够加快大型语言模型的训练过程,同时减少内存消耗。它具有以下几个优点:
- 保留了预训练模型中的原始权重:应用 LoRA 的过程中,模型的原始能力和知识不会被改变或丢失。这将灾难性遗忘的风险降至最低,确保了模型在适应新数据的同时保留其现有知识。
- 训练权重的可移植性:与原始模型相比,LoRA 中使用的秩分解矩阵的参数要少得多。这一特性使得经过训练的 LoRA 权重能够轻松地在不同环境中转移和使用,非常便携。
- 与原始模型的注意力层集成:LoRA 矩阵通常合并到原始模型的注意力层中。此外,这里提供了可以用来调节的 适应量表参数 ,它可以控制模型适应新训练数据的程度,即控制模型在面对新数据时,变化的幅度有多大。
- 提高内存效率:LoRA 提高了内存效率,只需要不到原来微调所需计算量的三分之一,就能完成微调任务。这使得模型微调更高效、更经济。
1.LoRA hyperparameters LoRA 超参数
在介绍各个超参数之前,我们先来了解一下模型参数与超参数的区别。
- 模型参数:
- 定义:模型参数是在训练过程中通过数据学习到的参数,是模型用来进行预测的实际参数。
- 设置方式:模型参数通过训练算法(如反向传播)从数据中自动学习和更新。
- 作用:模型参数直接决定了模型的预测能力,是模型在看到新的输入时生成输出的关键。
- 超参数
- 定义:超参数是在训练之前设定的参数,用于控制训练过程和模型架构。
- 设置方式:超参数由你手动设定,通常需要通过实验或调参方法(如网格搜索、随机搜索)来确定最佳值。
- 作用:超参数影响模型训练的效率和最终性能,但它们不是通过训练过程自动学习到的。
LoRA Rank 秩
💡秩有什么用?
这决定了秩分解矩阵的维度大小。也就是 “滤镜” 有多大。
Rank(维度)越高,结果越好,计算要求也越高。数据集越复杂,rank 就越高。
如果要进行完全微调(所有参数都被调整),您可以将 rank 设置为等于模型的隐藏大小。但是,不建议这样做,因为这会浪费大量资源。
您可以通过阅读
config.json
或使用 Transformers 加载模型AutoModel
并使用以下model.config.hidden_size
函数来找出模型的隐藏大小:
from transformers import AutoModelForCausalLM
model_name = "huggyllama/llama-7b" # can also be a local directory
model = AutoModelForCausalLM.from_pretrained(model_name)
hidden_size = model.config.hidden_size
print(hidden_size)
2
3
4
5
LoRA Alpha 超参数
💡超参数有什么用?
它决定了模型在微调时,LoRA 矩阵对模型权重 调整的幅度 。通过调整 Alpha,可以控制 LoRA 在新任务中的适应能力。
在 LoRA 中,低秩矩阵通常与模型的原始权重矩阵相加。
Alpha 用于缩放这个低秩矩阵,使得微调过程对原始权重的影响可以灵活调节:
- 小 Alpha 值:如果 Alpha 值较小,LoRA 对模型权重的调整较少,模型对新数据的适应性较低,但保持了对原始任务的良好性能。
- 大 Alpha 值:如果 Alpha 值较大,LoRA 对模型权重的调整较多,模型对新数据的适应性较高,但可能会在原始任务上表现稍差。
2.LoRA Target Modules 目标模块
💡目标模块有什么用?
在一个复杂的模型中,有很多不同的模块,每个模块都有它自己的参数。我们可以选择其中的一部分作为我们的目标模块,专注于改进和优化这些模块。这样做可以使模型在特定的任务上表现得更好,同时也可以提高训练的效率。
如何确定目标模块?
我们可以选择模型中某些特定的权重和矩阵进行训练。
在所有的权重和矩阵中,最基本和常用的训练对象是查询向量(Query Vectors) 和值向量(Value Vectors) 的投影矩阵。
- 查询向量的投影矩阵通常叫做
q_proj
。 - 值向量的投影矩阵通常叫做
v_proj
。
但是,不同的模型可能会使用不同的名称来表示这些矩阵。
您可以通过运行以下脚本来查找确切的名称:
from transformers import AutoModelForCausalLM
model_name = "huggyllama/llama-7b" # 这个名字也可以是一个本地的目录
model = AutoModelForCausalLM.from_pretrained(model_name)
layer_names = model.state_dict().keys()
for name in layer_names:
print(name)
2
3
4
5
6
7
这个程序会输出一长串名字,就像这样:
model.embed_tokens.weight
model.layers.0.self_attn.q_proj.weight
model.layers.0.self_attn.k_proj.weight
model.layers.0.self_attn.v_proj.weight
model.layers.0.self_attn.o_proj.weight
...
model.norm.weight
lm_head.weight
2
3
4
5
6
7
8
模块的基本说明
命名约定基本上是: {identifier}.{layer}.{layer_number}.{component}.{module}.{parameter}
.
注意!下面这些名称对于每个模型架构都不同
up_proj
(向上投影矩阵)
- 功能:用于从解码器传递到编码器的注意力机制。
- 作用:将解码器的隐藏状态转换为与编码器隐藏状态相同的维度,以便在注意力计算期间保持兼容性。
- 举例:就像我们把一个信号从一个设备发送到另一个设备时,需要确保信号格式兼容,
up_proj
就是用来做这种转换的。
down_proj
(向下投影矩阵)
- 功能:用于从编码器传递到解码器的注意力机制。
- 作用:将编码器的隐藏状态转换为解码器预期的维度,以便进行注意力计算。
- 举例:就像我们把一种语言翻译成另一种语言,确保解码器可以理解来自编码器的信息。
q_proj
(查询投影矩阵)
- 功能:应用于注意力机制中的查询向量。
- 作用:将输入的隐藏状态转换为所需的维度,以实现有效的查询表示形式。
- 举例:就像在搜索引擎中,我们输入查询词,然后系统将其转换为能在数据库中有效查找的格式。
v_proj
(值投影矩阵)
- 功能:应用于注意力机制中的值向量。
- 作用:将输入的隐藏状态转换为有效值表示的所需维度。
- 举例:就像我们找到了想要的信息,并将其转换成我们可以理解和使用的格式。
k_proj
(关键投影矩阵)
- 功能:应用于注意力机制中的关键向量。
- 作用:帮助模型确定哪些信息是重要的(关键的),并将这些信息转换为合适的维度。
- 举例:就像你在准备考试,需要复习很多书,但是时间有限。
k_proj
就像一个老师,他帮你挑选出考试最重要的部分,让你重点复习这些内容。
o_proj
(输出投影矩阵)
- 功能:应用于注意力机制的输出。
- 作用:将组合后的注意力输出转换为所需的维度,以便进一步处理。
- 举例:就像在一个团队会议中,汇总所有人的意见后,整理成一个清晰的报告进行下一步的决策。
但是,有三个(或 4 个,如果您的模型有偏差)异常值。它们不遵循上面指定的命名约定,省略了图层名称和编号。这些是:
- Embedding Token Weights
- 人话版:这个层是模型的起点,它把输入的单词或者标记转换成计算机能理解的形式。这些转换后的形式叫做密集向量。
embed_tokens
表示与模型嵌入层关联的参数,通常放置在模型的开头,因为它用于将输入标记或单词映射到其相应的密集向量表示。- 如果数据集具有自定义语法,则需要注意它能否准确 “翻译”。
2. Normalization Weights norm
- 模型中的归一化层。
- 人话版:当我们在训练神经网络时,数据会在各个层之间传递。在每一层,数据会经过各种计算,比如加法、乘法等。
norm
会调整数据,让它们在一个合适的范围内,这样模型能更好地学习。 - 层或批量归一化通常用于提高深度神经网络的稳定性和收敛性。它们通常放置在模型架构的某些层内或之后,以缓解梯度消失或爆炸等问题,并帮助加快训练速度和更好的泛化。
- 通常不针对 LoRA。
3. Output Layer lm_head
- 语言模型的输出层。
- 人话版:这个层是模型的最后一步,它根据前面的层学到的信息来预测下一个单词。
- 它负责根据从前几层学习到的表示形式为下一个令牌生成预测或分数,放在底部。
- 如果数据集具有自定义语法,则需要注意是否符合该语法。
四、QLoRA 量化低秩适应
我们首先要了解一下量化的概念。
1. 量化
💡量化是什么?
我们会发现,高清的图片往往比模糊的图片占有更多的空间。有时即使图片有些模糊,我们也可以看清主体。量化就像是把图片的像素变低,使它变得模糊,但仍保留主要特征。
在计算机里,量化是把复杂的数值数据用更简单的数值来表示。比如:
- 浮点数量化到整数:原来的数据是非常精确的浮点数(小数),现在你把它们变成了整数。虽然精确度降低了,但计算起来更简单,更快。
- 低位量化:原来每个数值可能需要很多位(二进制位)来表示,现在你用更少的位来表示。比如说,从 32 位变成 8 位,这样数据的体积就变小了,占用的存储空间也减少了。
量化的好处:
- 减少存储空间:数据变小了,占用的存储空间也就少了。
- 加快计算速度:简单的数据处理起来更快,计算效率提高了。
- 节省资源:更少的存储空间和计算资源意味着更低的成本。
2.QLoRA
💡QLoRA 是什么?
QLoRA,它不仅使用了 LoRA 的方法,还通过量化来进一步减小模型的大小和复杂度。
由于 QLoRA 多了量化这一步,所以相比 LoRA,QLoRA 生成的模型会更小、更紧凑,也实现过程更复杂一些。
由此,QLoRA 是一种高效的微调方法,可减少内存使用量,同时保持大型语言模型的高性能。
它支持在单个 48GB GPU 上微调 65B 参数模型,同时保留完整的 16 位微调任务性能。
QLoRA 的主要创新点包括:
- 在训练过程中,预训练的语言模型参数被量化为 4 位,并被冻结,不参与训练过程。只有低秩适配器(LoRA)的参数会在反向传播过程中更新,这样大大减少了计算量和存储需求。
- 使用 4 位 NormalFloat(NF4)(一种专门设计的数据类型),能够以最佳方式处理正态分布的权重。这种数据类型有助于在保持模型精度的同时减少计算和存储资源的消耗。
- 双重量化技术:不仅对模型权重进行量化,还对这些量化权重的量化常数本身也进行量化,从而大大减少了所需的存储空间。
- ** 分页优化器:** 用于在微调过程中有效管理内存峰值。就像分批搬运重物一样,分页优化器通过分阶段处理来减轻内存的负担。
五、Training Hyperparameters 训练超参数
💡让我们把训练类比为学习投篮
在练习投篮的过程中,我们需要知道如何改变自己的动作去完成正确的投篮。
其中一种改进方法是,进行一次投篮,看球是否进篮。如果投篮没有成功,观察为什么没进(如投得太轻、角度不对等),并通过这个反馈来判断做什么调整(如更用力、改变手腕角度等)。每次投篮,都会根据上一次的反馈进行调整,逐渐找到最佳投篮方式。
然后我们会基于这种方法进行练习。比如每组练习几次投篮,每天练习多少组。
这里的改进方法就相当于我们的优化算法。
例如随机梯度下降 (SGD),它是一种迭代学习算法,通过学习训练数据集,来逐步减少损失或误差,更新模型参数。
而我们练习的具体安排,就是训练超参数的配置。
训练超参数是指导训练过程的设置,用于确定模型如何从提供的数据中学习。
这类超参数的选择会显著影响训练的效果(模型的收敛性、泛化性和整体有效性)
1.Stochastic Gradient Descent 随机梯度下降
随机梯度下降 (SGD) 是一种优化算法,用于为模型查找最佳内部参数,旨在最大限度地减少对数损失或均方误差等性能指标。
了解常见的损失函数可以参考: 这篇文章
优化可以被认为是一个搜索过程,其中算法学习以改进模型。使用的特定优化算法称为 “梯度下降”。在这里,“梯度” 是指计算误差斜率或梯度,而 “下降” 表示沿此斜率向下移动以接近最小误差水平。
该算法以迭代方式工作,这意味着它经历了多个离散的步骤,每个步骤都旨在增强模型参数。在每个步骤中,模型使用当前的内部参数集对样本子集进行预测。然后将这些预测与实际的预期结果进行比较,从而计算误差。然后利用此错误更新内部模型参数。
更新过程因所使用的算法而异,但在人工神经网络的情况下,采用反向传播更新算法。
2.Sample 样本
💡样本是什么?
在投篮练习中,你每一次的投篮,就相当于一个 “样本”。每个投篮都有其输入(如你的站位、手腕的角度、投篮的力度)和输出(球是否进篮)。
样本或序列是单行数据。它包含输入算法的输入和用于与预测进行比较和计算误差的输出。样本也可以称为实例、观测值、输入向量、序列或特征向量。
训练数据集由许多行数据组成,例如许多样本。
3.Batch 批
💡批是什么?
在投篮训练中,每次你不只投一个球,而是连续投了 10 个球,这就形成了一个 “批次”。你根据这 10 个球的总体表现(如进了多少球)来判断和调整投篮技巧(如调整手腕角度或用力程度)。每个批次后,都会根据反馈来优化下一组的投篮策略。
批量大小是一个训练超参数,用于确定在更新模型的内部参数之前要处理的样本数。
批处理的过程是:处理一批样本后,将预测结果与预期输出进行比较,并计算误差。然后,该误差用于通过调整其参数来改进模型,并沿误差梯度的方向移动。
一个训练数据集可以分为一个或多个批次。以下是基于批量大小的不同类型的梯度下降算法:
- 批量梯度下降: 当批量大小等于训练样本总数时,称为批量梯度下降。在这种情况下,整个数据集用于计算预测并在更新模型之前计算误差。
- 随机梯度下降: 当批处理大小设置为 1 时,称为随机梯度下降。在这里,每个样本都是单独处理的,模型参数在每个样本之后更新。这种方法在学习过程中引入了更多的随机性。
- 小批量梯度下降: 当批量大小大于 1 且小于训练数据集的总大小时,称为小批量梯度下降。该算法适用于小批量样本,进行预测并相应地更新模型参数。小批量梯度下降在批量梯度下降的效率和随机梯度下降的随机性之间取得了平衡。
通过调整批量大小,我们可以控制计算效率和学习过程随机性之间的权衡,使我们能够找到有效训练模型的最佳平衡点。
Batch Gradient Descent:
Batch Size = Size of Training Set
批量梯度下降:
Batch Size = Size of Training Set
Stochastic Gradient Descent:
Batch Size = 1
随机梯度下降:
批量大小 = 1
Mini-Batch Gradient Descent:
1 < Batch Size < Size of Training Set
小批量梯度下降:
1 < Batch Size < Size of Training Set
在小批量梯度下降的情况下,常见的批次大小包括 32
、 64
和 128
个样品。您可能会在大多数教程中看到模型中使用的这些值。
💡如果数据集未按批处理大小均匀划分怎么办?
这种情况确实经常发生。它只是意味着最终批次的样本比其他批次少。您只需从数据集中删除一些样本或更改批次大小,以便数据集中的样本数能够整除批次大小。大多数训练脚本会自动处理此问题。
4.Epoch
💡epoch 是什么?
假设你每天安排完成 50 组投篮练习(每组 10 个球),每组包含 10 个球,总共要投 500 个球。完成这 500 个球的全部投篮练习,相当于整个训练数据集被 “使用” 了一次,这就是一个 Epoch。每天你会按照相同的方式进行一次完整的练习(即一个 epoch),每天都尝试通过多次练习来不断改进你的投篮技巧。
epoch 数是一个超参数,用于确定学习算法将遍历整个数据集的次数。1 个 Epoch 表示训练数据集中的每个样本都已使用一次来更新模型的内部参数。它由一个或多个批次组成。
例如,如果我们每个 epoch 有一个批次,则它对应于前面提到的批次梯度下降算法。
您可以将 epoch 数可视化为循环访问训练数据集的 “for-loop”。在此循环中,还有另一个嵌套的 “for-loop”,它遍历每批样本,其中批次包含根据批处理大小指定数量的样本。
为了评估模型在各个时期的性能,通常会创建折线图,也称为学习曲线。这些图在 x 轴上显示纪元为时间,在 y 轴上显示模型的误差或技能。学习曲线可用于诊断模型是否过度学习(高训练误差、低验证误差)、学习不足(低训练和验证误差)或达到与训练数据集的合适拟合(低训练误差,合理低验证误差)。我们将在下一部分深入研究学习曲线。
还是你还不明白其中的区别?在这种情况下,让我们看看批次和纪元之间的主要区别:
💡Batch vs Epoch 批处理与纪元
批量大小是在更新模型之前处理的样本数。
epoch 数是通过训练数据集完成传递的次数。
批次的大小必须大于或等于 1 (bsz=>1) 且小于或等于训练数据集中的样本数 (bsz=< No.Samples) 的 Samples 进行
纪元数可以设置为介于 1 和无穷大之间的整数值。您可以根据需要运行算法,甚至可以使用固定纪元数以外的其他标准停止算法,例如模型误差随时间的变化(或没有变化)。
它们都是整数值,并且都是学习算法的 hparams,例如学习过程的参数,而不是学习过程找到的内部模型参数。
您必须指定学习算法的批处理大小和周期数。
关于如何配置这些 hparams,没有神奇的经验法则。您应该尝试为您的特定用例找到最佳位置。😄
下面是一个快速工作示例:
假设您有一个包含 200 个样本(数据行或数据序列)的数据集,并且您选择的批处理大小为 5 和 1,000 个 epoch。这意味着数据集将分为 40 批,每批有 5 个样本。每批五个样品后,模型权重将更新。这也意味着一个时期将涉及 40 个批次或 40 次模型更新。
对于 1,000 个 epoch,模型将向整个数据集公开 1,000 次。在整个培训过程中,总共有 40,000 个批次。
请记住,批处理大小越大,GPU 内存使用率就越高。我们将使用梯度累积步骤来克服这个问题!
5.Learning Rate 学习率
正如 Batch 和 Epoch 一节中所讨论的,在机器学习中,我们经常使用一种称为随机梯度下降 (SGD) 的优化方法来训练我们的模型。SGD 的一个重要因素是学习率,它决定了模型在每次更新其权重期间应根据估计误差而变化的程度。
将学习率视为一个旋钮,用于控制为改进模型而采取的步骤的大小。如果学习率太小,模型可能需要很长时间才能学习或陷入次优解决方案。另一方面,如果学习率太大,模型可能会学习得太快,最终得到不稳定或不太准确的结果。
选择正确的学习率对于成功的培训至关重要。您需要试验和调查不同的学习率如何影响模型的性能。通过这样做,您可以直观地了解学习率如何影响模型在训练期间的行为。
因此,在微调训练过程时,请密切注意学习率,因为它在确定模型的学习和执行效率方面起着重要作用。
Learning Rate and Gradient Descent 学习率和梯度下降
随机梯度下降使用训练数据集中的示例估计模型当前状态的误差梯度,然后使用误差反向传播算法(简称为 “反向传播”)更新模型的权重。
在训练期间更新权重的量称为步长或 “学习率”。
具体来说,学习率是训练中使用的可配置超参数,其正值非常小,通常在 0.0 到 1.0 之间。(注意:在这些值之间,而不是这些值本身。)
学习率控制模型适应问题的速度。
- 较小的学习率将要求您有更多的训练 epoch,因为每次更新都会对权重进行较小的更改。
- 较大的学习率会导致模型参数快速变化,并且需要的训练 epoch 更少。
high learning rate = less epochs. 高学习率 = 更少的纪元。low learning rate = more epochs. 低学习率 = 更多时期。
学习率可能是最重要的超参数。如果你有时间只优化一个超参数,请调整学习率。
Configuring the Learning Rate 配置学习率
从合理的范围开始:首先考虑类似任务中常用的学习率值范围。找出用于您正在微调的预训练模型的学习率,并以此为基础。例如,一个常见的起点是 1e-5 (0.00001),这通常被发现对变压器模型有效。
观察训练进度:以选定的学习率运行训练过程,并在训练期间监控模型的性能。密切关注损失或准确性等指标,以评估模型的学习情况。
太慢了?如果学习率太小,您可能会注意到模型的训练进度很慢,需要很长时间才能收敛或做出明显的改进。在这种情况下,请考虑提高学习速度以加快学习过程。
太快了?如果学习率太大,模型可能会学习得太快,从而导致结果不稳定。 LR
过高的迹象包括训练期间损失或准确性的剧烈波动。如果您观察到此行为,请考虑降低 lr
。
迭代调整学习率:根据步骤 3 和 4 中的观察结果,迭代调整学习率并重新运行训练过程。逐渐缩小产生最佳性能的学习率范围。
计算学习率的通用公式是:
base_lr * sqrt(supervised_tokens_in_batch / pretrained_bsz)
base_lr
是指预训练模型的学习率。在 Mistral 的情况下,这是 5e-5。 supervised_tokens_in_batch
是指监督令牌的总数(一旦你开始训练,蝾螈就会报告这个数字),除以总步数(也由 axolotl 报告)除以 epoch 总数;即 total_supervised_tokens / (num_steps / num_epochs)
. pretrained_bsz
是指基础模型的原始批量大小。如果是 Mistral 和 Llama,则为 4,000,000(400 万)。例如,假设我们正在使用包含 200 万个监督标记的数据集来训练 Mistral,并且我们正在批处理大小为 1 的单个 GPU 上进行训练。我们还假设这需要 350 个步骤。最终公式将如下所示:
5e-5 * sqrt(2000000/(350/1) / 4000000) = 0.00000189
(1.89e-6) 5e-5 * sqrt(2000000/(350/1) / 4000000) = 0.00000189
(1.89e-6)
作为参考,Llama-2 模型的基本学习率为 3e-4。
6.Gradient Accumulation 梯度累积
批处理大小越大,显存消耗越大。但我们的显存是有限的。
梯度累积旨在解决这个问题。
Source: Towards Data Science 来源:Towards Data Science
首先我们来看一下反向传播。
Backpropagation 反向传播
在模型中,我们有许多层协同工作来处理我们的数据。将这些层视为相互关联的构建块。当我们通过模型传递数据时,它会一步一步地向前传递这些层中的每一个。当它穿过图层时,模型会对数据进行预测。
在数据通过所有层并且模型做出预测之后,我们需要衡量模型预测的准确性或 “正确性”。我们通过计算一个称为 “损失” 的值来做到这一点。损失告诉我们模型与每个数据样本的正确答案的偏差程度。
现在是有趣的部分。我们希望我们的模型能够从错误中吸取教训并改进其预测。为此,我们需要弄清楚当我们对模型的内部参数(如权重和偏差)进行小幅调整时,损失值是如何变化的。
一旦我们有了梯度,我们就可以使用它们来更新模型的参数并使其更好。我们选择一个优化器,它就像一个特殊的算法,负责指导这些参数更新。优化器考虑了梯度,以及其他因素,如学习率(更新的大小)和动量(有助于学习的速度和稳定性)。
为了简化起见,让我们考虑一种流行的优化算法,称为随机梯度下降 (SGD)。这就像一个公式:V = V - (lr * grad)。在这个公式中,V 表示模型中我们想要更新的任何参数(如权重或偏差),lr 是控制更新大小的学习率,grad 是我们之前计算的梯度。这个公式告诉我们如何根据梯度和学习率调整参数。
总之,反向传播是一个过程,在这个过程中,我们通过使用损失值来计算模型的错误程度。然后,我们使用梯度来了解调整模型参数的方向。最后,我们应用优化算法(如随机梯度下降)进行这些调整,并帮助我们的模型学习和改进其预测。
梯度累积
💡梯度累积是什么?
上面提到了,我们的显存可能不足的问题。
就像你想买一件昂贵的东西,比如一台电脑,但一次性拿不出拿不出足够的钱。
于是你决定每个月存一部分钱,等存够了再去买。每个月你都积攒一些钱,直到你有足够的积蓄来购买你想要的东西。
在深度学习中,训练模型的过程类似于 “买大件” 的过程。通常情况下,为了更新模型的参数,我们会用一个批次的数据计算出一次梯度,并立即用这个梯度更新模型的参数。这就相当于你每个月把所有的钱一次性拿出来去购物。
而梯度累积就像是 “攒钱”。每次我们用一个批次的数据计算出梯度时,不立即更新参数,而是把这个梯度存起来,然后继续用下一个批次的数据计算梯度,并把它加到之前存的梯度上。经过几次累积之后(相当于存了几个月的钱),当我们累积的梯度达到一定数量时(比如相当于你存够了买电脑的钱),再一次性用这些累积的梯度去更新模型的参数。
梯度累积就是让我们在不更新模型变量的情况下执行多个计算步骤,并且跟踪在这些步骤中获得的梯度,并在以后使用它们来计算变量更新。
总而言之,梯度累积允许我们将批次划分为小批量(更小的组),在不更新变量的情况下对每个小批量进行计算,然后累积所有小批量的梯度。这种累积确保我们获得与使用整个批次相同的整体梯度。
这样做的好处是,即使每次批次的数据量很小,我们也能得到一个更为稳定和准确的梯度更新,相当于每个月小额积蓄最终能买到一个大的东西。同时,梯度累积还能减少对计算资源的需求,因为我们可以在更小的显存占用下进行训练,适用于显存较小的设备或需要大批次训练的情况。
梯度累积的迭代过程
第一步:计算并存储梯度
我们从第一小批量样本开始,将其通过模型的前向传播和后向传播来计算每个可训练模型变量的梯度。此时,我们不会立即更新模型参数,而是将这些梯度存储起来。为此,我们需要为每个可训练的模型变量创建额外的变量来保存累积的梯度。
第二到四步:累积梯度
在第一步中计算的梯度会存储在为累积梯度创建的相应变量中。这样,第一步的梯度将会保留用于后续步骤。
在接下来的三个步骤中(步骤 2 至步骤 4),我们重复此过程,每一步都会计算一个新的小批量样本的梯度,并将这些梯度累积到存储变量中,但不更新模型参数。
第五步:更新变量
在第五步,我们再次计算一个小批量样本的梯度。
此时,我们将这些梯度与之前四个步骤中累积的梯度相结合,得到五个步骤的总梯度。然后,我们将这个总梯度插入优化器,用于计算变量更新。
这样,就相当于我们使用了一个大的批次(全局批次)中所有样本的梯度来进行更新。
后面就是不断重复这个过程。
公式如下
如果我们以随机梯度下降(SGD)为例,在第 5 步结束时,我们将使用这 5 个步骤的总梯度来更新模型参数。
t
次更新后的值 t
t-1
次)后的值
N
是累积的步骤数量,每个 grad
是小批次的梯度。
这个公式表示,在进行梯度累积时,我们将多个小批次的梯度累积到一起,然后用累积的总梯度乘以学习率,并从之前的模型参数 中减去该值,得到更新后的模型参数。
Configuring the number of gradient accumulation steps 配置梯度累积步骤的数量
正如我们广泛讨论的那样,您需要使用梯度累积步骤来实现接近或大于所需批次大小的有效批次大小。
例如,如果所需的批次大小为 32 个样本,但 VRAM 有限,只能处理 8 个样本大小,则可以将梯度累积步骤设置为 4。这意味着在执行更新之前,您需要在 4 个步骤中累积梯度,从而有效地模拟 32 (8 * 4) 的批处理大小。
一般来说,我建议平衡梯度累积步骤和可用资源,以最大限度地提高计算效率。累积步骤太少可能会导致梯度信息不足,而太多会增加内存需求并减慢训练过程。
7.Interpreting the Learning Curves 解释学习曲线
我们可以通过什么方式,了解学习过程中随着时间推移所取得的进步和表现呢?
让我们以学习日语为例。想象一下,你正在踏上学习日语的旅程,在一年的时间里,你每周都会为自己的语言能力评分,并将这些评分在一年(52 周)的时间里进行绘制,就能形成一条学习曲线。它直观地说明了您对语言的理解是如何随着时间的推移而演变的。
学习曲线通过图表的形式将学习进展可视化。
横轴(x 轴)通常表示时间、经验或学习的投入(如练习次数),而纵轴(y 轴)则表示学习的进步或表现的提升(如准确度、理解度、掌握的技能数量等)。
这种图表能够直观地展示学习的动态过程。
同理,我们也可以通过学习曲线,了解模型在训练过程中的性能变化。
如何 “评分”?
如何评判语言模型输出的好 / 不好?
我们可以将它的实际输出与我们预期输出进行对比。当它的实际输出和我们的预期输出越相符,我们就认为它学习效果越好。
因此,让我们考虑一个评分系统,其中分数越低(实际输出与预期输出的差异越小)代表学习成果越好。
在学习日语的例子中,如果您最初在基本词汇和语法方面遇到困难,您的分数可能会更高。但是,随着您继续学习并取得进步,您的分数会下降,这表明您对语言的掌握更加扎实。最终,如果你的分数达到 0.0,那就意味着你已经完美地掌握了日语,在学习过程中没有犯任何错误。
语言模型同理。
六、常用的学习曲线
在思考如何了解和评估 LLM 的学习效果前,我们可以想一想我们学习的过程。
你可能会遇到下面的问题:
为什么我在平时学习时成绩不错,但一到考试却表现不佳?
又或者,我看起来学习很努力,但成绩始终上不去?
为了回答这些问题,我们可以通过下面四种 “学习曲线” 来帮助我们了解和评估我们的学习情况。
1. 训练学习曲线(Training Learning Curve)
- 情景:
- 就像是你在做练习题时,记录自己每次答对和答错的情况。这条曲线展示了你在复习过程中,每次练习题的学习情况。它告诉你,随着复习的深入,你是否在不断提高,犯的错误是否越来越少。
- 在 LLM 中:
- 在语言模型中,训练学习曲线反映的是模型在训练数据集上不断优化的过程,显示损失(如误差)在训练过程中的变化,帮助判断模型是否有效学习。
2. 验证学习曲线(Validation Learning Curve)
- 情景:
- 想象你不仅做了练习题,还做了一些模拟考试。这些模拟考试代表着你在复习之外进行的额外测试。验证学习曲线展示了你在这些模拟考试中的表现情况,帮助你了解:你在做新题、面对新挑战时,能不能保持良好的表现(泛化良好);或者是你死记硬背,过度背诵了答案(过拟合)。
- 在 LLM 中:
- 在语言模型中,验证学习曲线显示的是模型在未见过的验证数据集上的表现,用于判断模型是否泛化良好或发生过拟合。
3. 优化学习曲线(Optimization Learning Curve)
- 情景:这是在做练习题时,关注自己如何改进学习方法的记录。例如,你可能发现某种学习方法效果更好,通过这条曲线,你可以看到这些调整是否让你的错误率降低了。这条曲线告诉你,你的学习策略是否有效,是否在朝着正确的方向进步。
- 在 LLM 中的具体含义:在语言模型中,优化学习曲线基于用于优化模型参数的指标(如损失),显示模型在训练过程中如何改进,帮助调整学习策略。
4. 性能学习曲线(Performance Learning Curve)
- 情景:这条曲线则更像是在考试中,记录你的成绩。它展示了你在真正考验中的表现,而不仅仅是练习和改进的过程。它帮助你评估自己在考试中能否发挥出色,是否真正掌握了所学的知识。
- 在 LLM 中的具体含义:在语言模型中,绩效学习曲线评估模型的实际表现,通过观察评估指标(如准确率),帮助判断模型在实际任务中的效果,是选择最优模型的重要参考。
5. 需要这些曲线的原因
这四条学习曲线就像是一个学生从练习到考试的完整学习评估系统。它们帮助我们了解到模型从 “学会了多少” 到 “考得好不好” 的全过程,确保模型既能学得快,又能考得好,最终具备实际应用能力。
现在您已经对学习曲线有了更多的了解,让我们看一下在学习曲线图中观察到的一些常见形状。
七、Model Behaviour Diagnostics 模型行为诊断
学习曲线的形态和变化趋势可以帮助诊断模型的表现,从而提供调整配置的建议,以提升模型的学习效果和性能。
您可能会在学习曲线中观察到 3 种常见的图像:
- Underfit 欠拟合
- Overfit 过拟合
- Well-fit 拟合良好
注意!下面的实例将假设你正在查看一个最小化指标,这意味着 y 轴上的相对分数越小表示学习效果越好。
1.Underfit Learning Curves 欠拟合学习曲线
欠拟合:指模型无法学习训练数据集。
您可以仅从训练损失曲线中轻松识别欠拟合模型。
它可能显示相对较高的损失的平线或噪声值,表明模型根本无法学习训练数据集。让我们看一下下面的示例,当模型没有适合数据集复杂性的能力时,这很常见:
欠拟合模型来源: Machine Learning Mastery
欠拟合图的特征是:
- 无论训练如何,训练损失都保持不变。
- 训练损失持续减少,直到训练结束。
2.Overfit Learning Curves 过拟合学习曲线
这是指一个模型对训练数据集的学习得太好,导致数据记忆化而不是泛化。这将包括训练数据集中存在的统计噪声或随机波动。
过度拟合的问题在于,模型对训练数据越专业化,它对新数据的泛化能力就越差,从而导致泛化误差增加。泛化误差的增加可以通过模型在验证数据集上的性能来衡量。
如果模型的容量超过所需问题所需的容量,并且反过来又具有太大的灵活性,则通常会发生这种情况。如果模型训练时间过长,也会发生这种情况。
在以下情况下,学习曲线图显示过拟合:
- 训练损失的情节随着经验的增加而继续减少。
- 验证损失图减小到一个点,然后再次开始增加。
验证损失的拐点可能是训练可以停止的点,因为该点之后的经验显示了过拟合的动态。下面是过拟合模型的示例图:
3.Well-fit Learning Curves 拟合良好的学习曲线
这将是您在训练期间的目标 - 过拟合和欠拟合模型之间的曲线。
通常将良好的拟合确定为训练和验证损失,该损失降低到稳定点,两个最终损失值之间的差距最小。
在训练数据集上,模型的损失将始终低于验证数据集。这意味着我们应该预料到训练和验证损失学习曲线之间存在一些差距。这种差距被称为 “泛化差距”。
此示例图将显示一个拟合良好的模型: