使用 PyTorch 构建自定义 GPT

一、介绍

       介绍大模型,首先考虑一下使用 ChatGPT、Bing Chat 或 Bard 。您是否想过拥有自己的 ChatGPT 会是什么样子?想象一下创建自己的 GPT 模型的兴奋程度。这确实是一种难以置信的感觉!

为了开始构建自定义 GPT 的旅程,让我们仔细看看 GPT 的运作方式。

二、了解 GPT

        GPT 是Generative Pre-trained Transformer的缩写,是一种充当解码器的 Transformer 模型。它已在 BookCorpus 和 WebText 等广泛数据集上进行了预训练。为了更深入地了解 GPT,有必要探索各种类型的变压器并深入研究纯解码器变压器的运行机制。

三、变形金刚解码

        Transformer 是一种深度神经网络架构,专门用于生成类似人类的文本。LLM 模型基于三种类型的变压器架构进行开发,有些模型正在其基础上进行开发。

  • 仅编码器变压器:仅编码器变压器是变压器架构的一种特殊变体,仅专注于理解和编码输入序列的任务。它们是仅使用 Transformer 模型的编码器部分的神经网络。仅编码器转换器用于各种自然语言处理任务,例如文本分类、命名实体识别和情感分析。仅编码器 Transformer 最突出的例子是BERT(来自 Transformers 的双向编码器表示)
  • 仅解码器变压器:仅解码器变压器是一种仅使用变压器模型的解码器组件的变压器架构。这些模型用于需要解码器组件的任务,例如文本生成、机器翻译和摘要。解码器组件负责从输入序列生成输出序列。仅解码器变压器最突出的例子是GPT(生成预训练变压器)
  • Encoder-Decoder Transformers(Cross-Attention):基于 Transformer 的 Encoder-Decoder 模型是一种神经网络架构,广泛应用于语言翻译、文本摘要等 NLP 任务中。这就是原来的变压器。该架构由两个主要组件组成:编码器和解码器。编码器处理输入序列并生成输入的连续表示,而解码器则根据编码器的表示生成输出序列。基于Transformer的编码器-解码器模型已在T5、Bart、Pegasus、ProphetNet、Marge等许多预训练模型中使用。

四、了解 GPT 架构:变压器块

Transformer 块具有三个目标:准备、丰富和预测。

4.1 准备阶段:

变压器模型的初始输入由一系列单词标记组成,然后将其转换为单词嵌入。这些嵌入通过位置编码来丰富以传达其位置信息。

  • 位置编码:在 Transformer 模型中,位置编码用于定义序列中实体的位置,确保每个位置都有不同的表示。变压器本身缺乏对输入序列顺序的固有理解。序列内特定位置 (p)和嵌入空间内维度 (i)位置编码是通过正弦和余弦函数的组合来实现的。

这里,“d”表示词嵌入的维度。这些函数为各个单词位置生成不同的代码。这些代码可以扩展到训练阶段未遇到的序列长度。下一步是将这个新序列输入到变压器块中,其中每个元素都被视为密集向量。

4.2 浓缩阶段:

        丰富包括多头注意力、位置前馈神经网络、残差连接和层归一化。

        多头注意力:注意力用于评估单词之间的重要性和联系。它通过合并更多上下文和逻辑来增强矢量表示。注意力依赖于三个向量:查询向量、键向量和值向量,它们源自初始层,即词嵌入向量。查询向量对应于当前标记,键向量包含序列中的所有其他标记,并且值向量包含序列中的所有标记。

        在自注意力过程中,我们首先计算查询向量和关键向量的缩放积以获得注意力分数。接下来,通过 softmax 函数处理该注意力分数,产生一组范围从0 到 1的注意力权重。然后,每个值向量按其各自的注意力权重进行缩放,最后,它们的总和产生自注意力层的输出。

        术语“多头注意力”源自其多(三个)注意力层的组成。

  • 早期注意: 在这种情况下,短程依赖涉及相邻标记之间的关系,例如词序、词性和基本句子结构,而不需要逐步方法。
  • 中间注意力:它包含输入序列的更广泛的上下文,其中可能包括语义信息、含义、短语之间的关系以及句子中不同单词的角色。
  • 后期注意力:它结合较低层以产生有凝聚力且上下文相关的结果,包括高级抽象、话语结构、情感分析和复杂的长期联系

        Position-wise Feed-Forward Neural Network:该组件的作用是获取序列中每个元素的注意力阶段收集的信息,并将其转换为更丰富的状态。它促进序列中每个元素的非线性变换,并在后续层中继续构建自身。

        残余连接:残余连接促进信息从较早的层直接流向较晚的层。它们在解决深度神经网络中经常遇到的梯度消失问题方面发挥着至关重要的作用。

        层标准化:它不是根据批次标准化输入,而是跨特征执行标准化。这有助于通过确保一致的输入分布来稳定网络的训练,这对于具有不同序列长度的任务至关重要。

4.3 预测阶段:

        在这个过程中,线性函数和softmax函数发挥了至关重要的作用。首先,我们有一系列来自最后一个转换器块的上下文感知向量。该序列中的每个向量代表一个输入标记,并受到其与所有其他标记的交互的影响。为了将向量的输出序列投影到维度为N_w的空间(其中N_w词汇表大小),我们使用线性函数。然后,我们将 softmax 函数应用于这些投影向量,以创建词汇表上的概率分布。这种分布有助于我们预测序列中的下一个标记。

五、变压器中的重要变量

5.1 输入变量

  • 词汇量:这是指模型可以识别的唯一标记的数量。
  • 嵌入/模型大小:表示词嵌入的维度,也称为隐藏大小。
  • 序列/上下文长度:这表示模型一次性可以处理的最大标记数。

5.2 内部变量

  • 注意力头计数:在多头注意力中,输入被分为特定数量的注意力头。
  • 中间层大小:前馈网络中中间层的大小通常大于嵌入大小。
  • 层数:这对应于变压器块的数量。

5.3 训练变量

  • 批量大小:它是指在训练期间一次前向传递中一起处理多少个示例。
  • 训练的令牌:它表示模型在训练期间遇到的令牌总数,通常比纪元数更频繁地报告。

六、自定义类似 GPT 的模型

使用 Pytorch,我们将构建自定义的类似 GPT 的模型。首先我们将导入所有必需的库。

# Import the necessary libraries
import torch
import torch.nn as nn
import torch.nn.functional as F
import math
import time
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns

        我们将定义 DecoderBlock,它是变压器块的单层。我们将定义解码器块所需的超参数:

  • d_model:输入向量的维度。
  • num_heads:多头注意力机制中头的数量。
  • ff_hidden_​​layer:前馈隐藏层的维度。
  • 辍学率:辍学率。

前向方法需要两个输入

  • x:输入张量。
  • target_mask:防止对某些位置的关注的掩码。
# Decoder Block
class DecoderBlock(nn.Module):
    def __init__(self, d_model, num_heads, ff_hidden_layer, dropout):
        super(DecoderBlock, self).__init__()

        self.self_attention = nn.MultiheadAttention(d_model, num_heads, dropout=dropout)
        self.norm1 = nn.LayerNorm(d_model)
        self.dropout1 = nn.Dropout(dropout)
        self.linear1 = nn.Linear(d_model, ff_hidden_layer)
        self.linear2 = nn.Linear(ff_hidden_layer, d_model)
        self.norm2 = nn.LayerNorm(d_model)
        self.dropout2 = nn.Dropout(dropout)

    
    def forward(self, x,target_mask):
        attn_output, _ = self.self_attention(x, x, x, attn_mask=target_mask)
        x = x + self.dropout1(attn_output)
        x = self.norm1(x)
        ff_output = self.linear2(F.relu(self.linear1(x)))
        x = x + self.dropout2(ff_output)
        x = self.norm2(x)
        return x
  

        现在,让我们创建 PositionalEncoding 类,它应用唯一的位置编码来为模型提供有关序列中标记的相对或绝对位置的信息。

# Positional Encoding

class PositionalEncoding(nn.Module):
    def __init__(self, d_model, dropout=0.1, max_len=5000):
        super(PositionalEncoding, self).__init__()
        self.dropout = nn.Dropout(p=dropout)

        pe = torch.zeros(max_len, d_model)
        position = torch.arange(0, max_len, dtype=torch.float).unsqueeze(1)
        div_term = torch.exp(torch.arange(0, d_model, 2).float() * (-math.log(10000.0) / d_model))
        pe[:, 0::2] = torch.sin(position * div_term)
        pe[:, 1::2] = torch.cos(position * div_term)
        pe = pe.unsqueeze(0).transpose(0, 1)
        self.register_buffer("pe", pe)

    def forward(self, x):
        x = x + self.pe[:x.size(0), :]
        return self.dropout(x)

我们需要屏蔽解码器的输入,以防止关注未来的位置。

def generate_square_subsequent_mask(sz):
    """Generate a mask to prevent attention to future positions."""
    mask = (torch.triu(torch.ones(sz, sz)) == 1).transpose(0, 1)
    mask = mask.float().masked_fill(mask == 0, float('-inf')).masked_fill(mask == 1, float(0.0))
    return mask

mask = generate_square_subsequent_mask(sz=5)

plt.figure(figsize=(5,5))
sns.heatmap(mask, cmap="crest", cbar=False, square=True)
plt.title("Mask for Transformer Decoder")
plt.show()

        现在,我们来描述完整的 Transformer 解码器,包括初始嵌入层、单独的 Transformer 解码器块以及最终的线性层和 Softmax 层。

        在 TransformerDecoder 中,线性层执行线性变换,映射输出维度以匹配词汇表大小。随后,应用softmax 层将输出转换为词汇表上的概率分布。整个过程被封装在转发方法中,该方法指示通过解码器的数据流。

# Transformer Decoder

class TransformerDecoder(nn.Module):
    def __init__(self, vocab_size, d_model, num_heads, ff_hidden_layer, dropout):
        super(TransformerDecoder, self).__init__()

        self.embedding = nn.Embedding(vocab_size, d_model)
        self.pos_encoder = PositionalEncoding(d_model, dropout)
        self.transformer_block = DecoderBlock(d_model, num_heads, ff_hidden_layer, dropout)
        self.linear = nn.Linear(d_model, vocab_size)
        self.softmax = nn.LogSoftmax(dim=-1)

    
    def forward(self, x):
        x = self.embedding(x)
        x = self.pos_encoder(x)
        tgt_mask = generate_square_subsequent_mask(x.size(0))
        x = self.transformer_block(x,tgt_mask)
        output = self.linear(x)
        output = self.softmax(output)
        return output

        首先,让我们建立初始解码器。为此,我们首先概述我们的超参数。接下来,我们将构造一个表示批量大小和上下文长度的张量。之后,我们将继续对模型进行前向传播。生成的输出将采用张量的形式。最后,我们将使用“argmax”函数来提取预测的单词索引。

# Define the hyperparameters
vocab_size     = 1000
d_model        = 512
num_heads      = 1
ff_hidden_layer  = 2*d_model
dropout        = 0.1
num_layers     = 10
context_length = 50
batch_size     = 1
# Initialize the model
model = TransformerDecoder(vocab_size, d_model, num_heads, ff_hidden_layer, dropout)

# Create a tensor representing batch size and context length
input_tensor = torch.randint(0, vocab_size, (context_length, batch_size))

# Forward pass through the model
output = model(input_tensor)


print(output.shape)  

# To get the predicted word indices, we can use the `argmax` function
predicted_indices = output.argmax(dim=-1)

print(predicted_indices.shape)

现在,计算参数。

def count_parameters(model):
    return sum(p.numel() for p in model.parameters() if p.requires_grad)

print(f"The model has {count_parameters(model):,} trainable parameters")

为了查看输出,我们会将对数概率转换为 概率,并将输出张量转换为 numpy 数组。

# Convert the log probabilities to probabilities
distribution = torch.exp(output[0, 0, :])

# Convert the output tensor to numpy array
distribution = distribution.detach().numpy()

# Plot the distribution
plt.figure(figsize=(12, 6))
plt.bar(np.arange(vocab_size), distribution)
plt.xlabel("Word Index")
plt.ylabel("Probability")
plt.title("Output Distribution over Vocabulary")
plt.show()

现在制作一个多层解码器,这将以层数作为参数。

class MultiLayerTransformerDecoder(nn.Module):
    def __init__(self, vocab_size, d_model, num_heads, ff_hidden_layer, dropout, num_layers):
        super(MultiLayerTransformerDecoder, self).__init__()

        self.embedding = nn.Embedding(vocab_size, d_model)
        self.pos_encoder = PositionalEncoding(d_model, dropout)
        self.transformer_blocks = nn.ModuleList([
            DecoderBlock(d_model, num_heads, ff_hidden_layer, dropout)
            for _ in range(num_layers)
        ])
        self.linear = nn.Linear(d_model, vocab_size)
        self.softmax = nn.LogSoftmax(dim=-1)


    def forward(self, x):
        x = self.embedding(x)
        x = self.pos_encoder(x)
        for transformer_block in self.transformer_blocks:
            target_mask = generate_square_subsequent_mask(x.size(0))
            x = transformer_block(x,target_mask)
        output = self.linear(x)
        output = self.softmax(output)
        return output

遵循相同的过程。

# Define the hyperparameters
vocab_size     = 10000
d_model        = 2048
num_heads      = 2
ff_hidden_layer  = 8*d_model
dropout        = 0.1
num_layers     = 20
context_length = 1000
batch_size     = 1

# Create our input to the model to process
input_tensor = torch.randint(0, vocab_size, (context_length, batch_size))

# Initialize the model with `num_layer` layers
model = MultiLayerTransformerDecoder(vocab_size, d_model, num_heads, ff_hidden_layer, dropout, num_layers)

# Print the number of trainable parameters
print(f"The model has {count_parameters(model):,} trainable parameters")

# Let's use the same input_tensor from the previous example
output = model(input_tensor)

# Convert the log probabilities to probabilities for the first sequence in the batch and the first position in the sequence
distribution = torch.exp(output[0, 0, :])

# Convert the output tensor to numpy array
distribution = distribution.detach().numpy()

# Now plot the distribution
plt.figure(figsize=(12, 6))
plt.bar(np.arange(vocab_size), distribution)
plt.xlabel("Word Index")
plt.ylabel("Probability")
plt.title("Output Distribution over Vocabulary")
plt.show()

你可以看到你的模型。

MultiLayerTransformerDecoder(
  (embedding): Embedding(10000, 2048)
  (pos_encoder): PositionalEncoding(
    (dropout): Dropout(p=0.1, inplace=False)
  )
  (transformer_blocks): ModuleList(
    (0-19): 20 x DecoderBlock(
      (self_attention): MultiheadAttention(
        (out_proj): NonDynamicallyQuantizableLinear(in_features=2048, out_features=2048, bias=True)
      )
      (norm1): LayerNorm((2048,), eps=1e-05, elementwise_affine=True)
      (dropout1): Dropout(p=0.1, inplace=False)
      (linear1): Linear(in_features=2048, out_features=16384, bias=True)
      (linear2): Linear(in_features=16384, out_features=2048, bias=True)
      (norm2): LayerNorm((2048,), eps=1e-05, elementwise_affine=True)
      (dropout2): Dropout(p=0.1, inplace=False)
    )
  )
  (linear): Linear(in_features=2048, out_features=10000, bias=True)
  (softmax): LogSoftmax(dim=-1)
)

        现在,您必须使用任何您想要用自己的仅解码器变压器模型进行实验的数据集,并且您将拥有自己的 GPT。玩得开心!(代码阿克里蒂·乌帕迪亚

七、结论

        创建我们自己的类似 GPT 的模型的过程包括理解架构、在代码中实现它以及使用数据集进行实验和微调。

这次旅程让我们能够释放创造力并探索令人兴奋的自然语言处理世界。

构建自定义 GPT 不仅是一项技术成就,而且还邀请您享受乐趣并探索文本生成的无限可能性。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/114884.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

算法leetcode|86. 分隔链表(rust重拳出击)

文章目录 86. 分隔链表:样例 1:样例 2:提示: 分析:题解:rust:go:c:python:java: 86. 分隔链表: 给你一个链表的头节点 head 和一个特…

echarts 简单渐变折线图

渐变颜色多个折线图 import * as echarts from echarts/core // 看个人项目echart引入方式 options: {tooltip: {trigger: axis},yAxis: {type: value,name: 能耗:标准煤kg},legend: {icon: circle,itemHeight: 8,itemWidth: 8,top: 1%,left: center// selectedMo…

版本控制系统-SVN

SVN Apache Subversion 通常被缩写成 SVN,是一个开放源代码的版本控制系统。 官网:https://subversion.apache.org 资料:https://svnbook.red-bean.com、https://www.runoob.com/svn/svn-tutorial.html 下载:https://sourceforg…

分享99个工作总结PPT,总有一款适合您

分享99个工作总结PPT,总有一款适合您 PPT下载链接:https://pan.baidu.com/s/1ETaGRUsTRRQq3i5PJUX7tA?pwd8888 提取码:8888 Python采集代码下载链接:采集代码.zip - 蓝奏云 黑金商务路演工作汇报PPT模板 古韵中国风财务计…

瑞禧生物分享~今天是 碲化银粉体 Ag2Te CAS:12002-99-2

碲化银粉体 Ag2Te CAS:12002-99-2 纯度:99% 仅用于科研 储藏条件:冷藏-20℃ 简介:碲化银是一种无机化合物,化学式是Ag2Te。它是一种单斜晶体,并以矿物的形式存在于自然界中。化学计量的碲化银具有n型半导…

自家开发VS第三方美颜SDK:技术和资源的比较

开发直播平台时,开发人员面临一个关键决策:是选择使用第三方美颜SDK,还是自家开发美颜算法?本文将深入探讨这两种方法的技术和资源方面的比较,帮助开发者更好地决定哪种途径最适合他们的应用。 一、第三方美颜SDK&am…

算法题:144.二叉树的前序遍历(递归、迭代)Java Python部分

1、递归法 其实递归法提交结果也挺好看的&#xff0c;代码如下&#xff1a; class Solution {//前序遍历public List<Integer> preorderTraversal(TreeNode root) {List<Integer> res new ArrayList<Integer>();preorder(root, res);return res;}public vo…

一条 SQL 是如何在 MyBatis 中执行的

前言 MyBatis 执行 SQL 的核心接口为 SqlSession 接口&#xff0c;该接口提供了一些 CURD 及控制事务的方法&#xff0c;另外还可以通过 SqlSession 先获取 Mapper 接口的实例&#xff0c;然后通过 Mapper 接口执行 SQL&#xff0c;Mapper 接口方法的执行最终还是委托到 SqlSe…

*LEEDCODE 73矩阵置零

![在这里插入代码片](https://img-blog.csdnimg.cn/ab1d7d4b9d5046d8900de430249be3bf.png)1 0 0 替换两个列表 2 记录时 0 0 已经是半改好的状态

【QT】绘图设备

绘图设备是指继承QPainterDevice的子类。Qt提供了很多这样的类&#xff0c;例如QPixmap、QBitmap、QImage和 QPicture。其中&#xff0c; QPixmap专门为图像在屏幕上的显示做了优化QBitmap是QPixmap的一个子类&#xff0c;它的色深限定为1&#xff0c;可以使用 QPixmap的isQBi…

AndroidPicker的使用

项目地址&#xff1a;https://github.com/gzu-liyujiang/AndroidPicker 历史版本:https://github.com/gzu-liyujiang/AndroidPicker/blob/master/ChangeLog.md 依赖配置 // JitPack 远程仓库&#xff1a;https://jitpack.iomaven { url https://jitpack.io } 所有选择器的基…

网络套接字编程(三)

网络套接字编程(三) 文章目录 网络套接字编程(三)简易日志组件引入日志的原因日志等级打印日志函数将日志组件使用到服务端中 守护进程概念进程组、终端、会话守护进程的实现原理守护进程化组件将守护进程化组件使用到服务端中 补充知识关于inet_ntoa 在上一篇博客 网络套接字…

STM32:使用蓝牙模块

一、蓝牙概要 蓝牙是一种常见的无线通信协议&#xff0c;通常用于短距离通信。蓝牙分为经典蓝牙和低功耗蓝牙(BLE)。经典蓝牙通常用于需要持续传输数据的设备&#xff0c;比如蓝牙耳机等。低功耗蓝牙通常用于只需要间歇性传输数据的设备&#xff0c;比如运动手环。 蓝牙…

51单片机电子钟闹钟温度LCD1602液晶显示设计( proteus仿真+程序+原理图+设计报告+讲解视频)

51单片机电子钟闹钟温度液晶显示设计( proteus仿真程序原理图设计报告讲解视频&#xff09; 1.主要功能&#xff1a;2.仿真3. 程序代码4. 原理图5. 设计报告6. 设计资料内容清单&&下载链接资料下载链接&#xff08;可点击&#xff09;&#xff1a; &#x1f31f;51单片…

【Redis】Java连接Redis及Java操作Redis常用数据类型

一&#xff0c;Java连接Redis 1.1 连接前端服务器 打开RedisDesktopManager并连接Redis 不知道可看我上一篇文章&#xff1a; 【Redis】安装(Linux&window)及Redis的常用命令-CSDN博客 1.2 后端依赖 导入相关的jedis依赖 注意&#xff1a;要在dependencies标签中导入…

【深度学习】pytorch——实现CIFAR-10数据集的分类

笔记为自我总结整理的学习笔记&#xff0c;若有错误欢迎指出哟~ 往期文章&#xff1a; 【深度学习】pytorch——快速入门 CIFAR-10分类 CIFAR-10简介CIFAR-10数据集分类实现步骤一、数据加载及预处理实现数据加载及预处理归一化的理解访问数据集Dataset对象Dataloader对象 二、…

【css3】涟漪动画

效果展示 dom代码 <div class"mapSelfTitle66"><div></div> </div> 样式代码 .mapSelfTitle66{width:120px;height:60px;position: relative;&>div{width:100%;height:100%;background: url("~/assets/images/video_show/err…

数据结构:邻接矩阵与邻接表

模型图 邻接矩阵 用于反应图中任意两点之间的关联&#xff0c;用二维数组表示比较方便 以行坐标为起点&#xff0c;列坐标为终点如果两个点之间有边&#xff0c;那么标记为绿色&#xff0c;如图&#xff1a; 适合表示稠密矩阵 邻接表 用一维数组 链表的形式表示&#xff…

离散数学实践(2)-编程实现关系性质的判断

*本文为博主本人校内的离散数学专业课的实践作业。由于实验步骤已经比较详细&#xff0c;故不再对该实验额外提供详解&#xff0c;本文仅提供填写的实验报告内容与代码部分&#xff0c;以供有需要的同学学习、参考。 -------------------------------------- 编程语言&#xff…

高效处理异常值的算法:One-class SVM模型的自动化方案

一、引言 数据清洗和异常值处理在数据分析和机器学习任务中扮演着关键的角色。清洗数据可以提高数据质量&#xff0c;消除噪声和错误&#xff0c;从而确保后续分析和建模的准确性和可靠性。而异常值则可能对数据分析结果产生严重影响&#xff0c;导致误导性的结论和决策。因此&…