700字范文,内容丰富有趣,生活中的好帮手!
700字范文 > 从零开始学keras之电影二分类

从零开始学keras之电影二分类

时间:2020-07-28 11:33:10

相关推荐

从零开始学keras之电影二分类

二分类问题可能是应用最广泛的机器学习问题。在这个例子中,你将学习根据电影评论的文字内容将其划分为正面或负面。

本博客使用 IMDB 数据集,它包含来自互联网电影数据库(IMDB)的 50 000 条严重两极分化的评论。数据集被分为用于训练的 25 000 条评论与用于测试的 25 000 条评论,训练集和测试集都包含 50% 的正面评论和 50% 的负面评论。

为什么要将训练集和测试集分开?因为你不应该将训练机器学习模型的同一批数据再用于测试模型!模型在训练数据上的表现很好,并不意味着它在前所未见的数据上也会表现得很好,而且你真正关心的是模型在新数据上的性能(因为你已经知道了训练数据对应的标签,显然不再需要模型来进行预测)。例如,你的模型最终可能只是记住了训练样本和目标值之间的映射关系,但这对在前所未见的数据上进行预测毫无用处。下一章将会更详细地讨论这一点。

与 MNIST 数据集一样,IMDB 数据集也内置于 Keras 库。它已经过预处理:评论(单词序列) 已经被转换为整数序列,其中每个整数代表字典中的某个单词。 下列代码将会加载 IMDB 数据集(第一次运行时会下载大约 80MB 的数据,可以不翻墙,反复试几次)。

import kerasfrom keras.datasets import imdb(train_data, train_labels), (test_data, test_labels) = imdb.load_data(num_words=10000)

参数 num_words=10000 的意思是仅保留训练数据中前 10 000 个最常出现的单词。低频单词将被舍弃。这样得到的向量数据不会太大,便于处理。

train_data 和 test_data 这两个变量都是评论组成的列表,每条评论又是单词索引组成 的列表(表示一系列单词)。train_labels 和 test_labels 都是 0 和 1 组成的列表,其中 0 代表负面(negative),1 代表正面(positive)。

train_data[0]train_labels[0]输出为1由于限定为前 10000 个最常见的单词,单词索引都不会超过 10 000。max([max(sequence) for sequence in train_data])输出为9999

准备数据

你不能将整数序列直接输入神经网络。你需要将列表转换为张量。转换方法有以下两种。

填充列表,使其具有相同的长度,再将列表转换成形状为 (samples, word_indices) 的整数张量,然后网络第一层使用能处理这种整数张量的层(即 Embedding 层,本书后面会详细介绍)。

对列表进行 one-hot 编码,将其转换为 0 和 1 组成的向量。举个例子,序列 [3, 5] 将会 被转换为 10 000 维向量,只有索引为 3 和 5 的元素是 1,其余元素都是 0。然后网络第一层可以用 Dense 层,它能够处理浮点数向量数据。

下面我们采用后一种方法将数据向量化。为了加深理解,你可以手动实现这一方法,如下所示。

import numpy as npdef vectorize_sequences(sequences, dimension=10000):# (创建一个形状为 (len(sequences), dimension) 的零矩阵)results = np.zeros((len(sequences), dimension))for i, sequence in enumerate(sequences):results[i, sequence] = 1. # (将 results[i] 的指定索引设为 1)return results# Our vectorized training data(将训练数据向量化)x_train = vectorize_sequences(train_data)# Our vectorized test data(将测试数据向量化)x_test = vectorize_sequences(test_data)

样本变为:

x_train[0]array([ 0., 1., 1., ..., 0., 0., 0.])

你还应该将标签向量化,这很简单。

y_train = np.asarray(train_labels).astype('float32')y_test = np.asarray(test_labels).astype('float32')

现在可以将数据输入到神经网络中。

构建网络

输入数据是向量,而标签是标量(1 和 0),这是你会遇到的最简单的情况。有一类网络在这种问题上表现很好, 就是带有 relu 激活的全连接层(Dense)的简单堆叠,比如Dense(16, activation='relu')。

传入Dense 层的参数(16)是该层隐藏单元的个数。一个隐藏单元(hidden unit)是该层 表示空间的一个维度。我们在第 2 章讲过,每个带有 relu 激活的 Dense 层都实现了下列张量运算:

output = relu(dot(W, input) + b)

16 个隐藏单元对应的权重矩阵 W 的形状为 (input_dimension, 16),与 W 做点积相当于将输入数据投影到 16 维表示空间中(然后再加上偏置向量 b 并应用 relu 运算)。你可以将表示空间的维度直观地理解为“网络学习内部表示时所拥有的自由度”。隐藏单元越多(即更高维的表示空间),网络越能够学到更加复杂的表示,但网络的计算代价也变得更大,而且可能会导致学到不好的模式(这种模式会提高训练数据上的性能,但不会提高测试数据上的性能)。对于这种 Dense 层的堆叠,你需要确定以下两个关键架构:

网络有多少层;

每层有多少个隐藏单元。

现在你选择下列架构:

两个中间层,每层都有 16 个隐藏单元;

第三层输出一个标量,预测当前评论的情感。

中间层使用 relu 作为激活函数,最后一层使用 sigmoid 激活以输出一个 0~1 范围内的概率值(表示样本的目标值等于 1 的可能性,即评论为正面的可能性)。relu(rectified linear unit,整流线性单元)函数将所有负值归零,而 sigmoid 函数则将任意值“压缩”到 [0,1] 区间内,其输出值可以看作概率值。

网络架构如下:

代码实现如下:

from keras import modelsfrom keras import layersmodel = models.Sequential()model.add(layers.Dense(16, activation='relu', input_shape=(10000,)))model.add(layers.Dense(16, activation='relu'))model.add(layers.Dense(1, activation='sigmoid'))

最后,你需要选择损失函数和优化器。由于你面对的是一个二分类问题,网络输出是一个概率值(网络最后一层使用 sigmoid 激活函数,仅包含一个单元),那么最好使用binary_crossentropy(二元交叉熵)损失。这并不是唯一可行的选择,比如你还可以使用mean_squared_error(均方误差)。但对于输出概率值的模型,交叉熵(crossentropy)往往是最好的选择。交叉熵是来自于信息论领域的概念,用于衡量概率分布之间的距离,在这个例子中就是真实分布与预测值之间的距离。

下面的步骤是用 rmsprop 优化器和binary_crossentropy损失函数来配置模型。注意,我们还在训练过程中监控精度。

pile(optimizer='rmsprop',loss='binary_crossentropy',metrics=['accuracy'])

上述代码将优化器、损失函数和指标作为字符串传入,这是因为 rmsprop、binary_ crossentropy 和 accuracy 都是 Keras 内置的一部分。有时你可能希望配置自定义优化器的 参数,或者传入自定义的损失函数或指标函数。前者可通过向 optimizer 参数传入一个优化器类实例来实现,如代码所示:

from keras import pile(optimizer=optimizers.RMSprop(lr=0.001),loss='binary_crossentropy',metrics=['accuracy'])

验证你的方法

为了在训练过程中监控模型在前所未见的数据上的精度,你需要将原始训练数据留出 10 000个样本作为验证集。

x_val = x_train[:10000]partial_x_train = x_train[10000:]y_val = y_train[:10000]partial_y_train = y_train[10000:]

现在使用 512 个样本组成的小批量,将模型训练 20 个轮次(即对 x_train 和 y_train 两 个张量中的所有样本进行 20 次迭代)。与此同时,你还要监控在留出的 10 000 个样本上的损失和精度。你可以通过将验证数据传入 validation_data 参数来完成。

history = model.fit(partial_x_train,partial_y_train,epochs=20,batch_size=512,validation_data=(x_val, y_val))

结果如下图:

调用 model.fit() 返回了一个 History 对象。这个对象有一个成员 history,它是一个字典,包含训练过程中的所有数据。我们来看一下。

history_dict = history.historyhistory_dict.keys()输出为:dict_keys(['val_loss', 'val_binary_accuracy', 'loss', 'binary_accuracy'])

字典中包含 4 个条目,对应训练过程和验证过程中监控的指标。在下面两个代码清单中, 我们将使用 Matplotlib 在同一张图上绘制训练损失和验证损失,以及训练精度和验证精度)。

import matplotlib.pyplot as plt%matplotlib inline #使显示的图像在notebook可见acc = history.history['binary_accuracy']val_acc = history.history['val_binary_accuracy']loss = history.history['loss']val_loss = history.history['val_loss']epochs = range(1, len(acc) + 1)# "bo" is for "blue dot"('bo' 表示蓝色圆点)plt.plot(epochs, loss, 'bo', label='Training loss')# b is for "solid blue line"('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() # clear figure(清空图像)acc_values = history_dict['binary_accuracy']val_acc_values = history_dict['val_binary_accuracy']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('Loss')plt.legend()plt.show()

点是训练损失和准确率,而实线是验证损失和准确性。 请注意,由于网络的随机初始化不同,您自己的结果可能略有不同。

如你所见,训练损失每轮都在降低,训练精度每轮都在提升。这就是梯度下降优化的预期 结果——你想要最小化的量随着每次迭代越来越小。但验证损失和验证精度并非如此:它们似 乎在第四轮达到最佳值。这就是我们之前警告过的一种情况:模型在训练数据上的表现越来越好, 但在前所未见的数据上不一定表现得越来越好。准确地说,你看到的是过拟合(overfit):在第二轮之后,你对训练数据过度优化,最终学到的表示仅针对于训练数据,无法泛化到训练集之外的数据。

在这种情况下,为了防止过拟合,你可以在 3 轮之后停止训练。通常来说,你可以使用许 多方法来降低过拟合,我们将在第 4 章中详细介绍。

我们从头开始训练一个新的网络,训练 4 轮,然后在测试数据上评估模型。

model = models.Sequential()model.add(layers.Dense(16, activation='relu', input_shape=(10000,)))model.add(layers.Dense(16, activation='relu'))model.add(layers.Dense(1, activation='sigmoid'))pile(optimizer='rmsprop',loss='binary_crossentropy',metrics=['accuracy'])model.fit(x_train, y_train, epochs=4, batch_size=512)results = model.evaluate(x_test, y_test)

迭代结果如下:

print(results)输出为:[0.32315461338043211, 0.87348000000000003]

这种相当简单的方法得到了 88% 的精度。

使用训练好的网络在新数据上生成预测结果

训练好网络之后,你希望将其用于实践。你可以用 predict 方法来得到评论为正面的可能性大小。

model.predict(x_test)输出为:array([[ 0.14026152],[ 0.99970287],[ 0.29552525],..., [ 0.07234977],[ 0.04342838],[ 0.48153383]], dtype=float32)

如你所见,网络对某些样本的结果非常确信(大于等于 0.99,或小于等于 0.01),但对其他结果却不那么确信(0.6 或 0.4)。

进一步改进

通过以下实验,你可以确信前面选择的网络架构是非常合理的,虽然仍有改进的空间。

前面使用了两个隐藏层。你可以尝试使用一个或三个隐藏层,然后观察对验证精度和测试精度的影响。

尝试使用更多或更少的隐藏单元,比如 32 个、64 个等。

尝试使用 mse 损失函数代替 binary_crossentropy。

尝试使用 tanh 激活(这种激活在神经网络早期非常流行)代替 relu。

这些实验将有助于说服您,我们所做的架构选择都是相当合理的,尽管它们仍然可以改进!

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。