NLP——Transfromer 架构详解

Transformer总体架构图

在这里插入图片描述
在这里插入图片描述

  1. 输入部分:源文本嵌入层及其位置编码器、目标文本嵌入层及其位置编码器

  2. 编码器部分
    由N个编码器层堆叠而成
    每个编码器层由两个子层连接结构组成
    第一个子层连接结构包括一个多头自注意力子层和规范化层以及一个残差连接
    第二个子层连接结构包括一个前馈全连接子层和规范化层以及一个残差连接

  3. 解码器部分
    由N个解码器层堆叠而成
    每个解码器层由三个子层连接结构组成
    第一个子层连接结构包括一个多头自注意力子层和规范化层以及一个残差连接
    第二个子层连接结构包括一个多头注意力子层和规范化层以及一个残差连接
    第三个子层连接结构包括一个前馈全连接子层和规范化层以及一个残差连接

  4. 输出部分包:线性层、softmax层

输入部分实现

1. 文本嵌入层
import torch
# 预定义的网络层torch.nn, 工具开发者已经帮助我们开发好的一些常用层, 
# 比如,卷积层, lstm层, embedding层等, 不需要我们再重新造轮子.
import torch.nn as nn
import math# torch中变量封装函数Variable.
from torch.autograd import Variable# 定义Embeddings类来实现文本嵌入层,这里s说明代表两个一模一样的嵌入层, 他们共享参数.
# 该类继承nn.Module, 这样就有标准层的一些功能, 这里我们也可以理解为一种模式, 我们自己实现的所有层都会这样去写.
class Embeddings(nn.Module):def __init__(self, d_model, vocab):"""类的初始化函数, 有两个参数, d_model: 指词嵌入的维度, vocab: 指词表的大小."""# 接着就是使用super的方式指明继承nn.Module的初始化函数, 我们自己实现的所有层都会这样去写.super(Embeddings, self).__init__()# 之后就是调用nn中的预定义层Embedding, 获得一个词嵌入对象self.lutself.lut = nn.Embedding(vocab, d_model)# 最后就是将d_model传入类中self.d_model = d_modeldef forward(self, x):"""可以将其理解为该层的前向传播逻辑,所有层中都会有此函数当传给该类的实例化对象参数时, 自动调用该类函数参数x: 因为Embedding层是首层, 所以代表输入给模型的文本通过词汇映射后的张量"""# 将x传给self.lut并与根号下self.d_model相乘作为结果返回# 让 embeddings vector 在增加 之后的 postion encoing 之前相对大一些的操作,# 主要是为了让position encoding 相对的小,这样会让原来的 embedding vector 中的信息在和 position encoding 的信息相加时不至于丢失掉# 让 embeddings vector 相对大一些return self.lut(x) * math.sqrt(self.d_model)
# 词嵌入维度是512维
d_model = 512# 词表大小是1000
vocab = 1000# 输入x是一个使用Variable封装的长整型张量, 形状是2 x 4
x = Variable(torch.LongTensor([[100,2,421,508],[491,998,1,221]]))emb = Embeddings(d_model, vocab)
embr = emb(x)
print("embr:", embr)
2. 位置编码器

因为在Transformer的编码器结构中, 并没有针对词汇位置信息的处理,因此需要在Embedding层后加入位置编码器,将词汇位置不同可能会产生不同语义的信息加入到词嵌入张量中, 以弥补位置信息的缺失.

# 定义位置编码器类, 我们同样把它看做一个层, 因此会继承nn.Module    
class PositionalEncoding(nn.Module):def __init__(self, d_model, dropout, max_len=5000):"""位置编码器类的初始化函数, 共有三个参数, 分别是d_model: 词嵌入维度, dropout: 置0比率, max_len: 每个句子的最大长度"""super(PositionalEncoding, self).__init__()# 实例化nn中预定义的Dropout层, 并将dropout传入其中, 获得对象self.dropoutself.dropout = nn.Dropout(p=dropout)# 初始化一个位置编码矩阵, 它是一个0阵,矩阵的大小是max_len x d_model.pe = torch.zeros(max_len, d_model)# 初始化一个绝对位置矩阵, 在我们这里,词汇的绝对位置就是用它的索引去表示. # 所以我们首先使用arange方法获得一个连续自然数向量,然后再使用unsqueeze方法拓展向量维度使其成为矩阵, # 又因为参数传的是1,代表矩阵拓展的位置,会使向量变成一个max_len x 1 的矩阵, position = torch.arange(0, max_len).unsqueeze(1)# 绝对位置矩阵初始化之后,接下来就是考虑如何将这些位置信息加入到位置编码矩阵中,# 最简单思路就是先将max_len x 1的绝对位置矩阵, 变换成max_len x d_model形状,然后覆盖原来的初始位置编码矩阵即可, # 要做这种矩阵变换,就需要一个1xd_model形状的变换矩阵div_term,我们对这个变换矩阵的要求除了形状外,# 还希望它能够将自然数的绝对位置编码缩放成足够小的数字,有助于在之后的梯度下降过程中更快的收敛.  这样我们就可以开始初始化这个变换矩阵了.# 首先使用arange获得一个自然数矩阵, 但是细心的同学们会发现, 我们这里并没有按照预计的一样初始化一个1xd_model的矩阵, # 而是有了一个跳跃,只初始化了一半即1xd_model/2 的矩阵。 为什么是一半呢,其实这里并不是真正意义上的初始化了一半的矩阵,# 我们可以把它看作是初始化了两次,而每次初始化的变换矩阵会做不同的处理,第一次初始化的变换矩阵分布在正弦波上, 第二次初始化的变换矩阵分布在余弦波上, # 并把这两个矩阵分别填充在位置编码矩阵的偶数和奇数位置上,组成最终的位置编码矩阵.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[:, 1::2] = torch.cos(position * div_term)# 这样我们就得到了位置编码矩阵pe, pe现在还只是一个二维矩阵,要想和embedding的输出(一个三维张量)相加,# 就必须拓展一个维度,所以这里使用unsqueeze拓展维度.pe = pe.unsqueeze(0)# 最后把pe位置编码矩阵注册成模型的buffer,什么是buffer呢,# 我们把它认为是对模型效果有帮助的,但是却不是模型结构中超参数或者参数,不需要随着优化步骤进行更新的增益对象. # 注册之后我们就可以在模型保存后重加载时和模型结构与参数一同被加载.self.register_buffer('pe', pe)def forward(self, x):"""forward函数的参数是x, 表示文本序列的词嵌入表示"""# 在相加之前我们对pe做一些适配工作, 将这个三维张量的第二维也就是句子最大长度的那一维将切片到与输入的x的第二维相同即x.size(1),# 因为我们默认max_len为5000一般来讲实在太大了,很难有一条句子包含5000个词汇,所以要进行与输入张量的适配. # 最后使用Variable进行封装,使其与x的样式相同,但是它是不需要进行梯度求解的,因此把requires_grad设置成false.x = x + Variable(self.pe[:, :x.size(1)], requires_grad=False)# 最后使用self.dropout对象进行'丢弃'操作, 并返回结果.return self.dropout(x)
# 词嵌入维度是512维
d_model = 512
# 置0比率为0.1
dropout = 0.1
# 句子最大长度
max_len=60
# 输入x是Embedding层的输出的张量, 形状是2 x 4 x 512
x = embrpe = PositionalEncoding(d_model, dropout, max_len)
pe_result = pe(x)
print("pe_result:", pe_result)
绘制词汇向量中特征的分布曲线
import matplotlib.pyplot as plt
import numpy as np# 创建一张15 x 5大小的画布
plt.figure(figsize=(15, 5))# 实例化PositionalEncoding类得到pe对象, 输入参数是20和0
pe = PositionalEncoding(20, 0)# 然后向pe传入被Variable封装的tensor, 这样pe会直接执行forward函数, 
# 且这个tensor里的数值都是0, 被处理后相当于位置编码张量
y = pe(Variable(torch.zeros(1, 100, 20)))# 然后定义画布的横纵坐标, 横坐标到100的长度, 纵坐标是某一个词汇中的某维特征在不同长度下对应的值
# 因为总共有20维之多, 我们这里只查看4,5,6,7维的值.
plt.plot(np.arange(100), y[0, :, 4:8].data.numpy())# 在画布上填写维度提示信息
plt.legend(["dim %d"%p for p in [4,5,6,7]])

在这里插入图片描述


编码器部分实现

编码器部分: * 由N个编码器层堆叠而成 * 每个编码器层由两个子层连接结构组成 * 第一个子层连接结构包括一个多头自注意力子层和规范化层以及一个残差连接 * 第二个子层连接结构包括一个前馈全连接子层和规范化层以及一个残差连接

1. 掩码张量

掩代表遮掩,码就是我们张量中的数值,它的尺寸不定,里面一般只有1和0的元素,代表位置被遮掩或者不被遮掩,至于是0位置被遮掩还是1位置被遮掩可以自定义,因此它的作用就是让另外一个张量中的一些数值被遮掩,也可以说被替换, 它的表现形式是一个张量.
在transformer中, 掩码张量的主要作用在应用attention(将在下一小节讲解)时,有一些生成的attention张量中的值计算有可能已知了未来信息而得到的,未来信息被看到是因为训练时会把整个输出结果都一次性进行Embedding,但是理论上解码器的的输出却不是一次就能产生最终结果的,而是一次次通过上一次结果综合得出的,因此,未来的信息可能被提前利用. 所以,我们会进行遮掩.

  • 生成掩码张量的代码分析
def subsequent_mask(size):"""生成向后遮掩的掩码张量, 参数size是掩码张量最后两个维度的大小, 它的最后两维形成一个方阵"""# 在函数中, 首先定义掩码张量的形状attn_shape = (1, size, size)# 然后使用np.ones方法向这个形状中添加1元素,形成上三角阵, 最后为了节约空间, # 再使其中的数据类型变为无符号8位整形unit8 subsequent_mask = np.triu(np.ones(attn_shape), k=1).astype('uint8')# 最后将numpy类型转化为torch中的tensor, 内部做一个1 - 的操作, # 在这个其实是做了一个三角阵的反转, subsequent_mask中的每个元素都会被1减, # 如果是0, subsequent_mask中的该位置由0变成1# 如果是1, subsequent_mask中的该位置由1变成0 return torch.from_numpy(1 - subsequent_mask)# 生成的掩码张量的最后两维的大小
size = 5
sm = subsequent_mask(size)
print("sm:", sm)
# 最后两维形成一个下三角阵
sm: (0 ,.,.) = 1  0  0  0  01  1  0  0  01  1  1  0  01  1  1  1  01  1  1  1  1
[torch.ByteTensor of size 1x5x5]
# 码张量的可视化
plt.figure(figsize=(5,5))
plt.imshow(subsequent_mask(20)[0])

在这里插入图片描述

2. 注意力机制
  • 注意力计算规则的代码分析
import torch.nn.functional as Fdef attention(query, key, value, mask=None, dropout=None):"""注意力机制的实现, 输入分别是query, key, value, mask: 掩码张量, dropout是nn.Dropout层的实例化对象, 默认为None"""# 在函数中, 首先取query的最后一维的大小, 一般情况下就等同于我们的词嵌入维度, 命名为d_kd_k = query.size(-1)# 按照注意力公式, 将query与key的转置相乘, 这里面key是将最后两个维度进行转置, 再除以缩放系数根号下d_k, 这种计算方法也称为缩放点积注意力计算.# 得到注意力得分张量scoresscores = torch.matmul(query, key.transpose(-2, -1)) / math.sqrt(d_k)# 接着判断是否使用掩码张量if mask is not None:# 使用tensor的masked_fill方法, 将掩码张量和scores张量每个位置一一比较, 如果掩码张量处为0# 则对应的scores张量用-1e9这个值来替换, 如下演示scores = scores.masked_fill(mask == 0, -1e9)# 对scores的最后一维进行softmax操作, 使用F.softmax方法, 第一个参数是softmax对象, 第二个是目标维度.# 这样获得最终的注意力张量p_attn = F.softmax(scores, dim = -1)# 之后判断是否使用dropout进行随机置0if dropout is not None:# 将p_attn传入dropout对象中进行'丢弃'处理p_attn = dropout(p_attn)# 最后, 根据公式将p_attn与value张量相乘获得最终的query注意力表示, 同时返回注意力张量return torch.matmul(p_attn, value), p_attn
# 我们令输入的query, key, value都相同, 位置编码的输出
query = key = value = pe_result
attn, p_attn = attention(query, key, value)
# attn, p_attn = attention(query, key, value, mask=mask)
print("attn:", attn)
print("p_attn:", p_attn)
3. 多头注意力机制

多头注意力的结构图中,貌似这个所谓的多个头就是指多组线性变换层,其实并不是,我只有使用了一组线性变化层,即三个变换张量对Q,K,V分别进行线性变换,这些变换不会改变原有张量的尺寸,因此每个变换矩阵都是方阵,得到输出结果后,多头的作用才开始显现,每个头开始从词义层面分割输出的张量,也就是每个头都想获得一组Q,K,V进行注意力机制的计算,但是句子中的每个词的表示只获得一部分,也就是只分割了最后一维的词嵌入向量. 这就是所谓的多头,将每个头的获得的输入送到注意力机制中, 就形成多头注意力机制.
在这里插入图片描述
这种结构设计能让每个注意力机制去优化每个词汇的不同特征部分,从而均衡同一种注意力机制可能产生的偏差,让词义拥有来自更多元的表达,实验表明可以从而提升模型效果.

  • 多头注意力机制的代码实现
import copy# 首先需要定义克隆函数, 因为在多头注意力机制的实现中, 用到多个结构相同的线性层.
# 我们将使用clone函数将他们一同初始化在一个网络层列表对象中. 之后的结构中也会用到该函数.
def clones(module, N):"""用于生成相同网络层的克隆函数, 它的参数module表示要克隆的目标网络层, N代表需要克隆的数量"""# 在函数中, 我们通过for循环对module进行N次深度拷贝, 使其每个module成为独立的层,# 然后将其放在nn.ModuleList类型的列表中存放.return nn.ModuleList([copy.deepcopy(module) for _ in range(N)])# 我们使用一个类来实现多头注意力机制的处理
class MultiHeadedAttention(nn.Module):def __init__(self, head, embedding_dim, dropout=0.1):"""在类的初始化时, 会传入三个参数,head代表头数,embedding_dim代表词嵌入的维度, dropout代表进行dropout操作时置0比率,默认是0.1."""super(MultiHeadedAttention, self).__init__()# 在函数中,首先使用了一个测试中常用的assert语句,判断h是否能被d_model整除,# 这是因为我们之后要给每个头分配等量的词特征.也就是embedding_dim/head个.assert embedding_dim % head == 0# 得到每个头获得的分割词向量维度d_kself.d_k = embedding_dim // head# 传入头数hself.head = head# 然后获得线性层对象,通过nn的Linear实例化,它的内部变换矩阵是embedding_dim x embedding_dim,然后使用clones函数克隆四个,# 为什么是四个呢,这是因为在多头注意力中,Q,K,V各需要一个,最后拼接的矩阵还需要一个,因此一共是四个.self.linears = clones(nn.Linear(embedding_dim, embedding_dim), 4)# self.attn为None,它代表最后得到的注意力张量,现在还没有结果所以为None.self.attn = None# 最后就是一个self.dropout对象,它通过nn中的Dropout实例化而来,置0比率为传进来的参数dropout.self.dropout = nn.Dropout(p=dropout)def forward(self, query, key, value, mask=None):"""前向逻辑函数, 它的输入参数有四个,前三个就是注意力机制需要的Q, K, V,最后一个是注意力机制中可能需要的mask掩码张量,默认是None. """# 如果存在掩码张量maskif mask is not None:# 使用unsqueeze拓展维度mask = mask.unsqueeze(0)# 接着,我们获得一个batch_size的变量,他是query尺寸的第1个数字,代表有多少条样本.batch_size = query.size(0)# 之后就进入多头处理环节# 首先利用zip将输入QKV与三个线性层组到一起,然后使用for循环,将输入QKV分别传到线性层中,# 做完线性变换后,开始为每个头分割输入,这里使用view方法对线性变换的结果进行维度重塑,多加了一个维度h,代表头数,# 这样就意味着每个头可以获得一部分词特征组成的句子,其中的-1代表自适应维度,# 计算机会根据这种变换自动计算这里的值.然后对第二维和第三维进行转置操作,# 为了让代表句子长度维度和词向量维度能够相邻,这样注意力机制才能找到词义与句子位置的关系,# 从attention函数中可以看到,利用的是原始输入的倒数第一和第二维.这样我们就得到了每个头的输入.query, key, value = \[model(x).view(batch_size, -1, self.head, self.d_k).transpose(1, 2)for model, x in zip(self.linears, (query, key, value))]# 得到每个头的输入后,接下来就是将他们传入到attention中,# 这里直接调用我们之前实现的attention函数.同时也将mask和dropout传入其中.x, self.attn = attention(query, key, value, mask=mask, dropout=self.dropout)# 通过多头注意力计算后,我们就得到了每个头计算结果组成的4维张量,我们需要将其转换为输入的形状以方便后续的计算,# 因此这里开始进行第一步处理环节的逆操作,先对第二和第三维进行转置,然后使用contiguous方法,# 这个方法的作用就是能够让转置后的张量应用view方法,否则将无法直接使用,# 所以,下一步就是使用view重塑形状,变成和输入形状相同.x = x.transpose(1, 2).contiguous().view(batch_size, -1, self.head * self.d_k)# 最后使用线性层列表中的最后一个线性层对输入进行线性变换得到最终的多头注意力结构的输出.return self.linears[-1](x)
# 假设输入的Q,K,V仍然相等
query = value = key = pe_result# 输入的掩码张量mask
mask = Variable(torch.zeros(8, 4, 4))
mha = MultiHeadedAttention(head, embedding_dim, dropout)
mha_result = mha(query, key, value, mask)
print(mha_result)
4. 前馈全连接层

在Transformer中前馈全连接层就是具有两层线性层的全连接网络.
前馈全连接层的作用:考虑注意力机制可能对复杂过程的拟合程度不够, 通过增加两层网络来增强模型的能力.

  • 前馈全连接层的代码分析
# 通过类PositionwiseFeedForward来实现前馈全连接层
class PositionwiseFeedForward(nn.Module):def __init__(self, d_model, d_ff, dropout=0.1):"""初始化函数有三个输入参数分别是d_model, d_ff,和dropout=0.1,第一个是线性层的输入维度也是第二个线性层的输出维度,因为我们希望输入通过前馈全连接层后输入和输出的维度不变. 第二个参数d_ff就是第二个线性层的输入维度和第一个线性层的输出维度. 最后一个是dropout置0比率."""super(PositionwiseFeedForward, self).__init__()# 首先按照我们预期使用nn实例化了两个线性层对象,self.w1和self.w2# 它们的参数分别是d_model, d_ff和d_ff, d_modelself.w1 = nn.Linear(d_model, d_ff)self.w2 = nn.Linear(d_ff, d_model)# 然后使用nn的Dropout实例化了对象self.dropoutself.dropout = nn.Dropout(dropout)def forward(self, x):"""输入参数为x,代表来自上一层的输出"""# 首先经过第一个线性层,然后使用Funtional中relu函数进行激活,# 之后再使用dropout进行随机置0,最后通过第二个线性层w2,返回最终结果.return self.w2(self.dropout(F.relu(self.w1(x))))
d_model = 512
# 线性变化的维度
d_ff = 64
dropout = 0.2
# 输入参数x可以是多头注意力机制的输出
x = mha_result
ff = PositionwiseFeedForward(d_model, d_ff, dropout)
ff_result = ff(x)
print(ff_result)
5. 规范化层

它是所有深层网络模型都需要的标准网络层,因为随着网络层数的增加,通过多层的计算后参数可能开始出现过大或过小的情况,这样可能会导致学习过程出现异常,模型可能收敛非常的慢. 因此都会在一定层数后接规范化层进行数值的规范化,使其特征数值在合理范围内.

  • 规范化层的代码实现
# 通过LayerNorm实现规范化层的类
class LayerNorm(nn.Module):def __init__(self, features, eps=1e-6):"""初始化函数有两个参数, 一个是features, 表示词嵌入的维度,另一个是eps它是一个足够小的数, 在规范化公式的分母中出现,防止分母为0.默认是1e-6."""super(LayerNorm, self).__init__()# 根据features的形状初始化两个参数张量a2,和b2,第一个初始化为1张量,# 也就是里面的元素都是1,第二个初始化为0张量,也就是里面的元素都是0,这两个张量就是规范化层的参数,# 因为直接对上一层得到的结果做规范化公式计算,将改变结果的正常表征,因此就需要有参数作为调节因子,# 使其即能满足规范化要求,又能不改变针对目标的表征.最后使用nn.parameter封装,代表他们是模型的参数。self.a2 = nn.Parameter(torch.ones(features))self.b2 = nn.Parameter(torch.zeros(features))# 把eps传到类中self.eps = epsdef forward(self, x):"""输入参数x代表来自上一层的输出"""# 在函数中,首先对输入变量x求其最后一个维度的均值,并保持输出维度与输入维度一致.# 接着再求最后一个维度的标准差,然后就是根据规范化公式,用x减去均值除以标准差获得规范化的结果,# 最后对结果乘以我们的缩放参数,即a2,*号代表同型点乘,即对应位置进行乘法操作,加上位移参数b2.返回即可.mean = x.mean(-1, keepdim=True)std = x.std(-1, keepdim=True)return self.a2 * (x - mean) / (std + self.eps) + self.b2
features = d_model = 512
eps = 1e-6
# 输入x来自前馈全连接层的输出
x = ff_result
ln = LayerNorm(features, eps)
ln_result = ln(x)
print(ln_result)
6. 子层连接结构

如图所示,输入到每个子层以及规范化层的过程中,还使用了残差链接(跳跃连接),因此我们把这一部分结构整体叫做子层连接(代表子层及其链接结构),在每个编码器层中,都有两个子层,这两个子层加上周围的链接结构就形成了两个子层连接结构.

  • 子层连接结构的代码分析
# 使用SublayerConnection来实现子层连接结构的类
class SublayerConnection(nn.Module):def __init__(self, size, dropout=0.1):"""它输入参数有两个, size以及dropout, size一般是都是词嵌入维度的大小, dropout本身是对模型结构中的节点数进行随机抑制的比率, 又因为节点被抑制等效就是该节点的输出都是0,因此也可以把dropout看作是对输出矩阵的随机置0的比率."""super(SublayerConnection, self).__init__()# 实例化了规范化对象self.normself.norm = LayerNorm(size)# 又使用nn中预定义的droupout实例化一个self.dropout对象.self.dropout = nn.Dropout(p=dropout)def forward(self, x, sublayer):"""前向逻辑函数中, 接收上一个层或者子层的输入作为第一个参数,将该子层连接中的子层函数作为第二个参数"""# 我们首先对输出进行规范化,然后将结果传给子层处理,之后再对子层进行dropout操作,# 随机停止一些网络中神经元的作用,来防止过拟合. 最后还有一个add操作, # 因为存在跳跃连接,所以是将输入x与dropout后的子层输出结果相加作为最终的子层连接输出.return x + self.dropout(sublayer(self.norm(x)))
size = 512
dropout = 0.2
head = 8
d_model = 512
# 令x为位置编码器的输出
x = pe_result
mask = Variable(torch.zeros(8, 4, 4))# 假设子层中装的是多头注意力层, 实例化这个类
self_attn =  MultiHeadedAttention(head, d_model)# 使用lambda获得一个函数类型的子层
sublayer = lambda x: self_attn(x, x, x, mask)
sc = SublayerConnection(size, dropout)
sc_result = sc(x, sublayer)
print(sc_result)
print(sc_result.shape)
7. 编码器层

作为编码器的组成单元, 每个编码器层完成一次对输入的特征提取过程, 即编码过程.

# 使用EncoderLayer类实现编码器层
class EncoderLayer(nn.Module):def __init__(self, size, self_attn, feed_forward, dropout):"""它的初始化函数参数有四个,分别是size,其实就是我们词嵌入维度的大小,它也将作为我们编码器层的大小, 第二个self_attn,之后我们将传入多头自注意力子层实例化对象, 并且是自注意力机制, 第三个是feed_froward, 之后我们将传入前馈全连接层实例化对象, 最后一个是置0比率dropout."""super(EncoderLayer, self).__init__()# 首先将self_attn和feed_forward传入其中.self.self_attn = self_attnself.feed_forward = feed_forward# 如图所示, 编码器层中有两个子层连接结构, 所以使用clones函数进行克隆self.sublayer = clones(SublayerConnection(size, dropout), 2)# 把size传入其中self.size = sizedef forward(self, x, mask):"""forward函数中有两个输入参数,x和mask,分别代表上一层的输出,和掩码张量mask."""# 里面就是按照结构图左侧的流程. 首先通过第一个子层连接结构,其中包含多头自注意力子层,# 然后通过第二个子层连接结构,其中包含前馈全连接子层. 最后返回结果.x = self.sublayer[0](x, lambda x: self.self_attn(x, x, x, mask))return self.sublayer[1](x, self.feed_forward)
size = 512
head = 8
d_model = 512
d_ff = 64
x = pe_result
dropout = 0.2
self_attn = MultiHeadedAttention(head, d_model)
ff = PositionwiseFeedForward(d_model, d_ff, dropout)
mask = Variable(torch.zeros(8, 4, 4))
el = EncoderLayer(size, self_attn, ff, dropout)
el_result = el(x, mask)
print(el_result)
print(el_result.shape)
8. 编码器

编码器用于对输入进行指定的特征提取过程, 也称为编码, 由N个编码器层堆叠而成.

  • 编码器的代码分析
# 使用Encoder类来实现编码器
class Encoder(nn.Module):def __init__(self, layer, N):"""初始化函数的两个参数分别代表编码器层和编码器层的个数"""super(Encoder, self).__init__()# 首先使用clones函数克隆N个编码器层放在self.layers中self.layers = clones(layer, N)# 再初始化一个规范化层, 它将用在编码器的最后面.self.norm = LayerNorm(layer.size)def forward(self, x, mask):"""forward函数的输入和编码器层相同, x代表上一层的输出, mask代表掩码张量"""# 首先就是对我们克隆的编码器层进行循环,每次都会得到一个新的x,# 这个循环的过程,就相当于输出的x经过了N个编码器层的处理. # 最后再通过规范化层的对象self.norm进行处理,最后返回结果. for layer in self.layers:x = layer(x, mask)return self.norm(x)
# 第一个实例化参数layer, 它是一个编码器层的实例化对象, 因此需要传入编码器层的参数
# 又因为编码器层中的子层是不共享的, 因此需要使用深度拷贝各个对象.
size = 512
head = 8
d_model = 512
d_ff = 64
c = copy.deepcopy
attn = MultiHeadedAttention(head, d_model)
ff = PositionwiseFeedForward(d_model, d_ff, dropout)
dropout = 0.2
layer = EncoderLayer(size, c(attn), c(ff), dropout)# 编码器中编码器层的个数N
N = 8
mask = Variable(torch.zeros(8, 4, 4))
en = Encoder(layer, N)
en_result = en(x, mask)
print(en_result)
print(en_result.shape)

解码器部分实现



输出部分实现



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

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

相关文章

基于Java中的SSM框架实现远程同步课堂系统项目【项目源码+论文说明】计算机毕业设计

基于Java中的SSM框架实现远程同步课堂系统演示 远程同步课堂系统设计与实现 摘要:在这样一个网络数据大爆炸的时代,人们获取知识、获取信息的通道非常的多元化,通过网络来实现数据信息的获取成为了现在非常常见的一种方式,而通过…

MindSearch:用于增强网络搜索效率的开源人工智能

Web 信息查找与集成是搜索、检索、提取或集成 Web 资源以满足特定需求的活动,是实际生活中几乎所有领域中每个决策和解决问题的实体都必须执行的操作。 大型语言模型 (LLM) 与搜索引擎的集成重新定义了我们在网络上查找和使用信息的方式。因此,LLM 能够…

leetcode递归(203. 移除链表元素)

前言 经过前期的基础训练以及部分实战练习,粗略掌握了各种题型的解题思路。现阶段开始专项练习。 描述 给你一个链表的头节点 head 和一个整数 val ,请你删除链表中所有满足 Node.val val 的节点,并返回 新的头节点 。 示例 1:…

将元组类型的日期时间转换为字符串格式time.asctime([t])

【小白从小学Python、C、Java】 【考研初试复试毕业设计】 【Python基础AI数据分析】 将元组类型的日期时间转换为 字符串格式 time.asctime([t]) [太阳]选择题 根据给定的Python代码,哪个选项是错误的? import time time_tuple (1993, 6, 30, 21, 49…

pytorch下载慢,如何下载到本地再去安装,本地安装pytorch

有时候按部就班的用指令去安装pytorch,网上很慢,并且往往最后可能还没有安装成功。 本次,介绍一下如何将这个文件先下载到本地,然后在去安装。 至于如何安装pytorch,先看一下我之前写的 深度学习环境-------pytorch…

什么是多模态大模型?为什么需要多模态大模型?

“ 多模态大模型,就是支持多种数据格式的模型**”** 很多人都听说过多模态,也知道多模态大模型,但如果让你介绍一下什么是多模态大模型,它有什么优点和缺点,以及为什么需要多模态,这时可能就有点傻眼了。‍…

jupyter项目使用Anaconda环境内核

1、创建虚拟环境 conda create --name myjupyter python3.7 2、进入虚拟环境 conda activate myjupyter 3、切换到自己jupyter notebook项目想在的目录 E: cd E:\first\project\jupyter\jupyter01 4、安装IPython内核包,这是Jupyter Notebook使用Python内核所必需的…

vlunstack-2(复现红日安全-ATT CK实战)

环境搭建 配置信息 DC IP:10.10.10.10 OS:Windows 2012(64) 应用:AD域 WEB IP1:10.10.10.80 IP2:192.168.47.131 OS:Windows 2008(64) 应用:Weblogic 10.3.6MSSQL 2008 PC IP1:10.10…

Chapter 30 多态

欢迎大家订阅【Python从入门到精通】专栏,一起探索Python的无限可能! 文章目录 前言一、基本概念二、抽象类 前言 多态(Polymorphism)是面向对象编程中的核心概念,本章将详细讲解 Python 中多态的实现方式以及如何应用…

科技驱动健康,景联文科技提供高质量高血压数据采集

当前,穿戴手表市场呈现出快速发展趋势,已成为可穿戴设备领域的一个重要组成部分。市场上智能手表的厂商包括小米、华为、苹果、步步高、vivo、努比亚、三六零、科大讯飞、等。 高血压数据采集可为高血压的预防提供支持,持续监测可以帮助用户及…

RabbitMQ高级特性 - 生产者消息确认机制

文章目录 生产者消息确认机制概述confirm 代码实现return 代码实现 生产者消息确认机制 概述 为了保证信息 从生产者 发送到 队列,因此引入了生产者的消息确认机制. RabbitMQ 提供了两种解决方案: 通过事务机制实现.通过发送确认机制(confi…

【第九节】python中xml解析和json编解码

目录 一、Python XML 解析 1.1 什么是XML 1.2 Python 对 XML 的解析方法 1.3 SAX解析xml 1.4 xml.dom解析xml 1.6 ElementTree解析XML 二、Python编解码json 2.1 什么是json 2.2 使用json 库 2.3 使用第三方库Demjson 一、Python XML 解析 1.1 什么是XML XML&#x…

Mouser中元件特性对比功能

搜索所需的元件,并点击比对 在比对界面里搜索所需比对的另外元器件,并比对3.得到的结果

从Vue到Postman全面验证API接口跨域问题

文章目录 1、前言2、跨域问题3、后端服务接口4、接口跨域测试4.1 Vue调用测试4.2 Postman测试 5、服务接口增加注解CrossOrigin解决跨域 1、前言 最近刚接手了一个新项目,业务还没了解全,让开发功能。做了俩接口,postman自测完能拿到数据就给…

影视解说中视频素材哪里找?

想做电影解说类视频,不知道哪里下载高清解说素材? 今天小编就带大家揭秘5大影视解说素材网站,赶紧进来看看吧!~ 1. 稻虎网 作为国内顶尖视频素材网站,稻虎素材网提供了丰富的电影解说视频素材,…

配置Mysql的慢查询日志

一、什么是Mysql慢查询日志 MySQL慢查询日志是MySQL数据库自带的一个功能,用于记录执行时间超过指定阈值的SQL语句,以便于后续的性能优化工作 帮助开发和DBA发现哪些SQL语句需要优化,在哪些地方需要修改,以提高数据库的性能 默认…

生成式AI,在中国工业找到新“活法”

生成式AI,正在经历一场关于落地前景的论战。 在德国中北部城市希尔德斯海姆,世界工业巨头博世已经把生成式AI技术用在了生产线上。他们以AI仿真绘制的方式合成出超过15000张关于产品缺陷的图片,然后将其应用于电机定子生产线的质检。借助生成…

MySQL中的索引——适合创建索引的情况

1.适合创建索引的情况 1、字段的数值有唯一性的限制 2、频繁作为 WHERE 查询条件的字段 某个字段在 SELECT 语句的 WHERE 条件中经常被使用到,那么就需要给这个字段创建索引了。尤其是在数据量大的情况下,创建普通索引就可以大幅提升数据查询的效率。 …

STM32基础篇:I2C通信协议

I2C总线 I2C(IIC---Inter Integrated Circuit 内部集成电路) 以上为I2C大体结构图,其中时钟线SCL用于传输时钟信号,数据线SDA来传输实际的数据。 主机与从机 1.主机的职责 假如由从机1和从机2,从机1想向SDA上发送一…

Java二十三种设计模式-桥接模式(10/23)

桥接模式:解耦抽象与实现的灵活设计 引言 桥接模式(Bridge Pattern)是一种结构型设计模式,用于将抽象部分与其实现部分分离,使它们可以独立地变化。它是一种对象结构型模式,又称为柄体(Handle and Body)模…