当前位置: 首页 > news >正文

第TR3周:Pytorch复现Transformer

  •          🍨 本文为🔗365天深度学习训练营中的学习记录博客
  •          🍖 原作者:K同学啊

一、Transformer与Seq2seq

1.Transformer架构直接基于Transformer模型。不依赖RNN,LSTM和CNN网络架构。

2. Seq2Seq也用于处理序列数据,通常由两个主要部分组成:编码器和解码器。编码器负责将输入序列编码为固定大小的向量,而解码器则使用此向量生成输出序列。

3.Transformer模型主要由编码器和解码器组成,它们由自注意力层和全连接前馈网络组成。它使用注意力机制来捕捉输入序列中不同位置之间的依赖关系,同时通过多头注意力来提高模型的表达能力。

二、Transformer宏观结构

1.Transformer可以看作是seq2seq模型的一种,因此,先从seq2seq的角度对Transformer进行宏观结构的学习。以机器翻译任务为例,先将Transformer看作一个黑盒,黑盒的输入是法语文本序列,输出是英语文本序列。

2.主要由编码器和解码器两部分组成。编码部分由多层编码器(Encoder)组成。解码部分也是由多层的解码器(Decoder)组成。每层编码器、解码器网络结构是-样的,但是不同层编码器、解码器网络结构不共享参数。 

3.其中,单层编码器主要由自注意力层(Self-Attention Layer)和全连接前馈网络(Feed ForwardNeural Network,FFNN)与组成,如下图所示: 

4.其中,解码器在编码器的自注意力层和全连接前馈网络中间插入了一个Encoder-Decoder Attention层,这个层帮助解码器聚焦于输入序列最相关的部分。 

三、复现Transformer
import math
import torch
import torch.nn as nn
device = torch.device("cpu")
# device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
device

device(type='cpu')  

1.shape变化类 
import torch.nn as nnclass Transpose(nn.Module):def init_(self,*dims, contiguous=False):super(Transpose,self).init_() #确保正确调用父类的构造函数self.dims=dims #存储交换的维度self.contiguous= contiguous #存储是否需要返回连续内存布局的标志def forward(self,x):#如果需要连续内存布局,调用.contiguous()方法来确保if self.contiguous:return x.transpose(*self.dims).contiguous()else:return x.transpose(*self.dims)
2.Scaled Dot-Product Attention多头注意力机制
import torch.nn.functional as F
class ScaledDotProductAttention(nn.Module):def __init__ (self,d_k:int):super(ScaledDotProductAttention, self).__init__()#确保正确调用父类的构造函数#初始化函数,dk表示键/查询向量的维度self.d_k=d_kdef forward(self,q,k,v,mask = None):#计算注意力机制的核心步骤#q:查询向量,k:键向量,v:值向量,mask:用于遮挡部分位置的掩码#计算查询和键的点积,得到相似度分数。这里的k在上层已经完成了转置scores =torch.matmul(q,k)#scores形状:[bs,n heads,d k,q len]#缩放分数,防止数值过大导致softmax梯度过小scores=scores/(self.d_k**0.5)#应用掩码(如果提供了掩码),将被掩码位置的分数设为一个极小值if mask is not None:scores.masked_fill(mask,-1e9)#对分数应用softmax,得到注意力权重attn =F.softmax(scores,dim=-1)# attn形状:[bs,n heads, q len, q len]#根据注意力权重加权求和值向量context =torch.matmul(attn,v)# context形状:[bs,n heads, q len, d_v]return context
2.多头注意力机制
class MultiHeadAttention(nn.Module):def __init__ (self,d_model,n_heads):"""多头注意力机制的初始化函数参数:d_model:输入特征的维度n_heads:注意力头的数量d_k:每个头中键/查询向量的维度d_v:每个头中值向量的维度"""super(MultiHeadAttention,self).__init__() #确保正确调用父类的构造函数assert d_model %n_heads ==0, f"d_model({d_model}) 必须被 n_heads({n_heads})整除"self.d_k= d_model//n_headsself.d_v= d_model//n_headsself.n_heads = n_heads#定义用于生成查询、键和值的线性层self.W_Q=nn.Linear(d_model,self.d_k*n_heads, bias=False)self.W_K=nn.Linear(d_model,self.d_k*n_heads, bias=False)self.W_V=nn.Linear(d_model,self.d_v*n_heads, bias=False)#用于将多头输出的拼接结果投影回输入特征维度的线性层self.W_0=nn.Linear(n_heads *self.d_v,d_model, bias=False)self.attention = ScaledDotProductAttention(self.d_k)def forward(self,Q,K,V,mask = None):#前向传播函数,计算多头注意力#0:查询向量,K:键向量,V:值向量,mask:用于遮挡部分位置的掩码bs =Q.size(0) #获取批量大小#将输入通过线性层生成多头的查询、键和值,并对其进行维度变换q_s = self.W_Q(Q).view(bs,-1,self.n_heads, self.d_k).transpose(1,2)  #q_s形状:[bs,n_heads,qlen,dk]k_s = self.W_K(K).view(bs, -1, self.n_heads, self.d_k).permute(0,2,3,1) # k_s形状: [bs,n_heads,dk,g_len]v_s= self.W_V(V).view(bs,-1,self.n_heads, self.d_v).transpose(1, 2) # v_s形状:[bs,n_heads,q_len,d_v]#计算缩放点积注意力context = self.attention(q_s, k_s, v_s, mask)#将多头的输出拼接起来,拼接后的形状:[bs,qlen,(n heads*dv)]context = context.transpose(1,2).contiguous().view(bs, -1, self.n_heads * self.d_v)#通过线性层映射回输入特征维度output =self.W_0(context) # output形状:[bs,g_len,d_model]return output
3.前馈传播
class Feedforward(nn.Module):def __init__ (self,d_model,d_ff,dropout=0.1):super(Feedforward,self).__init__()#两层线性映射和激活函数ReLUself.linear1 =nn.Linear(d_model,d_ff)self.dropout = nn.Dropout(dropout)self.linear2 =nn.Linear(d_ff, d_model)def forward(self,x):x= torch.nn.functional.relu(self.linear1(x))x= self.dropout(x)x= self.linear2(x)return x
4.位置编码
class PositionalEncoding(nn.Module):"实现位置编码"def __init__ (self,dmodel,dropout,max_len=5000):super(PositionalEncoding,self).__init__()self.dropout = nn.Dropout(p=dropout)#初始化Shape为(max len,d model)的PE(positional encoding)pe =torch.zeros(max_len,dmodel).to(device)#初始化一个tensor[[0,1,2,3,...]]position =torch.arange(0,max_len).unsqueeze(1)#这里就是sin和cos括号中的内容,通过e和1n进行了变换div_term = torch.exp(torch.arange(0,d_model,2)*-(math.log(10000.0)/ d_model))pe[:,0::2]=torch.sin(position*div_term)#计算PE(pos,2i)pe[:,1::2]=torch.cos(position*div_term)#计算PE(pos,2i+1)pe = pe.unsqueeze(0)#为了方便计算,在最外面在unsqueeze出一个batch#如果一个参数不参与梯度下降,但又希望保存model的时候将其保存下来#这个时候就可以用register bufferself.register_buffer("pe",pe)def forward(self,x):"""x为embedding后的inputs,例如(1,7,128),batch size为1,7个单词,单词维度为128"""#将x和positional encoding相加。x=x+ self.pe[:,:x.size(1)].requires_grad_(False)return self.dropout(x) 
5.编码层
class EncoderLayer(nn.Module):def __init__ (self,d_model,n_heads, d_ff, dropout=0.1):super(EncoderLayer,self).__init__()#编码器层包含自注意力机制和前馈神经网络self.self_attn =MultiHeadAttention(d_model,n_heads)self.feedforward = Feedforward(d_model, d_ff, dropout)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=x+ self.dropout(attn_output)x= self.norm1(x)#前馈神经网络ff_output = self.feedforward(x)x=x+ self.dropout(ff_output)x= self.norm2(x)return x
6.解码层
class DecoderLayer(nn.Module):def __init__ (self,d_model,n_heads,d_ff, dropout=0.1):super(DecoderLayer,self).__init__()#解码器层包含自注意力机制、编码器-解码器注意力机制和前馈神经网络self.self_attn=MultiHeadAttention(d_model,n_heads)self.enc_attn=MultiHeadAttention(d_model,n_heads)self.feedforward =Feedforward(d_model,d_ff, dropout)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, self_mask, context_mask):#自注意力机制attn_output=self.self_attn(x,x,x,self_mask)x=x+ self.dropout(attn_output)x= self.norm1(x)#编码器-解码器注意力机制attn_output = self.enc_attn(x,enc_output, enc_output, context_mask)x=x+ self.dropout(attn_output)x= self.norm2(x)#前馈神经网络ff_output = self.feedforward(x)x=x+ self.dropout(ff_output)x= self.norm3(x)return x
7.Transformer模型构建
class Transformer(nn.Module):def __init__ (self, vocab_size, d_model, n_heads,n_encoder_layers, n_decoder_layers,d_ff, dropout=0.1):super(Transformer,self).__init__()# Transformer 模型包含词嵌入、位置编码、编码器和解码器self.embedding=nn.Embedding(vocab_size,d_model)self.positional_encoding= PositionalEncoding(d_model, dropout)self.encoder_layers = nn.ModuleList([EncoderLayer(d_model,n_heads,d_ff, dropout) for _ in range(n_encoder_layers)])self.decoder_layers = nn.ModuleList([DecoderLayer(d_model,n_heads, d_ff, dropout) for _ in range(n_decoder_layers)])                                                        self.fc_out = nn.Linear(d_model,vocab_size)self.dropout = nn.Dropout(dropout)def forward(self,src,trg,src_mask, trg_mask):#词嵌入和位置编码src=self.embedding(src)src=self.positional_encoding(src)trg= self.embedding(trg)trg=self.positional_encoding(trg)#编码器for layer in self.encoder_layers :src=layer(src,src_mask)# 解码器for layer in self.decoder_layers:trg =layer(trg,src,trg_mask,src_mask)#输出层output = self.fc_out(trg)return output
8.输出模型结构 
#使用示例
vocab_size =10000 #假设词汇表大小为10000
d_model=512
n_heads=8
n_encoder_layers=6
n_decoder_layers=6
d_ff = 2048
dropout =0.1transformer_model = Transformer(vocab_size, d_model, n_heads, n_encoder_layers, n_decoder_layers, d_ff, dropout)#定义输入,这里的输入是假设的,需要根据实际情况修改
src=torch.randint(0,vocab_size,(32,10)) #源语言句子
trg=torch.randint(0,vocab_size,(32,20)) #目标语言句子#掩码,用于屏蔽填充的位置
src_mask=(src !=0).unsqueeze(1).unsqueeze(2)
trg_mask =(trg !=0).unsqueeze(1).unsqueeze(2) #掩码,用于屏蔽填充的位置print("实际|输入数据维度:",src.shape)
print("预期|输出数据维度:",trg.shape)
output =transformer_model(src,trg,src_mask,trg_mask)
print("实际|输出数据维度:",output.shape)
实际|输入数据维度: torch.Size([32, 10])
预期|输出数据维度: torch.Size([32, 20])
实际|输出数据维度: torch.Size([32, 20, 10000])

​​​​四、学习心得

       本周详细学习了Transformer架构,其与Seq2seq比较相似,都是包括编码器和解码器,不同之处在于Transformer架构就是基于注意力机制。不需要借助其他的LSTM等模型。

http://www.lqws.cn/news/596881.html

相关文章:

  • 快速手搓一个MCP服务指南(九): FastMCP 服务器组合技术:构建模块化AI应用的终极方案
  • 【仿muduo库实现并发服务器】Poller模块
  • 基于中国印尼会计准则差异,中国企业在印尼推广ERP(SAP、Oracle)系统需要注意的细节
  • Pycharm命令行能运行,但绿色三角报错?
  • mac重复文件清理,摄影师同款清理方案
  • nosql项目:基于 Redis 哨兵模式的鲜花预订配送系统
  • 设计模式之组合模式
  • 将实时流的 H.264(视频)与 G.711A(音频)封装成 MP4 文件
  • 关于量子计算的一份介绍
  • 12【进程间通信——管道】
  • Vue 响应式数据传递:ref、reactive 与 Provide/Inject 完全指南
  • 基于 Three.js 与 WebGL 的商场全景 VR 导航系统源码级解析
  • 遥感云大数据在灾害、水体与湿地领域案例及GPT应用
  • 第八章:LeRobot摄像头配置与应用指南
  • 使用GeoServer发布地图shapefi(.shp)数据
  • Spring Bean的生命周期与作用域详解
  • Vue-17-前端框架Vue之应用基础集中式状态管理pinia(二)
  • AI智能体在用户行为数据分析中有哪些应用?
  • Android 网络全栈攻略(四)—— TCPIP 协议族与 HTTPS 协议
  • Linux基本命令篇 —— grep命令
  • 基于ApachePOI实现百度POI分类快速导入PostgreSQL数据库实战
  • opencv使用 GStreamer 硬解码和 CUDA 加速的方案
  • 【cesium】基于vue-cesium开发地理空间分析应用
  • 在 Vue 3 中,如果需要显示 HTML 标签,可以使用 v-html 指令
  • android stdio 创建 mediaplayertest
  • 零信任安全管理系统产品对比介绍
  • 小米YU7使用UWB技术,厘米级定位精准迎宾,安全防破解无感控车
  • .NET测试工具Parasoft dotTEST:全兼容RMS的测试解决方案
  • 538. 把二叉搜索树转换为累加树
  • 清理 Docker 缓存占用