使用Pytorch从零开始构建Transformer

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

要详细了解 Transformer 模型,请访问这两篇文章:

  1. All you need to know about ‘Attention’ and ‘Transformers’ — In-depth Understanding — Part 1
  2. All you need to know about ‘Attention’ and ‘Transformers’ — In-depth Understanding — Part 2

要构建 Transformer 模型,我们将按照以下步骤操作:

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

让我们首先导入必要的库和模块。

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

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

多头注意力

在这里插入图片描述
多头注意力机制计算序列中每对位置之间的注意力。它由多个“注意力头”组成,捕获输入序列的不同方面。

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.Module 并实现了位置明智的前馈网络。该类使用两个线性变换层和一个 ReLU 激活函数进行初始化。前向方法依次应用这些变换和激活函数来计算输出。此过程使模型能够在进行预测时考虑输入元素的位置。

位置编码

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

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 分别计算偶数和奇数索引的正弦和余弦值。前向方法通过将存储的位置编码值添加到输入张量来计算位置编码,从而使模型能够捕获输入序列的位置信息。

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

编码器层

在这里插入图片描述
编码器层由多头注意力层、位置前馈层和两个层归一化层组成。

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

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

解码层

在这里插入图片描述
解码器层由两个多头注意力层、一个位置前馈层和三个层归一化层组成。

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

DecoderLayer 使用输入参数和组件进行初始化,例如用于屏蔽自注意力和交叉注意力的 MultiHeadAttention 模块、PositionWiseFeedForward 模块、三层归一化模块和 dropout 层。

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

计算屏蔽自注意力输出并将其添加到输入张量,然后进行 dropout 和层归一化。
计算解码器和编码器输出之间的交叉注意力输出,并将其添加到归一化屏蔽自注意力输出中,然后进行 dropout 和层归一化。
计算位置前馈输出并将其与归一化交叉注意力输出相结合,然后进行 dropout 和层归一化。
返回处理后的张量。
这些操作使解码器能够基于输入和编码器输出生成目标序列。

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

Transformer Model

在这里插入图片描述

将它们合并在一起:

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 类组合了前面定义的模块来创建完整的 Transformer 模型。在初始化期间,Transformer 模块设置输入参数并初始化各种组件,包括源序列和目标序列的嵌入层、PositionalEncoding 模块、用于创建堆叠层的 EncoderLayer 和 DecoderLayer 模块、用于投影解码器输出的线性层以及 dropout 层。

generate_mask 方法为源序列和目标序列创建二进制掩码,以忽略填充标记并防止解码器关注未来的标记。forward方法通过以下步骤计算Transformer模型的输出:

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

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

准备样本数据

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

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 中从头开始构建一个简单的 Transformer。所有大型语言模型都使用这些 Transformer 编码器或解码器块进行训练。因此,了解基础Transformer网络非常重要。希望这篇文章对所有想要深入研究大语言模型(LLM)的人有所帮助。

参考文献

Attention is all you need
A. Vaswani, N. Shazeer, N. Parmar, J. Uszkoreit, L. Jones, A. Gomez, {. Kaiser, and I. Polosukhin. Advances in Neural Information Processing Systems , page 5998–6008. (2017)

博文译自Arjun Sarkar 的博客。

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

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

相关文章

Centos7 mysql8.2.0

一、下载 选择社区开源版 二、解压安装 解压 tar -xvf mysql.tar查看是否存在mariadb,如果存在卸载,可能会有冲突 //查看mariadb rpm -qa|grep mariadb //存在即卸载 rpm -e --nodeps mariadb-libs 开始安装 //需要安装解压后其中几个rpm,包有依赖关系…

python变量、常量、数据类型

一、变量 变量是存储在内存中的值,这就意味着在创建变量时会在内存中开辟一个空间。 基于变量的数据类型,解释器会分配指定内存,并决定什么数据可以被存储在内存中。 因此,变量可以指定不同的数据类型,这些变量可以…

C语言——利用函数递归,编写函数不允许创建临时变量,求字符串长度

#define _CRT_SECURE_NO_WARNINGS 1#include<stdio.h>int my_strlen(char* str) {if(*str ! \0)return 1my_strlen(str1);elsereturn 0; }int main() {char arr[] "hello";int len my_strlen(arr); //arr是数组&#xff0c;数组传参&#xff0c;传过去的是第…

Android和iOS应用程序加固方法详解:混淆、加壳、数据加密、动态加载和数字签名实现

目录 Android和iOS应用程序加固方法详解&#xff1a;混淆、加壳、数据加密、动态加载和数字签名实现 APP 加固方式 iOS APP加固代码实现 打开要处理的IPA文件 设置签名使用的证书和描述文件 开始ios ipa重签名 APP 加固方式 iOSAPP 加固是优化 iOS安全性的一种方法&…

2020年09月 Scratch(三级)真题解析#中国电子学会#全国青少年软件编程等级考试

Scratch等级考试(1~4级)全部真题・点这里 一、单选题(共25题,每题2分,共50分) 第1题 执行下面程序,屏幕上最多会看到多少个苹果? A:10个 B:11个 C:1个 D:无法确定 答案:B 第2题 关于下面程序,说法正确的是 ? A:执行 后,马上执行

ChatGPT重磅升级!集简云支持GPT4 Turbo Vision, GPT4 Turbo, Dall.E 3,Whisper等最新模型

在11月7日凌晨&#xff0c;OpenAI全球开发者大会宣布了 GPT-4的一次大升级&#xff0c;推出了 GPT-4 Turbo号称为迄今为止最强的大模型。 此次GPT-4的更新和升级在多个方面显示出强大的优势和潜力。为了让集简云用户能快速体验新模型的能力&#xff0c;我们第一时间整理了大会发…

关于自学\跳槽\转行做网络安全行业的一些建议

很好&#xff0c;如果你是被题目吸引过来的&#xff0c;那请看完再走&#xff0c;还是有的~ 为什么写这篇文章 如何自学入行&#xff1f;如何小白跳槽&#xff0c;年纪大了如何转行等类似问题 &#xff0c;发现很多人都有这样的困惑。下面的文字其实是我以前的一个回答&#…

【EI会议投稿】第九届电子技术和信息科学国际学术会议(ICETIS 2024)

第九届电子技术和信息科学国际学术会议&#xff08;ICETIS 2024&#xff09; The 9th International Conference on Electronic Technology andInformation Science&#xff08;ICETIS 2024&#xff09; ICETIS会议始于2016年&#xff0c;先后吸引众多来自国内外高等院校、科…

浏览器没收到返回,后端也没报错,php的json_encode问题bug

今天网站遇到个问题&#xff0c;后端返回异常&#xff0c;但是浏览器状态码200&#xff0c;但是看不到结果。经过排查发现&#xff0c;我们在返回结果的时候使用了json_encode返回给前端&#xff0c;结果里面的字符编码异常&#xff0c;导致json_encode异常&#xff0c;但是php…

来聊聊JVM中的类加载过程以及双亲委派模型(学习Java必知内容)

文章目录 1. 类加载过程加载验证准备解析初始化 2. 双亲委派模型一个类的加载流程双亲委派模型的优点 总结 1. 类加载过程 在整个 JVM 执行过程中, 和我们程序员关系最密切的就是类加载的过程, 所以接下来我们来看下类加载的执行流程. 对于一个类来说, 它的生命周期是这样的:…

洛谷P1219 [USACO1.5] 八皇后【n皇后问题】【深搜+回溯 经典题】【附O(1)方法】

P1219 [USACO1.5] 八皇后 Checker Challenge 前言题目题目描述输入格式输出格式样例 #1样例输入 #1样例输出 #1 提示题目分析注意事项 代码深搜回溯打表 后话额外测试用例样例输入 #2样例输出 #2 王婆卖瓜 题目来源 前言 也是说到做到&#xff0c;来做搜索的题&#xff08;虽…

外汇天眼:外汇市场中的点差是什么? 又该怎么计算呢?

今天为大家揭开外汇点差的神秘面纱&#xff0c;了解这一外汇交易的核心概念。 定义 外汇点差&#xff0c;简单来说&#xff0c;就是外汇市场上买卖双方报价的差异。 每一笔交易由买卖报价中高低不同的部分构成&#xff0c;高出的部分是买方的盈利&#xff0c;低出的部分则是卖…

一文掌握 Spring Boot 常用注解,保姆级整理,建议收藏!

亲兄弟篇&#xff1a; SpringBoot注解大全&#xff08;超详细&#xff09;_Maiko Star的博客-CSDN博客 一、SpringBoot常用注解 二、Bean处理注解 2.1 Resource 依赖注入&#xff0c;自动导入标注的对象到当前类中&#xff0c;比如我们的 Controller 类通常要导入 Service 类…

【Unity】EventSystem.current.IsPointerOverGameObject()对碰撞体起作用

本来我是用 EventSystem.current.IsPointerOverGameObject()来检测是否点击在UI上的&#xff0c;但是发现&#xff0c;他对我的碰撞体也是返回ture,研究半天。。。。找不出问题&#xff0c;然后发现我的相机上挂载了PhysicsRaycaster&#xff0c;去掉之后就好了&#xff0c;至于…

王道操作系统大题汇总(纯手写版,思路过程详细)

文章目录 前言&#xff1a;一、计算机系统概述二、进程与线程三、内存管理四、文件管理五、输入/输出&#xff08;I/0&#xff09;管理 前言&#xff1a; 本文为笔者自用操作系统大题复习&#xff0c;大家可以作为学习的参考&#xff0c;文章只收录操作系统常考大题&#xff0…

Django之cookie和session

文章目录 Cookie的介绍Cookie的由来什么是CookieCookie原理Cookie覆盖浏览器查看Cookie 在Django中操作Cookie设置Cookie查询浏览器携带的Cookie删除Cookie Cookie校验登录sessionSession的由来Session设置查看、更新Session值删除Session值Seesion的其他方法Session的其他配置…

安全牛《数据分类分级自动化建设指南》发布|美创入选代表厂商,分享智能化探索

近日&#xff0c;安全牛发布《数据分类分级自动化建设指南》研究报告&#xff0c;对数据分类分级的主要技术、实施要点、选型指导、发展趋势等展开深入探讨&#xff0c;为各行业数据分类分级自动化工作落地提供帮助与指引。 美创科技被列为代表推荐厂商&#xff0c;落地案例—农…

C++初阶 | [五] 内存管理

摘要&#xff1a;new and delete&#xff0c;定位new&#xff0c;&#xff08;C内存管理的方式&#xff09;&#xff0c;malloc/free和new/delete的区别&#xff0c;内存泄漏 关于内存&#xff1a; 栈又叫堆栈——非静态局部变量/函数参数/返回值等等&#xff0c;栈是向下增长…

JAVA爬虫2 - Jsoup解析、对接MySQL、多线程爬虫、json库使用

官网:https://jsoup.org/download Jsoup是一款基于Java的HTML解析器,它可以方便地从网页中抓取和解析数据。它的主要作用是帮助开 发者处理HTML文档,提取所需的数据或信息。下面介绍几个常用的API: 选择器(Selector)API:用于根据CSS选择器语法选择HTML元素。 属性(Attribute…

深入了解前馈网络、CNN、RNN 和 Hugging Face 的 Transformer 技术!

一、说明 本篇在此对自然语言模型做一个简短总结&#xff0c;从CNN\RNN\变形金刚&#xff0c;和抱脸的变形金刚库说起。 二、基本前馈神经网络&#xff1a; 让我们分解一个基本的前馈神经网络&#xff0c;也称为多层感知器&#xff08;MLP&#xff09;。此代码示例将&#xff1…