电影评论文本分类-tensorflow入门

来源于网络电影数据库(Internet Movie Database)的 IMDB 数据集(IMDB dataset),
其包含 50,000 条影评文本。从该数据集切割出的25,000条评论用作训练,另外 25,000 条用作测试。
训练集与测试集是平衡的(balanced),意味着它们包含相等数量的积极和消极评论。

  1. 查看数据格式 准备数据
  2. 构建模型
  3. 训练模型
  4. 评估模型
from tensorflow import keras
import matplotlib.pyplot as plt

# 下载数据集 该数据集已经经过预处理,评论(单词序列)已经被转换为整数序列,其中每个整数表示字典中的特定单词。
imdb = keras.datasets.imdb
# num_words=10000 保留了训练数据中最常出现的 10,000 个单词
(train_data, train_labels), (test_data, test_labels) = imdb.load_data(num_words=10000)

"""
查看数据格式
该数据集是经过预处理的:每个样本都是一个表示影评中词汇的整数数组。
每个标签都是一个值为 0 或 1 的整数值,其中 0 代表消极评论,1 代表积极评论。
"""
print("Training entries: {}, labels: {}".format(len(train_data), len(train_labels)))

# 评论文本被转换为整数值,其中每个整数代表词典中的一个单词。查看首条评论
print(train_data[0])

# 电影评论可能具有不同的长度 由于神经网络的输入必须是统一的长度,需要解决这个问题。
print(len(train_data[0]), len(train_data[1]), len(train_data[2]))

# 一个映射单词的整数索引的字典
word_index = imdb.get_word_index()
# 保留第一个索引
word_index = {k: (v + 3) for k, v in word_index.items()}
word_index["<PAD>"] = 0
word_index["<START>"] = 1
word_index["<UNK>"] = 2 # unknown
word_index["<UNUSED>"] = 3

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])

# 查看首条评论的文本内容
print(decode_review(train_data[0]))

准备数据

影评——即整数数组必须在输入神经网络之前转换为张量。这种转换可以通过以下两种方式来完成:

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

或者,可以填充数组来保证输入数据具有相同的长度,然后创建一个大小为 max_length * num_reviews 的整型张量。
可以使用能够处理此形状数据的嵌入层作为网络中的第一层。

使用第二种方法

# 由于电影评论长度必须相同,使用 pad_sequences 函数来使长度标准化
train_data = keras.preprocessing.sequence.pad_sequences(train_data,
value=word_index['<PAD>'],
padding='post',
maxlen=256)

test_data = keras.preprocessing.sequence.pad_sequences(test_data,
value=word_index['<PAD>'],
padding='post',
maxlen=256)
# 处理之后的样本长度
print(len(train_data[0]), len(train_data[1]), len(train_data[2]))

# 查看首条评论,此时已经填充
print(train_data[0])

构建模型

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

  1. 第一层是嵌入(Embedding)层。该层采用整数编码的词汇表,并查找每个词索引的嵌入向量(embedding vector)。这些向量是通过模型训练学习到的。 向量向输出数组增加了一个维度。得到的维度为:(batch, sequence, embedding)。
  2. 接下来,GlobalAveragePooling1D 将通过对序列维度求平均值来为每个样本返回一个定长输出向量。这允许模型以尽可能最简单的方式处理变长输入。
  3. 该定长输出向量通过一个有 16 个隐层单元的全连接(Dense)层传输。
  4. 最后一层与单个输出结点密集连接。使用 Sigmoid 激活函数,其函数值为介于 0 与 1 之间的浮点数,表示概率或置信度。
"""
构建模型

"""
# 输入形状是用于电影评论的词汇数目
vocab_size = 10000

model = keras.Sequential()
model.add(keras.layers.Embedding(vocab_size, 16))
model.add(keras.layers.GlobalAveragePooling1D())
model.add(keras.layers.Dense(16, activation='relu'))
model.add(keras.layers.Dense(1, activation='sigmoid'))

model.summary()
"""
隐层单元
上述模型在输入输出之间有两个中间层或“隐藏层”。输出(单元,结点或神经元)的数量即为层表示空间的维度。换句话说,是学习内部表示时网络所允许的自由度。

如果模型具有更多的隐层单元(更高维度的表示空间)和/或更多层,则可以学习到更复杂的表示。
但是,这会使网络的计算成本更高,并且可能导致学习到不需要的模式——一些能够在训练数据上而不是测试数据上改善性能的模式。
这被称为过拟合(overfitting)
"""
# 二分类问题且模型输出概率值(一个使用 sigmoid 激活函数的单一单元层),此处使用 binary_crossentropy 损失函数。
model.compile(optimizer='adam',
loss='binary_crossentropy',
metrics=['accuracy'])

# 创建验证集 检查模型在未见过的数据上的准确率(accuracy)
# 通过从原始训练数据中分离10000个样本来创建一个验证集. 目标是只使用训练数据来开发和调整模型,使用测试数据评估准确率,所以此处不用测试集
x_val = train_data[0:10000]
partial_x_train = train_data[10000:]

y_val = train_labels[0:10000]
partial_y_train = train_labels[10000:]

训练模型

以512个样本的mini-batch大小迭代40个epoch来训练模型。
这是指对 x_train 和 y_train 张量中所有样本的的 40 次迭代。在训练过程中,监测来自验证集的 10,000 个样本上的损失值(loss)和准确率(accuracy)

  • verbose:日志显示
  • verbose = 0 为不在标准输出流输出日志信息
  • verbose = 1 为输出进度条记录
  • verbose = 2 为每个epoch输出一行记录
  • 注意: 默认为 1
    history = model.fit(partial_x_train,
    partial_y_train,
    epochs=40,
    batch_size=512,
    validation_data=(x_val,y_val),
    verbose=1)

评估模型

点代表训练损失值(loss)与准确率(accuracy),实线代表验证损失值(loss)与准确率(accuracy)
训练损失值随每一个 epoch 下降而训练准确率(accuracy)随每一个 epoch 上升.
验证过程的损失值(loss)与准确率(accuracy)的情况并非如此,在20个epoch后达到峰值趋于平缓。
这是过拟合的一个实例:模型在训练数据上的表现比在以前从未见过的数据上的表现要更好。
在此之后,模型过度优化并学习特定于训练数据的表示,而不能够泛化到测试数据。
对于这种特殊情况,可以通过在 20 个左右的 epoch 后停止训练来避免过拟合。

# 评估模型
results = model.evaluate(test_data, test_labels, verbose=2)
print(results)


# 创建一个准确率(accuracy)和损失值(loss)随时间变化的图表 方便调参
# model.fit() 返回一个 History 对象,该对象包含一个字典,其中包含训练阶段所发生的一切事件
history_dict = history.history
history_dict.keys()

# 有'loss', 'accuracy', 'val_loss', 'val_accuracy'四个条目
# 在训练和验证期间,每个条目对应一个监控指标。可以使用这些条目来绘制训练与验证过程的损失值(loss)和准确率(accuracy),以便进行比较
acc = history_dict['accuracy']
val_acc = history_dict['val_accuracy']
loss = history_dict['loss']
val_loss = history_dict['val_loss']

epochs = range(1, len(acc) + 1)

# “bo”代表 "蓝点"
plt.plot(epochs, loss, 'bo', label='Training loss')
# b代表“蓝色实线”
plt.plot(epochs, val_loss, 'b', label= 'Validation loss')
plt.title('Training and validation loss')
plt.xlabel('Epochs')
plt.ylabel('Loss')
plt.legend()

plt.show()

# 清除数字
plt.clf()
plt.plot(epochs, acc, 'bo', label='Training acc')
plt.plot(epochs, val_acc, 'b', label='Validation acc')
plt.title('Training and validation accuracy')
plt.xlabel('Epochs')
plt.ylabel('Accuracy')
plt.legend()

plt.show()