[文本处理之使用pytorch文本情感分类

准备数据

电影评论数据下载地址
将文本数据处理成torch,我们希望可以得到的target是他的评论态度是积极还是消极,将数据分为2500训练,2500测试,这里网址下载的数据数量已经分好了,利用pytorch进行文本处理

处理数据

遇到这个问题
RuntimeError: each element in list of batch should be of equal size
这里重写一下Dataloader中的collate_fn方法

import os
import re
from lib import ws,max_len
import torch
from torch.utils.data import Dataset,DataLoader

# 将一个句子分为由单词组成的列表
def tokenize(str):
    # 这个是re库里的函数,其原型为re.sub(pattern, repl, string, count)
    # 第一个参数为正则表达式需要被替换的参数,第二个参数是替换后的字符串,
    # 第三个参数为输入的字符串,第四个参数指替换个数。默认为0,表示每个匹配项都替换。
    filter = ['!','"','#','$','%','&','\(','\)','\*','\+',',','-','\.','/',':',';','<','=','>','\?','@'
        ,'\[','\\','\]','^','_','`','\{','\|','\}','~','\t','\n','\x97','\x96','”','“']
    token = re.sub('|'.join(filter),'',str)
    token = token.strip()
    return token.split(' ')


class Datas(Dataset):
    def __init__(self,train=True):
        self.train_path = r'data/aclImdb/train'
        self.test_path = r'data/aclImdb/test'
        temp_path = self.train_path if train else self.test_path
        data_path = [temp_path+'/pos', temp_path+'/neg']
        # 将含评论的所有文件放在列表中
        self.files = []
        for i in data_path:
            listdirs = os.listdir(i)
            data_path2 = [i+'/'+j for j in listdirs]
            self.files.extend(data_path2)

    def __len__(self):
    # 有多少评论数据
        return len(self.files)

    def __getitem__(self, index):
        file_path = self.files[index]
        label = file_path.split('/')[-2]
        label = 1 if label == 'pos' else 0
        # 这里label就是我们想预测的
        # 从文件中读到的是真实值
        content = open(file_path,encoding='utf-8').read()
        # 返回经过分词的句子列表
        return label,tokenize(content)

# 需要重写collate_fn方法
def collate_fn(batch):
    #  batch是一个列表,其中是一个一个的元组,每个元组是dataset中_getitem__的结果
    batch = list(zip(*batch))
    labels = torch.tensor(batch[0], dtype=torch.int64)
    texts = batch[1]
    texts = [ws.transform(text,max_len=max_len) for text in texts]
    texts = torch.LongTensor(texts)
    # labels = torch.LongTensor(labels)
    del batch
    # 返回真实值,评论(特征值)
    return labels, texts


def get_dataloader():
    data = Datas(True)
    dataloader = DataLoader(dataset=data, batch_size=64, shuffle=True, collate_fn=collate_fn)
    # 传入经过分词的64个评论一个batch进入到数据加载器中
    return dataloader


if __name__ == '__main__':

    # print(tokenize("e D:/test/deep_learing/test/text_test.py data/aclImdb/train/pos/0_9.txt pos1"))
    for idx,(input,out) in enumerate(get_dataloader()):
        print(idx)
        print(input)
        print(out)
        break

在这里插入图片描述
结果是这样,第二行的tensor代表的是两个评论的label都是0因为,第三行是两个评论的文本分词
得到了评论的数据之后,我们需要对词语进行序列化
即把词语转换为数字

序列化

class word2sequense():
# 定义当我们训练出来的词典的两个关键词:UNK就是字典里没有出现的,APP就是由于句子长度不够,填充的符号
    unknown_tag = 'UNK'
    append_tag = 'APP'
    UNK = 0
    APP = 1

    def __init__(self):
    # 定义词典
        self.dict = {
            self.unknown_tag:self.UNK,
            self.append_tag:self.APP

        }
    # 统计词频
    count = {}

    def fit(self,sentence):
        for word in sentence:
            self.count[word] = self.count.get(word,0) + 1

    def filter_count(self,min=5,max=None,max_features=None):
        """
        :param min:最小词频
        :param max: 最大词频
        :param max_features:一共保存的词语个数
        :return:
        """
        # 去掉词频小于min的
        self.count = {word:value for word,value in self.count.items() if value > min}
        # 去掉大于max的
        if max is not None:
            self.count = {word: value for word, value in self.count.items() if value < max}
        if max_features is not None:
            # 把count进行排序,取出前max_features个
            temp = sorted(self.count.items(),key=lambda x:x[-1],reverse=True)[:max_features]
            # 转回字典
            self.count = dict(temp)
        # 将单词用数字表示,数字就是第几个加到词典的
        for word in self.count:
            self.dict[word] = len(self.dict)
        # 将数字用单词表示
        self.dict2 = dict(zip(self.dict.values(),self.dict.keys()))

    def transform(self,sentence,max_len=None):
        if max_len is not None:
            # 将句子长度统一
            if max_len>len(sentence):
            # 当长度不够的时候,在句子后面加上对应数量的app符号
                sentence += [self.APP]*(max_len-len(sentence))
            if max_len<len(sentence):
                # 裁剪后面的
                sentence = sentence[:max_len]
        # 将句子转换成序列
        # for word in sentence:
        #     self.dict.get(word,self.UNK)
        return [self.dict.get(word,self.UNK) for word in sentence]
        # 从dict里找单词的编号,找不到为0

    def inverse_trans(self,indics):
        # 将序列转换成句子
        return [self.dict2.get(indx) for indx in indics]
 
    def __len__(self):
        return len(self.dict)


if __name__ == '__main__':
    word2 = word2sequense()
    word2.fit(['日','你','妈'])
    word2.fit(['退','钱'])
    word2.fit(['中','果','足','球','冲','进','世界','杯'])
    word2.filter_count(min=0)
    print(word2.dict)
    print(word2.dict2)

结果是这样
在这里插入图片描述
在这里将使用pickle将以上保存起来

import pickle
from tqdm import tqdm
# 显示进度
import os
from text_test import tokenize
from word2 import word2sequense
ws = word2sequense()
# ws就是我们的序列化器
# 传入要训练的句子,从train文件中将所有的评论文件放入
train_path = r'data/aclImdb/train'
data_path = [train_path + '/pos', train_path + '/neg']
files = []
for i in data_path:
    listdirs = os.listdir(i)
    datas_path = [i + '/' + j for j in listdirs]
    for data in tqdm(datas_path):
        sentence = tokenize(open(data,encoding='UTF-8').read())
        ws.fit(sentence)
ws.filter_count()
# 将训练好的词典保存起来
pickle.dump(ws, open(r'model/ws.okl','wb'))
print(len(ws))

在这里如果出现了open error找不到文件,创建就好了不知道为什么wb模式自动创建没有成功,不过我们手动创建的小效果一样
如果出现了编码的问题,open的时候加入encoding='utf-8’即可,具体情况具体分析.
在这里保存之后,后续使用pickle.load(model/ws.okl)进行载入
去之前的最开始的地方进行检测
对前面的collate_fn进行部分修改

def collate_fn(batch):
    #  batch是一个列表,其中是一个一个的元组,每个元组是dataset中_getitem__的结果
    batch = list(zip(*batch))
    labels = torch.tensor(batch[0], dtype=torch.int32)
    texts = batch[1]
    texts = [ws.transform(text) for text in texts]
    del batch
    return labels, texts

修改后的运行结果
在这里插入图片描述
从之前的词语成功的变为数字,达到了目的
,至此数据处理完成

模型建立

在这里插入图片描述
模型建立之后遇到了问题,注意以下label和text的顺序,就好了

import torch.nn as nn
from lib import ws,max_len
import torch.nn.functional as f
from torch.optim import Adam


class mymodel(nn.Module):
    def __init__(self):
        super(mymodel, self).__init__()
        self.embedding = nn.Embedding(len(ws),100)
        # 传入参数:词的数量,多少个词形成一个向量
        self.fc = nn.Linear(max_len*100,2)

    def forward(self,input):
        """
        :param input:[batch_size,句子长度(max_size)]
        :return:
        """
        x = self.embedding(input)  # x的形状为[batch_size,句子长度(max_size),100]
        # 变换形状
        x = x.view([-1,max_len*100])
        out = self.fc(x)
        return f.log_softmax(out)


from text_test import get_dataloader

model = mymodel()
# 优化器,传入参数和学习率
optimizer= Adam(model.parameters(),0.001)


def train():
    for idx,(out,input) in enumerate(get_dataloader()):
        optimizer.zero_grad()
        pre = model(input)
        # 计算损失
        loss = f.nll_loss(pre,out)
        loss.backward()
        optimizer.step()
        print(loss.item())


if __name__ == '__main__':
    train()


运行结果:
在这里插入图片描述

RNN循环神经网络的使用进行优化

这里只是将几个单词看成一个向量,训练的效果并不好
循环神经网络的使用,可以让数据具有时序性,即考虑的不仅仅是当前的输入,也考虑之前的输入来调节参数的概率从而使训练效果更好
具有短期记忆–rnn循环神经网络

RNN分类

在这里插入图片描述

使用LSTM解决RNN中的长期依赖问题(long-Term Dependencies)

rnn存在间隔长的词语,导致前面的词语对后面的影响小的问题,即长期依赖问题
LSTM的三个输入:
上一次的隐藏状态+上一次的细胞状态(记忆)+本次的输入
三个输出:
隐藏状态+细胞状态+输出,输出和隐藏状态是一样的
输入的形状:
input : [seq_len,batch,embedding size]
输出的形状:
output:[seq_len, batch, num_directions * hidden_size]—>batch_first=False
,/其中hidden_size为一层神经系统里有多少lstm,num_direction是由单向lstm还是双向决定的
单向为1,双向为2
h_n(隐藏状态):(num_layers * num_directions, batch, hidden_size)
c_n(细胞状态): (num_layers * num_directions, batch, hidden_size)
num_layers为有多少层神经系统

实例化LSTM

torch.nn.LSTM(input_size,hidden_size,num_layers,batch_first,dropout,bidirectional)

input_size:输入数据的形状,即embedding_dim
hidden_size:隐藏层神经元的数量,即每一层有多少个LSTM单元
num_layer :即RNN的中LSTM单元的层数
batch_first:默认值为False,输入的数据需要[seq_len,batch,feature],如果为True,则为[batch,seq_len,feature]
dropout:dropout的比例,默认值为0。dropout是一种训练过程中让部分参数随机失活的一种方式,能够提高训练速度,同时能够解决过拟合的问题。这里是在LSTM的最后一层,对每个输出进行dropout
bidirectional:是否使用双向LSTM,默认是False

import os

import torch
import torch.nn as nn
from lib import ws,max_len
import torch.nn.functional as f
from torch.optim import Adam
import lib


class mymodel(nn.Module):
    def __init__(self):
        super(mymodel, self).__init__()
        self.embedding = nn.Embedding(len(ws),100)
        # 传入参数:词的数量,多少个词形成一个向量
        self.fc = nn.Linear(lib.hidden_size*2,2)
        # LSTM优化,实例化
        self.LSTM= nn.LSTM(input_size=100,hidden_size=lib.hidden_size,
                           num_layers=lib.layer_num,bidirectional=lib.biderational,
                           batch_first=True,dropout=lib.dropout)

    def forward(self,input):
        """
        :param input:[batch_size,句子长度(max_size)]
        :return:
        """
        x = self.embedding(input)  # x的形状为[batch_size,句子长度(max_size),100]
        # 变换形状
        # lstm处理
        x,(h_n,c_n) = self.LSTM(x)
        # x的形状[batch_size,句子长度(max_size),batch_size*2(方向)],h_n的形状[方向*层数(4),batch_size,hidden_size,]
        # c_n的形状[方向*层数(4),batch_size,hidden_size]
        # 获取两个方向的output进行拼接,第一维是层数,第一层正向的最后一个,即层数-2,第二层就是-1
        output_fw = h_n[-2,:,:]
        output_bw = h_n[-1,:,:]
        output = torch.cat((output_fw,output_bw),dim=-1)
        # output形状为[batch_size,hidden_size*2]
        # x = x.view([-1,max_len*100])
        out = self.fc(output)
        return f.log_softmax(out)


from text_test import get_dataloader

model = mymodel()
# 优化器,传入参数和学习率
optimizer= Adam(model.parameters(),0.001)
if os.path.exists('model/model1.okl'):
    model.load_state_dict(torch.load('model/model1.kl'))
    optimizer.load_state_dict(torch.load('model/opt1.kl'))


def train():
    for idx,(out,input) in enumerate(get_dataloader()):
        optimizer.zero_grad()
        pre = model(input)
        # 计算损失
        loss = f.nll_loss(pre,out)
        loss.backward()
        optimizer.step()
        print(loss.item())
        # 保存模型
        if idx % 100 == 0:
            torch.save(model.state_dict(),'model/model1.okl')
            torch.save(optimizer.state_dict(),'model/opt1.okl')


if __name__ == '__main__':
    train()


进行训练的时候.使用cpu训练的时间较长采用gpu计算

校验模型

模型的检验使用训练好的模型,将test数据放入,获取到误差的平均值和准确值作为模型评判

  • 0
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值