【神经网络与深度学习】Transformer模型

视频来源:
Tranformer实战
Transformer从零详细解读

1 Transformer总体架构图

在这里插入图片描述

1.1 输入部分:

  • 源文本嵌入层及其位置编码器
  • 目标文本嵌入层及其位置编码器
    在这里插入图片描述

1.1.1 文本嵌入层

无论是源文本嵌入层还是目标文本嵌入层,都是为了将文本中词汇的数字表示转变为向量表示,希望在这样的高维空间捕捉词汇间的关系。

代码分析:

#导入必备的工具包
import torch
#预定义的网络层torch.nn,工具开发者已经帮助我们开发好的一些常用层
#比如,卷积层,lstm层,embedding层等,不需要我们再重新造轮子
import torch.nn as nn
#数学计算工具包
import math
#torch中变量封装函数Variable
from torch.autograd import Variable

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

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

#定义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.lut
        self.lut = nn.Embedding(vocab,d_model)
        #最后就是将d_model传入类中
        self.d_model = d_model

    def forward(self,x):
        '''
        可以将其理解为该层的前向传播逻辑,所有层中都会有此函数
        当传给该类的实例化对象参数时,自动调用该类函数
        :param x: 因为Embedding层是首层,所以代表输入给模型的文本通过词汇映射后的数字张量
        :return:
        '''
        #将x传给self.lut并与根号下self.d_module相乘作为结果返回
        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)
print(embr.shape)

输出结果:
在这里插入图片描述

1.1.2 位置编码器

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

  • 位置编码公式
    在这里插入图片描述
    其中的2i表示偶数,2i+1表示奇数。偶数用sin,奇数用cos。

在这里插入图片描述

代码分析

class PositionEmbedding(nn.Module):
    def __init__(self, d_model, dropout, max_len=5000):
        '''
        位置编码器类的初始化函数,共有三个参数
        :param d_model: 词嵌入维度
        :param dropout: 置0比率
        :param max_len: 每个句子的最大长度
        '''
        super(PositionEmbedding, self).__init__()

        # 实例化nn中的预定义的Dropout层,并将dropout传入其中,获得对象self.dropout
        # Dropout 是一种常用的正则化技术,用于在深度学习模型中减少过拟合
        self.dropout = nn.Dropout(p=dropout)

        # 初始化一个位置编码矩阵,它是一个0阵,矩阵的大小是max_len × d_model 的矩阵
        pe = torch.zeros(max_len, d_model)

        # 初始化一个绝对位置矩阵,在我们这里,词汇的绝对位置就是用它的索引去表示
        # 所以我们首先使用arange方法获得一个连续自然数向量,然后再使用unsqueeze方法拓展向量维度
        # 又因为参数传的是1,代表矩阵拓展的位置,会使向量变成一个max_len × 1 的矩阵,在后面增加一列,成为二维矩阵
        position = torch.arange(0, max_len).unsqueeze(1)

        # 绝对位置矩阵初始化之后,接下来就是考虑如何将这些位置信息加入到位置编码矩阵中
        # 最简单的思路就是先将max_len × 1的绝对位置矩阵,变换成max_len × d_model形状,然后覆盖原来的初始位置编码矩阵即可。
        # 要做这种矩阵变换,就需要一个1 × d_model形状的变换矩阵div_term,我们对这个变换矩阵的要求除了形状外,
        # 还希望它能够将自然数的绝对位置编码缩放成足够小的数字,有助于在之后的梯度下降过程中更快的收敛,这样我们就可以开始初始化
        # 首先使用arange获得一个自然数矩阵,但是我们这里有了一个跳跃,只初始化了一半即1 × d_model/2的矩阵
        # 我们可以把它看作初始化了两次,而每次初始化的变换矩阵会做不同的处理,第一次初始化的变换矩阵分布在正弦波上,第二次初始化
        # 并把这两个矩阵分别填充在位置编码矩阵的偶数和奇数位置上,组成最终的位置编码矩阵
        # 在 Transformer 模型中引入位置信息,使模型可以感知输入序列中不同位置的相对关系
        div_term = torch.exp(torch.arange(0, d_model, 2) *  # 偶数
                             -(math.log(10000.0) / d_model))  # 缩放数字,调整大小
        # 将前面定义的变化矩阵进行奇数、偶数的分别赋值
        pe[:, 0::2] = torch.sin(position * div_term)  # 偶数列,将 position 乘以对应的 div_term,然后再求 sin
        pe[:, 1::2] = torch.cos(position * div_term)  # 奇数列

        # 这样我们就得到了位置编码矩阵pe,pe现在还只是一个二维矩阵,要想和embedding的输出(一个
        # 就必须拓展一个维度,所以这里使用unsqueeze拓展维度
        pe = pe.unsqueeze(0)  # 在第0个位置添加新维度,成三维

        # 最后把pe位置编码矩阵注册成模型的buffer,什么是buffer呢
        # 我们把它认为是对模型效果有帮助的,但是却不是模型结构中超参数或者参数,不需要随着优化步骤进行更新的增益对象
        # 注册之后我们就可以在模型保存后重加载时和模型结构与参数一同被加载。
        self.register_buffer('pe', pe)

    def forward(self, x):
        '''
        :param x:表示文本序列的词嵌入表示
        :return:
        '''
        # 在想加之前对pe做一些适配工作,将这个三维张量的第二维也就是句子最大长度的那一维将切片到与输入的x的第二维相同,即x.size(1),
        # 因为我们默认max+len为5000太大了,所以要进行与输入张量的适配.
        # 最后使用Variable进行封装,使其与x的样式相同,但是它是不需要进行梯度求解的,因此把requests_grad设置成False
        x = x + Variable(self.pe[:, :x.size(1)],requires_grad=False)  # 位置编码不参与更新
        # 最后使用self.dorpout对象进行‘丢弃’操作,并返回结果
        return self.dropout(x)


d_model = 512
dropout = 0.1
max_len = 60
x = embr
pe = PositionEmbedding(d_model, dropout, max_len)
pe_result = pe(x)
print(pe_result)
print(pe_result.shape)

输出结果:
在这里插入图片描述
绘制曲线
绘制词汇向量中特征的分布曲线代码:

#绘制词汇向量中特征的分布曲线
#创建一张15 × 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维的值
#代码中的 np.arange(100) 创建了一个包含从 0 到 99 的整数序列的 NumPy 数组,作为 x 轴的值。
# 而 y[0,:,4:8].data.numpy() 抽取了 y 张量中第一个样本、所有行、列索引范围在 4 到 7 之间的部分,并将其转换为 NumPy 数组。
plt.plot(np.arange(100),y[0,:,4:8].data.numpy())

#在画布上填写维度提示信息
plt.legend(["dim %d"%p for p in [4,5,6,7]])

plt.show()

输出效果:
在这里插入图片描述

输出效果分析:

  • 每条颜色的曲线代表一个词汇中的特征在不同位置的含义。
  • 保证同一词汇随着所在位置不同它对应位置嵌入向量会发生变化。
  • 正弦波和余弦波的值域范围都是1到-1,这又很好的控制了嵌入数值的大小,有助于梯度的快速计算。

1.2 编码器部分:

  • 由N个编码器层堆叠而成
  • 每个编码器层由两个子层连接结构组成
  • 第一个子层连接结构包括一个多头自注意力子层和规范化层以及一个残差连接
  • 第二个子层连接结构包括一个前馈全连接子层和规范化层以及一个残差连接
    在这里插入图片描述

1.2.1 掩码张量

什么是掩码张量?

  • 掩代表遮掩,码就是我们张量中的数值,它的尺寸不定,里面一般只有1和0的元素,代表位置被遮掩或者不被遮掩,至于是0位置被遮掩还是1位置被遮掩可以自定义,因此它的作用就是让另外一个张量中的一些数值被遮掩,也可以说是被替换,它的表现形式是一个张量。

掩码张量的作用:

  • 在transformer中,掩码张量的主要作用在应用attention时,有一些生成的attention张量中的计算有可能已知了未来信息而得到的,未来信息被看到是因为训练时会把整个输出结果都一次性进行Embedding,但是理论上解码器的输出却不是一次就能产生最终结果的,而是一次次通过上一次结果综合得出的,因此,未来的信息可能被提前利用。所以,我们会进行遮掩

生成掩码张量代码分析:

#---------------------------------生成掩码张量--------------------------------------------
#np.triu演示
# a = np.triu([[1,2,3],[4,5,6],[7,8,9],[10,11,12]],k=-1)
# b = np.triu([[1,2,3],[4,5,6],[7,8,9],[10,11,12]],k=0)
# c = np.triu([[1,2,3],[4,5,6],[7,8,9],[10,11,12]],k=1)
# print(a)
# print(b)
# print(c)

def subsequent_mask(size):
    '''
    生成向后遮掩的掩码张量,参数size是掩码张量最后两个维度的大小,他的最后两维形成一个方阵
    :param size:
    :return:
    '''
    #在函数中,首先定义掩码张量的形状
    attn_shape  = (1,size,size)

    #然后使用np.ones方法向这个形状中添加1元素,形成上三角阵,最后为了节约空间
    #再使其中的数据类型变为无符号8位整型uint8
    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中的位置由0变为0
    return torch.from_numpy(1-subsequent_mask)

size = 5
sm = subsequent_mask(size)
print("sm:",sm)

#掩码张量的可视化
plt.figure(figsize=(5,5))
plt.imshow(subsequent_mask(20)[0])
plt.show()

输出结果:
在这里插入图片描述
在这里插入图片描述

输出效果分析:

  • 黄色是1的部分,代表被遮掩;紫色代表没被遮掩的信息
  • 横坐标代表目标词汇的位置;纵坐标代表可查看的位置

1.2.2 注意力机制

什么是注意力?

  • 我们在观察事物时,之所以能够快速判断一种事物(当然允许判断是错误的),是因为我们大脑能够很快把注意力放在事物最具有辨识度的部分从而做出判断,而并非是从头到尾的观察一遍事物后,才能有判断结果。正是基于这样的理论,就产生了注意力机制。

什么是注意力计算规则?

  • 它需要指定的输入Q(query),K(key),V(value),然后通过公式得到注意力的计算结果,这个结果代表query在key和value作用下的表示,此处只介绍一种:
    在这里插入图片描述
  • Q是一段准备被概括的文本;K是给出的提示;V是大脑中的对提示K的延伸。
  • 当Q=K=V时,称作自注意力机制。

举例说明一:图片中的婴儿在干嘛?
在这里插入图片描述
将图片划分为“左上”、“左下”、“右上”、“右下”四部分。
婴儿就是Q,分的区域对应的某种向量为K,各个区域中的内容对应的某种向量为V。
在这里插入图片描述
举例说明二:输入的Q是“爱”,输入的K矩阵是“我不爱你”四个字对应的某种向量。
在这里插入图片描述
下面是具体的计算过程:
在这里插入图片描述
实际代码中使用矩阵进行计算,得到注意力
在这里插入图片描述

什么是注意力机制?

  • 注意力机制是注意力计算规则能够应用的深度学习网络的载体,除了注意力计算规则外,还包括一些必要的全连接层以及相关张量处理,使其与应用网络融为一体,使用自注意力计算规则的注意力机制称为自注意力机制。

在这里插入图片描述
注意力机制代码分析:

#---------------------------------注意力计算规则--------------------------------------------
#tensor.masked_fill演示:
# input = Variable(torch.randn(5,5))
# print(input)
# mask = Variable(torch.zeros(5,5))
# print(mask)
# then = input.masked_fill(mask==0,-1e9)
# print(then)

def attention(query,key,value,mask=None,dropout=None):
    '''
    注意力机制的实现,输入分别是query,key,value
    :param mask: 掩码张量
    :param dropout: nn.Dropout层的实例化对象,默认为None
    '''
    #在函数中,首先取query的最后一维的大小,一般情况下就等同于我们的词嵌入维度,命名为d_k
    d_k = query.size(-1)
    # 按照注意力公式,将query与key的转置相乘,这里面key是将最后两个维度进行转置,再除以缩放系数
    # 得到注意力得分张量scores
    scores = 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对象
    #第二个参数通过指定dim=-1,将softmax操作应用于最后一个维度(key_length维度),即对每个查询向量中的注意力权重进行归一化。
    #这样获得最终的注意力张量
    p_attn = F.softmax(scores,dim = -1)

    #之后判断是否使用dropout进行随机置0
    if 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
#不带mask
attn,p_attn = attention(query,key,value)
print("attn:",attn)
print("p_attn:",p_attn)

输出结果:
在这里插入图片描述
带mask的代码:

#请自行将不带mask的行注释掉,再加入以下代码
#带有mask的输入参数
#令mask为一个2×4×4的零张量
mask = Variable(torch.zeros(2,4,4))
attn,p_attn = attention(query,key,value,mask=mask)

输出结果:
在这里插入图片描述

1.2.3 多头注意力机制

什么是多头注意力机制?

  • 从多头注意力的结构图中,貌似这个所谓的多个头就是指多组线性变换层,其实并不是,只使用了一组线性变换层,即三个变换张量对QKV分别进行线性变换,这些变换不会改变原有张量的尺寸,因此每个变换矩阵都是方阵,得到输出结果后,多头的作用才开始显现,每个头开始从词义层面分割输出的张量,也就是每个头都想获得一组QKV进行注意力机制的计算,但是句子中的每个词的表示只获得一部分,也就是只分割了最后一维的词嵌入向量,这就是所谓的多头,将每个头的获得的输入送到注意力机制中,就形成多头注意力机制。
  • 总的来说就是,为了达到使得建模能力更强,表征空间更丰富的目的。多头自注意力机制又多组Q,K,V构成,每组单独计算一个attention向量,把每组的attention向量拼起来,并进入一个FFN(前馈神经网络)得到最终的向量。

实际代码中用矩阵进行计算,多头注意力机制就使用多套参数
在这里插入图片描述
在这里插入图片描述

多头注意力机制结构图:
在这里插入图片描述

多头注意力机制的作用:

  • 这种结构设计能让每个注意力机制去优化每个词汇的不同特征部分,从而均衡同一种注意力机制可能产生的偏差,让词义拥有来自更多元的表述,实验表明可以从而提升模型效果。

多头注意力机制的代码分析:

#---------------------------------多头注意力机制--------------------------------------------

#tensor.view演示:
# x = torch.randn(4,4)
# print(x.size())
# y = x.view(16)
# print(y.size())
# z = x.view(-1,8)  #-1是自适应
# print(z.size())
#
# a = torch.randn(1,2,3,4)      #两大块,每块三行四列
# print(a.size())
# print(a)
# b = a.transpose(1,2)  #第二个维度和第三个维度互换
# print(b.size())
# print(b)
# c = a.view(1,3,2,4)   #变为三大块,每块两行四列
# print(c.size())
# print(c)
# print(torch.equal(b,c))   #b和c并不一样

#torch.transpose演示:
x = torch.randn(2,3)
# print(x)
# print(torch.transpose(x,0,1))

#首先需要定义克隆函数,因为在多头注意力机制的实现中,用到多个结构相同的线性层
#我们将使用clone函数将他们一同初始化在一个网络层列表对象中,之后的结构中也会用到该函数
def clones(module,N):
    '''
    用于生成相同网络层的克隆函数
    :param module:表示要克隆的目标网络层
    :param N:需要克隆的数量
    :return:
    '''
    #在函数中,我们通过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):
        '''
        在类的初始化时,会传入三个参数
        :param head: 代表头数
        :param enbedding_dim: 代表词嵌入的维度
        :param dropout: 代表进行dropout操作时置0比率,默认是0.1
        '''
        super(MultiHeadedAttention,self).__init__()

        #在函数中,首先使用了一个测试中常用的assert语句,判断h是否能够被d_model整除
        #这是因为我们之后要给每个头分配等量的词特征,也就是embedding_dim,head个。
        assert embedding_dim % head == 0

        #得到每个头获得的分割词向量维度d_k
        self.d_k = embedding_dim // head

        #传入头像h
        self.head = head
        self.embedding_dim = embedding_dim

        #然后获得线性层对象,通过nn的Linear实例化,它的内部变换矩阵是embedding_dim × embedding_dim,
        #为什么是四呢,这是因为在多头注意力中,Q,K,V各需要一个,最后拼接的矩阵还需要一个,
        self.linears = clones(nn.Linear(embedding_dim,embedding_dim),4)

        #self.attn为None,它代表最后得到的注意力张量,现在还没有结果所以为None
        self.attn = None

        #最后就是一个self.dropout对象,它通过nn中的Dropout实例化而来,置零比率为传进来的参数
        self.dropout = nn.Dropout(p=dropout)

    def forward(self,query,key,value,mask=None):
        '''
        前向逻辑函数,它的输入参数有四个,前三个就是注意力机制需要的Q,K,V
        :param mask: 掩码张量,默认是None
        '''

        #如果存在掩码张量mask
        if mask is not None:
            #使用unsqueeze拓展维度,代表多头中的第n头
            mask = mask.unsqueeze(1)

        #接着,我们获得一个batch_size的变量,它是query尺寸的第1个数字,代表有多少条样本。
        batch_size = query.size(0)

        #之后就进入多头处理环节
        #首先利用zip将输入QKV与三个线性层组到一起,然后使用了for循环,将输入QKV分别传到线性层中,
        #做完线性变换后,开始为每个头分割输入,这里使用view方法对线性变换的结果进行维度重塑
        #这样就意味着每个头可以获得一部分词特征组成的句子,其中的-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)

#实例化若干参数
head = 8
embedding_dim = 512
dropout = 0.2

#若干输入参数的初始化
query = key = value = pe_result
mask = Variable(torch.zeros(2,4,4))
mha = MultiHeadedAttention(head,embedding_dim,dropout)
mha_result = mha(query,key,value,mask)
print(mha_result)
print(mha_result.shape)

输出结果:
在这里插入图片描述

1.2.4 前馈全连接层

什么是前馈全连接层?

  • 在Transformer中前馈全连接层就是具有两层线性层的全连接网络。用于对输入数据进行非线性变换和特征提取。
  • 在神经网络中,全连接层指的是上一层的所有节点都连接到下一层的每一个节点。而前馈表示数据是单向传播的,即从输入层经过各隐藏层最终到输出层。
  • 前馈全连接层由两部分组成:首先是线性变换,然后是非线性激活函数。

前馈全连接层的作用:

  • 考虑注意力机制可能对复杂过程的拟合程度不够,通过增加两层网络来增强模型的能力

前馈全连接层的代码分析:

#---------------------------------前馈全连接层--------------------------------------------
#通过类PositionwiseFeedForward来实现前馈全连接层
class PositionwiseFeedForward(nn.Module):
    def __init__(self,d_model,d_ff,dropout=0.1):
        '''
        初始化函数有三个参数
        :param d_model: 第一个线性层的输入维度,也是第二个线性层的输出维度
        :param d_ff: 第二个线性层的输入维度和第一个线性层的输出维度
        :param dropout: 置0比率
        '''
        super(PositionwiseFeedForward,self).__init__()

        #首先按照我们预期使用nn实例化了两个线性层对象,self.w1和self.w2
        #它们的参数分别是d_model,d_ff和d_ff,d_model
        self.w1 = nn.Linear(d_model,d_ff)
        self.w2 = nn.Linear(d_ff,d_model)
        #然后使用nn的Dropout实例化了对象self.dropout
        self.dropout = nn.Dropout(dropout)

    def forward(self,x):
        '''
        :param x:代表来自上一层的输出
        '''
        #首先经过第一个线性层,然后使用Functional中relu函数进行激活
        #之后再使用dropout进行随机置0,最后通过第二个线性层w2,返回最终结果
        return self.w2(self.dropout(F.relu(self.w1(x))))

d_model = 512
#线性变化的维度
d_ff = 64
dropout = 0.2
x = mha_result
ff = PositionwiseFeedForward(d_model,d_ff,dropout)
ff_result = ff(x)
print(ff_result)
print(ff_result.shape)

输出结果:
在这里插入图片描述

ReLu函数公式:ReLu(x)=max(0,x)
ReLu函数图像:
在这里插入图片描述

1.2.5 规范化层

规范化层的作用:

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

规范化层的代码分析:

#---------------------------------规范化层--------------------------------------------
#通过LayerNorm实现规范化层类
class LayerNorm(nn.Module):
    def __init__(self,features,eps=1e-6):
        '''
        初始化函数有两个参数
        :param features: 表示词嵌入的维度
        :param 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 = eps

    def forward(self,x):
        '''
        :param 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 = ff_result
ln = LayerNorm(features,eps)
ln_result = ln(x)
print(ln_result)
print(ln_result.shape)

输出结果:
在这里插入图片描述

1.2.6 子层连接结构

什么是子层连接结构?

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

子层连接结构图:
在这里插入图片描述
在这里插入图片描述
子层连接结构的代码分析:

#--------------------------------子层连接结构-------------------------------------------
#使用SublayerConnection来实现子层连接结构的类
class SublayerConnection(nn.Module):
    def __init__(self,size,dropout=0.1):
        '''
        :param size: 词嵌入维度的大小
        :param dropout: 本身是对模型结构中的节点数进行随机抑制的比率,又因为节点被抑制等效就是该节点的输出都是0,
        因此也可以把dropout看作是对输出矩阵的随机
        '''
        super(SublayerConnection,self).__init__()
        #实例化了规范化对象self.norm
        self.norm = LayerNorm(size)
        #又使用nn中预定的dropout实例化一个self.dropout对象
        self.dropout = nn.Dropout(dropout)

    def forward(self,x,sublayer):
        '''
        :param x: 接收上一层或子层的输入作为第一个参数
        :param sublayer: 该子层连接中的子层函数作为第二个参数
        :return:
        '''
        #首先对输出进行规范化,然后将结果传给子层处理,在对子层进行dropout操作
        #随即停止一些网络中神经元的作用,来防止过拟合,最后还有一个add操作
        #因为存在跳跃连接,所以是将输入x与dropout后的子层输出结果相加作为最终的子层连接输出
        return x+self.dropout(sublayer(self.norm(x)))

size = d_model = 512
dropout = 0.2
head = 8
#令x为位置编码器的输出
x = pe_result
mask = Variable(torch.zeros(2,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)

输出结果:
在这里插入图片描述

1.2.7 编码器层

编码器层的作用:

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

编码器层的构成图:
在这里插入图片描述
编码器层的代码分析:

#--------------------------------编码器层------------------------------------------
#使用EncoderLayer类实现编码器层
class EncoderLayer(nn.Module):
    def __init__(self,size,self_attn,feed_forward,dropout):
        '''
        它的初始化函数参数有四个
        :param size: 词嵌入维度的大小,它也将作为我们编码
        :param self_attn: 之后我们将传入多头自注意力子层实例化对象,并且是自注意力机制
        :param feed_forward: 之后我们将传入前馈全连接层实例化对象
        :param dropout: 置零比率
        '''
        super(EncoderLayer, self).__init__()

        #首先将self_attn和feed_forward传入其中
        self.self_attn = self_attn
        self.feed_forward = feed_forward

        #如图所示,编码器层中有两个子层连接结构,所以使用clones函数进行克隆
        self.sublayer = clones(SublayerConnection(size,dropout),2)
        #把size传入其中
        self.size = size

    def forward(self,x,mask):
        '''
        :param x: 上一层的输出
        :param mask: 掩码张量
        :return:
        '''
        #里面就是按照结构图左侧流程,首先通过第一个子层连接结构,其中包含多头自注意力子层,
        #然后通过第二个子层连接结构,其中包含前馈全连接子层,最后返回结果
        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(2,4,4))

#调用
el = EncoderLayer(size,self_attn,ff,dropout)
el_result = el(x,mask)
print(el_result)
print(el_result.shape)

输出结果:
在这里插入图片描述

1.2.8 编码器

编码器的作用:

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

编码器的结构图:
在这里插入图片描述

编码器的代码分析:

#--------------------------------编码器------------------------------------------
#使用Encoder类来实现编码器
class Encoder(nn.Module):
    def __init__(self,layer,N):
        '''
        初始化函数有两个参数
        :param layer: 编码器层
        :param 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函数的输入和编码器层相同
        :param x: 代表上一层的输出
        :param mask: 代表掩码张量
        :return:
        '''
        #首先就是对我们克隆的编码器层进行循环,每次都会得到一个新的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(2,4,4))

#调用
en = Encoder(layer,N)
en_result = en(x,mask)
print(en_result)
print(en_result.shape)

输出结果:
在这里插入图片描述

1.2.9 残差连接

如下图,X代表词向量,将X与位置编码器进行对位相加后得到新的X,经过注意力层得到输出结果Z。残差就是将经过位置编码之后的新的X与Z对位相加,最后经过LayerNorm函数作为输出。
在这里插入图片描述
详细了解残差,x作为输入,经过两层网络后的输出为F(x),如果没有残差网络则直接输出的是F(x),有残差网络的话,将x原封不动的拿过来,和F(x)进行对位相加,将最终的结果输出。
在这里插入图片描述

1.3 解码器部分:

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

在这里插入图片描述

说明:

  • 解码器层中的各个部分,如,多头注意力机制、规范化层、前馈全连接网络、子层连接结构都与编码器中的实现相同,因此这里可以直接拿来构建解码器层。

Encoder生成K,V矩阵,Decoder生成Q矩阵,Q,K,V进行交互生成多头注意力机制。
在这里插入图片描述

1.3.1 解码器层

解码器层的作用:

  • 作为解码器的组成单元,每个解码器层根据给定的输入向目标方向进行特征提取操作,即解码过程

解码器的代码实现:

#--------------------------------解码器层------------------------------------------
#使用DecoderLayer的类实现解码器层
class DecoderLayer(nn.Module):
    def __init__(self,size,self_attn,src_attn,feed_forward,dropout):
        '''
        初始化函数的参数有5个
        :param size: 词嵌入的维度大小,同时也代表解码器层的尺寸
        :param self_attn: 多头自注意力对象,也就是说这个注意力机制需要Q=K=V
        :param src_attn: 多头注意力对象,这里Q!=K=V
        :param feed_forward: 前馈全连接层对象
        :param dropout: 置零比率
        '''
        super(DecoderLayer, self).__init__()
        #在初始化函数中,主要就是将这些传入到类中
        self.size = size
        self.self_attn = self_attn
        self.src_attn = src_attn
        self.feed_forward = feed_forward
        #按照结构图使用clones函数克隆三个字层连接对象
        self.sublayer = clones(SublayerConnection(size,dropout),3)

    def forward(self,x,memory,source_mask,targer_mask):
        '''
        有4个参数
        :param x:来自上一层的输入
        :param memory: 来自编码器层的语义存储变量
        :param source_mask: 源数据掩码张量
        :param targer_mask: 目标数据掩码张量
        :return:
        '''
        #将memory表示成m方便之后使用
        m = memory

        #将x传入第一个子层结构,第一个子层结构的输入分别是x和self-attn函数,因为是自注意力机制,所以Q,K,V都是x
        #最后一个参数就是目标函数掩码张量,这是要对目标函数进行遮掩,因为此时模型可能还没有生成任何目标数据,
        #比如在解码器准备生成第一个字符或词汇时,我们其实已经传入了第一个字符以便计算损失
        #但是我们不希望在生成第一个字符或词汇时模型能利用这个信息,因此我们会将其遮掩,同样生成第二个字符或词汇时,
        #模型只能使用第一个字符或词汇信息,第二个字符以及之后的信息都不允许被模型使用
        x = self.sublayer[0](x,lambda x:self.self_attn(x,x,x,targer_mask))

        #接着进入第二个子层,这个子层中常规的注意力机制,q是输入x:k,v是编码器层输出memory
        #同样也传入source_mask,但是进行源数据遮掩的原因并非是抑制信息泄露,而是遮蔽掉对结果没有意义的字符而产生的注意力值,
        #以此提升模型效果和训练速度,这样就完成了第二个子层处理
        x = self.sublayer[1](x,lambda x:self.src_attn(x,x,x,source_mask))

        #最后一个子层就是前馈全连接子层,经过它的处理后就可以返回结果,这就是我们的解码器层结构
        return self.sublayer[2](x,self.feed_forward)

#实例化参数
head = 8
size = 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是来自目标数据的词嵌入表示 ,但形势和源数据的词嵌入表示相同,这里使用per充当,
x = pe_result

#memory是来自编码器的输出
memory = en_result

#实际中source_mask和target_mask并不相同,这里为了方便计算使他们都为mask
mask = Variable(torch.zeros(2,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)
print(dl_result)
print(dl_result.shape)

输出结果:
在这里插入图片描述

1.3.2 解码器

解码器的作用:

  • 根据解码器的结果以及上一次预测的结果,对下一次可能出现的‘值’进行特曾表示。

解码器的代码分析:

#--------------------------------解码器------------------------------------------
#使用Decoder类来实现解码器
class Decoder(nn.Module):
    def __init__(self,layer,N):
        '''
        初始化函数的参数有两个
        :param layer: 解码器层
        :param N: 解码器层的个数
        '''
        super(Decoder, self).__init__()
        #首先使用clones方法克隆了N个layer,然后实例化了一个规范化层
        #因为数据走过了所有的解码器层后最后要做规范化处理
        self.layers = clones(layer,N)
        self.norm = LayerNorm(layer.size)

    def forward(self,x,memory,source_mask,target_mask):
        '''
        有4个参数
        :param x:代表目标数据的嵌入表示
        :param memory: 编码器层的输出
        :param source_mask: 源数据的掩码张量
        :param target_mask: 目标数据的掩码张量
        :return:
        '''
        #然后就是对每个层进行循环,当然这个循环就是变量x通过每一个层的处理,
        #得出最后的结果,在进行一次规范化返回即可
        for layer in self.layers:
            x = layer(x,memory,source_mask,target_mask)
        return self.norm(x)

size = 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 = 8

#输入参数与解码器层的输入参数相同
x = pe_result
memory = en_result
mask = Variable(torch.zeros(2,4,4))
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)

输出结果:
在这里插入图片描述

1.4 输出部分:

  • 线性层
  • softmax层:提取出概率最大的值
    在这里插入图片描述

线性层的作用:

  • 通过对上一步的线性变化得到指定维度的输出,也就是转换维度的作用

softmax 层的作用:

  • 使最后一维的向量中的数字缩放到 0-1 的概率值域内,并满足他们的和为 1

线性层和softmax层的代码分析:

#--------------------------------线性层和softmax层------------------------------------------
#nn.Linear演示:
m = nn.Linear(20,30)
input = torch.randn(128,20)
output = m(input)
print(output.size())

#将线性层和softmax计算层一起实现,因为二者的共同目标是生成最后的结构
#因此把类的名字叫做Generator,生成器类
class Generator(nn.Module):
    def __init__(self,d_module,vocab_size):
        '''
        初始化函数的输入参数有两个
        :param d_module: 词嵌入的维度
        :param vocab_size: 词表大小
        '''
        super(Generator,self).__init__()
        #首先就是使用nn中的预定义线性层进行实例化,得到一个对象self.project等待使用,
        #这个线性层的参数有两个,就是初始化函数传进来的两个参数:d_model,vocab_size
        self.project = nn.Linear(d_module,vocab_size)

    def forward(self,x):
        '''
        前向逻辑函数
        :param x: 上一层的输出张量
        :return:
        '''
        #在函数中,首先使用上一步得到的self.project对x进行线性变化,
        #然后使用F中已经实现的log_softmax进行的softmax处理
        #在这里之所以使用log_softmax是因为和我们这个pytorch版本的损失函数实现有关,在其他版本
        #log_softmax就是对softmax的结果又取了对数,因为对数函数是单调递增函数
        #因此对最终我们取最大的概率值没有影响,最后返回结果即可
        return F.log_softmax(self.project(x),dim=-1)

#实例化参数
#词嵌入维度是512维
d_model = 512

#词表的大小是1000
vocab_size = 1000

#输入x是上一层网络的输出,我们使用来自解码器层的输出
x = de_result

#调用
gen = Generator(d_model,vocab_size)
gen_result = gen(x)
print(gen_result)
print(gen_result.shape)

输出结果:
在这里插入图片描述

1.5 模型构建

通过上面的内容,我们已经完成了所有组成部分的实现,接下来就来实现完整的编码器-解码器结构。
Transformer总体架构图:
在这里插入图片描述

编码器-解码器结构的代码实现:

#--------------------------------编码器-解码器结构------------------------------------------
#使用EncoderDecoder类来实现编码器-解码器结构
class EncoderDecoder(nn.Module):
    def __init__(self,encoder,decoder,source_embed,target_embed,generator):
        '''
        初始化函数中有5个参数
        :param encoder: 编码器对象
        :param decoder: 解码器对象
        :param source_embed: 源数据嵌入函数
        :param target_embed: 目标数据嵌入函数
        :param generator: 输出部分的生成器对象
        '''
        super(EncoderDecoder, self).__init__()
        #将参数传入到类中
        self.encoder = encoder
        self.decoder = decoder
        self.src_embed = source_embed
        self.tgt_embed = target_embed
        self.generator = generator

    def forward(self,source,target,source_mask,target_mask):
        '''
        有四个参数
        :param source: 源数据
        :param target: 目标数据
        :param source_mask: 源数据掩码张量
        :param target_mask: 目标数据掩码张量
        :return:
        '''
        #在函数中,将source,soource_mask传入编码函数,得到结果后
        #与source_mask,target,和target_mask一同传给解码函数
        return self.decode(self.encode(source,source_mask),source_mask,target,target_mask)

    def encode(self,source,source_mask):
        '''
        编码函数
        :param source:
        :param source_mask:
        :return:
        '''
        #使用src_embed对source做处理,然后和source_mask一起传给self.encoder
        return self.encoder(self.src_embed(source),source_mask)

    def decode(self,memory,source_mask,target,target_mask):
        '''
        解码函数
        :param memory: 编码器的输出
        :param source_mask:
        :param target:
        :param target_mask:
        :return:
        '''
        #使用tgt_embed对target做处理,然后和source_mask,target_mask,memory一起传给self.decoder
        return self.decoder(self.tgt_embed(target),memory,source_mask,target_mask)

#实例化参数
vocab_size = 1000
d_model = 512
encoder = en
decoder = de
source_embed = nn.Embedding(vocab_size,d_model)
target_embed = nn.Embedding(vocab_size,d_model)
generator = gen

#假设源数据与目标数据相同个,实际中并不相同
source = target = Variable(torch.LongTensor([[100,2,421,508],[491,998,1,221]]))

#假设src_mask与tgt_mask相同,实际中并不相同
source_mask = target_mask = Variable(torch.zeros(2,4,4))

#调用
ed = EncoderDecoder(encoder,decoder,source_embed,target_embed,generator)
ed_result = ed(source,target,source_mask,target_mask)
print(ed_result)
print(ed_result.shape)

输出结果:
在这里插入图片描述
接着将基于以上结构构建用于训练的模型。
Transformer模型构建过程的代码分析:

#--------------------------------构建Transformer模型------------------------------------------
#nn.init.xavier_uniform演示:
#结果服从均匀分布U(-a,a)
w = torch.empty(3,5)
w = nn.init.xavier_uniform_(w,gain=nn.init.calculate_gain('relu'))
# print(w)


def make_model(source_vocab,target_vocab,N=6,d_model=512,d_ff=2048,head=8,dropout=0.1):
    '''
    该函数用来构建模型,有7个参数
    :param source_vocab: 源数据特征(词汇)总数
    :param target_vocab: 目标数据特征(词汇)总数
    :param N: 编码器和解码器对叠数
    :param d_model: 词向量映射维度
    :param d_ff: 前馈全连接网络中变换矩阵的维度
    :param head: 多头注意力结构中的多头数
    :param dropout: 置零比率
    :return:
    '''
    #首先得到一个深度拷贝命令,接下来很多结构都需要进行深度拷贝
    #来保证他们彼此之间相互独立,不受干扰
    c = copy.deepcopy

    #实例化了多头注意力类,得到对象attn
    attn = MultiHeadedAttention(head,d_model)

    #然后实例化前馈全连接类,得到对象ff
    ff = PositionwiseFeedForward(d_model,d_ff, dropout)

    #实例化位置编码类,得到对象position
    position = PositionalEncoding(d_model, dropout)

    #根据结构图,最外层是EncoderDecoder,在EncoderDecoder中,
    #分别是编码器层,解码器层,源数据Embedding层和位置编码组成的有序结构
    #目标数据Embedding层和位置编码组成的有序结构,以及类别生成器层
    #在编码器层中有attention子层以及前馈全连接子层
    #在解码器中有两个attention子层以及前馈全连接层
    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))

    #模型结构完成后,接下俩就是初始化模型中的参数,比如线性层中的变换矩阵
    #这里一旦判断参数的维度大于1,则会将其初始化成一个服从均匀分布的矩阵
    for p in model.parameters():
        if p.dim() > 1:
            nn.init.xavier_uniform(p)
    return model

source_vocab = 11
target_vocab = 11
N = 6
#其他参数使用默认值

#调用
if __name__ == '__main__':
    res = make_model(source_vocab,target_vocab,N)
    print(res)

输出结果:

EncoderDecoder(
  (encoder): Encoder(
    (layers): ModuleList(
      (0): EncoderLayer(
        (self_attn): MultiHeadedAttention(
          (linears): ModuleList(
            (0): Linear(in_features=512, out_features=512, bias=True)
            (1): Linear(in_features=512, out_features=512, bias=True)
            (2): Linear(in_features=512, out_features=512, bias=True)
            (3): Linear(in_features=512, out_features=512, bias=True)
          )
          (dropout): Dropout(p=0.1, inplace=False)
        )
        (feed_forward): PositionwiseFeedForward(
          (w1): Linear(in_features=512, out_features=2048, bias=True)
          (w2): Linear(in_features=2048, out_features=512, bias=True)
          (dropout): Dropout(p=0.1, inplace=False)
        )
        (sublayer): ModuleList(
          (0): SublayerConnection(
            (norm): LayerNorm()
            (dropout): Dropout(p=0.1, inplace=False)
          )
          (1): SublayerConnection(
            (norm): LayerNorm()
            (dropout): Dropout(p=0.1, inplace=False)
          )
        )
      )
      (1): EncoderLayer(
        (self_attn): MultiHeadedAttention(
          (linears): ModuleList(
            (0): Linear(in_features=512, out_features=512, bias=True)
            (1): Linear(in_features=512, out_features=512, bias=True)
            (2): Linear(in_features=512, out_features=512, bias=True)
            (3): Linear(in_features=512, out_features=512, bias=True)
          )
          (dropout): Dropout(p=0.1, inplace=False)
        )
        (feed_forward): PositionwiseFeedForward(
          (w1): Linear(in_features=512, out_features=2048, bias=True)
          (w2): Linear(in_features=2048, out_features=512, bias=True)
          (dropout): Dropout(p=0.1, inplace=False)
        )
        (sublayer): ModuleList(
          (0): SublayerConnection(
            (norm): LayerNorm()
            (dropout): Dropout(p=0.1, inplace=False)
          )
          (1): SublayerConnection(
            (norm): LayerNorm()
            (dropout): Dropout(p=0.1, inplace=False)
          )
        )
      )
      (2): EncoderLayer(
        (self_attn): MultiHeadedAttention(
          (linears): ModuleList(
            (0): Linear(in_features=512, out_features=512, bias=True)
            (1): Linear(in_features=512, out_features=512, bias=True)
            (2): Linear(in_features=512, out_features=512, bias=True)
            (3): Linear(in_features=512, out_features=512, bias=True)
          )
          (dropout): Dropout(p=0.1, inplace=False)
        )
        (feed_forward): PositionwiseFeedForward(
          (w1): Linear(in_features=512, out_features=2048, bias=True)
          (w2): Linear(in_features=2048, out_features=512, bias=True)
          (dropout): Dropout(p=0.1, inplace=False)
        )
        (sublayer): ModuleList(
          (0): SublayerConnection(
            (norm): LayerNorm()
            (dropout): Dropout(p=0.1, inplace=False)
          )
          (1): SublayerConnection(
            (norm): LayerNorm()
            (dropout): Dropout(p=0.1, inplace=False)
          )
        )
      )
      (3): EncoderLayer(
        (self_attn): MultiHeadedAttention(
          (linears): ModuleList(
            (0): Linear(in_features=512, out_features=512, bias=True)
            (1): Linear(in_features=512, out_features=512, bias=True)
            (2): Linear(in_features=512, out_features=512, bias=True)
            (3): Linear(in_features=512, out_features=512, bias=True)
          )
          (dropout): Dropout(p=0.1, inplace=False)
        )
        (feed_forward): PositionwiseFeedForward(
          (w1): Linear(in_features=512, out_features=2048, bias=True)
          (w2): Linear(in_features=2048, out_features=512, bias=True)
          (dropout): Dropout(p=0.1, inplace=False)
        )
        (sublayer): ModuleList(
          (0): SublayerConnection(
            (norm): LayerNorm()
            (dropout): Dropout(p=0.1, inplace=False)
          )
          (1): SublayerConnection(
            (norm): LayerNorm()
            (dropout): Dropout(p=0.1, inplace=False)
          )
        )
      )
      (4): EncoderLayer(
        (self_attn): MultiHeadedAttention(
          (linears): ModuleList(
            (0): Linear(in_features=512, out_features=512, bias=True)
            (1): Linear(in_features=512, out_features=512, bias=True)
            (2): Linear(in_features=512, out_features=512, bias=True)
            (3): Linear(in_features=512, out_features=512, bias=True)
          )
          (dropout): Dropout(p=0.1, inplace=False)
        )
        (feed_forward): PositionwiseFeedForward(
          (w1): Linear(in_features=512, out_features=2048, bias=True)
          (w2): Linear(in_features=2048, out_features=512, bias=True)
          (dropout): Dropout(p=0.1, inplace=False)
        )
        (sublayer): ModuleList(
          (0): SublayerConnection(
            (norm): LayerNorm()
            (dropout): Dropout(p=0.1, inplace=False)
          )
          (1): SublayerConnection(
            (norm): LayerNorm()
            (dropout): Dropout(p=0.1, inplace=False)
          )
        )
      )
      (5): EncoderLayer(
        (self_attn): MultiHeadedAttention(
          (linears): ModuleList(
            (0): Linear(in_features=512, out_features=512, bias=True)
            (1): Linear(in_features=512, out_features=512, bias=True)
            (2): Linear(in_features=512, out_features=512, bias=True)
            (3): Linear(in_features=512, out_features=512, bias=True)
          )
          (dropout): Dropout(p=0.1, inplace=False)
        )
        (feed_forward): PositionwiseFeedForward(
          (w1): Linear(in_features=512, out_features=2048, bias=True)
          (w2): Linear(in_features=2048, out_features=512, bias=True)
          (dropout): Dropout(p=0.1, inplace=False)
        )
        (sublayer): ModuleList(
          (0): SublayerConnection(
            (norm): LayerNorm()
            (dropout): Dropout(p=0.1, inplace=False)
          )
          (1): SublayerConnection(
            (norm): LayerNorm()
            (dropout): Dropout(p=0.1, inplace=False)
          )
        )
      )
    )
    (norm): LayerNorm()
  )
  (decoder): Decoder(
    (layers): ModuleList(
      (0): DecoderLayer(
        (self_attn): MultiHeadedAttention(
          (linears): ModuleList(
            (0): Linear(in_features=512, out_features=512, bias=True)
            (1): Linear(in_features=512, out_features=512, bias=True)
            (2): Linear(in_features=512, out_features=512, bias=True)
            (3): Linear(in_features=512, out_features=512, bias=True)
          )
          (dropout): Dropout(p=0.1, inplace=False)
        )
        (src_attn): MultiHeadedAttention(
          (linears): ModuleList(
            (0): Linear(in_features=512, out_features=512, bias=True)
            (1): Linear(in_features=512, out_features=512, bias=True)
            (2): Linear(in_features=512, out_features=512, bias=True)
            (3): Linear(in_features=512, out_features=512, bias=True)
          )
          (dropout): Dropout(p=0.1, inplace=False)
        )
        (feed_forward): PositionwiseFeedForward(
          (w1): Linear(in_features=512, out_features=2048, bias=True)
          (w2): Linear(in_features=2048, out_features=512, bias=True)
          (dropout): Dropout(p=0.1, inplace=False)
        )
        (sublayer): ModuleList(
          (0): SublayerConnection(
            (norm): LayerNorm()
            (dropout): Dropout(p=0.1, inplace=False)
          )
          (1): SublayerConnection(
            (norm): LayerNorm()
            (dropout): Dropout(p=0.1, inplace=False)
          )
          (2): SublayerConnection(
            (norm): LayerNorm()
            (dropout): Dropout(p=0.1, inplace=False)
          )
        )
      )
      (1): DecoderLayer(
        (self_attn): MultiHeadedAttention(
          (linears): ModuleList(
            (0): Linear(in_features=512, out_features=512, bias=True)
            (1): Linear(in_features=512, out_features=512, bias=True)
            (2): Linear(in_features=512, out_features=512, bias=True)
            (3): Linear(in_features=512, out_features=512, bias=True)
          )
          (dropout): Dropout(p=0.1, inplace=False)
        )
        (src_attn): MultiHeadedAttention(
          (linears): ModuleList(
            (0): Linear(in_features=512, out_features=512, bias=True)
            (1): Linear(in_features=512, out_features=512, bias=True)
            (2): Linear(in_features=512, out_features=512, bias=True)
            (3): Linear(in_features=512, out_features=512, bias=True)
          )
          (dropout): Dropout(p=0.1, inplace=False)
        )
        (feed_forward): PositionwiseFeedForward(
          (w1): Linear(in_features=512, out_features=2048, bias=True)
          (w2): Linear(in_features=2048, out_features=512, bias=True)
          (dropout): Dropout(p=0.1, inplace=False)
        )
        (sublayer): ModuleList(
          (0): SublayerConnection(
            (norm): LayerNorm()
            (dropout): Dropout(p=0.1, inplace=False)
          )
          (1): SublayerConnection(
            (norm): LayerNorm()
            (dropout): Dropout(p=0.1, inplace=False)
          )
          (2): SublayerConnection(
            (norm): LayerNorm()
            (dropout): Dropout(p=0.1, inplace=False)
          )
        )
      )
      (2): DecoderLayer(
        (self_attn): MultiHeadedAttention(
          (linears): ModuleList(
            (0): Linear(in_features=512, out_features=512, bias=True)
            (1): Linear(in_features=512, out_features=512, bias=True)
            (2): Linear(in_features=512, out_features=512, bias=True)
            (3): Linear(in_features=512, out_features=512, bias=True)
          )
          (dropout): Dropout(p=0.1, inplace=False)
        )
        (src_attn): MultiHeadedAttention(
          (linears): ModuleList(
            (0): Linear(in_features=512, out_features=512, bias=True)
            (1): Linear(in_features=512, out_features=512, bias=True)
            (2): Linear(in_features=512, out_features=512, bias=True)
            (3): Linear(in_features=512, out_features=512, bias=True)
          )
          (dropout): Dropout(p=0.1, inplace=False)
        )
        (feed_forward): PositionwiseFeedForward(
          (w1): Linear(in_features=512, out_features=2048, bias=True)
          (w2): Linear(in_features=2048, out_features=512, bias=True)
          (dropout): Dropout(p=0.1, inplace=False)
        )
        (sublayer): ModuleList(
          (0): SublayerConnection(
            (norm): LayerNorm()
            (dropout): Dropout(p=0.1, inplace=False)
          )
          (1): SublayerConnection(
            (norm): LayerNorm()
            (dropout): Dropout(p=0.1, inplace=False)
          )
          (2): SublayerConnection(
            (norm): LayerNorm()
            (dropout): Dropout(p=0.1, inplace=False)
          )
        )
      )
      (3): DecoderLayer(
        (self_attn): MultiHeadedAttention(
          (linears): ModuleList(
            (0): Linear(in_features=512, out_features=512, bias=True)
            (1): Linear(in_features=512, out_features=512, bias=True)
            (2): Linear(in_features=512, out_features=512, bias=True)
            (3): Linear(in_features=512, out_features=512, bias=True)
          )
          (dropout): Dropout(p=0.1, inplace=False)
        )
        (src_attn): MultiHeadedAttention(
          (linears): ModuleList(
            (0): Linear(in_features=512, out_features=512, bias=True)
            (1): Linear(in_features=512, out_features=512, bias=True)
            (2): Linear(in_features=512, out_features=512, bias=True)
            (3): Linear(in_features=512, out_features=512, bias=True)
          )
          (dropout): Dropout(p=0.1, inplace=False)
        )
        (feed_forward): PositionwiseFeedForward(
          (w1): Linear(in_features=512, out_features=2048, bias=True)
          (w2): Linear(in_features=2048, out_features=512, bias=True)
          (dropout): Dropout(p=0.1, inplace=False)
        )
        (sublayer): ModuleList(
          (0): SublayerConnection(
            (norm): LayerNorm()
            (dropout): Dropout(p=0.1, inplace=False)
          )
          (1): SublayerConnection(
            (norm): LayerNorm()
            (dropout): Dropout(p=0.1, inplace=False)
          )
          (2): SublayerConnection(
            (norm): LayerNorm()
            (dropout): Dropout(p=0.1, inplace=False)
          )
        )
      )
      (4): DecoderLayer(
        (self_attn): MultiHeadedAttention(
          (linears): ModuleList(
            (0): Linear(in_features=512, out_features=512, bias=True)
            (1): Linear(in_features=512, out_features=512, bias=True)
            (2): Linear(in_features=512, out_features=512, bias=True)
            (3): Linear(in_features=512, out_features=512, bias=True)
          )
          (dropout): Dropout(p=0.1, inplace=False)
        )
        (src_attn): MultiHeadedAttention(
          (linears): ModuleList(
            (0): Linear(in_features=512, out_features=512, bias=True)
            (1): Linear(in_features=512, out_features=512, bias=True)
            (2): Linear(in_features=512, out_features=512, bias=True)
            (3): Linear(in_features=512, out_features=512, bias=True)
          )
          (dropout): Dropout(p=0.1, inplace=False)
        )
        (feed_forward): PositionwiseFeedForward(
          (w1): Linear(in_features=512, out_features=2048, bias=True)
          (w2): Linear(in_features=2048, out_features=512, bias=True)
          (dropout): Dropout(p=0.1, inplace=False)
        )
        (sublayer): ModuleList(
          (0): SublayerConnection(
            (norm): LayerNorm()
            (dropout): Dropout(p=0.1, inplace=False)
          )
          (1): SublayerConnection(
            (norm): LayerNorm()
            (dropout): Dropout(p=0.1, inplace=False)
          )
          (2): SublayerConnection(
            (norm): LayerNorm()
            (dropout): Dropout(p=0.1, inplace=False)
          )
        )
      )
      (5): DecoderLayer(
        (self_attn): MultiHeadedAttention(
          (linears): ModuleList(
            (0): Linear(in_features=512, out_features=512, bias=True)
            (1): Linear(in_features=512, out_features=512, bias=True)
            (2): Linear(in_features=512, out_features=512, bias=True)
            (3): Linear(in_features=512, out_features=512, bias=True)
          )
          (dropout): Dropout(p=0.1, inplace=False)
        )
        (src_attn): MultiHeadedAttention(
          (linears): ModuleList(
            (0): Linear(in_features=512, out_features=512, bias=True)
            (1): Linear(in_features=512, out_features=512, bias=True)
            (2): Linear(in_features=512, out_features=512, bias=True)
            (3): Linear(in_features=512, out_features=512, bias=True)
          )
          (dropout): Dropout(p=0.1, inplace=False)
        )
        (feed_forward): PositionwiseFeedForward(
          (w1): Linear(in_features=512, out_features=2048, bias=True)
          (w2): Linear(in_features=2048, out_features=512, bias=True)
          (dropout): Dropout(p=0.1, inplace=False)
        )
        (sublayer): ModuleList(
          (0): SublayerConnection(
            (norm): LayerNorm()
            (dropout): Dropout(p=0.1, inplace=False)
          )
          (1): SublayerConnection(
            (norm): LayerNorm()
            (dropout): Dropout(p=0.1, inplace=False)
          )
          (2): SublayerConnection(
            (norm): LayerNorm()
            (dropout): Dropout(p=0.1, inplace=False)
          )
        )
      )
    )
    (norm): LayerNorm()
  )
  (src_embed): Sequential(
    (0): Embeddings(
      (lut): Embedding(11, 512)
    )
    (1): PositionalEncoding(
      (dropout): Dropout(p=0.1, inplace=False)
    )
  )
  (tgt_embed): Sequential(
    (0): Embeddings(
      (lut): Embedding(11, 512)
    )
    (1): PositionalEncoding(
      (dropout): Dropout(p=0.1, inplace=False)
    )
  )
  (generator): Generator(
    (project): Linear(in_features=512, out_features=11, bias=True)
  )
)

1.6 模型基本测试运行

我们将通过一个小的copy任务万层模型的基本测试工作
copy任务介绍:

  • 任务描述:针对数字序列进行学习,学习的最终目标是使输出与输入的序列相同,如输入【1,5,8,9,3】,输出也是【1,5,8,9,3】。
  • 任务意义:copy任务在模型基础测试中具有重要意义,因为copy操作对于模型来讲是一条明显规律,因此模型能否在短时间内,小数据集中学会它,可以帮助我们断定模型所有过程是否正常,是否已具备学习能力。

使用copy任务进行模型基本测试的四部曲:

  • 第一步:构建数据集生成器
  • 第二步:获得Transformer模型及其优化器和损失函数
  • 第三步:运行模型进行训练和评估

1.6.1 第一步:构建数据集生成器

#-------------------------------模型基本测试运行-----------------------------------------
#-------------------------------第一步-----------------------------------------
def data_generator(V, batch_size, num_batch):
    '''
    该函数用于随机生成copy任务的数据
    :param V: 随机生成数字的最大值+1
    :param batch: 每次输送给模型更新一次参数的数据量
    :param num_batch: 一共输送num_batch次完成一轮
    :return:
    '''
    # 使用for循环遍历nbatches
    for i in range(num_batch):
        # 在循环中使用np的random.randint方法随机生成[1,V)的整数
        # 分布在(batch,10)形状的矩阵中,然后再把numpy形式转换成torch中的tensor
        data = torch.from_numpy(np.random.randint(1, V, size=(batch_size, 10)))

        # 接着使数据矩阵中的第一列数字都为1,这一列也就成为了起始标识列,
        #当解码器进行第一次解码的时候,会使用起始标志作为输入
        data[:,0] = 1
        #因为是copy任务,所有source与target是完全相同的,且数据样本作用变量不需要求梯度
        #因此requires_grad设置为False,样本参数不需要参与梯度的计算
        source = Variable(data,requires_grad=False)
        target = Variable(data,requires_grad=False)

        #使用Batch对source和target进行对应批次的掩码张量生成,最后使用yield返回
        yield Batch(source,target)

#将生成0-10的整数
V = 11

#每次喂给模型20个数据进行参数更新
batch_size = 20

#连续喂30次完成全部数据的遍历,也就是1轮
num_batch = 30

if __name__ == '__main__':
    res = data_generator(V,batch_size,num_batch)
    print(res)

输出效果:
在这里插入图片描述

1.6.2 第二步:获得Transformer模型及其优化器和损失函数

#-------------------------------第二步-----------------------------------------
#使用make_model获得model
model = make_model(V,V,N=2)

#使用get_std_opt获得模型优化器
model_optimizer = get_std_opt(model)

#使用LabelSmoothing获得模型优化器
criterion = LabelSmoothing(size=V,padding_idx=0,smoothing=0.0)

#使用SimpleLossCompute获得利用标签平滑结果的损失计算方法
loss = SimpleLossCompute(model.generator,criterion,model_optimizer)

标签平滑示例:

#标签平滑示例
# 使用LabelSmoothing实例化一个crit对象
#第一个参数size代表目标数据的词汇总数,也是模型最后一层得到张量的最后一维大小
#这里是5,说明目标词汇总数是5个,第二个参数padding_idx表示要将那些tensor中的数字
#替换成0,一般padding_idx=0表示不进行替换,第三个参数smoothing,表示标签的平滑程度
#如原来标签的标示值为1,则平滑后它的值域变成【1-smoothing,1+smoothing】
crit = LabelSmoothing(size=5,padding_idx=0,smoothing=0.5)

#假定一个任意的模型最后输出预测结果和真实结果
predict = Variable(torch.FloatTensor([[0,0.2,0.7,0.1,0],
                                      [0,0.2,0.7,0.1,0],
                                      [0,0.2,0.7,0.1,0]]))
#标签的表示值是0,1,2
target = Variable(torch.LongTensor([2,1,0]))

#将predict,target传入到对象中
crit(predict,target)

#绘制标签平滑图像
plt.imshow(crit.true_dist)
plt.show()

标签平滑图像:
在这里插入图片描述

标签平滑图像分析:

  • 我们目光集中在黄色小方块上,它相对于横坐标横跨的值域就是标签平滑后的正向平滑值域,我们可以看到大致是从0.5到2.5
  • 它相对于纵坐标横跨的值域就是标签平滑后的负向平滑值域,我们可以看到大致是从-0.5到1.5,总的值域空间由原来的[0,2]变成[-0.5,2.5]

1.6.3 第三步:运行模型进行训练和评估

#-------------------------------第三步-----------------------------------------
#运行模型进行训练和评估
def run(model,loss,epochs):
    '''
    模型训练函数,共有三个参数
    :param model: 代表将要进行训练的模型
    :param loss: 代表使用的损失计算方法
    :param epochs: 代表模型训练的轮廓
    :return:
    '''
    #遍历轮数
    for epoch in range(epochs):
        #模型使用训练模式,所有参数将被更新
        model.train()
        #训练时,batch_size是20
        run_epoch(data_generator(V,8,20),model,loss)

        #模型使用评估模式,参数将不会变化
        model.eval()
        #评估时,batch_size是5
        run_epoch(data_generator(V,8,5),model,loss)

#进行10轮训练
epochs = 10

#model和loss都是来自上一步的结果
if __name__=='__main__':
    run(model,loss,epochs)

未完待续……

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值