一个简单的语言模型(transformer)的使用,代码逐行解析版

# 1 导包
# 数学计算包
import math
import  torch
# 卷积层 lstm层 embedding层等
import torch.nn as nn
#工具包中装载了网络中那些只进行计算,而没有参数的层
import torch.nn.functional as F
# torch经典文本数据集有关的工具包
#可以做文本语料加载,文本迭代器构建,包含经典语料的预加载方法,包含情感分析的sst和imdb用于分类的trec
# 用于翻译的wmt14等
import torchtext
# torchtext中的数据处理工具,get_tokenizer用于英文分词
from torchtext.data.utils import get_tokenizer
# 已构建完成的transformer model
from  pyitcast.transformer import TransformerModel

#2 导入wikitext-2数据集并做基本处理
# 创建语料域,语料域是存放语料的数据结构
# 它的四个参数代表给存放语料(文本)施加的作用 tokenize:获得一个分割器对象,按照基础英文分割
# init_token为文本施加的起始符,《sos>给文本施加的终止符《eos》,lower=true存放的文本字母全部小写

TEXT = torchtext.data.Field(tokenize=get_tokenizer("basic_english"),
                            init_token='',
                            eos_token='',
                            lower=True)

#print(TEXT)
#最终获得一个field对象
#
# 然后使用torchtext的数据集方法导入wikitetx2数据
# 并切分为对应训练文本 验证文本 测试文本,并对这些文本施加刚刚创建的语料域
train_txt,val_txt,test_txt=torchtext.datasets.WikiText2.splits(TEXT)

#我们可以通过example[0].text取出文本对象进行查看
#print(test_txt.examples[0].text[:10])

#将训练集文本数据构建一共vocab对象
#这样可以使用vocab对象的stoi方法统计本文包含的不重复总词汇
TEXT.build_vocab(train_txt)

# 然后选择cuda或者cpu
device =torch.device("cuda" if torch.cuda.is_available() else "cpu")

# 3构建用于模型输入的批次化数据
def batchify(data,bsz):
    # 用于将文本数据映射成连续数字,并转换成指定的样式
    # 两个参数 data得到的文本数据 bsz每次模型更新的数据量
    #使用此方法将单词映射为对应连续数字
    data =TEXT.numericalize([data.examples[0].text])

    #用数据词汇总数除以bsz
    #取整数得到一共nbatch代表需要多少次后能够遍历完所有数据
    nbatch=data.size(0)//bsz

    #使用narrow方法对不规整的剩余数据进行删除
    # 第一个参数表示横轴删除还是纵轴删除,0为横轴,1为纵轴
    # 第二个和第三个代表保留开始轴到结束轴的数值,类似于切片
    data=data.narrow(0,0,nbatch*bsz)

    # 使用view方法对data进行矩阵变换,会做转置操作,形状为【none,bsz]
    #如果输入是训练数据【104335,20】可以通过打印data。shape得到  data的列数是等于bsz的值的
    data=data.view(bsz,-1).t().contiguous()
    #最后将数据分配到指定的设备上
    return  data.to(device)

# 4 使用batchify来处理训练数据,验证数据以及测试数据
# 训练数据的batch_size
batch_size=20
# 验证和测试数据的batch size
eval_batch_size=10
#获得train_data val_data test_data
train_data=batchify(train_txt,batch_size)
val_data=batchify(val_txt,eval_batch_size)
test_data=batchify(test_txt,eval_batch_size)

#句子长度允许的最大值bptt为35
bptt=35

# 用于获得每个批次合理大小的源数据和目标数据
def get_batch(source,i):
    # source是通过batchify得到的train_data/val_data/test_data i是具体的批次次数

    # 确定句子的长度,它是bptt和len(source)-1-i中最小值
    # 前面的批次都是bptt值,最后一共批次可能不够bptt的35个,会变成len(source)-1-i
    seq_len=min(bptt,len(source)-1-i)

    #语言模型训练的源数据的第i批数据建是batchify的结构的切片【i:i+seq_len]
    data=source[i:i+seq_len]

    #根据语料规定,它的目标数量是源数据向后移动1位
    # 因为最后目标数据的切片会越界。因此使用view(-1)来保证形状正常
    target=source[i+1:i+1+seq_len].view(-1)
    return  data,target

# 5构建训练和评估函数
#通过TEXT.vocab.stoi方法获得不重复词汇总数
ntokens=len(TEXT.vocab.stoi)
#词嵌入大小为200
emsize=200
#前馈全连接层的节点数
nhid=200
#编码器层的数量
nlayers=2
#多头注意力机制的头数
nhead=2
# 置0比率
dropout=0.2
#将参数输入到transformermodel中
model=TransformerModel(ntokens,emsize,nhead,nhid,nlayers,dropout).to(device)

#模型初始化之后,进行损失函数和优化方法的选择
#损失函数使用nn自带的交叉熵损失
criterion=nn.CrossEntropyLoss()
#学习率始终定为5.0
lr=5.0
# 优化器选择torch自带的sgd随机梯度下降方法,并把lr传入其中
optimizer=torch.optim.SGD(model.parameters(),lr=lr)
#定义学习率调整方法,使用torch自带的lr_scheduler,将优化器传入其中
scheduler =torch.optim.lr_scheduler.StepLR(optimizer,1.0,gamma=0.95)

# 6 模型训练代码分析
import time
def train():
    # 模型开启训练模式
    model.train()
    #定义初始损失为0
    total_loss=0
    #获得当前时间
    start_time=time.time()
    #开始遍历批次数据
    for batch ,i in enumerate(range(0,train_data.size(0)-1,bptt)):
        #通过get_batch获得源数据和目标数据
        data,targets=get_batch(train_data,i)
        #设置优化器初始梯度为0梯度
        optimizer.zero_grad()
        # 将数据装入model得到输出
        output=model(data)
        # 将输出和目标数据传入损失函数对象
        loss =criterion(output.view(-1,ntokens),targets)
        # 将损失进行反向传播获得总损失
        loss.backward()
        # 使用nn自带的clip_grad_norm_方法进行梯度规范化,防止出现梯度消失或爆炸
        torch.nn.utils.clip_grad_norm(model.parameters(),0.5)
        #模型参数进行更新
        optimizer.step()
        # 将每层的损失相加获得总的损失
        total_loss+=loss.item()
        # 日志打印间隔为200
        log_interval=200
        #如果batch是200的倍数且大于0,则打印相关日志
        if batch % log_interval ==0 and batch>0:
            #评价损失为总损失除以log_interval
            cur_loss=total_loss/log_interval
            #需要的时间为当前的时间前去开始时间
            elapsed=time.time()-start_time
            # 打印轮数,当前批次和总批次,当前学习率,训练速度(每毫秒处理多少批次)
            #平均损失,以及困惑度:衡量语言模型的重要指标,计算方法为第交叉熵平均损失取自然对数的底数
            print('| epoch{:3d}{:5d} batches|'
                  'lr{:02.2f}|ms/batch{:5.2f}|'
                  'loss{:5.2f}|ppl{:8.2f}'.format( #删除了epoch报错
                batch,len(train_data)//bptt,scheduler.get_lr()[0],
                elapsed*1000/log_interval,
                cur_loss,math.exp(cur_loss)))
            #每个批次结束后,总损失归0
            total_loss=0
            #开始时间取当前时间
            start_time=time.time()

# 7 模型评估代码分析
def evaluate(eval_model,data_source):
    #评估函数,包括验证和测试 参数:每轮训练产生的模型  验证和测试数据集
    #模型开始评估模式
    eval_model.eval()
    #总损失归0
    total_loss=0
    # 因为评估模型参数不变,因此反向传播不需要求导,以加快计算
    with torch.no_grad():
        for i in range(0,data_source.size(0)-1,bptt):
            #首先通过get_batch获得验证数据集的源数据和目标数据
            data,targets=get_batch(data_source,i)
            #通过eval_model获得输出
            output=eval_model(data)
            #对输出形状扁平化,变为全部词汇的概率分布
            output_flat=output.view(-1,ntokens)
            # 获得评估过程的总损失
            total_loss += criterion(output_flat,targets).item()
    #返回每轮的总损失
    return  total_loss


# 8 进行训练和评估
#初始化最佳验证损失,初始值为无穷大
best_val_loss =float("inf")
#定义训练轮数
epochs=3
#定义最佳模型变量,初始值为None
best_model=None
#使用for循环遍历轮数
for epoch in range(1,epochs+1):
    # 首先获得轮数的开始时间
    epoch_start_time=time.time()
    # 调用训练函数
    train()
    #该轮训练后我们的模型参数以及发生了变化
    #将模型和评估数据传入到评估函数中
    val_loss=evaluate(model,val_data)
    #打印每轮的评估日志,分布有轮数 耗时,验证损失以及验证困惑度
    print('-'*89)
    print('| end of epoch{:3d} |time{:5.2f}s | valid loss{:5.2f} |'
          'valid ppl{:8.2f}'.format(epoch,(time.time()-epoch_start_time),
                                    val_loss,math.exp(val_loss)))
    print('-'*89)
    #我们将比较那一轮损失最小,复制给best_val_loss
    #并取该损失下的模型为best_model
    if val_loss<best_val_loss:
        best_val_loss=val_loss
        best_model=model
    #每轮都会对优化方法的学习率做调整
    scheduler.step()

# 9 模型测试代码分析
# 任然使用evaluate函数,这次它的参数是best_model以及测试数据
test_loss=evaluate(best_model,test_data)
#打印日志 包括测试损失和测试困惑度
print('-' * 89)
print('| end of training |test loss {:5.2f}s | test ppi{:8.2f} |'
     .format(test_loss, math.exp(test_loss)))
print('-' * 89)



你可能感兴趣的:(语言模型,transformer,深度学习)