赞
踩
用于语言建模的最常见架构是 Transformer 架构,由 Vaswani 等人在著名论文《[Attention Is All You Need》]中提出。我们不会在这里讨论该架构的具体细节,因为我们必须讨论导致并促成其创建的所有旧技术。Transformer 使我们能够训练具有惊人推理能力的大型语言模型 (LLM),同时保持架构足够简单,以便机器学习新手可以开始训练/使用它们。
用于训练和构建 Transformer 模型的最常见语言是 Python,这是一种非常高级(即远离原始机器代码)的语言。这使得外行人很容易熟悉该过程。最流行的库是 HuggingFace [Transformers],它是当今几乎每个 LLM 培训师的支柱。
本质上,LLM 是一种有损文本压缩形式。我们创建具有随机值和参数的张量(多维矩阵),然后向其输入大量文本数据(以 TB 为单位!),以便它们能够学习所有数据之间的关系并识别它们之间的模式。所有这些模式都存储在我们随机初始化为概率的张量中 - 模型会学习一个特定单词后面跟着另一个单词的可能性,依此类推。LLM 的一个非常高级的定义是“将一种语言(例如英语)的概率分布压缩成一组矩阵。”
例如,如果你在 LLM 中输入“How are”,它会计算下一个单词出现的概率。例如,它可能会为“you?”分配 60% 的概率,“things”分配 20% 的概率,等等。
上面讨论的随机初始化在很大程度上不适用于我们,因为它非常昂贵(我们说的是大型模型的数百万美元)。本文将介绍如何微调模型
- 即采用预先训练的模型并为其提供少量数据(通常为几 MB),以使其行为与您所想的任何任务保持一致。例如,如果您想要一个编码助手模型,您可以根据编码示例对模型进行微调,等等。
了解您正在处理的内容始终是一种很好的做法,尽管对于微调目的而言这并不是绝对必要的,因为您将运行调用 Transformers 库的类的脚本。
当然,最好的来源是[《Attention Is All You Need》]这篇论文。它介绍了 Transformer 架构,是一篇非常重要的论文。不过,你可能需要先阅读这些,因为作者假设你已经对神经网络有了基本的了解。我建议按顺序阅读这些:
论文太难读?
你并不孤单。学者们往往会故意混淆他们的论文。你可以随时查找每个主题的博客文章或文章,它们往往会提供易于理解的解释。一个很好的资源是 HuggingFace 博客文章。
训练 LLM 基本上有三 (3) 种方法:预训练、微调和 LoRA/Q-LoRA。
预训练涉及几个步骤。首先,收集大量文本数据集(通常以 TB 为单位)。接下来,选择或创建专门用于当前任务的模型架构。此外,训练标记器以适当处理数据,确保它能够有效地对文本进行编码和解码。然后使用标记器的词汇表对数据集进行预处理,将原始文本转换为适合训练模型的格式。此步骤涉及将标记映射到其相应的 ID,并合并任何必要的特殊标记或注意掩码。数据集预处理后,即可用于预训练阶段。
在预训练期间,模型会利用大量数据来学习预测句子中的下一个单词或填补缺失的单词。此过程涉及通过迭代训练程序优化模型的参数,以最大限度地提高根据上下文生成正确单词或单词序列的可能性。
为实现此目的,预训练阶段通常采用自监督学习技术
的变体。向模型提供部分掩蔽的输入序列,其中某些标记被故意隐藏,并且模型必须根据周围上下文预测那些缺失的标记。通过以这种方式对大量数据进行训练,模型逐渐对语言模式、语法和语义关系有了丰富的理解。这种特定方法用于[掩蔽语言建模]。然而,当今最常用的方法是[因果语言建模]。与掩蔽语言建模(其中某些标记被掩蔽并且模型预测那些缺失的标记)不同,因果语言建模侧重于根据前面的上下文预测句子中的下一个单词
。
初始预训练阶段旨在捕捉一般语言知识,使模型成为熟练的语言编码器。但毫不奇怪,它缺乏有关特定任务或领域的具体知识。为了弥补这一差距,预训练之后是后续的微调阶段
。
在初始预训练阶段之后,模型会学习一般的语言知识,然后通过微调我们可以专门化模型的功能,并在更窄、特定于任务的数据集上优化其性能
。
微调过程涉及几个关键步骤。首先,收集特定于任务的数据集,其中包含与所需任务相关的标记示例。例如,如果任务是指令调整,则收集指令-响应
对的数据集。微调数据集的大小明显小于通常用于预训练的数据集。
接下来,使用之前学习到的参数初始化预训练模型。然后在特定任务数据集上训练模型,优化其参数以最小化特定任务的损失函数(即模型与期望结果的“偏差”程度)。
对于普通消费者来说,将内存需求减少 3 倍仍然是不可行的。幸运的是,引入了一种新的 LoRA 训练方法:量化低秩自适应 (QLoRA)。它利用 bitsandbytes 库对语言模型进行即时和近乎无损的量化,并将其应用于 LoRA 训练过程。这大大减少了内存需求 - 使得在 2 个 NVIDIA RTX 3090 上训练多达 700 亿个参数的模型成为可能!相比之下,通常需要超过 16 个 A100-80GB GPU 来微调该大小的模型;相关成本将是巨大的。
本系列的下一部分将重点介绍微调和 LoRA/QLoRA 方法。
如前所述,微调的成本可能很高,具体取决于您选择的模型大小。您通常需要至少 6B/7B 个参数。我们将介绍一些获取训练计算的选项。
毫无疑问,数据集收集是微调过程中最重要的部分。质量和数量都很重要 - 但质量更重要
。
首先,想想你希望经过微调的模型做什么。写故事?角色扮演?帮你写电子邮件?也许你想创建自己的 AI waifubot。为了本实验的目的,我们假设你想训练一个聊天和角色扮演模型,比如[Pygmalion]。你需要收集对话数据集。具体来说,是互联网角色扮演风格的对话。收集部分可能相当具有挑战性;你必须自己弄清楚.
您需要为数据集概述一个结构。本质上,您需要:
您现在可能有一堆文本数据。在继续之前,您需要将它们解析为适合预处理的格式。假设您的数据集处于以下条件之一:
如果您从网站上抓取数据,则可能会有 HTML 文件。在这种情况下,您的首要任务是从 HTML 元素中提取数据。如果您的头脑不正常,您会尝试使用纯 RegEx 来执行此操作。这非常低效,所以幸运的是,有库可以处理此问题。您可以使用[Beautiful Soup] Python 库来帮助您解决这个问题。您可以阅读它的文档,但它通常是这样使用的:
from bs4 import BeautifulSoup # HTML content to be parsed html_content = ''' <html> <head> <title>Example HTML Page</title> </head> <body> <h1>Welcome to the Example Page</h1> <p>This is a paragraph of text.</p> <div class="content"> <h2>Section 1</h2> <p>This is the first section.</p> </div> <div class="content"> <h2>Section 2</h2> <p>This is the second section.</p> </div> </body> </html> ''' # Create a BeautifulSoup object soup = BeautifulSoup(html_content, 'html.parser') # Extract text from the HTML text = soup.get_text() # Print the extracted text print(text)
你将得到如下输出:
Example HTML Page
Welcome to the Example Page
This is a paragraph of text.
Section 1
This is the first section.
Section 2
This is the second section.
如果您从在线开放数据源获取了数据集,则可以获得 CSV 文件。解析它们的最简单方法是使用pandas
Python 库。基本用法如下:
import pandas as pd
# Read the CSV file
df = pd.read_csv('dataset.csv')
# Extract plaintext from a specific column
column_data = df['column_name'].astype(str)
plaintext = column_data.to_string(index=False)
# Print the extracted plaintext data
print(plaintext)
您必须指定列名。
这个会有点难。你可以采取明智的方法,使用 MariaDB 或 PostgreSQL 等数据库框架将数据集解析为纯文本,但也有用于此目的的 Python 库;一个例子是[sqlparse]。基本用法是:
>>> import sqlparse >>> # Split a string containing two SQL statements: >>> raw = 'select * from foo; select * from bar;' >>> statements = sqlparse.split(raw) >>> statements ['select * from foo;', 'select * from bar;'] >>> # Format the first statement and print it out: >>> first = statements[0] >>> print(sqlparse.format(first, reindent=True, keyword_case='upper')) SELECT * FROM foo; >>> # Parsing a SQL statement: >>> parsed = sqlparse.parse('select * from foo')[0] >>> parsed.tokens [<DML 'select' at 0x7f22c5e15368>, <Whitespace ' ' at 0x7f22c5e153b0>, <Wildcard '*' … ] >>>
最好的语言模型是随机的,这使得很难预测它们的行为,即使输入提示保持不变。这有时会导致低质量和不理想的输出。您需要确保数据集中没有不需要的元素。如果您的数据源是合成的,即由 GPT-4/3 生成,这一点就更加重要。
我们将使用[axolotl]训练器进行微调,因为它使用简单且具有我们需要的所有功能。
如果您正在使用云计算服务(例如 RunPod),那么您可能已经满足所有必要的要求。
git clone https://github.com/OpenAccess-AI-Collective/axolotl && cd axolotl
pip3 install packaging
pip3 install -e '.[flash-attn,deepspeed]'
这将安装 axolotl,然后我们就可以开始微调了。
Axolotl 将所有训练选项都放在一个yaml
文件中。目录中已经有一些examples
针对各种不同模型的示例配置。
对于此示例,我们将使用 QLoRA 方法训练 Mistral 模型,这应该可以在单个 3090 GPU 上实现。要开始运行,只需执行以下命令:
accelerate launch -m axolotl.cli.train examples/mistral/config.yml
恭喜!您刚刚训练了 Mistral!示例配置使用非常小的数据集,因此训练所需的时间非常短。
要使用自定义数据集,您需要将其正确格式化为文件JSONL
。Axolotl 接受多种不同的格式,您可以[在此处]找到示例。然后,您可以编辑文件qlora.yml
并将其指向您的数据集。所有配置选项的完整说明都[在此处],请确保单击展开按钮以查看所有选项!
您现在知道如何训练模型了,但让我们在下一节中了解一些非常重要的信息。我们首先解释一下 LoRA 到底是什么,以及它为什么有效。
LoRA 是一种训练方法,旨在加快大型语言模型的训练过程,同时减少内存消耗。通过将秩分解权重矩阵对(称为更新矩阵)引入现有权重,LoRA 仅专注于训练这些新添加的权重。这种方法有几个优点:
这决定了秩分解矩阵的数量。将秩分解应用于权重矩阵是为了减少内存消耗和计算要求。原始[LoRA 论文]建议将秩设为 8 ( r = 8
)。请记住,秩越高,结果越好,计算要求也越高。数据集越复杂,秩就需要越高。
为了匹配完整的微调,您可以将等级设置为等于模型的隐藏大小。但是,不建议这样做,因为这会浪费大量资源。
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)
这是 LoRA 的缩放因子,它决定了模型适应新训练数据的程度。alpha 值调整训练过程中更新矩阵的贡献。较低的值会给予原始数据更大的权重,并且与较高的值相比,在更大程度上保持模型的现有知识。
在这里,您可以确定要训练哪些特定的权重和矩阵。最基本的训练是查询向量(例如q_proj
)和值向量(例如v_proj
)投影矩阵。这些矩阵的名称因模型而异。您可以通过运行以下脚本来找出确切的名称:
from transformers import AutoModelForCausalLM
model_name = "huggyllama/llama-7b" # can also be a local directory
model = AutoModelForCausalLM.from_pretrained(model_name)
layer_names = model.state_dict().keys()
for name in layer_names:
print(name)
这将给你如下输出:
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.layers.0.self_attn.rotary_emb.inv_freq model.layers.0.mlp.gate_proj.weight model.layers.0.mlp.down_proj.weight model.layers.0.mlp.up_proj.weight model.layers.0.input_layernorm.weight model.layers.0.post_attention_layernorm.weight ... model.norm.weight lm_head.weight
命名约定本质上是:{identifier}.{layer}.{layer_number}.{component}.{module}.{parameter}
。以下是每个模块的基本解释(请记住,每个模型架构的名称都不同):
up_proj
:向上(解码器到编码器)注意力传递中使用的投影矩阵。它将解码器的隐藏状态投影到与编码器的隐藏状态相同的维度,以便在注意力计算期间实现兼容性。down_proj
:向下(编码器到解码器)注意力传递中使用的投影矩阵。它将编码器的隐藏状态投影到解码器预期的维度,以进行注意力计算。q_proj
:注意力机制中应用于查询向量的投影矩阵。将输入隐藏状态转换为有效查询表示所需的维度。v_proj
:应用于注意力机制中的值向量的投影矩阵。将输入的隐藏状态转换为有效值表示所需的维度。k_proj
:投影矩阵应用于关键向量等等。o_proj
:应用于注意力机制输出的投影矩阵。在进一步处理之前,将组合的注意力输出转换为所需的维度。但是,有三个(或 4 个,如果你的模型有偏差)异常值。它们不遵循上面指定的命名约定,放弃了层名称和编号。这些是:
embed_tokens
:表示与模型的嵌入层相关的参数,通常放置在模型的开头,因为它用于将输入标记或单词映射到其相应的密集向量表示。如果您的数据集具有自定义语法,则目标很重要。norm
:模型中的归一化层。层或批量归一化通常用于提高深度神经网络的稳定性和收敛性。这些通常放置在模型架构中的某些层内或之后,以缓解梯度消失或爆炸等问题,并有助于加快训练速度和提高泛化能力。通常不针对 LoRA。lm_head
:语言建模 LLM 的输出层。它负责根据从_前面的_层学习到的表示生成下一个标记的预测或分数。位于底部。如果您的数据集具有自定义语法,则这一点很重要。QLoRA(量化低秩适配器)是一种高效的微调方法,可减少内存使用量,同时保持大型语言模型的高性能。它能够在单个 48GB GPU 上对 65B 参数模型进行微调,同时保留完整的 16 位微调任务性能。
QLoRA 的主要创新包括:
在接下来的部分中,我们将尝试了解所有训练超参数(又名配置)的作用。
训练超参数在塑造模型的行为和性能方面起着至关重要的作用。这些 hparams 是指导训练过程的设置,决定了模型如何从提供的数据中学习。选择合适的 hparams 可以显著影响模型的收敛性、泛化能力和整体效果。
在本节中,我们将尝试解释在训练阶段需要仔细考虑的关键训练 hparams。我们将讨论批大小、时期、学习率、正则化等概念。通过深入了解这些 hparams 及其影响,您将能够有效地微调和优化模型,确保在各种机器学习任务中获得最佳性能。所以让我们深入研究并揭开训练 hparams 背后的奥秘。
随机梯度下降 (SGD) 是一种具有多个超参数的学习算法。新手经常会混淆的两个参数是批次大小和周期数。它们都是整数值,似乎做同样的事情。让我们回顾一下本节的主要内容:
我们必须将此部分分为五(5)个部分。
随机梯度下降 (SGD) 是一种优化算法,用于寻找模型的最佳内部参数,旨在最小化对数损失或均方误差等性能指标。
优化可以看作是一个搜索过程,其中算法学习改进模型。所使用的具体优化算法称为“梯度下降”。这里的“梯度”是指计算误差斜率或[梯度],而“下降”表示沿着这个斜率向下移动以接近最小误差水平。
该算法以迭代方式工作,这意味着它要经历多个离散步骤,每个步骤都旨在增强模型参数。在每个步骤中,模型使用当前的内部参数集对样本子集进行预测。然后将这些预测与实际预期结果进行比较,从而计算出误差。然后利用该误差来更新内部模型参数。更新过程因所使用的算法而异,但在人工神经网络的情况下,采用反向传播更新算法。
在深入探讨批次和时期的概念之前,让我们先澄清一下“样本”的含义。
样本或序列是一行数据。它包含输入到算法中的输入以及用于与预测进行比较并计算误差的输出。
训练数据集由多行数据组成,例如许多样本。样本也可以称为实例、观察、输入向量、序列或特征���量。
现在我们知道了什么是样本,让我们定义一个批次。
批处理大小是一个 hparam,它决定在更新模型的内部参数之前要处理多少个样本。想象一个“for 循环”,它迭代特定数量的样本并进行预测。处理完批处理后,将预测与预期输出进行比较,并计算出误差。然后,该误差用于通过调整模型的参数来改进模型,沿着误差梯度的方向移动。
训练数据集可以分为一个或多个批次。以下是基于批次大小的不同类型的梯度下降算法:
通过调整批量大小,我们可以控制计算效率和学习过程的随机性之间的权衡,从而找到有效训练模型的最佳平衡点。
Batch Size = Size of Training Set
Batch Size = 1
1 < Batch Size < Size of Training Set
对于小批量梯度下降,常用的批量大小包括32
、64
和128
样本。您可能会在大多数教程中的模型中看到使用这些值。
如果数据集不能均匀地除以批次大小,该怎么办?
这种情况经常_发生_。这仅仅意味着最终批次的样本数量少于其他批次。您只需从数据集中删除一些样本或更改批次大小,以便数据集中的样本数量能够均匀地除以批次大小。大多数训练脚本都会自动处理此问题。
现在,让我们讨论一个时代。
Epoch是一个超参数,它决定了学习算法在整个数据集上迭代的次数。
一个 (1) epoch 表示训练数据集中的每个样本已被使用一次来更新模型的内部参数。它由一个或多个批次组成。例如,如果我们每个 epoch 有一个批次,它对应于前面提到的批量梯度下降算法。
您可以将 epoch 的数量可视化为在训练数据集上迭代的“for 循环”。在此循环中,还有另一个嵌套的“for 循环”,用于遍历每一批样本,其中每一批包含根据批次大小指定的样本数量。
为了评估模型在各个时期的表现,通常会创建线图,也称为学习曲线。这些图在 x 轴上显示时期(时间),在 y 轴上显示模型的错误或技能。学习曲线可用于诊断模型是否过度学习(训练错误率高,验证错误率低)、学习不足(训练和验证错误率低)或与训练数据集的拟合程度合适(训练错误率低,验证错误率合理低)。我们将在下一部分深入探讨学习曲线。
或者你仍然不明白其中的区别? 在这种情况下,让我们看看批次和时期之间的主要区别…
[batch size是模型更新]之前处理的样本数量。
epoch是通过训练数据集的完整次数。
批次的大小必须大于或等于一(bsz=>1)且小于或等于训练数据集中的样本数(bsz=< 样本数)。
可以将周期数设置为介于一 (1) 和无穷大之间的整数值。您可以根据需要运行算法,甚至可以使用除固定周期数之外的其他标准(例如模型误差随时间的变化(或无变化))来停止算法。
它们都是整数值,并且都是学习算法的参数,例如学习过程的参数,而不是学习过程发现的内部模型参数。
您必须为学习算法指定批量大小和时期数。
关于如何配置这些 hparams,没有神奇的经验法则。你应该尝试找到适合你特定用例的最佳点。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。