NLP入门——词袋语言模型的搭建、训练与预测

卷积语言模型实际上是取了句子最后ctx_len个词作为上下文输入模型来预测之后的分词。但更好的选择是我们做一个词袋,将所有分词装在词袋中作为上下文,这样预测的分词不只根据最后ctx_len个分词,而是整个词袋中的所有分词。
例如我们的序列是:1 2 3 4 5
则首先词袋中是1,有
首先 1 -> 2
接着 1 + 2 -> 3
接着 1 + 2 + 3 -> 4
最后 1 + 2 + 3 + 4 -> 5

模型的搭建

pytorch求前缀和函数

>>> import torch
>>> a = torch.arange(5)
>>> a.cumsum(dim=-1)
tensor([ 0,  1,  3,  6, 10])
>>> a = torch.randn(3,4)
>>> a
tensor([[-0.0626, -1.4848, -0.4831,  0.4393],[ 0.6631, -0.8985, -0.5386,  1.2894],[ 1.2553,  0.1273,  1.0798,  0.4363]])
>>> a.cumsum(0)
tensor([[-0.0626, -1.4848, -0.4831,  0.4393],[ 0.6005, -2.3833, -1.0217,  1.7287],[ 1.8558, -2.2560,  0.0581,  2.1650]])

我们可以看到,使用cumsum函数可以求制定维度的前缀和累加。使用这个函数帮助我们实现新的分词进入词袋的过程。

#BoWLM.py
#encoding: utf-8
import torch
from torch import nnclass NNLayer(nn.Module):def __init__(self, isize, hsize, dropout,norm_residual=True,**kwargs):super(NNLayer, self,).__init__()   ##调用父类的初始化函数self.net = nn.Sequential(nn.Linear(isize, hsize),nn.ReLU(inplace=True), #设置relu激活函数,inplace=True在原始张量上进行nn.Dropout(p=dropout, inplace=False),#设置丢弃率防止过拟合,同时创建一个新的张量nn.Linear(hsize, isize, bias=False), nn.Dropout(p=dropout, inplace=True))self.normer = nn.LayerNorm(isize) #做归一化self.norm_residual = norm_residual #设置变量存储做判断def forward(self, input):_ = self.normer(input) #稳定之后的结果 return (_ if self.norm_residual else input) + self.net(_)#如果参数初始化做的好,就用LayerNorm后的值,否则用原始值class NNLM(nn.Module):def __init__(self, vcb_size, isize, hsize, dropout,nlayer, bindemb=True, **kwargs): #有多少个词,就分多少类,类别数为vcb_sizesuper(NNLM, self).__init__()self.emb = nn.Embedding(vcb_size, isize,padding_idx=0)                #<pad>的索引为0#self.comp = nn.Linear(ctx_len * isize, isize, bias=False) #将4个词的向量降维为isizeself.drop = nn.Dropout(p=dropout, inplace=True) #embedding后dropoutself.nets = nn.Sequential(*[NNLayer(isize, hsize, dropout)for _ in range(nlayer)])self.classifier = nn.Linear(isize, vcb_size)if bindemb:self.classifier.weight = self.emb.weight#将emb的权重赋给分类器self.normer = nn.LayerNorm(isize)self.out_normer = nn.LayerNorm(isize)# input: (bsize, seql-1) 句数,句长-1 由于最后一个词是预测不作为输入def forward(self, input):out = self.emb(input)# out: (bsize, seql-1, isize)   out = self.drop(out).cumsum(dim=1) #在句子这一维度累加#(bsize, sum_pretex, isize)out = self.normer(out) #使用归一化,使模长均匀out = self.out_normer(self.nets(out))return self.classifier(out) #分类产生参数  

相比于卷积模型,词袋语言模型少了卷积以及降维的操作过程。

模型的训练

#BoWtrain.py
#encoding: utf-8import torch
from torch import nn
from BoWLM import NNLM #导入模型
from h5py import File as h5File #读训练数据
from math import sqrt
from random import shuffle #使输入数据乱序,使模型更均衡
from lrsch import SqrtDecayLR
from tqdm import tqdmtrain_data = "train.h5"#之前已经张量转文本的h5文件
isize = 64              
hsize = isize * 2       #设置初始参数
dropout = 0.3           #设置丢弃率
nlayer = 4              #设置层数
gpu_id = -1             #设置是否使用gpu
lr = 1e-3               #设置初始学习率
max_run = 8           #设置训练轮数nreport = 5000          #每训练5000个batch打印一次
tokens_optm = 25000     #设置更新参数的词数阈值def init_model_parameters(modin): #初始化模型参数with torch.no_grad():         #不是训练不用求导for para in modin.parameters():if para.dim() > 1:          #若维度大于1,说明是权重参数_ = 1.0 / sqrt(para.size(-1))para.uniform_(-_,_)     #均匀分布初始化for _m in modin.modules():      #遍历所有小模型if isinstance(_m, nn.Linear):#如果小模型是linear类型if _m.bias is not None: #初始化bias_m.bias.zero_()elif isinstance(_m, nn.LayerNorm):#初始化LayerNorm参数_m.weight.fill_(1.0)_m.bias.zero_()elif isinstance(_m, nn.Embedding):if _m.padding_idx >= 0:_m.weight[_m.padding_idx].zero_()return modindef train(train_data, tl, model, lossf, optm, cuda_device,nreport=nreport, tokens_optm=tokens_optm):#nreport为每训练一部分词打一次epochmodel.train() #设置模型在训练的模式src_grp = train_data["src"] #从输入数据中取出句子_l = 0.0  #_l用来存当前loss_t = 0    #_t用来存句子数_lb = 0.0_tb = 0_tom = 0for _i, _id in tqdm(enumerate(tl, 1)):seq_batch = torch.from_numpy(src_grp[_id][()])#seq_batch:[bsize, seql]_seqlen = seq_batch.size(-1)  #取出每个batch的句长if cuda_device is not None:seq_batch = seq_batch.to(cuda_device, non_blocking=True)#将数据放在同一gpu上seq_batch = seq_batch.long()   #数据转换为long类型seq_i = seq_batch.narrow(1, 0, _seqlen - 1) #训练数据读取前seql-1的数据#seq_i:[bsize, seql-1]seq_o = seq_batch.narrow(1, 1, _seqlen - 1) #预测数据读取后seql-1的数据做标签#seq_o:[bsize, seql-1]out = model(seq_i)          #获得模型结果#out: {bsize, seql-1, vcb_size} vcb_size即预测类别数loss = lossf(out.view(-1, out.size(-1)), seq_o.contiguous().view(-1)) #转换out维度为[bsize*(seql-1),vcb_size],seq_o:[bsize*(seql-1)]_lossv = loss.item()_l += _lossv        #整个训练集的loss_lb += _lossv       #每个batch的loss_n = seq_o.ne(0).int().sum().item() #seq_o中不是<pad>的位置的数量_t += _n            #整个训练集的分词数_tb += _n           #每个batch的分词数_tom += _nloss.backward()                 #反向传播求导if _tom > tokens_optm:          #当词数大于时更新参数optm.step()                     #参数的更新optm.zero_grad(set_to_none=True)#参数更新后清空梯度_tom = 0if _i % nreport == 0:   #每训练5000个batch打印一次print("Average loss over %d tokens: %.2f"%(_tb, _lb/_tb))_lb = 0.0_tb = 0save_model(model, "checkpoint.pt") #暂存检查点模型            return _l / _t #返回总的lossdef save_model(modin, fname): #保存模型所有内容 权重、偏移、优化torch.save({name: para.cpu() for name, para inmodel.named_parameters()}, fname)t_data = h5File(train_data, "r")#以读的方式打开训练数据vcb_size = t_data["nword"][()].tolist()[0] #将返回的numpy的ndarray转为list
#在我们的h5文件中存储了nword: 总词数model = NNLM(vcb_size, isize, hsize, dropout, nlayer)
model = init_model_parameters(model) #在cpu上初始化模型
lossf = nn.CrossEntropyLoss(reduction='sum', ignore_index=0,
label_smoothing=0.1)
#设置ignore_index=0,即忽略<pad>的影响if (gpu_id >= 0) and torch.cuda.is_available(): #如果使用gpu且设备支持cudacuda_device = torch.device("cuda", gpu_id)  #配置gputorch.set_default_device(cuda_device)
else:cuda_device = Noneif cuda_device is not None:                     #如果要用gpumodel.to(cuda_device)                       #将模型和损失函数放在gpu上lossf.to(cuda_device)optm = torch.optim.Adam(model.parameters(), lr=lr, 
betas=(0.9, 0.98), eps=1e-08)
#使用model.parameters()返回模型所有参数,
lrm = SqrtDecayLR(optm, lr) #将优化器和初始学习率传入tl = [str(_) for _ in range(t_data["ndata"][()].item())] #获得字符串构成的训练数据的listsave_model(model, "eva.pt")
for i in range(1, max_run + 1):shuffle(tl)         #使数据乱序_tloss = train(t_data, tl, model, lossf, optm,cuda_device)  #获取每轮训练的损失print("Epoch %d: train loss %.2f"%(i, _tloss)) #打印日志save_model(model, "eva.pt")lrm.step() #每轮训练后更新学习率t_data.close()

在训练脚本中,我们的输入是一个(bsize, seql-1)的张量,即每个batch中句子的前 seq - 1 个分词。标签数据也是(bsize, seql-1)的张量,是每个batch中句子的后 seq - 1 个分词。
若我们有句子:1 2 3 4 5 6 7
seq_i : 1 2 3 4 5 6
seq_o : 2 3 4 5 6 7
通过1预测2,通过1和2 预测3,通过1 ,2和 3 预测4,直到通过 1-6 预测 7,计算预测结果和标签的交叉熵损失函数。
在命令行输入训练模型:

:~/nlp/lm$ python BoWtrain.py
4999it [29:43,  2.79it/s]Average loss over 21151520 tokens: 7.31
9999it [59:34,  2.76it/s]Average loss over 21146102 tokens: 6.84

模型的解码与预测

模型的解码

我们需要在模型文件NNLM类中添加decode方法

#BoWLM.py
#encoding: utf-8
class NNLM(nn.Module): # input: (bsize, seql)def decode(self, input, maxlen=50): #设置最多生成50个分词rs = inputbsize =input.size(0)done_trans = _sum_cache = None   #记录是否完成生成for i in range(maxlen):_sum_cache = self.drop(self.emb(rs)).sum(dim=1) if _sum_cache is None else (_sum_cache + self.drop(self.emb(out.squeeze(-1)))) #squeeze(-1) 作用是去掉最后一维# ->(bsize, seql, isize) -> (bsize, isize)#将前seql个词的向量求和#初始化_sum_cache为None,如果不为None说明已有值,则只需要把新的分词累加即可,无需从头再求一遍和out = self.normer(_sum_cache)out = self.out_normer(self.nets(out))out = self.classifier(out).argmax(-1,keepdim=True) #取最后一维分数最高的索引#这一步对应分类,keepdim=True保持维度便于拼接# out:(bsize, isize) -> (bsize, vcb_size) -> (bsize, 1)rs = torch.cat([rs, out], dim=1)    #将预测的词拼接到原句后,在第一维度即seql后_eos = out.eq(2) #当遇到<eos>解码停止# _eos:(bsize, 1)if done_trans is None:done_trans = _eoselse:done_trans |= _eos #将_eos中的True赋给done_trans#_eos中的元素如果为True,则说明在该索引位置上out值为2即结束标志if done_trans.all().item(): #当全都为True,说明此batch中所有句子预测都为<eos>,即解码完成breakreturn rs

模型的预测

在模型的预测时,我们需要保证输入句子的<pad>向量为0,避免对模型训练产生影响。

>>> import torch
>>> from torch import nn
>>> emb = nn.Embedding(5, 3, padding_idx=0)
>>> emb
Embedding(5, 3, padding_idx=0)
>>> emb.weight
Parameter containing:
tensor([[ 0.0000,  0.0000,  0.0000],[ 0.9253,  1.2580, -0.6622],[ 0.0658,  0.1537, -0.3299],[ 0.6379, -0.2940, -0.1276],[-0.7669,  0.5647,  0.1014]], requires_grad=True)

打印emb的weight权重矩阵,我们设置padding_idx=0,则会将下标为0的向量初始化为零向量,避免其干扰训练结果。
但在训练过程中可能会导致零向量被修改,为了避免其被修改,我们在解码时再将其赋值为0:

>>> emb.padding_idx
0
>>> with torch.no_grad():
...     emb.weight[emb.padding_idx].zero_()
... 
tensor([0., 0., 0.], grad_fn=<Invalid>)
>>> emb.weight
Parameter containing:
tensor([[ 0.0000,  0.0000,  0.0000],[ 0.9253,  1.2580, -0.6622],[ 0.0658,  0.1537, -0.3299],[ 0.6379, -0.2940, -0.1276],[-0.7669,  0.5647,  0.1014]], requires_grad=True)

这里需要注意关闭torch的梯度计算,才能安全修改权重矩阵的值以及其他参数。

#BoWpredict.py
#encoding: utf-8import sys
import torch
from BoWLM import NNLM               #读模型
from h5py import File as h5File         #读文件
from vcb import load_vcb, reverse_vcb   #获取词表isize = 64              
hsize = isize * 2       #设置初始参数
dropout = 0.3           #设置丢弃率
nlayer = 4              #设置层数
gpu_id = -1             #设置是否使用gpudef extract(lin, vcb): #提取结果的函数rs = []for lu in lin:if lu > 1:if lu == 2:breakelse:rs.append(vcb[lu])  #返回索引对应词典中的分词return " ".join(rs)     #返回空格分隔的解码后的字符串test_data = sys.argv[1]
test_file = h5File(test_data, "r")            #读验证集
vcb_size = test_file["nword"][()].tolist()[0] #获取总词数tgt_vcb = reverse_vcb(load_vcb(sys.argv[2], vanilla=False))
#vanilla设置为false,读取词表时需考虑到特殊标记:0,1,2model = NNLM(vcb_size, isize, hsize, dropout, nlayer)
model_file = sys.argv[-1]       #获取模型
with torch.no_grad():           #避免求导_ = torch.load(model_file)  #加载词典for name, para in model.named_parameters():if name in _:para.copy_(_[name]) #从词典里取出name的参数model.emb.weight[model.emb.padding_idx].zero_()if (gpu_id >= 0) and torch.cuda.is_available():cuda_device = torch.device("cuda", gpu_id)torch.set_default_device(cuda_device)
else:cuda_device = Noneif cuda_device is not None:model.to(cuda_device)       #判断是否使用cudamodel.eval()src_grp = test_file["src"]
ens = "\n".encode("utf-8")
with torch.no_grad(), open(sys.argv[3],"wb") as f: #解码避免求导,将预测标签按行写入文件for _ in range(test_file["ndata"][()].item()):#每个batch上遍历seq_batch = torch.from_numpy(src_grp[str(_)][()])if cuda_device is not None:seq_batch = seq_batch.to(cuda_device, non_blocking=True)seq_batch = seq_batch.long()    #s数据类型转换output = model.decode(seq_batch).tolist() #将解码后的numpy转为listoutput = [extract(_, tgt_vcb) for _ in output] #将张量转为文本f.write("\n".join(output).encode("utf-8"))f.write(ens)                #每个batch间还应有换行test_file.close()

在命令行输入并查看模型预测的结果:

:~/nlp/lm$ python BoWpredict.py test.h5 zh.vcb pred.txt checkpoint.pt 
:~/nlp/lm$ less pred.txt 

在这里插入图片描述

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

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

相关文章

javaweb图书商城系统带万字文档网上书城java项目java课程设计java毕业设计

文章目录 图书商城系统一、项目演示二、项目介绍三、万字项目文档四、部分功能截图五、部分代码展示六、底部获取项目源码带万字文档&#xff08;9.9&#xffe5;带走&#xff09; 图书商城系统 一、项目演示 网上书城 二、项目介绍 语言&#xff1a;java 数据库&#xff1a;…

typora 两边太宽,设置宽度

步骤&#xff1a; 查看目前使用主题类型 文件 —> 偏好设置 —> 外观 —> 打开主题文件夹 修改对应的主题&#xff1a;max-width

MySQL 数据库支持存储emoji表情

当你通过node.js&#xff0c;往mysql存储emoji表情的时候&#xff0c;可能会遇到报错&#xff1a; code: ‘ER_TRUNCATED_WRONG_VALUE_FOR_FIELD’, errno: 1366, sqlMessage: “Incorrect string value: ‘\xF0\x9F\x8D\x94’ for column ‘nick_name’ at row 1”, sqlState…

MySQL数据库主从复制+mycat读写分离+MHA实操

目录 一、主从复制 1.1 主从复制简介 1.2 MySQL支持的复制类型 1.3 主从复制的工作过程 1.4 主从复制的同步模式 1.4.1 异步复制&#xff08;Asynchronous replication&#xff09; 1.4.2 全同步复制&#xff08;Fully synchronous replication&#xff09; 1.4.3 半同…

力扣2356.二维差分模板——子矩阵元素加1

力扣2356.二维差分模板——子矩阵元素加1 模板题 最后将n2*n2的矩阵删去周围一圈变成n*n矩阵的操作 class Solution {public:vector<vector<int>> rangeAddQueries(int n, vector<vector<int>>& queries) {vector<vector<int>> res…

【JavaWeb程序设计】Servlet(二)

目录 一、改进上一篇博客Servlet&#xff08;一&#xff09;的第一题 1. 运行截图 2. 建表 3. 实体类 4. JSP页面 4.1 login.jsp 4.2 loginSuccess.jsp 4.3 loginFail.jsp 5. mybatis-config.xml 6. 工具类&#xff1a;创建SqlSessionFactory实例&#xff0c;进行 My…

UI设计工具选择指南:Sketch、XD、Figma、即时设计

在数字产品设计产业链中&#xff0c;UI设计师往往起着连接前后的作用。产品经理从一个“需求”开始&#xff0c;制定一个抽象的产品概念原型。UI设计师通过视觉呈现将抽象概念具体化&#xff0c;完成线框图交互逻辑视觉用户体验&#xff0c;最终输出高保真原型&#xff0c;并将…

threadx netxduo stm32f407上实现http server

这次用的是CubeIDE CubeMX 要把NX_APP的mem分配的大一些&#xff0c;在app_azure_rtos.c中&#xff0c;我给的是40*1024&#xff0c;如果给的不够&#xff0c;会导致后面无法分配pool和thread等等 需要用到filex 要在CubeMX里面勾选上&#xff0c;还要用到http_server和dhcp …

1. InternLM - 入门岛

第1关 Linux 基础知识 1. 完成SSH连接与端口映射并运行hello_world.py SSH连接配置 # wsl2中生成密钥对&#xff08;~/.ssh/id_rsa, ~/.ssh/id_rsa.pub&#xff09; ssh-keygen -t rsa# 将id_rsa.pub在internStudio作为公钥导入SSH登录 $ ssh -p 38871 rootssh.intern-ai.o…

汽车免拆诊断案例 | 奥迪 Q7 e-tron无法通过插电式充电器充电

故障现象 车主反映&#xff0c;车辆无法使用自带的插电式充电器充电。&#xff08;这种充电方法是“Mode 2充电”&#xff0c;3针插头&#xff0c;10 A&#xff0c;2.2 kW&#xff09; 接车后验证故障&#xff0c;将Type 2充电插头连接到车辆时&#xff0c;充电口锁定销循环三…

3DSC(3D形状上下文特征)

形状上下文(shape context简写为SC)由Serge Belongie等人于2002年首次提出,是一种很流行的二维形状特征描述子,多用于目标识别和形状特征匹配。 2004年,Andrea Frome等人将形状上下文的工作从二维数据迁移到三维数据上提出了3D形状上下文(3DSC) 原理解析 2DSC的算法流程…

Git的基本知识点 + GitBash安装Pacman + Git命令含有中文,终端输出中文乱码

Git的基本知识点&#xff1a;整理自以下作者的文章繁华似锦Fighting的文章https://www.jianshu.com/nb/49854893另外还补充了git ls-file、.gitignore 等内容&#xff0c;涉及具体操作&#xff0c;还有命令总结。简略版可以看以上作者的文章&#xff0c;详细版可以看网盘里面的…

【STM32学习】stm32cubemx基础配置,点亮/闪烁一个LED灯

1、cubemx开始 打开cubemx&#xff0c;在help栏中&#xff0c;点击&#xff0c;选择安装你所需要的stm32芯片版本&#xff0c; 打开后&#xff0c;选择你所配置的芯片型号&#xff0c;如我配置的是stm32f4系列&#xff0c;勾选&#xff0c;点击Install。 安装好后&#xff0c;…

HybridCLR原理中的重点总结

序言 该文章以一个新手的身份&#xff0c;讲一下自己学习的经过&#xff0c;大家更快的学习HrbirdCLR。 我之前的两个Unity项目中&#xff0c;都使用到了热更新功能&#xff0c;而热更新的技术栈都是用的HybridCLR。 第一个项目本身虽然已经集成好了热更逻辑&#xff08;使用…

Java | Leetcode Java题解之第227题基本计算器II

题目&#xff1a; 题解&#xff1a; class Solution {public int calculate(String s) {Deque<Integer> stack new ArrayDeque<Integer>();char preSign ;int num 0;int n s.length();for (int i 0; i < n; i) {if (Character.isDigit(s.charAt(i))) {num…

Elasticseach学习

概念 是一个开源的分布式搜索引擎&#xff0c;可以应用于搜索、日志监控等 倒排索引 正向索引&#xff1a;基于文档id创建索引。查询词条时必须先找到文档&#xff0c;而后判断是否包含词条 倒排索引&#xff1a;对文档内容分词&#xff0c;对词条创建索引&#xff0c;并记录…

docker-compose mongodb 副本集

准备 cd /opt/mongos 把 3 个节点的数据目录创建好 mkdir -p ./mongos/{mongo1,mongo2,mongo3} mongodb 使用 keyFile 进行认证&#xff0c;副本集群中的每个节点的 mongodb 使用 keyFile 的内容作为认证其他成员的共享密码。mongodb 实例只有拥有正确的 keyFile 才可以加入副…

HCIP课堂笔记

第一章 1、数据转换---目标&#xff1a;抽象语言---二进制---电信号 2、应用程序---接收参数和指令&#xff08;编码&#xff1a;接收传递给计算机指令参数最终转换为二进制&#xff09; 3、二进制---电信号 4、对于整个互联网而言指定了统一的标准——OSI/RM参考模型 &…

GitLab和Git

GitLab保姆级教程 文章目录 GitLab保姆级教程一、GitLab安装二、添加组和用户三、新增项目四、Git上传项目说明五、命令行指引 根据以下说明从计算机中上传现有文件&#xff1a;六、创建与合并分支七、GitLab回滚到特定版本八、数据备份与恢复九、docker中创建gitlab GIT 常用命…

“金山-讯飞”杯2024年武汉理工大学程序设计竞赛 A. Mobiusp败走***(思维题-点双连通分量、连通性)

题目 思路来源 官方题解 题解 手玩发现&#xff0c;能换的话&#xff0c;当且仅当.和1在一个环里&#xff0c;而这就是点双连通分量 所以最优策略是先把.换到(x,y)的位置&#xff0c;然后判断.和1在不在一个环里 也就是&#xff1a; 1. 判断删掉1时&#xff0c;.和(x,y)联…