使用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.xdnf.cn/news/1411171.html

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

相关文章

BUUCTF---misc---被偷走的文件

1、题目描述 2、下载附件,是一个流量包,拿去wireshark分析,依次点开流量,发现有个流量的内容显示flag.rar 3、接着在kali中分离出压缩包,使用下面命令,将压缩包,分离出放在out3文件夹中 4、在文…

【docker】常用的Docker编排和调度平台

常用的Docker编排和调度平台 Kubernetes (K8s): Kubernetes是目前市场上最流行和功能最全面的容器编排和调度平台。它由Google开发并开源,现由CNCF(云原生计算基金会)维护。Kubernetes设计用于自动化容器部署、扩展和管理,支持跨…

《金融研究》:普惠金融改革试验区DID工具变量数据(2012-2023年)

数据简介:本数据集包括普惠金融改革试验区和普惠金融服务乡村振兴改革试验区两类。 其中,河南兰考、浙江宁波、福建龙岩和宁德、江西赣州和吉安、陕西铜川五省七地为普惠金融改革试验区。山东临沂、浙江丽水、四川成都三地设立的是普惠金融服务乡村振兴…

C#知识|事件集中响应,多个按钮关联同一事件(实例练习)

哈喽,你好,我是雷工! 本节学习窗体Controls集合、控件事件的统一关联及如何优化重复代码。 01 事件集中响应 原理:就是相同的控件,可以关联同一个事件响应方法。 02 示例演示 2.1、示例功能 该示例实现窗体中选择…

Idea 自动生成测试

先添加测试依赖&#xff01;&#xff01; <!--Junit单元测试依赖--><dependency><groupId>org.junit.jupiter</groupId><artifactId>junit-jupiter</artifactId><version>5.9.1</version><scope>test</scope><…

Sass语法---sass的安装和引用

什么是Sass Sass&#xff08;英文全称&#xff1a;Syntactically Awesome Stylesheets&#xff09; Sass 是一个 CSS 预处理器。 Sass 是 CSS 扩展语言&#xff0c;可以帮助我们减少 CSS 重复的代码&#xff0c;节省开发时间。 Sass 完全兼容所有版本的 CSS。 Sass 扩展了…

每日OJ题_贪心算法二④_力扣2418. 按身高排序

目录 力扣2418. 按身高排序 解析代码 力扣2418. 按身高排序 2418. 按身高排序 难度 简单 给你一个字符串数组 names &#xff0c;和一个由 互不相同 的正整数组成的数组 heights 。两个数组的长度均为 n 。 对于每个下标 i&#xff0c;names[i] 和 heights[i] 表示第 i 个…

C#简单创建DLL文件并调用

DLL是Dynamic Link Library的缩写&#xff0c;意为动态链接库。动态链接库其实是由编译器将一系列相关的类型编译、链接并封装成一个独立的文件&#xff0c;与对其进行调用的程序分开。这样一个独立的文件相当于程序的一个模块&#xff0c;如果需要对程序进行更新&#xff0c;只…

如何完全卸载QT

第一步&#xff0c;用QT自带的软件卸载QT 第二步&#xff0c;卸载下面路径的所有QT配置 C:用户/(你的用户)/AppData/Local/目录下所有与Qt相关内容 C:用户/(你的用户)/AppData/Local/Temp/所有与Qt相关内容 C:用户/(你的用户)/AppData/Roaming/所有与Qt相关内容

华为机考入门python3--(19)牛客19- 简单错误记录

分类&#xff1a;字符串 知识点&#xff1a; 分割字符串 my_str.split(\\) 字符串只保留最后16位字符 my_str[-16:] 列表可以作为队列、栈 添加元素到第一个位置 my_list.insert(0, elem) 增加元素到最后一个位置 my_list.append(elem) 删除第一个 my_list.pop(0)…

ElasticSearch01(ES简介,安装ES,操作索引,操作文档,RestAPI)【全详解】

目录 一、ES简介 1. 数据库查询的问题 2. ES简介 1 ElasticSearch简介 2 ElasticSearch发展 3. 倒排索引【面试】 1 正向索引 2 倒排索引 4. ES和MySql 5. 小结 二、安装ES 1. 方式1:使用docker安装 1 准备工作 2 创建ElasticSearch容器 3 给ElasticSearch配置i…

从零开始学AI绘画,万字Stable Diffusion终极教程(一)

【第1期】SD入门 2022年8月&#xff0c;一款叫Stable Diffusion的AI绘画软件开源发布&#xff0c;从此开启了AIGC在图像上的爆火发展时期 率先学会SD的人&#xff0c;已经挖掘出了越来越多AI绘画有趣的玩法 从开始的AI美女、线稿上色、真人漫改、头像壁纸 到后来的AI创意字、AI…

ARP欺骗使局域网内设备断网

一、实验准备 kali系统&#xff1a;可使用虚拟机软件模拟 kali虚拟机镜像链接&#xff1a;https://www.kali.org/get-kali/#kali-virtual-machines 注意虚拟机网络适配器采用桥接模式 局域网内存在指定断网的设备 二、实验步骤 打开kali系统命令行&#xff1a;ctrlaltt可快…

Docker 加持的安卓手机:随身携带的知识库(一)

这篇文章聊聊&#xff0c;如何借助 Docker &#xff0c;尝试将一台五年前的手机&#xff0c;构建成一个随身携带的、本地化的知识库。 写在前面 本篇文章&#xff0c;我使用了一台去年从二手平台购入的五年前的手机&#xff0c;K20 Pro。 为了让它能够稳定持续的运行&#xf…

MySQL技能树学习——数据库组成

数据库组成&#xff1a; 数据库是一个组织和存储数据的系统&#xff0c;它由多个组件组成&#xff0c;这些组件共同工作以确保数据的安全、可靠和高效的存储和访问。数据库的主要组成部分包括&#xff1a; 数据库管理系统&#xff08;DBMS&#xff09;&#xff1a; 数据库管理系…

【LeetCode刷题】410. 分割数组的最大值

1. 题目链接2. 题目描述3. 解题方法4. 代码 1. 题目链接 410. 分割数组的最大值 2. 题目描述 3. 解题方法 题目中提到的是某个和的最大值是最小的&#xff0c;这种题目是可以用二分来解决的。 确定区间&#xff0c;根据题目的数据范围&#xff0c;可以确定区间就是[0, 1e9]…

C语言/数据结构——每日一题(分割链表)

一.前言 今天在LeetCode觉得很不错&#xff0c;想和大家们一起分享这道链表题——分割链表&#xff1a;https://leetcode.cn/problems/partition-list-lcci废话不多说&#xff0c;让我们直接进入正题吧。 二.正文 1.1题目描述 1.2题目分析 大致思路&#xff1a;我们可以通过…

如何使用 GPT API 从 PDF 出版物导出研究图表?

原文地址&#xff1a;how-to-use-gpt-api-to-export-a-research-graph-from-pdf-publications 揭示内部结构——提取研究实体和关系 2024 年 2 月 6 日 介绍 研究图是研究对象的结构化表示&#xff0c;它捕获有关实体的信息以及研究人员、组织、出版物、资助和研究数据之间的关…

从零开始搭建Springboot项目脚手架1:新建项目

1、技术栈 SpringBoot 3.2.5&#xff1a; 2、 新建项目 使用SpringInitializr 选择Lombok、Configuration Processor、Spring Web&#xff0c;同时IDEA也要安装Lombok插件 删除多余的Maven目录、Maven文件&#xff0c;把HELP.md改成README.md。 当然前提是已经安装好Maven和配…

Linux——socket编程之tcp通信

前言 前面我们学习socket的udp通信&#xff0c;了解到了socket的概念与udp的实现方法&#xff0c;今天我们来学习一下面向连接的tcp通信。 一、tcp套接字创建 UDP和TCP都是通过套接字&#xff08;socket&#xff09;来实现通信的&#xff0c;因此TCP也得使用socket()接口创建…