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

在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

绝对值不等式运用(C++)

货仓选址 用数学公式表达题意&#xff0c;假设有位置a1~an,假设选址在x位置处&#xff0c;则有&#xff1a; 如何让这个最小&#xff0c;我们把两个式子整合一下&#xff0c;利用绝对值不等式&#xff1a; 我们知道&#xff1a; 如下图所示&#xff1a;到A&#xff0c;B两点&…

【深度学习入门篇 ②】Pytorch完成线性回归!

&#x1f34a;嗨&#xff0c;大家好&#xff0c;我是小森( &#xfe61;ˆoˆ&#xfe61; )&#xff01; 易编橙终身成长社群创始团队嘉宾&#xff0c;橙似锦计划领衔成员、阿里云专家博主、腾讯云内容共创官、CSDN人工智能领域优质创作者 。 易编橙&#xff1a;一个帮助编程小…

简单状压dp(以力扣464为例)

目录 1.状态压缩dp是啥&#xff1f; 2.题目分析 3.解题思路 4.算法分析 5.代码分析 6.代码一览 7.结语 1.状态压缩dp是啥&#xff1f; 顾名思义&#xff0c;状态压缩dp就是将原本会超出内存限制的存储改用更加有效的存储方式。简而言之&#xff0c;就是压缩dp的空间。 …

昇思MindSpore学习笔记6-05计算机视觉--SSD目标检测

摘要&#xff1a; 记录MindSpore AI框架使用SSD目标检测算法对图像内容识别的过程、步骤和方法。包括环境准备、下载数据集、数据采样、数据集加载和预处理、构建模型、损失函数、模型训练、模型评估等。 一、概念 1.模型简介 SSD目标检测算法 Single Shot MultiBox Detecto…

WebRTC群发消息API接口选型指南!怎么用?

WebRTC群发消息API接口安全性如何&#xff1f;API接口怎么优化&#xff1f; WebRTC技术在现代实时通信中占据了重要地位。对于需要实现群发消息功能的应用程序来说&#xff0c;选择合适的WebRTC群发消息API接口是至关重要的。AokSend将详细介绍WebRTC群发消息API接口的选型指南…

大数据开发者如何快速熟悉新公司业务

作为一名大数据开发工程师,进入一家新公司后快速熟悉业务是至关重要的。 目录 1. 了解产品形态故事1:电商平台的数据分析故事2:金融科技的风控系统故事3:社交媒体的推荐算法 2. 了解业务流程故事1:物流配送系统的优化故事2:医疗保险的理赔流程故事3:银行的贷款审批流程 3. 走…

openfoam生成的非均匀固体Solid数据分析、VTK数据格式分析、以及paraview官方用户指导文档和使用方法

一、openfoam生成的非均匀固体Solid数据分析 对于Solid/dealii-output文件&#xff0c;固体的数据文件&#xff0c; # vtk DataFile Version 3.0 #This file was generated by the deal.II library on 2024/7/10 at 9:46:15 ASCII DATASET UNSTRUCTURED_GRIDPOINTS 108000 do…

【算法】【二分法】二分法详解

先给y总打一个广告。&#xff08;我这种废物收不到钱&#xff09; 本科时候就在打蓝桥杯玩玩算法&#xff0c;当时听朋友的一个刷题且涵盖教程的网站&#xff0c;ACWING。 www.acwing.com 里面好处是大部分基础算法都有&#xff0c;Y总的视频&#xff01; y总我的神&#xff01…

IDEA之Debug的使用

自定义功能图表 功能说明 光标回到Debug行 执行到光标所在行 Force Step into Trace Current Stream Chain Reset Frame 重置方法入栈

C++基础学习笔记

1.命名空间(namespace) 1.什么是命名空间&命名空间的作用 1.在C/C中&#xff0c;变量、函数、类都是大量存在的&#xff0c;这些变量等的名称将都存在于全局作用域中&#xff0c;就会导致很多的命名冲突等。使用命名空间的目的就是对标识符的名称进行本地化&#xff0c;以…

Nginx七层(应用层)反向代理:UWSGI代理uwsgi_pass篇

Nginx七层&#xff08;应用层&#xff09;反向代理 UWSGI代理uwsgi_pass篇 - 文章信息 - Author: 李俊才 (jcLee95) Visit me at CSDN: https://jclee95.blog.csdn.netMy WebSite&#xff1a;http://thispage.tech/Email: 291148484163.com. Shenzhen ChinaAddress of this a…

JavaSE学习笔记第二弹——对象和多态(下)

今天我们继续复习与JavaSE相关的知识&#xff0c;使用的编译器仍然是IDEA2022&#xff0c;大家伙使用eclipse或其他编译环境是一样的&#xff0c;都可以。 目录 数组 定义 一维数组 ​编辑 二维数组 多维数组 数组的遍历 for循环遍历 ​编辑 foreach遍历 封装、继承和…

网络编程的学习之udp

Udp编程过程 Sento不会阻塞 实现聊天室效果 上线 聊天 下线 服务端需要一个地址&#xff0c;去保留名字和ip地址 交互的时候发结构体 下面这个宏只能在c语言里使用 ser.sin_port htons(50000); 上面是端口号50000以上&#xff0c;两边要一样 这里是不要让udp发的太快&am…

Git命令常规操作

目录 常用操作示意图 文件的状态变化周期 1. 创建文件 2. 修改原有文件 3. 删除原有文件 没有添加到暂存区的数据直接 rm 删除即可&#xff1a; 对于添加到暂存区的数据 文件或目录&#xff1a; 4. 重命名暂存区数据 5. 查看历史记录 6. 还原历史数据 恢复过程的原…

C/C++ list模拟

模拟准备 避免和库冲突&#xff0c;自己定义一个命名空间 namespace yx {template<class T>struct ListNode{ListNode<T>* _next;ListNode<T>* _prev;T _data;};template<class T>class list{typedef ListNode<T> Node;public:private:Node* _…

低代码平台赋能企业全面数字化转型

引言&#xff1a;在当今这个日新月异的数字化时代&#xff0c;企业正面临着前所未有的机遇与挑战。为了保持竞争力并实现可持续发展&#xff0c;企业亟需进行全面的数字化转型。而低代码平台作为数字化转型的重要工具&#xff0c;正以其独特的优势赋能企业&#xff0c;推动其向…

Ollama完整教程:本地LLM管理、WebUI对话、Python/Java客户端API应用

老牛同学在前面有关大模型应用的文章中&#xff0c;多次使用了Ollama来管理和部署本地大模型&#xff08;包括&#xff1a;Qwen2、Llama3、Phi3、Gemma2等&#xff09;&#xff0c;但对Ollama这个非常方便管理本地大模型的软件的介绍却很少。 目前&#xff0c;清华和智谱 AI 联…

[计网初识1] TCP/UDP

学习内容 1.TCP建立链接的3次握手&#xff0c;断开连接的4次挥手 2.TCP报文段组成 内容 1.TCP 建立连接的3次握手? 假设主动方是客户端&#xff0c;被动方是服务端。 第一次 客户端给服务端发送 “hello,我是客户端” (TCP段中 SYN1) 第二次 服务端给客户端发送"我接…

汽车免拆诊断案例 | 2016款保时捷Macan车发动机故障灯异常点亮

故障现象  一辆2016款保时捷Macan车&#xff0c;搭载CYP发动机&#xff0c;累计行驶里程约为11.2万km。车主进厂反映&#xff0c;发动机故障灯异常点亮。 故障诊断  接车后试车&#xff0c;发动机怠速无明显异常&#xff0c;组合仪表上的发动机故障灯异常点亮。用故障检测仪…

AI in Finance 金融领域AI应用-基于DeepNLP AI App Store 真实用户评论打分和排名

AI在金融领域应用 AI in Finance 金融服务领域的AI应用和传统的金融智能应用不同。传统金融智能应用包括如风险评估 (Risk assessment), 风险管理&#xff08;Risk management), 欺诈检测 (Fraud Detection&#xff09;等等。 通用AI大模型和人工智能应用如ChatGPT&#xff0c…