Transformer详细代码逐行解析(B站搬运手敲版)nlp基础

import numpy as np
import  torch
# 数学计算包
import math
# 卷积层 lstm层 embedding层等
import torch.nn as nn
import  copy
#torch变量中封装函数
from torch.autograd import Variable
#工具包中装载了网络中那些只进行计算,而没有参数的层
import torch.nn.functional as F
# torch经典文本数据集有关的工具包
import torchtext
# torchtext中的数据处理工具,get_tokenizer用于英文分词
from torchtext.data.utils import get_tokenizer

# 已构建完成的transformer model
from  pyitcast.transformer import TransformerModel

#导入工具包batch,它能够对原始样本数据生成对应批次的掩码张量
from pyitcast.transformer_utils import Batch
#导入工具包 get_std_opt,该工具获得标准优化器,使其对序列到序列的任务更有效
from pyitcast.transformer_utils import get_std_opt
#导入标签平滑工具包,小幅度的改变原有标签的值域,可以防止过拟合(人工标注的数据也可能因为外界影响而产生偏差)
from pyitcast.transformer_utils import LabelSmoothing
#导入计算损失包,该工具能够使用标签平滑后的结果进行损失的计算,损失的计算方法是交叉熵损失函数
from pyitcast.transformer_utils import SimpleLossCompute
#导入模型单轮训练工具包run_epoch,该工具将对模型使用给定的损失函数计算方法进行单轮参数更新
#并打印每轮参数更新的损失结果
from pyitcast.transformer_utils import run_epoch
import matplotlib.pyplot as plt
#贪婪解码工具包greedy_decode,该工具对最终结进行贪婪解吗
#每次预测概率最大的结果作为暑促和,不一定全局最优但是执行效率最高
from pyitcast.transformer_utils import greedy_decode


# 文本嵌入层
class Embedings(nn.Module):
    def __init__(self,d_model,vocab):# 词嵌入的维度和词表的大小
       #super继承nn.model的初始化函数,自己实现的所有层一般都会去做
        super(Embedings, self).__init__()
       #调用预定义的embedding层,获得一个词表嵌入对象self.lut
        self.lut=nn.Embedding(vocab,d_model)
       #将d_model传入类中
        self.d_model=d_model

    def forward(self,x):
        #该层的向前传播逻辑,所有层都有此函数
        #x:因为embedding是首层,所以x代表输入给模型的文本通过词汇映射后的张量,
        # 将x传给self.lut并与根号下self.d-model相乘作为结果返回

        return self.lut(x)*math.sqrt(self.d_model)

    # 生成掩码张量的分析
def subsequenet_mask(size):
    #生成向后遮掩的掩码张量,参数size是掩码张量的最后两个维度的大小,
    # 它的最后两维形成一个方阵,在函数中,定义掩码张量的形状
    attn_shape =(1,size,size)
    #使用np.ones方法向这个形状中添加1元素,形成上三角矩阵,最后为了节省空间
    # 再使其中的数据类型变为无符号的8位整型 unit8
    subsequenet_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-subsequenet_mask)
#size=5
#sm=subsequenet_mask(size)
    #定义位置编码器类,看成一层,因此也会继承nn.module
#print("sm:",sm)
# 位置编码
class PositionalEncoding(nn.Module):
    def __init__(self,d_model,dropout,max_len=5000):
    #位置编码类的初始化函数,三个参数 词嵌入维度 置0比率 每个句子的最大长度
        super(PositionalEncoding, self).__init__()
    #实例化nn中预定义的dropout层,并将dropout传入其中,获得对象self.dropout
        self.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的1矩阵
        position =torch.arange(0,max_len).unsqueeze(1)

    # 绝对位置矩阵初始化完成后,考虑将位置信息加入到位置编码矩阵中
    # 思路 将绝对位置矩阵的形状变化为位置编码矩阵的形状,然后覆盖原来的的初始位置编码矩阵
    # 要完成这种矩阵变换,需要一个1Xd_model形状的变换矩阵div_term,并且要求此矩阵能将自然数
    # 绝对位置编码缩放成足够小的数字,使得之后梯度下降更快的收敛
        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现在还是一个二维矩阵,需要使用unsqueeze拓展到三维,然后和embedding
        #矩阵相加
        pe =pe.unsqueeze(0)

        # 最后把pe位置编码矩阵注册成模型的buffer
        # buffer是对模型效果有帮助,但不是模型结果中超参数和参数,不需要随着优化步骤而更新
        # 注册之后我们就可以再模型保存后重加载时和模型结构参数一同被加载。
        self.register_buffer('pe',pe)

    def forward(self,x):# 参数x表示文本序列的词嵌入表示
        #将三维张量最大长度的那一维切片到与输入的x的第二维相同,即x.size(1),
        #默认max-len为5000太大了,要进行张量适配
        x=x+Variable(self.pe[:,:x.size(1)],
                    requires_grad=False) # 位置编码不参与更新
        #最后使用self.dropout对象进行丢弃操作,并返回结果
        return  self.dropout(x)

#d_model=512
#dropout=0.1
#max_len=60

# x=embr
#pe =PositionalEncoding(d_model,dropout,max_len)
#pe_result=pe(x)


#注意力机制
def attention(query,key,value,mask=None,dropout=None):
    #注意力机制的实现,输入分别是qkv,mask,掩码张量
    #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的mask_fill方法,将掩码张量和scores张量每一个位置进行比较,如果掩码张量
        #为0,则对应的scores张量用-1e9这个值来替换,如下所示
        scores=scores.masked_fill(mask==0,-1e9)
    #对scores的最后一个维度进行softmax操作,使用softmax方法,第一个参数是softmax对象,第二个参数
    #这样获得最终的注意力张量
    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

#多头注意力机制
#先定义克隆函数,在多头注意力机制的实现中,需要用到多个结构相同的线性层
#我们使用clone函数将它们一同初始化在一个网络层列表对象中之后的结构也会用到该函数
def clones(module,N):
    #用于生成相同网络层的克隆函数,他的参数moudle表示克隆的目标网络层,n表示需要克隆的数量
    # 在函数中,我们通过for循环对moudle进行n次深度拷贝,使其每个moudle成为独立的层
    #然后将其放在nn.moudlelist类型的列表中
    return nn.ModuleList([copy.deepcopy(module) for _ in range(N)])

# 多头注意力机制的类
class MultiHeadAttention(nn.Module):
    def __init__(self,head,embedding_dim,dropout=0.1):
        #head表示头数 embedding_dim表示词嵌入维度 dropout代表置0比率,默认0.1
        super(MultiHeadAttention, 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

        #然后获得线性层对象,通过nn.linear实例化,它的内部变化矩阵是embedding_dim x embedding_dim,
        #然后使用clones  在多头注意力中qkv各需要一个,最后拼接的矩阵还需要一个,共4个
        self.linears=clones(nn.Linear(embedding_dim,embedding_dim),4)

        #self.att为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):
        #向前逻辑函数,最后一个参数是掩码张量默认None

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

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

        #进入多头环节
        #利用zip将qkv三个线性层组到一起,然后for循环,将qkv传到线性层中
        #做完线性变化后,开始为每个头分割输入,这里使用view方法对线性变换的结果进行维度重塑,多增加了一个维度
        # 这样意味着每个头可以获得一部分词特征组成的句子,其中-1表示自适应维度
        #计算机会根据这种变换自动计算这里的值,然后对第二维和第三维进行转置操作
        # 为了让代表句子长度维度和词向量维度能够相邻,这样自注意力机制才能找到词义与句子位置的关系
        # 从atten函数中可以看到,利用原始输入的倒数第一和第二维,这样就得到了每个头的输入
        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)


#前馈全连接层
class PositionwiseFeedFoeward(nn.Module):
    def __init__(self,d_model,d_ff,dropout=0.1):
        # 三个参数 d_model 线性层的输入维度,我们希望通过前馈全连接层后输入和输出维度不变
        # d_ff是第二个线性层的输入维度和第一个线性层的输出维度 dropout 置0比率
        super(PositionwiseFeedFoeward, self).__init__()
        #使用nn实例化两个线性层模型 self.w1和self.w2
        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):
        "输入参数为x,代表来自上一层的输出"
        # 首先经过第一个线性层,然后使用funtional中relu的函数进行激活
        # 之后再使用dropout进行随机置0,然后通过第二个线性层w2,返回最终结果
        # relu公式 max(0,x)
        return self.w2(self.dropout(F.relu(self.w1(x))))


# 规范化层
# 通过layernorm实现规范化层的类
class LayerNorm(nn.Module):
    def __init__(self,fetures,eps=1e-6):
        #feature表示词嵌入的维度 eps它是一个足够小得到数,在规范化的分母中出现(防止分母为0)
        super(LayerNorm, self).__init__()

        # 根据feature的形状初始化两个参数张量a2,b2,第一个初始化为1的张量
        # 也就是里面的元素都是1,第二个初始化0张量,也就是里面元素都是0,这两个张量就是规范化的参数
        #直接对上一层得到的结果尽心规范化计算,将改变结果的正常表征,因此就需要参数作为调节因子
        #使其既能满足规范化要求,又能不改变对目标的表征,最后使用nn.parmeter封装,代表他们是模型的参数
        self.a2=nn.Parameter(torch.ones(fetures))
        self.b2=nn.Parameter(torch.zeros(fetures))

        #把eps传入类中
        self.eps=eps

    def 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

#子连接结构的代码分析
#使用sublayerconnection来实现子层连接结构的类
class SublayerConnection(nn.Module):
    def __init__(self,size,dropout=0.1):
    #size词嵌入维度的大小 dropout本身是对模型结构中的节点数进行随机抑制的比率
    #又因为节点被抑制等效为该节点的输出都是0,因此可以把dropout看作对输出矩阵的置0比率

        super(SublayerConnection, self).__init__()
        # 实例化了规范化对象self.norm
        self.norm=LayerNorm(size)
        # 又使用nn预定义的dropout实例化了一个self.dropout对象
        self.dropout=nn.Dropout(p=dropout)
        self.size=size

    def forward(self,x,sublayer):
        #向前逻辑函数中,接受上一个层或子厝的输入作为第一个参数,将该子层
        #连接中的子函数作为第二个参数

        # 我们首先对输出进行规范化,然后将结果传给子层处理,然后再对子层进行dropout操作
        #随机停止一些网络的神经元作用,用来防止过拟合,最后还有一个add操作
        #因为存在跳跃连接(残差连接),所以是将x与dropout后的子层输出结果相加作为最终的子层连接输出
        return x+self.dropout(sublayer(self.norm(x)))
    
# 编码器层的代码分析
class EncoderLayer(nn.Module):
    def __init__(self,size,self_attn,feed_forward,dropout):
        # 参数 size词嵌入维度的大小,也是我们编码器
        # self_attn 传入多头注意力子层实例化对象,并且是自注意力机制
        # feed_forward 传入前馈全连接层实例化对象,dropout是置0比率
        super(EncoderLayer, self).__init__()

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

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

    def forward(self,x,mask):
        #x表示上一层的输出 mask表示掩码张量
        #结合图片 首先通过第一个子连接结构,其中包含多头注意力子层
        # 然后通过第二个子层连接结构,其中包含前馈全连接子层,最后返回结果
        x=self.sublayer[0](x,lambda x:self.self_attn(x,x,x,mask))
        return  self.sublayer[1](x,self.feed_forward)

#编码器Encoder
class Encoder(nn.Module):
    def __init__(self,layer,N):
        #编码器层和编码器层的个数
        super(Encoder, self).__init__()
        # 首先使用clone函数克隆n个编码器防止self.layer中
        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)


# 解码器层
class DecoderLayer(nn.Module):
    def __init__(self,size,self_attn,src_attn,feed_forward,dropout):
        # size 词嵌入维度的大小,同时也代表解码器的尺寸
        # self_attn,多头自注意力对象,q=k=v
        #src_attn多头注意对象 q!=k=v 第四个是前馈全连接层对象,最后是置0比率
        super(DecoderLayer, self).__init__()

        #在初始化函数中,将这些输入传入类中
        self.size=size
        self.self_attn=self_attn
        self.src_attn=src_attn
        self.feed_forwward=feed_forward
        # 按照图结构使用clone函数克隆三个子链接层对象
        self.sublayer=clones(SublayerConnection(size,dropout),3)

    def forward(self,x,memory,source_mask,target_mask):
        #x是上一层的输入 memory编码器的语义存储变量 源数据掩码张量和目标数据掩码张量
        #将memory表示成m方便使用
        m=memory

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

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

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

# 解码器Decoder
class Decoder(nn.Module):
    def __init__(self,layer,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):
        # x是目标数据的嵌入表示,memory是编码器层的输出
        #source_mask,target_mask代表源数据和目标数据的掩码张量

        # 然后就是对每个层进行循环,当然这个循环就是变量x通过每一层的处理
        #得出来最后的结果,再进行一次规范化
        for layer in self.layers:
            x=layer(x,memory,source_mask,target_mask)
        return self.norm(x)

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

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

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

    def forward(self,source,target,source_mask,target_mask):
        # 参数 源数据 目标数据 源数据和目标数据对应的掩码张量
        # 在函数中,将source,source_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):
        #编码函数
        #使用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):
        #解码函数 memory是编码器的输出
        # 使用tgt_embed对target做处理,然后和source_mask,target_mask,memory
        #一起传给self.decoder
        return self.decoder(self.tgt_embed(target),memory,source_mask,target_mask)

# tansformer模型构建过程代码
def make_model(source_vocab,target_vocab,N=6,d_model=512,d_ff=2048,head=8,dropout=0.1):
    # 参数 源数据词汇(特征)总数,目标数据特征(词汇)总数 编码器和解码器堆叠数,词向量映射维度
    # 前馈全连接网络中变换矩阵的维度,多头注意力结果中的多头数,置零比率

    # 首先得到一个深度拷贝命令,接下来很多结构都需要进行深度拷贝
    #来保证他们彼此之间相互独立,不受干扰
    c=copy.deepcopy

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

    #然后实例化前馈全连接类,得到对象ff
    ff=PositionwiseFeedFoeward(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(Embedings(d_model,source_vocab),c(position)),
        nn.Sequential(Embedings(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



# 下面开始正式跑模型
#1 构建数据集
def data_generator(V,batch,num_batch):
    # 该函数用于随机生成copy函数的数据,它的三个输入参数是 V随机生成数字的最大值+1
    #batch:每次输送给模型更新一次参数的数据量,Num_batch:一共输送模型多少轮数据

    #使用for循环变量n batches
    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,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)

# 2 获得transformer模型及其优化器和损失函数
V=11
batch_size=20
num_batch=30
#获得模型
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)
#使用simplelosscomputer获得利用标签平滑结果的损失计算方法
loss=SimpleLossCompute(model.generator,criterion,model_optimizer)

#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]]))
#target=Variable(torch.LongTensor([2,1,0]))
#crit(predict,target)
#plt.imshow(crit.true_dist)

#3运行模型进行训练和评估
def run (model,loss,epochs=10):
    # 模型 损失计算方法,模型训练的轮数

    #遍历轮数
    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)

    #模型进入测试模式
    model.eval()
    #假定的输入张量
    source=Variable(torch.LongTensor([[1,3,2,5,4,6,7,8,9,10]]))

    #定义源数据掩码张量,因为元素都是,在我们这里1代表不遮掩
    #因此相当于对源数据没有任何遮掩
    source_mask=Variable(torch.ones(1,1,10))

    #最后将model,src,src_mask解码的最长长度限制max_len,默认为10
    # 以及起始标志数字,默认为1,我们这里使用的也是1
    result =greedy_decode(model,source,source_mask,max_len=10,start_symbol=1)
    print(result)


你可能感兴趣的:(transformer,深度学习,python)