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

news2024/9/20 1:05:43

卷积语言模型实际上是取了句子最后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 nn

class 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_size
        super(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个词的向量降维为isize
        self.drop = nn.Dropout(p=dropout, inplace=True) #embedding后dropout
        self.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-8

import 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 tqdm

train_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 modin

def train(train_data, tl, model, lossf, optm, cuda_device,
 nreport=nreport, tokens_optm=tokens_optm):#nreport为每训练一部分词打一次epoch
    
    model.train() #设置模型在训练的模式
    src_grp = train_data["src"] #从输入数据中取出句子
    _l = 0.0  #_l用来存当前loss
    _t = 0    #_t用来存句子数
    _lb = 0.0
    _tb = 0
    _tom = 0
    
    for _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 += _n
        loss.backward()                 #反向传播求导
        if _tom > tokens_optm:          #当词数大于时更新参数
            optm.step()                     #参数的更新
            optm.zero_grad(set_to_none=True)#参数更新后清空梯度
            _tom = 0
        if _i % nreport == 0:   #每训练5000个batch打印一次
            print("Average loss over %d tokens: %.2f"%(_tb, _lb/_tb))
            _lb = 0.0
            _tb = 0
            save_model(model, "checkpoint.pt") #暂存检查点模型            
        
    return _l / _t #返回总的loss

def save_model(modin, fname): #保存模型所有内容 权重、偏移、优化
    
    torch.save({name: para.cpu() for name, para in
    model.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且设备支持cuda
    cuda_device = torch.device("cuda", gpu_id)  #配置gpu
    torch.set_default_device(cuda_device)
else:
    cuda_device = None

if cuda_device is not None:                     #如果要用gpu
    model.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())] #获得字符串构成的训练数据的list

save_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 = input
        bsize =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 = _eos
            else:
                done_trans |= _eos #将_eos中的True赋给done_trans
            #_eos中的元素如果为True,则说明在该索引位置上out值为2即结束标志
            if done_trans.all().item(): #当全都为True,说明此batch中所有句子预测都为<eos>,即解码完成
                break
        
        return 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-8

import 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             #设置是否使用gpu

def extract(lin, vcb): #提取结果的函数
    
    rs = []
    for lu in lin:
        if lu > 1:
            if lu == 2:
                break
            else:
                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,2

model = 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 = None
    
if cuda_device is not None:
    model.to(cuda_device)       #判断是否使用cuda

model.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转为list
        output = [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.coloradmin.cn/o/1916820.html

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

相关文章

以太网的演变之路:技术进步与应用拓展

最初的以太网是由美国施乐&#xff08;Xerox&#xff09;公司的Palo Alto研究中心&#xff08;简称为PARC&#xff09;于1975年研制成功的。以太网从标准以太网&#xff08;10Mbit/s&#xff0c;也称为传统以太网&#xff09;开始逐步在有线局域网市场中占据了统治地位&#xf…

禁用windows的语音识别快捷键win+ctrl+s

win11组合键winctrls会弹出语音识别提示&#xff0c;即使到设置里禁用了语音识别也没用 解决办法&#xff1a;安装PowerToys&#xff0c;通过“键盘管理器”-“重新映射快捷键”禁用 PowerToys是微软自己的工具&#xff0c;不用担心安全问题&#xff0c;下载地址&#xff1a;h…

游戏视频是后期配音好还是边录边配 游戏视频怎么剪辑制作才能火 视频剪辑免费软件

游戏视频后期配音是先配还是先剪&#xff1f;游戏视频后期配音没有统一的准则&#xff0c;可以先配&#xff0c;也可以后配&#xff0c;主要是根据内容而定。游戏视频剪辑在游戏玩家中十分流行&#xff0c;那么&#xff0c;游戏视频怎么剪辑制作&#xff1f;下面让我们以具体的…

医院门诊预约挂号小程序模板源码

医院门诊预约挂号小程序模板源码,主要有&#xff1a;绿色的医院住院办理&#xff0c;门诊预约挂号微信小程序页面模板。包含&#xff1a;办卡绑定、快速办理预约挂号、门诊缴费、住院服务、医院信息、个人中心、添加就诊人、找医生等等。 医院门诊预约挂号小程序模板源码

zookeeper的shell操作

一&#xff1a;启动拽库的shell命令行 zkCli.sh -server localhost:2181 退出&#xff1a;quit 二&#xff1a;查询所有的命令 help 三&#xff1a;查询对应的节点 --查询zk上的根节点 ls / ls /zookeeper 四&#xff1a;查询对应节点的节点信息&#xff08;节点的元数据&a…

读人工智能全传09神经网络

1. 机器学习 1.1. 人们对人工智能的态度发生突如其来的巨大变化&#xff0c;是由一项核心人工智能技术——机器学习的快速发展所推动的 1.1.1. 机器学习是人工智能的一个分支领域&#xff0c;但在过去60年的绝大部分时间里&#xff0c;它一直在一条独…

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…

大数据基础:Hadoop之MapReduce重点架构原理

文章目录 Hadoop之MapReduce重点架构原理 一、MapReduce概念 二、MapReduce 编程思想 2.1、Map阶段 2.2、Reduce阶段 三、MapReduce处理数据流程 四、MapReduce Shuffle 五、MapReduce注意点 六、MapReduce的三次排序 Hadoop之MapReduce重点架构原理 一、MapReduce概…

微积分-导数6(隐式导数)

隐式导数 前面我们学了如何求这些方程的导数&#xff1a; y x 3 1 or y x sin ⁡ x y \sqrt{x^31} \quad \text{or} \quad y x\sin x yx31 ​oryxsinx 但是如果是下面的方程&#xff0c;又该如何求导呢&#xff1f; x 3 y 3 6 x y x^3 y^3 6xy x3y36xy 这个方程的图…

【架构】分布式与微服务架构解析

分布式与微服务架构解析 一、分布式1、什么是分布式架构2、为什么需要分布式架构3、分布式架构有哪些优势&#xff1f;4、分布式架构有什么劣势&#xff1f;5、分布式架构有哪些关键技术&#xff1f;6、基于分布式架构如何提高其高性能&#xff1f;7、如何基于架构提高系统的稳…

企业资产管理系统带万字文档公司资产管理系统java项目java课程设计java毕业设计

文章目录 企业资产管理系统一、项目演示二、项目介绍三、万字项目文档四、部分功能截图五、部分代码展示六、底部获取项目源码带万字文档&#xff08;9.9&#xffe5;带走&#xff09; 企业资产管理系统 一、项目演示 企业资产管理系统 二、项目介绍 语言&#xff1a;java 数…

开始性能测试之前的准备工作!

性能测试是软件测试中不可或缺的一部分&#xff0c;它可以帮助我们评估软件系统的性能表现&#xff0c;并找出潜在的性能瓶颈。在进行性能测试之前&#xff0c;需要做好充分的准备工作&#xff0c;以确保测试的有效性和准确性。 1. 确定性能测试的目标和范围 * 明确测试目标:性…

Qt/QML学习-定位器

QML学习 定位器例程视频讲解代码 main.qml import QtQuick 2.15 import QtQuick.Window 2.15Window {width: 640height: 480visible: truetitle: qsTr("positioner")Rectangle {id: rectColumnwidth: parent.width / 2height: parent.height / 2border.width: 1Col…

【安全设备】数据库审计

一、什么是数据库审计 数据库审计&#xff08;简称DBAudit&#xff09;是一种以安全事件为中心&#xff0c;实时记录网络上的数据库活动&#xff0c;并对数据库操作进行细粒度审计的合规性管理技术。它通过对用户访问行为的记录、分析和汇报&#xff0c;帮助用户事后生成合规报…

记录一次微信小程序申诉定位权限过程

1 小程序接到通知&#xff0c;检测到违规&#xff0c;需要及时处理&#xff0c;给一周的缓冲时间&#xff0c;如果到期未处理&#xff0c;会封禁能力&#xff08;2023-11-17&#xff09; 2 到期后&#xff0c;仍未处理&#xff0c;封禁能力&#xff08;2023-11-24&#xff09; …

江波龙 128G msata量产

一小主机不断重启&#xff0c;用DG格式化 无法完成&#xff0c;应该是有坏块了 找一个usb转msata转换板 查了一下是2246en aa主控 颗粒应该是三星的 缓存是现代的 找到量产工具sm22XMPToolP0219B 打开量产工具 用镊子先短接一下jp1 插入usb口&#xff0c;再拿走镊子 scan …

宏任务与微任务对比【前端异步】

目录 简介微任务与宏任务的基本概念宏任务&#xff08;Macrotasks&#xff09;微任务&#xff08;Microtasks&#xff09;宏任务示例微任务示例微任务与宏任务的执行时序 结论 简介 在JavaScript的异步编程中&#xff0c;理解事件循环&#xff08;Event Loop&#xff09;是至关…

el-from中校验,如果某一项需要另一项填写才能校验

使用validateField <el-form:model"params":rules"rules":scroll-to-error"true"ref"refrom"v-else><el-form-item label"用户姓名" prop"name"><el-input placeholder"请输入用户姓名"…

【IT领域新生必看】编程中的错误处理大师:解密 `throw` 和 `throws` 的神秘差异

文章目录 引言异常处理的基础知识什么是异常&#xff1f;异常分类 什么是 throw&#xff1f;throw 的使用示例throw 的特性 什么是 throws&#xff1f;throws 的使用示例throws 的特性 throw 和 throws 的区别结合使用 throw 和 throws异常处理的最佳实践结论 引言 在编程的世…

一套基于 Ant Design 和 Blazor 的开源企业级组件库

前言 今天大姚给大家分享一套基于Ant Design和Blazor的开源&#xff08;MIT License&#xff09;、免费的企业级组件库&#xff08;喜欢Ant Design风格的同学推荐使用&#xff09;&#xff1a;Ant Design Blazor。 项目特性 提炼自企业级中后台产品的交互语言和视觉风格。 开…