CNN实战之如何分析影评-好看又有趣的讲解
前言
认识影评数据集
了解TextCNN模型
获取影评数据
生成文本数据集
生成TextCNN模型
评估模型
前言
话说老王买了两张电影票打算请女神小丽去看电影,老王希望看完电影趁着热度可以和小丽的关系更进一步。于是老王买了两张最近大火的《剩女日记》,看完电影,效果喜人,老王差点又做了单身狗。。。
在这里插入图片描述

认识影评数据集
为了杜绝这种乌龙事件的再度发生,老王决定通过大数据分析的手段对上映的电影进行一个预筛分,精准挑选优质电影。

通常而言,总会有一些人通过某些特殊手段提前了解到上映电影的剧情,然后很热情的写下观影报告,这些所谓的观影报告即影评。如果能将每个人的评价划分为电影值得看和电影不值得看,那么无疑对观众在选择是否观影可以提供很好的参考,尽管一千个人会看到一千个哈姆雷特,但是当数据量足够大时,基本上是可以代表一大部分人的真实体验的。

当电影评价被归纳为‘消极’和‘积极’两种分类时,问题就转变为了二分类问题,这是一个重要的且非常基础的机器学习问题。这种问题的处理过程如下图所示:
在这里插入图片描述

了解TextCNN模型

在这里插入图片描述

在这里插入图片描述

获取影评数据首先还是老生常谈的问题,对环境变量进行配置,代码如下:

from __future__ import absolute_import, division,
print_function, unicode_literals        #该行要放在第一行位置                                                     

import warnings                                                            

#忽略系统警告提示                                                         

warnings.filterwarnings('ignore')                                                

import tensorflow as tf                                                       
from tensorflow import keras                                                  
import numpy as np                                                         

print(tf.__version__)                                                          

以上是对环境的基本配置,其中包括基本变量的引入,该段代码应置于首行,否则会出现报错提醒。配置完毕基本运行环境后,开始下载数据集,关于电影评论的数据集IMDB,在tensorflow的打包文件中都有包含。该数据集经过预处理,评论(单词序列)已经被转换为整数序列,其中每个整数表示字典中的特定单词。具体代码如下:

imdb = keras.datasets.imdb                                                    

(train_data, train_labels), (test_data, test_labels) =
imdb.load_data(num_words=10000)      

上述代码中参数num_words=10000表示保留了训练数据中最常出现的 10,000 个单词,为了保持数据规模的可管理性,防止数据量无限变大,低频词汇将被丢弃。

生成文本数据集
在生成文本数据之前,首先要了解上文提到的整数序列的含义。上文提到过,下载的数据集都是经过预处理,也就是说每个样本都是一个表示影评中词汇的整数数组。每个标签都是一个值为0或1的整数值,其中0代表消极评论,1代表积极评论。这相当于对影评中每个单词对应一个数字,通过机器学习的方法,找到每个数字在积极评价或者消极评价中对应的权重,如图所示:
在这里插入图片描述

如图所示,影评中每个单词对应一个数字,其中相同的单词对应相同的数字。以上面两条评价为例,‘电影还不错’对应积极评价,‘电影很一般’对应消极评价,则得到下式:

在这里插入图片描述

在这里插入图片描述

"<START>
this film was just brilliant casting location scenery story direction
everyone's really suited the part they played and you could just imagine being
there robert <UNK> is an amazing actor and now the same being director
<UNK> father came from the same scottish island as myself so i loved the
fact there was a real connection with this film the witty remarks throughout
the film were great it was just brilliant so much that i bought the film as
soon as it was released for <UNK> and would recommend it to everyone to
watch and the fly fishing was amazing really cried at the end it was so sad and
you know what they say if you cry at a film it must have been good and this
definitely was also <UNK> to the two little boy's that played the
<UNK> of norman and paul they were just brilliant children are often left
out of the <UNK> list i think because the stars that play them all grown
up are such a big profile for the whole film but these children are amazing and
should be praised for what they have done don't you think the whole story was
so lovely because it was true and was someone's life after all that was shared
with us all" 

现在把它显示为数组形式,对应代码如下:

print(train_data[0])  

结果显示如下:

[1,14, 22, 16, 43, 530, 973, 1622, 1385, 65, 458, 4468, 66, 3941, 4, 173, 36, 256,
5, 25, 100, 43, 838, 112, 50, 670, 2, 9, 35, 480, 284, 5, 150, 4, 172, 112,
167, 2, 336, 385, 39, 4, 172, 4536, 1111, 17, 546, 38, 13, 447, 4, 192, 50, 16,
6, 147, 2025, 19, 14, 22, 4, 1920, 4613, 469, 4, 22, 71, 87, 12, 16, 43, 530,
38, 76, 15, 13, 1247, 4, 22, 17, 515, 17, 12, 16, 626, 18, 2, 5, 62, 386, 12,
8, 316, 8, 106, 5, 4, 2223, 5244, 16, 480, 66, 3785, 33, 4, 130, 12, 16, 38,
619, 5, 25, 124, 51, 36, 135, 48, 25, 1415, 33, 6, 22, 12, 215, 28, 77, 52, 5,
14, 407, 16, 82, 2, 8, 4, 107, 117, 5952, 15, 256, 4, 2, 7, 3766, 5, 723, 36,
71, 43, 530, 476, 26, 400, 317, 46, 7, 4, 2, 1029, 13, 104, 88, 4, 381, 15,
297, 98, 32, 2071, 56, 26, 141, 6, 194, 7486, 18, 4, 226, 22, 21, 134, 476, 26,
480, 5, 144, 30, 5535, 18, 51, 36, 28, 224, 92, 25, 104, 4, 226, 65, 16, 38,
1334, 88, 12, 16, 283, 5, 16, 4472, 113, 103, 32, 15, 16, 5345, 19, 178, 32]       

这里有一点要注意的是,转换的数组中每一个数字代表的是影视评价的一个单词,而非一个字母,这一点务必要搞清楚。

前面说过,在进行影评分析时,录入的数据都是预处理过的信息,那么如果想知道这些信息原来代表的评价的真实意思,该如何操作?这里给大家演示一个辅助函数,帮助大家查询一个整数数列到字符串之间通过映射关系所对应的对象信息:

#一个映射单词到整数索引的词典                                                  

word_index
= imdb.get_word_index()#建立词典索引                                

#保留第一个索引                                                            

word_index
= {k:(v+3) for k,v in word_index.items()}                                   

word_index["<PAD>"]
= 0#这里0代表<PAD>                                         

word_index["<START>"]
= 1#这里1代表<START>                                   

word_index["<UNK>"]
= 2#这里2代表<UNK>(unknown)                             

word_index["<UNUSED>"]
= 3#这里3代表<UNUSED>                              

reverse_word_index
= dict([(value, key) for (key, value) in word_index.items()])                     

def
decode_review(text):                                                       

    return ' '.join([reverse_word_index.get(i,
 '?') for i in text])                                                

首先建立一个索引,然后将key和value依次填入索引内部,标注特殊符号,之后就可以将原评价显示出来。

len(train_data[0]),
len(train_data[1])                                              

通过上述代码可以显示影评第一行和第二行的相关信息,很显然,不同的影评,内容和文字数量都很难相同:

 (218,189)      

这显然是不符合神经网络的输入要求的:由于神经网络的输入必须是张量形式,因此影评需要首先转换为张量,然后才可以进行学习,转换的方式有两种:

(1) 将数组转换为表示单词出现与否的由0和1组成的向量,类似于
one-hot 编码。例如,序列[3, 5]将转换为一个10,000维的向量,该向量除了索引为3和5的位置是1以外,其他都为
0。然后,将其作为网络的首层——一个可以处理浮点型向量数据的稠密层。显然,这种方法需要占用计算机大量的内存,需要一个大小为 num_words * num_reviews
的矩阵。

(2)可以通过填充数组的手段来保证输入数据具有相同的长度,然后创建一个大小为 max_length * num_reviews 的整型张量。可以使用能够处理此形状数据的嵌入层作为网络中的第一层。在本示例中将使用pad_sequences 函数来使长度标准化:

#训练数据长度设置为256                                                        

train_data =
keras.preprocessing.sequence.pad_sequences(train_data,                        

value=word_index["<PAD>"] ,
padding='post' , maxlen=256)                          

#测试数据长度设置为256                                                     

test_data =
keras.preprocessing.sequence.pad_sequences(test_data,                        

value=word_index["<PAD>"]
, padding='post' , maxlen=256)     

通过上面的标准化操作,再次来查看样本的长度:

len(train_data[0]),
len(train_data[1])                                              

(256, 256)         

对数据进行填充后,首条评论的张量形式如下所示:

在这里插入图片描述

如图所示,其中相同的数字代表相同单词,例如‘14’代表影评中的‘this’。

生成TextCNN模型
了解了影评数据的下载及原始数据预处理后,下一步就是对模型进行搭建,神经网络由堆叠的层来构建,这需要从两个主要方面来进行体系结构决策:模型里有多少层?每个层里有多少隐层单元(hidden units)?在此样本中,输入数据包含一个单词索引的数组。要预测的标签为0或1。在本案例中,首先为该问题构建一个模型,这里利用keras.Sequential进行层的序列化添加,具体代码如下:

# 输入形状是用于电影评论的词汇数目(10,000 词)                               

vocab_size = 10000                                                             


model = keras.sequential()#搭建层                                                   

model.add(keras.layers.Embedding(vocab_size,
16))#embedding 是一个将单词向量化的函#数,嵌入(embeddings)输出的形状都是:(num_examples,
embedding_dimension)         

model.add(keras.layers.GloabAveragePooling1D())#添加全局平均池化层                 

model.add(keras.layers.Dense(16, activation = 'relu'))
                            

model.add(keras.layers.Dense(1, activation =
'sigmoid'))           

                   


model.summary()                                                                        


Model: "sequential"                                                           

_________________________________________________________________             

Layer (type)              Output Shape                   Param   #                    

=================================================================  

embedding (Embedding)     (None, None, 16)                160000                 

_________________________________________________________________             

global_average_pooling1d (Gl (None, 16)                0                            

_________________________________________________________________             

dense (Dense)              (None, 16)                       272                        

_________________________________________________________________                

dense_1 (Dense)            (None, 1)                         17                      

=================================================================  

Total params: 160,289                                                            

Trainable params: 160,289                                                        


Non-trainable params: 0    

层按顺序堆叠以构建分类器:

第一层是嵌入(Embedding)层。该层采用整数编码的词汇表,并查找每个词索引的嵌入向量(embedding vector)。这些向量是通过模型训练学习到的。向量向输出数组增加了一个维度。得到的维度为:(batch, sequence,
embedding)。embedding是一个将单词向量化的函数,嵌入(embeddings)输出的形状都是:(num_examples,
embedding_dimension)

接下来,GlobalAveragePooling1D
将通过对序列维度求平均值来为每个样本返回一个定长输出向量。这允许模型以尽可能最简单的方式处理变长输入。

该定长输出向量通过一个有
16 个隐层单元的全连接(Dense)层传输。

最后一层与单个输出结点密集连接。使用 Sigmoid 激活函数,其函数值为介于 0 与
1 之间的浮点数,表示概率或置信度。

接下来需要对模型进行编译,编译的目的前文提过,此处不表,代码如下:

model.compile(optimize = 'adam',                                                    
loss = 'binary_crossentropy',                                            

          metrics = ['accuracy'])     

评估模型本文将模型评估分为三个步骤:验证模型、训练模型、评估模型。

   验证模型代码如下:



x_val = train_data[:10000]#取训练数据集前10000个进行训练和验证                  

partial_x_train = train_data[10000:]                                               

y_val = train_labels[:10000]#同理取前10000个标签                                

partial_y_train =
train_labels[10000:] 

训练模型代码如下:

#以 512 个样本的 mini-batch 大小迭代 40 个 epoch 来训练模型。这是指对 x_train 

#和 y_train 张量中所有样本的的 40 次迭代。在训练过程中,监测来自验证集的 #10,000 个样本上的损失值(loss)       和准确率(accuracy):                         

history = model.fit(partial_x_train,                                               


               
partial_y_train,                                              

               
epochs=40,                                                


               
batch_size=512,                                                

               
validation_data=(x_val, y_val),                                    

                verbose=1)      

训练过程如下所示:

Train on 15000 samples, validate on 10000 samples                                     

Epoch 1/40                                                                    


15000/15000 [==============================] - 1s 88us/sample -
loss: 0.6924 - accuracy: 0.6045 - val_loss: 0.6910 - val_accuracy: 0.6819                                  

Epoch 2/40                                                                   


15000/15000 [==============================] - 0s 22us/sample -
loss: 0.6885 - accuracy: 0.6392 - val_loss: 0.6856 - val_accuracy: 0.7129                            

Epoch 3/40                                                                    


15000/15000 [==============================] - 0s 22us/sample -
loss: 0.6798 - accuracy: 0.7371 - val_loss: 0.6747 - val_accuracy: 0.7141                                 

……                                                                       


Epoch 38/40                                                                     


15000/15000 [==============================] - 0s 24us/sample -
loss: 0.1036 - accuracy:   0.9715 -
val_loss: 0.3067 - val_accuracy: 0.8807                            

Epoch 39/40                                                                     


15000/15000 [==============================] - 0s 24us/sample -
loss: 0.0996 - accuracy: 0.9724 - val_loss: 0.3068 - val_accuracy: 0.8830                               

Epoch 40/40                                                                   


15000/15000
[==============================] - 0s 24us/sample - loss: 0.0956 - accuracy:
0.9749 - val_loss: 0.3109 - val_accuracy: 0.8823     

来看一下模型的性能如何。将返回两个值。损失值(loss)(一个表示误差的数字,值越低越好)与准确率(accuracy)。

模型评估代码如下:

results = model.evaluate(test_data, 
test_labels, verbose=2)                               

print(results)                                                                 




25000/1 - 2s - loss: 0.3454 - accuracy: 0.8732                                        

[0.32927662477493286,    0.8732]      

这种十分朴素的方法得到了约 87% 的准确率(accuracy)。若采用更好的方法,模型的准确率应当接近 95%。

老王掌握了判断电影优劣的方法后,兴冲冲的再一次约了女神小丽去看电影,结果果然不一样了

在这里插入图片描述