transformer代码注解

其中代码均来自李沐老师的动手学pytorch中。

class PositionWiseFFN(nn.Module):'''ffn_num_inputs 4ffn_num_hiddens 4ffn_num_outputs 8'''def __init__(self,ffn_num_inputs,ffn_num_hiddens,ffn_num_outputs):super(PositionWiseFFN,self).__init__()self.dense1 = nn.Linear(ffn_num_inputs,ffn_num_hiddens)#4*4self.relu = nn.ReLU()self.dense2 = nn.Linear(ffn_num_hiddens,ffn_num_outputs)#4*8def forward(self,X):return self.dense2(self.relu(self.dense1(X)))
positionWiseFFN = PositionWiseFFN(4,4,8)
positionWiseFFN.eval()
positionWiseFFN(torch.ones(size=(2,3,4)))[0]

上面的代码为前馈神经网络结构,其实也就是一个全连接层。

class AddNorm(nn.Module):def __init__(self,normalized_shape,dropout):super(AddNorm, self).__init__()self.dropout=nn.Dropout(dropout)self.layer_norm=nn.LayerNorm(normalized_shape=normalized_shape)def forward(self,x,y):return self.layer_norm(self.dropout(y)+x)
#比如[3, 4]或torch.Size([3, 4]),则会对网络最后的两维进行归一化,且要求输入数据的最后两维尺寸也是[3, 4]
add_norm = AddNorm(normalized_shape=[3,4],dropout=0.5)
add_norm.eval()
add_norm(torch.ones(size=(2,3,4)),torch.ones(size=(2,3,4)))

这里实现的是残差化和规范化。nn.LayerNorm(normalized_shape=normalized_shape)为layer规范化,其中normalized_shape为[3, 4],对网络最后的两维进行归一化。

class MultiHeadAttention(nn.Module):def __init__(self,query_size,key_size,value_size,num_hiddens,num_heads,dropout,bias=False):super(MultiHeadAttention, self).__init__()self.num_heads=num_heads#用独立学习得到的 ℎ 组不同的线性投影(linear projections)来变换查询、键和值self.attention=d2l.torch.DotProductAttention(dropout)self.W_q=nn.Linear(query_size,num_hiddens,bias=bias)self.W_k=nn.Linear(key_size,num_hiddens,bias=bias)self.W_v = nn.Linear(value_size, num_hiddens, bias=bias)self.W_o = nn.Linear(num_hiddens, num_hiddens, bias=bias)
#总之就是:我们的Q,K,V的embedding,怎么拆分成k个头的数据,然后放到一个大头中,一遍算出multi_head的值
#这里是一组QKV乘一组W 直接生成特征大小的结果,在切分成8份,放到batch里等价于并行计算def forward(self,queries,keys,values,valid_lens):# print('----')# print(queries)queries = transpose_qkv(self.W_q(queries),self.num_heads)# print(queries)# print('----')keys = transpose_qkv(self.W_k(keys),self.num_heads)values = transpose_qkv(self.W_v(values),self.num_heads)if valid_lens is not None:# 在轴0,将第一项(标量或者矢量)复制num_heads次,# 然后如此复制第二项,然后诸如此类。valid_lens = torch.repeat_interleave(valid_lens,repeats=self.num_heads,dim=0)#valid_lens tensor([3, 3, 3, 3, 3, 2, 2, 2, 2, 2])# output的形状:(batch_size*num_heads,查询的个数,# num_hiddens/num_heads)# print(queries.shape)# print(keys.shape)'''queries-->torch.Size([10, 4, 20])keys----->torch.Size([10, 6, 20])两个批次,每次五个多头注意力,就一共会有十个注意力需要做。得出的矩阵为10*4*6,表示为10次注意力每个注意力query和key的矩阵为4*6keys  keys  keys  keys  keys  keysQueryQueryQueryQuery在经过mask时 需要将10*4*6的矩阵,转为二维矩阵,就是40*6。valid_lens首先会在上面的代码中,扩展至num_heads,然后会在masked_softmax中扩至40大小。'''output = self.attention(queries,keys,values,valid_lens)# print('-----')# print(output)# print('-----')# output_concat的形状:(batch_size,查询的个数,num_hiddens)output_concat = transpose_output(output,self.num_heads)# print(output_concat.shape)torch.Size([2, 4, 100])return self.W_o(output_concat)
def transpose_qkv(X,num_heads):# 2,6,100 2,4,100X = X.reshape(X.shape[0],X.shape[1],num_heads,-1)# 2,5,6,20  2,5,4,20# 输出X的形状: (batch_size,num_heads,查询或者“键-值”对的个数, num_hiddens/num_heads)X = X.permute(0, 2, 1, 3)#最终输出的形状: (batch_size * num_heads,查询或者“键-值”对的个数,num_hiddens/num_heads)#10,6,20  10,6,20return X.reshape(-1, X.shape[2], X.shape[3])
def transpose_output(X,num_heads):"""逆转transpose_qkv函数的操作"""X = X.reshape(-1,num_heads,X.shape[1],X.shape[2])X = X.permute(0,2,1,3)return X.reshape(X.shape[0],X.shape[1],-1)
#在这里,我们设置head为5,也就是一共有5次self-attention。
num_hiddens,num_heads = 100,5
multiHeadAttention = MultiHeadAttention(num_hiddens,num_hiddens,num_hiddens,num_hiddens,5,0.5)
multiHeadAttention.eval()batch_size,num_queries = 2,4
num_kvpairs,valid_lens = 6,torch.tensor([3,2])
#2,6,100 批次 句子长度 embedsize
Y = torch.ones(size=(batch_size,num_kvpairs,num_hiddens))
#2,4,100
X = torch.ones(size=(batch_size,num_queries,num_hiddens))
print(multiHeadAttention(X,Y,Y,valid_lens).shape)

首先我们设置head为5。num_hiddens可以理解为query或者key的大小,num_kvpairs表示每次注意力中key的数量,num_queries表示每次注意力中query的数量。value的数量与key的数量一样。另外一种理解就是,将X理解为批次*句子长度(单词的数量)*embedding size。每个单词对应一次查询。随后就是__init__,创建几个全连接层,对query、key、value进行变换,不同注意力的query、key和value,均不一样。
111111
主要是实现图中红色部分。然后会调用forward函数,transpose_qkv函数进行切分,假定原本的输入为2 * 6 * 100,因为大小为两个批次,每个批次需要做五个注意力机制,每个注意力机制的key的数量为6,所以将输入为2 * 6 * 100,转换为10 * 6 * 20。意思就是10次注意力,每个注意力中的key为6个,每个key由20维度的向量表示。query同理。因为我们要并行计算,这样使用torch.bmm可以直接进行计算,计算得出Query和key矩阵。在上面的例子中,计算得出的为10 * 4 * 6大小的矩阵。
在训练时刻的mask中,首先会将结果转变为二维矩阵40 * 6,其中的每一行代表了query与不同key计算的结果,有时候query只能和部分key进行计算,比如:第二个词的query只能计算第一个词与第二个词的key,而之后key需要进行mask。我们会给定一个valid_lens 代表需要保留的计算结果。其中mask部分会调用以下代码:

 mask = torch.arange((maxlen), dtype=torch.float32,device=X.device)[None, :] < valid_len[:, None]X[~mask] = value#value为极小值。

torch.arange((maxlen)会生成从0到5的矩阵,valid_len在之前会经过扩展为140大小的矩阵,然后转换为40 * 1的矩阵。最终的mask会变成40 * 6大小矩阵就像以下形式:
[True,True,True,False,False]
而最后两个False是需要进行mask的,X[~mask] = value将最后两个Fasle,变为负极小值,再经过softmax之后,结果将趋近于0,从而将其mask。然后与value相乘,得出结果为10 * 4
20矩阵大小的结果,在经过变换,变为2 * 4* 100矩阵,最后再经过最后一次全连接层,然后输出结果。

class PositionalEncoding(nn.Module):"""位置编码"""def __init__(self,num_hiddens,dropout,max_len=1000):super(PositionalEncoding,self).__init__()self.dropout = nn.Dropout(dropout)# 创建一个足够长的Pself.P = torch.zeros(size=(1,max_len,num_hiddens))X = torch.arange(max_len,dtype=torch.float32).reshape(-1,1)/torch.pow(1000,torch.arange(0,num_hiddens,2,dtype=torch.float32)/num_hiddens)self.P[:,:,0::2] = torch.sin(X)self.P[:,:,1::2] = torch.cos(X)def forward(self,X):X = X+self.P[:,:X.shape[1],:].to(X.device)return self.dropout(X)

主要实现位置编码,将基于正弦函数和余弦函数的固定位置编码公式进行实现,X就是将公式进行实现,P的大小为批次 * 输入模型单词可能最多的数量 * 每个单词的embedding size。我们可以假设P的大小为1 * 1000 * 32的矩阵,其中1000代表网络一次最多输入1000个词,每个词使用32维度向量表示。生成的X,是一个1000 * 16大小的矩阵,其中每一行的数值均不相同。P中每一行的偶数位置数据是由torch.sin(X)来生成的,奇数位置数据由torch.cos(X)生成。这样位置编码已经提前生成好了,在需要进行位置编码的时候,直接拿取前多少行,就行了。

class EncoderBlock(nn.Module):"""transformer编码器块"""
#EncoderBlock(query_size=24, key_size=24, value_size=24, num_hiddens=24, normalized_shape=[100, 24],ffn_num_inputs=24, ffn_num_hiddens=48, num_heads=8, dropout=0.5, use_bias=False)def __init__(self,query_size,key_size,value_size,num_hiddens,normalized_shape,ffn_num_inputs,ffn_num_hiddens,num_heads,dropout,use_bias=False):super(EncoderBlock,self).__init__()self.multihead_attention = MultiHeadAttention(key_size,query_size,value_size,num_hiddens,num_heads,dropout,use_bias)self.addnorm1 = AddNorm(normalized_shape,dropout)self.ffn = PositionWiseFFN(ffn_num_inputs,ffn_num_hiddens,num_hiddens)self.addnorm2 = AddNorm(normalized_shape,dropout)def forward(self,X,valid_lens):Y = self.addnorm1(X,self.multihead_attention(X,X,X,valid_lens))return self.addnorm2(Y,self.ffn(Y))

EncoderBlock对一个encoderBlock进行实现。先后经过,多头注意力机制,残差和规范化,前馈神经网络,残差和规范化,最后将结果输出。

class TransformerEncoder(d2l.torch.Encoder):"""transformer编码器"""def __init__(self,vocab_size,query_size,key_size,value_size,num_hiddens,normalized_shape,ffn_num_inputs,ffn_num_hiddens,num_heads,num_layers,dropout,use_bias=False):super(TransformerEncoder,self).__init__()self.num_hiddens = num_hiddensself.embedding = nn.Embedding(vocab_size,num_hiddens)self.positionalEncoding = d2l.torch.PositionalEncoding(num_hiddens,dropout)self.encoder_blocks = nn.Sequential()for i in range(num_layers):self.encoder_blocks.add_module(f'encoder_block{i}',EncoderBlock(query_size,key_size,value_size,num_hiddens,normalized_shape,ffn_num_inputs,ffn_num_hiddens,num_heads,dropout,use_bias=use_bias))def forward(self, X,valid_lens, *args):。X = self.positionalEncoding(self.embedding(X)*math.sqrt(self.num_hiddens))self.attention_weights = [None]*len(self.encoder_blocks)for i,encoder_block in enumerate(self.encoder_blocks):X = encoder_block(X,valid_lens)self.attention_weights[i] = encoder_block.multihead_attention.attention.attention_weightsreturn X

transformer编码器,对encoder进行堆叠,self.embedding(X)*math.sqrt(self.num_hiddens 主要因为embedding的值相对于位置编码比较小,乘以math.sqrt(self.num_hiddens,使得值与位置编码的值,差不多大小。

class DecoderBlock(nn.Module):"""解码器中第i个块"""#decoder_block = DecoderBlock(24,24,24,24,[100,24],24,48,8,0.5,0,use_bias=False)def __init__(self, query_size, key_size, value_size, num_hiddens, normalized_shape, ffn_num_inputs, ffn_num_hiddens,num_heads, dropout, i, use_bias=False):super(DecoderBlock, self).__init__()self.i = i  # i表示这是第i个DecoderBlock块self.mask_multihead_attention1 = MultiHeadAttention(key_size, query_size, value_size, num_hiddens,num_heads, dropout, bias=use_bias)self.addnorm1 = AddNorm(normalized_shape, dropout)self.mutilhead_attention2 = MultiHeadAttention(key_size, query_size, value_size, num_hiddens,num_heads, dropout, bias=use_bias)self.addnorm2 = AddNorm(normalized_shape, dropout)self.ffn = PositionWiseFFN(ffn_num_inputs, ffn_num_hiddens, num_hiddens)self.addnorm3 = AddNorm(normalized_shape, dropout)def forward(self, X, state):enc_outputs, enc_valid_lens = state[0], state[1]# 训练阶段,输出序列的所有词元都在同一时间处理,# 因此state[2][self.i]初始化为None。# 预测阶段,输出序列是通过词元一个接着一个解码的,# 因此state[2][self.i]包含着直到当前时间步第i个块解码的输出表示# 训练时,由于每次都需要调用init_state函数,因此重新训练一个batch时,state[2]始终是一个None列表,# 当测试时,由于每次根据当前时间步的词元预测下一个词元时都不会重新调用init_state()函数,# 不会重新初始化state,因此state[2]里面保存的是之前时间步预测出来的词元信息(存的是decoder每层第一个掩码多头注意力state信息)if state[2][self.i] is None:keys_values = Xelse:keys_values = torch.cat([state[2][self.i], X], dim=1)state[2][self.i] = keys_valuesif self.training:#[2, 100, 24]batch_size, num_step, _ = X.shape# 训练时执行当前时间步的query时只看它前面的keys,values,不看它后面的keys,values。# 因为预测时是从左往右预测的,右边还没有预测出来,因此右侧的keys是没有的,看不到右侧的keys;# 训练时预测当前时间步词元能看到后面的目标词元,因此需要dec_valid_lens# dec_valid_lens的开头:(batch_size,num_steps),# 其中每一行是[1,2,...,num_steps]dec_valid_lens = torch.arange(1, num_step + 1, device=X.device).repeat(batch_size, 1)print(dec_valid_lens)else:# 测试时预测当前时间步的词元只能看到之前预测出来的词元,后面还没预测的词元还看不到,因此dec_valid_lens可以不需要dec_valid_lens = None# 自注意力X2 = self.mask_multihead_attention1(X, keys_values, keys_values, dec_valid_lens)Y = self.addnorm1(X, X2)# 编码器-解码器注意力。# enc_outputs的开头:(batch_size,num_steps,num_hiddens)Y2 = self.mutilhead_attention2(Y, enc_outputs, enc_outputs, enc_valid_lens)Z = self.addnorm2(Y, Y2)return self.addnorm3(Z, self.ffn(Z)), state

区分主要在两点,就是训练的时候,会执行if state[2][self.i] is None:,所以第一次多头注意力,输入的key和value,均为本身。而在预测阶段,第一次多头注意力输入的为之前生成

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

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

相关文章

微服务项目,maven无法加载其他服务依赖

微服务项目&#xff0c;导入了工具类工程&#xff0c;但是一直报错&#xff0c;没有该类&#xff0c; 检查maven 这里的Maven的版本与idea版本不匹配可能是导致依赖加载失败的最重要原因 检查maven配置&#xff0c;我这是原来的maven&#xff0c;home 修改之后,就不报错了

Autosar通信实战系列02-CAN报文发送周期测试脚本开发及周期不准优化

本文框架 前言1. CAN发送报文的周期测试脚本开发2. 发送报文周期不准的可能原因及优化策略2.1 发送报文的控制逻辑2.2 送报文周期不准的可能原因及优化策略前言 在本系列笔者将结合工作中对通信实战部分的应用经验进一步介绍常用,包括但不限于通信各模块的开发教程,代码逻辑…

【ArcGIS Pro二次开发】(53):村规制表、制图【福建省】

这篇算是村规入库的一个延续。 村庄规划中有一些图纸是需要严格按照规范制图&#xff0c;或形成一定规范格式的。 这些图纸的制作基本算是机械式的工作&#xff0c;可以用工具来代替人工。 一、要实现的功能 如上图所示&#xff0c;在【村庄规划】组&#xff0c;新增了两个工…

【雕爷学编程】MicroPython动手做(16)——掌控板之图片图像显示3

知识点&#xff1a;什么是掌控板&#xff1f; 掌控板是一块普及STEAM创客教育、人工智能教育、机器人编程教育的开源智能硬件。它集成ESP-32高性能双核芯片&#xff0c;支持WiFi和蓝牙双模通信&#xff0c;可作为物联网节点&#xff0c;实现物联网应用。同时掌控板上集成了OLED…

vue3+vite——打测试包+正式包+本地预览打包后的文件——基础积累

最近在学习vue3vite的内容&#xff0c;发现vite和webpack类似&#xff0c;下面将区别及使用方法做一下记录&#xff1a; 1.vite添加环境配置文件 ... ├── src ... ├── .env # 通用环境变量配置 ├── .env.development …

机器学习:提取问题答案

模型BERT 任务&#xff1a;提取问题和答案 问题的起始位置和结束位置。 数据集 数据集 DRCDODSQA 先分词&#xff0c;然后tokenize 文章长度是不同的&#xff0c;bert的token的长度有限制&#xff0c;一般是512&#xff0c; self-attention的计算量是 O ( n 2 ) O(n^2) O(n…

opencv+ffmpeg环境(ubuntu)搭建全面详解

一.先讲讲opencv和ffmpeg之间的关系 1.1它们之间的联系 我们知道opencv主要是用来做图像处理的&#xff0c;但也包含视频解码的功能&#xff0c;而在视频解码部分的功能opencv是使用了ffmpeg。所以它们都是可以处理图像和视频的编解码&#xff0c;我个人感觉两个的侧重点不一…

无涯教程-jQuery - Selectable选择函数

选择能力功能可与JqueryUI中的交互一起使用。此功能可在任何DOM元素上启用选择能力功能。用光标绘制一个框以选择项目。按住Ctrl键可进行多个不相邻的选择。 Select able - 语法 $( "#selectable" ).selectable(); Select able - 示例 以下是一个简单的示例&…

目标检测应用场景—数据集【NO.14】行人跌倒测试

写在前面&#xff1a;数据集对应应用场景&#xff0c;不同的应用场景有不同的检测难点以及对应改进方法&#xff0c;本系列整理汇总领域内的数据集&#xff0c;方便大家下载数据集&#xff0c;若无法下载可关注后私信领取。关注免费领取整理好的数据集资料&#xff01;今天分享…

DevOps(三)

CD(二) 1. 整体流程2. 环境准备1. jenkins安装2. 编译安装git3. docker安装4. docker-compose安装5. sonarqube安装6. harbor安装7. gitlab私服8. maven安装9. Nexus部署10. K8s部署3. 安装java及编写代码3.1 安装java3.2 安装IntelliJ IDEA3.3 安装tomcat3.4 安装maven3.5 c…

【LLM】大语言模型学习之LLAMA 2:Open Foundation and Fine-Tuned Chat Model

大语言模型学习之LLAMA 2:Open Foundation and Fine-Tuned Chat Model 快速了解预训练预训练模型评估微调有监督微调(SFT)人类反馈的强化学习(RLHF)RLHF结果局限性安全性预训练的安全性安全微调上手就干使用登记代码下载获取模型转换模型搭建Text-Generation-WebUI分发模型…

UE5、CesiumForUnreal加载无高度地形

文章目录 1.实现目标2.实现过程3.参考资料1.实现目标 在UE5中,CesiumForUnreal插件默认的地形都是带高度的,这里加载没有高度的地形,即大地高程为0,GIF动图如下: 2.实现过程 参考官方的教程,下载无高度的DEM,再切片加载到UE中。 (1)下载无高度地形DEM0。 在官方帖子…

uniapp使用uni-swipe-action后右侧多了小于1px的间隙

问题&#xff1a;uniapp使用uni-swipe-action后右侧多了小于1px的间隙。且在真机上没有问题&#xff0c;但是在微信开发者工具中有问题。 代码如下&#xff1a;在滑动滑块或者点击这个区域时&#xff0c;就会出现问题。 <scroll-view :scroll-y"true" :style&quo…

文心一言大数据模型-文心千帆大模型平台

官网&#xff1a; 文心千帆大模型平台 (baidu.com) 文心千帆大模型 (baidu.com) 模型优势 1、模型效果优&#xff1a;所需标注数据少&#xff0c;在各场景上的效果处于业界领先水平 2、生成能力强&#xff1a;拥有丰富的AI内容生成&#xff08;AIGC&#xff09;能力 3、应用…

【笔试强训选择题】Day32.习题(错题)解析

作者简介&#xff1a;大家好&#xff0c;我是未央&#xff1b; 博客首页&#xff1a;未央.303 系列专栏&#xff1a;笔试强训选择题 每日一句&#xff1a;人的一生&#xff0c;可以有所作为的时机只有一次&#xff0c;那就是现在&#xff01;&#xff01; 文章目录 前言 一、Da…

HDFS异构存储详解

异构存储 HDFS异构存储类型什么是异构存储异构存储类型如何让HDFS知道集群中的数据存储目录是那种类型存储介质 块存储选择策略选择策略说明选择策略的命令 案例&#xff1a;冷热温数据异构存储对应步骤 HDFS内存存储策略支持-- LAZY PERSIST介绍执行使用 HDFS异构存储类型 冷…

回归预测 | MATLAB实现SO-CNN-BiLSTM蛇群算法优化卷积双向长短期记忆神经网络多输入单输出回归预测

回归预测 | MATLAB实现SO-CNN-BiLSTM蛇群算法优化卷积双向长短期记忆神经网络多输入单输出回归预测 目录 回归预测 | MATLAB实现SO-CNN-BiLSTM蛇群算法优化卷积双向长短期记忆神经网络多输入单输出回归预测预测效果基本介绍模型描述程序设计参考资料 预测效果 基本介绍 Matlab实…

十五章:使用类别峰值响应的弱监督实例分割

0.摘要 目前&#xff0c;使用图像级别标签而不是昂贵的像素级掩码进行弱监督实例分割的研究还未得到充分探索。本文通过利用类别峰值响应来实现一个分类网络&#xff0c;用于提取实例掩码&#xff0c;来解决这个具有挑战性的问题。只通过图像标签的监督下&#xff0c;完全卷积的…

SqueezeLM 的想法,压缩输入句子潜变量,生成下一句子

又搞了一段时间。还是感觉LongNet那种空洞注意力做编码器有搞头。 RetNet等AFT方法&#xff0c;直接生成太长的句子感觉有点难度&#xff0c;不过可以一句句生成&#xff0c;每次生成短句&#xff0c;这样感觉比较合适。 启发 受 MemroyTransformer 和 GLM 启发 想了一个类似…

MySQL的JSON操作

官网地址 1. MySQL json介绍 As of MySQL 5.7.8, MySQL supports a native JSON data type defined by RFC 7159 that enables efficient access to data in JSON (JavaScript Object Notation) documents. Automatic validation of JSON documents stored in JSON columns. …