模型的搭建
线性层
>>> import torch
>>> from torch import nn
>>> class DBG(nn.Module):
... def forward(self,x):
... print(x.size())
... return x
...
>>> tmod = nn.Sequential(nn.Linear(3,4),DBG(),nn.Linear(4,5),DBG())
>>> tmod(torch.randn(2,3))
torch.Size([2, 4])
torch.Size([2, 5])
tensor([[-0.0408, 0.3847, 0.0409, -0.6591, -0.0459],
[-0.0791, 0.2998, 0.3464, -0.7436, -0.2738]],
grad_fn=<AddmmBackward0>)
通过以上代码,输入一个2x3的矩阵,在第一个线性层会变化为一个2x4的矩阵,随后使用DBG类输出当前矩阵的形状。在第二次线性层的变换后,矩阵变成2x5。
设置丢弃率
>>> import torch
>>> from torch import nn
>>> h = torch.randn(5)
>>> td = nn.Dropout(0.2)
>>> td(h)
tensor([-0.7068, 0.8269, -1.1000, -0.2249, 0.0000])
>>> h
tensor([-0.5654, 0.6615, -0.8800, -0.1799, 0.7755])
我们设置丢弃率为0.2,结果打印丢弃后的张量,发现张量的第五个值被舍弃为0,但其他的值均发生了变化。
原因是丢弃值置0后向量的模长会发生很大变化,在我们线性层输入计算特征值时,模长的变化会造成结果的变化,在测试时我们是用原始不丢弃值的张量测试的,为了平衡丢弃前后张量的模长相近,Dropout
函数对丢弃后的张量的其余值做了缩放。
>>> h/0.8
tensor([-0.7068, 0.8269, -1.1000, -0.2249, 0.9693])
>>> td(h)
tensor([-0.7068, 0.8269, -1.1000, -0.2249, 0.0000])
我们丢弃率是0.2,Dropout
函数就用其余值/0.8后保留。因此Dropout函数做了两件事:1.按照给定概率p丢掉值。2.用剩下其余值/(1-p),从而保持前后模长一致。
>>> td.eval()
Dropout(p=0.2, inplace=False)
>>> td.training #eval()函数使此值为False,不丢弃
False
>>> td(h)
tensor([-0.5654, 0.6615, -0.8800, -0.1799, 0.7755])
>>> td.train()
Dropout(p=0.2, inplace=False)
>>> td.training #train()函数使此值为True,丢弃
True
>>> td(h)
tensor([-0.7068, 0.0000, -1.1000, -0.2249, 0.0000])
以上是Dropout
函数的两种模式。
归一化层
在神经网络训练中,可能产生张量的某些参数值过大或过小,这样的极端值不利于产生合适的预测值。在优化过程中,如果我们设置学习率很小,则很小的参数值就变化很慢;如果学习率过大,则大的参数值变化过慢。
因此我们需要用nn.LayerNorm
来进行归一化处理:
归一化的公式如上,E(x)是向量的均值。
x
−
E
[
x
]
x-E[x]
x−E[x]的作用是将向量移到0的附近:
>>> a = torch.randn(5)*100
>>> a
tensor([ -61.7625, 16.2004, -117.4248, 84.3843, -54.4913])
>>> m = a.mean(-1,keepdim=True)
>>> m
tensor([-26.6188])
>>> a-m
tensor([-35.1437, 42.8192, -90.8060, 111.0031, -27.8725])
>>> (a-m).mean(-1)
tensor(0.)
分母上 V a r [ x ] Var[x] Var[x]是标准差,有可能为0,因此加一个很小的数来避免除以0异常。
>>> s=a.std(-1,keepdim=True)#标准差
>>> s
tensor([78.1231])
>>> h=(a-m)/(s+1e-5)
>>> h
tensor([-0.4499, 0.5481, -1.1623, 1.4209, -0.3568])
模型代码
#NNModel.py
#encoding: utf-8
from torch import nn
class BoWLayer(nn.Module):
def __init__(self, isize, hsize, dropout,norm_residual=True,
**kwargs):
super(BoWLayer, 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 NNBoW(nn.Module):
def __init__(self, vcb_size, nclass, isize, hsize, dropout,
nlayer, **kwargs):
super(NNBoW, self).__init__()
self.emb = nn.Embedding(vcb_size, isize,
padding_idx=0) #<pad>的索引为0
self.drop = nn.Dropout(p=dropout, inplace=True) #embedding后dropout
self.nets = nn.Sequential(*[BoWLayer(isize, hsize, dropout)
for _ in range(nlayer)])
self.classifier = nn.Linear(isize, nclass)
self.normer = nn.LayerNorm(isize)
self.out_normer = nn.LayerNorm(isize)
# input: (bsize, seql) 句数、句长
def forward(self, input):
mask = input.eq(0) #找到<pad>的位置
# mask: (bsize, seql)
out = self.emb(input)
# out: (bsize, seql, isize)
out = out.masked_fill(mask.unsqueeze(-1), 0.0) #将out中<pad>的位置置为0
out = self.drop(out)
out = out.sum(1) #对序列求和,在第一维度求和
#求和后out: (bsize, seql, isize) -> out: (bsize, isize)
out = self.normer(out) #使用归一化,使模长均匀
out = self.nets(out) #特征提取
out = self.out_normer(out) #特征提取后,分类前再做一次归一化
out = self.classifier(out) #分类产生参数
#out: (bsize, isize) -> out: (bsize, nclass)
return out
模型的训练
从h5文件中取值
我们上节存储h5文件时,特别存储了nword的Dataset,来保存总词数以及总类别数:
:~/nlp/tnews$ h5ls -d train.h5/nword
nword Dataset {2}
Data:
15379, 15
如果我们直接写是无法得到其中的具体值的
>>> from h5py import File as h5File
>>> t_data = h5File("train.h5","r")
>>> t_data["nword"]
<HDF5 dataset "nword": shape (2,), type "<i4">
我们如果从中取值,要这样写t_data["nword"][()]
,传一个空的tuple,表示取所有值。
>>> t_data["nword"][()]
array([15379, 15], dtype=int32)
>>> type(t_data["nword"][()])
<class 'numpy.ndarray'>
我们可以看到,取出的类型为numpy的ndarray,我们需要使用tolist()
转成list后取出。
>>> vsize,nclass=t_data["nword"][()].tolist()
>>> vsize,nclass
(15379, 15)
损失函数优化
使用torch.nn.CrossEntropyLoss:
CLASStorch.nn.CrossEntropyLoss(weight=None, size_average=None, ignore_index=-100, reduce=None, reduction='mean', label_smoothing=0.0)
:
>>> import torch
>>> a=torch.randn(4)
>>> a
tensor([0.7171, 0.0033, 1.3289, 1.5256])
>>> a.softmax(-1)
tensor([0.1793, 0.0878, 0.3305, 0.4024])
>>> p=a.softmax(-1)
>>> p.sum()
tensor(1.0000)
>>> _ = a.exp()
>>> _
tensor([2.0485, 1.0033, 3.7768, 4.5980])
>>> _/_.sum(-1,keepdim=True)
tensor([0.1793, 0.0878, 0.3305, 0.4024])
softmax
函数是将输入的分数先求指数后再除以求指数后的和。经过softmax函数之后,再对结果值求负对数。由于经过softmax函数后张量中的值均是0到1的分数,则分数越接近于1,求负对数后值越接近于0。
>>> p
tensor([0.1793, 0.0878, 0.3305, 0.4024])
>>> -p.log()
tensor([1.7188, 2.4327, 1.1071, 0.9103])
在以上代码中,p[-1]值最大,则取负对数后值就越小。
label_smoothing参数的设置
若我们有四个类别{0, 1, 2, 3},softmax函数处理后得到的[P1, P2, P3, P4]. 若正确的类别是2。则交叉熵损失函数得到的分数为:
−
l
o
g
(
P
2
)
-log(P_2)
−log(P2).
我们设置 label_smoothing=0.1
,则会分配权重:
W
2
=
1
−
0.1
=
0.9
W_2 = 1 - 0.1 = 0.9
W2=1−0.1=0.9,
W
0
=
W
1
=
W
3
=
0.1
/
3
=
1
30
W_0 = W_1 = W_3=0.1/3 = \frac{1}{30}
W0=W1=W3=0.1/3=301
则 label_smoothing = 0.1得到的分数为:
l
s
l
o
s
s
=
1
30
∗
s
u
m
(
−
l
o
g
(
P
0
P
1
P
3
)
+
0.9
∗
(
−
l
o
g
(
P
2
)
)
)
ls loss = \frac{1}{30} * sum(-log(P_0P_1P_3)+0.9*(-log(P_2)))
lsloss=301∗sum(−log(P0P1P3)+0.9∗(−log(P2)))
如果我们只使用CrossEntropyLoss,我们可能会使模型向一个极端方向走,容易忽略其他参数的影响。而设置label_smoothing,它会分配权重来承认正确类别足够重要,但也不能忽视其他类别。
学习率调度
class StepLR(LRScheduler):
def __init__(self, optimizer, step_size, gamma=0.1, last_epoch=-1, verbose="deprecated"):
self.step_size = step_size
self.gamma = gamma
super().__init__(optimizer, last_epoch, verbose)
def get_lr(self):
if not self._get_lr_called_within_step:
warnings.warn("To get the last learning rate computed by the scheduler, "
"please use `get_last_lr()`.", UserWarning)
if (self.last_epoch == 0) or (self.last_epoch % self.step_size != 0):
return [group['lr'] for group in self.optimizer.param_groups]
return [group['lr'] * self.gamma
for group in self.optimizer.param_groups]
我们需要写两个函数分别用来初始化和更新当前学习率。我们用初始学习率除以步数开根号来做学习率的下降:
#lrsch.py
#encoding: utf-8
from torch.optim.lr_scheduler import _LRScheduler
from math import sqrt
class SqrtDecayLR(_LRScheduler):
#base_lr / sqrt(step)
def __init__(self, optimizer, base_lr, min_lr=1e-8, step=1,
last_epoch=-1, **kwargs):
self.base_lr, self.min_lr, self._step = base_lr, min_lr, step
#设置最小的学习率
super().__init__(optimizer, last_epoch)
def get_lr(self):
_lr = max(self.base_lr / sqrt(self._step), self.min_lr)
self._step += 1
#每次计算学习率
return [_lr for _ in range(len(self.base_lrs))]
训练模型
#train.py
#encoding: utf-8
import torch
from torch import nn
from NNModel import NNBoW #导入模型
from h5py import File as h5File #读训练数据
from math import sqrt
from random import shuffle #使输入数据乱序,使模型更均衡
from lrsch import SqrtDecayLR
train_data = "train.h5"
dev_data = "dev.h5" #之前已经张量转文本的h5文件
isize = 64
hsize = isize * 2 #设置初始参数
dropout = 0.3 #设置丢弃率
nlayer = 4 #设置层数
gpu_id = -1 #设置是否使用gpu
lr = 1e-3 #设置初始学习率
max_run = 512 #设置训练轮数
early_stop = 16 #设置早停轮数
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 modin
def train(train_data, tl, model, lossf, optm, cuda_device):
model.train() #设置模型在训练的模式
src_grp, tgt_grp = train_data["src"], train_data["tgt"] #从输入数据中取出句子和标签
_l = 0.0 #_l用来存当前loss
_t = 0 #_t用来存句子数
for _id in tl:
seq_batch = torch.from_numpy(src_grp[_id][()])
seq_o = torch.from_numpy(tgt_grp[_id][()]) #取出句子和标签转化成torch类型
if cuda_device is not None:
seq_batch = seq_batch.to(cuda_device, non_blocking=True)
seq_o = seq_o.to(cuda_device, non_blocking=True) #将数据放在同一gpu上
seq_batch, seq_o = seq_batch.long(), seq_o.long() #数据转换为long类型
out = model(seq_batch) #获得模型结果
loss = lossf(out, seq_o) #获得损失函数
_l += loss.item() #获得浮点数
_t += seq_batch.size(0) #累加获得当前句数
loss.backward() #反向传播求导
optm.step() #参数的更新
optm.zero_grad(set_to_none=True)#清空梯度
return _l / _t #返回当前loss
def eva(vd, nd, model, lossf, cuda_device):
model.eval() #设置模型在验证方式
src_grp, tgt_grp = vd["src"], vd["tgt"]
_loss = 0.0
_t = _err = 0 #_err记录错误的句数
with torch.no_grad(): #禁用求导,节省计算开销
for i in range(nd):
_ = str(i) #获取字符串形式的key
seq_batch = torch.from_numpy(src_grp[_][()])
seq_o = torch.from_numpy(tgt_grp[_][()]) #取句子和标签
if cuda_device is not None:
seq_batch = seq_batch.to(cuda_device, non_blocking=True)
seq_o = seq_o.to(cuda_device, non_blocking=True) #放在同一设备上
seq_batch, seq_o = seq_batch.long(), seq_o.long() #数据类型转换
out = model(seq_batch)
loss = lossf(out, seq_o)
_loss += loss.item() #loss累加
_t += seq_batch.size(0) #记录数据总量
_err += out.argmax(-1).ne(seq_o).int().sum().item() #argmax获取最大值的位置,当做预测的类别位置
#ne()判断和正确类别是否不等,不等为T相等为F,转成0和1后累加得到的值就是错的总数
model.train() #模型恢复为训练方式
return _loss / _t, float(_err) / _t *100.0 #返回平均的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")
d_data = h5File(dev_data, "r") #以读的方式打开训练以及验证数据
vcb_size, nclass = t_data["nword"][()].tolist() #将返回的numpy的ndarray转为list
#在我们的h5文件中存储了nword:(总词数,总类别数)
model = NNBoW(vcb_size, nclass, isize, hsize, dropout, nlayer)
model = init_model_parameters(model) #在cpu上初始化模型
lossf = nn.CrossEntropyLoss(reduction='sum', label_smoothing=0.1)
#设置损失函数优化,由于句长不一致,我们使用sum而非mean方式
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
nvalid = d_data["ndata"][()].item()
min_loss, min_err = eva(d_data, nvalid, model, lossf,
cuda_device)
print("Init dev_loss %.2f, error %.2f" % (min_loss, min_err,))#打印一下初始状态
namin = 0
for i in range(1, max_run + 1):
shuffle(tl) #使数据乱序
_tloss = train(t_data, tl, model, lossf, optm,
cuda_device) #获取每轮训练的损失
_dloss, _derr = eva(d_data, nvalid, model, lossf,
cuda_device) #获取每轮验证的损失和错误率
print("Epoch %d: train loss %.2f, dev loss %.2f, error %.2f"
%(i, _tloss, _dloss, _derr)) #打印日志
_save_model = False #模型的保存,保存在验证集上表现最好的模型
if _dloss < min_loss:
_save_model = True
min_loss = _dloss
if _derr < min_err:
_save_model = True
min_err = _derr #保存在loss和err指标上最好的模型
if _save_model: #如果需要保存模型
save_model(model, "eva.pt")
namin = 0
else:
namin += 1
if namin >= early_stop: #早停逻辑
break
lrm.step() #每轮训练后更新学习率
t_data.close()
d_data.close() #最后关闭这两个文件
在命令行输入python train.py运行文件,我们可以看到:
训练进行了22轮出现早停,在训练集上验证的错误率在48%左右。
模型的解码
解码首先我们需要对验证集做排序:
#sorti.py
#encoding: utf-8
import sys
def handle(srcf, srts, max_len=1048576):
# {length: {(src, label)}} 外层dict,中层set,内层tuple
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] #取出句长对应的set
fsrc.write("\n".join(lset).encode("utf-8")) #在每个句子间插入换行符
fsrc.write(ens) #每个句子后插入换行
if __name__ == "__main__":
handle(*sys.argv[1:3])
在命令行执行:
:~/nlp/tnews$ python sorti.py src.dev.bpe.txt src.dev.bpe.txt.srt
查看排序后的.srt文件:
可以看到已经按照句子长度(即分词个数)升序排序并存储。
存储为h5文件
#mktesth5.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 = 2048, **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 + 1
if (_n * _mlen) > max_tokens: #如果把添加了这句话的 句数*分词数量 大于最大值则不能放
if ri: #如果ri,rt不为空
yield ri, mlen #返回ri,rt和原来的句子长度
ri, mlen, n = [ls], _l, 1 #返回后重新初始化,将本句加入新的batch
else: #如果不超过当前长度,则将此句添加到batch中
ri.append(ls)
mlen, n = _mlen, _n #更新句子长度与句子数量
if ri: #最后若仍然有数据,则返回为一个新的batch
yield ri, mlen
def batch_mapper(fsrc, vcbs, **kwargs): #将分词变索引
for ri, mlen in batch_loader(fsrc, **kwargs):
yield [[vcbs[_word] for _word in _s if _word in vcbs]
for _s in ri], mlen
#遍历每个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>的索引0
rs.append(lu)
return rs #返回的是均已对齐的每个batch
def 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 ) #设置压缩存储节省空间,压缩等级设置为最大压缩代价9
h5f["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/tnews$ python mktesth5.py src.dev.bpe.txt.srt src.vcb test.h5
查看存储后的h5文件,可以看到验证集的总词数为15379,总batch数为78:
:~/nlp/tnews$ h5ls test.h5
ndata Dataset {1}
nword Dataset {1}
src Group
:~/nlp/tnews$ h5ls -d test.h5/ndata
ndata Dataset {1}
Data:
78
:~/nlp/tnews$ h5ls -d test.h5/nword
nword Dataset {1}
Data:
15379
模型的预测
#predict1.py
#encoding: utf-8
import sys
import torch
from NNModel import NNBoW #读模型
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 #模型的初始化参数
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=True))
nclass = len(tgt_vcb) #获取总类别数
model = NNBoW(vcb_size, nclass, 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的参数
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
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数据类型转换
out = model(seq_batch).argmax(-1).tolist() #将每个batch的预测下标转列表
out = "\n".join([tgt_vcb[_i] for _i in out]) #将预测下标转为对应的类别,类别间按行隔开
f.write(out.encode("utf-8"))
f.write(ens) #每个batch间还应有换行
test_file.close()
我们将预测类别存入out.txt,eva.pt是模型文件。在命令行输入:
:~/nlp/tnews$ python predict1.py test.h5 tgt.vcb out.txt eva.pt
:~/nlp/tnews$ less out.txt
请注意此时,我们存储预测的类别是对验证集排序后每句预测的类别,我们要对比原验证集的标签,需要将顺序恢复:
#restore.py
#encoding: utf-8
#python restore.py srti.txt srtp.txt srci.txt rs.txt
import sys
def load(srcf, tgtf):
rs = {}
with open(srcf, "rb") as fsrc,open(tgtf, "rb") as ftgt:
for ls, lt in zip(fsrc, ftgt):
ls, lt = ls.strip(), lt.strip()
if ls and lt:
ls, lt =ls.decode("utf-8"), lt.decode("utf-8")
rs[ls] = lt #将每句话及其对应的预测类别存入字典rs
return rs
def lookup(srcf, rsf, mapd):
ens = "\n".encode("utf-8")
with open(srcf, "rb") as frd,open(rsf, "wb") as fwrt:
for line in frd:
line = line.strip()
if line:
line = line.decode("utf-8")
fwrt.write(mapd[line].encode("utf-8"))
#按照排序前文件的顺序将预测的类别写入新文件
fwrt.write(ens)
if __name__ == "__main__":
lookup(*sys.argv[3:5], load(*sys.argv[1:3]))
#将load处理后的字典传入lookup中
在命令行执行:
:~/nlp/tnews$ python restore.py src.dev.bpe.txt.srt out.txt src.dev.bpe.txt pred.dev.txt
:~/nlp/tnews$ less pred.dev.txt
计算准确率
利用我们前面写的脚本来预测准确率,在命令行执行:
:~/nlp/tnews$ python acc.py pred.dev.txt tgt.dev.s.txt
51.59
:~/nlp/tnews$ python acc.py pred/pred.learnw.dev.txt tgt.dev.s.txt
46.92
相较于前面的将函数做非线性变换前的模型,我们前馈词袋分类模型预测准确率上升了5个百分点。