从头构建和训练 GPT-2 |实战

简介: 从头构建和训练 GPT-2 |实战

引言

该项目将引导您完成构建简单 GPT-2 模型的所有步骤,并使用 Taylor Swift 和 Ed Sheeran 的一堆歌曲进行训练。本文的数据集和源代码将在 Github 上提供。

构建 GPT-2 架构

我们将逐步推进这个项目,不断优化一个基础的模型框架,并在其基础上增加新的层次,这些层次都是基于 GPT-2 的原始设计。

我们将按照以下步骤进行:

  • 制作一个定制的分词工具
  • 开发一个数据加载程序
  • 培养一个基础的语言处理能力
  • 完成 GPT-2 架构的实现(第二部分)

该项目分为两个部分,第一个部分介绍语言建模的基础知识,第二部分直接跳到 GPT-2 实现。我建议您按照本文进行操作并自己构建它,这将使学习 GPT-2 变得更加有趣和有趣。

最终模型:

1. 构建自定义分词器

语言模型不像我们一样看到文本。相反,它们将数字序列识别为特定文本的标记。因此,第一步是导入我们的数据并构建我们自己的角色级别分词器。

data_dir = "data.txt"
text = open(data_dir, 'r').read() # load all the data as simple string

# Get all unique characters in the text as vocabulary
chars = list(set(text))
vocab_size = len(chars)

如果您看到上面的输出,我们就有了在初始化过程中从文本数据中提取的所有唯一字符的列表。字符标记化基本上是使用词汇表中字符的索引位置并将其映射到输入文本中的相应字符。

# build the character level tokenizer
chr_to_idx = {
   
   c:i for i, c in enumerate(chars)}
idx_to_chr = {
   
   i:c for i, c in enumerate(chars)}

def encode(input_text: str) -> list[int]:
    return [chr_to_idx[t] for t in input_text]

def decode(input_tokens: list[int]) -> str:
    return "".join([idx_to_chr[i] for i in input_tokens])

import torch
# use cpu or gpu based on your system
device = "cpu"
if torch.cuda.is_available():
    device = "cuda"

# convert our text data into tokenized tensor
data = torch.tensor(encode(text), dtyppe=torch.long, device=device)

现在,我们有了标记化的张量数据,其中文本中的每个字符都转换为各自的标记。

import torch

data_dir = "data.txt"
text = open(data_dir, 'r').read() # load all the data as simple string

# Get all unique characters in the text as vocabulary
chars = list(set(text))
vocab_size = len(chars)

# build the character level tokenizer
chr_to_idx = {
   
   c:i for i, c in enumerate(chars)}
idx_to_chr = {
   
   i:c for i, c in enumerate(chars)}

def encode(input_text: str) -> list[int]:
    return [chr_to_idx[t] for t in input_text]

def decode(input_tokens: list[int]) -> str:
    return "".join([idx_to_chr[i] for i in input_tokens])


# convert our text data into tokenized tensor
data = torch.tensor(encode(text), dtyppe=torch.long, device=device)

2. 构建数据加载器

现在,在构建模型之前,我们必须定义如何将数据输入模型进行训练,以及数据的维度和批量大小。

让我们定义我们的数据加载器如下:

train_batch_size = 16  # training batch size
eval_batch_size = 8  # evaluation batch size
context_length = 256  # number of tokens processed in a single batch
train_split = 0.8  # percentage of data to use from total data for training

# split data into trian and eval
n_data = len(data)
train_data = data[:int(n_data * train_split)]
eval_data = data[int(n_data * train_split):]


class DataLoader:
    def __init__(self, tokens, batch_size, context_length) -> None:
        self.tokens = tokens
        self.batch_size = batch_size
        self.context_length = context_length

        self.current_position = 0

    def get_batch(self) -> torch.tensor:
        b, c = self.batch_size, self.context_length

        start_pos = self.current_position
        end_pos = self.current_position + b * c + 1

        # if the batch exceeds total length, get the data till last token
        # and take remaining from starting token to avoid always excluding some data
        add_data = -1 # n, if length exceeds and we need `n` additional tokens from start
        if end_pos > len(self.tokens):
            add_data = end_pos - len(self.tokens) - 1
            end_pos = len(self.tokens) - 1

        d = self.tokens[start_pos:end_pos]
        if add_data != -1:
            d = torch.cat([d, self.tokens[:add_data]])
        x = (d[:-1]).view(b, c)  # inputs
        y = (d[1:]).view(b, c)  # targets

        self.current_position += b * c # set the next position
        return x, y

train_loader = DataLoader(train_data, train_batch_size, context_length)
eval_loader = DataLoader(eval_data, eval_batch_size, context_length)

我们现在已经开发了自己的专用数据加载工具,它既可以用于模型的训练阶段,也可以用于评估阶段。这个工具包含一个 get_batch 功能,它能够一次性提供大小为 batch_size 乘以 context_length 的数据批次。

如果你好奇为什么 x 的范围是从序列的起始点到结束点,而 y 的范围则是从 x 的起始点后一位到结束点后一位,这是因为模型的核心任务是预测给定前序序列之后的下一个元素。换句话说,在 y 中会多出一个标记,这样模型就可以基于 x 中的最后 n 个标记来预测下一个,也就是第 (n+1) 个标记。如果这听起来有些难以理解,可以参阅下面的图解说明。

3. 训练简单的语言模型

现在,我们即将利用我们刚刚加载的数据,来搭建和训练一个基础的语言模型。

在本节中,我们将保持操作的简洁性,采用一个简单的二元语法模型,即基于上一个词来预测下一个词。如你所见,我们将只利用 Embedding 层,而忽略主解码模块。

Embedding 层能够为词汇表中的每个字符表示出 n = d_model 个独特的属性,并且该层会根据字符在词汇表中的索引来提取这些属性。

你会惊讶地发现,仅仅依靠 Embedding 层,模型就能表现出色。我们将通过逐步增加更多的层来优化模型,所以请耐心等待并继续关注。

嵌入的维度,也就是 d_model,目前设置为等于词汇表的大小 vocab_size,这是因为模型的最终输出需要对应到词汇表中每个字符的对数几率,以便计算它们各自的概率。在未来,我们会引入一个线性层(Linear 层),它负责将 d_model 的输出维度转换为 vocab_size,这样我们就可以使用自定义的嵌入维度 embedding_dimension

import torch.nn as nn
import torch.nn.functional as F

class GPT(nn.Module):
    def __init__(self, vocab_size, d_model):
        super().__init__()
        self.wte = nn.Embedding(vocab_size, d_model) # word token embeddings

    def forward(self, inputs, targets = None):
        logits = self.wte(inputs) # dim -> batch_size, sequence_length, d_model
        loss = None
        if targets != None:
            batch_size, sequence_length, d_model = logits.shape
            # to calculate loss for all token embeddings in a batch
            # kind of a requirement for cross_entropy
            logits = logits.view(batch_size * sequence_length, d_model)
            targets = targets.view(batch_size * sequence_length)
            loss = F.cross_entropy(logits, targets)
        return logits, loss

    def generate(self, inputs, max_new_tokens):
        # this will store the model outputs along with the initial input sequence
        # make a copy so that it doesn't interfare with model 
        for _ in range(max_new_tokens):
            # we only pass targets on training to calculate loss
            logits, _ = self(inputs)  
            # for all the batches, get the embeds for last predicted sequence
            logits = logits[:, -1, :] 
            probs = F.softmax(logits, dim=1)            
            # get the probable token based on the input probs
            idx_next = torch.multinomial(probs, num_samples=1) 

            inputs = torch.cat([inputs, idx_next], dim=1)
        # as the inputs has all model outputs + initial inputs, we can use it as final output
        return inputs

m = GPT(vocab_size=vocab_size, d_model=d_model).to(device)

我们已经成功构建了一个模型,它仅由一个嵌入层(Embedding layer)和用于生成标记的 Softmax 函数组成。接下来,让我们观察一下,当模型接收到一些输入字符时,它的反应和表现会是怎样。

现在,我们来到了最后的关键步骤——训练模型,让它学会识别和理解字符。接下来,我们将配置优化器。目前,我们选择使用一个基础的 AdamW 优化器,设置的学习率为 0.001。在未来的章节中,我们会探讨如何进一步提升优化过程。

lr = 1e-3
optim = torch.optim.AdamW(m.parameters(), lr=lr)
Below is a very simple training loop.
epochs = 5000
eval_steps = 1000 # perform evaluation in every n steps
for ep in range(epochs):
    xb, yb = train_loader.get_batch()

    logits, loss = m(xb, yb)
    optim.zero_grad(set_to_none=True)
    loss.backward()
    optim.step()

    if ep % eval_steps == 0 or ep == epochs-1:
        m.eval()
        with torch.no_grad():
            xvb, yvb = eval_loader.get_batch()
            _, e_loss = m(xvb, yvb)

            print(f"Epoch: {ep}tlr: {lr}ttrain_loss: {loss}teval_loss: {e_loss}")
        m.train() # back to training mode

我们取得了相当不错的损失值。但我们还没有完全成功。你可以看到,直到训练的第2000个周期,错误率有了显著的下降,但之后的提升就不明显了。这是因为模型目前还缺乏足够的智能(或者说是层数/神经网络的数量),它仅仅是在比较不同字符的嵌入表示。

现在模型的输出看起来如下所示:

相关文章
|
1月前
|
数据采集 人工智能 自然语言处理
GPT被封锁了怎么办?轻松获取高质量的数据,训练自己的人工智能和大语言模型。
2023年标志着AI大模型时代的到来,GPT-4等模型在多个领域展现巨大潜力。然而,OpenAI对中国区服务的限制提出了挑战。本文探讨如何使用亮数据代理获取训练大模型所需的数据,包括确定目标、选择代理、数据抓取、清洗,并以西方历史为例,展示如何使用亮数据的静态住宅代理稳定获取DE区域数据,最终在国产AI平台上训练模型,提升知识库的丰富度和准确性。尽管面临外部障碍,但自主获取和训练数据能增强本土AI能力。
|
1月前
|
存储 监控 安全
|
2月前
|
人工智能
拯救被掰弯的GPT-4!西交微软北大联合提出IN2训练治疗LLM中间迷失
【6月更文挑战第1天】研究人员为解决大型语言模型(LLM)的“中间迷失”问题,提出了IN2训练方法。此方法通过显式监督增强模型对长文本上下文的理解,改善了信息检索能力。应用IN2训练的FILM-7B模型在长文本任务上表现出色,尤其在NarrativeQA数据集上的F1分数提升了3.4。尽管面临数据合成和计算成本的挑战,IN2训练为LLM的进步开辟了新途径,预示着未来在长文本处理领域的潜力。论文链接:https://arxiv.org/pdf/2404.16811
37 5
|
3月前
|
自然语言处理
Meta首发变色龙挑战GPT-4o,34B参数引领多模态革命!10万亿token训练刷新SOTA
【5月更文挑战第27天】Meta推出34B参数的多模态模型Chameleon,通过早期融合技术处理图像和文本,实现全面的多模态建模。在10万亿token的训练数据下,Chameleon在图像字幕生成和文本推理任务中刷新SOTA,展现出在混合模态生成和推理的潜力。然而,模型可能无法完全捕捉图像语义信息,且在某些特定任务上有优化空间。[论文链接](https://arxiv.org/pdf/2405.09818)
61 1
|
2月前
|
人工智能 机器人 API
Dify 构建一个基于 GPT 的 AI 客服系统
Dify 构建一个基于 GPT 的 AI 客服系统
491 0
|
3月前
|
人工智能 安全 测试技术
Infection-2.5登场,训练计算量仅40%、性能直逼GPT-4!
【2月更文挑战第18天】Infection-2.5登场,训练计算量仅40%、性能直逼GPT-4!
57 3
Infection-2.5登场,训练计算量仅40%、性能直逼GPT-4!
|
3月前
|
人工智能 PyTorch iOS开发
苹果AppleMacOs最新Sonoma系统本地训练和推理GPT-SoVITS模型实践
GPT-SoVITS是少有的可以在MacOs系统下训练和推理的TTS项目,虽然在效率上没有办法和N卡设备相提并论,但终归是开发者在MacOs系统构建基于M系列芯片AI生态的第一步。
苹果AppleMacOs最新Sonoma系统本地训练和推理GPT-SoVITS模型实践
|
3月前
|
自然语言处理 C++
GPT4 vs Llama,大模型训练的坑
训练大模型,总觉得效果哪里不对,查了三天,终于发现了原因
113 0
|
3月前
|
SQL 人工智能 JSON
JARVIS 变为现实:使用 Python、React 和 GPT-3 构建个人 AI 助理
JARVIS 变为现实:使用 Python、React 和 GPT-3 构建个人 AI 助理
136 0
|
3月前
|
人工智能 JavaScript 前端开发
使用 Node.js、Socket.IO 和 GPT-4 构建 AI 聊天机器人
使用 Node.js、Socket.IO 和 GPT-4 构建 AI 聊天机器人
195 0