NLP学习(十)-情感分析技术及案例实现-Python3实现

1 情感分析简述

文本情感分析(Sentiment Analysis)是指利用自然语言处理和文本挖掘技术,对带有情感色彩的主观性文本进行分析、处理和抽取的过程[1]。目前,文本情感分析研究涵盖了包括自然语言处理、文本挖掘、信息检索、信息抽取、机器学习和本体学等多个领域,得到了许多学者以及研究机构的关注,近几年持续成为自然语言处理和文本挖掘领域研究的热点问题之一。情感分析任务按其分析的粒度可以分为篇章级,句子级,词或短语级;按其处理文本的类别可分为基于产品评论的情感分析和基于新闻评论的情感分析;按其研究的任务类型,可分为情感分类,情感检索和情感抽取等子问题[2]。文本情感分析的基本流程如图 1 所示,包括从原始文本爬取,文本预处理,语料库和情感词库构建以及情感分析结果等全流程。由于文本原始素材爬取,分词等预处理技术已比较成熟,本文接下来将通过情感分析的主要任务情感分类,情感检索,情感抽取问题来分析和阐述已有的相关研究工作。
在这里插入图片描述

2 情感分类

情感分类又称情感倾向性分析,是指对给定的文本,识别其中主观性文本的倾向是肯定还是否定的,或者说是正面还是负面的,是情感分析领域研究最多的。通常网络文本存在大量的主观性文本和客观性文本。客观性文本是对事物的客观性描述,不带有感情色彩和情感倾向,主观性文本则是作者对各种事物的看法或想法,带有作者的喜好厌恶等情感倾向。情感分类的对象是带有情感倾向的主观性文本,因此情感分类首先要进行文本的主客观分类。文本的主客观分类主要以情感词识别为主,利用不同的文本特征表示方法和分类器进行识别分类,对网络文本事先进行主客观分类,能够提高情感分类的速度和准确度[3]。纵观目前主观性文本情感倾向性分析的研究工作,主要研究思路分为基于语义的情感词典方法和基于机器学习的方法。

2.1 基于语义的情感词典方法

(1) 构建词典

情感词典的构建是情感分类的前提和基础,目前在实际使用中,可将其归为 4 类:通用情感词、程度副词、否定词
、领域词。目前国内外,情感词典的构建方法主要是利用已有电子词典扩展生成情感词典。英文方面主要是基于对英文词典 的Word Net 的扩充,Hu 和 Liu[4]在已手工建立种子形容词词汇表的基础上,利用 World Net 中词间的同义和近义关系判断情感词的情感倾向,并以此来判断观点的情感极性。中文方面则主要是对知网 How net 的扩充,朱嫣岚[5]利用语义相似度计算方法计算词语与基准情感词集的语义相似度,以此推断该词语的情感倾向。此外,还可以建立专门的领域词典,以提高情感分类的准确性。

(2)构建倾向性计算算法

基于语义的情感词典的倾向性计算不同于所需大量训练数据集的机器学习算法,主要是利用情感词典及句式词库分析文本语句的特殊结构及情感倾向词,采用权值算法代替传统人工判别或仅利用简单统计的方法进行情感分类。给情感强度不同的情感词赋予不同权值,然后进行加权求和。文献[6]利用加权平均算法式(1)计算,可有效提高通用领域情感分类的效率和准确率

其中,\large N_p,N_n分别代表表达正面情感和负面情感的词汇数目;\large wp_i,wp_j分别代表正面情感词汇和负面情感词汇的权值。

(3)确定阈值来判断文本倾向性

一般情况下,加权计算结果为正是正面倾向,结果为负是负面倾向 ,得分为零无倾向。所得结果评价一般采用自然语言中
经常使用的正确率、召回率和 F 值来评判算法效果。
基于情感词典的方法和基于机器学习的分类算法相比,虽属于粗粒度的倾向性分类方法,但由于不依赖标注好的训练集,实现相对简单,对于普遍通用领域的网络文本可有效快速地进行情感分类。

扫描二维码关注公众号,回复: 11778863 查看本文章

2.2 基于机器学习的情感分类方法

文本情感倾向性分析与传统的基于主题的文本分类相似但有所不同,基于主题的文本分类是把文本分类到各个预定义的主题上,如军事,互联网,政治,体育等,而情感分类不是基于内容本身的,而是按照文本持有的情感、态度进行判断。现有任何机器学习的分类方法都可以用到情感分类中来。基于机器学习的情感分类,其大致流程如下:首先人工标注文本倾向性作为训练集,提取文本情感特征,通过机器学习的方法构造情感分类器,待分类的文本通过分类器进行倾向性分类。常用的情感分类特征包括情感词,词性,句法结构,否定表达模板,连接,语义话题等[7],研究者通过挖掘各种不同的特征以期望提高情感分类的能。常用的特征提取方法有信息增益( Information Gain,IG),CHI 统计量( Chi - square,CHI) 和文档频率( Document Frequency,DF) 等。常用的分类方法有中心向量分类方法、K - 近邻(K - Nearest - Neighbor,KNN) 分类方法、贝叶斯分类器、支持向量机、条件随机场、最大熵分类器等。
最早从事情感分析研究的 Pang 等人[8]使用词袋(Bag - of - Feature) 框架选定文本的 N 元语法( N -Gram) 和词性( POS) 等作为情感 uo 特征,使用有监督的机器学习的方法将电影评论分为正向和负向两类,分别使用朴素贝叶斯,最大熵模型和支持向量机作为有监督学习算法的分类器。结果显示支持向量机在几种分类方法中效果最好,分类准确率达到 80% 。文本情感分类的准确率难以达到普通文本分类的水平,主要是情感文本中复杂的情感表达和大量的情感歧义造成的。在基于机器学习的情感分类算法中,每篇文章被转换成一个对应的特征向量来表示。特征选择的好坏将直接影响情感分析任务的性能。在 Pang 等人的研究基础上,后续研究主要是把情感分类作为一个特征优化任务[9- 11]。随着语义特征信息的加入和训练语料库的发展,基于机器学习的分类将会有广阔的发展前景。

3 情感检索

情感检索是从海量文本中查询到观点信息,根据主题相关度和观点倾向性对结果排序。情感检索返回的结果要同时满足主题相关和带有情感倾向或指定的情感倾向,是比情感分类更为复杂的任务。主题相关度和观点倾向性对结果排序,随着人们网络检索需求的增高,在传统搜索中加入情感倾向成了搜索技术中一个新的研究热点。和传统的互联网搜索相似,情感检索有两个主要任务:(1) 检索和查询相关的文档或句子。(2)对检索的相关文档或句子进行排序。与传统搜索不同的是互联网搜索的任务只要求找到和查询相关的文档和句子,而情感检索还要确定文档和句子是否表达了观点,以及观点是正面的或是负面的。目前情感检索主要实现方法有两种:一是按传统信息检索模型进行主题相关的文档检索,对检索结果进行情感分类;另一种是同时计算主题相关值和情感倾向值进行检索。第一种方法一般使用传统的检索模型以及较为成熟的查询扩展技术,然后用情感分类方法进行倾向性计
算。文献[12 ~ 13]给出的情感检索系统是国际文本检索会议 TREC(Text Retrieval Evaluation Conference)博客观点搜索任务的优胜者,该系统分为两部分检索部分和观点分类部分。检索部分完成传统的信息检索任务,同时在处理用户查询时将用户查询中的概念进行识别和消歧义,对于每个搜索查询进行同义词扩展,使用概念和关键字针对扩展后的查询对每个文档计算一个相似度,查询的关键字和文档的相关度是这两种相似度的综合。观点分类部分使用监督学习的方法使用两个分类器将文档分为两个类别带观点和不带观点的,带观点的文档再分为正面,负面或者混合的观点。第一个分类器训练数据是从评价网站包括 rateilt-
all. com 和 epinion. com 收集大量带观点的数据和从维基百科等客观性网站收集不带观点的训练数据。第二个分类器训练数据来自评论网站包含打分的评论,低的打分表明负面观点,高的打分表明正面观点。这里两种监督学习的分类器都采用支持向量机。在 TREC博客检索数据集研究的基础上,研究者采用不同的情感分类方法开展了后续研究[14 - 16]。

上面的方法是将检索和情感分类独立计算的,实际中主题相关和情感匹配是有关联的,需要同时计算主题相关和情感匹配,这是因为不同的情感词在文档中对不同的查询词下可能有相反的情感倾向。第二种方法则是同时考虑主题相关和情感文档排序,选择排序策略时需要同时兼顾。很多学者[17 - 18]对排序策略进行了研究,一般是分别计算情感倾向值和查询相关度值,然后加权求和进行排序。Zhang 等人[19]提出一种融合文档情感得分和文档查询相关度得分的概率生成模型排序方法,取得了理想的效果。
情感信息检索是传统信息检索技术和情感分析技术的融合,如何更好的融合二者得到理想的情感检索结果是未来要重点关注的。

4 情感抽取

情感抽取是指抽取情感文本中有价值的情感信息,其要判断一个单词或词组在情感表达中扮演的角色,包括情感表达者识别,评价对象识别,情感观点词识别等任务。情感表达者识别又称观点持有者抽取,其是观点、评论的隶属者。在社交媒体和产品评论中,观点持有者通常是文本的作者或者评论员,其的登录账号是可见的,观点持有者抽取比较简单。而对于新闻文章和其他一些表达观点的任务或者组织显式的出现在文档时,观点持有者一般则是由机构名或人名组成,所以可采用命名实体识别方法进行抽取。Kim[20]等人借助语义角色标注来完成观点持有者的抽取。然而这些处理方法会导致较低的语言覆盖现象和较差的领域适应性,可以通过基于模式识别的信息抽取 ( Information Extraction) 和 机 器 学 习 ( Machine Learning )技 术 来解决[21]。评价对象和情感词抽取在情感分析中具有重要作用。利用评价对象和情感词的抽取,可以构建领域相关的主题词表和情感词表,情感词表的构建在情感分类部分已做阐述。评价对象是指某段评论中的主题,是评论文本中评价词语修饰的对象,现有的研究大多
将评价对象限定在名词或名词短语的范畴内,一般使用基于模板和规则的方法抽取评价对象。规则的制定通常基于一系列的语言分析和预处理过程,命名实体识别,词性标注和句法分析等方法[22 - 25]都被用来进行评价对象抽取。文献[26]便是使用 3 条限制等级逐渐渐进的词性规则从评价对象集中抽取评价对象,取得了较好的结果。
情感抽取是情感分析的基础任务,通过对大量的情感文本分析,有价值的情感信息抽取对于情感分析的上层任务情感检索和情感分类有直接帮助,如何准确抽取情感信息一直都是研究者关注的重点。

文本情感分析评测

近年来,情感分析得到了越来越多研究机构和学者的关注,在 SIGIR、ACL、WWW、CIKM、WSDM 等著名国际会议上,针对这一问题的研究成果层出不穷[27],国内外研究机构组织了众多相关评测来推动情感分析技术的发展。

由国际文本检索会议 TREC 针对英文文本观点检索任务的博客检索任务(Blog Track),篇章情感分类任务,以及其他一些有趣的情感分析任务;由日本国立信息学研究所主办的搜索引擎评价国际会议 NTCIR(NIITest Collection for IR Systems) 针对日、韩、英、中文文本的情感分类以及观点持有者抽取任务。由中文信息学会信息检索委员会主办的每年一次的中文倾向性分析评测 C
OAE(Chinese Opinion Analysis Evaluation) 已举办了 5 届,在关注情感词语和观点句子的抽取以及倾向性识别的基础上重点对于否定句、比较句以及微博观点句进行评测[28]。众多研究机构的评测推动了情感分析研究的发展,出现了很多有代表性的情感分析语料库资源,文献[29 ~ 30]对语料库构建进行了详细阐述,如康奈尔影评数据集(Cornell Movie - Review Datasets),多视角问答( Multiple - Perspective Question Answering,MPQA)语料库,TREC 测试集,NTCIR 多语言语料库(
NTCIRmultilingual corpus),中文 COAE 语料库等。

5 情感分析实战

5.1 词向量模型

计算机可只认识数字!
在这里插入图片描述
我们可以将一句话中的每一个词都转换成一个向量
在这里插入图片描述
你可以将输入数据看成是一个 16*D 的一个矩阵。

词向量是具有空间意义的并不是简单的映射!例如,我们希望单词 “love” 和 “adore” 这两个词在向量空间中是有一定的相关性的,因为他们有类似的定义,他们都在类似的上下文中使用。单词的向量表示也被称之为词嵌入。
在这里插入图片描述

5.2 Word2Vec

为了去得到这些词嵌入,我们使用一个非常牛逼的模型 “Word2Vec”。简单的说,这个模型根据上下文的语境来推断出每个词的词向量。如果两个个词在上下文的语境中,可以被互相替换,那么这两个词的距离就非常近。在自然语言中,上下文的语境对分析词语的意义是非常重要的。比如,之前我们提到的 “adore” 和 “love” 这两个词,我们观察如下上下文的语境。
在这里插入图片描述
从句子中我们可以看到,这两个词通常在句子中是表现积极的,而且一般比名词或者名词组合要好。这也说明了,这两个词可以被互相替换,他们的意思是非常相近的。对于句子的语法结构分析,上下文语境也是非常重要的。所以,这个模型的作用就是从一大堆句子(以 Wikipedia 为例)中为每个独一无二的单词进行建模,并且输出一个唯一的向量。Word2Vec 模型的输出被称为一个嵌入矩阵。
在这里插入图片描述
这个嵌入矩阵包含训练集中每个词的一个向量。传统来讲,这个嵌入矩阵中的词向量数据会很大。

Word2Vec 模型根据数据集中的每个句子进行训练,并且以一个固定窗口在句子上进行滑动,根据句子的上下文来预测固定窗口中间那个词的向量。然后根据一个损失函数和优化方法,来对这个模型进行训练。

5.3 卷积神经网络 CNN

在这里插入图片描述
他用的结构比较简单,就是使用长度不同的filter 对文本矩阵进行卷积,filter的宽度等于词向量的长度,然后使用max-pooling 对每一filter提取的向量进行操作,最后每一个filter对应一个数字,把这些filter拼接起来,就得到了一个表征该句子的向量。最后的预测都是基于该句子的。

5.4 循环神经网络 RNN

循环神经网络主要适合处理有连续特征的数据(序列数据),比如语音、文本等
在这里插入图片描述
  对于自然语言处理来讲,通常我们会首先对一段话进行分词,将分好后的词X0,X1,X2…Xt依次输入其中,前面的每个词经过rnn中的A(类似于bp神经网络结构)后,都会对A产生影响,从而对后面词的输出产生影响,来完成对一整段连续数据的处理,例如机器翻译,输出这段文本的分类等。
在这里插入图片描述
  rnn在A向后传播的时候,后面词对输出的影响会逐渐减弱,这样就会导致只有前面的词对结果的影响比较大,针对这种缺陷,大神们又推出了rnn的变种LSTM(长短时记忆网络)

5.5 长短时记忆网络 LSTM

在这里插入图片描述

在这里插入图片描述
上图1是lstm的基本神经元结构,信号从图的下方输入,从图的上方输出,关键的是 他的三个门,输入门(Input Gate)、遗忘门(Forget Gate)、输出门(Output Gate),这三个门的参数初始值都是随机初始化的,通过带标签的数据进行训练,最终得到相应的值,比如lstm模型中某个神经元的输入门是0.133221,遗忘门是0.233345,输出门是0.89893。
在看2图右边,输入数据与各门相乘再向后传播,说实话我没有很清晰的理顺数据传输的过程,然后我又去看了下吴恩达老师的lstm的讲解,很遗憾我还是不能很清晰的理顺,这个之后还需再找些其他的详细资料看一下。
在这里插入图片描述
  上图是lstm的整体数据处理的过程,通过各种门可以非常好的控制什么信号可以输入、什么信号不能输入、什么时候能让他输出(序列数据时许很重要,如机器翻译)还有在隐藏层里的数据在向后传的时候让他忘记还是不忘记。

六 案例流程

npy文件见https://pan.baidu.com/s/1SctPmfFlq6ilY2bxcXHIFA
或者https://download.csdn.net/download/qq_30868737/12858000
keras数据集分享百度云:https://pan.baidu.com/s/1aZRp0uMkNj2QEWYstaNsKQ
提取码: 3a2u
数据集相关介绍可参考:https://blog.csdn.net/qq_37879432/article/details/78557234
训练数据
http://ai.stanford.edu/~amaas/data/sentiment/aclImdb_v1.tar.gz

  1. 制作词向量,可以使用gensim这个库,也可以直接用现成的
  2. 词和ID的映射,常规套路了
  3. 构建RNN网络架构
  4. 训练我们的模型
  5. 试试咋样

1、导入数据
首先,我们需要去创建词向量。为了简单起见,我们使用训练好的模型来创建。

作为该领域的一个最大玩家,Google 已经帮助我们在大规模数据集上训练出来了 Word2Vec 模型,包括 1000 亿个不同的词!在这个模型中,谷歌能创建 300 万个词向量,每个向量维度为 300。

在理想情况下,我们将使用这些向量来构建模型,但是因为这个单词向量矩阵相当大(3.6G),我们用另外一个现成的小一些的,该矩阵由 GloVe 进行训练得到。矩阵将包含 400000 个词向量,每个向量的维数为 50。

我们将导入两个不同的数据结构,一个是包含 400000 个单词的 Python 列表,一个是包含所有单词向量值得 400000*50 维的嵌入矩阵

# -*- coding: utf-8 -*-

import numpy as np
wordsList = np.load('../testdata/wordsList.npy')
print('Loaded the word list!')
wordsList = wordsList.tolist() #Originally loaded as numpy array
wordsList = [word.decode('UTF-8') for word in wordsList] #Encode words as UTF-8
wordVectors = np.load('../testdata/wordVectors.npy')
print ('Loaded the word vectors!')
print(len(wordsList))
print(wordVectors.shape)
out:
Loaded the word list!
Loaded the word vectors!
400000
(400000, 50)

我们也可以在词库中搜索单词,比如 “baseball”,然后可以通过访问嵌入矩阵来得到相应的向量,如下:

baseballIndex = wordsList.index('baseball')
wordVectors[baseballIndex]
output:
array([-1.93270004,  1.04209995, -0.78514999,  0.91033   ,  0.22711   ,
       -0.62158   , -1.64929998,  0.07686   , -0.58679998,  0.058831  ,
        0.35628   ,  0.68915999, -0.50598001,  0.70472997,  1.26639998,
       -0.40031001, -0.020687  ,  0.80862999, -0.90565997, -0.074054  ,
       -0.87674999, -0.62910002, -0.12684999,  0.11524   , -0.55685002,
       -1.68260002, -0.26291001,  0.22632   ,  0.713     , -1.08280003,
        2.12310004,  0.49869001,  0.066711  , -0.48225999, -0.17896999,
        0.47699001,  0.16384   ,  0.16537   , -0.11506   , -0.15962   ,
       -0.94926   , -0.42833   , -0.59456998,  1.35660005, -0.27506   ,
        0.19918001, -0.36008   ,  0.55667001, -0.70314997,  0.17157   ], dtype=float32)

现在我们有了向量,我们的第一步就是输入一个句子,然后构造它的向量表示。假设我们现在的输入句子是 “I thought the movie was incredible and inspiring”。为了得到词向量,我们可以使用 TensorFlow 的嵌入函数。这个函数有两个参数,一个是嵌入矩阵(在我们的情况下是词向量矩阵),另一个是每个词对应的索引。

import tensorflow as tf
maxSeqLength = 10 #Maximum length of sentence
numDimensions = 300 #Dimensions for each word vector
firstSentence = np.zeros((maxSeqLength), dtype='int32')
firstSentence[0] = wordsList.index("i")
firstSentence[1] = wordsList.index("thought")
firstSentence[2] = wordsList.index("the")
firstSentence[3] = wordsList.index("movie")
firstSentence[4] = wordsList.index("was")
firstSentence[5] = wordsList.index("incredible")
firstSentence[6] = wordsList.index("and")
firstSentence[7] = wordsList.index("inspiring")
#firstSentence[8] and firstSentence[9] are going to be 0
print(firstSentence.shape)
print(firstSentence) #Shows the row index for each word
output:
(10,)
[    41    804 201534   1005     15   7446      5  13767      0      0]

数据管道如下图所示:
在这里插入图片描述
输出数据是一个 10*50 的词矩阵,其中包括 10 个词,每个词的向量维度是 50。就是去找到这些词对应的向量

#输出数据是一个 10*50 的词矩阵,其中包括 10 个词,每个词的向量维度是 50。就是去找到这些词对应的向量
tf.compat.v1.disable_eager_execution()
with tf.compat.v1.Session() as sess:
    print(tf.nn.embedding_lookup(wordVectors,firstSentence).eval().shape)
out:
(10, 50)

在整个训练集上面构造索引之前,我们先花一些时间来可视化我们所拥有的数据类型。这将帮助我们去决定如何设置最大序列长度的最佳值。在前面的例子中,我们设置了最大长度为 10,但这个值在很大程度上取决于你输入的数据。

训练集我们使用的是 IMDB 数据集。这个数据集包含 25000 条电影数据,其中 12500 条正向数据,12500 条负向数据。这些数据都是存储在一个文本文件中,首先我们需要做的就是去解析这个文件。正向数据包含在一个文件中,负向数据包含在另一个文件中。

# 训练集我们使用的是 IMDB 数据集。这个数据集包含 25000 条电影数据,其中 12500 条正向数据,12500 条负向数据。
# 这些数据都是存储在一个文本文件中,首先我们需要做的就是去解析这个文件。正向数据包含在一个文件中,负向数据包含在另一个文件中。
from os import listdir
from os.path import isfile, join
positiveFiles = ['../testdata/aclImdb/train/pos/' + f for f in listdir('../testdata/aclImdb/train/pos/') if isfile(join('../testdata/aclImdb/train/pos/', f))]
negativeFiles = ['../testdata/aclImdb/train/neg/' + f for f in listdir('../testdata/aclImdb/train/neg/') if isfile(join('../testdata/aclImdb/train/neg/', f))]
numWords = []
for pf in positiveFiles:
    with open(pf, "r", encoding='utf-8') as f:
        line=f.readline()
        counter = len(line.split())
        numWords.append(counter)
print('Positive files finished')

for nf in negativeFiles:
    with open(nf, "r", encoding='utf-8') as f:
        line=f.readline()
        counter = len(line.split())
        numWords.append(counter)
print('Negative files finished')

numFiles = len(numWords)
print('The total number of files is', numFiles)
print('The total number of words in the files is', sum(numWords))
print('The average number of words in the files is', sum(numWords)/len(numWords))
output:
Positive files finished
Negative files finished
The total number of files is 25000
The total number of words in the files is 5844680
The average number of words in the files is 233.7872
import matplotlib.pyplot as plt
%matplotlib inline
plt.hist(numWords, 50)
plt.xlabel('Sequence Length')
plt.ylabel('Frequency')
plt.axis([0, 1200, 0, 8000])
plt.show()

在这里插入图片描述
从直方图和句子的平均单词数,我们认为将句子最大长度设置为 250 是可行的。

maxSeqLength = 250

接下来,让我们看看如何将单个文件中的文本转换成索引矩阵,比如下面的代码就是文本中的其中一个评论。

#如何将单个文件中的文本转换成索引矩阵
fname = positiveFiles[3] #Can use any valid index (not just 3)
with open(fname) as f:
    for lines in f:
        print(lines)
        exit
# 删除标点符号、括号、问号等,只留下字母数字字符
import re
strip_special_chars = re.compile("[^A-Za-z0-9 ]+")

def cleanSentences(string):
    string = string.lower().replace("<br />", " ")
    return re.sub(strip_special_chars, "", string.lower())
#定义一个长度是maxSeqLength且全为零的数组
firstFile = np.zeros((maxSeqLength), dtype='int32')
with open(fname) as f:
    indexCounter = 0
    line=f.readline()
    cleanedLine = cleanSentences(line)
    split = cleanedLine.split()
    for word in split:
        try:
            firstFile[indexCounter] = wordsList.index(word)
        except ValueError:
            firstFile[indexCounter] = 399999 #Vector for unknown words
        indexCounter = indexCounter + 1
print(firstFile)
array([    37,     14,   2407, 201534,     96,  37314,    319,   7158,
       201534,   6469,   8828,   1085,     47,   9703,     20,    260,
           36,    455,      7,   7284,   1139,      3,  26494,   2633,
          203,    197,   3941,  12739,    646,      7,   7284,   1139,
            3,  11990,   7792,     46,  12608,    646,      7,   7284,
         1139,      3,   8593,     81,  36381,    109,      3, 201534,
         8735,    807,   2983,     34,    149,     37,    319,     14,
          191,  31906,      6,      7,    179,    109,  15402,     32,
           36,      5,      4,   2933,     12,    138,      6,      7,
          523,     59,     77,      3, 201534,     96,   4246,  30006,
          235,      3,    908,     14,   4702,   4571,     47,     36,
       201534,   6429,    691,     34,     47,     36,  35404,    900,
          192,     91,   4499,     14,     12,   6469,    189,     33,
         1784,   1318,   1726,      6, 201534,    410,     41,    835,
        10464,     19,      7,    369,      5,   1541,     36,    100,
          181,     19,      7,    410,      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,      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,
            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])

现在,我们用相同的方法来处理全部的 25000 条评论。我们将导入电影训练集,并且得到一个 25000 * 250 的矩阵。这是一个计算成本非常高的过程,可以直接使用理好的索引矩阵文件。

# ids = np.zeros((numFiles, maxSeqLength), dtype='int32')
# fileCounter = 0
# for pf in positiveFiles:
#    with open(pf, "r") as f:
#        indexCounter = 0
#        line=f.readline()
#        cleanedLine = cleanSentences(line)
#        split = cleanedLine.split()
#        for word in split:
#            try:
#                ids[fileCounter][indexCounter] = wordsList.index(word)
#            except ValueError:
#                ids[fileCounter][indexCounter] = 399999 #Vector for unkown words
#            indexCounter = indexCounter + 1
#            if indexCounter >= maxSeqLength:
#                break
#        fileCounter = fileCounter + 1 

# for nf in negativeFiles:
#    with open(nf, "r") as f:
#        indexCounter = 0
#        line=f.readline()
#        cleanedLine = cleanSentences(line)
#        split = cleanedLine.split()
#        for word in split:
#            try:
#                ids[fileCounter][indexCounter] = wordsList.index(word)
#            except ValueError:
#                ids[fileCounter][indexCounter] = 399999 #Vector for unkown words
#            indexCounter = indexCounter + 1
#            if indexCounter >= maxSeqLength:
#                break
#        fileCounter = fileCounter + 1 
# #Pass into embedding function and see if it evaluates. 

# np.save('idsMatrix', ids)

ids = np.load('./training_data/idsMatrix.npy')

2、辅助函数

from random import randint

def getTrainBatch(batchSize,maxSeqLength,ids):#从文件总获取训练集
    labels=[]
    arr = np.zeros([batchSize,maxSeqLength])
    for i in range(batchSize):
        if (i%2)==0:
            num = randint(1,11499)
            labels.append([1,0])
        else:
            num = randint(13499,24999)
            labels.append([0,1])
        arr[i] = ids[num-1:num]
    return arr,labels
def getTestBatch(batchSize,maxSeqLength,ids):#从文件中获取数据集
    labels = []
    arr = np.zeros([batchSize, maxSeqLength])
    for i in range(batchSize):
        num = randint(11499,13499)
        if (num <= 12499):
            labels.append([1,0])
        else:
            labels.append([0,1])
        arr[i] = ids[num-1:num]
    return arr, labels

3、RNN Model
现在,我们可以开始构建我们的 TensorFlow 图模型。首先,我们需要去定义一些超参数,比如批处理大小,LSTM的单元个数,分类类别和训练次数。

batchSize = 24
lstmUnits = 64
numClasses = 2
iterations = 50000

与大多数 TensorFlow 图一样,现在我们需要指定两个占位符,一个用于数据输入,另一个用于标签数据。对于占位符,最重要的一点就是确定好维度。
在这里插入图片描述
标签占位符代表一组值,每一个值都为 [1,0] 或者 [0,1],这个取决于数据是正向的还是负向的。输入占位符,是一个整数化的索引数组。

tf.compat.v1.reset_default_graph()

labels = tf.compat.v1.placeholder(tf.float32, [batchSize, numClasses])
input_data = tf.compat.v1.placeholder(tf.int32, [batchSize, maxSeqLength])

一旦,我们设置了我们的输入数据占位符,我们可以调用 tf.nn.embedding_lookup() 函数来得到我们的词向量。该函数最后将返回一个三维向量,第一个维度是批处理大小,第二个维度是句子长度,第三个维度是词向量长度。更清晰的表达,如下图所示:
在这里插入图片描述

data = tf.Variable(tf.zeros([batchSize, maxSeqLength, numDimensions]),dtype=tf.float32)
data = tf.nn.embedding_lookup(wordVectors,input_data)

现在我们已经得到了我们想要的数据形式,那么揭晓了我们看看如何才能将这种数据形式输入到我们的 LSTM 网络中。首先,我们使用 tf.nn.rnn_cell.BasicLSTMCell 函数,这个函数输入的参数是一个整数,表示需要几个 LSTM 单元。这是我们设置的一个超参数,我们需要对这个数值进行调试从而来找到最优的解。然后,我们会设置一个 dropout 参数,以此来避免一些过拟合。

最后,我们将 LSTM cell 和三维的数据输入到 tf.nn.dynamic_rnn ,这个函数的功能是展开整个网络,并且构建一整个 RNN 模型。

lstmCell = tf.compat.v1.nn.rnn_cell.BasicLSTMCell(lstmUnits)
lstmCell = tf.compat.v1.nn.rnn_cell.DropoutWrapper(cell=lstmCell, output_keep_prob=0.75)
value, _ = tf.compat.v1.nn.dynamic_rnn(lstmCell, data, dtype=tf.float32)

堆栈 LSTM 网络是一个比较好的网络架构。也就是前一个LSTM 隐藏层的输出是下一个LSTM的输入。堆栈LSTM可以帮助模型记住更多的上下文信息,但是带来的弊端是训练参数会增加很多,模型的训练时间会很长,过拟合的几率也会增加。

dynamic RNN 函数的第一个输出可以被认为是最后的隐藏状态向量。这个向量将被重新确定维度,然后乘以最后的权重矩阵和一个偏置项来获得最终的输出值。

weight = tf.Variable(tf.compat.v1.truncated_normal([lstmUnits, numClasses]))
bias = tf.Variable(tf.constant(0.1, shape=[numClasses]))
value = tf.transpose(value, [1, 0, 2])
#取最终的结果值
last = tf.gather(value, int(value.get_shape()[0]) - 1)
prediction = (tf.matmul(last, weight) + bias)

接下来,我们需要定义正确的预测函数和正确率评估参数。正确的预测形式是查看最后输出的0-1向量是否和标记的0-1向量相同。

correctPred = tf.equal(tf.argmax(prediction,1), tf.argmax(labels,1))
accuracy = tf.reduce_mean(tf.cast(correctPred, tf.float32))

之后,我们使用一个标准的交叉熵损失函数来作为损失值。对于优化器,我们选择 Adam,并且采用默认的学习率。

loss = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(logits=prediction, labels=labels))
optimizer = tf.compat.v1.train.AdamOptimizer().minimize(loss)

4、超参数调整
选择合适的超参数来训练你的神经网络是至关重要的。你会发现你的训练损失值与你选择的优化器(Adam,Adadelta,SGD,等等),学习率和网络架构都有很大的关系。特别是在RNN和LSTM中,单元数量和词向量的大小都是重要因素。

  1. 学习率:RNN最难的一点就是它的训练非常困难,因为时间步骤很长。那么,学习率就变得非常重要了。如果我们将学习率设置的很大,那么学习曲线就会波动性很大,如果我们将学习率设置的很小,那么训练过程就会非常缓慢。根据经验,将学习率默认设置为 0.001 是一个比较好的开始。如果训练的非常缓慢,那么你可以适当的增大这个值,如果训练过程非常的不稳定,那么你可以适当的减小这个值。
  2. 优化器:这个在研究中没有一个一致的选择,但是 Adam 优化器被广泛的使用。
  3. LSTM单元的数量:这个值很大程度上取决于输入文本的平均长度。而更多的单元数量可以帮助模型存储更多的文本信息,当然模型的训练时间就会增加很多,并且计算成本会非常昂贵。
  4. 词向量维度:词向量的维度一般我们设置为50到300。维度越多意味着可以存储更多的单词信息,但是你需要付出的是更昂贵的计算成本。

5、训练
训练过程的基本思路是,我们首先先定义一个 TensorFlow 会话。然后,我们加载一批评论和对应的标签。接下来,我们调用会话的 run 函数。这个函数有两个参数,第一个参数被称为 fetches 参数,这个参数定义了我们感兴趣的值。我们希望通过我们的优化器来最小化损失函数。第二个参数被称为 feed_dict 参数。这个数据结构就是我们提供给我们的占位符。我们需要将一个批处理的评论和标签输入模型,然后不断对这一组训练数据进行循环训练。

sess = tf.compat.v1.InteractiveSession()
saver = tf.compat.v1.train.Saver()
sess.run(tf.compat.v1.global_variables_initializer())

for i in range(iterations):
    # Next Batch of reviews
    nextBatch, nextBatchLabels = getTrainBatch(batchSize,maxSeqLength,ids)
    sess.run(optimizer, {input_data: nextBatch, labels: nextBatchLabels})

    if (i % 1000 == 0 and i != 0):
        loss_ = sess.run(loss, {input_data: nextBatch, labels: nextBatchLabels})
        accuracy_ = sess.run(accuracy, {input_data: nextBatch, labels: nextBatchLabels})

        print("iteration {}/{}...".format(i + 1, iterations),
              "loss {}...".format(loss_),
              "accuracy {}...".format(accuracy_))
        # Save the network every 10,000 training iterations
    if (i % 10000 == 0 and i != 0):
        save_path = saver.save(sess, "models/pretrained_lstm.ckpt", global_step=i)
        print("saved to %s" % save_path)
# output
iteration 1001/50000... loss 0.6308178901672363... accuracy 0.5...
iteration 2001/50000... loss 0.7168402671813965... accuracy 0.625...
iteration 3001/50000... loss 0.7420873641967773... accuracy 0.5...
iteration 4001/50000... loss 0.650059700012207... accuracy 0.5416666865348816...
iteration 5001/50000... loss 0.6791467070579529... accuracy 0.5...
iteration 6001/50000... loss 0.6914048790931702... accuracy 0.5416666865348816...
iteration 7001/50000... loss 0.36072710156440735... accuracy 0.8333333134651184...
iteration 8001/50000... loss 0.5486791729927063... accuracy 0.75...
iteration 9001/50000... loss 0.41976991295814514... accuracy 0.7916666865348816...
iteration 10001/50000... loss 0.10224487632513046... accuracy 1.0...
saved to models/pretrained_lstm.ckpt-10000
iteration 11001/50000... loss 0.37682783603668213... accuracy 0.8333333134651184...
iteration 12001/50000... loss 0.266050785779953... accuracy 0.9166666865348816...
iteration 13001/50000... loss 0.40790924429893494... accuracy 0.7916666865348816...
iteration 14001/50000... loss 0.22000855207443237... accuracy 0.875...
iteration 15001/50000... loss 0.49727579951286316... accuracy 0.7916666865348816...
iteration 16001/50000... loss 0.21477992832660675... accuracy 0.9166666865348816...
iteration 17001/50000... loss 0.31636106967926025... accuracy 0.875...
iteration 18001/50000... loss 0.17190784215927124... accuracy 0.9166666865348816...
iteration 19001/50000... loss 0.11049345880746841... accuracy 1.0...
iteration 20001/50000... loss 0.06362085044384003... accuracy 1.0...
saved to models/pretrained_lstm.ckpt-20000
iteration 21001/50000... loss 0.19093847274780273... accuracy 0.9583333134651184...
iteration 22001/50000... loss 0.06586482375860214... accuracy 0.9583333134651184...
iteration 23001/50000... loss 0.02577809803187847... accuracy 1.0...
iteration 24001/50000... loss 0.0732395276427269... accuracy 0.9583333134651184...
iteration 25001/50000... loss 0.30879321694374084... accuracy 0.9583333134651184...
iteration 26001/50000... loss 0.2742778956890106... accuracy 0.9583333134651184...
iteration 27001/50000... loss 0.23742587864398956... accuracy 0.875...
iteration 28001/50000... loss 0.04694415628910065... accuracy 1.0...
iteration 29001/50000... loss 0.031666990369558334... accuracy 1.0...
iteration 30001/50000... loss 0.09171193093061447... accuracy 1.0...
saved to models/pretrained_lstm.ckpt-30000
iteration 31001/50000... loss 0.03852967545390129... accuracy 1.0...
iteration 32001/50000... loss 0.06964454054832458... accuracy 1.0...
iteration 33001/50000... loss 0.12447216361761093... accuracy 0.9583333134651184...
iteration 34001/50000... loss 0.008963108994066715... accuracy 1.0...
iteration 35001/50000... loss 0.04129207879304886... accuracy 0.9583333134651184...
iteration 36001/50000... loss 0.0081111378967762... accuracy 1.0...
iteration 37001/50000... loss 0.022405564785003662... accuracy 1.0...
iteration 38001/50000... loss 0.03473325073719025... accuracy 1.0...
iteration 39001/50000... loss 0.09315425157546997... accuracy 0.9583333134651184...
iteration 40001/50000... loss 0.3166258931159973... accuracy 0.9583333134651184...
saved to models/pretrained_lstm.ckpt-40000
iteration 41001/50000... loss 0.03648881986737251... accuracy 1.0...
iteration 42001/50000... loss 0.2616865932941437... accuracy 0.9583333134651184...
iteration 43001/50000... loss 0.013914794661104679... accuracy 1.0...
iteration 44001/50000... loss 0.020460862666368484... accuracy 1.0...
iteration 45001/50000... loss 0.15876878798007965... accuracy 0.9583333134651184...
iteration 46001/50000... loss 0.007766606751829386... accuracy 1.0...
iteration 47001/50000... loss 0.02079685777425766... accuracy 1.0...
iteration 48001/50000... loss 0.017801295965909958... accuracy 1.0...
iteration 49001/50000... loss 0.017789073288440704... accuracy 1.0...

在这里插入图片描述
在这里插入图片描述
查看上面的训练曲线,我们发现这个模型的训练结果还是不错的。损失值在稳定的下降,正确率也不断的在接近 100% 。然而,当分析训练曲线的时候,我们应该注意到我们的模型可能在训练集上面已经过拟合了。过拟合是机器学习中一个非常常见的问题,表示模型在训练集上面拟合的太好了,但是在测试集上面的泛化能力就会差很多。也就是说,如果你在训练集上面取得了损失值是 0 的模型,但是这个结果也不一定是最好的结果。当我们训练 LSTM 的时候,提前终止是一种常见的防止过拟合的方法。基本思路是,我们在训练集上面进行模型训练,同事不断的在测试集上面测量它的性能。一旦测试误差停止下降了,或者误差开始增大了,那么我们就需要停止训练了。因为这个迹象表明,我们网络的性能开始退化了。

导入一个预训练的模型需要使用 TensorFlow 的另一个会话函数,称为 Server ,然后利用这个会话函数来调用 restore 函数。这个函数包括两个参数,一个表示当前的会话,另一个表示保存的模型。

sess = tf.compat.v1.InteractiveSession()
saver = tf.compat.v1.train.Saver()
saver.restore(sess, tf.train.latest_checkpoint('models'))
INFO:tensorflow:Restoring parameters from models\pretrained_lstm.ckpt-40000

然后,从我们的测试集中导入一些电影评论。请注意,这些评论是模型从来没有看见过的。

iterations = 10
for i in range(iterations):
    nextBatch, nextBatchLabels = getTestBatch(batchSize,maxSeqLength,ids)
    print("Accuracy for this batch:", (sess.run(accuracy, {input_data: nextBatch, labels: nextBatchLabels})) * 100)
Accuracy for this batch: 91.6666686535
Accuracy for this batch: 79.1666686535
Accuracy for this batch: 87.5
Accuracy for this batch: 87.5
Accuracy for this batch: 91.6666686535
Accuracy for this batch: 75.0
Accuracy for this batch: 91.6666686535
Accuracy for this batch: 70.8333313465
Accuracy for this batch: 83.3333313465
Accuracy for this batch: 95.8333313465

参考文献

[1] PANG B,LEE L. Opinion mining and sentiment analysis[J].Foundations and Trends in InformationRetrieval,2008,2 (1 -2) :130 - 135.
[2] 赵妍妍,秦兵,刘挺,等. 文本倾向性分析[J]. 软件学报,2010,21(8) :1834 - 1848.
[3] 厉小军,戴霖,施寒潇,等. 文本倾向性分析综述[J]. 浙江大学学报,2011,45(7):1167 - 1175.
[4] HU M,LIU B. Mining and summarizing customer reviews[C]. NY,USA:Proceedings of Knowledge Discoveryand Da-ta Mining,2004:168 - 177.
[5] 朱嫣岚,闵锦,周雅倩,等. 基于 How Net 的词汇语义倾向计算[J]. 中文信息学报,2006,20(1):14 - 20.
[6] 张昊旻,石博莹,刘栩宏. 基于权值算法的中文情感分析系统研究与实现[J]. 计算机应用研究,201229 (12):4571 - 4573.

[7] 李方涛. 基于产品评论的情感分析研究[D]. 北京:清华大学,2011.

[8] PANG B,LEE L,VAITHYANATHAN S. Thumbs up:senti-ment classification using machine learning techniques [
C].PA,USA:Proceedings of the ACL - 02 Conference on Empir-ical methods in natural language processing - Volume 10,Stroudsburg,Association for Computational Linguistics,2002:79 - 86.
[9] MELVILLE P,GRYC W,LAWRENCE. Sentiment analysis ofblogs by combining lexical knowledge with text classification[C]. New York:Proceedings of SIGKDD,ACM,2009.
[10] LI S,HUANG C,ZHOU G. Employing personal impersonal viewsin supervised and semisupervised sentiment classification [C].New York:Proceedings of ACL,ACM,2010:414 - 423.
[11] KUMAR A,SEBASTIAN T M. Sentiment analysis on twitter[J]. International Journal of Computer ScienceIssues,2012,9(4) :628 - 633.
[12] ZHANG W,YU C,MENG W. Opinion retrieval from blogs[C]. Proceedings of the Sixteenth ACM Conferenceon Con-ference on Information and Knowledge Management,ACM,2007:831 - 840.
[13] ZHANG W,JIA L,YU C,et al. Improve the effectiveness ofthe opinion retrieval and opinion polarity classification [C].MA USA:Proceedings of the 17th ACM Conference on Infor-mation and Knowledge Management,ACM,2008:1415 - 1416.

主要参考了:文本情感分析研究综述 马 力1,宫玉龙2

猜你喜欢

转载自blog.csdn.net/qq_30868737/article/details/108651267
今日推荐