【变形金刚03】使用 Pytorch 开始构建transformer

 

一、说明

        在本教程中,我们将使用 PyTorch 从头开始构建一个基本的转换器模型。Vaswani等人在论文“注意力是你所需要的一切”中引入的Transformer模型是一种深度学习架构,专为序列到序列任务而设计,例如机器翻译和文本摘要。它基于自我注意机制,已成为许多最先进的自然语言处理模型的基础,如GPT和BERT。

二、准备活动

        若要生成转换器模型,我们将按照以下步骤操作:

  1. 导入必要的库和模块
  2. 定义基本构建块:多头注意力、位置前馈网络、位置编码
  3. 构建编码器和解码器层
  4. 组合编码器和解码器层以创建完整的转换器模型
  5. 准备示例数据
  6. 训练模型

        让我们从导入必要的库和模块开始。

import torch
import torch.nn as nn
import torch.optim as optim
import torch.utils.data as data
import math
import copy

现在,我们将定义转换器模型的基本构建基块。

三、多头注意力

图2.多头注意力(来源:作者创建的图像)

        多头注意力机制计算序列中每对位置之间的注意力。它由多个“注意头”组成,用于捕获输入序列的不同方面。

class MultiHeadAttention(nn.Module):
    def __init__(self, d_model, num_heads):
        super(MultiHeadAttention, self).__init__()
        assert d_model % num_heads == 0, "d_model must be divisible by num_heads"
        
        self.d_model = d_model
        self.num_heads = num_heads
        self.d_k = d_model // num_heads
        
        self.W_q = nn.Linear(d_model, d_model)
        self.W_k = nn.Linear(d_model, d_model)
        self.W_v = nn.Linear(d_model, d_model)
        self.W_o = nn.Linear(d_model, d_model)
        
    def scaled_dot_product_attention(self, Q, K, V, mask=None):
        attn_scores = torch.matmul(Q, K.transpose(-2, -1)) / math.sqrt(self.d_k)
        if mask is not None:
            attn_scores = attn_scores.masked_fill(mask == 0, -1e9)
        attn_probs = torch.softmax(attn_scores, dim=-1)
        output = torch.matmul(attn_probs, V)
        return output
        
    def split_heads(self, x):
        batch_size, seq_length, d_model = x.size()
        return x.view(batch_size, seq_length, self.num_heads, self.d_k).transpose(1, 2)
        
    def combine_heads(self, x):
        batch_size, _, seq_length, d_k = x.size()
        return x.transpose(1, 2).contiguous().view(batch_size, seq_length, self.d_model)
        
    def forward(self, Q, K, V, mask=None):
        Q = self.split_heads(self.W_q(Q))
        K = self.split_heads(self.W_k(K))
        V = self.split_heads(self.W_v(V))
        
        attn_output = self.scaled_dot_product_attention(Q, K, V, mask)
        output = self.W_o(self.combine_heads(attn_output))
        return output

        MultiHeadAttention 代码使用输入参数和线性变换层初始化模块。它计算注意力分数,将输入张量重塑为多个头部,并将所有头部的注意力输出组合在一起。前向方法计算多头自我注意,允许模型专注于输入序列的某些不同方面。

四、位置前馈网络

class PositionWiseFeedForward(nn.Module):
    def __init__(self, d_model, d_ff):
        super(PositionWiseFeedForward, self).__init__()
        self.fc1 = nn.Linear(d_model, d_ff)
        self.fc2 = nn.Linear(d_ff, d_model)
        self.relu = nn.ReLU()

    def forward(self, x):
        return self.fc2(self.relu(self.fc1(x)))

PositionWiseFeedForward 类扩展了 PyTorch 的 nn。模块并实现按位置的前馈网络。该类使用两个线性转换层和一个 ReLU 激活函数进行初始化。forward 方法按顺序应用这些转换和激活函数来计算输出。此过程使模型能够在进行预测时考虑输入元素的位置。

五、位置编码

        位置编码用于注入输入序列中每个令牌的位置信息。它使用不同频率的正弦和余弦函数来生成位置编码。

class PositionalEncoding(nn.Module):
    def __init__(self, d_model, max_seq_length):
        super(PositionalEncoding, self).__init__()
        
        pe = torch.zeros(max_seq_length, d_model)
        position = torch.arange(0, max_seq_length, 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)
        
        self.register_buffer('pe', pe.unsqueeze(0))
        
    def forward(self, x):
        return x + self.pe[:, :x.size(1)]

PositionalEncoding 类使用 d_model 和 max_seq_length 输入参数进行初始化,从而创建一个张量来存储位置编码值。该类根据比例因子div_term分别计算偶数和奇数指数的正弦和余弦值。前向方法通过将存储的位置编码值添加到输入张量中来计算位置编码,从而使模型能够捕获输入序列的位置信息。

现在,我们将构建编码器层和解码器层。

六、编码器层

图3.变压器网络的编码器部分(来源:图片来自原文)

编码器层由多头注意力层、位置前馈层和两个层归一化层组成。

class EncoderLayer(nn.Module):
    def __init__(self, d_model, num_heads, d_ff, dropout):
        super(EncoderLayer, self).__init__()
        self.self_attn = MultiHeadAttention(d_model, num_heads)
        self.feed_forward = PositionWiseFeedForward(d_model, d_ff)
        self.norm1 = nn.LayerNorm(d_model)
        self.norm2 = nn.LayerNorm(d_model)
        self.dropout = nn.Dropout(dropout)
        
    def forward(self, x, mask):
        attn_output = self.self_attn(x, x, x, mask)
        x = self.norm1(x + self.dropout(attn_output))
        ff_output = self.feed_forward(x)
        x = self.norm2(x + self.dropout(ff_output))
        return x

类使用输入参数和组件进行初始化,包括一个多头注意模块、一个 PositionWiseFeedForward 模块、两个层规范化模块和一个 dropout 层。前向方法通过应用自注意、将注意力输出添加到输入张量并规范化结果来计算编码器层输出。然后,它计算按位置的前馈输出,将其与归一化的自我注意输出相结合,并在返回处理后的张量之前对最终结果进行归一化。

七、解码器层

图4.变压器网络的解码器部分(Souce:图片来自原始论文)

解码器层由两个多头注意力层、一个位置前馈层和三个层归一化层组成。

class DecoderLayer(nn.Module):
    def __init__(self, d_model, num_heads, d_ff, dropout):
        super(DecoderLayer, self).__init__()
        self.self_attn = MultiHeadAttention(d_model, num_heads)
        self.cross_attn = MultiHeadAttention(d_model, num_heads)
        self.feed_forward = PositionWiseFeedForward(d_model, d_ff)
        self.norm1 = nn.LayerNorm(d_model)
        self.norm2 = nn.LayerNorm(d_model)
        self.norm3 = nn.LayerNorm(d_model)
        self.dropout = nn.Dropout(dropout)
        
    def forward(self, x, enc_output, src_mask, tgt_mask):
        attn_output = self.self_attn(x, x, x, tgt_mask)
        x = self.norm1(x + self.dropout(attn_output))
        attn_output = self.cross_attn(x, enc_output, enc_output, src_mask)
        x = self.norm2(x + self.dropout(attn_output))
        ff_output = self.feed_forward(x)
        x = self.norm3(x + self.dropout(ff_output))
        return x

解码器层使用输入参数和组件进行初始化,例如用于屏蔽自我注意和交叉注意力的多头注意模块、PositionWiseFeedForward 模块、三层归一化模块和辍学层。

转发方法通过执行以下步骤来计算解码器层输出:

  1. 计算掩蔽的自我注意输出并将其添加到输入张量中,然后进行 dropout 和层归一化。
  2. 计算解码器和编码器输出之间的交叉注意力输出,并将其添加到规范化的掩码自注意力输出中,然后进行 dropout 和层规范化。
  3. 计算按位置的前馈输出,并将其与归一化交叉注意力输出相结合,然后是压差和层归一化。
  4. 返回已处理的张量。

这些操作使解码器能够根据输入和编码器输出生成目标序列。

现在,让我们组合编码器和解码器层来创建完整的转换器模型。

八、变压器型号

图5.The Transformer Network(来源:图片来源于原文)

将它们全部合并在一起:

class Transformer(nn.Module):
    def __init__(self, src_vocab_size, tgt_vocab_size, d_model, num_heads, num_layers, d_ff, max_seq_length, dropout):
        super(Transformer, self).__init__()
        self.encoder_embedding = nn.Embedding(src_vocab_size, d_model)
        self.decoder_embedding = nn.Embedding(tgt_vocab_size, d_model)
        self.positional_encoding = PositionalEncoding(d_model, max_seq_length)

        self.encoder_layers = nn.ModuleList([EncoderLayer(d_model, num_heads, d_ff, dropout) for _ in range(num_layers)])
        self.decoder_layers = nn.ModuleList([DecoderLayer(d_model, num_heads, d_ff, dropout) for _ in range(num_layers)])

        self.fc = nn.Linear(d_model, tgt_vocab_size)
        self.dropout = nn.Dropout(dropout)

    def generate_mask(self, src, tgt):
        src_mask = (src != 0).unsqueeze(1).unsqueeze(2)
        tgt_mask = (tgt != 0).unsqueeze(1).unsqueeze(3)
        seq_length = tgt.size(1)
        nopeak_mask = (1 - torch.triu(torch.ones(1, seq_length, seq_length), diagonal=1)).bool()
        tgt_mask = tgt_mask & nopeak_mask
        return src_mask, tgt_mask

    def forward(self, src, tgt):
        src_mask, tgt_mask = self.generate_mask(src, tgt)
        src_embedded = self.dropout(self.positional_encoding(self.encoder_embedding(src)))
        tgt_embedded = self.dropout(self.positional_encoding(self.decoder_embedding(tgt)))

        enc_output = src_embedded
        for enc_layer in self.encoder_layers:
            enc_output = enc_layer(enc_output, src_mask)

        dec_output = tgt_embedded
        for dec_layer in self.decoder_layers:
            dec_output = dec_layer(dec_output, enc_output, src_mask, tgt_mask)

        output = self.fc(dec_output)
        return output

类组合了以前定义的模块以创建完整的转换器模型。在初始化期间,Transformer 模块设置输入参数并初始化各种组件,包括源序列和目标序列的嵌入层、位置编码模块、用于创建堆叠层的编码器层和解码器层模块、用于投影解码器输出的线性层以及 dropout 层。

generate_mask 方法为源序列和目标序列创建二进制掩码,以忽略填充标记并防止解码器处理将来的令牌。前向方法通过以下步骤计算转换器模型的输出:

  1. 使用 generate_mask 方法生成源掩码和目标掩码。
  2. 计算源和目标嵌入,并应用位置编码和丢弃。
  3. 通过编码器层处理源序列,更新enc_output张量。
  4. 通过解码器层处理目标序列,使用enc_output和掩码,并更新dec_output张量。
  5. 将线性投影层应用于解码器输出,获取输出对数。

这些步骤使转换器模型能够处理输入序列,并根据其组件的组合功能生成输出序列。

九、准备样本数据

        在此示例中,我们将创建一个用于演示目的的玩具数据集。实际上,您将使用更大的数据集,预处理文本,并为源语言和目标语言创建词汇映射。

src_vocab_size = 5000
tgt_vocab_size = 5000
d_model = 512
num_heads = 8
num_layers = 6
d_ff = 2048
max_seq_length = 100
dropout = 0.1

transformer = Transformer(src_vocab_size, tgt_vocab_size, d_model, num_heads, num_layers, d_ff, max_seq_length, dropout)

# Generate random sample data
src_data = torch.randint(1, src_vocab_size, (64, max_seq_length))  # (batch_size, seq_length)
tgt_data = torch.randint(1, tgt_vocab_size, (64, max_seq_length))  # (batch_size, seq_length)

十、训练模型

        现在,我们将使用示例数据训练模型。在实践中,您将使用更大的数据集并将其拆分为训练集和验证集。

criterion = nn.CrossEntropyLoss(ignore_index=0)
optimizer = optim.Adam(transformer.parameters(), lr=0.0001, betas=(0.9, 0.98), eps=1e-9)

transformer.train()

for epoch in range(100):
    optimizer.zero_grad()
    output = transformer(src_data, tgt_data[:, :-1])
    loss = criterion(output.contiguous().view(-1, tgt_vocab_size), tgt_data[:, 1:].contiguous().view(-1))
    loss.backward()
    optimizer.step()
    print(f"Epoch: {epoch+1}, Loss: {loss.item()}")

        我们可以使用这种方式在 Pytorch 中从头开始构建一个简单的转换器。所有大型语言模型都使用这些转换器编码器或解码器块进行训练。因此,了解启动这一切的网络非常重要。希望本文能帮助所有希望深入了解LLM的人。

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

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

相关文章

【Quarkus技术系列】打造基于Quarkus的云原生微服务框架实践(1)

前提介绍 本系列文章主要讲解如何基于Quarkus技术搭建和开发"专为Kubernetes而优化的Java微服务框架"的入门和实践,你将会学习到如何搭建Quarkus微服务脚环境及脚手架,开发Quarkus的端点服务,系统和应用层级的配置介绍与Quarkus的…

一文读懂c++语言

一文读懂C语言 C的发展C的设计目标C的特性C的挑战 C的发展 C是一种通用的、高级的编程语言,它是C语言的扩展。C由Bjarne Stroustrup于1983年首次引入,并在之后的几十年中不断发展壮大。C被广泛应用于各种领域,包括系统开发、游戏开发、嵌入式…

概率图模型(Probabilistic Graphical Model,PGM)

概率图模型(Probabilistic Graphical Model,PGM),是一种用图结构来描述多元随机变量之间条件独立性的概率模型。它可以用来表示复杂的概率分布,进行有效的推理和学习,以及解决各种实际问题,如图…

传输控制协议TCP

目录 TCP报文格式 TCP的特点 TCP原理: 1.确认应答机制 2.超时重传机制 3.连接管理机制 建立连接 ​编辑关闭连接 4.滑动窗口机制 ​5.流量控制 6.拥塞控制 7.延迟应答 8.捎带应答 TCP报文格式 1.源端口号:发送端的哪一个端口发出的 2.目的端口号:接收端的哪一个端…

Jupyter Notebook 500 : Internal Server Error

1. 这个问题的根本原因在于: pygments 包 版本过高。 安装pygments 2.6.1 2.jupyter版本如下 如果某个版本有冲突,卸载了重新安装一下就行。 安装命令: pip install pygments 2.6.1 -i https://pypi.tuna.tsinghua.edu.cn/simple 另外…

NanoPi NEO移植LVGL8.3.5到1.69寸ST7789V屏幕

移植前准备 移植好fbtft屏幕驱动 参考链接:友善之臂NanoPi NEO利用fbtft驱动点亮1.69寸ST7789V2屏幕 获取源码 名称地址描述lvglhttps://github.com/lvgl/lvgl.gitlvgl-8.3.5lv_drivershttps://github.com/lvgl/lv_drivers.gitlv_drivers-6.1.1 创建工程目录 创…

UGUI组件EventTrigger用法

一.Unity编辑器中EventTrigger组件用法 1.添加事件类型 2.绑定gameObject指定组件的方法 3.方法执行逻辑 public class NewBehaviourScript : MonoBehaviour {public void PointerDown(){Debug.Log("Trigger PointerDown");} } 4.按下鼠标,绑定方法成功…

Spring Boot 统一功能处理(拦截器实现用户登录权限的统一校验、统一异常返回、统一数据格式返回)

目录 1. 用户登录权限校验 1.1 最初用户登录权限效验 1.2 Spring AOP 用户统⼀登录验证 1.3 Spring 拦截器 (1)创建自定义拦截器 (2)将自定义拦截器添加到系统配置中,并设置拦截的规则 1.4 练习:登录…

idea如何上传项目到github(超详细)

idea如何上传项目到github 1、IDEA配置2、项目上传到本地仓库2.1、创建本地git仓库2.2、Add操作2.3、Commit操作 3、项目上传到Github4、拿到登录Github的token 1、IDEA配置 File-Settings-VersionControl-Git Git的安装路径下bin目录下的git.exe可执行文件 可以直接点 Gene…

UVA-1601 万圣节后的早晨 题解答案代码 算法竞赛入门经典第二版

GitHub - jzplp/aoapc-UVA-Answer: 算法竞赛入门经典 例题和习题答案 刘汝佳 第二版 以三个点的当前位置作为状态,广度优先遍历,找到终点即为最短次数。 注意: 一次可以移动多个点,但是每个点只能移动一步。在同一次中&#xf…

tomcat服务七层搭建动态页面查看

一个服务器多实例复制完成 配置tomcat多实例的环境变量 vim /etc/profile.d/tomcat.sh配置tomcat1和tomcat2的环境变量 进入tomcat1修改配置 测试通信端口是否正常 连接正常 toncat 2 配置修改 修改这三个 端口配置修改完成 修改tomcat1 shudown 分别把启动文件指向tomcat1…

Spring Cloud 面试突击2

Spring Cloud 面试突击2 高并发:是一种系统运行过程中遇到的短时间大量的请求操作 响应时间: 吞吐量: QPS:数据库为维度 TPS 并发用户数 并发的维度:很多的 并发是不是达到的当前系统的瓶颈 缓存 &#xff08…

2023牛客暑期多校训练营7

Beautiful Sequence 贪心,二进制,构造 Cyperation 模拟 ,数学 We Love Strings 分块,二进制枚举,二进制容斥dp Writing Books 签到 根据相邻两个异或值B,因为前小于等于后,故从高到低遍历B的每一…

实验二十六、RC桥式正弦波振荡电路参数选择

一、题目 电路如图1所示。利用 Multisim 分析下列问题: (1)选择合适的 R f R_f Rf​ 和稳压管,使电路产生正弦波振荡,并观察起振过程; (2)调整电路参数,使输出电压峰值…

CH342/CH343/CH344/CH347/CH9101/CH9102/CH9103/CH9104 Linux串口驱动使用教程

CH343 Linux串口驱动 ch343ser_linux 支持USB转串口芯片 ch342/ch343/ch344/ch347/ch9101/ch9102/ch9103/ch9104等 ,同时该驱动配合ch343_lib库还提供了芯片GPIO接口的读写功能,内部EEPROM的信息配置和读取功能等。 芯片型号串口数量GPIO数量CH342F/K2C…

CCLINK IE FIELD BASIC转MODBUS-TCP网关cclink与以太网的区别

协议的不同,数据读取困难,这是很多生产管理系统的难题。但是现在,捷米JM-CCLKIE-TCP通讯网关,让这个问题变得非常简单。这款通讯网关可以将各种MODBUS-TCP设备接入到CCLINK IE FIELD BASIC网络中,连接到MODBUS-TCP总线…

cpu的架构

明天继续搞一下cache,还有后面的, 下面是cpu框架图 开始解释cpu 1.控制器 控制器又称为控制单元(Control Unit,简称CU),下面是控制器的组成 1.指令寄存器IR:是用来存放当前正在执行的的一条指令。当一条指令需要被执行时,先按…

模型性能的主要指标

主要参数 ROC 曲线和混淆矩阵都是用来评估分类模型性能的工具 ROC曲线(Receiver Operating Characteristic curve): ROC曲线描述了当阈值变化时,真正类率(True Positive Rate, TPR)和假正类率&#xff0…

Android Studio跳过Haxm打开模拟器

由于公司权限限制无法安装Haxm,这个时候我们可以试试Arm相关的镜像去跳过Haxm运行模拟器。解决方案:安装API27以下的Arm Image. #ifdef __x86_64__if (sarch "arm64" && apiLevel >28) {APANIC("Avds CPU Architecture %s i…

NPM与外部服务的集成(上)

目录 1、关于访问令牌 1.1 关于传统令牌 1.2 关于粒度访问令牌 2、创建和查看访问令牌 2.1 创建访问令牌 在网站上创建传统令牌 在网站上创建粒度访问令牌 使用CLI创建令牌 CIDR限制令牌错误 查看访问令牌 在网站上查看令牌 在CLI上查看令牌 令牌属性 1、关于访问令…