从头构建和训练 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个周期,错误率有了显著的下降,但之后的提升就不明显了。这是因为模型目前还缺乏足够的智能(或者说是层数/神经网络的数量),它仅仅是在比较不同字符的嵌入表示。

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

相关文章
|
2天前
|
机器学习/深度学习 人工智能 PyTorch
使用PyTorch实现GPT-2直接偏好优化训练:DPO方法改进及其与监督微调的效果对比
本文将系统阐述DPO的工作原理、实现机制,以及其与传统RLHF和SFT方法的本质区别。
41 22
使用PyTorch实现GPT-2直接偏好优化训练:DPO方法改进及其与监督微调的效果对比
|
2月前
|
存储 数据采集 数据安全/隐私保护
商汤、清华、复旦等开源百亿级多模态数据集,可训练类GPT-4o模型
商汤科技、清华大学和复旦大学等机构联合开源了名为OmniCorpus的多模态数据集,规模达百亿级,旨在支持类似GPT-4级别的大型多模态模型训练。该数据集包含86亿张图像和1696亿个文本标记,远超现有数据集规模并保持高质量,具备广泛来源和灵活性,可轻松转换为纯文本或图像-文本对。经验证,该数据集质量优良,有望促进多模态模型研究,但同时也面临存储管理、数据偏见及隐私保护等挑战。
183 60
|
2月前
|
机器学习/深度学习 弹性计算 人工智能
大模型进阶微调篇(三):微调GPT2大模型实战
本文详细介绍了如何在普通个人电脑上微调GPT2大模型,包括环境配置、代码实现和技术要点。通过合理设置训练参数和优化代码,即使在无独显的设备上也能完成微调,耗时约14小时。文章还涵盖了GPT-2的简介、数据集处理、自定义进度条回调等内容,适合初学者参考。
452 6
|
2月前
|
人工智能 语音技术 UED
仅用4块GPU、不到3天训练出开源版GPT-4o,这是国内团队最新研究
【10月更文挑战第19天】中国科学院计算技术研究所提出了一种名为LLaMA-Omni的新型模型架构,实现与大型语言模型(LLMs)的低延迟、高质量语音交互。该模型集成了预训练的语音编码器、语音适配器、LLM和流式语音解码器,能够在不进行语音转录的情况下直接生成文本和语音响应,显著提升了用户体验。实验结果显示,LLaMA-Omni的响应延迟低至226ms,具有创新性和实用性。
69 1
|
3月前
|
数据可视化 Swift
小钢炮进化,MiniCPM 3.0 开源!4B参数超GPT3.5性能,无限长文本,超强RAG三件套!模型推理、微调实战来啦!
旗舰端侧模型面壁「小钢炮」系列进化为全新 MiniCPM 3.0 基座模型,再次以小博大,以 4B 参数,带来超越 GPT-3.5 的性能。并且,量化后仅 2GB 内存,端侧友好。
小钢炮进化,MiniCPM 3.0 开源!4B参数超GPT3.5性能,无限长文本,超强RAG三件套!模型推理、微调实战来啦!
|
2月前
|
开发工具 git
LLM-03 大模型 15分钟 FineTuning 微调 GPT2 模型 finetuning GPT微调实战 仅需6GB显存 单卡微调 数据 10MB数据集微调
LLM-03 大模型 15分钟 FineTuning 微调 GPT2 模型 finetuning GPT微调实战 仅需6GB显存 单卡微调 数据 10MB数据集微调
75 0
|
5月前
|
数据采集 人工智能 自然语言处理
GPT被封锁了怎么办?轻松获取高质量的数据,训练自己的人工智能和大语言模型。
2023年标志着AI大模型时代的到来,GPT-4等模型在多个领域展现巨大潜力。然而,OpenAI对中国区服务的限制提出了挑战。本文探讨如何使用亮数据代理获取训练大模型所需的数据,包括确定目标、选择代理、数据抓取、清洗,并以西方历史为例,展示如何使用亮数据的静态住宅代理稳定获取DE区域数据,最终在国产AI平台上训练模型,提升知识库的丰富度和准确性。尽管面临外部障碍,但自主获取和训练数据能增强本土AI能力。
|
6月前
|
人工智能
拯救被掰弯的GPT-4!西交微软北大联合提出IN2训练治疗LLM中间迷失
【6月更文挑战第1天】研究人员为解决大型语言模型(LLM)的“中间迷失”问题,提出了IN2训练方法。此方法通过显式监督增强模型对长文本上下文的理解,改善了信息检索能力。应用IN2训练的FILM-7B模型在长文本任务上表现出色,尤其在NarrativeQA数据集上的F1分数提升了3.4。尽管面临数据合成和计算成本的挑战,IN2训练为LLM的进步开辟了新途径,预示着未来在长文本处理领域的潜力。论文链接:https://arxiv.org/pdf/2404.16811
91 5
|
5月前
|
存储 监控 安全
|
7月前
|
自然语言处理
Meta首发变色龙挑战GPT-4o,34B参数引领多模态革命!10万亿token训练刷新SOTA
【5月更文挑战第27天】Meta推出34B参数的多模态模型Chameleon,通过早期融合技术处理图像和文本,实现全面的多模态建模。在10万亿token的训练数据下,Chameleon在图像字幕生成和文本推理任务中刷新SOTA,展现出在混合模态生成和推理的潜力。然而,模型可能无法完全捕捉图像语义信息,且在某些特定任务上有优化空间。[论文链接](https://arxiv.org/pdf/2405.09818)
105 1

热门文章

最新文章