基于Pytorch的NLP入门任务思想及代码实现:判断文本中是否出现指定字

今天学了第一个基于Pytorch框架的NLP任务:

判断文本中是否出现指定字

思路:(注意:这是基于字的算法)

任务:判断文本中是否出现“xyz”,出现其中之一即可

训练部分:

一,我们要先设计一个模型去训练数据。
这个Pytorch的模型:
首先通过embedding层:将字符转化为离散数值(矩阵)
通过线性层:设置网络的连接层,进行映射
通过dropout层:将一部分输入设为0(可去掉)
通过激活层:sigmoid激活
通过一个pooling层:降维,将矩阵->向量
通过另一个输出线性层:使输出是一维(1或0)
通过一个激活层*:sigmoid激活。

二,设置一个函数:这个函数能将设定的字符变成字符集,将每一个字符设定一个代号,比如说:“我爱你”-> 我:1,爱:2,你:3。当出现"你爱我"时,计算机接受的是:3,2,1。这样方便计算机处理字符。

三,因为我们没有训练样本和测试样本,所以我们要自己生成一些随机样本。通过random.choice在字符集中随机顺序输出字符作为输入,并将输入中含有"xyz"的样本的输出值为“1”,反之为“0”

四,设置一个函数,将随机得到的样本,放入数据集中(列表),便于运算。

五,设置测试函数:随机建立一些样本,根据样本的输出来设定有多少个正样本,多少个负样本,再将预测的样本输出来与样本输出对比,得到正确率。

六,最后的main函数:按照训练轮数和训练组数,通过BP反向传播更新权重进行训练。然后调取测试函数得到acc等数据。将loss和acc的数值绘制下来,保存模型和词表。

预测部分

将保存的词表和模型加载进来,将输入的字符转化为列表,然后进入模型forward函数进行预测,最后打印出结果。

代码实现:

import torch
import torch.nn as nn
import numpy as np
import random
import json
import matplotlib.pyplot as plt

"""
基于pytorch的网络编写
实现一个网络完成一个简单nlp任务
判断文本中是否有某些特定字符出现
"""

class TorchModel(nn.Module):
    def __init__(self, input_dim, sentence_length, vocab):
        super(TorchModel, self).__init__()
        self.embedding = nn.Embedding(len(vocab) + 1, input_dim)    #embedding层:将字符转化为离散数值
        self.layer = nn.Linear(input_dim, input_dim)    #对输入数据做线性变换
        self.classify = nn.Linear(input_dim, 1)     #映射到一维
        self.pool = nn.AvgPool1d(sentence_length)   #pooling层:降维
        self.activation = torch.sigmoid     #sigmoid做激活函数
        self.dropout = nn.Dropout(0.1)  #一部分输入为0
        self.loss = nn.functional.mse_loss  #loss采用均方差损失

    #当输入真实标签,返回loss值;无真实标签,返回预测值
    def forward(self, x, y=None):
        x = self.embedding(x)
        #输入维度:(batch_size, sen_len)输出维度:(batch_size, sen_len, input_dim)将文本->矩阵
        x = self.layer(x)
        #输入维度:(batch_size, sen_len, input_dim)输出维度:(batch_size, sen_len, input_dim)
        x = self.dropout(x)
        #输入维度:(batch_size, sen_len, input_dim)输出维度:(batch_size, sen_len, input_dim)
        x = self.activation(x)
        #输入维度:(batch_size, sen_len, input_dim)输出维度:(batch_size, sen_len, input_dim)
        x = self.pool(x.transpose(1,2)).squeeze()
        #输入维度:(batch_size, sen_len, input_dim)输出维度:(batch_size, input_dim)将矩阵->向量
        x = self.classify(x)
        #输入维度:(batch_size, input_dim)输出维度:(batch_size, 1)
        y_pred = self.activation(x)
        #输入维度:(batch_size, 1)输出维度:(batch_size, 1)
        if y is not None:
            return self.loss(y_pred, y)
        else:
            return y_pred

#字符集随便挑了一些汉字,实际上还可以扩充
#为每个汉字生成一个标号
#{"不":1, "东":2, "个":3...}
#不东个->[1,2,3]
def build_vocab():
    chars = "不东个么买五你儿几发可同名呢方人上额旅法xyz"  #随便设置一个字符集
    vocab = {}
    for index, char in enumerate(chars):
        vocab[char] = index + 1   #每个字对应一个序号,+1是序号从1开始
    vocab['unk'] = len(vocab)+1   #不在表中的值设为前一个+1
    return vocab

#随机生成一个样本
#从所有字中选取sentence_length个字
#如果vocab中的xyz出现在样本中,则为正样本
#反之为负样本
def build_sample(vocab, sentence_length):
    #将vacab转化为字表,随机从字表选取sentence_length个字,可能重复
    x = [random.choice(list(vocab.keys())) for _ in range(sentence_length)]
    #指定哪些字必须在正样本出现
    if set("xyz") & set(x):     #若xyz与x中的字符相匹配,则为1,为正样本
        y = 1
    else:
        y = 0
    x = [vocab.get(word,vocab['unk']) for word in x]   #将字转换成序号
    return x, y

#建立数据集
#输入需要的样本数量。需要多少生成多少
def build_dataset(sample_length,vocab, sentence_length):
    dataset_x = []
    dataset_y = []
    for i in range(sample_length):
        x, y = build_sample(vocab, sentence_length)
        dataset_x.append(x)
        dataset_y.append([y])
    return torch.LongTensor(dataset_x), torch.FloatTensor(dataset_y)

#建立模型
def build_model(vocab, char_dim, sentence_length):
    model = TorchModel(char_dim, sentence_length, vocab)
    return model

#测试代码
#用来测试每轮模型的准确率
def evaluate(model, vocab, sample_length):
    model.eval()
    x, y = build_dataset(200, vocab, sample_length)
    #建立200个用于测试的样本(因为测试样本是随机生成的,所以不存在过拟合)
    print("本次预测集中共有%d个正样本,%d个负样本"%(sum(y), 200 - sum(y)))
    correct, wrong = 0, 0
    with torch.no_grad():
        y_pred = model(x)      #调用Pytorch模型预测
        for y_p, y_t in zip(y_pred, y):  #与真实标签进行对比
            if float(y_p) < 0.5 and int(y_t) == 0:
                correct += 1   #负样本判断正确
            elif float(y_p) >= 0.5 and int(y_t) == 1:
                correct += 1   #正样本判断正确
            else:
                wrong += 1
    print("正确预测个数:%d, 正确率:%f"%(correct, correct/(correct+wrong)))
    return correct/(correct+wrong)


def main():
    epoch_num = 10        #训练轮数
    batch_size = 20       #每次训练样本个数
    train_sample = 1000   #每轮训练总共训练的样本总数
    char_dim = 20         #每个字的维度
    sentence_length = 10   #样本文本长度
    vocab = build_vocab()       #建立字表
    model = build_model(vocab, char_dim, sentence_length)    #建立模型
    optim = torch.optim.Adam(model.parameters(), lr=0.005)   #建立优化器
    log = []
    for epoch in range(epoch_num):
        model.train()
        watch_loss = []
        for batch in range(int(train_sample / batch_size)):
            x, y = build_dataset(batch_size, vocab, sentence_length) #每次训练构建一组训练样本
            optim.zero_grad()    #梯度归零
            loss = model(x, y)   #计算loss
            watch_loss.append(loss.item())  #将loss存下来,方便画图
            loss.backward()      #计算梯度
            optim.step()         #更新权重
        print("=========\n第%d轮平均loss:%f" % (epoch + 1, np.mean(watch_loss)))
        acc = evaluate(model, vocab, sentence_length)   #测试本轮模型结果
        log.append([acc, np.mean(watch_loss)])
    plt.plot(range(len(log)), [l[0] for l in log])  #画acc曲线:蓝色的
    plt.plot(range(len(log)), [l[1] for l in log])  #画loss曲线:黄色的
    plt.show()
    #保存模型
    torch.save(model.state_dict(), "model.pth")
    writer = open("vocab.json", "w", encoding="utf8")
    #保存词表
    writer.write(json.dumps(vocab, ensure_ascii=False, indent=2))
    writer.close()
    return

#最终预测
def predict(model_path, vocab_path, input_strings):
    char_dim = 20  # 每个字的维度
    sentence_length = 10  # 样本文本长度
    vocab = json.load(open(vocab_path, "r", encoding="utf8"))
    model = build_model(vocab, char_dim, sentence_length)    #建立模型
    model.load_state_dict(torch.load(model_path))   #将模型文件加载进来
    x = []
    for input_string in input_strings:  #转化输入
        x.append([vocab[char] for char in input_string])
    model.eval()    #在torch中预测注意这个:停止dropout
    with torch.no_grad():   #在torch中预测注意这个:停止梯度
        result = model.forward(torch.LongTensor(x)) #根据自己设计的函数定义,只输入x就会输出预测值
    for i, input_string in enumerate(input_strings):
        print(round(float(result[i])), input_string, result[i])
        #round(float(result))是将预测结果四舍五入得到0或1的预测值



if __name__ == "__main__":
    main()
    #如果是进行预测,将下面两行解除注释,将main()注释掉,即可调用最终预测函数进行预测
    # test_strings = ["个么买不东五你儿x发", "不东东么儿几买五你发", "不东个么买五你个么买", "不z个五么买你儿几发"]
    # predict("model.pth", "vocab.json", test_strings)

运行结果展示:

训练部分:

在这里插入图片描述
(蓝色是acc曲线,黄色是loss曲线)

预测部分:

在这里插入图片描述

一些补充:

一:model.eval()或者model.train()的作用

如果模型中有BN层(Batch Normalization)和Dropout,需要在训练时添加model.train(),在测试时添加model.eval()。其中model.train()是保证BN层用每一批数据的均值和方差,而model.eval()是保证BN用全部训练数据的均值和方差;而对于Dropout,model.train()是随机取一部分网络连接来训练更新参数,而model.eval()是利用到了所有网络连接。

二:Pytorch模型中用了两次激活函数

在每一个网络层后使用一个激活层是一种比较常见的模型搭建方式,但不是必要的。这个只是举例,去掉也是可行的。在具体任务中,带着好还是不带好也跟数据和任务本身有关,没有确定答案(如果在代码 中把第一个激活层注释掉 反而性能更好)

三:对x = self.pool(x.transpose(1,2)).squeeze()代码的解读

通过shape方法我们能知道,在pool前,x的维度输出是[20,10,20],代表20个10×20的矩阵,代表着[这一批的个数,样本文本长度,输入维度],transpose(1,2)是将x中行和列调换(转置),然后通过pooling层将[20,20,10]->[20,20,1],最后通过squeeze()进行降维变成[20,20]。
(池化层的作用及理解)

四:embedding层的理解

embedding层并不是单纯的单词映射,而是将单词表中每个单词的数值与权重相乘。在第一次时有默认权重,然后在接下来的训练中,embedding层的权重与分类权重一起经过训练。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值