从零开始构建一个大语言模型-第五章第五节

第五章目录

5.1 评估生成文本模型
5.2 训练一个LLM
5.3 控制随机性的解码策略
5.4 在PyTorch中加载和保存模型权重
5.5 从OpenAI加载预训练权重

5.5 从OpenAI加载预训练权重

此前,我们使用一个由一本短篇小说集组成的有限数据集训练了一个小型GPT - 2模型 。这种方法使我们能够专注于基础知识,而无需大量的时间和计算资源。

幸运的是,OpenAI公开分享了他们的GPT - 2模型权重,这样我们就无需自己花费 数万到数十万美元在大型语料库上重新训练模型。那么,让我们将这些权重加载到我 们的GPTModel类中,并使用该模型进行文本生成。这里,weights例如指的是存储在Py Torch的线性层和嵌入层的.weight属性中的权重参数。我们之前在训练模型时通过mod el.parameters()访问过它们。在第6章中,我们将重新使用这些预训练权重来针对文本 分类任务对模型进行微调,并遵循类似于ChatGPT的指令。

请注意,OpenAI最初是通过TensorFlow保存GPT - 2权重的,我们必须安装TensorFl ow才能在Python中加载这些权重。以下代码将使用一个名为tqdm的进度条工具来跟踪 下载过程,我们也必须安装该工具。

你可以安装

pip install tensorflow>=2.15.0 tqdm>=4.66

下载代码比较长,大多是模板代码,不是很有趣。因此,我们没有占用宝贵的篇幅来 讨论从互联网上获取文件的Python代码,而是直接从本章的在线仓库中下载gpt_downl oad.py Python模块:

import urllib.request 

url = ( 
	"https://raw.githubusercontent.com/rasbt/" 
	"LLMs-from-scratch/main/ch05/" 
	"01_main-chapter-code/gpt_download.py" 
) 
filename = url.split('/')[-1] 
urllib.request.urlretrieve(url, filename)

接下来,将此文件下载到Python会话的本地目录后,你应该简要检查该文件的内容, 以确保其保存正确且包含有效的Python代码。

现在我们可以按如下方式从gpt_download.py文件中导入download_and_load_gpt2函 数,该函数会将GPT - 2的架构设置(settings)和权重参数(params)加载到我们的Py thon会话中:

from gpt_download import download_and_load_gpt2 
settings, params = download_and_load_gpt2( 
	model_size="124M", models_dir="gpt2" 
)

执行此代码将下载与124M参数的GPT-2模型相关的以下七个文件:

checkpoint: 100%|███████████████████████████| 77.0/77.0 [00:00<00:00, 63.9kiB/s] 
encoder.json: 100%|█████████████████████████| 1.04M/1.04M [00:00<00:00, 2.20MiB/s]
hprams.json: 100%|██████████████████████████| 90.0/90.0 [00:00<00:00, 78.3kiB/s] 
model.ckpt.data-00000-of-00001: 100%|███████| 498M/498M [01:09<00:00, 7.16MiB/s] 
model.ckpt.index: 100%|█████████████████████| 5.21k/5.21k [00:00<00:00, 3.24MiB/s] 
model.ckpt.meta: 100%|██████████████████████| 471k/471k [00:00<00:00, 2.46MiB/s] 
vocab.bpe: 100%|████████████████████████████| 456k/456k [00:00<00:00, 1.70MiB/s]

[!NOTE] 注意
如果下载代码对你不起作用,可能是由于网络连接不稳定、服务器问题 ,或者OpenAI共享开源GPT-2模型权重的方式发生了变化。在这种情况下, 请访问本章的在线代码库(https://github.com/rasbt/LLMs-from-scratch)获取替 代和更新的说明,并通过曼宁论坛提出进一步的问题。

假设前面代码执行完毕,我们来检查一下settings和params的内容:

print("Settings:", settings) 
print("Parameter dictionary keys:", params.keys())

内容如

Settings: {'n_vocab': 50257, 'n_ctx': 1024, 'n_embd': 768, 'n_head': 12, 'n_layer': 12} 
Parameter dictionary keys: dict_keys(['blocks', 'b', 'g', 'wpe', 'wte'])

settings和params都是Python字典。settings字典存储的大语言模型(LLM)架构设置与 我们手动定义的GPT_CONFIG_124M设置类似。params字典包含实际的权重张量。请 注意,我们只打印了字典键,因为打印权重内容会占用太多屏幕空间;不过,我们可 以通过print(params)打印整个字典来检查这些权重张量,也可以通过相应的字典键选择 单个张量,例如嵌入层权重:

print(params["wte"]) 
print("Token embedding weight tensor dimensions:", params["wte"].shape)

词元嵌入层的权重是

[[-0.11010301 ... -0.1363697 0.01506208 0.04531523] 
[ 0.04034033 ... 0.08605453 0.00253983 0.04318958] 
[-0.12746179 ... 0.08991534 -0.12972379 -0.08785918] 
... 
[-0.04453601 ... 0.10435229 0.09783269 -0.06952604] 
[ 0.1860082 ... -0.09625227 0.07847701 -0.02245961]
[ 0.05135201 ... 0.00704835 0.15519823 0.12067825]] 
Token embedding weight tensor dimensions: (50257, 768)

我们通过download_and_load_gpt2(model_size="124M",...)设置下载并加载了最小的GP T-2模型的权重。OpenAI还分享了更大模型的权重:3.55亿参数、7.74亿参数和15.58亿 参数。如图5.17所示,这些不同规模的GPT模型的总体架构是相同的,除了不同的架构元素的重复次数不同,嵌入大小也不同。本章的其余代码也与这些更大的模型兼 容。
从零开始构建一个大语言模型-第五章第五节_第1张图片

图5.17 GPT - 2大语言模型有几种不同的模型规模,参数数量从1.24亿到15.58亿不等。其核心架构相同,唯一的区别 在于嵌入尺寸以及诸如注意力头和Transformer块等单个组件的重复次数。

将GPT - 2模型权重加载到Python中后,我们仍需要将它们从设置和参数字典转移到 我们的GPTModel实例中。首先,我们创建一个字典,列出图5.17中不同GPT模型规模 之间的差异:

model_configs = { 
	"gpt2-small (124M)": {"emb_dim": 768, "n_layers": 12, "n_heads": 12}, 
	"gpt2-medium (355M)": {"emb_dim": 1024, "n_layers": 24, "n_heads": 16}, 
	"gpt2-large (774M)": {"emb_dim": 1280, "n_layers": 36, "n_heads": 20}, 
	"gpt2-xl (1558M)": {"emb_dim": 1600, "n_layers": 48, "n_heads": 25}, 
}

假设我们想加载最小的模型“gpt2 - small(1.24亿参数)”。我们可以使用model_con figs表中的相应设置来更新我们之前定义并使用的完整长度的GPT_CONFIG_124M

model_name = "gpt2-small (124M)" 
NEW_CONFIG = GPT_CONFIG_124M.copy() 
NEW_CONFIG.update(model_configs[model_name])

细心的读者可能还记得,我们之前使用的是256个词元的长度,但OpenAI最初的GPT - 2模型是使用1024个词元的长度进行训练的,所以我们必须相应地更新NEW_CONFIG

NEW_CONFIG.update({"context_length": 1024})

此外,OpenAI在多头注意力模块的线性层中使用偏置向量来实现查询、键和值矩阵的 计算。如今,偏置向量在大语言模型中已不常用,因为它们并不能提升建模性能,因 此是不必要的。然而,由于我们使用的是预训练权重,为保持一致性,我们需要匹配 相关设置并启用这些偏置向量:

NEW_CONFIG.update({"qkv_bias": True})

现在我们可以使用更新后的NEW_CONFIG字典来初始化一个新的GPTModel实例:

gpt = GPTModel(NEW_CONFIG) 
gpt.eval()

默认情况下,GPTModel实例会使用随机权重进行预训练初始化。使用OpenAI模型权 重的最后一步是用我们加载到params字典中的权重覆盖这些随机权重。为此,我们 首先将定义一个小型的assign实用函数,该函数会检查两个张量或数组(左和右)是 否具有相同的维度或形状,并将右边的张量作为可训练的PyTorch参数返回:

def assign(left, right): 
	if left.shape != right.shape: 
		raise ValueError(f"Shape mismatch. Left: {left.shape}, " 
			"Right: {right.shape}" 
		) 
	return torch.nn.Parameter(torch.tensor(right))

接下来,我们定义一个load_weights_into_gpt函数,该函数将params字典中的权重加 载到GPTModel实例gpt中。
从零开始构建一个大语言模型-第五章第五节_第2张图片

load_weights_into_gpt函数中,我们仔细地将OpenAI实现中的权重与我们的GPTM odel实现进行匹配。举一个具体的例子,OpenAI将第一个Transformer块的输出投影层 的权重张量存储为params["blocks"][0]["attn"]["c_proj"]["w"]。在我们的实现中,这个 权重张量对应于gpt.trf_blocks[b].att.out_proj.weight,其中gpt是一个GPTModel实例 。

开发load_weights_into_gpt函数需要大量的猜测工作,因为OpenAI使用的命名规范 与我们的略有不同。不过,如果我们试图匹配两个维度不同的张量,assign函数会提 醒我们。此外,如果我们在这个函数中犯了错误,我们也会注意到,因为最终得到的 GPT模型将无法生成连贯的文本。

现在让我们实际尝试一下 load_weights_into_gpt,并将OpenAI模型权重加载到我 们的GPTModel实例 gpt 中:

load_weights_into_gpt(gpt, params) 
gpt.to(device)

如果模型加载正确,我们现在就可以使用之前的 generate 函数,用它来生成新的文 本:

torch.manual_seed(123) 
token_ids = generate( 
	model=gpt, idx=text_to_token_ids("Every effort moves you", tokenizer).to(device), 
	max_new_tokens=25, 
	context_size=NEW_CONFIG["context_length"], 
	top_k=50, 
	temperature=1.5 
) 
print("Output text:\n", token_ids_to_text(token_ids, tokenizer))

生成的文本如下:

Output text: 
Every effort moves you toward finding an ideal new way to practice something! 
What makes us want to be on top of that?

我们可以确信模型权重加载正确,因为模型能够生成连贯的文本。这个过程中哪怕一 个小错误都会导致模型无法正常运行。在接下来的章节中,我们将进一步使用这个预 训练模型,并对其进行微调,以实现文本分类和执行指令的功能 。

练习5.5
使用来自OpenAI的预训练权重,在“The Verdict”数据集上计算GPTModel的训练集 和验证集损失。

练习5.6

对不同规模的GPT - 2模型进行实验,例如参数达15.58亿的最大模型,并将其生成的 文本与1.24亿参数的模型生成的文本进行比较。

总结

  • 大型语言模型生成文本时,每次输出一个词元。
  • 默认情况下,通过将模型输 出转换为概率分数,并从词汇表中选择对应最高概率分数的词元来生成下一个词 元,这被称为 “贪心解码”。
  • 通过使用概率采样和温度缩放,我们可以影响生 成文本的多样性和连贯性。
  • 训练集和验证集的损失可用于衡量大型语言模型在 训练期间生成文本的质量。
  • 预训练大型语言模型(LLM)需要调整其权重以最小化训练损失。
  • 大型语言 模型的训练循环本身是深度学习中的标准流程,使用传统的交叉熵损失和AdamW 优化器。
  • 在大型文本语料库上预训练大型语言模型既耗时又耗费资源,因此我 们可以加载公开可用的权重,以替代自己在大型数据集上对模型进行预训练。

你可能感兴趣的:(从零开始构建一个大语言模型,语言模型,人工智能,自然语言处理,机器学习,pytorch)