Transformer简介
Transformer模型的作用
基于seq2seq架构的transformer模型可以完成NLP领域研究的典型任务, 如机器翻译, 文本生成等. 同时又可以构建预训练语言模型,用于不同任务的迁移学习.
在接下来的架构分析中, 我们将假设使用Transformer模型架构处理从一种语言文本到另一种语言文本的翻译工作, 因此很多命名方式遵循NLP中的规则. 比如: Embeddding层将称作文本嵌入层, Embedding层产生的张量称为词嵌入张量, 它的最后一维将称作词向量等.
结构示意图
输入部分
源文本嵌入层及其位置编码器
目标文本嵌入层及其位置编码器
如图中所示的:
输出部分
线性层
softmax层
编码器
由N个编码器层堆叠而成
每个编码器层由两个子层连接结构组成
第一个子层连接结构包括一个多头自注意力子层和规范化层以及一个残差连接
第二个子层连接结构包括一个前馈全连接子层和规范化层以及一个残差连接
解码器
由N个解码器层堆叠而成
每个解码器层由三个子层连接结构组成
第一个子层连接结构包括一个多头自注意力子层和规范化层以及一个残差连接
第二个子层连接结构包括一个多头注意力子层和规范化层以及一个残差连接
第三个子层连接结构包括一个前馈全连接子层和规范化层以及一个残差连接
补充:区别:注意力之一般注意力 (q!=k!=v);自注意力 q=k=v
输入部分实现
torch.Variable()
官方文档链接
参数说明:
tensor:要封装的张量
requires_grad:bool,是否要求计算导数
Varibale三属性:
data:存储了Tensor,是本体的数据
grad:保存了data的梯度,类型Variable而非Tensor,与data形状一致
grad_fn:指向Function对象,用于反向传播的梯度计算之用
简单示例:
from torch.autograd import Variable
x = Variable(tensor, requires_grad = True)
参考链接:
文本嵌入层
作用
无论是源文本嵌入还是目标文本嵌入,都是为了将文本中词汇的数字表示转变为向量表示, 希望在这样的高维空间捕捉词汇间的关系.
代码实现
# 导入必备的工具包
import torch
# 预定义的网络层torch.nn, 工具开发者已经帮助我们开发好的一些常用层,
# 比如,卷积层, lstm层, embedding层等, 不需要我们再重新造轮子.
import torch.nn as nn
# 数学计算工具包
import math
# torch中变量封装函数Variable.
from torch.autograd import Variable
# Embeddings类 实现思路分析
# 1 init函数 (self, d_model, vocab)
# 设置类属性 定义词嵌入层 self.lut层
# 2 forward(x)函数
# self.lut(x) * math.sqrt(self.d_model)
class Embeddings(nn.Module):
def __init__(self, d_model, vocab):
# 参数d_model 每个词汇的特征尺寸 词嵌入维度
# 参数vocab 词汇表大小
super(Embeddings, self).__init__()
self.d_model = d_model
self.vocab = vocab
# 定义词嵌入层
self.lut = nn.Embedding(self.vocab, self.d_model)
def forward(self, x):
# 将x传给self.lut并与根号下self.d_model相乘作为结果返回
# x经过词嵌入后 增大x的值, 词嵌入后的embedding_vector+位置编码信息,值量纲差差不多
return self.lut(x) * math.sqrt(self.d_model)
測試一下
def dm_test_Embeddings():
d_model = 512 # 词嵌入维度是512维
vocab = 1000 # 词表大小是1000
# 实例化词嵌入层
my_embeddings = Embeddings(d_model, vocab)
x = Variable(torch.LongTensor([[100,2,421,508],[491,998,1,221]]))
embed = my_embeddings(x)
print('embed.shape', embed.shape, '\nembed--->\n',embed)
对于传播的单独测试
def forward(self, x):
# 将x传给self.lut并与根号下self.d_model相乘作为结果返回
# x经过词嵌入后 增大x的值, 词嵌入后的embedding_vector+位置编码信息,值量纲差差不多
print("x",x)
print("self.lut(x)", self.lut(x))
print("math.sqrt(self.d_model)", math.sqrt(self.d_model))
return self.lut(x) * math.sqrt(self.d_model)
位置编码器
作用
因为在Transformer的编码器结构中, 并没有针对词汇位置信息的处理,因此需要在Embedding层后加入位置编码器,将词汇位置不同可能会产生不同语义的信息加入到词嵌入张量中, 以弥补位置信息的缺失.
本质
为后面编码层的掩码埋下伏笔
复习dropout层
在每次训练的时候,随机让一定数量的卷积停止工作,这样可以提高网络的泛化能力,Hinton又把它称之为dropout。
做分类的时候,Dropout层一般加在全连接层防止过拟合提升模型泛化能力
代码实现
class PositionalEncoding(nn.Module):
def __init__(self, d_model, dropout, max_len=5000):
# 参数d_model 词嵌入维度 eg: 512个特征
# 参数max_len 单词token个数 eg: 60个单词
super(PositionalEncoding, self).__init__()
# 定义dropout层
self.dropout = nn.Dropout(p=dropout)
# 思路:位置编码矩阵 + 特征矩阵 相当于给特征增加了位置信息
# 定义位置编码矩阵PE eg pe[60, 512], 位置编码矩阵和特征矩阵形状是一样的
pe = torch.zeros(max_len, d_model)
# 定义位置列-矩阵position 数据形状[max_len,1] eg: [0,1,2,3,4...60]^T
position = torch.arange(0, max_len).unsqueeze(1)
# print('position--->', position.shape, position)
# 定义变化矩阵div_term [1,256]
# torch.arange(start=1, end=512, 2)结果并不包含end。在start和end之间做一个等差数组 [0, 2, 4, 6 ... 510]
div_term = torch.exp(torch.arange(0, d_model, 2) * -(math.log(10000.0) / d_model))
# 位置列-矩阵 @ 变化矩阵 做矩阵运算 [60*1]@ [1*256] ==> 60 *256
# 矩阵相乘也就是行列对应位置相乘再相加,其含义,给每一个列属性(列特征)增加位置编码信息
my_matmulres = position * div_term
# print('my_matmulres--->', my_matmulres.shape, my_matmulres)
# 给位置编码矩阵奇数列,赋值sin曲线特征
pe[:, 0::2] = torch.sin(my_matmulres)
# 给位置编码矩阵偶数列,赋值cos曲线特征
pe[:, 1::2] = torch.cos(my_matmulres)
# 形状变化 [60,512]-->[1,60,512]
pe = pe.unsqueeze(0)
# 把pe位置编码矩阵 注册成模型的持久缓冲区buffer; 模型保存再加载时,可以根模型参数一样,一同被加载
# 什么是buffer: 对模型效果有帮助的,但是却不是模型结构中超参数或者参数,不参与模型训练
self.register_buffer('pe', pe)
def forward(self, x):
# 注意:输入的x形状2*4*512 pe是1*60*512 形状 如何进行相加
# 只需按照x的单词个数 给特征增加位置信息
x = x + Variable( self.pe[:,:x.size()[1]], requires_grad=False)
return self.dropout(x)
测试一下:
def dm_test_PositionalEncoding():
d_model = 512 # 词嵌入维度是512维
vocab = 1000 # 词表大小是1000
# 1 实例化词嵌入层
my_embeddings = Embeddings(d_model, vocab)
# 2 让数据经过词嵌入层 [2,4] --->[2,4,512]
x = Variable(torch.LongTensor([[100, 2, 421, 508], [491, 998, 1, 221]]))
embed = my_embeddings(x)
# print('embed--->', embed.shape)
# 3 创建pe位置矩阵 生成位置特征数据[1,60,512]
my_pe = PositionalEncoding(d_model=d_model, dropout=0.1, max_len=60)
# 4 给词嵌入数据embed 添加位置特征 [2,4,512] ---> [2,4,512]
pe_result = my_pe(embed)
print('pe_result.shape--->', pe_result.shape)
print('pe_result--->', pe_result)
编码器
掩码张量
简介
掩代表遮掩,码就是我们张量中的数值,它的尺寸不定,里面一般只有1和0的元素,代表位置被遮掩或者不被遮掩,至于是0位置被遮掩还是1位置被遮掩可以自定义,因此它的作用就是让另外一个张量中的一些数值被遮掩,也可以说被替换, 它的表现形式是一个张量.
作用
有一些生成的attention张量中的值计算有可能已知了未来信息而得到的,未来信息被看到是因为训练时会把整个输出结果都一次性进行Embedding,但是理论上解码器的的输出却不是一次就能产生最终结果的,而是一次次通过上一次结果综合得出的,因此,未来的信息可能被提前利用. 所以,我们会进行遮掩
上三角矩阵和np.triu函数演示、
# nn.triu()函数功能介绍
# def triu(m, k)
# m:表示一个矩阵
# K:表示对角线的起始位置(k取值默认为0)
# return: 返回函数的上三角矩阵
但是上三角和我们的目标相反,python也没有提供下三角的创建方法,这可如何是好?
下三角矩阵的生成
def subsequent_mask(size):
# 产生上三角矩阵 产生一个方阵
subsequent_mask = np.triu(m = np.ones((1, size, size)), k=1).astype('uint8')
# 返回下三角矩阵
return torch.from_numpy(1 - subsequent_mask)
测试一下
def dm_test_subsequent_mask():
# 产生5*5的下三角矩阵
size = 5
sm = subsequent_mask(size)
print('下三角矩阵--->\n', sm)
下三角矩阵--->
tensor([[[1, 0, 0, 0, 0],
[1, 1, 0, 0, 0],
[1, 1, 1, 0, 0],
[1, 1, 1, 1, 0],
[1, 1, 1, 1, 1]]], dtype=torch.uint8)
注意力机制
本文注意力规则
注意
在编码阶段,对注意力权重分布不做掩码
代码实现
def attention(query, key, value, mask=None, dropout=None):
# query, key, value:代表注意力的三个输入张量
# mask:代表掩码张量
# dropout:传入的dropout实例化对象
# 1 求查询张量特征尺寸大小
d_k = query.size()[-1]
# 2 求查询张量q的权重分布socres q@k^T /math.sqrt(d_k)
# [2,4,512] @ [2,512,4] --->[2,4,4]
scores = torch.matmul(query, key.transpose(-2, -1) ) / math.sqrt(d_k)
# 3 是否对权重分布scores 进行 masked_fill
if mask is not None:
# 根据mask矩阵0的位置 对sorces矩阵对应位置进行掩码
scores = scores.masked_fill(mask == 0, -1e9)
# 4 求查询张量q的权重分布 softmax
p_attn = F.softmax(scores, dim=-1)
# 5 是否对p_attn进行dropout
if dropout is not None:
p_attn = dropout(p_attn)
# 返回 查询张量q的注意力结果表示 bmm-matmul运算, 注意力查询张量q的权重分布p_attn
# [2,4,4]*[2,4,512] --->[2,4,512]
return torch.matmul(p_attn, value), p_attn
多头注意力机制
从多头注意力的结构图中,貌似这个所谓的多个头就是指多组线性变换层,其实并不是,我只有使用了一组线性变化层,即三个变换张量对Q,K,V分别进行线性变换,这些变换不会改变原有张量的尺寸,因此每个变换矩阵都是方阵,得到输出结果后,多头的作用才开始显现,每个头开始从词义层面分割输出的张量,也就是每个头都想获得一组Q,K,V进行注意力机制的计算,但是句子中的每个词的表示只获得一部分,也就是只分割了最后一维的词嵌入向量. 这就是所谓的多头,将每个头的获得的输入送到注意力机制中, 就形成多头注意力机制.
代码实现
def clones(module, N):
return nn.ModuleList([copy.deepcopy(module) for _ in range(N)])
class MultiHeadedAttention(nn.Module):
def __init__(self, head, embedding_dim, dropout=0.1):
super(MultiHeadedAttention, self).__init__()
# 确认数据特征能否被被整除 eg 特征尺寸256 % 头数8
assert embedding_dim % head == 0
# 计算每个头特征尺寸 特征尺寸256 // 头数8 = 64
self.d_k = embedding_dim // head
# 多少头数
self.head = head
# 四个线性层
self.linears = clones(nn.Linear(embedding_dim, embedding_dim), 4)
# 注意力权重分布
self.attn = None
# dropout层
self.dropout = nn.Dropout(p = dropout)
def forward(self, query, key, value, mask=None):
# 若使用掩码,则掩码增加一个维度[8,4,4] -->[1,8,4,4]
if mask is not None:
mask = mask.unsqueeze(0)
# 求数据多少行 eg:[2,4,512] 则batch_size=2
batch_size = query.size()[0]
# 数据形状变化[2,4,512] ---> [2,4,8,64] ---> [2,8,4,64]
# 4代表4个单词 8代表8个头 让句子长度4和句子特征64靠在一起 更有利捕捉句子特征
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) ) ]
# myoutptlist_data = []
# for model, x in zip(self.linears, (query, key, value)):
# print('x--->', x.shape) # [2,4,512]
# myoutput = model(x)
# print('myoutput--->', myoutput.shape) # [2,4,512]
# # [2,4,512] --> [2,4,8,64] --> [2,8,4,64]
# tmpmyoutput = myoutput.view(batch_size, -1, self.head, self.d_k).transpose(1, 2)
# myoutptlist_data.append( tmpmyoutput )
# mylen = len(myoutptlist_data) # mylen:3
# query = myoutptlist_data[0] # [2,8,4,64]
# key = myoutptlist_data[1] # [2,8,4,64]
# value = myoutptlist_data[2] # [2,8,4,64]
# 注意力结果表示x形状 [2,8,4,64] 注意力权重attn形状:[2,8,4,4]
# attention([2,8,4,64],[2,8,4,64],[2,8,4,64],[1,8,4,4]) ==> x[2,8,4,64], self.attn[2,8,4,4]]
x, self.attn = attention(query, key, value, mask=mask, dropout=self.dropout)
# 数据形状变化 [2,8,4,64] ---> [2,4,8,64] ---> [2,4,512]
x = x.transpose(1,2).contiguous().view(batch_size, -1, self.head*self.d_k)
# 返回最后变化后的结果 [2,4,512]---> [2,4,512]
return self.linears[-1](x)
测试一下
# 测试多头注意力机制
def dm_test_MultiHeadedAttention():
d_model = 512 # 词嵌入维度是512维
vocab = 1000 # 词表大小是1000
# 输入x 是一个使用Variable封装的长整型张量, 形状是2 x 4
x = Variable(torch.LongTensor([[100, 2, 421, 508], [491, 998, 1, 221]]))
my_embeddings = Embeddings(d_model, vocab)
x = my_embeddings(x)
dropout = 0.1 # 置0比率为0.1
max_len = 60 # 句子最大长度
my_pe = PositionalEncoding(d_model, dropout, max_len)
pe_result = my_pe(x)
head = 8 # 头数head
query = key = value = pe_result # torch.Size([2, 4, 512])
# 输入的掩码张量mask
mask = Variable(torch.zeros(8, 4, 4))
my_mha = MultiHeadedAttention(head, d_model, dropout)
x = my_mha(query, key, value, mask)
print('多头注意机制后的x', x.shape, '\n', x)
print('多头注意力机制的注意力权重分布', my_mha.attn.shape)
前馈全连接层
在Transformer中前馈全连接层就是具有两层线性层的全连接网络
ReLU函数
ReLU(x)=max(0, x)
馈全连接层代码实现
class PositionwiseFeedForward(nn.Module):
def __init__(self, d_model, d_ff, dropout=0.1):
# d_model 第1个线性层输入维度
# d_ff 第2个线性层输出维度
super(PositionwiseFeedForward, self).__init__()
# 定义线性层w1 w2 dropout
self.w1 = nn.Linear(d_model, d_ff)
self.w2 = nn.Linear(d_ff, d_model)
self.dropout = nn.Dropout(p= dropout)
def forward(self, x):
# 数据依次经过第1个线性层 relu激活层 dropout层,然后是第2个线性层
return self.w2(self.dropout(F.relu(self.w1(x))))
规范化层
它是所有深层网络模型都需要的标准网络层,因为随着网络层数的增加,通过多层的计算后参数可能开始出现过大或过小的情况,这样可能会导致学习过程出现异常,模型可能收敛非常的慢. 因此都会在一定层数后接规范化层进行数值的规范化,使其特征数值在合理范围内.
规范化层的代码实现
class LayerNorm(nn.Module):
def __init__(self, features, eps=1e-6):
# 参数features 待规范化的数据
# 参数 eps=1e-6 防止分母为零
super(LayerNorm, self).__init__()
# 定义a2 规范化层的系数 y=kx+b中的k
self.a2 = nn.Parameter(torch.ones(features))
# 定义b2 规范化层的系数 y=kx+b中的b
self.b2 = nn.Parameter(torch.zeros(features))
self.eps = eps
def forward(self, x):
# 对数据求均值 保持形状不变
# [2,4,512] -> [2,4,1]
mean = x.mean(-1,keepdims=True)
# 对数据求方差 保持形状不变
# [2,4,512] -> [2,4,1]
std = x.std(-1, keepdims=True)
# 对数据进行标准化变换 反向传播可学习参数a2 b2
# 注意 * 表示对应位置相乘 不是矩阵运算
y = self.a2 * (x-mean)/(std + self.eps) + self.b2
return y
子层连接结构
输入到每个子层以及规范化层的过程中,还使用了残差链接(跳跃连接),因此我们把这一部分结构整体叫做子层连接(代表子层及其链接结构),在每个编码器层中,都有两个子层,这两个子层加上周围的链接结构就形成了两个子层连接结构.
class SublayerConnection(nn.Module):
def __init__(self, size, dropout=0.1):
# 参数size 词嵌入维度尺寸大小
# 参数dropout 置零比率
super(SublayerConnection, self).__init__()
# 定义norm层
self.norm = LayerNorm(size)
# 定义dropout
self.dropout = nn.Dropout(dropout)
def forward(self, x, sublayer):
# 参数x 代表数据
# sublayer 函数入口地址 子层函数(前馈全连接层 或者 注意力机制层函数的入口地址)
# 方式1 # 数据self.norm() -> sublayer()->self.dropout() + x
myres = x + self.dropout(sublayer(self.norm(x)))
# 方式2 # 数据sublayer() -> self.norm() ->self.dropout() + x
# myres = x + self.dropout(self.norm(x.subtype(x)))
return myres
编码器层
作为编码器的组成单元, 每个编码器层完成一次对输入的特征提取过程, 即编码过程
代码实现
class EncoderLayer(nn.Module):
def __init__(self, size, self_atten, feed_forward, dropout):
super(EncoderLayer, self).__init__()
# 实例化多头注意力层对象
self.self_attn = self_atten
# 前馈全连接层对象feed_forward
self.feed_forward = feed_forward
# size词嵌入维度512
self.size = size
# clones两个子层连接结构 self.sublayer = clones(SublayerConnection(size,dropout),2)
self.sublayer = clones(SublayerConnection(size, dropout) ,2)
def forward(self, x, mask):
# 数据经过第1个子层连接结构
# 参数x:传入的数据 参数lambda x... : 子函数入口地址
x = self.sublayer[0](x, lambda x:self.self_attn(x, x, x, mask))
# 数据经过第2个子层连接结构
# 参数x:传入的数据 self.feed_forward子函数入口地址
x = self.sublayer[1](x, self.feed_forward)
return x
进行测试
def dm_test_EncoderLayer():
d_model = 512
vocab = 1000 # 词表大小是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)
dropout = 0.2
max_len = 60 # 句子最大长度
x = embr # [2, 4, 512]
pe = PositionalEncoding(d_model, dropout, max_len)
pe_result = pe(x)
x = pe_result
size = 512
head = 8
d_ff = 64
# 实例化多头注意力机制类对象
self_attn = MultiHeadedAttention(head, d_model)
# 实例化前馈全连接层对象
ff = PositionwiseFeedForward(d_model, d_ff, dropout)
# mask数据
mask = Variable(torch.zeros(8, 4, 4))
# 实例化编码器层对象
my_encoderlayer = EncoderLayer(size, self_attn, ff, dropout)
# 数据通过编码层编码
el_result = my_encoderlayer(x, mask)
print('el_result.shape', el_result.shape, el_result)
编码器
编码器用于对输入进行指定的特征提取过程, 也称为编码, 由N个编码器层堆叠而成
代码实现
class Encoder(nn.Module):
def __init__(self, layer, N):
# 参数layer 1个编码器层
# 参数 编码器层的个数
super(Encoder, self).__init__()
# 实例化多个编码器层对象
self.layers = clones(layer, N)
# 实例化规范化层
self.norm = LayerNorm(layer.size)
def forward(self, x, mask):
# 数据经过N个层 x = layer(x, mask)
for layer in self.layers:
x = layer(x, mask)
# 返回规范化后的数据 return self.norm(x)
return self.norm(x)
测试
def dm_test_Encoder():
d_model = 512
vocab = 1000 # 词表大小是1000
# 输入x 是一个使用Variable封装的长整型张量, 形状是2 x 4
x = Variable(torch.LongTensor([[100, 2, 421, 508], [491, 998, 1, 221]]))
# writeFile("dafdsafds")
emb = Embeddings(d_model, vocab)
embr = emb(x)
dropout = 0.2
max_len = 60 # 句子最大长度
x = embr # [2, 4, 512]
pe = PositionalEncoding(d_model, dropout, max_len)
pe_result = pe(x)
x = pe_result # 获取位置编码器层 编码以后的结果
size = 512
head = 8
d_model = 512
d_ff = 64
c = copy.deepcopy
attn = MultiHeadedAttention(head, d_model)
dropout = 0.2
ff = PositionwiseFeedForward(d_model, d_ff, dropout)
layer = EncoderLayer(size, c(attn), c(ff), dropout)
# 编码器中编码器层的个数N
N = 6
mask = Variable(torch.zeros(8, 4, 4))
# 实例化编码器对象
en = Encoder(layer, N)
en_result = en(x, mask)
print('en_result.shape--->', en_result.shape)
print('en_result--->',en_result )
解码器
解码器构成
由N个解码器层堆叠而成
每个解码器层由三个子层连接结构组成
第一个子层连接结构包括一个多头自注意力子层和规范化层以及一个残差连接
第二个子层连接结构包括一个多头注意力子层和规范化层以及一个残差连接
第三个子层连接结构包括一个前馈全连接子层和规范化层以及一个残差连接
解码器层
作用
作为解码器的组成单元, 每个解码器层根据给定的输入向目标方向进行特征提取操作,即解码过程
代码实现
class DecoderLayer(nn.Module):
def __init__(self, size, self_attn, src_attn, feed_forward, dropout):
super(DecoderLayer, self).__init__()
# 词嵌入维度尺寸大小
self.size = size
# 自注意力机制层对象 q=k=v
self.self_attn = self_attn
# 一遍注意力机制对象 q!=k=v
self.src_attn = src_attn
# 前馈全连接层对象
self.feed_forward = feed_forward
# clones3子层连接结构
self.sublayer = clones(SublayerConnection(size, dropout), 3)
def forward(self, x, memory, source_mask, target_mask):
m = memory
# 数据经过子层连接结构1
x = self.sublayer[0](x, lambda x:self.self_attn(x, x, x, target_mask))
# 数据经过子层连接结构2
x = self.sublayer[1](x, lambda x:self.src_attn (x, m, m, source_mask))
# 数据经过子层连接结构3
x = self.sublayer[2](x, self.feed_forward)
return x
解码器
根据编码器的结果以及上一次预测的结果, 对下一次可能出现的’值’进行特征表
class Decoder(nn.Module):
def __init__(self, layer, N):
# 参数layer 解码器层对象
# 参数N 解码器层对象的个数
super(Decoder, self).__init__()
# clones N个解码器层
self.layers = clones(layer, N)
# 定义规范化层
self.norm = LayerNorm(layer.size)
def forward(self, x, memory, source_mask, target_mask):
# 数据以此经过各个子层
for layer in self.layers:
x = layer(x, memory, source_mask, target_mask)
# 数据最后经过规范化层
return self.norm(x)
測試一下
def dm_test_Decoder():
d_model = 512
vocab = 1000 # 词表大小是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)
dropout = 0.2
max_len = 60 # 句子最大长度
x = embr # [2, 4, 512]
pe = PositionalEncoding(d_model, dropout, max_len)
pe_result = pe(x)
x = pe_result # 获取位置编码器层 编码以后的结果
# 分别是解码器层layer和解码器层的个数N
size = 512
d_model = 512
head = 8
d_ff = 64
dropout = 0.2
c = copy.deepcopy
# 多头注意力对象
attn = MultiHeadedAttention(head, d_model)
# 前馈全连接层
ff = PositionwiseFeedForward(d_model, d_ff, dropout)
# 解码器层
layer = DecoderLayer(d_model, c(attn), c(attn), c(ff), dropout)
N = 6
# 输入参数与解码器层的输入参数相同
x = pe_result
# 产生编码器结果
en_result = demo238_test_Encoder()
memory = en_result
# 掩码对象
mask = Variable(torch.zeros(8, 4, 4))
# sorce掩码 target掩码
source_mask = target_mask = mask
# 创建 解码器 对象
de = Decoder(layer, N)
# 解码器对象 解码
de_result = de(x, memory, source_mask, target_mask)
print(de_result)
print(de_result.shape)
输出部分
包含内容
线性层:通过对上一步的线性变化得到指定维度的输出, 也就是转换维度的作用
softmax层:使最后一维的向量中的数字缩放到0-1的概率值域内, 并满足他们的和为1
线性层和softmax层的代码实现
class Generator(nn.Module):
def __init__(self, d_model, vocab_size):
# 参数d_model 线性层输入特征尺寸大小
# 参数vocab_size 线层输出尺寸大小
super(Generator, self).__init__()
# 定义线性层
self.project = nn.Linear(d_model, vocab_size)
def forward(self, x):
# 数据经过线性层 最后一个维度归一化 log方式
x = F.log_softmax(self.project(x), dim=-1)
return x
Tansformer模型构建
make_model函数初始化一个一个组件对象(轮子对象),调用EncoderDecoder()函数
def make_model(source_vocab, target_vocab, N=6,
d_model=512, d_ff=2048, head=8, dropout=0.1):
"""
make_model函数实现思路分析
函数原型 (source_vocab, target_vocab, N=6, d_model=512, d_ff=2048, head=8, dropout=0.1)
实例化多头注意力层对象 attn
实例化前馈全连接对象ff
实例化位置编码器对象position
构建 EncoderDecoder对象(Encoder对象, Decoder对象,
source端输入部分nn.Sequential(),
target端输入部分nn.Sequential(),
线性层输出Generator)
对模型参数初始化 nn.init.xavier_uniform_(p)
注意使用 c = copy.deepcopy
返回model
"""
c = copy.deepcopy
# 实例化多头注意力层对象
attn = MultiHeadedAttention(head=8, embedding_dim= 512, dropout=dropout)
# 实例化前馈全连接对象ff
ff = PositionwiseFeedForward(d_model=d_model, d_ff=d_ff, dropout=dropout)
# 实例化 位置编码器对象position
position = PositionalEncoding(d_model=d_model, dropout=dropout)
# 构建 EncoderDecoder对象
model = EncoderDecoder(
# 编码器对象
Encoder( EncoderLayer(d_model, c(attn), c(ff), dropout), N),
# 解码器对象
Decoder(DecoderLayer(d_model, c(attn), c(attn), c(ff),dropout), N),
# 词嵌入层 位置编码器层容器
nn.Sequential(Embeddings(d_model, source_vocab), c(position)),
# 词嵌入层 位置编码器层容器
nn.Sequential(Embeddings(d_model, target_vocab), c(position)),
# 输出层对象
Generator(d_model, target_vocab))
for p in model.parameters():
if p.dim() > 1:
nn.init.xavier_uniform_(p)
return model
测试一下:
def dm_test_make_model():
source_vocab = 500
target_vocab = 1000
N = 6
my_transform_modelobj = make_model(source_vocab, target_vocab,
N=6, d_model=512, d_ff=2048, head=8, dropout=0.1)
print(my_transform_modelobj)
# 假设源数据与目标数据相同, 实际中并不相同
source = target = Variable(torch.LongTensor([[1, 2, 3, 8], [3, 4, 1, 8]]))
# 假设src_mask与tgt_mask相同,实际中并不相同
source_mask = target_mask = Variable(torch.zeros(8, 4, 4)) #
mydata = my_transform_modelobj(source, target, source_mask, target_mask)
print('mydata.shape--->', mydata.shape)
print('mydata--->', mydata)