自然语言处理---Transformer构建语言模型

语言模型概述

以一个符合语言规律的序列为输入,模型将利用序列间关系等特征,输出一个在所有词汇上的概率分布,这样的模型称为语言模型。

# 语言模型的训练语料一般来自于文章,对应的源文本和目标文本形如:
src1 = "I can do" tgt1 = "can do it"
src2 = "can do it", tgt2 = "do it <eos>"

语言模型能解决哪些问题:

  • 根据语言模型的定义,可以在它的基础上完成机器翻译,文本生成等任务,因为通过最后输出的概率分布来预测下一个词汇是什么。
  • 语言模型可以判断输入的序列是否为一句完整的话,因为可以根据输出的概率分布查看最大概率是否落在句子结束符上,来判断完整性。
  • 语言模型本身的训练目标是预测下一个词,因为它的特征提取部分会抽象很多语言序列之间的关系,这些关系可能同样对其他语言类任务有效果。因此可以作为预训练模型进行迁移学习。

语言模型实现

  • 第一步: 导入必备的工具包
  • 第二步: 导入wikiText-2数据集并作基本处理
  • 第三步: 构建用于模型输入的批次化数据
  • 第四步: 构建训练和评估函数
  • 第五步: 进行训练和评估(包括验证以及测试)

1. 导入必备的工具包

# 数学计算工具包math
import math# torch以及torch.nn, torch.nn.functional
import torch
import torch.nn as nn
import torch.nn.functional as F# torch中经典文本数据集有关的工具包
# 具体详情参考下方torchtext介绍
import torchtext# torchtext中的数据处理工具, get_tokenizer用于英文分词
from torchtext.data.utils import get_tokenizer# 已经构建完成的TransformerModel
from pyitcast.transformer import TransformerModel

2. 导入wikiText-2数据集并作基本处理

# 创建语料域, 语料域是存放语料的数据结构, 
# 它的四个参数代表给存放语料(或称作文本)施加的作用. 
# 分别为 tokenize,使用get_tokenizer("basic_english")获得一个分割器对象,
# 分割方式按照文本为基础英文进行分割. 
# init_token为给文本施加的起始符 <sos>给文本施加的终止符<eos>, 
# 最后一个lower为True, 存放的文本字母全部小写.
TEXT = torchtext.data.Field(tokenize=get_tokenizer("basic_english"),init_token='<sos>',eos_token='<eos>',lower=True)# 最终获得一个Field对象.
# <torchtext.data.field.Field object at 0x7fc42a02e7f0># 然后使用torchtext的数据集方法导入WikiText2数据, 
# 并切分为对应训练文本, 验证文本,测试文本, 并对这些文本施加刚刚创建的语料域.
train_txt, val_txt, test_txt = torchtext.datasets.WikiText2.splits(TEXT)# 我们可以通过examples[0].text取出文本对象进行查看.
# >>> test_txt.examples[0].text[:10]
# ['<eos>', '=', 'robert', '<unk>', '=', '<eos>', '<eos>', 'robert', '<unk>', 'is']# 将训练集文本数据构建一个vocab对象, 
# 这样可以使用vocab对象的stoi方法统计文本共包含的不重复词汇总数.
TEXT.build_vocab(train_txt)# 然后选择设备cuda或者cpu
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")

3. 构建用于模型输入的批次化数据

  • 批次化过程的第一个函数batchify代码分析:
def batchify(data, bsz):"""batchify函数用于将文本数据映射成连续数字, 并转换成指定的样式, 指定的样式可参考下图.它有两个输入参数, data就是我们之前得到的文本数据(train_txt, val_txt, test_txt),bsz是就是batch_size, 每次模型更新参数的数据量"""# 使用TEXT的numericalize方法将单词映射成对应的连续数字.data = TEXT.numericalize([data.examples[0].text])# >>> data# tensor([[   3],#    [  12],#    [3852],#    ...,#    [   6],#    [   3],#    [   3]])# 接着用数据词汇总数除以bsz,# 取整数得到一个nbatch代表需要多少次batch后能够遍历完所有数据nbatch = data.size(0) // bsz# 之后使用narrow方法对不规整的剩余数据进行删除,# 第一个参数是代表横轴删除还是纵轴删除, 0为横轴,1为纵轴# 第二个和第三个参数代表保留开始轴到结束轴的数值.类似于切片# 可参考下方演示示例进行更深理解.data = data.narrow(0, 0, nbatch * bsz)# >>> data# tensor([[   3],#    [  12],#    [3852],#    ...,#    [  78],#    [ 299],#    [  36]])# 后面不能形成bsz个的一组数据被删除# 接着我们使用view方法对data进行矩阵变换, 使其成为如下样式:# tensor([[    3,    25,  1849,  ...,     5,    65,    30],#    [   12,    66,    13,  ...,    35,  2438,  4064],#    [ 3852, 13667,  2962,  ...,   902,    33,    20],#    ...,#    [  154,     7,    10,  ...,     5,  1076,    78],#    [   25,     4,  4135,  ...,     4,    56,   299],#    [    6,    57,   385,  ...,  3168,   737,    36]])# 因为会做转置操作, 因此这个矩阵的形状是[None, bsz],# 如果输入是训练数据的话,形状为[104335, 20], 可以通过打印data.shape获得.# 也就是data的列数是等于bsz的值的.data = data.view(bsz, -1).t().contiguous()# 最后将数据分配在指定的设备上.return data.to(device)
  • batchify的样式转化图

大写字母A,B,C ... 代表句子中的每个单词 

  • 接下来将使用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)
  • 上面的分割批次并没有进行源数据与目标数据的处理,接下来将根据语言模型训练的语料规定来构建源数据与目标数据。
  • 语言模型训练的语料规定:如果源数据为句子ABCD,ABCD代表句子中的词汇或符号,则它的目标数据为BCDE,BCDE分别代表ABCD的下一个词汇
  • 如图所示,这里的句子序列是竖着的,而且发现如果用一个批次处理完所有数据,以训练数据为例,每个句子长度高达104335,这明显是不科学的,因此在这里要限定每个批次中的句子长度允许的最大值bptt。
  • 批次化过程的第二个函数get_batch代码分析 
# 令子长度允许的最大值bptt为35
bptt = 35def 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]# 根据语言模型训练的语料规定, 它的目标数据是源数据向后移动一位# 因为最后目标数据的切片会越界, 因此使用view(-1)来保证形状正常.target = source[i+1:i+1+seq_len].view(-1)return data, target

4.  构建训练和评估函数

  • 设置模型超参数和初始化模型
# 通过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)
  • 模型训练代码分析
# 导入时间工具包
import timedef train():"""训练函数"""# 模型开启训练模式model.train()# 定义初始损失为0total_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()# 日志打印间隔定为200log_interval = 200# 如果batch是200的倍数且大于0,则打印相关日志if batch % log_interval == 0 and batch > 0:# 平均损失为总损失除以log_intervalcur_loss = total_loss / log_interval# 需要的时间为当前时间减去开始时间elapsed = time.time() - start_time# 打印轮数, 当前批次和总批次, 当前学习率, 训练速度(每豪秒处理多少批次),# 平均损失, 以及困惑度, 困惑度是衡量语言模型的重要指标, 它的计算方法就是# 对交叉熵平均损失取自然对数的底数.print('| epoch {:3d} | {:5d}/{: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)))# 每个批次结束后, 总损失归0total_loss = 0# 开始时间取当前时间start_time = time.time()
  • 模型评估代码分析
def evaluate(eval_model, data_source):"""评估函数, 评估阶段包括验证和测试,它的两个参数eval_model为每轮训练产生的模型data_source代表验证或测试数据集"""# 模型开启评估模式eval_model.eval()# 总损失归0total_loss = 0# 因为评估模式模型参数不变, 因此反向传播不需要求导, 以加快计算with torch.no_grad():# 与训练过程相同, 但是因为过程不需要打印信息, 因此不需要batch数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()# 计算平均损失cur_loss = total_loss / ((data_source.size(0) - 1) / bptt)            # 返回平均损失return cur_loss

5. 进行训练和评估

  • 模型的训练与验证代码分析
# 首先初始化最佳验证损失,初始值为无穷大
import copy
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_modelif val_loss < best_val_loss:best_val_loss = val_loss# 使用深拷贝,拷贝最优模型best_model = copy.deepcopy(model)# 每轮都会对优化方法的学习率做调整scheduler.step()
  • 模型测试代码分析
# 我们仍然使用evaluate函数,这次它的参数是best_model以及测试数据
test_loss = evaluate(best_model, test_data)# 打印测试日志,包括测试损失和测试困惑度
print('=' * 89)
print('| End of training | test loss {:5.2f} | test ppl {:8.2f}'.format(test_loss, math.exp(test_loss)))
print('=' * 89)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/168159.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

硬件服务器更换IPMI

重启按F2进入Biso界面 然后进入Server Management界面&#xff0c;选择BMC LAN Configuration

excel怎么固定前几行前几列不滚动?

在Excel中&#xff0c;如果你想固定前几行或前几列不滚动&#xff0c;可以通过以下几种方法来实现。详细的介绍如下&#xff1a; **固定前几行不滚动&#xff1a;** 1. 选择需要固定的行数。例如&#xff0c;如果你想要固定前3行&#xff0c;应该选中第4行的单元格。 2. 在E…

Markdown语法详解

文章目录 [toc] 一、简介二、样式1. 标题2. 字体3. 引用4. 分割线5. 图片6. 超链接7. 列表8. 表格9. 代码 一、简介 以前写学习文档常用的软件都是Word或者CSDN自带的编辑器&#xff0c;但Word用起来不太灵活&#xff0c;而CSDN自带编辑器又感觉逼格不够&#xff08;主要原因&…

c语言练习91:合并两个有序链表

合并两个有序链表 力扣&#xff08;LeetCode&#xff09;官网 - 全球极客挚爱的技术成长平台 代码1&#xff1a; /*** Definition for singly-linked list.* struct ListNode {* int val;* struct ListNode *next;* };*/typedef struct ListNode ListNode; struct Li…

AD9371 官方例程HDL详解之JESD204B TX侧时钟生成 (一)

AD9371 系列快速入口 AD9371ZCU102 移植到 ZCU106 &#xff1a; AD9371 官方例程构建及单音信号收发 ad9371_tx_jesd -->util_ad9371_xcvr接口映射&#xff1a; AD9371 官方例程之 tx_jesd 与 xcvr接口映射 参考资料&#xff1a; UltraScale Architecture GTH Transceive…

驱动开发LED灯绑定设备文件

头文件 #ifndef __HEAD_H__ #define __HEAD_H__typedef struct {unsigned int MODER;unsigned int OTYPER;unsigned int OSPEEDR;unsigned int PUPDR;unsigned int IDR;unsigned int ODR; }gpio_t;#define PHY_LED1_ADDR 0x50006000 #define PHY_LED2_ADDR 0x50007000 #defin…

【Java集合类面试六】、 HashMap有什么特点?

文章底部有个人公众号&#xff1a;热爱技术的小郑。主要分享开发知识、学习资料、毕业设计指导等。有兴趣的可以关注一下。为何分享&#xff1f; 踩过的坑没必要让别人在再踩&#xff0c;自己复盘也能加深记忆。利己利人、所谓双赢。 面试官&#xff1a;HashMap有什么特点&…

ONEPIECE!程序环境和预处理——C语言最终章

时间过得飞快呀&#xff0c;从第一篇blog到现在&#xff0c;已经有三四个月的时间了&#xff0c;而我们终于也迎来了C语言的最终章——程序环境和预处理&#xff01;加油吧朋友们&#xff0c;ONEPIECE就在眼前~ 目录 一、程序的"翻译环境"和"运行环境" 二…

VUE到底有什么好处?

网上有许多前端开发框架的对比&#xff0c;相对的&#xff0c;VUE在综合评分方面还是优秀的。以下是一些State of JavaScript调查数据结果&#xff1a; 使用率&#xff1a;VUE使用者在调研开发者中占比 51%&#xff1b; 开发者满意度&#xff1a;VUE的综合开发者满意度达到64%…

springcloud笔记 (8) -网关 Gateway

网关 出国需要过海关 网关&#xff1a;网络的关卡 网关的作用 1&#xff1a;路由转发 2&#xff1a;安全控制 保护每个服务&#xff0c;不需要将每个暴露出去 3&#xff1a;负载均衡 1.没有网关&#xff1a;客户端直接访问我们的微服务&#xff0c;会需要在客户端配置很多…

ZooKeeper下载、安装、配置和使用

天行健&#xff0c;君子以自强不息&#xff1b;地势坤&#xff0c;君子以厚德载物。 每个人都有惰性&#xff0c;但不断学习是好好生活的根本&#xff0c;共勉&#xff01; 文章均为学习整理笔记&#xff0c;分享记录为主&#xff0c;如有错误请指正&#xff0c;共同学习进步。…

用“价值”的视角来看安全:《构建新型网络形态下的网络空间安全体系》

文章目录 写在前面安全认知的硬核概念威胁的演化路径与发展趋势构建网络空间安全体系好书推荐 写作末尾 写在前面 网络空间安全体系是建立在先进技术、严密监控和综合策略之上的综合性框架&#xff0c;旨在保护网络免受恶意攻击、数据泄露和网络犯罪的威胁&#xff0c;其核心包…

window11安装Python环境

python环境安装 访问Python官网:https://www.python.org/ 点击downloads按钮&#xff0c;在下拉框中选择系统类型(windows/Mac OS/Linux等) 选择下载最新版本的Python cmd命令如果出现版本号以及>>>则表示安装成功 如果出现命令行中输入python出现如下错误 可能…

现在大火的低代码是怎么回事?进来聊聊低代码

一、前言 开发过程中&#xff0c;只是觉得前端后端合起来&#xff0c;有很多冗余信息&#xff0c;被代码一遍遍重复表达&#xff0c;是一件很枯燥、无聊的事情。 这些枯燥的重复工作&#xff0c;完全可以由机器来做&#xff0c;以便解放出我们的时间&#xff0c;来做更有价值的…

wordpress搬家后,更改固定链接404文章无法打开,找不到网页与解决办法

出现这个问题的原因可能是服务没有设置伪静态设置&#xff0c;需要配置nginx服务器或者apach服务器 1.问题描述 WordPress中修改固定链接&#xff0c;如下。 保存后再打开网页出现类似于如下404错误。 2.解决办法 打开Nginx的配置文件(如果你没有自己写过那就是默认的nginx.…

sql server2014如何添加多个实例 | 以及如何删除多个实例中的单个实例

标题sql server2014如何添加多个实例 前提&#xff08;已安装sql server2014 且已有默认实例MSSQLSERVER&#xff09; 添加新的实例 其实就是根据安装步骤再安装一次&#xff08;区别在过程中说明&#xff09; 双击安装 选择“全新独立安装或添加现有功能” 然后下一步下一…

博客后台模块续更(五)

十一、后台模块-菜单列表 菜单指的是权限菜单&#xff0c;也就是一堆权限字符串 1. 查询菜单 1.1 接口分析 需要展示菜单列表&#xff0c;不需要分页。可以针对菜单名进行模糊查询。也可以针对菜单的状态进行查询。菜单要按照父菜单id和orderNum进行排序 请求方式 请求路径…

【数据结构】——二叉树的基础知识

数概念及结构 数的分类 二叉树、多叉树 数的概念 树是一种非线性的数据结构&#xff0c;它是由n(n>0)个有限节点组成一个具有层次关系的集合。把它叫做树的原因是它看起来像一颗倒挂的树&#xff0c;也就是说它是跟朝上&#xff0c;而叶朝下的。 有一个特殊的节点&…

java读取指定文件夹下的全部文件,并输出文件名,文件大小,文件创建时间

import java.io.IOException; import java.nio.file.*; import java.nio.file.attribute.*; import java.util.ArrayList; import java.util.List; public class Main { public static void main(String[] args) { try { Path startingDir Paths.get("你的目…

Spring中动态代理设计模式

目录 一、什么是动态代理 二、动态代理开发步骤 2.1 搭建开发环境 2.2 具体过程 三、动态字节码技术 四、动态代理开发简化代理开发 一、什么是动态代理 其实不管是静态代理还是动态代理其本质都是一样的&#xff0c;都是通过代理类为目标类增加额外功能&#xff0c;从而方便目…