2 Transformer 模型的输入部分

2. Transformer 模型的输入部分

目标:

  • 掌握文本嵌入层和位置编码层的作用
  • 掌握文本嵌入层和位置编码层的实现过程

输入部分包含:

  • 源文本嵌入层及其位置编码
  • 目标文本嵌入层及其位置编码

image.png


2.1 Transformer的文本嵌入层(词嵌入)

  • 文本嵌入层的作用: 文本嵌入层是为了实现将文本中词汇的数字表示转为向量表示,从而希望在这种更高维度的空间中捕捉词汇间的关系。

文本嵌入层的代码实现:

import imp
from cycler import V
import torch    
import torch.nn as nn
import torch.nn.functional as F
from torch.autograd import Variable
import numpy as np
import math
import copy
import matplotlib.pyplot as plt

# embedding = nn.Embedding(10,3)
# input = torch.LongTensor([[1,2,4,5],[4,3,2,9]])
# print(embedding(input))
# print(embedding(input).shape)
# print(embedding(input).shape[-1])

# embedding = nn.Embedding(10,3, padding_idx=0)
# input = torch.LongTensor([[0,2,0,5],[0,3,2,9]])
# print(embedding(input))

print('-------------------')
# 构建 Embedding 类来实现文本嵌入曾
class Embeddings(nn.Mod9ule):
    def __init__(self, d_model, vocab):
        # d_model: 词嵌入的维度
        # vocab: 词表大小
        super(Embeddings, self).__init__()
        self.lut = nn.Embedding(vocab, d_model)
        self.d_model = d_model
    def forward(self, x):
        return self.lut(x) * math.sqrt(self.d_model)
d_model = 512
vocab = 100
x = Variable(torch.LongTensor([[1,2,4,5],[4,3,2,9]]))

# 实例化 Embeddings
embedding = Embeddings(d_model, vocab)
embr = embedding(x)
print(embr)
print(embr.shape)

2.2 Transformer的位置编码层

  • 位置编码层的作用:

    • 位置编码层是为了赋予句子单词一个独有的位置编码表示,位置编码是通过正弦余弦函数分别将偶数位置和奇数位置编码出来(都是唯一的)。
    • 位置编码可以看作是一个常量,其不参与模型的反向传播过程。位置编码是预先计算好的。
  • 位置编码层的实现:

image.png

2.3 编码器部分实现

2.3.1 掩码张量

  • 什么是掩码张量?
    • 掩码表示遮掩掉张量中的一些数值。掩码张量的尺寸不是固定的,掩码张量中通常只有1,0两种数值,其表现形式是张量。
  • 掩码张量的作用?
    • 掩码张量的作用就是用来让另一个张量中的某些数值被遮掩(也可以说是被替换)。
  • 掩码张量的实现过程:

2.3.2 注意力机制

  • 什么是注意力计算规则和注意力机制?
    • 注意力计算规则有三类,我们只讲一类;
    image.png
    • 注意力是指 针对一个事物(query),我们利用其关键信息(key),得到自己的理解(value)这一过程。
    • 注意力机制是指应用注意力的载体,即使用注意力计算规程进行注意力计算的网络结构,我们都能称为注意力机制。
    • Q、K、V的理解:以“段落主旨总结”为例,我们设段落为Q,则段落中的关键词就为K。基于段落信息Q,并利用其关键词K所得到的段落主旨,称之为V。
    • 其中Q、K、V通常是通过对输入数据进行线性变换得到的,而这些线性变换是由可学习的权重矩阵W_Q、W_K、W_V(也称为线性变换矩阵)完成的。
    • 注意力机制的实现过程:

image.png

2.3.3 多头注意力机制(MHA)

  • 什么是多头注意力机制?
    • 多头注意力(Multi-Head Attention)机制是 Transformer 模型中的关键组件,它不是指多个独立的注意力层,而是指在一个注意力层内部并行地执行多个注意力计算过程,每个过程称为一个“头”(head)。这些头相互独立,各自专注于输入数据的不同表示子空间(或者说,从不同的角度或特征维度上关注输入信息),然后将所有头的结果合并起来,以此来增强模型的注意力能力,使其能捕捉更丰富和多样的上下文依赖关系。
    • 每个head仅关注每个词嵌入的一部分,即只分割最后一维的词嵌入向量。
    • 在多头注意力机制中,有四组线性变换层(linear(input=embedding_dim,output=embedding_dim) 层)用来转换query、key、value矩阵以及用作最后的线性转换。
    • 多头注意力层(Multi-Head Attention Layer)的参数主要包括以下几类:查询(Query)、键(Key)、值(Value)的线性变换权重矩阵W_Q、W_K、W_V缩放系数(Scaling Factor)最终输出的线性变换矩阵Dropout层的丢弃概率 (一个超参数,决定了在训练期间随机丢弃多少比例的激活单元)。
  • 多头注意力机制的作用:
    • 让各个头去优化词嵌入的不同特征部分,以缓解单头注意力机制下可能会产生的偏差,同时也可以帮助模型学到更多元的语义信息。

2.3.5 前馈全连接层

  • 什么是前馈全连接层?
    • 在Transformer中,前馈全连接层表示一个具有两层线性层的全连接网络;
  • 前馈全连接层的作用是什么?
    • 前馈全连接层能够增强注意力机制对于复杂过程的拟合,能提高模型的性能。
  • 前馈全连接层的代码实现PositionwiseFeedForward()
    • PositionwiseFeedForward的实例化参数有d_model(词嵌入维度)、d_ff(线性变换维度)、dropout(置零比例(我喜欢写成dropout_rate));
    • PositionwiseFeedForward的输入参数是x,x表示上一层的输出;
    • PositionwiseFeedForward的输入参数size和其输出结果size一致。
  • 前馈全连接层的代码实现
# 构建前馈全连接层网络
class PositionwiseFeedForward(nn.Module):
    def __init__(self,d_model,d_ff,dropout_rate=0.1):
        # d_model: 词嵌入的维度
        # d_ff: 前馈全连接层的维度
        # dropout: Dropout的概率
        super(PositionwiseFeedForward,self).__init__()
        # 实例化两个线性层对象
        self.L1=nn.Linear(d_model,d_ff)
        self.L2=nn.Linear(d_ff,d_model)
        # 实例化Dropout对象
        self.dropout=nn.Dropout(dropout_rate)
    def forward(self,x):
        # x 表示上一层的输出
        return self.L2(self.dropout(F.relu(self.L1(x))))
    
# 定义相关参数
d_model=512
d_ff=64
dropout=0.2
x=mha_result
print("mha_result size:",mha_result.size())
# 实例化PositionwiseFeedForward
ff = PositionwiseFeedForward(d_model,d_ff,dropout)
# 输出前馈全连接层的结果
ff_result = ff(x)
print("---------------------------")
print("--------ff_result----------")
print(ff_result)
print("ff_result size:",ff_result.size())

2.3.5 规范化层

  • 规范化层的作用:

    • 随着网络层数的增加,经过多层网络计算后的结果值可能会出现值过大或过小的情况,这种情况可能会导致模型的学习过程出现异常,如模型收敛速度变慢。因此规范化层的作用就是保证这些结果值处于正常合理范围内。
  • LayNorm的实例化参数:

    • feature:词嵌入的维度(d_model);
    • eps: 极小的正值
  • LayNorm的输入和输出:

    • 输入x:上一网络层的输出
    • 输出是经过规范化的特征表示
    • LayNorm的输入和输出size一致
  • LayNorm的主要参数:

    • 缩放因子
    • 位移参数
    • 上述两个参数都是模型的参数,在反向传播的过程中会被优化。
  • 代码实现:

# 构建规范化层类
class LayNorm(nn.Module):
    def __init__(self, features, eps):
        super(LayNorm,self).__init__()
        # 定义两个辅助变量 a2 b2
        # a2:缩放因子
        # b2:位移参数
        self.a2 = nn.Parameter(torch.ones(features))
        self.b2 = nn.Parameter(torch.zeros(features))
        # 将极小正数值eps引入类中
        self.eps = eps
    def forward(self,x):
        # LayNorm中的forward主要实现对词嵌入维度的均值和方差的计算
        # 并同时保证计算过程中输入和输出维度的一致
        # 之后再利用规范化公式进行计算
        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=ff_result
print(ff_result)
# 实例化LayNorm对象
ln = LayNorm(features=features,eps=eps)
ln_result = ln(x)
print("---------------------------")
print("--------ln_result----------")
print(ln_result)
print("ln_result size:",ln_result.size())

2.3.6 子层连接结构

  • 子层连接结构简称子层,其包含残差连接、归范化层以及相应子层网络的集合,如下图所示就是一个完整的子层连接结构:

image.png

  • 子层连接结构是编码器和解码器的重要组成部分,其在编码器中有两个,在解码器中有三个。
  • 子层连接结构SublayerConnection的主要参数
    • 实例化参数
      • size:词嵌入的维度(d_model)
      • dropout_rate:Dropout的置零比率
    • 输入参数
      • x:子层连接结构的输入
      • sublayer:表示子层连接结构对应的子网络(如Multi-HeadAttention网络)
  • 子层连接结构的输入和输出的size一致
  • 代码实现
# 实现子层连接结构
# 子层连接结构即残差连接、规范化层以及相应的子层结合体
class SublayerConnection(nn.Module):
    def __init__(self, size, dropout_rate=0.1):
        # size:词嵌入的维度
        # dropout_rate:Dropout的概率
        super(SublayerConnection,self).__init__()
        # 实例化一个规范化层对象
        self.norm = LayNorm(size,eps=1e-6)
        # 实例化一个Dropout对象
        self.dropout = nn.Dropout(dropout)
        # 将size传入类中
        self.size = size
    def forward(self,x,sublayer):
        # x: 子层连接的输入
        # sublayer: 相应的子层网络

        # 子层连接的输入和输出的size是一致的
        return x+self.dropout(sublayer(self.norm(x)))
    

# 相关变量
size=d_model=512
head=8
dropout_rate=0.2
x=pe_result
mask=Variable(torch.zeros(8,4,4))
self_attn=MultiHeadAttention(head,d_model,dropout_rate)

sublayer=lambda x: self_attn(x,x,x,mask) 

# 实例化子层连接对象
sc = SublayerConnection(size,dropout_rate)
sc_result=sc(x,sublayer)
print("---------------------------")
print("--------sc_result----------")
print(sc_result)
print(sc_result.size())

  • 编码器层的作用:

    • 编码器的组成单元;
    • 编码器层用来完成一次对输入特征的提取过程,即编码过程
  • 编码器层的构成图

image.png

  • 编码器层的主要参数

    • EncoderLayer的实例化参数:

      • size: 词嵌入的维度
      • self_attn: 多头注意力对象
      • feedforward: 前馈全连接对象
      • dropout_rate: Dropout的概率
    • EncoderLayer的输入参数:

      • x:代表子层连接结构的输入
      • mask:掩码张量
  • 数据x经过编码器层之后,其size不会发生改变。

  • 编码器层的代码实现:

# 构建编码器层
class EncoderLayer(nn.Module):
    def __init__(self,size,self_attn,feedforward,dropout_rate):
        # size: 词嵌入的维度
        # self_attn: 多头注意力对象
        # feedforward: 前馈全连接对象
        # dropout_rate: Dropout的概率
        super(EncoderLayer,self).__init__()
        # 将多头注意力对象传入类中
        self.self_attn = self_attn
        # 将前馈全连接对象传入类中
        self.feedforward = feedforward
        # 将词嵌入维度传入类中
        self.size = size
        # 实例化子层连接对象
        self.sublayer = clones(SublayerConnection(size,dropout_rate),2)
    def forward(self,x,mask):
        # x:代表子层连接结构的输入
        # mask:掩码张量
        # EncoderLayer中的foeward函数主要用来实现x在多头注意力子层结构和前馈全连接层子层结构间的传递
        x=self.sublayer[0](x,lambda x: self.self_attn(x,x,x,mask))
        return self.sublayer[1](x,self.feedforward)
    
    
# 相关参数
size=d_model=512
head=8
dropout_rate=0.2
d_ff=64
x=pe_result

# 实例化相关网络层
self_attn=MultiHeadAttention(head,d_model,dropout_rate)
feed_forward=PositionwiseFeedForward(d_model,d_ff,dropout_rate=dropout_rate)
mask=Variable(torch.zeros(8,4,4))
# 实例化EncoderLayer
el=EncoderLayer(size,self_attn,feed_forward,dropout_rate)
el_result=el(x,mask)
print("---------------------------")
print("--------el_result----------")
print(el_result)
print(el_result.size())

2.3.8 编码器

  • 编码器的作用:
    • 用于对输入(词嵌入)进行指定的特征提取,即编码过程;
    • 由N个编码器层组成
  • 编码器结构图

image.png

  • 编码器Encoder的主要参数
    • Encoder的实例化参数
      • layer: 编码器层的实例化对象
      • N: 编码器层的个数
    • Encoder的输入参数
      • x:Encoder的输入,即词嵌入pe_result
      • mask:掩码张量
    • Encoder的关键参数
      • self.layers = clones(layer,N)
  • 代码实现
# 构建编码器类
class Encoder(nn.Module):
    def __init__(self,layer,N):
        # layer: 编码器层的实例化对象
        # N: 编码器层的个数
        
        super(Encoder,self).__init__()
        # 使用深度克隆函数clones将layer克隆N个
        self.layers = clones(layer,N)
        # 实例化规范化层对象
        self.norm = LayNorm(layer.size)

    def forward(self,x,mask):
        # x:Encoder的输入,即词嵌入pe_result
        # mask:掩码张量
        # Encoder的forward函数主要用来实现多个EncoderLayer的堆叠
        # x会依次经过各个编码器层,并在最后经过一次规范化层再输出
        for layer in self.layers:
            x=layer(x,mask)

        return self.norm(x)
    
# 相关变量
size=d_model=512
head=8
dropout_rate=0.2
x=pe_result
d_ff=64
N=8
mask=Variable(torch.zeros(8,4,4))
# 实例化相关网络结构 
c =copy.deepcopy
attn=MultiHeadAttention(head=head,embedding_dim=d_model)
ff=PositionwiseFeedForward(d_model=d_model,d_ff=d_ff,dropout_rate=dropout)
layer=EncoderLayer(size=size,self_attn=c(attn),feedforward=c(ff),dropout_rate=dropout_rate)

# 实例化Encoder
en = Encoder(layer,N)
en_result = en(x,mask)
print("---------------------------")
print("--------en_result----------")
print(en_result)
print(en_result.size())

2.4 解码器部分

2.4.1 解码器层

  • 解码器层的作用:
    • 是构成解码器的组成单元
    • 每个解码器层都会根据给定的输入,向目标方向进行特征提取,即解码过程;
    • 每个解码器层都由三个子层连接结构构成,分别为:
      • 解码器多头自注意力子层;
      • 编码器-解码器多头注意力子层;
      • 前馈全连接子层
  • 解码器层的结构图:

image.png

  • 代码实现:
# 构建解码器层
class DecoderLayer(nn.Module):
    def __init__(self,size,self_attn,src_attn,feed_forward,dropout_rate):
        # size:词嵌入的维度
        # self_attn:解码器自注意力实例化对象
        # src_attn:编码器-解码器注意力实例化对象
        # feed_forward:前馈全连接实例化对象
        # dropout_rate:Dropout的概率
        super(DecoderLayer,self).__init__()

        # 将参数传入类中
        self.size = size
        self.self_attn = self_attn
        self.src_attn = src_attn
        self.feed_forward = feed_forward
        self.dropout_rate = dropout_rate
        # 使用clones函数实例化子层连接对象
        self.layer=clones(SublayerConnection(size,dropout_rate),3)

    def forward(self,x,memory,source_mask,target_mask):
        # x:解码器的输入
        # memory:编码器的输出
        # source_mask:源数据的掩码张量
        # target_mask:目标数据的掩码张量

        m=memory
        # x经过解码器多头自注意力子层结构
        x=self.layer[0](x,lambda x:self.self_attn(x,x,x,target_mask))
        # x经过编码器-解码器多头注意力子层结构
        x=self.layer[1](x,lambda x:self.src_attn(x,m,m,source_mask))
        # x经过前馈全连接子层结构
        return self.layer[2](x,self.feed_forward)
    

# 相关参数
head=8
size=d_model=512
dropout_rate=0.2
d_ff=64
x=pe_result
memory=en_result
source_mask=target_mask=Variable(torch.zeros(8,4,4))

# 实例化相关网络
self_attn=src_attn=MultiHeadAttention(head=head,embedding_dim=d_model,dropout_rate=dropout_rate)
ff=PositionwiseFeedForward(d_model=d_model,d_ff=d_ff,dropout_rate=dropout_rate)
# 实例化解码器层
dl=DecoderLayer(size=size,self_attn=self_attn,src_attn=src_attn,feed_forward=ff,dropout_rate=dropout_rate)
dl_result=dl(x=x,memory=memory,source_mask=source_mask,target_mask=target_mask)
print("---------------------------")
print("--------dl_result----------")
print(dl_result)
print(dl_result.size())

2.4.2 解码器

  • 解码器的作用:

    • 根据编码器的结果以及解码器上一次预测结果,对下一次可能出现的值进行特征表示
  • 解码器的结构图:

image.png

  • 解码器的代码实现:
# 构建解码器
class Decoder(nn.Module):
    def __init__(self,layer,N):
        # layer:解码器层DecoderLayer的实例化对象
        # N:解码器层的个数

        super(Decoder,self).__init__()
        # 使用clones克隆N个解码器层
        self.layers = clones(layer,N)
        # 实例化规范化层对象
        self.norm=LayNorm(layer.size)

    def forward(self,x,memory,source_mask,target_mask):
        # x:表示目标数据的嵌入表示
        # memory:表示编码器的输出
        # source_mask:源数据的掩码张量
        # target_mask:目标数据的掩码张量

        # x 会依次经过N个解码器层
        for layer in self.layers:
            x=layer(x=x,memory=memory,source_mask=source_mask,target_mask=target_mask)
        return self.norm(x)
    

# 相关变量
size=d_model=512
head=8
dropout_rate=0.2
d_ff=64
self_attn=src_attn=MultiHeadAttention(head=head,embedding_dim=d_model,dropout_rate=dropout_rate)
ff=PositionwiseFeedForward(d_model=d_model,d_ff=d_ff,dropout_rate=dropout_rate)
# Decoder的实例化变量
N=8
layer=DecoderLayer(size=size,self_attn=c(self_attn),src_attn=c(src_attn),feed_forward=c(ff),dropout_rate=dropout_rate)
# Decoder的输入变量
x=pe_result
memory=en_result
source_mask=target_mask=Variable(torch.zeros(8,4,4))

# 实例化Decoder对象
de=Decoder(layer=layer,N=N)
de_result=de(x=x,memory=memory,source_mask=source_mask,target_mask=target_mask)
print("---------------------------")
print("--------de_result----------")
print(de_result)
print(de_result.size())

2.5 输出部分

  • 输出部分的组成:线性层和softmax层
  • 线性层的作用:转换维度
  • softmax的作用:将最后一维中的向量中数值缩放到0-1的概率区间内,并满足他们之和为1
  • nn.functional工具包中包含的是只计算,而没有参数的网络层
  • 输出层的网络结构:

image.png

  • 代码实现
import torch.nn.functional as F
# 构建Transformer的输出部分
class Generator(nn.Module):
    def __init__(self,d_model,vocab_size):
        # d_model:词嵌入的维度
        # vocab:词表大小
        super(Generator,self).__init__()
        # 实例化线性层对象
        self.proj=nn.Linear(d_model,vocab_size)

    def forward(self,x):
        # x:表示Transformer的输出
        # 返回线性层的输出
        return F.log_softmax(self.proj(x),dim=-1)
    

# 输出部分Generator的实例化参数
d_model=512
vocab_size=1000

# 输出部分Generator的输入参数
x=de_result
# 实例化输出部分
gen = Generator(d_model=d_model,vocab_size=vocab_size)
gen_result=gen(x)
print("---------------------------")
print("--------gen_result----------")
print(gen_result)
print(gen_result.size())
print(gen_result[1])
posted @ 2024-05-09 21:52  wuhaoliu  阅读(32)  评论(0)    收藏  举报  来源