基于 Text-CNN 的情感分析(文本分类)----概念与应用

基于Text-CNN情感分析

大家都知道,CNN(Convolutional Neural Network) 是深度学习中十分重要的一种神经网络,一般用于图像的处理。

但是也存在一种 CNN 的变体 Text-CNN 用来处理文本信息,本次我们将基于 Text-CNN 实现来实现评论情感分析(文本分类),本次实验属于评论三分类(好中差评)研究,数据集共有17万多条京东的手机评论数据,经过实验发现基于Text-CNN模型三分类的效果在测试集的准确度可达到77%左右。

卷积的基本概念

在实现情感分类之前,我们先来了解下一维卷积是如何工作的。需要注意的是,这只是基于互相关运算的二维卷积的特例。

在这里插入图片描述

如上图所示,在一维情况下,卷积窗口在输入张量上从左向右滑动。在滑动期间,卷积窗口中某个位置包含的输入子张量(例如上图的 0 0 0 1 1 1)和核张量(例如上图中的 1 1 1 2 2 2)按元素相乘。这些乘法的总和在输出张量的相应位置给出单个标量值(例如上图中的 0 × 1 + 1 × 2 = 2 0 \times 1 + 1 \times 2 = 2 0×1+1×2=2)。

对于任何具有多个通道的一维输入,卷积核需要具有相同数量的输入通道。然后,对于每个通道,对输入的一维张量和卷积核的一维张量执行互相关运算,将所有通道上的结果相加以产生一维输出张量。 下图演示了具有3个输入通道的一维互相关操作。

在这里插入图片描述

这里需要注意,多输入通道的一维互相关等同于单输入通道的二维互相关。 如下图:

在这里插入图片描述

Text-CNN的核心思想

Text-CNN模型继承了CNN模型的思想,继续使用卷积操作和最大时间汇聚。其中卷积操作是为了捕获不同数目的相邻词元之间的局部特征,最大时间汇聚是为了对采取的特征进行压缩操作

Text-CNN定义了多个卷积层操作,用以不同程度提取词元序列中的信息,最后将所有汇聚层输出的标量连结为向量,再使用全连接层将连结后的向量转换为输出类别。

其中,对于具有由 d d d 维向量表示的 n n n 个词元的单个文本序列,输入张量的宽度、高度和通道数分别为 n 、 1 、 d n、1、d n1d。模型运行图如下:

在这里插入图片描述

上图通过一个具体的例子说明了Text-CNN的模型架构。输入是具有11个词元的句子,其中每个词元由6维向量表示。因此,我们有一个宽度为11的6通道输入。定义两个宽度为2和4的一维卷积核,分别具有4个和5个输出通道。它们产生4个宽度为 11 − 2 + 1 = 10 11 - 2 + 1 = 10 112+1=10 的输出通道和5个宽度为 11 − 4 + 1 = 8 11 - 4 + 1 = 8 114+1=8的输出通道。尽管这9个通道的宽度不同,但最大时间汇聚层给出了一个连结的9维向量,该向量最终被转换为用于二元情感预测的2维输出向量。

实现

接下来我们将会一步步实现Text-CNN实现的文本分类模型。

数据预处理

首先导入一些必要的库包。

from d2l import torch as d2l
import csv
import torch
from torch import nn
import pandas as pd
from gensim.models import Word2Vec
from collections import Counter
from tqdm import tqdm

读取评论文件,并返回tokens评论语句和labels标签信息。

def read_file(file_name):
    
    all_data = None
    with open(file_name, 'r', encoding='UTF-8') as f:
        reader = csv.reader(f)
        # 读取分词后的评论信息和对应的标签
        all_data = [[comment[2], int(comment[1])] for comment in reader]
    
    # 打乱数据集信息
    random.shuffle(all_data)
    
    # 去重操作
    pd_data = pd.DataFrame(all_data)
    same_comment_sum = pd_data.duplicated().sum()
    if same_comment_sum > 0:
        pd_data = pd_data.drop_duplicates()
    all_data = pd_data.values.tolist()          # 再次转化为列表数据
    
    
    tokens = [sentence[0].strip().split(' ') for sentence in all_data ]
    labels = [sentence[1] for sentence in all_data]
    
    # tokens列表(二维数组,每个元素为一个列表(句子分词后的词元列表))
    # labels列表,代表评价信息
    return tokens, labels

读取tokens和labels信息。

tokens, labels = read_file('./file/comments.csv')

现在输出我们本次测试的数据集大小(大约17万评论数据)

print('tokens num:', len(tokens), '\tlabels num:', len(labels))
tokens num: 171946 	labels num: 171946

输出前10个评论信息和对应的标签信息

print(tokens[:10], labels[:10])
[['发热', '特别', '摄像头', '苹果', '带套', '都', '烫', '看', '摄像头', '位置', '发热', '玩游戏', '真', '想象'], ['一段时间', '正品', '新机', '开机', '速度', '运行', '速度', '都', '不错', '看中', '麒麟', '芯片', '买', '照相', '效果', '拍', '美美', '哒'], ['拍照', '效果', '系列', '感觉', '真实', '画质', '真实', '特色', '屏幕', '不错', '触感', '很棒', '待机时间', '充电', '待机', '时', '长', '够用', '外形', '外观', '光圈', '看起来', '有点', '土', '屏幕', '音效', '满意', '运行', '速度', '不错'], ['拿到', '手机', '没', '来得及', '评价', '不错', '外观', '内在', '都', '特别', '棒', '喜欢', '款', '手机', '舒服', '不错', '快递', '小哥', '特别', '给力', '京东', '购物', '服务', '都', '特别', '棒', '需要'], ['快充', '假', '还', '另配', '充电器'], ['外形', '外观', '比米', '轻薄', '手感', '不错', '运行', '速度', '骁龙', '旗舰', '中', '旗舰', '大核', '速度', '提升', '很大', '屏幕', '音效', '屏幕', '最', '吸引', '购买', '显示', '效果', '细腻', '赫兹', '太丝', '滑', '完美'], ['提前', '买', '没', '优惠', '还', '送', '耳机', '非常', '不开森'], ['京东', '最差', '购物', '体验', '京东', '自营', '买', '离着', '几十公里', '都', '没能', '预计', '送达', '时间', '送到', '物流配送', '真不知道', '京东', '自营', '买', '都', '预计', '时间', '当天', '送到', '现在', '物流配送', '差', '物流配送', '想', '说', '差', '差差'], ['拍照', '效果', '清楚', '外形', '外观', '漂亮', '待机时间', '目前', '还', '知道', '挺久', '回来', '充了', '电', '目前', '正常', '特色', '手感', '不错'], ['外形', '外观', '差差', '屏幕', '音效', '差差', '拍照', '效果', '差差', '运行', '速度', '差差', '待机时间', '差差', '客服', '态度恶劣']] [0, 2, 2, 2, 1, 2, 0, 1, 2, 0]
批量处理操作–填充与截断

为什么要对一些句子进行填充和截断的操作呢?

因为在深度学习中,为了提高模型的训练速度,所以要经常进行批量的数据操作,此时需要保证每个评论语句的长度一致,此时需要对句子进行填充或截断操作,使其达到一致的长度。

现在,我们来统计最常出现的评论长度。

# 获取所有评论长度的频率统计
counter = Counter([len(sentence) for sentence in tokens])

# 对句子长度出现的频率进行排序
counter_freq_dec = sorted(counter.items(), key=lambda x:x[1], reverse=True)

# 输出评论语句长度频率最高的前20个长度
print(counter_freq_dec[:20])
[(5, 9633), (6, 9499), (7, 8957), (4, 8287), (8, 7821), (9, 7086), (10, 6455), (11, 6117), (12, 5714), (13, 5361), (3, 5165), (14, 4986), (15, 4813), (16, 4595), (17, 4326), (18, 4194), (20, 4172), (19, 4111), (21, 3969), (22, 3729)]

从以上我们可以看出,短句子出现的频率非常高,所以我们不能填充过多,所以这里选取填充到64个长度。

"""
填充或截断操作函数:
    tokens---->需要进行处理的评论语句
    padding_token---->需要填充的tokens,一般为 '<pad>'
    length---->所有评论语句的长度
"""
def truncation_and_padding(tokens, padding_token, length):
    
    for i in range(len(tokens)):
        if len(tokens[i]) < length:
            # 若长度未能达到length长度时,进行填充操作
            tokens[i] += (length - len(tokens[i])) * [padding_token]
        else:
            # 若长度超出length长度,进行截断操作
            tokens[i] = tokens[i][:length]
    
    # 返回填充或截取后的tokens信息
    return tokens
# 调用函数,填充或截断tokens信息
padding_token = truncation_and_padding(tokens, '<pad>', 64)

现在我们输出前两个句子,如下

print(padding_token[:2])
[['发热', '特别', '摄像头', '苹果', '带套', '都', '烫', '看', '摄像头', '位置', '发热', '玩游戏', '真', '想象', '<pad>', '<pad>', '<pad>', '<pad>', '<pad>', '<pad>', '<pad>', '<pad>', '<pad>', '<pad>', '<pad>', '<pad>', '<pad>', '<pad>', '<pad>', '<pad>', '<pad>', '<pad>', '<pad>', '<pad>', '<pad>', '<pad>', '<pad>', '<pad>', '<pad>', '<pad>', '<pad>', '<pad>', '<pad>', '<pad>', '<pad>', '<pad>', '<pad>', '<pad>', '<pad>', '<pad>', '<pad>', '<pad>', '<pad>', '<pad>', '<pad>', '<pad>', '<pad>', '<pad>', '<pad>', '<pad>', '<pad>', '<pad>', '<pad>', '<pad>'], ['一段时间', '正品', '新机', '开机', '速度', '运行', '速度', '都', '不错', '看中', '麒麟', '芯片', '买', '照相', '效果', '拍', '美美', '哒', '<pad>', '<pad>', '<pad>', '<pad>', '<pad>', '<pad>', '<pad>', '<pad>', '<pad>', '<pad>', '<pad>', '<pad>', '<pad>', '<pad>', '<pad>', '<pad>', '<pad>', '<pad>', '<pad>', '<pad>', '<pad>', '<pad>', '<pad>', '<pad>', '<pad>', '<pad>', '<pad>', '<pad>', '<pad>', '<pad>', '<pad>', '<pad>', '<pad>', '<pad>', '<pad>', '<pad>', '<pad>', '<pad>', '<pad>', '<pad>', '<pad>', '<pad>', '<pad>', '<pad>', '<pad>', '<pad>']]

使用word2vec对所有的词元信息进行编码操作,生成词嵌入矩阵。

word2vec = Word2Vec(padding_token, min_count=1)         # 生成的词嵌入模型词最小频率为1

将tokens列表转化为词嵌入字典中下标信息。

def corpus(padding_token, word2vec):
    
    corpus_tokens = []
    
    # 将所有的tokens信息转化为在 词嵌入字典中对应的 下标信息(索引信息)
    for sentence in padding_token:
        corpus_tokens.append([word2vec.wv.get_index(token) for token in sentence])
    
    # 返回corpus_tokens词元索引的评论信息
    return corpus_tokens
# 进行转化操作
corpus_tokens = corpus(padding_token, word2vec)

输出前2个corpus_tokens信息,即前两条由词元索引组成的评论信息

print(corpus_tokens[:2])
[[57, 30, 86, 39, 6650, 7, 209, 48, 86, 943, 57, 78, 120, 282, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [178, 164, 200, 159, 6, 9, 6, 7, 11, 935, 617, 369, 4, 298, 12, 93, 1285, 580, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]]
拆分训练集与测试集

现在我们以 6:4 比例划分训练集与测试集,分别用于对模型进行训练与测试。

split_len = int(len(corpus_tokens) * 0.6)          # 设定切分线

# 划分训练集和测试集的评论信息和标签信息 
train_comments, train_labels = corpus_tokens[:split_len], labels[:split_len]
test_comments, test_labels = corpus_tokens[split_len:], labels[split_len:]

print('train_set_len:', len(train_comments), '\t', len(train_labels))
print('test_set_len:', len(test_comments), '\t', len(test_labels))
train_set_len: 103167 	 103167
test_set_len: 68779 	 68779
定义Text-CNN模型
"""
定义Text-CNN模型
"""
class TextCNN(nn.Module):
    def __init__(self, vocab_size, embed_size, kernel_sizes, num_channels, embed, **kwargs):
        super(TextCNN, self).__init__(**kwargs)
        
        # 定义embedding词嵌入模型,并将word2vec生成的词向量嵌入进来
        self.embedding = nn.Embedding(vocab_size, embed_size)
        self.embedding.from_pretrained(torch.tensor(embed))
        
        # 此嵌入层同上
        self.constant_embedding = nn.Embedding(vocab_size, embed_size)
        self.constant_embedding.from_pretrained(torch.tensor(embed))
        
        # 暂退法减少模型复杂度,并定义全连接层decoder
        self.dropout = nn.Dropout(0.5)
        self.decoder = nn.Linear(sum(num_channels), 3)
        
        # 最大时间汇聚层没有参数,因此可以共享此实例
        self.pool = nn.AdaptiveAvgPool1d(1)
        self.relu = nn.ReLU()
        
        # 创建多个一维卷积层
        self.convs = nn.ModuleList()
        
        # 追加多个一维卷积层,用以提取文本的不同特征信息
        for c, k in zip(num_channels, kernel_sizes):
            # 输入通道数为 2 * embed_size, 输出通道数为 c, 卷积核为 k
            self.convs.append(nn.Conv1d(2 * embed_size, c, k))
    
    def forward(self, inputs):
        # 沿着向量维度将两个嵌入层连结起来,
        # 每个嵌入层的输出形状都是(批量大小,词元数量,词元向量维度)连结起来
        embeddings = torch.cat((
            self.embedding(inputs), self.constant_embedding(inputs)), dim=2)
        
        # 根据一维卷积层的输入格式,重新排列张量,以便通道作为第2维
        embeddings = embeddings.permute(0, 2, 1)
        
        # 每个一维卷积层在最大时间汇聚层合并后,获得的张量形状是(批量大小,通道数,1)
        # 删除最后一个维度并沿通道维度连结
        encoding = torch.cat([
            torch.squeeze(self.relu(self.pool(conv(embeddings))), dim=-1)
            for conv in self.convs], dim=1)
        
        # 使用全连接层输出概率分布,并返回概率结果
        outputs = self.decoder(self.dropout(encoding))
        return outputs
设计模型定义与训练参数

设计初始化模型参数。

# 词嵌入维度为100,卷积核为[3, 4, 5], 输出通道数为[100, 100, 100]
embed_size, kernel_sizes, nums_channels = 100, [3, 4, 5], [100, 100, 100]

# 使用GPU进行训练
devices = d2l.try_gpu()

# 定义text-cnn网络模型,并存入GPU,并初始化模型参数
net = TextCNN(len(word2vec.wv.vectors), embed_size, kernel_sizes, nums_channels, word2vec.wv.vectors)
net = net.to(device=devices)
def init_weights(m):
    if type(m) in (nn.Linear, nn.Conv1d):
        nn.init.xavier_uniform_(m.weight)
net.apply(init_weights)
TextCNN(
  (embedding): Embedding(63332, 100)
  (constant_embedding): Embedding(63332, 100)
  (dropout): Dropout(p=0.5, inplace=False)
  (decoder): Linear(in_features=300, out_features=3, bias=True)
  (pool): AdaptiveAvgPool1d(output_size=1)
  (relu): ReLU()
  (convs): ModuleList(
    (0): Conv1d(200, 100, kernel_size=(3,), stride=(1,))
    (1): Conv1d(200, 100, kernel_size=(4,), stride=(1,))
    (2): Conv1d(200, 100, kernel_size=(5,), stride=(1,))
  )
)

定义模型训练的学习率,迭代次数,Adam优化器,交叉熵损失函数。

# 定义学习率与迭代次数
lr, num_epochs = 0.0001, 20
trainer = torch.optim.Adam(net.parameters(), lr=lr)        # 优化器
loss = nn.CrossEntropyLoss(reduction="none")               # 损失函数
训练并评估模型

定义评估模型与训练模型的函数,可视化训练过程。

"""
评估函数,用以评估数据集在神经网络下的精确度
"""
def evaluate(net, comments_data, labels_data):
    
    sum_correct, i = 0, 0
    
    while i <= len(comments_data):
        
        # 批量计算正确率,一次计算64个评论信息
        comments = comments_data[i: min(i + 64, len(comments_data))]
        
        tokens_X = torch.tensor(comments).to(device=devices)

        res = net(tokens_X)                                          # 获得到预测结果

        y = torch.tensor(labels_data[i: min(i + 64, len(comments_data))]).reshape(-1).to(device=devices)

        sum_correct += (res.argmax(axis=1) == y).sum()              # 累加预测正确的结果
        i += 64

    return sum_correct/len(comments_data)                           # 返回(总正确结果/所有样本),精确率



"""
训练函数:用以训练模型,并保存最好结果时的模型参数
"""
def train(net, trainer, loss, train_comments, train_labels, test_comments, test_lables,
         num_epochs, devices):
    
    
    max_value = 0.5                       # 初始化模型预测最大精度
    
    # 多次迭代训练模型
    for epoch in tqdm(range(num_epochs)):

        sum_loss, i = 0, 0                # 定义模型损失总和为 sum_loss, 变量 i
        
        while i < len(train_comments):
            
            # 批量64个数据训练模型
            comments = train_comments[i: min(i+64, len(train_comments))]
            
            # X 转化为 tensor
            inputs_X = torch.tensor(comments).to(device=devices)
            # Y 转化为 tensor
            y = torch.tensor(train_labels[i: min(i+64, len(train_comments))]).to(device=devices)
            
            # 将X放入模型,得到概率分布预测结果
            res = net(inputs_X)

            l = loss(res, y)                          # 计算预测结果与真实结果损失
            trainer.zero_grad()                       # 清空优化器梯度
            l.sum().backward()                        # 后向传播
            trainer.step()                            # 更新模型参数信息

            sum_loss += l.sum()                      # 累加损失

            i += 16

        print('loss:\t', sum_loss/len(train_comments))
        
        # 计算训练集与测试集的精度
        train_acc = evaluate(net, train_comments, train_labels)
        test_acc = evaluate(net, test_comments, test_labels)

        # 保存下模型跑出最好的结果
        if test_acc >= max_value:
            max_value = test_acc
            torch.save(net.state_dict(), 'text_cnn.parameters')

        # 输出训练信息
        print('-epoch:\t', epoch+1,
              '\t-loss:\t', sum_loss / len(train_comments),
              '\ttrain-acc:', train_acc,
              '\ttest-acc:', test_acc)
    

开始训练模型

train(net, trainer, loss, train_comments, train_labels, test_comments, test_labels, num_epochs, devices)

在这里插入图片描述

训练结果如上图,可见在测试集上能够达到77%的精确度。

结语

最近有些懈怠,混了些日子,有些自责,不过希望慢慢调整过来,充实起来。

靡不有初,鲜克有终。

加油!

  • 8
    点赞
  • 64
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 13
    评论
### 回答1: 电影评论情感分类是一项重要的自然语言处理任务,旨在自动将电影评论分为正面或负面情感。为了解决这个问题,研究人员已经开发了各种机器学习模型,其中卷积神经网络text-cnn是一种有效的模型。 TensorFlow是一个强大的深度学习库,提供了text-cnn模型的实现。text-cnn模型由多个卷积层和全局最大池化层组成,每个卷积层用于提取文本中的特定特征,而全局最大池化层则用于提取最具代表性的特征。最终,这些特征将被用于分类任务,通过一个全连接层来实现。 与其他情感分类模型相比,text-cnn模型具有许多优点。首先,它可以自适应不同长度的文本输入,并且不需要手动提取特征。其次,text-cnn模型具有较高的分类准确率,并且可以在大规模数据上进行训练,以提高其性能。最后,TensorFlow提供了一个简单的接口来实现text-cnn模型,并且提供了丰富的调试和可视化工具,使得模型的训练和评估变得更加容易。 总之,卷积神经网络text-cnn模型是一种高效、准确的情感分类模型,结合TensorFlow库的支持,可以有效地应用于电影评论等自然语言处理任务中。 ### 回答2: 电影评论情感分类是一类自然语言处理任务,它的目标是对一段文本进行情感分类,预测这段文本表达的情感是正面的(positive)还是负面的(negative)。在实践中,卷积神经网络(CNN)已经被广泛应用于情感分类,其中text-cnn模型是最常用的一种。 Text-cnn模型在情感分类任务中的表现优秀,它将文本看作是一种二维结构,其中一个维度是词语,另一个维度是嵌入矩阵中的向量。文本中的词被编码为嵌入向量,并且这些嵌入向量被视为图像的像素。在text-cnn模型中,多个不同大小的卷积核被用来通过卷积操作提取出文本的局部特征。这些局部特征被压缩成一个全局特征向量,并通过一个全连接层进行分类器预测。 TensorFlow是实现text-cnn模型的流行工具之一,它是一个开源的机器学习框架,提供了广泛的API和工具来创建高效的深度学习模型。TensorFlow可以轻松地构建text-cnn模型,而且具有内置的优化器和损失函数,它可以加速模型训练和优化。 总的来说,text-cnn模型是一个强大的情感分类器,它已经在几个领域得到了成功的应用。在使用TensorFlow实现text-cnn模型时,需要注意模型的超参数调整,以及数据预处理和特征工程的优化,这些都可以影响模型的性能和泛化能力。 ### 回答3: 电影评论情感分类是NLP领域的一个基础应用问题,通过对文本进行情感分类可以帮助我们更好地理解用户心理、市场需求等诸多方面。卷积神经网络(CNN)是目前NLP领域应用广泛的深度学习算法,它能够对输入的多维矩阵进行特征提取,逐层降维,最终将特征表示为一维向量。 Text-CNNCNN在NLP领域的应用,它主要通过卷积层和池化层对文本进行特征提取和降维。卷积层通过提取矩阵中的局部特征,池化层通过按照一定的规则对特征进行采样,最终形成一个固定长度的向量作为文本的表示。在情感分类任务中,Text-CNN可以通过对输入的文本进行卷积和池化操作,得到文本的固定长度特征向量,进而输出文本的情感类别。 TensorFlow是当前最受欢迎的深度学习框架之一,它提供了丰富的API和工具,能够方便地构建并训练Text-CNN模型。在构建Text-CNN模型时,首先需要进行文本的预处理,将文本转换为数字表示,然后使用TensorFlow对模型进行定义和训练。 总之,电影评论情感分类是NLP领域一个重要的应用问题,采用Text-CNN模型可以准确有效地对文本进行情感分类,而TensorFlow提供了一个便捷的框架和工具,用于构建和训练Text-CNN模型。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Gaolw1102

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值