当前位置:   article > 正文

Pytorch+Text-CNN+Word2vec+电影评论情感分析实战_word2vec评论分析

word2vec评论分析

0.前言

很多人喜欢使用IMDB数据集来做电影评论情感分析示范,但这却是我极其不建议的,因为其一般被很多官方的库都收录,所以有很多接口用于处理这个数据集,这让我们无法体验到真实场景,一旦碰到真正的数据集,我们将手都动不了。于是乎,有了这一篇。

1.电影评论数据集

本文所使用数据集的资源下载处:https://download.csdn.net/download/qq_43391414/20143453。(已经设置了不要积分)。

当然了,使用你自己的数据集也是可以的,下面的数据集处理过程通用,你只需要对号入座即可。

我的数据集格式如下:

1如果 我 无聊 时 网上 乱逛 偶尔 看到 这部 电影 我 可能 会 给 它 打 四星 但是 TNND 姐是 花 大洋 去 电影院 看 姐在 电影院 睡着 姐 现在 非常 心疼 电影票 钱 一部 无聊 至极 电影
0没有 华丽 镜头 或者 刻意 营造 小 清新 就是 普普通通 台湾 巷子 简简单单 中学 教室 但 就是 那么 纯真 那么 青春 那么 美好 让 人 怀念 虽然 上课 打 飞机 同学 非常 重 口味 偷笑

没有头部,第一行就是数据,第一列是标签,1表示差评,0表示好评。然后是\t分隔符,然后是第二列电影评论,这个已经经过分词处理(有没有分词无所谓),词和词之间用空格隔开。

再次强调,如果你的数据集没有分词也没关系,无非就是你的输入是字,我的输入是词,此外没有区别,对号入座!

在这里插入图片描述
训练数据集一共有19998条电影评论。另外,补充一句,我们本文只用训练数据集,因为这个训练数据集挺大的了,所以我们直接从里面划分出测试集。

2.数据读取

root_path="D:\Download\Dataset\Dataset"
train_path="D:\Download\Dataset\Dataset\\train.txt"
import pandas as pd
train_data = pd.read_csv("D:\Download\Dataset\Dataset\\train.txt",names=["label","comment"],sep="\t")
print(train_data.head())
  • 1
  • 2
  • 3
  • 4
  • 5

在这里插入图片描述

comments_len=train_data.iloc[:,1].apply(lambda x:len(x.split()))
#每一条评论的词数个数。
comments_len
  • 1
  • 2
  • 3

在这里插入图片描述

如果没有分词那么就是字数,我的是词数,你应该这么做!对号入座!此后不再详述,自己对应。

comments_len=train_data.iloc[:,1].apply(lambda x:len(x))
#每一条评论的词数个数。
comments_len
  • 1
  • 2
  • 3
train_data["comments_len"]=comments_len
train_data["comments_len"].describe(percentiles=[.5,.95])#本来是三分位的,这里自己选了两个
  • 1
  • 2

在这里插入图片描述
也就是说我们的电影评论有95%都是在62个词以下。

3.数据预处理

from collections import Counter
words=[]
for i in range(len(train_data)):
    com=train_data["comment"][i].split()
    words=words+com
len(words)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

在这里插入图片描述
一共有89万个词,当然了,有很多重复的。

Freq=30
import os
with open(os.path.join(root_path,"word_freq.txt"), 'w', encoding='utf-8') as fout:
    for word,freq in Counter(words).most_common():
        if freq>Freq:
            fout.write(word+"\n")
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

我只要那些出现频率大于30的词,这个你可以自己调节,我这里由于计算资源不太雄厚,所以30比较高。不过,其实那些“难看”,“好看”这样的词按理在19998条数据中应该是绝对超过30的,所以我也算是保留了情感分类的关键词。

这个时候,我们的文件夹下多了一个文件:“word_freq.txt”。

#初始化vocab
with open(os.path.join(root_path,"word_freq.txt"), encoding='utf-8') as fin:
    vocab = [i.strip() for i in fin]
vocab=set(vocab)
word2idx = {i:index for index, i in enumerate(vocab)}
idx2word = {index:i for index, i in enumerate(vocab)}#没有想到列表竟然可以枚举。
vocab_size = len(vocab)
len(vocab)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

在这里插入图片描述
我们发现,即使我们去除了频率低于30的词,仍然有3000个词!

pad_id=word2idx["把"]
print(pad_id)
  • 1
  • 2

在这里插入图片描述

我们把那些出现频率低于30的词都变成"把“字,变成什么无所谓,只要不会影响我们的分类结果就行,这个字是个中性词,所以不会对情感分类有什么影响。

sequence_length = 62
#对输入数据进行预处理,主要是对句子用索引表示且对句子进行截断与padding,将填充使用”把“来。
def tokenizer():
    inputs = []
    sentence_char = [i.split() for i in train_data["comment"]]
    # 将输入文本进行padding
    for index,i in enumerate(sentence_char):
        temp=[word2idx.get(j,pad_id) for j in i]#表示如果词表中没有这个稀有词,无法获得,那么就默认返回pad_id。
        if(len(i)<sequence_length):
            #应该padding。
            for _ in range(sequence_length-len(i)):
                temp.append(pad_id)
        else:
            temp = temp[:sequence_length]
        inputs.append(temp)
    return inputs
data_input = tokenizer()
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

对我们所有的电影评论进行预处理,如果电影评论超过了62个词,那么切掉后面的词,不管了,否则补充”把“字。

在这里插入图片描述

4.准备训练和测试集

导入包

import torch
import torch.nn as nn
import torch.utils.data as Data
import torch.optim as optim
import torch.nn.functional as F
import numpy as np

# device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
device="cpu"
Embedding_size = 50
Batch_Size = 32
Kernel = 3
Filter_num = 10#卷积核的数量。
Epoch = 60
Dropout = 0.5
Learning_rate = 1e-3
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
class TextCNNDataSet(Data.Dataset):
    def __init__(self, data_inputs, data_targets):
        self.inputs = torch.LongTensor(data_inputs)
        self.label = torch.LongTensor(data_targets)
        
    def __getitem__(self, index):
        return self.inputs[index], self.label[index] 
    
    def __len__(self):
        return len(self.inputs)
    
TextCNNDataSet = TextCNNDataSet(data_input, list(train_data["label"]))
train_size = int(len(data_input) * 0.2)
test_size = int(len(data_input) * 0.05)
val_size= len(data_input) -train_size-test_size#乘以0.75反而报错,因为那个有取整,所以导致了舍入。
train_dataset,val_dataset,test_dataset = torch.utils.data.random_split(TextCNNDataSet, [train_size,val_size, test_size])

TrainDataLoader = Data.DataLoader(train_dataset, batch_size=Batch_Size, shuffle=True)
TestDataLoader = Data.DataLoader(test_dataset, batch_size=Batch_Size, shuffle=True)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

我们划分了数据集,比例为(0.2,0.75,0.05),哈哈,计算机资源有限,我们只取其中的20%作为训练集,75%的验证集我们直接扔了,不要,我们不做验证。

在这里插入图片描述

5.加载词向量模型Word2vec

没有gensim包的请自行下载,很容易的。

from gensim.models import keyedvectors
w2v=keyedvectors.load_word2vec_format(os.path.join(root_path,"wiki_word2vec_50.bin"),binary=True)

w2v[["的","在"]]

  • 1
  • 2
  • 3
  • 4
  • 5

在这里插入图片描述
前面那个字是50维的,后面那个字也是50维的。
不过,并不是所有的字或词都可以被w2v翻译成词向量,必须之前训练w2v时经历过的。我们可以查看可以翻译那些字和词:

w2v.key_to_index
  • 1

在这里插入图片描述
如果我们给的词不在这个模型的词表里面,我们的词向量模型翻译时会报错。而且,我试过,有一些我们电影评论中的词这个词向量模型真的没有收录,所以很麻烦,我直接随机初始化了一个50维的向量。

#用于补充无法转为向量的词,随机分配。不然会报错。
for i in range(len(vocab)):
    try:
        w2v[vocab_l[i]]=w2v[vocab[i]]
    except Exception as e:
        w2v[vocab_l[i]]=np.random.randn(50,)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

6.定义网络

图1 卷积层
图2 全连接层
def word2vec(x):
    #x:batch_size,sequence_length
    #-》x:batch_size,sequence_length,embedding_size
    #x是以编号的形式来反映的,所以需要将其翻译一下。
    x2v=np.ones((len(x),x.shape[1],Embedding_size))
    for i in range(len(x)):
#         seqtext=[idx2char[j.item()] for j in x[i]]
        x2v[i]=w2v[[idx2word[j.item()] for j in x[i]]]
    return torch.tensor(x2v).to(torch.float32)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
#使用word2vec版本的。
num_classs = 2#2分类问题。

class TextCNN(nn.Module):
    def __init__(self):
        super(TextCNN, self).__init__()
#         self.W = nn.Embedding(vocab_size, embedding_dim=Embedding_size)
        out_channel = Filter_num #可以等价为通道的解释。
        self.conv = nn.Sequential(
                    nn.Conv2d(1, out_channel, (2, Embedding_size)),#卷积核大小为2*Embedding_size,默认当然是步长为1
                    nn.ReLU(),
                    nn.MaxPool2d((sequence_length-1,1)),
        )
        self.dropout = nn.Dropout(Dropout)
        self.fc = nn.Linear(out_channel, num_classs)
    
    def forward(self, X):
        batch_size = X.shape[0]
        #x:batch_size*seq_len
        embedding_X =  word2vec(X)
        # batch_size, sequence_length, embedding_size
        embedding_X = embedding_X.unsqueeze(1)
        # batch_size, 1,sequence_length, embedding_size
        conved = self.conv(embedding_X)
        #batch_size,10,seq_len-1,1
        #batch_size,10,seq_len-1,1
        #batch_size,10,1,1########直接被maxpooliing了,从一个序列变成一个向量,表示将整个句子选出一个最关键的情感分类词来。
        conved = self.dropout(conved)
        flatten = conved.view(batch_size, -1)
        # [batch_size, 10]
        output = self.fc(flatten)
        #2分类问题,往往使用softmax,表示概率。
        return F.log_softmax(output)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33

7.训练网络

下面没有什么好说的,基本都是模板,都这么做,大同小异。

model = TextCNN().to(device)
optimizer = optim.Adam(model.parameters(),lr=Learning_rate)

def binary_acc(pred, y):
    """
    计算模型的准确率
    :param pred: 预测值
    :param y: 实际真实值
    :return: 返回准确率
    """
    correct = torch.eq(pred, y).float()
    acc = correct.sum() / len(correct)
    return acc.item()

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
def train():
    avg_acc = []
    model.train()
    for index, (batch_x, batch_y) in enumerate(TrainDataLoader):
        batch_x, batch_y = batch_x.to(device), batch_y.to(device)
        pred = model(batch_x)
        loss = F.nll_loss(pred, batch_y)
        acc = binary_acc(torch.max(pred, dim=1)[1], batch_y)
        avg_acc.append(acc)
        optimizer.zero_grad()
        loss.backward()
        optimizer.step()
    avg_acc = np.array(avg_acc).mean()
    return avg_acc
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
# Training cycle
model_train_acc, model_test_acc = [], []
for epoch in range(Epoch):
    train_acc = train()
    print("epoch = {}, 训练准确率={}".format(epoch + 1, train_acc))
    model_train_acc.append(train_acc)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

在这里插入图片描述
训练了20多分钟吧。

8.测试网络和可视化

def evaluate():
    """
    模型评估
    :param model: 使用的模型
    :return: 返回当前训练的模型在测试集上的结果
    """
    avg_acc = []
    model.eval()  # 进入测试模式
    with torch.no_grad():
        for x_batch, y_batch in TestDataLoader:
            x_batch, y_batch = x_batch.to(device), y_batch.to(device)
            pred = model(x_batch)
            acc = binary_acc(torch.max(pred, dim=1)[1], y_batch)
            avg_acc.append(acc)
    return np.array(avg_acc).mean()
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
evaluate()
  • 1

在这里插入图片描述
在测试集上68%的准确率,还行吧。

下面绘制我们的模型在训练过程中对训练数据的拟合程度。

import matplotlib.pyplot as plt
plt.plot(model_train_acc)
plt.ylim(ymin=0.5, ymax=0.8)
plt.title("The accuracy of textCNN model")
plt.show()
  • 1
  • 2
  • 3
  • 4
  • 5

在这里插入图片描述

9.总结

我觉得改进在于一开始就可以将数据集变成25%,然后在这25%的数据集上建立词表,从而词表会更小,而且评论长度可能会因此变小,比62小。

这样,就会有更少的”把“,评论更加真实,噪音更小了。

而且由于词表更小了,那么词向量w2v不用填充那么多随机的向量,肯定会更精确。

然后25%的数据集就划分dataloader比例为(0.8,0,0.2)。

我觉得这个效果肯定好很多。

最后,本文参考了这位博主的文章,我很喜欢这个博主:http://www.hengsblog.com/2021/02/14/TextCnn_base/

历史:

1.Pytorch+CNN+MNIST手写数字识别实战
2.Pytorch+CNN+猫狗分类实战

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/你好赵伟/article/detail/794509
推荐阅读
相关标签
  

闽ICP备14008679号