理论介绍完了,下面回到鉴定评论文本的情感属性这个案例。先把这些文本向量化,然后用Keras中最简单的循环网络神经结构—Simple RNN层,构建循环神经网络,鉴定一下哪些客户的留言是好评,哪些是差评。

7.4.1 用Tokenizer给文本分词

同学们可以在Kaggle网站通过关键字Product Comments搜索该数据集,然后基于该数据集新建Notebook。

读入这个评论文本数据集:

import pandas as pd # 导入Pandas

import numpy as np # 导入Num Py

dir = '../input/product-comments/'

dir_train = dir+'Clothing Reviews.csv'

df_train = pd.read_csv(dir_train) # 读入训练集

df_train.head() # 输出部分数据

训练集中的前五条数据

然后对数据集进行分词工作。词典的大小设定为2万。

from keras.preprocessing.text import Tokenizer # 导入分词工具

X_train_lst = df_train["Review Text"].values # 将评论读入张量(训练集)

y_train = df_train["Rating"].values # 构建标签集

dictionary_size = 20000 # 设定词典的大小

tokenizer = Tokenizer(num_words=dictionary_size) # 初始化词典

tokenizer.fit_on_texts( X_train_lst ) # 使用训练集创建词典索引

# 为所有的单词分配索引值,完成分词工作

X_train_tokenized_lst = tokenizer.texts_to_sequences(X_train_lst)

分词之后,如果随机显示X_train_tokenized_lst的几个数据,会看到完成了以下两个目标。

■评论句子已经被分解为单词。

■每个单词已经被分配一个唯一的词典索引。

X_train_tokenized_lst目前是列表类型的数据。

[[665, 75, 1, 135, 118, 178, 28, 560, 4639, 12576, 1226, 82, 324, 52, 2339, 18256,

51, 7266, 15, 63, 4997, 146, 6, 3858, 34, 121, 1262, 9902, 2843, 4, 49, 61, 267, 1,

403, 33, 1, 39, 27, 142, 71, 4093, 89, 3185, 3859, 2208, 1068],

[18257, 50, 2209, 13, 771, 6469, 71, 3485, 2562, 20, 93, 39, 952, 3186, 1194, 607,

5886, 184],

… … … …

[5, 1607, 19, 28, 2844, 53, 1030, 5, 637, 40, 27, 201, 15]]

还可以随机显示目前标签集的一个数据,目前y_train是形状为(22 641,)的张量:

[4]

下面将通过直方图显示各条评论中单词个数的分布情况,这个步骤是为词嵌入做准备:

import matplotlib.pyplot as plt # 导入matplotlib

word_per_comment = [len(comment) for comment in X_train_tokenized_lst]

plt.hist(word_per_comment, bins = np.arange(0,500,10)) # 显示评论长度分布

plt.show()

评论长度分布直方图如下图所示。

评论长度分布

上图中的评论长度分布情况表明多数评论的词数在120以内,所以我们只需要处理前120个词,就能够判定绝大多数评论的类型。如果这个数目太大,那么将来构造出的词嵌入张量就达不到密集矩阵的效果。而且,词数太长的序列,Simple RNN处理起来效果也不好。

下面的pad_sequences方法会把数据截取成相同的长度。如果长度大于120,将被截断;如果长度小于120,将填充无意义的0值。

from keras.preprocessing.sequence import pad_sequences

max_comment_length = 120 # 设定评论输入长度为120,并填充默认值(如字数少于120)

X_train = pad_sequences(X_train_tokenized_lst, maxlen=max_comment_length)

至此,分词工作就完成了。此时尚未做词嵌入的工作,因为词嵌入是要和神经网络的训练过程中一并进行的。

7.4.2 构建包含词嵌入的SimpleRNN 

现在通过Keras来构建一个含有词嵌入的Simple RNN:

from keras.models import Sequential # 导入序贯模型

from keras.layers.embeddings import Embedding #导入词嵌入层

from keras.layers import Dense #导入全连接层

from keras.layers import Simple RNN #导入Simple RNN层

embedding_vecor_length = 60 # 设定词嵌入向量长度为60

rnn = Sequential() #序贯模型

rnn.add(Embedding(dictionary_size, embedding_vecor_length,

input_length=max_comment_length)) # 加入词嵌入层

rnn.add(Simple RNN(100)) # 加入Simple RNN层

rnn.add(Dense(10, activation='relu')) # 加入全连接层

rnn.add(Dense(6, activation='softmax')) # 加入分类输出层

rnn.compile(loss='sparse_categorical_crossentropy', #损失函数

optimizer='adam', # 优化器

metrics=['acc']) # 评估指标

print(rnn.summary()) #输出网络模型

神经网络的构建我们已经相当熟悉了,并不需要太多的解释,这里的流程如下。

■先通过Embedding层进行词嵌入的工作,词嵌入之后学到的向量长度为60(密集矩阵),其维度远远小于词典的大小20 000(稀疏矩阵)。

■加一个含有100个神经元的Simple RNN层。

■再加一个含有10个神经元的全连接层。

■最后一个全连接层负责输出分类结果。使用Softmax函数激活的原因是我们试图实现的是一个从0到5的多元分类。

■编译网络时,损失函数选择的是sparse_categorical_crossentropy,我们是第一次使用这个损失函数,因为这个训练集的标签,是1,2,3,4,5这样的整数,而不是one-hot编码。优化器的选择是adam,评估指标还是选择acc。

网络结构如下:

_________________________________________________________________

Layer (type)         Output Shape        Param #

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

embedding_1 (Embedding)   (None, 300, 60)      1200000

_________________________________________________________________

simple_rnn_1 (Simple RNN)   (None, 100)        16100

_________________________________________________________________

dense_1 (Dense)        (None, 10)         1010

_________________________________________________________________

dense_2 (Dense)        (None, 6)         66

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

Total params: 1,217,176

Trainable params: 1,217,176

Non-trainable params: 0

_________________________________________________________________

7.4.3 训练网络并查看验证准确率

网络构建完成后,开始训练网络:

history = rnn.fit(X_train, y_train,

validation_split = 0.3,

epochs=10,

batch_size=64)

这里在训练网络的同时把原始训练集临时拆分成训练集和验证集,不使用测试集。而且理论上Kaggle竞赛根本不提供验证集的标签,因此也无法用验证集进行验证。

训练结果显示,10轮之后的验证准确率为0.5606:

Train on 7000 samples, validate on 3000 samples

Epoch 1/10

15848/15848 [============================] - 24s 1ms/step - loss: 1.2480 - acc:

0.5503 - val_loss: 1.2242 - val_acc: 0.5429

Epoch 2/10

15848/15848 [============================] - 35s 2ms/step - loss: 1.1596 - acc:

0.5622 - val_loss: 1.1692 - val_acc: 0.5520

… …

Epoch 10/10

15848/15848 [==============================] - 24s 2ms/step - loss: 0.8630 - acc:

0.6456 - val_loss: 1.1032 - val_acc: 0.5606

如果采用其他类型的前馈神经网络,其效率和RNN的成绩会相距甚远。如何进一步提高验证集准确率呢?我们下面会继续寻找方法。