韩国 裸舞 《Python机器学习》作家科普长文:从新构建类GPT文安分类器
发布日期:2024-09-30 12:21 点击次数:199近日韩国 裸舞,机器学习有计划员、畅销书《Python 机器学习》作家 Sebastian Raschka 又共享了一篇长文,主题为《从新伊始构建一个 GPT 格调的 LLM 分类器》。
著述展示了如何将预试验的大型讲话模子(LLM)转换为庞杂的文安分类器。机器之心对著述内容进行了不改变首肯的编译、整理:
为什么要关注分类呢?起先,针对分类任务,对预试验模子进行微调是一个浅显灵验的 LLM 学问初学款式。其次,文安分类有许多生意应用场景,比如:垃圾邮件检测、情感分析、客户反馈分类、主题分类等等。
阅读完本文,你将找到以下 7 个问题的谜底:
1. 需要试验扫数层吗?
2. 为什么微调终末一个 token,而不是第一个 token?
3. BERT 与 GPT 在性能上有何比较?
4. 应该禁用因果掩码吗?
5. 扩大模子限度会有什么影响?
6. LoRA 不错带来什么纠正?
7. Padding 如故不 Padding?
完整代码不错从 GitHub 找到:https://github.com/rasbt/LLMs-from-scratch/blob/main/ch06/01_main-chapter-code/ch06.ipynb
Different categories of finetuning
微调的不同种类
指示微斡旋分类微调是最常见的讲话模子微调步调。指示微调是用特定任务试验模子,提高它交融和试验当然讲话教导中所姿首任务的智商,如下图 1 所示。
图 1:指示微调的两种场景。上方:模子的任务是判断文本是否为垃圾邮件;下方:模子的任务是将英词句子翻译成德语。
在分类微调中,模子被试验用于识别特定的类别标签,比如「垃圾邮件」和「非垃圾邮件」。分类任务还包括从图像中识别不同的植物、给新闻按体育、政事或科技等主题分类,从医学影像中诀别良性和恶性肿瘤等等。
不外经过分类微调的模子只可判断类别,不可对输入的文本作出其他判断。
图 2:一个使用 LLM 进行垃圾邮件分类的示例。针对垃圾邮件分类微调的模子在输入时不需要独特的指示,关联词,与指示微调模子比较,它的回复只然则「垃圾邮件」和「非垃圾邮件」。
指示微调的模子时常粗略试验更等闲的任务。咱们不错将分类微调的模子视为是高度专科化的模子,一般来说,开垦一个专用模子比开垦一个在多样任务上推崇精湛的通用模子更容易。
使用预试验权重运行化模子
下图中展示了将通用预试验 LLM 转换为挑升用于分类任务的 LLM 需要作念的修改:
图 3:在此跳过法子 1-5,径直过问法子 6(将不才一节伊始)。
在作念修改之前,让咱们先浅显了解一下正在使用的预试验 LLM。为便捷起见,假定咱们缔造了如下代码来加载该模子:
model = GPTModel (BASE_CONFIG)
load_weights_into_gpt (model, params)
model.eval ()
在将模子权重加载到 GPT 后,使用下列文本生成的函数库,确保模子生成连贯的文本:
from chapter04 import generate_text_simple
from chapter05 import text_to_token_ids, token_ids_to_text
text_1 = "Every effort moves you"
token_ids = generate_text_simple (
model=model,
idx=text_to_token_ids (text_1, tokenizer),
max_new_tokens=15,
context_size=BASE_CONFIG ["context_length"]
print (token_ids_to_text (token_ids, tokenizer))
凭据以下输出,咱们不错看到模子生成了连贯的文本,这标明模子权重已正确加载:
Every effort moves you forward.
The first step is to understand the importance of your work
让咱们先望望模子是否不错通过指示微调完成垃圾邮件的分类:
text_2 = (
"Is the following text'spam'? Answer with 'yes' or 'no':"
"'You are a winner you have been specially"
"selected to receive $1000 cash or a $2000 award.'"
token_ids = generate_text_simple (
model=model,
idx=text_to_token_ids (text_2, tokenizer),
max_new_tokens=23,
context_size=BASE_CONFIG ["context_length"]
print (token_ids_to_text (token_ids, tokenizer))
模子的输出如下所示:
Is the following text'spam'? Answer with 'yes' or 'no': 'You are a winner you have been specially selected to receive $1000 cash or a $2000 award.'
The following text'spam'? Answer with 'yes' or 'no': 'You are a winner
不错赫然看出模子在准确受命指示方面碰到了一些挑战。这是不错预思的,因为它仅经过了预试验,缺少指示微调。
加入分类头
咱们将原始输出层(这层的功能是将模子里面生成的荫藏示意提拔为一个包含 50,257 个 tokens 的词表)替换为一个较小的输出层,该层映射到两个类别:0(非垃圾邮件)和 1(垃圾邮件),如下图 4 所示。
图 4:此图展示了如何通过改变架构将 GPT 模子适配为垃圾邮件分类。领先,模子的线性输出层将 768 个荫藏单位映射到一个包含 50,257 个 tokens 的词汇表。为了进行垃圾邮件检测,这一层被替换为一个新的输出层,该层将交流的 768 个荫藏单位映射到两个类别,分别示意「垃圾邮件」和「非垃圾邮件」。
输出层节点
从本事上讲,因为这是一个二元分类任务,不错只用一个输出节点。关联词,这将需要修改耗费函数。因此,咱们遴荐一种更通用的步调,匹配输出节点与分类的数目。举例,对于一个分三类的问题,如将新闻著述分类为「科技」、「体育」或「政事」,使用三个输出节点,以此类推。
在尝试进行图 4 中所示的修改之前,先通过 print (model) 输出模子架构:
GPTModel (
(tok_emb): Embedding (50257, 768)
(pos_emb): Embedding (1024, 768)
(drop_emb): Dropout (p=0.0, inplace=False)
(trf_blocks): Sequential (
(11): TransformerBlock (
(att): MultiHeadAttention (
(W_query): Linear (in_features=768, out_features=768, bias=True)
(W_key): Linear (in_features=768, out_features=768, bias=True)
(W_value): Linear (in_features=768, out_features=768, bias=True)
(out_proj): Linear (in_features=768, out_features=768, bias=True)
(dropout): Dropout (p=0.0, inplace=False)
(ff): FeedForward (
(layers): Sequential (
(0): Linear (in_features=768, out_features=3072, bias=True)
(1): GELU ()
(2): Linear (in_features=3072, out_features=768, bias=True)
(norm1): LayerNorm ()
(norm2): LayerNorm ()
(drop_resid): Dropout (p=0.0, inplace=False)
(final_norm): LayerNorm ()
(out_head): Linear (in_features=768, out_features=50257, bias=False)
如上所示,GPTModel 由镶嵌层和 12 个交流的 transformer 块构成,为简单起见,仅露馅终末一个块,然后是最终的 LayerNorm 和输出层 out_head。
接下来,咱们将 out_head 替换为一个新的输出层,如图 4 所示,咱们将对这一层进行微调。
遴荐微调特定层与微调扫数层
咱们无须对模子每一层进行微调,因为神经蚁集的较低层捕捉到的基本的讲话结构和语义是通用的,不错在许多不同的任务和数据麇集髻挥作用。
因此,咱们仅微调终末几层(汇聚输出的层)就够了,这些层更具体于轻飘的讲话模式和任务特征。这种步调在野心上也将愈加高效。
为了准备进行分类微调,起先咱们冻结模子,行将扫数层缔造为不可试验:
for param in model.parameters ():
param.requires_grad = False
然后,如图 4 所示,咱们修改输出层 model.out_head :
torch.manual_seed (123)
num_classes = 2
model.out_head = torch.nn.Linear (
in_features=BASE_CONFIG ["emb_dim"],
out_features=num_classes
细心,在上述代码中,咱们使用了 BASE_CONFIG ["emb_dim"],它的值在 “gpt2-small(124M)” 模子中为 768。这么作念的倡导是为了让后续的代码愈加通用,交流的代码也能措置其他型号的 GPT-2 模子。
新的 model.out_head 输出层的 requires_grad 属性默许缔造为 True,这意味着这是模子中独一会在试验时代更新的层。
从本事上讲,只试验刚刚添加的输出层就有余了。关联词,我在实验中发现,微调独特的层,不错显耀提高微调模子的预测性能。
此外,咱们将终末一个 transformer 块以及联结该块与输出层的 LayerNorm 模块缔造为可试验,如图 5 所示。
图 5:用我的法子开垦的 GPT 模子包含 12 个重叠的 transformer 块。除了输出层,咱们将终末的 LayerNorm 和终末一个 transformer 块缔造为可试验,而其余 11 个 transformer 块和镶嵌层保合手为不可试验。
为了作念到这点,咱们将它们各自的 requires_grad 缔造为 True:
for param in model.trf_blocks [-1].parameters ():
param.requires_grad = True
for param in model.final_norm.parameters ():
param.requires_grad = True
尽管咱们添加了一个新的输出层,并将某些层缔造为不可试验,咱们仍然不错使用这个模子。举例,咱们不错像之前那样输入一段示例文本:
inputs = tokenizer.encode ("Do you have time")
inputs = torch.tensor (inputs).unsqueeze (0)
print ("Inputs:", inputs)
print ("Inputs dimensions:", inputs.shape)
如输出所示,上述代码将输入编码为一个包含 4 个输入 tokens 的张量:
Inputs: tensor ([[5211, 345, 423, 640]])
Inputs dimensions: torch.Size ([1, 4])
然后,咱们将编码后的 token IDs 输入模子:
with torch.no_grad ():
outputs = model (inputs)
print ("Outputs:\n", outputs)
print ("Outputs dimensions:", outputs.shape)
输出张量如下所示:
Outputs:
tensor ([[[-1.5854, 0.9904],
[-3.7235, 7.4548],
[-2.2661, 6.6049],
[-3.5983, 3.9902]]])
Outputs dimensions: torch.Size ([1, 4, 2])
模子将输出一个 [1, 4, 50257] 的输出张量,其中 50,257 代表词汇表的大小。输出行数对应于输入记号的数目(在本例中是 4)。每个输出的镶嵌维度(列数)当今减少到 2,而不是 50,257,因为咱们替换了模子的输出层。
由于咱们的主要指标是微调出更擅长对垃圾邮件进行分类的模子。为了终了这少许,咱们不需要对扫数行进行微调,不错专注于一个单一的输出 token。具体来说,咱们将专注于终末一瞥,对应的终末一个输出 token,如图 6 所示。
图 6: 本图展示了 GPT 模子措置一个包含 4 个 token 的输入示例,并生成相应输出的详备进程。模子的输出层经过调整,输出张量仅包含 2 列,为了完因素类微调,咱们专注于输出的终末一瞥,对应的终末一个 token。
不错使用以下代码从输出张量中索求终末一个输出 token:
print ("Last output token:", outputs [:, -1, :])
Print 出来扫尾如下:
Last output token: tensor([[-3.5983, 3.9902]])
那么,咱们为什么要遴荐终末一个 token,而不是其他位置上的 token 呢?
细心力机制缔造了每个输入 token 与其他 token 之间的关系,为了让「细心力」集结,需要用到因果细心力掩码。它的旨趣是放弃每个 token 只关注我方和前边的 token,如下图 7 所示:
图 7:因果细心力机制,矩阵露馅了每个输入 token 之间的细心力得分。空缺单位格示意被掩码屏蔽的位置,细心 token 关注自后的 token。终末一个 token「time」是独一需要为扫数之前的 token 野心细心力得分的 token。
如图所示,序列中的终末一个 token 积贮了最多的信息,因此,在微调进程中,咱们要点关注这个终末的 token。
如何将终末一个 token 提拔为分类标签预测,并野心模子的运行预测准确率。接下来,咱们将在后续部分微调模子以完成垃圾邮件分类任务。
评估模子性能
由于这部安分容还是很长,我就概略备商讨模子评估的细节了。不外,我思至少共享一张图,展示试验进程中,模子试验集和考证集的分类准确率,以展示模子确乎学得很好。
图 8:试验准确率(实线)和考证准确率(虚线)在早期的试验周期中大幅高潮,然后趋于稳重,达到了险些完竣的准确率 1.0,对应 100%。两条线在通盘试验进程中相距较近,标明模子对试验数据并莫得过度拟合。
模子的考证准确率约为 97%。测试准确率约为 96%。此外,咱们不错看到模子略微有少许点过拟合,因为试验集的准确率稍高。
从补充实验得出的洞见
到这里,你可能对某些联想遴荐有许多疑问,是以我进行了一些补充实验并把扫尾共享了出来。再走时行这些实验的代码还是放在了以下 GitHub 神情中。
GitHub 地址:https://github.com/rasbt/LLMs-from-scratch/tree/main/ch06/02_bonus_additional-experiments
需要试验扫数层吗?
出于遵循原因,咱们仅试验输出层和终末一个 transformer 块。如前所述,对于分类微调,无需更新 LLM 中的扫数层。咱们更新的权重越少,试验速率就越快,因为咱们不需要在反向传播时代野心权重的梯度。
国产肛交但是,你可能思知说念若是不更新扫数层,咱们会留住若打扰测性能。因此,不才表中,我对扫数层、仅终末一个 transformer 块(包括终末一层)、仅终末一层进行了微调。
表 1:试验扫数层 vs 仅试验终末一个 Transformer 块(包括终末一层)vs 仅试验终末一层
如上表 1 所示,试验扫数层的性能稍好一些:96.67% vs 95.00%。不外,这使运行时期增多了约 2.5 倍。
为什么要微调终末一个 token,而不是第一个 token?
若是你熟谙 BERT(Devlin et al. 2018)等编码器式讲话模子,你可能知说念这些模子有一个指定的分类 token 动作其第一个 token,如下图所示:
图来自 BERT 原始论文:https://arxiv.org/abs/1810.04805
与 BERT 比较,GPT 是一种具有因果细心力掩码的解码器式模子(如图 7 所示)。这意味着第一个 token 莫得输入中任何其他 token 的坎坷文信息。唯有终末一个 token 具有干系扫数其他 token 的信息。
因此,若是咱们思使用像 GPT 这么的模子进行分类微调,咱们应该关注终末一个 token 记号以拿获扫数其他输入 token 的坎坷文信息。
如下表所示,咱们不错看到使用第一个 token 来微调 GPT 模子进行分类会导致性能更差。
表 2:微调 GPT 模子中的终末一个 token 与第一个 token。
BERT 与 GPT 的性能比较如何?
说到 BERT,你可能思知说念它在分类任务上与类 GPT 模子的性能比较如何?浅显来说,在垃圾邮件分类任务上,更小的 GPT-2(124M)与更大 BERT(340M)的性能雷同,具体如下表 3 所示。
表 3:GPT-2 与 BERT 的扫尾比较。
不错看到,BERT 模子的推崇比 GPT-2 略略好少许(测试准确率高 1%),但 BERT 的参数限度险些是 GPT-2 的 3 倍。此外,数据集可能太小且太浅显了,因此我又在 IMDB Movie Review 数据集上尝试比较了情感分类推崇(即预测不雅看者是否心爱一部电影)。
表 4:GPT-2 与 BERT 在影评分类任务上的比较。
不错看到,在这个更大的数据集上(包含 25k 试验和 25k 测试集纪录),GPT-2 与 BERT 两个模子的预测性能雷同雷同。
总的来说,在分类任务上,BERT 和其他编码器格调的模子被以为优于解码器格调的模子。但是,实验扫尾也标明,编码器格调的 BERT 息争码器格调的 GPT 模子之间莫得太大的各异。
此外,若是你对更多基准比较以及如何进一步进步解码器格调模子的分类性能感兴味,不错参阅以下两篇最近的论文:
Label Supervised LLaMA Finetuning:https://arxiv.org/abs/2310.01208 LLM2Vec: Large Language Models Are Secretly Powerful Text Encoders:https://arxiv.org/abs/2404.05961其中第一篇论文商讨了:在分类微调时代移除因果掩码不错进步解码器格调模子的分类性能。
咱们应该禁用因果掩码吗?
当咱们不才一个词(next-word)预测任务上试验类 GPT 模子时,GPT 架构的中枢特征是因果细心力掩码,这与 BERT 模子或原始 transformer 架构不同。
但实质上,咱们不错在分类微调阶段移除因果掩码, 从而允许咱们微调第一个而不是终末一个 token。这是因为昔时的 tokens 将不再被掩码,况兼第一个 token 不错看到扫数其他的 tokens.
有 / 无因果掩码的细心力权重矩阵。
庆幸的是,在类 GPT 大讲话模子中禁用因果细心力掩码只需要改变 2 行代码。
class MultiheadAttention (nn.Module):
def __init__(self, d_in, d_out, context_length, dropout, num_heads):
super ().__init__()
# ...
def forward (self, x):
b, num_tokens, d_in = x.shape
keys = self.W_key (x) # Shape: (b, num_tokens, d_out)
queries = self.W_query (x)
values = self.W_value (x)
# ...
attn_scores = queries @ keys.transpose (2, 3)
# Comment out the causal attention mask part
# mask_bool = self.mask.bool ()[:num_tokens, :num_tokens]
# attn_scores.masked_fill_(mask_bool, -torch.inf)
attn_weights = torch.softmax (
attn_scores /keys.shape [-1]**0.5, dim=-1
context_vec = (attn_weights @ values).transpose (1, 2)
context_vec = context_vec.contiguous ().view (
b, num_tokens, self.d_out
context_vec = self.out_proj (context_vec)
return context_vec
下表 5 展示了改变代码后对垃圾邮件分类任务带来的影响。
表 5:有无使用因果细心力掩码来微调 GPT-2 分类器的扫尾。
不错看到,在微调阶段禁用因果掩码不错带来略微的进步。
增多模子大小会带来哪些影响?
目下为止,咱们只看到了最小的 GPT-2(124M)模子的性能,那么与限度更大的 GPT-2 变体比较如何呢?比如 GPT-2 medium(355M)、GPT-2 large(774M)和 GPT-2 XL(1558M)。扫尾如下表 6 所示。
表 6:不同参数限度的 GPT-2 变体的分类微调扫尾。
不错看到,跟着模子参数增多,预测准确率显耀进步。不外 GPT-2 medium 是个例外,它在其他数据集上的性能雷同很差。我怀疑该模子可能莫得经过很好的预试验。
此外,最大的 GPT-2 XL 赢得了比最小的 GPT-2 small(124M)好得多的分类准确率,但微调时期也长了 7 倍。
LoRA 展望能带来哪些纠正?
回到本文第一个问题:咱们需要试验扫数层吗?扫尾发现,当只是微调终末一个 transformer 块而不是通盘模子时, 咱们不错(或险些不错)匹配分拨性能。是以只是微调终末一个块的上风在于试验速率更快,毕竟不是扫数的权重参数齐要更新。
接下来的问题是与低秩相宜(LoRA)的比较扫尾如何,LoRA 是一种参数高效的微调本事。
表 7:隐痛扫数层的完整微调 vs 诈欺 LoRA 的参数高效微调。
不错看到,完整微调(扫数层)和 LoRA 在数据集上赢得了相似的测试集性能。
在小模子上,LoRA 会略略慢少许,添加 LoRA 层带来的独特支出可能会卓越赢得的收益。但当试验更大的 15 亿参数模子时,LoRA 的试验速率会快 1.53 倍。
填充(Padding)如故不填充?
若是咱们思要在试验或推理阶段分批次地措置数据(包括一次措置多个输入序列),则需要插入 padding token,以确保试验样本的长度至极。
图中姿首了给定批次中的输入文本如安在 padding 进程中保合手长度至极。
在惯例文本生成任务中,由于 padding tokens 时常要添加到右侧,因而 padding 不影响模子的反应扫尾。况兼由于前边商讨过的因果掩码,这些 padding tokens 也不影响其他 token。
但是,咱们对终末一个 token 进行了微调。同期由于 padding tokens 在终末一个 token 的左侧,因此可能影响扫尾。
若是咱们使用的批大小为 1,实质上不需要 pad 输入。虽然,这么作念从野心的角度来看愈加高效(一次只措置一个输入样本)。况兼批大小为 1 不错用作一个变通步调,来测试使用 padding 是否影响扫尾。
表 8:有无 padding 时,GPT-2(124M)的试验准确率、考证准确率和测试准确率变化。
不错看到,幸免 padding tokens 实在不错为模子带来后果的显耀进步。这里使用了梯度累计来模拟批大小 8,以匹配默许实验的批大小,并进行公说念比较。
作家先容
个东说念主主页:https://sebastianraschka.com/
Sebastian Raschka 是别称机器学习和东说念主工智能有计划员,曾在威斯康星大学麦迪逊分校担任统计学助理熏陶,挑升有计划深度学习和机器学习。他用功于对于 AI 和深度学习干系的内容更浅显易懂。
Sebastian 还热衷于开源软件,十多年来,他一直是一个充满关爱的开源孝敬者。他提议的步调现已到手在 Kaggle 等机器学习竞赛中得到应用。
除了编写代码,Sebastian 还心爱写稿,他撰写了畅销书《Python Machine Learning》(《Python 机器学习》)和《Machine Learning with PyTorch and ScikitLearn》。
这篇博客的内容是他的新书《Build a Large Language Model (From Scratch)》的第六章。
更多有计划细节韩国 裸舞,可参考原博客。
原博联结:https://magazine.sebastianraschka.com/p/building-a-gpt-style-llm-classifier 垃圾邮件模子idsmodeltoken发布于:北京市声明:该文不雅点仅代表作家本东说念主,搜狐号系信息发布平台,搜狐仅提供信息存储空间职业。