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

语言模型建模是针对句子建模,主要分为掩码语言模型和自回归语言模型。
我们从corpus中截取一句话作为例子,这句话是bpe分词后的句子:

1994 年 5 月 17 日 安全 理事会 第 33@@ 77 次 会议 通过

掩码语言模型的主要机制是:例如将33@@ 77这两个分词做掩码,这样句子变成了

1994 年 5 月 17 日 安全 理事会 第 [MASK] [MASK] 会议 通过

将带有掩码的句子输入神经网络,让模型预测掩码位置的正确分词,例如上文正确的分词为 33@@ 、77.在分词的词表中,训练的目的是为了使得预测这句话的mask时,33@@、77分词的概率最高,从而选取这两个分词作为正确的预测结果。

自回归语言模型的机制是:给出这句话的前i-1个词,来预测第i个词;随后加入第i个词,预测这句话的第i+1个词。例如:

1994 -> 年
1994 年 -> 5
1994 年 5 -> 月

1994 年 5 月 17 日 安全 理事会 第 33@@ 77 次 会议 -> 通过

在自回归语言模型主要有两个问题:
1.句首的词怎么来,比如本句话的1994.
2.句末的分词“通过”生成之后,是继续生成还是结束?
因此,对句子的处理我们要加两个特殊标记:句首加[SOS](start of sentence),句末加[EOS](end of sentence).例如:

[SOS] 1994 年 5 月 17 日 安全 理事会 第 33@@ 77 次 会议 -> 通过 [EOS]

[SOS]作触发器,当遇到时开始了第一个词的生成以及整个句子的生成;[EOS]是结束的作用,当分类产生的概率[EOS]最大的时候,就认为不需要继续生成了。

模型的搭建

首先我们对分词后的语料按句长升序排序,设置最长句长为254,这样加上 eos 和 sos 后最大句长为256。

#sorti.py
#encoding: utf-8
import sysdef handle(srcf, srts, max_len=256): #设置句长最大256,大于则丢弃# {length: {src}}  外层dict,内层set_maxlen = max_len - 2   #减去[SOS]和[EOS]data = {}with open(srcf, "rb") as fsrc:for ls in fsrc:ls = ls.strip()if ls:ls = ls.decode("utf-8")_ = len(ls.split()) #获取句子的分词个数if _ <= max_len:if _ in data:   #若已有这个长度在data中if ls not in data[_]: #去重,重复的跳过data[_].add(ls)   #不重复的添加else:data[_] = set([ls])   #转化成set去重ens = "\n".encode("utf-8")with open(srts, "wb") as fsrc: #写入for _l in sorted(data.keys()):  #按照句子长度从小到大排lset = data[_l] #取出句长对应的setfsrc.write("\n".join(lset).encode("utf-8")) #在每个句子间插入换行符fsrc.write(ens) #每个句子后插入换行if __name__ == "__main__":handle(*sys.argv[1:3])

在命令行执行并查看结果文件:

:~/nlp/lm$ python sorti.py ../token/zh.bpe zh.srt
:~/nlp/lm$ less zh.srt

在这里插入图片描述
由于自回归语言模型的机制,我们通过已有的分词预测句子中下一个分词,则不断将预测的分词加入句子来预测下一个分词,那么输入就是变长的。为了解决这个问题,我们使用卷积操作,设置每次固定的上下文长度ctx_len,来预测下一个分词。
假设ctx_len=4,如果我们有序列0 1 2 3 4 5 6 7 8,则有:
0 1 2 3 -> 4
1 2 3 4 -> 5
2 3 4 5 -> 6
3 4 5 6 -> 7
4 5 6 7 -> 8
因此,若每个batch中句子的长度为seql,则我们每次需要取seql - ctx_len个长度的分词,共取ctx_len次。

>>> import torch        
>>> a = torch.arange(9,dtype=torch.long)
>>> ctx_len = 4
>>> nlen = 9 - ctx_len
>>> _ = [a.narrow(0,i,nlen) for i  in range(ctx_len)]
>>> _
[tensor([0, 1, 2, 3, 4]), tensor([1, 2, 3, 4, 5]), tensor([2, 3, 4, 5, 6]), tensor([3, 4, 5, 6, 7])]
>>> b = torch.stack(_,dim=-1)
>>> b
tensor([[0, 1, 2, 3],[1, 2, 3, 4],[2, 3, 4, 5],[3, 4, 5, 6],[4, 5, 6, 7]])
#CNNLM.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, ctx_len=4, bindemb=True, **kwargs): #有多少个词,就分多少类,类别数为vcb_sizesuper(NNLM, self).__init__()self.emb = nn.Embedding(vcb_size, isize,padding_idx=0)                #<pad>的索引为0self.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)self.ctx_len = ctx_len      #保存上下文长度# input: (bsize, seql-1) 句数,句长-1 由于最后一个词是预测不作为输入def forward(self, input):out = self.emb(input)# out: (bsize, seql-1, isize)_nlen = input.size(1) - self.ctx_len + 1# 每次取_nlen长度的分词数_ = []for i in range(self.ctx_len):_.append(out.narrow(1, i, _nlen)) #从seql这一维取,每次从i开始取,取_nlen长度out = torch.cat(_,dim=-1)    #[(bsize, _nlen, isize)] -> (bsize, _nlen, ctx_len * isize)  out = self.normer(self.drop(out))out = self.comp(out)  #降维#(bsize, _nlen, ctx_len * isize) -> (bsize, _nlen, isize)   out = self.normer(out) #使用归一化,使模长均匀 out = self.out_normer(self.nets(out))return self.classifier(out) #分类产生参数

分词词典映射

#vcb.py
#encoding: utf-8
import sys#预定义规则
init_vcb ={"<pad>": 0, "<sos>": 1, "<eos>": 2} #将占位符、开始标志和结束标志的ID映射为0、1、2
init_token_id = len(init_vcb)      #设置其他词的ID从3开始编号def get_vcb(*files): #词频统计#{word: freq}vcb = {}for file in files:with open(file, "rb") as f:for line in f:line = line.strip()if line:for word in line.decode("utf-8").split():vcb[word] = vcb.get(word,0) + 1return vcb# freq word0 word1 word2 将同一频率的词放在一起
def save_vcb(vcb, file):# {freq: [word]}rs = {}for word, freq in vcb.items():if freq in rs: #若此词频项已存在,则添加该词到listrs[freq].append(word)else:          #若此项不存在,则将该词作为list第一个词rs[freq] = [word]with open(file, "wb") as f:for freq in sorted(rs.keys(), reverse=True):#从大到小读rs的频率,reverse=True说明按照从大到小排序f.write(("%d\t%s\n"%(freq,"\t".join(rs[freq]))).#将同一词频的词合成一个字符串,词间用制表符分开encode("utf-8"))def load_vcb(file, vanilla = False, init_vcb = init_vcb, init_token_id = init_token_id):#为每个分词建立索引#vanilla变量是为了区分是否使用了<pad>,若使用则编号从1开始否则从0开始vcb, cid = ({}, 0) if vanilla else (init_vcb.copy(), init_token_id)with open(file, "rb") as f:for line in f:line = line.strip()if line:for word in line.decode("utf-8").split()[1:]: #舍弃词频,只需要保留分词vcb[word] = cidcid += 1#为每个分词建立索引return vcbreverse_vcb = lambda vcb: {v: k for k, v in vcb.items()}
#使用匿名函数将 词->映射 转换为 映射->词if __name__ == "__main__":save_vcb(get_vcb(*sys.argv[1:-1]), sys.argv[-1])#最后一个参数为存词频————分词的文件

我们对已经排好序的句子进行词频的收集,在命令行执行:

:~/nlp/lm$ python vcb.py zh.srt zh.vcb
:~/nlp/lm$ less zh.vcb

在这里插入图片描述
我们可以看到收集好的词典按照词频从高到低保存了所有分词。

文本转张量

#mkh5.py
#encoding: utf-8
import sys
from h5py import File as h5File
import numpy
from vcb import load_vcb
#导入加载词典的函数def batch_loader(fsrc, max_tokens = 4096, **kwargs):#返回一批一batch的数据,设置每个batch最多存放2048个子词 ri = []mlen = n = 0    #n记录当前收集了多少条句子,mlen记录当前收集的句子长度with open(fsrc, "rb") as fs:for ls in fs:ls = ls.strip() if ls:ls =ls.decode("utf-8").split()_l = len(ls)            #当前行中的分词个数_mlen = max(_l, mlen)   #当前行或当前batch中句子的长度_n = n + 1if (_n * _mlen) > max_tokens: #如果把添加了这句话的 句数*分词数量 大于最大值则不能放if ri:       #如果ri不为空yield ri, mlen  #返回ri和原来的句子长度ri, mlen, n = [ls], _l, 1 #返回后重新初始化,将本句加入新的batchelse: #如果不超过当前长度,则将此句添加到batch中ri.append(ls)mlen, n = _mlen, _n #更新句子长度与句子数量if ri: #最后若仍然有数据,则返回为一个新的batchyield ri, mlendef map_instance(lin, vcb, sos_id=1, eos_id=2):rs = [sos_id]  #添加开始标志rs.extend([vcb[_word] for _word in lin if _word  in vcb])rs.append(eos_id) #添加结束标志return rsdef batch_mapper(fsrc, vcbs, **kwargs): #将分词变索引for ri, mlen in batch_loader(fsrc, **kwargs):yield [map_instance(_s, vcbs) for _s in ri], mlen + 2#遍历每个batch中的句子,返回每个batch中每个分词的索引,以及batch长度def pad_batch(lin, mlen, pad_id = 0):#补<pad>的函数rs = []for lu in lin: #每个batch中的每句_d = mlen - len(lu) #当前此句需要补<pad>的个数if _d > 0:lu.extend([pad_id for _ in range(_d)])#extend函数用来拼接两个列表。补_d个<pad>的索引0rs.append(lu)return rs #返回的是均已对齐的每个batchdef batch_padder(fsrc, vcbs, **kwargs):for ri, mlen in batch_mapper(fsrc, vcbs, **kwargs):yield pad_batch(ri, mlen) #返回的是每个已补齐的batch,以及batch中句子的长度def handle(fsrc, fvcbs, frs, **kwargs):vcbs = load_vcb(fvcbs, vanilla = False)with h5File(frs, "w", libver = 'latest', track_order = False) as h5f:#libver使用最新的,track_order表示无需记录顺序src_grp = h5f.create_group("src", track_order=False)    #创建两个组,分别放句子和标签 for i, ri in enumerate(batch_padder(fsrc, vcbs, **kwargs)):ri = numpy.array(ri, dtype = numpy.int32) #转化成numpy数组并设置数据类型,target的数据很小,所以我们只需要int16存储src_grp.create_dataset(str(i), data=ri, compression="gzip",compression_opts=9, shuffle=True ) #设置压缩存储节省空间,压缩等级设置为最大压缩代价9h5f["nword"] = numpy.array([len(vcbs)], dtype=numpy.int32) #存储总词数h5f["ndata"] = numpy.array([i + 1], dtype=numpy.int32) #存储总batch数if __name__ == "__main__":handle(*sys.argv[1:4])

在命令行执行:

:~/nlp/lm$ python mkh5.py zh.srt zh.vcb train.h5
:~/nlp/lm$ h5ls -d train.h5 
ndata                    Dataset {1}Data:83685
nword                    Dataset {1}Data:42585
src                      Group

可以看到我们总的训练集张量共有42585个分词以及83685个batch。

模型的训练

#train.py
#encoding: utf-8import torch
from torch import nn
from CNNLM 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           #设置训练轮数
#early_stop = 16         #设置早停轮数
ctx_len = 4             #设置上下文长度
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_()return modindef train(train_data, tl, model, lossf, optm, cuda_device,nreport=nreport, tokens_optm=tokens_optm, ctx_len=ctx_len):#nreport为每训练一部分词打一次epochmodel.train() #设置模型在训练的模式src_grp = train_data["src"] #从输入数据中取出句子_l = 0.0  #_l用来存当前loss_t = 0    #_t用来存句子数_lb = 0.0_tb = 0_tom = 0_min_len_thres = ctx_len + 1for _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 _seqlen < _min_len_thres:  #至少有ctx_len个词才能运行,否则跳过continueif 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) #训练数据读取bsize个seql-1的数据#seq_i:[bsize, seql-1]seq_o = seq_batch.narrow(1, ctx_len, _seqlen - ctx_len) #预测数据读取ctx_len~seql-1的预测数据#seq_o:[bsize, seql-ctx_len]#out: {bsize, seql-ctx_len, vcb_size} vcb_size即预测类别数out = model(seq_i)          #获得模型结果loss = lossf(out.view(-1, out.size(-1)), seq_o.contiguous().view(-1)) #转换out维度为[bsize*(seql-ctx_len),vcb_size],seq_o:[bsize*(seql-ctx_len)]_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, ctx_len=ctx_len)
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())] #获得字符串构成的训练数据的listfor 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()

在训练脚本中,我们要注意训练集很大,所以设置每nreport 个batch打印一次结果。更新参数时也设置一个阈值来多轮更新一次而不是每个batch更新一次。
由于整体训练模型开销大,因此我们每nreport个batch暂存一次检查点模型方便解码和后续工作的推进。
我们训练的过程就是将句子的前seql-1个词输入模型(seql为句子长度,即句子分词个数),将训练集句子第ctx_len以后的词作为标签(模型从前ctx_len个词预测第ctx_len+1的词,再添加再预测以此类推),直到下标为 seql-ctx_len-2 到 seql-2 预测下标为 seql-1 的词。至此句长为seql的0~seql-1的词都过了训练。计算预测分词和标签的交叉熵损失。

模型的预测

预测数据准备

我们选取两句话让模型预测之后的句子:

审议 了 1994 年 5 月 13 日 秘书长 的 报告 ( S / 1994 / 565 ) ,
重申 其 1993 年 9 月 29 日 关于 联合国 行动 安全 的 第 868 ( 1993 ) 号 决议 ,

我们做测试用的句子需满足句长(即分词个数)大于5,即大于ctx_len+1.
我们将其存入文件中,然后保存为h5文件:

:~/nlp/lm$ less test.bpe 
审议 了 1994513 日 秘书长 的 报告 ( S / 1994 / 565 ) ,
重申 其 1993929 日 关于 联合国 行动 安全 的 第 868 ( 1993 ) 号 决议 ,

由于这两句是长度升序排列,所以省去排序的步骤。

:~/nlp/lm$ python mkh5.py test.bpe zh.vcb test.h5 
:~/nlp/lm$ h5ls -d test.h5/src
0                        Dataset {2, 23}Data:1, 159, 14, 1246, 12, 112, 20, 352, 35, 125, 3, 34, 11, 532, 13, 1246, 13, 12210, 10, 4, 2, 0,0, 1, 489, 39, 1459, 12, 165, 20, 600, 35, 41, 33, 78, 79, 3, 16, 20380, 11, 1459, 10, 63, 67,4, 2

我们可以看到第一句话的张量是:[ 1, 159, 14, 1246, 12, 112, 20, 352, 35, 125, 3, 34, 11, 532, 13, 1246, 13, 12210, 10, 4, 2, 0, 0,]
第二句话的张量是:[1, 489, 39, 1459, 12, 165, 20, 600, 35, 41, 33, 78, 79, 3, 16, 20380, 11, 1459, 10, 63, 67, 4, 2]

他们都满足:开头是<sos>,索引为1;结尾是<eos>,索引为2。第一句话由于要和第二句长度对齐构成一个batch,在末尾补了两个<pad>,索引为0.
而我们预测续写句子时,在测试集我们首先不需要结尾标记2以及为补齐句子长度而在末尾添加的0.于是我们将pad占位符放在句子的开头,对mkh5.py脚本做以下修改:

#mkh5test.py
def pad_batch(lin, mlen, pad_id = 0):#补<pad>的函数rs = []for lu in lin: #每个batch中的每句_d = mlen - len(lu) #当前此句需要补<pad>的个数if _d > 0:_ = [pad_id for _ in range(_d)]#lu.extend([pad_id for _ in range(_d)])#extend函数用来拼接两个列表。补_d个<pad>_.extend(lu) #将原句放在<pad>后else:_ = lurs.append(_)return rs #返回的是均已对齐的每个batch
def map_instance(lin, vcb, sos_id=1, eos_id=2):rs = [sos_id]rs.extend([vcb[_word] for _word in lin if _word  in vcb])# rs.append(eos_id) 测试时句末无需加eosreturn rs

再次保存h5文件后:

:~/nlp/lm$ python mktesth5.py test.bpe zh.vcb test.h5 
:~/nlp/lm$ h5ls -d test.h5/src
0                        Dataset {2, 22}Data:0, 0, 1, 159, 14, 1246, 12, 112, 20, 352, 35, 125, 3, 34, 11, 532, 13, 1246, 13, 12210, 10, 4,1, 489, 39, 1459, 12, 165, 20, 600, 35, 41, 33, 78, 79, 3, 16, 20380, 11, 1459, 10, 63, 67, 4

第一句话张量:[ 0, 0, 1, 159, 14, 1246, 12, 112, 20, 352, 35, 125, 3, 34, 11, 532, 13, 1246, 13, 12210, 10, 4,]
第二句话张量:[ 1, 489, 39, 1459, 12, 165, 20, 600, 35, 41, 33, 78, 79, 3, 16, 20380, 11, 1459, 10, 63, 67, 4]

解码与预测

模型的解码

    #CNNLM.py# input: (bsize, seql)def decode(self, input, maxlen=50):  #设置最多生成50个分词rs = inputbsize =input.size(0)done_trans = None   #记录是否完成生成for i in range(maxlen):out = rs.narrow(1, rs.size(1) - self.ctx_len, self.ctx_len) #利用后四个词预测下一个词# (bsize, ctx_len, isize)out = self.emb(out).view(bsize, -1) # (bsize, ctx_len*isize)out = self.normer(self.comp(self.drop(out)))out = self.out_normer(self.nets(out))out = self.classifier(out).argmax(-1,keepdim=True) #取最后一维分数最高的索引#这一步对应分类,keepdim=True保持维度便于拼接# out:(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

首先,我们需要在模型文件的NNLM类中添加decode方法用于解码,decode方法将输入句子的最后ctx_len个分词作为模型的输入,预测下一个分词后将其拼接到句子中,下次再取后三个以及新生成的分词作为输入预测;
重复此步骤直至生成超过50个分词或者这一个batch中生成全部为<eos>结束标志,则停止。
self.classifier(out).argmax(-1,keepdim=True) 这句话在执行argmax函数前,classifier分类器分类后的size是(bsize, vcb_size),所以执行argmax函数在最后一维取的下标索引即为分词在词表中的下标。

模型的预测

#predict.py
#encoding: utf-8import sys
import torch
from CNNLM 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
ctx_len = 4             #设置上下文长度def 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, ctx_len=ctx_len)
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的参数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)       #判断是否使用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 predict.py test.h5 zh.vcb pred.txt checkpoint.pt 
:~/nlp/lm$ less pred.txt

在这里插入图片描述
可以看到模型对测试句子做了续写。

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

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

相关文章

PostgREST API 安装及基础使用

PostgREST是一个独立的Web服务器&#xff0c;它将PostgreSQL数据库转换为RESTful API。它提供基于基础数据库的结构自定义的API。 PostgREST安装 首先访问Releases PostgREST/postgrest (github.com)&#xff0c;根据安装平台选择下载的源码。比如我现在的设备是Mac但是我的…

删掉此电脑中烦人的百度网盘、WPS、迅雷下载图标(还有导航栏)-win10/11

删掉此电脑的图标 1、首先 搜索 注册表管理器 或者 win R 输入 regedit 2、在红框的地方输入这串路径&#xff08;下2选1&#xff09;&#xff1a; 计算机\HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer\MyComputer\NameSpace\ HKEY_CURRENT_US…

【C++】使用C++实现基于Socket的通信

在本文中&#xff0c;我们将详细讨论如何使用C实现基于Socket的通信&#xff0c;并设计一个TLV&#xff08;Type-Length-Value&#xff09;协议用于数据交互。TLV协议因其灵活性和可扩展性&#xff0c;在多种通信协议中被广泛使用&#xff0c;特别是在需要动态定义数据结构的场…

【Axure】产品原型如何在谷歌浏览器中打开

作为一名前端开发来说&#xff0c;在拿到产品的原型图后&#xff0c;如何打开&#xff1f;直接用谷歌浏览器打开&#xff0c;是打不开的&#xff0c;需要安装对应的插件。但是谷歌插件市场在不翻墙的情况下&#xff0c;是没有办法直接打开的&#xff0c;分享一种超级简单的方法…

深入解析RocketMQ的名字服务机制:功能、原理与应用

NameServer 是专为 RocketMQ 设计的轻量级名字服务&#xff0c;它的源码非常精简&#xff0c;八个类 &#xff0c;少于 1000 行代码。 这篇文章&#xff0c; 笔者会从基础概念、Broker 发送心跳包、NameServer 维护路由、Zookeeper vs NameServer 四个模块揭秘名字服务的设计精…

电脑怎样连接打印机?详细步骤告诉你!

在现代办公环境中&#xff0c;打印机是必不可少的设备之一。无论是打印文档、图片还是其他资料&#xff0c;连接打印机都是我们日常工作中的基本操作。然而&#xff0c;不同类型的打印机和连接方式可能会让人感到困惑。本文将介绍电脑怎样连接打印机的3种方法&#xff0c;帮助您…

简易限流实现

需求描述 写一个1秒两个的限流工具类&#xff0c;2r/s 使用semaphore 代码实现-类似令牌桶算法 public class LimitHelper {private int maxLimit;private Semaphore semaphore;private int timeoutSeconds;public LimitHelper(int maxLimit, int timeoutSeconds) {this.max…

LNMP搭建Discuz和Wordpress

1、LNMP L:linux操作系统 N&#xff1a;nginx展示前端页面web服务 M&#xff1a;mysql数据库&#xff0c;保存用户和密码&#xff0c;以及论坛相关的内容 P&#xff1a;php动态请求转发的中间件 数据库的作用&#xff1a; 登录时验证用户名和密码 创建用户和密码 发布和…

昇思25天学习打卡营第十七天|文本解码原理--以MindNLP为例

背景 提供免费算力支持&#xff0c;有交流群有值班教师答疑的华为昇思训练营进入第十七天了。 今天是第十七天&#xff0c;从第十天开始&#xff0c;进入了应用实战阶段&#xff0c;前九天都是基础入门阶段&#xff0c;具体的学习内容可以看链接 基础学习部分 昇思25天学习打卡…

Unity发布webgl之后修改StreamingAssets 内的配置文件读取到的还是之前的配置文件的解决方案

问题描述 unity发布webgl之后&#xff0c;修改在StreamingAssets 中的配置信息&#xff0c;修改之后读取的还是之前的配置信息 读取配置文件的代码IEnumerator IE_WebGL_LoadWebSocketServerCopnfig(){var uri new System.Uri(Path.Combine(Application.streamingAssetsPath…

居家客服人员分散,更需要统一客服话术

1、居家客服服务需求激增 近年来&#xff0c;随着线上消费的兴起&#xff0c;以及客服人员成本的不断攀升&#xff0c;越来越多的企业选择雇佣居家客服&#xff0c;以客服服务发包的形式接待客户的咨询。因此&#xff0c;居家客服人员的数量也逐渐增加。然而&#xff0c;居家办…

c++ 多边形 xyz 数据 获取 中心点方法

有需求需要对。多边形 获取中心点方法&#xff0c;绝大多数都是 puthon和java版本。立体几何学中的知识。 封装函数 point ##########::getCenterOfGravity(std::vector<point> polygon) {if (polygon.size() < 2)return point();auto Area [](point p0, point p1, p…

PHP充电桩小程序系统源码

绿色出行新伴侣&#xff01;充电桩小程序&#xff0c;让充电不再烦恼✨ &#x1f50b; 开篇&#xff1a;告别电量焦虑&#xff0c;充电桩小程序来救场&#xff01; 在这个电动车日益普及的时代&#xff0c;电量不足成了不少车主的“心头大患”。但别担心&#xff0c;充电桩小…

nprogress进度条插件

打开一个页面时&#xff0c;往往会伴随一些请求&#xff0c;并且会在页面上方出现进度条。它的原理时&#xff0c;在我们发起请求的时候开启进度条&#xff0c;在请求成功后关闭进度条&#xff0c;所以只需要在request.js中进行配置。 如下图所示&#xff0c;我们页面加载时发起…

【R语言+Gephi】利用R语言和Gephi实现共发生网络的可视化

【R语言Gephi】利用R语言和Gephi实现共发生网络的可视化 注&#xff1a;本文仅作为自己的学习记录以备以后复习查阅 一 概述 Gephi是一款开源免费的多平台网络分析软件&#xff0c;在Windows、Linux和Mac os上均可以运行&#xff0c;像他们官网所说的&#xff0c;他们致力于…

【深度学习】Pytorch完成线性回归!

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

提升困难生学工支持:智慧校园的新功能介绍

智慧校园的学工管理系统内嵌的困难生信息管理功能&#xff0c;是一个综合性的服务平台&#xff0c;专注于精准识别校园内的经济困难学生&#xff0c;并给予他们必要的帮助与关怀&#xff0c;确保每位学生都能在公平的环境中追求学业和个人成长。这一功能通过一系列信息化手段&a…

标准立项 | 深度脱氨生物填料选型指南

编制单位&#xff1a;北京科净源科技股份有限公司、中国地质大学(北京)、中华环保联合会水环境治理专业委员会、清华大学、北京师范大学、中国环境科学研究院、清华大学、天津高端装备研究院、中车环境科技有限公司、云南滇池水务股份有限公司等。

职升网:考取专科学历的途径包括以下这些!

高考统招&#xff1a; 每年6月举行的全国统一高考&#xff0c;是获得专科学历的传统途径。 考生需参加由教育部组织的统一考试&#xff0c;按照分数由高到低依次录取。 适合高中毕业生或具有同等学历的学生。 自学考试&#xff1a; 又称自考&#xff0c;是一种没有入学考试…

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* _…