d2l-Transformer

news/2025/3/12 21:01:26/文章来源:https://www.cnblogs.com/hifrank/p/18758766

Transformer模型完全基于注意力机制,没有任何卷积层或循环神经网络层。

Transformer原先应用于文本数据上序列到序列的学习,现在已经被应用语言、视觉、语音和强化学习领域。

1. 模型架构

img

Transformer是由编码器和解码器组成的,其编码器和解码器是由自注意力的模块叠加而成的。
源(输入)序列和目标(输出)序列的嵌入 (embedding) 表示将加上位置编码(positional encoding),再分别输入到编码器和解码器中。

编码器:Transformer的编码器是由多个相同的层叠加而成的,每个层都有两个子层(sublayer):

  • 多头注意力(multi-head self-attention)汇聚:计算自注意力时,query, key, value都来自前一个编码器层的输出。
  • 基于位置的前馈网络(positionwise feed-forward network)

每个子层后需要进行add & norm

  • 每个子层都采用了残差连接(residual connection)。Transformer中,序列中任何位置的输入\(x \in R^d\),经过子层的输出维度仍为\(sublayer(x) \in R^d\),以便进行残差连接 \(x + sublayer(x) \in R^d\)
  • 在残差连接之后,应用层规范化(layer normalization)。

解码器:Transformer的解码器也是由多个相同的层叠加而成的,并且层之间使用了残差连接和层规范化。除了编码器中提到的两个子层,解码器中还包含了编码器-解码器注意力(encoder-decoder attention)。

  • 编码器-解码器注意力:query来自上一个解码器层的输出;key, value来自整个编码器的输出
  • 解码器自注意力:query, key, value都来自上一个解码器层的输出。

解码器中的每个位置只能考虑该位置之前的位置,这种掩码(masked)注意力保留了自回归属性,确保预测仅依赖于已生成的输出词元。

2. 基于位置的前馈网络

基于位置的前馈网络,本质上一个多层感知机(MLP)。

会改变张量的最里层维度的尺寸:

  • 输入:(batch_size, num_steps, ffn_num_input)
  • 输出:(batch_size, num_steps, ffn_num_output)
class PositionWiseFFN(nn.Module):"""基于位置的前馈网络"""def __init__(self, ffn_num_input, ffn_num_hiddens, ffn_num_outputs,**kwargs):super(PositionWiseFFN, self).__init__(**kwargs)self.dense1 = nn.Linear(ffn_num_input, ffn_num_hiddens)self.relu = nn.ReLU()self.dense2 = nn.Linear(ffn_num_hiddens, ffn_num_outputs)def forward(self, X):return self.dense2(self.relu(self.dense1(X)))

3. 残差连接和层规范化

  • 计算机视觉领域(CV)常用批量归一化(batch normalization)
  • 自然语言处理领域(NLP)常用层归一化(layer normalization),基于特征维度进行规范化
ln = nn.LayerNorm(2)
bn = nn.BatchNorm1d(2)
X = torch.tensor([[1, 2], [2, 3]], dtype=torch.float32)
# 在训练模式下计算X的均值和方差
print('layer norm:', ln(X), '\nbatch norm:', bn(X))layer norm: tensor([[-1.0000,  1.0000],[-1.0000,  1.0000]], grad_fn=<NativeLayerNormBackward0>)
batch norm: tensor([[-1.0000, -1.0000],[ 1.0000,  1.0000]], grad_fn=<NativeBatchNormBackward0>)
class AddNorm(nn.Module):"""残差连接后进行层规范化"""def __init__(self, normalized_shape, dropout, **kwargs):super(AddNorm, self).__init__(**kwargs)self.dropout = nn.Dropout(dropout)self.ln = nn.LayerNorm(normalized_shape)def forward(self, X, Y):return self.ln(self.dropout(Y) + X)

4. 编码器

首先实现编码器中的一个基本块

class EncoderBlock(nn.Module):"""Transformer编码器块"""def __init__(self, key_size, query_size, value_size, num_hiddens,norm_shape, ffn_num_input, ffn_num_hiddens, num_heads,dropout, use_bias=False, **kwargs):super(EncoderBlock, self).__init__(**kwargs)self.attention = d2l.MultiHeadAttention(key_size, query_size, value_size, num_hiddens, num_heads, dropout,use_bias)self.addnorm1 = AddNorm(norm_shape, dropout)self.ffn = PositionWiseFFN(ffn_num_input, ffn_num_hiddens, num_hiddens)self.addnorm2 = AddNorm(norm_shape, dropout)def forward(self, X, valid_lens):Y = self.addnorm1(X, self.attention(X, X, X, valid_lens))return self.addnorm2(Y, self.ffn(Y))

Transfomer的编码器堆叠了num_layers个编码器块。
因为这里使用的时值范围在[-1, 1]之间的固定位置编码,因此通过学习得到的输入的嵌入表示的值需要先乘以维度的平方根进行重新缩放,然后再与位置编码相加。

class TransformerEncoder(d2l.Encoder):"""Transformer编码器"""def __init__(self, vocab_size, key_size, query_size, value_size,num_hiddens, norm_shape, ffn_num_input, ffn_num_hiddens,num_heads, num_layers, dropout, use_bias=False, **kwargs):super(TransformerEncoder, self).__init__(**kwargs)self.num_hiddens = num_hiddensself.embedding = nn.Embedding(vocab_size, num_hiddens)self.pos_encoding = d2l.PositionalEncoding(num_hiddens, dropout)self.blks = nn.Sequential()for i in range(num_layers):self.blks.add_module("block"+str(i),EncoderBlock(key_size, query_size, value_size, num_hiddens,norm_shape, ffn_num_input, ffn_num_hiddens,num_heads, dropout, use_bias))def forward(self, X, valid_lens, *args):# 因为位置编码值在-1和1之间,# 因此嵌入值乘以嵌入维度的平方根进行缩放,# 然后再与位置编码相加。X = self.pos_encoding(self.embedding(X) * math.sqrt(self.num_hiddens))self.attention_weights = [None] * len(self.blks)for i, blk in enumerate(self.blks):X = blk(X, valid_lens)self.attention_weights[i] = blk.attention.attention.attention_weightsreturn X

5. 解码器

关于序列到序列模型(sequence-to-sequence model):

  • 训练阶段,其输出序列的所有位置(时间步)的词元都是已知的
  • 预测阶段,其输出序列的词元是逐个生成的。

因此,在任何解码器时间步中,只有生成的词元才能用于解码器的自注意力计算中。
为了在解码器中保留自回归的属性,其掩蔽自注意力设定了参数dec_valid_lens,以便任何查询都只会与解码器中所有已经生成词元的位置(即直到该查询位置为止)进行注意力计算。

class DecoderBlock(nn.Module):"""解码器中第i个块"""def __init__(self, key_size, query_size, value_size, num_hiddens,norm_shape, ffn_num_input, ffn_num_hiddens, num_heads,dropout, i, **kwargs):super(DecoderBlock, self).__init__(**kwargs)self.i = iself.attention1 = d2l.MultiHeadAttention(key_size, query_size, value_size, num_hiddens, num_heads, dropout)self.addnorm1 = AddNorm(norm_shape, dropout)self.attention2 = d2l.MultiHeadAttention(key_size, query_size, value_size, num_hiddens, num_heads, dropout)self.addnorm2 = AddNorm(norm_shape, dropout)self.ffn = PositionWiseFFN(ffn_num_input, ffn_num_hiddens,num_hiddens)self.addnorm3 = AddNorm(norm_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个块解码的输出表示if state[2][self.i] is None:key_values = Xelse:key_values = torch.cat((state[2][self.i], X), axis=1)state[2][self.i] = key_valuesif self.training:batch_size, num_steps, _ = X.shape# dec_valid_lens的开头:(batch_size,num_steps),# 其中每一行是[1,2,...,num_steps]dec_valid_lens = torch.arange(1, num_steps + 1, device=X.device).repeat(batch_size, 1)else:dec_valid_lens = None# 自注意力X2 = self.attention1(X, key_values, key_values, dec_valid_lens)Y = self.addnorm1(X, X2)# 编码器-解码器注意力。# enc_outputs的开头:(batch_size,num_steps,num_hiddens)Y2 = self.attention2(Y, enc_outputs, enc_outputs, enc_valid_lens)Z = self.addnorm2(Y, Y2)return self.addnorm3(Z, self.ffn(Z)), state
class TransformerDecoder(d2l.AttentionDecoder):def __init__(self, vocab_size, key_size, query_size, value_size,num_hiddens, norm_shape, ffn_num_input, ffn_num_hiddens,num_heads, num_layers, dropout, **kwargs):super(TransformerDecoder, self).__init__(**kwargs)self.num_hiddens = num_hiddensself.num_layers = num_layersself.embedding = nn.Embedding(vocab_size, num_hiddens)self.pos_encoding = d2l.PositionalEncoding(num_hiddens, dropout)self.blks = nn.Sequential()for i in range(num_layers):self.blks.add_module("block"+str(i),DecoderBlock(key_size, query_size, value_size, num_hiddens,norm_shape, ffn_num_input, ffn_num_hiddens,num_heads, dropout, i))self.dense = nn.Linear(num_hiddens, vocab_size)def init_state(self, enc_outputs, enc_valid_lens, *args):return [enc_outputs, enc_valid_lens, [None] * self.num_layers]def forward(self, X, state):X = self.pos_encoding(self.embedding(X) * math.sqrt(self.num_hiddens))self._attention_weights = [[None] * len(self.blks) for _ in range (2)]for i, blk in enumerate(self.blks):X, state = blk(X, state)# 解码器自注意力权重self._attention_weights[0][i] = blk.attention1.attention.attention_weights# “编码器-解码器”自注意力权重self._attention_weights[1][i] = blk.attention2.attention.attention_weightsreturn self.dense(X), state@propertydef attention_weights(self):return self._attention_weights

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

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

相关文章

Day06_离散数学_英语学习

离散数学 关系的性质 自反性与反自反性 Definition 设 R 是集合 A 上的关系. 1 如果对任意的 x ∈ A, 都有 < x, x >∈R, 那么称 R 在 A 上是自反的(reflexive), 或称R 具有自反性(reflexivity); 2 如果对任意的 x ∈ A, 都有 < x,x >∈ R, 那么称 R 在 A 上是反自…

【AI工具】12 款方便高效的 AI 抠图工具推荐,免费|无水印|在线抠图

大家好,我是小宇,欢迎来到 小宇科技酷 。关注我,每天为你分享实用、有趣的网站或者APP,以及一些好玩有用的AI工具。前言 抠图一直是图片处理里最麻烦的一个步骤,有时我们需要用很精细的素材,但又找不到免费且合适的透明底素材,只能手动抠图。抠家具、抠人物倒也还能忍受…

020 Vue3加载Element-plus

1、VUE2 的Element 网址: Element - 网站快速成型工具 2、Vue3 的Element-plus 网址:一个 Vue 3 UI 框架 | Element Plus 区别: Element,一套为开发者、设计师和产品经理准备的基于Vue2.0的桌面端组件库 Element Plus基于Vue3,面向设计师和开发者的组件库安装E…

分布式锁—6.Redisson的同步器组件

大纲 1.Redisson的分布式锁简单总结 2.Redisson的Semaphore简介 3.Redisson的Semaphore源码剖析 4.Redisson的CountDownLatch简介 5.Redisson的CountDownLatch源码剖析1.Redisson的分布式锁简单总结 (1)可重入锁RedissonLock (2)公平锁RedissonFairLock (3)联锁MultiLock (4)红…

挖坑(kruskal/prim)

对于prim算法 我们要更新一个点到一个已经在在树里的点的最小距离,作为答案,最开始要将起点加入优先队列,并且最开始要把d数组初始化为最大值 对于kruskal算法 我们需要用到并查集,对所有的边的边权进行排序,如果边的两个不在一个集合,就可以连接两点并让答案加上边权,如…

20242902程若曦-网络攻防第二次作业

1.实验要求指定域名的IP、DNS等信息查询获取好友IP地址,追踪到具体地理位置使用Nmap命令扫描靶机使用Nessus扫描靶机端口、自动扫描漏洞追踪自己网上足迹,查看隐私信息泄露问题2.实验过程 2.1 从www.besti.edu.cn、baidu.com、sina.com.cn中选择一个DNS域名进行查询,获取如下…

Qt+libcef开发的多窗口客户端

目录一、概述二、效果展示1、智能 URL 交互,上网快人一步2、全屏切换,沉浸式体验随心所欲3、多样布局,满足多元需求4、右键菜单,操作便捷功能丰富5、免费使用6、定制7、费用8、下载连接三、相关文章 原文链接:Qt+libcef开发的多窗口客户端 一、概述 各位上网冲浪达人们,今…

25种均线经典形态图解

多头排列:特征:均线多头排列是指短期移动平均线在上,中期移动平均线居中,长期移动平均线在下;几根移动平均线同时向上移动的一种排列方式。技术含义:一般说来,无论大盘还是个股,均线出现多头排列表明多头力量较强,做多主力正在控制局势,这是一种比较典型的做多信号。空…

Vulnhub-FristiLeaks_1.3

一、靶机搭建 选择扫描虚拟机选择路径即可二、信息收集 靶机信息 产品名称:Fristileaks 1.3 作者:Ar0xA 发布日期: 2015 年 12 月 14 日 目标:获取root(uid 0)并读取标志文件 #UID为0,即root权限 难度:初级 说明: 一个为荷兰非正式黑客聚会Fristileaks制作的小…

3.7学习开发app

教师要求笔记本本地的后端运行后,通过自己开发的手机app可以访问到 本来准备看完一个四个小时视频,后来发现不需要,视频大部分讲解如何开发一个完整的app,但是通过查ai资料就足以完成需求 主要是后端配置可以被外部访问的配置 server.port=8080 server.address=0.0.0.0 和 …

巨变的时代

记录作者对manus的期待AI发展的速度真是以天为计量单位。感觉昨天满眼还都是DeepSeek,结果马上就被Trae给刷屏。 又在刚刚,一家名为Monica的中国公司又推出Manus。 我也尝试注册了一个邀请码,等到审核通过后再来围观看看怎么个事吧:0

个人作业2查重系统

查重系统这个作业属于哪个课程 软件工程这个作业要求在哪里 作业要求这个作业的目标 初步使用PSP表格,完成一个文本查重系统我的GitHubPSP表格PSP2.1 Personal Software Process Stages 预估耗时(分钟) 实际耗时(分钟)Planning 计划 45 60Estimate 估计这个任务需要多少时…