分类电影评论:二元分类的例子
两级分类或二元分类可能是应用最广泛的一种机器学习问题。 在这个例子中,我们将学习根据评论的文本内容将电影评论分为“积极”评论和“消极”评论。
IMDB数据集
我们将与“IMDB数据集”进行合作,这是一组来自互联网电影数据库的5万个高度极化的评论。它们分为25,000次培训评估和25,000次测试评估,每项评估包括50%的否定和50%的正面评估。为什么我们有这两个单独的培训和测试集?您不应该使用您曾经训练过的相同数据来测试机器学习模型!仅仅因为模型在训练数据上表现良好并不意味着它对于从未见过的数据表现良好,而且您真正关心的是您的模型在新数据上的表现(因为您已经知道训练数据的标签 - 显然你不需要你的模型来预测那些)。例如,您的模型可能最终只会记住您的训练样本与其目标之间的映射 - 对于预测之前从未见过的数据目标而言,这完全没有用处。我们将在下一章更详细地讨论这一点。
就像MNIST数据集一样,IMDB数据集也与Keras打包在一起。它已被预处理:评论(单词序列)已被转换为整数序列,其中每个整数代表字典中的特定单词。
下面的代码将加载数据集(当您第一次运行它时,大约80MB的数据将被下载到您的机器中):
from keras.datasets import imdb
(train_data, train_labels), (test_data, test_labels) = imdb.load_data(num_words=10000)
参数num_words = 10000意味着我们将只保留训练数据中前10000个最常出现的单词。 罕见的话将被丢弃。 这使我们能够处理可管理大小的矢量数据。
变量train_data和test_data是评论列表,每个评论是词索引的列表(编码词的序列)。 train_labels和test_labels是0和1的列表,其中0代表“否定”,1代表“正面”:
train_data[0]
train_labels[0]
由于我们限制在最常见的10,000个单词中,因此任何单词索引都不会超过10,000个:
max([max(sequence) for sequence in train_data])
对于踢球,以下是如何快速解码这些评论之一回到英文单词:
# word_index is a dictionary mapping words to an integer index
word_index = imdb.get_word_index()
# We reverse it, mapping integer indices to words
reverse_word_index = dict([(value, key) for (key, value) in word_index.items()])
# We decode the review; note that our indices were offset by 3
# because 0, 1 and 2 are reserved indices for "padding", "start of sequence", and "unknown".
decoded_review = ' '.join([reverse_word_index.get(i - 3, '?') for i in train_data[0]])
decoded_review
准备数据
我们无法将整数列表输入到神经网络中。 我们必须将我们的列表变成张量。 有两种方法可以做到这一点:
我们可以填充我们的列表,使它们都具有相同的长度,并将它们转换为整形张量(样本,word_indices),然后在我们的网络中作为第一层使用能够处理这种整数张量的层(嵌入层, 我们将在本书后面详细介绍)。
我们可以对我们的列表进行热编码,将它们变成0和1的向量。 具体地说,这意味着例如将序列[3,5]转换为10,000维向量,除了索引3和5之外,这将是全零。 然后,我们可以将我们网络中的第一层用作密集层,能够处理浮点向量数据。
我们将采用后一种解决方案。 让我们对我们的数据进行矢量化,我们将手动进行数据处理以获得最大清晰度:
import numpy as np
def vectorize_sequences(sequences, dimension=10000):
# Create an all-zero matrix of shape (len(sequences), dimension)
results = np.zeros((len(sequences), dimension))
for i, sequence in enumerate(sequences):
results[i, sequence] = 1. # set specific indices of results[i] to 1s
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]
我们还应该将我们的标签矢量化,这很简单:
y_train = np.asarray(train_labels).astype('float32')
y_test = np.asarray(test_labels).astype('float32')
现在我们的数据已准备好输入到神经网络中。
建立我们的网络
我们的输入数据仅仅是矢量,我们的标签是标量(1s和0s):这是您遇到的最简单的设置。一种在这样的问题上表现良好的网络将会是一个简单的完全连接(Dense)层的堆栈,并且具有relu激活:Dense(16,
活化= 'RELU')
传递给每个密集层(16)的参数是该图层的“隐藏单元”的数量。什么是隐藏单位?它是图层表示空间中的一个维度。您可能还记得,从前一章中可以看出,每个具有激活激活的密集层都实现了以下张量操作链:
输出= relu(点(W,输入)+ b)
具有16个隐藏单位意味着权重矩阵W将具有形状(input_dimension,16),即具有W的点积将投影数据投影到16维表示空间上(然后我们将添加偏移矢量b并应用relu操作)。您可以直观地理解表示空间的维度,即“在学习内部表示时允许网络拥有多少自由度”。拥有更多隐藏单元(更高维表示空间)可让您的网络学习更复杂的表示形式,但它会使您的网络在计算上更加昂贵,并可能导致学习不需要的模式(模式可提高训练数据的性能,但不会影响测试数据)。
关于这种密集层的堆栈有两个关键的体系结构决策:
多少层使用。
为每个图层选择多少个“隐藏单元”。
在下一章中,您将学习正式的原则来指导您做出这些选择。就目前而言,您必须通过以下架构选择信任我们:两个中间层,每个中间层有16个隐藏单元,第三个层将输出关于当前评论情感的标量预测。中间层将使用relu作为它们的“激活函数”,并且最后一层将使用sigmoid激活,以便输出概率(0到1之间的分数,表示样本有多大可能具有目标“1”即审查有多可能是积极的)。 relu(整型线性单位)是一个函数,旨在将负值清零,而sigmoid将任意值“压扁”到[0,1]区间,从而输出可解释为概率的内容。
以下是我们的网络的样子:
from keras import models
from keras import layers
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'))
最后,我们需要选择损失函数和优化器。 由于我们正面临二元分类问题,并且我们的网络输出是一个概率(我们用一个单元层结束了我们的网络,并且有一个S形激活),最好是使用binary_crossentropy损失。 它不是唯一可行的选择:例如,可以使用mean_squared_error。 但是当你处理输出概率的模型时,交叉熵通常是最好的选择。 交叉熵是来自信息论领域的量,它衡量概率分布之间的“距离”,或者在我们的例子中,是在地面实况分布和我们的预测之间。
以下是我们使用rmsprop优化器和binary_crossentropy损失函数配置模型的步骤。 请注意,我们还将在训练期间监控准确性。
model.compile(optimizer='rmsprop',
loss='binary_crossentropy',
metrics=['accuracy'])
我们将优化器,损失函数和指标作为字符串传递,这可能是因为rmsprop,binary_crossentropy和准确性被封装为Keras的一部分。 有时您可能想要配置优化器的参数,或者传递自定义丢失函数或度量函数。 前者可以通过传递优化器类实例作为优化器参数来完成:
from keras import optimizers
model.compile(optimizer=optimizers.RMSprop(lr=0.001),
loss='binary_crossentropy',
metrics=['accuracy'])
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'))
model.compile(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)
plt.clf() # clear figure
acc_values = history_dict['acc']
val_acc_values = history_dict['val_acc']
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()
import matplotlib.pyplot as plt
acc = history.history['acc']
val_acc = history.history['val_acc']
loss = history.history['loss']
val_loss = history.history['val_loss']
epochs = range(1, len(acc) + 1)
# "bo" is for "blue dot"
plt.plot(epochs, loss, 'bo', label='Training loss')
# b is for "solid blue line"
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()
history_dict = history.history
history_dict.keys()
history = model.fit(partial_x_train,
partial_y_train,
epochs=20,
batch_size=512,
validation_data=(x_val, y_val))
x_val = x_train[:10000]
partial_x_train = x_train[10000:]
y_val = y_train[:10000]
partial_y_train = y_train[10000:]
from keras import losses
from keras import metrics
model.compile(optimizer=optimizers.RMSprop(lr=0.001),
loss=losses.binary_crossentropy,
metrics=[metrics.binary_accuracy])