情感分析

学习任务

  文本情感分析(Sentiment Analysis)是自然语言处理(NLP)方法中常见的应用,也是一个有趣的基本任务,尤其是以提炼文本情绪内容为目的的分类,它是对带有情感色彩的主观性文本进行分析、处理、归纳和推理的过程。本章介绍情感分析中的情感极性(倾向)分析。所谓情感极性分析,指的是对文本进行褒义、贬义、中性的判断。在大多应用场景下,只分为两类。例如对于“喜爱”和“厌恶”这两个词,就属于不同的情感倾向。

知识点

自然语言处理、RNN/LSTM模型

1.问题描述

  本项目实现微博评论数据的探查,重点掌握pandas数据分析工具的使用。对数据可视化,重点掌握seaborn,pyecharts 可视化工具的实用。掌握卷积神经网络RNN/LSTM模型原理,掌握PyTorch 中关于自然语言处理torchtext库使用,特别是torchtext 中重点工具BucketIterator使用,torch.nn模型使用。通过本项目的实践,希望学员们可以掌握情感分析RNN/LSTM模型构建和训练的方法,并使用评论情感分析模型预测文本所反映的情感。

2.数据描述

NLP&CC 2013: 该数据集是xml格式,有大概3w条数据,17w条数据作为测试样本,分为none、like、disgust、anger、happiness、fear、sadness、surprise等几个类别,其中none又占绝大多数。

如下图所示: 1562564298530.png

2.1 加载数据

在使用python处理数据的过程中,经常需要做一些数据读取和写入的工作,比较常用的数据格式是csv,csv文件是一种以逗号分割字符的文件形式。读写csv文件常用的有两种方式,一种是使用csv,一种是使用pandas。

import pandas as pd
import jieba

train = pd.read_csv('data/train.csv')
val = pd.read_csv('data/val.csv')

train.head()

数据预处理和数据封装 TorchText 的数据预处理流程为:

定义样本的处理操作。—> torchtext.data.Field

  • 加载 corpus (都是 string)—> torchtext.data.Datasets
  • 在Datasets 中,torchtext 将 corpus 处理成一个个的 torchtext.data.Example 实例
  • 创建 torchtext.data.Example 的时候,会调用 field.preprocess 方法
  • 创建词汇表, 用来将 string token 转成 index —> field.build_vocab()
  • 词汇表负责:string token —> index, index —> string token ,string token —> word vector
  • 将处理后的数据 进行 batch 操作。—> torchtext.data.Iterator
  • 将 Datasets 中的数据 batch 化
  • 其中会包含一些 pad 操作,保证一个 batch 中的 example 长度一致 在这里将 string token 转化成index。

tokenization,vocab, numericalize, embedding lookup 和 TorchText 数据预处理阶段的对应关系是:

  • tokenization —> Dataset 的构造函数中,由 Field 的 tokenize 操作
  • vocab —> field.build_vocab 时,由 Field 保存 映射关系
  • numericalize —> 发生在 iterator 准备 batch 的时候,由 Field 执行 numericalize 操作
  • embedding lookup —> 由 pytorch Embedding Layer 提供此功能。
# 导入torch库
import torch
# 导入torchtext库,主要自然语言处理预处理
from torchtext.data import Field, TabularDataset
# 加载外部词向量
from torchtext.vocab import Vectors

# 引入自定义配置
from configs import BasicConfigs
# 引入工具类
from utils import chi_tokenizer

config = BasicConfigs()
# 定义字段 (TEXT/LABEL)
## include_lengths=True 为了方便后续使用torch pack_padded_sequence
## chi_tokenizer 分词器,主要对我们的每个句子进行切分
TEXT = Field(tokenize=chi_tokenizer, include_lengths=True)

LABEL = Field(eos_token=None, pad_token=None, unk_token=None)

## torchtext中于文件配对关系
fields = [('data', TEXT), ('label', LABEL)]

# 加载数据
## 注意skip_header = True
train_data, val_data = TabularDataset.splits(path='data',
                                             train='train.csv',
                                             validation='val.csv',
                                             format='csv',
                                             fields=fields,
                                             skip_header=True)

## 数据记录数统计
print('train total_count = ', len(train_data.examples))
print('val total_count = ', len(val_data.examples))


from torchtext.data import BucketIterator
train_iter = BucketIterator(train_data,
               batch_size=config.batch_size,
               sort_key=lambda x:len(x.data),
               sort_within_batch=True,
               shuffle=True,
               device=config.device)

val_iter = BucketIterator(val_data,
               batch_size=config.batch_size,
               sort_key=lambda x:len(x.data),
               sort_within_batch=True,
               shuffle=False,
               device=config.device)

最后一步数据的准备是创建iterators。每个itartion都会返回一个batch的examples。 我们会使用BucketIterator。BucketIterator会把长度差不多的句子放到同一个batch中,确保每个batch中不出现太多的padding。 严格来说,我们这份notebook中的模型代码都有一个问题,也就是我们把也当做了模型的输入进行训练。更好的做法是在模型中把由产生的输出给消除掉。 如果我们有GPU,还可以指定每个iteration返回的tensor都在GPU上。

使用以下Python脚本,可以统计出数据集中的情感分布以及评论句子长度分布。

import pandas as pd
import matplotlib.pyplot as plt
from matplotlib import font_manager
from itertools import accumulate

# 设置matplotlib绘图时的字体
my_font = font_manager.FontProperties(fname="C:\Windows\Fonts\Songti.ttc")

# 统计句子长度及长度出现的频数
df = pd.read_csv('./corpus.csv')
print(df.groupby('label')['label'].count())

df['length'] = df['evaluation'].apply(lambda x: len(x))
len_df = df.groupby('length').count()
sent_length = len_df.index.tolist()
sent_freq = len_df['evaluation'].tolist()

# 绘制句子长度及出现频数统计图
plt.bar(sent_length, sent_freq)
plt.title("句子长度及出现频数统计图", fontproperties=my_font)
plt.xlabel("句子长度", fontproperties=my_font)
plt.ylabel("句子长度出现的频数", fontproperties=my_font)
plt.savefig("./句子长度及出现频数统计图.png")
plt.close()

# 绘制句子长度累积分布函数(CDF)
sent_pentage_list = [(count/sum(sent_freq)) for count in accumulate(sent_freq)]

# 绘制CDF
plt.plot(sent_length, sent_pentage_list)

# 寻找分位点为quantile的句子长度
quantile = 0.91
#print(list(sent_pentage_list))
for length, per in zip(sent_length, sent_pentage_list):
    if round(per, 2) == quantile:
        index = length
        break
print("\n分位点为%s的句子长度:%d." % (quantile, index))

# 绘制句子长度累积分布函数图
plt.plot(sent_length, sent_pentage_list)
plt.hlines(quantile, 0, index, colors="c", linestyles="dashed")
plt.vlines(index, 0, quantile, colors="c", linestyles="dashed")
plt.text(0, quantile, str(quantile))
plt.text(index, 0, str(index))
plt.title("句子长度累积分布函数图", fontproperties=my_font)
plt.xlabel("句子长度", fontproperties=my_font)
plt.ylabel("句子长度累积频率", fontproperties=my_font)
plt.savefig("./句子长度累积分布函数图.png")
plt.close()

句子长度累积分布函数图如下:

9419034-08563e119c9e8092.png

可以看到,大多数样本的句子长度集中在1-200之间,句子长度累计频率取0.91分位点,则长度为183左右。

2.2 分词模块jieba

“结巴”分词是一个Python 中文分词组件,参见https://github.com/fxsjy/jieba, 可以对中文文本进行分词、词性标注、关键词抽取等功能,并且支持自定义词典。 jieba的主要功能有:

  1. 分词
  2. 添加自定义词典
  3. 关键字提取
  4. 词性标注

我们主要使用它的分词功能,HMM模型原理和viterbi算法原理可参阅结巴网页。

jieba支持三种分词模式:

  1. 精确模式,适合文本分析。
  2. 全模式,把所有可以成词的词语都扫描出来,不能解决歧义。
  3. 搜索引擎模式,在精确模式的基础上对长词再次切分,适合搜索引擎分词。 分词功能调用方法 jieba.cut 接受三个输入参数: 需要分词的字符串;cut_all 参数用来控制是否采用全模式;HMM 参数用来控制是否使用 HMM 模型

jieba.cut 以及 jieba.cut_for_search 返回的结构都是一个可迭代的 generator,可以使用 for 循环来获得分词后得到的每一个词语(unicode),或者用

jieba.lcut 以及 jieba.lcut_for_search 直接返回 list

jieba.Tokenizer(dictionary=DEFAULT_DICT) 新建自定义分词器,可用于同时使用不同词典。jieba.dt 为默认分词器,所有全局分词相关函数都是该分词器的映射。

代码示例:

# encoding=utf-8
import jieba

seg_list = jieba.cut("我来到北京清华大学", cut_all=True)
print("Full Mode: " + "/ ".join(seg_list))  # 全模式

seg_list = jieba.cut("我来到北京清华大学", cut_all=False)
print("Default Mode: " + "/ ".join(seg_list))  # 精确模式

seg_list = jieba.cut("他来到了网易杭研大厦")  # 默认是精确模式
print(", ".join(seg_list))

seg_list = jieba.cut_for_search("小明硕士毕业于中国科学院计算所,后在日本京都大学深造")  # 搜索引擎模式
print(", ".join(seg_list))

输出:

【全模式】: 我/ 来到/ 北京/ 清华/ 清华大学/ 华大/ 大学
【精确模式】: 我/ 来到/ 北京/ 清华大学
【新词识别】:他, 来到, 了, 网易, 杭研, 大厦    (此处,“杭研”并没有在词典中,但是也被Viterbi算法识别出来了)
【搜索引擎模式】: 小明, 硕士, 毕业, 于, 中国, 科学, 学院, 科学院, 中国科学院, 计算, 计算所, 后, 在, 日本, 京都, 大学, 日本京都大学, 深造

3.主要网络模型介绍

3.1标准RNN 模型

下面我们尝试把模型换成一个recurrent neural network (RNN)。RNN经常会被用来encode一个sequence

我们使用最后一个hidden state h T h_Th 来表示整个句子。 然后我们把h T h_Th 通过一个线性变换f ff,然后用来预测句子的情感。 20200211203612187.png 使用train和evaluate 创建模型 自定义RNN 模型,在pytorch中需要继承nn.Model,同时在__init__ 方法中定义layer 这里,我们定义三层 an embedding layer,our RNN,and a linear layer

embedding layer:对每个word 进行one-hot 编码处理,spare vector-> dense vector . [sentence length, batch size, embedding dim] RNN:获取dense vector and hidden state(prev layers) a linear layer: 最后的hidden state ,通过一个FC,获取最终维度的数据

3.2 LSTM(Long Short-Term Memory)

LSTM出现背景:由于RNN存在梯度消失的问题,很难处理长序列的数据。为了解决RNN存在问题,后续人们对RNN做了改进,得到了RNN的特例LSTM,它可以避免常规RNN的梯度消失,因此在工业界得到了广泛的应用。 LSTM模型是RNN的变体,它能够学习长期依赖,允许信息长期存在。

举个例子来讲:比如人们读文章的时候,人们会根据已经阅读过的内容来对后面的内容进行理解,不会把之前的东西都丢掉从头进行思考,对内容的理解是贯穿的。 传统的神经网络即RNN做不到这一点,LSTM是具有循环的网络,解决了信息无法长期存在的问题,在工业界普遍使用有良好的效果。

2020021120370338.png

LSTM的核心在于单元(细胞)中的状态,也就是上图中最上面的那根线。但是如果只有上面那一条线,那么没有办法实现信息的增加或者删除,所以在LSTM是通过一个叫做的结构实现,门可以选择让信息通过或者不通过。

在理解LSTM时,我们需要理解遗忘门,输入门以及输出门的相关概念以及公式的说明和推导。

LSTM 和 RNN 区别 LSTM(Long Short-Item Memory) : 相比普通的RNN 效果为好。普通RNN 会出现梯度消失问题,而LSTM 主要为了解决梯度消息和梯度爆炸问题。LSTM 能够在更长的序列中有更好的表现。

RNN 只有一个传递状态 h t h_th t​

LSTM有两个传递状态 c t c_tc t​(cell state) 和 h^t (hidden state),其中: c t c_tc t​改变很慢,h t h_th t​在不通节点上改变很大

LSTM内部主要有三个阶段:

忘记阶段。这个阶段主要是对上一个节点传进来的输入进行选择性忘记。简单来说就是会 “忘记不重要的,记住重要的”。

选择记忆阶段。这个阶段将这个阶段的输入有选择性地进行“记忆”。

输出阶段。通过门控状态来控制传输状态,记住需要长时间记忆的,忘记不重要的信息;而不像普通的RNN那样只能够“呆萌”地仅有一种记忆叠加方式。对很多需要“长期记忆”的任务来说,尤其好用。

LSTM相比RNN也存在问题:因为引入了很多内容,导致参数变多,也使得训练难度加大了很多。因此很多时候我们往往会使用效果和LSTM相当但参数更少的GRU来构建大训练量的模型.

3.3 正则化

   为了提升model效果,我们往往会增加大量的参数。结果,带来的问题是:过拟合(在训练数据集上的错误率很 低,但在验证/测试集上错误率高)。

解决方法是采用正则化(regularization),例如:直接丢弃dropout-在前向传递的时候,使得某些神经元权重0,每个前向传递生成一个弱模型。对这些弱模型进行集成,理论上可以解决过拟合问题。

当我们将样本输入到模型中时,将调用forward方法。

每批文本都是一个大小为[sentence length,batch size]的张量。这是一批句子,每个句子都将每个单词转换成一个one-hot向量。

由于one-hot向量,这个张量应该有另一个维度,然而PyTorch很方便地将一个one-hot向量存储为其索引值,即表示一个句子的张量只是该句子中每个标记的索引的张量。将tokens列表转换为索引列表的行为通常称为数字化。

然后,输入批处理(input batch)通过嵌入层得到嵌入值,从而为我们的句子提供密集的向量表示。embedded是一个大小为[sentence length,batch size,hidden dim]的张量。

embedded然后馈送到RNN。在一些框架中,必须将初始隐藏状态 h 0 h_0 h0​输入RNN,但是在PyTorch中,如果没有将初始隐藏状态作为参数传递,则默认为初始是一个全为0的张量。

RNN返回2个张量,输出(output)的大小为[sentence length,batch size,hidden dim]和隐藏(hidden)的大小为[1,batch size,hidden dim]。输出(output)是每个时间步骤的隐藏状态的连接,而隐藏(hidden)只是最终的隐藏状态。

最后,通过线性层fc输入最后一个隐藏状态,hidden,来产生一个预测。

4.实现过程

4.1 pytorch中的LSTM

先来看一下pytorch提供的LSTM的实现 torch.nn.LSTM(args, kwargs)*

官方API: https://pytorch.org/docs/stable/nn.html?highlight=lstm#torch.nn.LSTM

参数

  • input_size
  • hidden_size
  • num_layers
  • bias
  • batch_first
  • dropout
  • bidirectional

输入

  • input (seq_len, batch, input_size)
  • h_0 (num_layers * num_directions, batch, hidden_size)
  • c_0 (num_layers * num_directions, batch, hidden_size)

输出

  • output (seq_len, batch, num_directions * hidden_size)
  • h_n (num_layers * num_directions, batch, hidden_size)
  • c_n (num_layers * num_directions, batch, hidden_size)

4.2网络模型定义

  在这里,我们将先使用Pytorch的原生API,搭建一个BiLSTM。处理序列的基本步骤如下:

  1. 准备torch.Tensor格式的data=x,label=y,length=L,等等
  2. 数据根据length排序,由函数sort_batch完成
  3. pack_padded_sequence操作
  4. 输入到lstm中进行训练
# 导入定义bilstm库
import torch
from torch import nn

class BiLSTM(nn.Module):
    def __init__(self, vocab_size, embedding_dim, hidden_size, num_layers, pad_idx,
                 unk_idx, pre_trained_embedding=None):
        super(BiLSTM, self).__init__()

        # lookup table that stores embeddings of fixed dictionary and size
        self.embedding = nn.Embedding(vocab_size, embedding_dim, padding_idx=pad_idx)

        # 加载 预处理embeddding
        if pre_trained_embedding is not None:
            self.embedding.weight.data.copy_(pre_trained_embedding)

            # 对于pre_trained vocab 没有对应的vector
            self.embedding.weight.data[unk_idx] = torch.zeros(embedding_dim)
            self.embedding.weight.data[pad_idx] = torch.zeros(embedding_dim)

        # 定义encoder
        self.encoder = nn.LSTM(input_size=embedding_dim,
                               hidden_size=hidden_size,
                               num_layers=num_layers,
                               bidirectional=True)

        # 定义decoder
        self.decoder = nn.Linear(2 * hidden_size, 2)

        # 定义dropout
        ## 减少过拟合,增加我们模型的泛化能力
        ## Dropout 在深度网络训练中,以一定随机概率临时丢失一部分神经元的节点
        self.dropout = nn.Dropout(0.5)

    def forward(self, inputs, text_lengths):
        """

        :param inputs:  每个batch text 内容
        :param text_lengths:  words 的长度,词汇长度
        :return:
        """

        # 提取词特征(词数,批量大小)
        ......
        # pad sequence <pad> 设置
        ......

	    # encoder
	    ......

        # decoder
        ......
        return outs

4.3准确率指标

分别有两个指标一个是topk的AverageMeter,另一个是使用混淆矩阵。混淆矩阵的实现的时候先转为(pred, label)的二元对,然后相应的填充到表中。

class AvgrageMeter(object):

    def __init__(self):
        self.reset()

    def reset(self):
        self.avg = 0
        self.sum = 0
        self.cnt = 0

    def update(self, val, n=1):
        self.sum += val * n
        self.cnt += n
        self.avg = self.sum / self.cnt
#混淆矩阵指标
class ConfuseMeter(object):

    def __init__(self):
        self.reset()

    def reset(self):
        # 标签的分类:0 pos 1 neg 
        self.confuse_mat = torch.zeros(2,2)
        self.tp = self.confuse_mat[0,0]
        self.fp = self.confuse_mat[0,1]
        self.tn = self.confuse_mat[1,1]
        self.fn = self.confuse_mat[1,0]
        self.acc = 0
        self.pre = 0
        self.rec = 0
        self.F1 = 0
    def update(self, output, label):
        pred = output.argmax(dim = 1)
        for l, p in zip(label.view(-1),pred.view(-1)):
            self.confuse_mat[p.long(), l.long()] += 1 # 对应的格子加1
        self.tp = self.confuse_mat[0,0]
        self.fp = self.confuse_mat[0,1]
        self.tn = self.confuse_mat[1,1]
        self.fn = self.confuse_mat[1,0]
        self.acc = (self.tp+self.tn) / self.confuse_mat.sum()
        self.pre = self.tp / (self.tp + self.fp)
        self.rec = self.tp / (self.tp + self.fn)
        self.F1 = 2 * self.pre*self.rec / (self.pre + self.rec)
## topk的准确率计算
def accuracy(output, label, topk=(1,)):
    maxk = max(topk) 
    batch_size = label.size(0)

    # 获取前K的索引
    _, pred = output.topk(maxk, 1, True, True) #使用topk来获得前k个的索引
    pred = pred.t() # 进行转置
    # eq按照对应元素进行比较 view(1,-1) 自动转换到行为1,的形状, expand_as(pred) 扩展到pred的shape
    # expand_as 执行按行复制来扩展,要保证列相等
    correct = pred.eq(label.view(1, -1).expand_as(pred)) # 与正确标签序列形成的矩阵相比,生成True/False矩阵
#     print(correct)

    rtn = []
    for k in topk:
        correct_k = correct[:k].view(-1).float().sum(0) # 前k行的数据 然后平整到1维度,来计算true的总个数
        rtn.append(correct_k.mul_(100.0 / batch_size)) # mul_() ternsor 的乘法  正确的数目/总的数目 乘以100 变成百分比
    return rtn

4.4训练函数

#一个epoch的训练逻辑
def train(epoch,epochs, train_loader, device, model, criterion, optimizer,scheduler,tensorboard_path):
    model.train()
    top1 = AvgrageMeter()
    model = model.to(device)
    train_loss = 0.0
    for i, data in enumerate(train_loader, 0):  # 0是下标起始位置默认为0
        inputs, labels, batch_seq_len = data[0].to(device), data[1].to(device), data[2]
        # 初始为0,清除上个batch的梯度信息
        optimizer.zero_grad()
        outputs,hidden = model(inputs,batch_seq_len)

        loss = criterion(outputs,labels)
        loss.backward()
        optimizer.step()
        _,pred = outputs.topk(1)
        prec1, prec2= accuracy(outputs, labels, topk=(1,2))
        n = inputs.size(0)
        top1.update(prec1.item(), n)
        train_loss += loss.item()
        postfix = {'train_loss': '%.6f' % (train_loss / (i + 1)), 'train_acc': '%.6f' % top1.avg}
        train_loader.set_postfix(log=postfix)

        # ternsorboard 曲线绘制
        if os.path.exists(tensorboard_path) == False: 
            os.mkdir(tensorboard_path)    
        writer = SummaryWriter(tensorboard_path)
        writer.add_scalar('Train/Loss', loss.item(), epoch)
        writer.add_scalar('Train/Accuracy', top1.avg, epoch)
        writer.flush()
    scheduler.step()

#     print('Finished Training')

4.5验证函数

def validate(epoch,validate_loader, device, model, criterion, tensorboard_path):
    val_acc = 0.0
    model = model.to(device)
    model.eval()
    with torch.no_grad():  # 进行评测的时候网络不更新梯度
        val_top1 = AvgrageMeter()
        validate_loader = tqdm(validate_loader)
        validate_loss = 0.0
        for i, data in enumerate(validate_loader, 0):  # 0是下标起始位置默认为0
            inputs, labels, batch_seq_len = data[0].to(device), data[1].to(device), data[2]
            #         inputs,labels = data[0],data[1]
            outputs,_ = model(inputs, batch_seq_len)
            loss = criterion(outputs, labels)

            prec1, prec2 = accuracy(outputs, labels, topk=(1, 2))
            n = inputs.size(0)
            val_top1.update(prec1.item(), n)
            validate_loss += loss.item()
            postfix = {'validate_loss': '%.6f' % (validate_loss / (i + 1)), 'validate_acc': '%.6f' % val_top1.avg}
            validate_loader.set_postfix(log=postfix)

            # tensorboard 曲线绘制
            if os.path.exists(tensorboard_path) == False: 
                os.mkdir(tensorboard_path)    
            writer = SummaryWriter(tensorboard_path)
            writer.add_scalar('Validate/Loss', loss.item(), epoch)
            writer.add_scalar('Validate/Accuracy', val_top1.avg, epoch)
            writer.flush()
        val_acc = val_top1.avg
    return val_acc

4.6测试函数

def test(validate_loader, device, model, criterion):
    val_acc = 0.0
    model = model.to(device)
    model.eval()
    confuse_meter = ConfuseMeter()
    with torch.no_grad():  # 进行评测的时候网络不更新梯度
        val_top1 = AvgrageMeter()
        validate_loader = tqdm(validate_loader)
        validate_loss = 0.0
        for i, data in enumerate(validate_loader, 0):  # 0是下标起始位置默认为0
            inputs, labels, batch_seq_len = data[0].to(device), data[1].to(device), data[2]
            #         inputs,labels = data[0],data[1]
            outputs,_ = model(inputs, batch_seq_len)
#             loss = criterion(outputs, labels)

            prec1, prec2 = accuracy(outputs, labels, topk=(1, 2))
            n = inputs.size(0)
            val_top1.update(prec1.item(), n)
            confuse_meter.update(outputs, labels)
#             validate_loss += loss.item()
            postfix = { 'test_acc': '%.6f' % val_top1.avg,
                      'confuse_acc': '%.6f' % confuse_meter.acc}
            validate_loader.set_postfix(log=postfix)
        val_acc = val_top1.avg
    return confuse_meter

最后我们在测试集上评估模型并打印出评分和准确率.

# evaluate
score, acc = model.evaluate(Xtest, ytest, batch_size=BATCH_SIZE)
print("Test score: %.3f, accuracy: %.3f" % (score, acc))

for i in range(5):
    idx = np.random.randint(len(Xtest))
    xtest = Xtest[idx].reshape(1,40)
    ylabel = ytest[idx]
    ypred = model.predict(xtest)[0][0]
    sent = " ".join([index2word[x] for x in xtest[0].tolist() if x != 0])
    print("%.0f\t%d\t%s" % (ypred, ylabel, sent))

通过改变不同参数,可以得到类似下图的结果:

817128-20200805150113772-534424049.png

5.小结

本项目使用深度神经网络实现对互联网上微博短文本的情感分析。首先将训练的词向量作为原始特征向量,在RNN循环神经网络和LSTM长短期记忆神经网络的基础上,对微博文本情感进行分析,通过在数据集上的相关实验,结果表明LSTM神经网络特殊的门控制单元能够记忆更多有用的历史特征信息,对文本的分类产生重要影响。下一步,可以将训练好的模型应用于微博的舆情监测、用户投诉反馈分析等场景上,通过加载训练好的模型,可以完成对收集到的评论文本进行情感分析及舆论导向的统计。