使用PyTorch从头实现Transformer

前言

  • 本文使用Pytorch从头实现Transformer,原论文Attention is all you need paper,最佳解读博客,学习视频
  • GitHub项目地址Some-Paper-CN。本项目是译者在学习长时间序列预测、CV、NLP和机器学习过程中精读的一些论文,并对其进行了中文翻译。还有部分最佳示例教程
  • 如果有帮助到大家,请帮忙点亮Star,也是对译者莫大的鼓励,谢谢啦~

SelfAttention

  • 整篇论文中,最核心的部分就是SelfAttention部分,SelfAttention模块架构图如下。
    请添加图片描述

规范化公式:
A t t e n t i o n ( Q , K , V ) = s o f t m a x ( Q K T d k ) V Attention(Q,K,V) = softmax(\frac{QK^T}{\sqrt{d_k}})V Attention(Q,K,V)=softmax(dk QKT)V

class SelfAttention(nn.Module):def __init__(self, embed_size, heads):super(SelfAttention, self).__init__()self.embed_size = embed_sizeself.heads = headsself.head_dim = embed_size // headsself.values = nn.Linear(self.head_dim, self.head_dim, bias=False)self.keys = nn.Linear(self.head_dim, self.head_dim, bias=False)self.queries = nn.Linear(self.head_dim, self.head_dim, bias=False)self.fc_out = nn.Linear(heads * self.head_dim, embed_size)def forward(self, values, keys, query, mask):# batch_sizeN = query.shape[0]value_len, keys_len, query_len = values.shape[1], keys.shape[1], query.shape[1]values = values.reshape(N, value_len, self.heads, self.head_dim)keys = keys.reshape(N, keys_len, self.heads, self.head_dim)queries = query.reshape(N, query_len, self.heads, self.head_dim)# values, keys, queries shape:(N, seq_len, heads, head_dim)values = self.values(values)keys = self.keys(keys)queries = self.queries(queries)energy = torch.einsum("nqhd,nkhd->nhqk", [queries, keys])# queries shape:(N, query_len, heads, heads_dim)# keys shape:(N, key_len, heads, heads_dim)# energy shape:(N, heads, query_len, key_len)if mask is not None:energy = energy.masked_fill(mask == 0 ,float("-1e20"))attention = torch.softmax(energy / (self.embed_size ** (1/2)), dim=3)# attention shape:(N, heads, seq_len, seq_len)out = torch.einsum("nhql,nlhd->nqhd", [attention, values]).reshape(N, query_len, self.heads*self.head_dim)# attention shape:(N, heads, query_len, key_len)# values shape:(N, values_len, heads, head_dim)# after einsum (N, query_len, heads, head_dim) then flatten lash two dimensionsout = self.fc_out(out)return out
  • 请注意values keysqueryLinear层是不带偏置的!
  • 上述代码中,较难理解的是torch.einsum(),爱因斯坦求和约定,nqhd,nkhd->nhqk可以理解为维度是 ( n , q , h , d ) (n,q,h,d) (n,q,h,d)的张量与 ( n , k , h , d ) (n,k,h,d) (n,k,h,d)的张量沿着维度 d d d相乘,得到维度 ( n , q , d , h ) (n,q,d,h) (n,q,d,h)重新排列后变成 ( n , h , q , k ) (n,h,q,k) (n,h,q,k)
  • 传入mask矩阵是因为每个句子的长度不一样,为了保证维度相同,在长度不足的句子后面使用padding补齐,而padding是不用计算损失的,所以需要mask告诉模型哪些位置需要计算损失。被mask遮掩的地方,被赋予无限小的值,这样在softmax以后概率就几乎为0了。
  • mask这个地方后面也会写到,如果不理解的话,先有这个概念,后面看完代码就会理解了。

TransformerBlock

  • 实现完SelfAttention,开始实现基本模块TransformerBlock,架构图如下。

请添加图片描述

class TransformerBlock(nn.Module):def __init__(self, embed_size, heads, dropout, forward_expansion):super(TransformerBlock, self).__init__()self.attention = SelfAttention(embed_size, heads)self.norm1 = nn.LayerNorm(embed_size)self.norm2 = nn.LayerNorm(embed_size)self.feed_forward = nn.Sequential(nn.Linear(embed_size, forward_expansion * embed_size),nn.ReLU(),nn.Linear(forward_expansion * embed_size, embed_size))self.dropout = nn.Dropout(dropout)def forward(self, value, key, query, mask):attention = self.attention(value, key, query, mask)# attention shape:(N, seq_len, emb_dim)x = self.dropout(self.norm1(attention + query))forward = self.feed_forward(x)out = self.dropout(self.norm2(forward + x))return out
  • Feed Forward部分由两层带偏置的Linear层和ReLU激活函数

  • 注意,Norm层是LayerNorm层,不是BatchNorm层。原因主要有:

    • 在进行BatchNorm操作时,同一个batch中的所有样本都会被考虑在内。这意味着一个样本的输出可能会受到同一批次其他样本的影响。然而,我们在处理文本数据时,通常希望每个样本(在此例中,是一个句子或一个句子段落)都是独立的。因此,LayerNorm是一个更好的选择,因为它只对单个样本进行操作。
    • 文本通常不是固定长度的,这就意味着每个batch的大小可能会有所不同。BatchNorm需要固定大小的batch才能正常工作,LayerNorm在这点上更为灵活。
  • forward_expansion是为了扩展embedding的维度,使Feed Forward包含更多参数量。

Encoder

  • 实现完TransformerBlock,就可以实现模型的Encoder部分,模块架构图如下。

请添加图片描述

class Encoder(nn.Module):def __init__(self,src_vocab_size,embed_size,num_layers,heads,device,forward_expansion,dropout,max_length):super(Encoder, self).__init__()self.embed_size = embed_sizeself.device = deviceself.word_embedding = nn.Embedding(src_vocab_size, embed_size)self.position_embedding = nn.Embedding(max_length, embed_size)self.layers = nn.ModuleList([TransformerBlock(embed_size,heads,dropout=dropout,forward_expansion=forward_expansion) for _ in range(num_layers)])self.dropout = nn.Dropout(dropout)def forward(self, x, mask):N, seq_length = x.shapepositions = torch.arange(0, seq_length).expand(N, seq_length).to(self.device)# positions shape:(N, seq_len)out = self.dropout(self.word_embedding(x) + self.position_embedding(positions))# out shape:(N, seq_len, emb_dim)for layer in self.layers:out = layer(out, out, out, mask)return out
  • 为了更好的理解positions并没有按照论文中使用sincos构造,但这一部分并不困难,后面大家有兴趣可以进行替换。

  • positions是为句子的每个字从0开始编号,假设有2个句子,第一个句子有3个字,第二个句子有4个字,即positions = [[0,1,2],[0,1,2,3]]

  • positionsx进入embedding层后相加,然后进入dropout

  • 因为TransformerBlock可能有多个串联,所以使用ModuleList包起来

  • 注意残差连接部分的操作。

DecoderBlock

  • 实现完Encoder部分,整个模型就已经完成一半了,接下来实现Decoder基本单元DecoderBlock,模块架构图如下。

请添加图片描述

class DecoderBlock(nn.Module):def __init__(self, embed_size, heads, forward_expansion, dropout, device):super(DecoderBlock, self).__init__()self.attention = SelfAttention(embed_size, heads)self.norm = nn.LayerNorm(embed_size)self.transformer_block = TransformerBlock(embed_size, heads, dropout, forward_expansion)self.dropout = nn.Dropout(dropout)def forward(self, x, value, key, src_mask, trg_mask):attention = self.attention(x, x, x, trg_mask)query = self.dropout(self.norm(attention + x))out = self.transformer_block(value, key, query, src_mask)return out
  • 注意!这里有两个Attention模块,首先输入x要进入Masked Attention得到query,然后与Encoder部分的输出组成新的v,k,q
  • 第二部分是基本单元transformer_block,可以直接调用。
  • 注意残差连接部分即可。

Decoder

  • 实现完Decoder基本单元DecoderBlock后,就可以正式开始实现Decoder部分了,模块架构图如下。

请添加图片描述

class Decoder(nn.Module):def __init__(self,trg_vocab_size,embed_size,num_layers,heads,forward_expansion,dropout,device,max_length,):super(Decoder, self).__init__()self.device = deviceself.word_embedding = nn.Embedding(trg_vocab_size, embed_size)self.position_embedding = nn.Embedding(max_length, embed_size)self.layers = nn.ModuleList([DecoderBlock(embed_size, heads, forward_expansion, dropout, device)for _ in range(num_layers)])self.fc_out = nn.Linear(embed_size, trg_vocab_size)self.dropout = nn.Dropout(dropout)def forward(self, x, enc_out, src_mask, trg_mask):N, seq_length = x.shapepositions = torch.arange(0, seq_length).expand(N, seq_length).to(self.device)x = self.dropout(self.word_embedding(x) + self.position_embedding(positions))for layer in self.layers:x = layer(x, enc_out, enc_out, src_mask, trg_mask)out = self.fc_out(x)return out
  • Decoder部分的embedding部分和Encoder部分差不多,word_embeddingposition_embedding相加进入dropout层。
  • 基本单元DecoderBlock会重复多次,用ModuleList包裹。
  • enc_outEncoder部分的输出,变成了valuekey

Transformer

  • 在实现完EncoderDecoder后,就可以实现整个Transformer结构了,架构图如下。
    请添加图片描述
class Transformer(nn.Module):def __init__(self,src_vocab_size,trg_vocab_size,src_pad_idx,trg_pad_idx,embed_size=256,num_layers=6,forward_expansion=4,heads=8,dropout=0,device='cpu',max_length=64):super(Transformer, self).__init__()self.encoder = Encoder(src_vocab_size,embed_size,num_layers,heads,device,forward_expansion,dropout,max_length)self.decoder = Decoder(trg_vocab_size,embed_size,num_layers,heads,forward_expansion,dropout,device,max_length)self.src_pad_idx = src_pad_idxself.trg_pad_idx = trg_pad_idxself.device = devicedef mask_src_mask(self, src):src_mask = (src != self.src_pad_idx).unsqueeze(1).unsqueeze(2)# src_mask shape:(N, 1, 1, src_len)return src_mask.to(self.device)def mask_trg_mask(self, trg):N, trg_len = trg.shapetrg_mask = torch.tril(torch.ones((trg_len, trg_len))).expand(N, 1, trg_len, trg_len)# trg_mask shape:(N, 1, 1, trg_len)return trg_mask.to(self.device)def forward(self, src, trg):src_mask = self.mask_src_mask(src)trg_mask = self.mask_trg_mask(trg)enc_src = self.encoder(src, src_mask)# enc_src shape:(N, seq_len, emb_dim)out = self.decoder(trg, enc_src, src_mask, trg_mask)return out
  • 需要注意的是输入的mask构造方法mask_src_mask,和输出的mask构造方法mask_trg_maskmask_src_mask是对输入的padding部分进行maskmask_trg_mask根据输出构建下三角矩阵想象一下,当模型预测第一个字的时候,后面的所有内容都是不可见的,当模型预测第二个字的时候,仅第一个字可见,后面的内容都不可见…

检验

  • 构建完成后,使用一个简单的小例子检验一下模型是否可以正常运行。
if __name__ == "__main__":device = 'cpu'# x shape:(N, seq_len)x = torch.tensor([[1, 5, 6, 4, 3, 9, 5, 2, 0],[1, 8, 7, 3, 4, 5, 6, 7, 2]]).to(device)trg = torch.tensor([[1, 7, 4, 3, 5, 9, 2, 0],[1, 5, 6, 2, 4, 7, 6, 2]]).to(device)src_pad_idx = 0trg_pad_idx = 0src_vocab_size = 10trg_vocab_size = 10model = Transformer(src_vocab_size, trg_vocab_size, src_pad_idx, trg_pad_idx).to(device)out = model(x, trg[:, :-1])print(out.shape)
  • 输出:(2, 7, 10),完整代码放在GitHub项目Some-Paper-CN中。

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

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

相关文章

node.js中path模块-路径处理,语法讲解

node中的path 模块是node.js的基础语法,实际开发中,我们通过使用 path 模块来得到绝对路径,避免因为相对路径带来的找不到资源的问题。 具体来说:Node.js 执行 JS 代码时,代码中的路径都是以终端所在文件夹出发查找相…

基于Springboot的滑雪场管理系统(有报告)。Javaee项目,springboot项目。

演示视频: 基于Springboot的滑雪场管理系统(有报告)。Javaee项目,springboot项目。 项目介绍: 采用M(model)V(view)C(controller)三层体系结构&a…

搜好货API接口:快速获取商品列表的利器

搜好货商品列表API接口允许开发者根据关键字搜索并获取相关的商品列表数据。接口支持多种参数配置,可以根据需求灵活调整搜索条件和结果返回格式。 点击获取key和secret API接口请求说明 请求地址:https://api.souhaohuo.com/goods/search请求方法&…

速卖通关键字搜索API接口:快速获取商品列表的利器

速卖通关键字搜索API接口允许开发者根据用户输入的关键字进行商品搜索,并返回与之相关的商品列表。通过调用该接口,您可以快速获取与关键字匹配的商品信息,包括商品标题、价格、图片等,为您的电商业务提供有力支持。 三、API接口…

以信息挖掘为关键技术的智慧校园建设

随着信息技术的快速发展,数据信息资源以井喷的姿态涌现。数据信息的大量涌现给人们带来丰富的数据信息资源,但面对海量的信息资源时,加大了人们对有效信息资源获取的难度,数据挖掘技术正是这一背景下的产物,基于数据挖…

【Redis】Redis安装、配置、卸载使用可视化工具连接Redis

文章目录 1.前置条件2.安装Redis2.1下载Redis安装包并解压2.2在redis目录下执行make命令2.3修改Redis配置文件2.4启动Redis服务2.5连接redis服务 3.Redis卸载4.使用可视化工具连接Redis 1.前置条件 Linux操作系统需要要是64位.如果不清楚自己Linux上是多少位的,可以使用以下命…

C语言之详细讲解文件操作(抓住文件操作的奥秘)

什么是文件 与普通文件载体不同,文件是以硬盘为载体存储在计算机上的信息集合,文件可以是文本文档、图片、程序等等。文件通常具有点三个字母的文件扩展名,用于指示文件类型(例如,图片文件常常以KPEG格式保存并且文件…

一文了解复杂度

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 前言一、算法效率二、时间复杂度1.定义2.大O的渐进表示法3.一般常见复杂度4.实例 三、空间复杂度1.定义2.空间复杂度计算3.实例 总结 前言 计算复杂性理论&#xf…

Redis的持久化方法,各自优缺点,怎么选择?

持久化: redis基于内存是数据库,内容存到内存中,也可以存到硬盘中,这个过程就叫持久化。有两种方案,RDB和AOP两种。 RDB RDB持久化就是把当前进程数据生成快照保存到硬盘的过程RDB文件是⼀个压缩的二进制文件&#…

VisualGLM-6B微调(V100)

Visualglm-6b-CSDN博客文章浏览阅读1.3k次。【官方教程】XrayGLM微调实践,(加强后的GPT-3.5)能力媲美4.0,无次数限制。_visualglm-6bhttps://blog.csdn.net/u012193416/article/details/131074962?ops_request_misc%257B%2522req…

【经典论文阅读1】FM模型——搜推算法里的瑞士军刀

全文由『说文科技』原创出品,文章同步更新于公众号『说文科技』。版权所有,翻版必究。 FM模型发表于2010年,它灵活好用且易部署。作者行文极其流畅,作者首先对要处理的问题进行介绍,接着作者提出FM模型,这…

Vue单页面应用和多页面应用的区别

概念: SPA单页面应用(SinglePage Web Application),指只有一个主页面的应用,一开始只需要加载一次js、css等相关资源。所有内容都包含在主页面,对每一个功能模块组件化。单页应用跳转,就是切换…

笔试强训-day17_T2 十字爆破

一、题目链接 十字爆破 二、题目描述 牛牛在玩一个游戏: 一共有n行m列共nm个方格,每个方格中有一个整数。 牛牛选择一个方格,可以得到和这个方格同行、同列的所有数之和的得分。 例如:对于一个22的方格: 1 2 3 4 牛牛…

Python字符串常用方法(全网最细,仅此一份)

🥇作者简介:CSDN内容合伙人、新星计划第三季Python赛道Top1 🔥本文已收录于Python系列专栏: 👉Python从入门到精通 💬订阅专栏后可私信博主进入Python学习交流群,进群可领取Python180G全栈视频教程以及Python相关电子书合集 😊私信未回可以加V:hacker0327 备注P…

文心一言 VS 讯飞星火 VS chatgpt (251)-- 算法导论18.2 4题

四、假设关键字 {1,2,…,n} 被插入一棵最小度数为 2 的空 B 树中,那么最终的B树有多少个结点?如果要写代码,请用go语言。 文心一言: B树(B-tree)是一种自平衡的树&…

商务分析方法与工具(一):Python的趣味快捷-运算符、表达式与内置对象

Tips:"分享是快乐的源泉💧,在我的博客里,不仅有知识的海洋🌊,还有满满的正能量加持💪,快来和我一起分享这份快乐吧😊! 喜欢我的博客的话,记得…

组合数问题

1.直接用递推&#xff1a; 下面是AC代码“&#xff1a; #include<bits/stdc.h> using namespace std; const int N2010,mod1e97; int a[N][N]; void init() {for(int i0;i<N;i){for(int j0;j<i;j){if(j0) a[i][j]1;else a[i][j](a[i-1][j]a[i-1][j-1])%mod;}} } i…

结构体介绍(1)

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 结构体&#xff08;1&#xff09; 前言一、struct介绍结构体声明结构体创建和初始化struct 的特殊声明结构体自引用 二、结构体内存对齐2.1.对齐规则 总结 前言 结构体 属于…

复习结构体

1.怎样使用结构体变量2.关于 . 和-> 的用法 pst -> age 会被计算机内部转化为&#xff08;*pst).age&#xff0c;这就是 ->的含义&#xff0c;是一种硬性规定 像 float 类型和 double 类型 &#xff0c;由于编码原因&#xff0c;一般都不能被精确存储 像 数字66.6在C…

【快速入门Linux】10_Linux命令—Vi编辑器

文章目录 一、vi 简介1.1 vi1.2 vim1.3查询软连接命令&#xff08;知道&#xff09; 二、打开和新建文件&#xff08;重点&#xff09;2.1 打开文件并且定位行2.2 异常处理 三、vi三种工作模式&#xff08;重点&#xff09;3.1 末行模式-命令 四、常用命令4.0 命令线路图4.1 移…