输入部分包括两部分:源⽂本嵌⼊层及其位置编码器(左),⽬标⽂本嵌⼊层及其位置编码器(右)。
文本嵌入层将⽂本中词汇的数字表示转变为向量表示,希望在这样的⾼维空间捕捉词汇间的关系。
import torch
import torch.nn as nn
import math
from torch.autograd import Variable
class Embeddings(nn.Module):#定义文本嵌入网络层
def __init__(self,vocab,d_model):#1.定义网络结构和参数。d_model:词嵌入的维度;vocab:词表的大小
super(Embeddings,self).__init__()#2.继承nn.Model的初始化函数
self.lut=nn.Embedding(vocab,d_model)#3.用torch.nn的预定义层Embedding,获得一个词嵌入对象
self.d_model=d_model
def forward(self,x):#运行网络结构
return self.lut(x)*math.sqrt(self.d_model)
d_model=512
vocab=1000
x=Variable(torch.LongTensor([[100,2,421,508],[491,998,1,221]]))#输入2个4纬表示的文本数据
emb=Embeddings(vocab,d_model)#实例化这个结构
emb_out=emb(x)#输入放到定义的embeddings结构里面运行,结果是[2, 4, 512]维度
因为在Transformer的编码器结构中, 并没有针对词汇位置信息的处理,因此需要在Embedding层后加⼊位置编码器,将词汇位置不同可能会产⽣不同语义的信息加⼊到词嵌⼊张量中, 以弥补位置信息的缺失,感觉想图像里面的ax+b里面的b。
class PositionalEncoding(nn.Module):
def __init__(self,d_model,dropout,max_len=5000):#位置编码器的结构,max_len:每个句子的最大长度
super(PositionalEncoding,self).__init__()
self.dropout=nn.Dropout(p=dropout)
pe=torch.zeros(max_len,d_model)#初始化位置编码矩阵
position=torch.arange(0,max_len).unsqueeze(1)#绝对位置矩阵:max_len*1的矩阵
#利用绝对位置矩阵填充pe位置编码矩阵,
div_term=torch.exp(torch.arange(0,d_model,2)*-(math.log(1000.0)/d_model))
pe[:,0::2]=torch.sin(div_term*position)#偶数列的数据填充
pe[:,1::2]=torch.cos(div_term*position)
pe=pe.unsqueeze(0)#外层扩展一个维度
self.register_buffer('pe',pe)#因为位置编码矩阵并不是模型结构中的超参或者参数,不需要随模型的优化更新,所以给他注册成为模型的buffer
def forward(self,x):
x=x+Variable(self.pe[:,:x.size(1)],requires_grad=False)#将矩阵的句子最大长度5000截取和输入句子一样长的长度,然后加到embedding后面
return self.dropout(x)
dropout=0.1
max_len=160
pe=PositionalEncoding(d_model,dropout,max_len)
pe_out=pe(emb_out)
由N个编码器层堆叠⽽成,每个编码器层由两个⼦层连接结构组成:
第⼀个⼦层连接结构包括⼀个多头⾃注意⼒⼦层和规范化层以及⼀个残差连接,
第⼆个⼦层连接结构包括⼀个前馈全连接⼦层和规范化层以及⼀个残差连接。
⾥⾯⼀般只有1和0的元素,代表位置被遮掩或者不被遮掩。训练时会把整个输出结果都⼀次性进⾏Embedding,但是理论上解码器的的输出却不是⼀次就能产⽣最终结果的,⽽是⼀次次通过上⼀次结果综合得出的,因此,未来的信息可能被提前利⽤,所以,我们会进⾏遮掩。
import numpy as np
import torch
import matplotlib.pyplot as plt
def subsequent_mask(size):
attn_shape=(1,size,size)#定义mask是三维矩阵
subsequent_mask=np.triu(np.ones(attn_shape),k=1).astype('uint8')
return torch.from_numpy(1-subsequent_mask)#数值是1的下三角矩阵
size=20
sm=subsequent_mask(size)
plt.figure(figsize=(5,5))
plt.imshow(sm[0], cmap='viridis') # 使用cmap参数指定颜色映射
plt.colorbar()
plt.clim(0, 1)
plt.show()
注意⼒机制是注意⼒计算规则能够应⽤的深度学习⽹络的载体, 除了注意⼒计算规则外,还包括⼀些必要的全连接层以及相关张量处理, 使其与应⽤⽹络融为⼀体。
注意⼒计算规则需要三个指定的输⼊Q(query), K(key), V(value), 然后通过公式得到注意⼒的计算结果,这个结果代表query在key和value作⽤下的表示. ⽽这个具体的计算规则有很多种。
def attention(query,key,value,mask=None,dropout=None):
d_k=query.size(-1)#query的最后一纬尺寸,一般等于词嵌入维度
scores=torch.matmul(query,key.transpose(-2,-1))/math.sqrt(d_k)#通过公式计算得到注意力的得分
if mask is not None:
scores=scores.masked_fill(mask==0,-1e9)
p_attn=F.softmax(scores,dim=-1)
if dropout is not None:
p_attn=dropout(p_attn)
return torch.matmul(p_attn,value),p_attn#返回最终的注意力表示query,和注意力张量
query=key=value=pe_out #三个值初始值一样就是自注意力
mask=Variable(torch.zeros(2,4,4))
attn,p_attn=attention(query,key,value,mask=mask)
使⽤了⼀组线性变化层,即三个变换张量对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__()
assert embedding_dim%head==0
self.d_k=embedding_dim//head
self.head=head
self.linears=clones(nn.Linear(embedding_dim,embedding_dim),4)#q,k,v以及concat一共四个内部变化矩阵
self.attn=None#最后的注意力张量
self.dropout=nn.Dropout(p=dropout)
def forward(self,query,key,value,mask=None):
if mask is not None:
mask=mask.unsqueeze(0)
batch_size=query.size(0)
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))]
x,self.attn=attention(query,key,value,mask=mask,dropout=self.dropout)
x=x.transpose(1,2).contiguous().view(batch_size,-1,self.head*self.d_k)
return self.linears[-1](x)
head=8
embedding_dim=512
dropout=0.2
query=key=value=pe_out
mask=Variable(torch.zeros(8,4,4))#头数,输入的文本数据长度
mha=MultiHeadedAttention(head,embedding_dim,dropout)
mha_out=mha(query,key,value,mask)
print(mha_out.size())#torch.Size([2, 4, 512])
注意⼒机制可能对复杂过程的拟合程度不够, 通过增加两层线性层的全连接⽹络来增强模型的能⼒。
class PositionwiseFeedForward(nn.Module):
def __init__(self,d_model,d_ff,dropout=0.1):#d_ff是第一层的输出维度也是第二层的输入维度
super(PositionwiseFeedForward,self).__init__()
self.w1=nn.Linear(d_model,d_ff)
self.w2=nn.Linear(d_ff,d_model)
self.dropout=nn.Dropout(dropout)
def forward(self,x):
return self.w2(self.dropout(F.relu(self.w1(x))))
d_model=512
d_ff=64
dropout=0.2
ff=PositionwiseFeedForward(d_model,d_ff,dropout)
ff_out=ff(mha_out)
是所有深层⽹络模型都需要的标准⽹络层,因为随着⽹络层数的增加,通过多层的计算后参数可能开始出现过⼤或过⼩的情况,这样可能会导致学习过程出现异常,模型可能收敛⾮常的慢. 因此都会在⼀定层数后接规范化层进⾏数值的规范化,使其特征数值在合理范围内。
class LayerNorm(nn.Module):
def __init__(self,d_model,eps=1e-6):
super(LayerNorm,self).__init__()
self.a2=nn.Parameter(torch.ones(d_model))#缩放参数,nn.Parameter封装代表他们是模型的参数
self.b2=nn.Parameter(torch.zeros(d_model))#位移参数
self.eps=eps
def forward(self,x):
mean=x.mean(-1,keepdim=True)#在最后一个维度上求mean,并保持输出维度与输入维度一致
std=x.std(-1,keepdim=True)
return self.a2*(x-mean)/(std+self.eps)+self.b2
d_model=512
eps=1e-6
ln=LayerNorm(d_model,eps)
ln_out=ln(ff_out)
在每个编码器层中,都有两个⼦层,这两个⼦层加上周围的链接结构就形成了两个⼦层连接结构。感觉像是两个残差网络,这里把多头和前馈抽象成sublayer结构,然后这两个子层就可以封装成一个类。
class SublayerConnection(nn.Module):
def __init__(self,size,dropout=0.1):#size一般是词嵌入维度
super(SublayerConnection,self).__init__()
self.norm=LayerNorm(size)
self.dropout=nn.Dropout(p=dropout)
def forward(self,x,sublayer):
return x+self.dropout(sublayer(self.norm(x)))
size = 512
dropout = 0.2
head = 8
d_model = 512
x=pe_out#x是输入层的位置编码的输出
mask=Variable(torch.zeros(8,4,4))#8个多头,2条4纬向量的输入
self_attn=MultiHeadedAttention(head,d_model)#多头注意力结构
sublayer=lambda x:self_attn(x,x,x,mask)#qkv都是x,所以是自注意力,
sc=SublayerConnection(size,dropout)
sc_out=sc(x,sublayer)#([2, 4, 512])
作为编码器的组成单元, 每个编码器层完成⼀次对输⼊的特征提取过程, 即编码过程。也就是将两个子连接层连接起来的结构,如图所示。
class EncoderLayer(nn.Module):
def __init__(self,size,self_attn,feed_forward,dropout):#size是词嵌入维度
super (EncoderLayer, self).__init__()
self.self_attn=self_attn
self.feed_forward=feed_forward
self.sublayers=clones(SublayerConnection(size,dropout),2)
self.size=size
def forward(self,x,mask):
x=self.sublayers[0](x,lambda x:self.self_attn(x,x,x,mask))
return self.sublayers[1](x,self.feed_forward)
self_attn=MultiHeadedAttention(head,d_model)#第一个子层,多头自注意力
ff=PositionwiseFeedForward(d_model,d_ff,dropout)#第二个子层,前馈全连接
el=EncoderLayer(size,self_attn,ff,dropout)
el_out=el(x,mask)#torch.Size([2, 4, 512])
class Encoder(nn.Module):
def __init__(self,layer,N):#初始化的两个参数分别是编码器层结构和编码器层数
super(Encoder,self).__init__()
self.layers=clones(layer,N)
self.norm=LayerNorm(layer.size)#初始化一个规范化层,用在编码器的最后
def forward(self,x,mask):
for layer in self.layers:
x=layer(x,mask)
return self.norm(x)
N=9#编码器层一共9个
en=Encoder(el,N)
en_out=en(x,mask)#torch.Size([2, 4, 512])
解码器由N个解码器层堆叠⽽成,每个解码器层由三个⼦层连接结构组成:
第⼀个⼦层连接结构包括⼀个多头⾃注意⼒⼦层和规范化层以及⼀个残差连接。
第⼆个⼦层连接结构包括⼀个多头注意⼒⼦层和规范化层以及⼀个残差连接。
第三个⼦层连接结构包括⼀个前馈全连接⼦层和规范化层以及⼀个残差连接。
class DecoderLayer(nn.Module):
def __init__(self,size,self_attn,src_attn,feed_forward,dropout):#词嵌入维度,多头自注意力对象,多头注意力对象,前馈全连接对象
super(DecoderLayer, self).__init__()
self.size=size
self.self_attn=self_attn
self.src_attn=self_attn
self.feed_forward=feed_forward
self.sublayers=clones(SublayerConnection(size,dropout),3)#克隆3个子层连接对象
def forward(self,x,memory,source_mask,target_mask):#上一层的输出,编码器的语义存储变量memory,源数据掩码张量,目标数据掩码张量
x=self.sublayers[0](x,lambda x:self.self_attn(x,x,x,target_mask))#第一个子层,多头自注意力
x=self.sublayers[1](x,lambda x:self.src_attn(x,memory,memory,source_mask))#第二个子层,常规多头注意力
return self.sublayers[2](x,self.feed_forward)
head = 8
size = 512
d_model = 512
d_ff = 64
dropout = 0.2
self_attn = src_attn = MultiHeadedAttention(head, d_model, dropout)
ff = PositionwiseFeedForward(d_model, d_ff, dropout)
x = pe_out#输入层的输出
memory = en_out#编码层的输出
mask = Variable(torch.zeros(8, 4, 4))
source_mask = target_mask = mask
dl = DecoderLayer(size, self_attn, src_attn, ff, dropout)
dl_result = dl(x, memory, source_mask, target_mask)#torch.Size([2, 4, 512])
class Decoder(nn.Module):
def __init__(self,layer,N):#解码器层对象,解码器层数
super(Decoder,self).__init__()
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)
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 = 8
x = pe_out
memory = en_out
de = Decoder(layer, N)
de_result = de(x, memory, source_mask, target_mask)#torch.Size([2, 4, 512])