使用 PyTorch 构建自定义 GPT

news2025/7/19 13:28:06

一、介绍

       介绍大模型,首先考虑一下使用 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 不仅是一项技术成就,而且还邀请您享受乐趣并探索文本生成的无限可能性。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

韩山师范学院学子获第四届“火焰杯”软件测试开发选拔赛全国奖项

2023年10月12日,第四届“火焰杯”软件测试开发选拔赛颁奖仪式在信息科技大楼805机房举行。组委会成员测吧(北京)科技有限公司项目总监王雪冬担任颁奖嘉宾。计算机科学与技术系2020级汤新寅、江湘婷、李若诗同学获得第四届“火焰杯”软件测试开…

idea 配置checkstyle全过程

checkstyle是提高代码质量,检查代码规范的很好用的一款工具,本文简单介绍一下集成的步骤,并提供一份完整的checkstyle的代码规范格式文件,以及常见的格式问题的解决方法。 一,安装 打开idea的文件选项,选择设置&…

Linux权限+Shell和Linux的关系

文章目录 1.Shell存在的意义及作用1.1对于Shell的认知过程1.2Shell/图形化界面外壳程序的意义 2.对Linux权限的理解2.1对Linux权限的认知过程2.2对于Linux下ll指令显示的文件属性的认识 1.Shell存在的意义及作用 1.1对于Shell的认知过程 Linux是什么? Linux是一套免费使用和自…

3.字符集和比较规则简介

3.字符集和比较规则简介 1.字符集和比较规则简介1.1 字符集简介1.2 比较规则简介1.3 一些重要的比较规则 2. MySQL 中支持的字符集和比较规则2.1 MySQL 的 utf8 和 utf8mb42.2 字符集查看2.3 比较规则查看 3. 字符集和比较规则的应用3.1 各级别的字符集和比较规则1. 服务器级别…

多测师肖sir_高级金牌讲师_单个接口性能测试

一、添加聚合报告 聚合报告: jmeter聚合报告名词解释 聚合报告:生成此次性能的测试数据报告 Samples:测试一共发出的请求 ,虚拟用户循环次数 Average:平均响应时间,是单个Request的平均响应时间 Median&am…

C语言-指针讲解(2)

文章目录 1.野指针1.1 什么是野指针1.2 造成野指针的原因有哪些呢1.2.1造成野指针具体代码实例: 1.3 如何避免野指针呢?1.3.1如何对指针进行初始化?1.3.2如何才能小心指针越界?1.3.3 指针变量不再使用时,如何及时置NULL,在指针使用之前检查…

2024年天津高职升本科考试将于11月开始报名

2024年天津高职升本科考试文化课网上报名及其现场确认将于11月下旬开始 2023年11月1日,天津招考资讯官方网站发布了本月(11月)报名事项安排,将进行下列考试项目网上报名工作,2024年备考天津专升本的考生可以看到2024年…

django如何连接sqlite数据库?

目录 一、SQLite数据库简介 二、Django连接SQLite数据库 1、配置数据库 2、创建数据库表 三、使用Django ORM操作SQLite数据库 1、定义模型 2、创建对象 3、查询对象 总结 本文将深入探讨如何在Django框架中连接和使用SQLite数据库。我们将介绍SQLite数据库的特点&…

Java——Spring常见的基础知识

1、Spring 答:Spring 总共大约有 20 个模块, 由 1300 多个不同的文件构成。 而这些组件被分别整合在核心容器(Core Container) 、 AOP(Aspect Oriented Programming)和设备支持(Instrmentation) 、数据访问与集成(Data Access/Integeration) 、Web、 消…

列式数据库ClickHouse,大宽表聚合、报表一下全搞定

一、前言 现在数据库的种类也是特别的多,大致的类别包括: 关系型数据库( MySQL、Oracle、PostgreSQL)非关系型数据库(Redis、MongoDB、Cassandra、Neo4j)全文搜索引擎和分布式文档存储系统(El…

阿里云绝地反击:老用户购买云服务器99元一年

2023阿里云服务器优惠活动来了,以前一直是腾讯云比阿里云优惠,阿里云绝地反击,放开老用户购买资格,99元服务器老用户可以买,并且享受99元续费,阿腾云亲测可行,大家抓紧吧,数量不多&a…

【LeetCode:80. 删除有序数组中的重复项 II | 双指针】

🚀 算法题 🚀 🌲 算法刷题专栏 | 面试必备算法 | 面试高频算法 🍀 🌲 越难的东西,越要努力坚持,因为它具有很高的价值,算法就是这样✨ 🌲 作者简介:硕风和炜,…

【排序算法】 计数排序(非比较排序)详解!了解哈希思想!

🎥 屿小夏 : 个人主页 🔥个人专栏 : 算法—排序篇 🌄 莫道桑榆晚,为霞尚满天! 文章目录 📑前言🌤️计数排序的概念☁️什么是计数排序?☁️计数排序思想⭐绝对…

免费低代码软件:最佳选型推荐

低代码是什么?他是鉴于0代码和高代码之间的概念,主要强调用户无需专业的代码知识即可完成一个成熟的应用程序的搭建。而市场上众多的低代码软件,如何选择一个合适自身企业的产品呢?小编建议不妨先试试免费低代码软件Zoho Creator。…

栅格及波段融合工具

支持大部分栅格数据的波段融合,可以将不同栅格数据的任意波段融合到一个栅格文件里,可以选择融合的波段。 下载地址: https://download.csdn.net/download/qq_35582643/88490703?spm1001.2014.3001.5503 运行方式: 输入…

万达商业携手蜂鸟视图实现CAD图纸一键转换三维地图

室内三维电子地图在很多行业已经形成了广泛应用,特别是商业地产领域的需求持续增长。然而,应用方一直面临挑战的难题是传统的地图绘制工作的繁重和难以满足时效性。为应对这一难题,在2022年蜂鸟视图推出CAD快速转换工具,能够将主流…

Python和urllib库下载网页内容

首先我们需要导入urllib库,然后使用urllib库的urlopen()函数来打开网页。urlopen()函数的第一个参数是需要下载的网页的URL,第二个参数是服务器的URL和端口。 import urllib.request ​ # 打开网页 url proxy_url response urllib.request.urlopen…

记一次 .NET 某工厂无人车调度系统 线程爆高分析

一:背景 1. 讲故事 前些天有位朋友找到我,说他程序中的线程数爆高,让我帮忙看下怎么回事,这种线程数爆高的情况找问题相对比较容易,就让朋友丢一个dump给我,看看便知。 二:为什么会爆高 1. …

【java学习—十一】泛型(1)

文章目录 1. 为什么要有泛型Generic2. 泛型怎么用2.1. 泛型类2.2. 泛型接口2.3. 泛型方法 3. 泛型通配符3.1. 通配符3.2. 有限制的通配符 1. 为什么要有泛型Generic 泛型,JDK1.5新加入的,解决数据类型的安全性问题,其主要原理是在类声明时通过…

精选10款Python可视化工具,请查收

今天我们会介绍一下10个适用于多个学科的Python数据可视化库,其中有名气很大的也有鲜为人知的。 1、matplotlib matplotlib 是Python可视化程序库的泰斗。经过十几年它仍然是Python使用者最常用的画图库。它的设计和在1980年代被设计的商业化程序语言MATLAB非常接近…