把一批数据分成两类,可能是机器学习应用最为广泛的分类方法。
与数据集 MNIST 相伴,IMDB 数据集也被打包进 Keras。影评已经预处理,文字序列转换成为数字序列,每个数字表示字典中的一个单词。下面的代码首次运行,会把约 80 MB 的数据下载到你的计算机。
载入数据集 IMDB
from keras.datasets import imdb
(train_data, train_labels), (test_data, test_labels) = imdb.load_data(num_words=10000)
变量 train_data 和 test_data 是影评列表。影评是单词索引的列表,单词索引是单词序列的编码。train_labels 和 test_labels 是由数字 0 和 1 组成的列表,0 代表负面评论,1 代表正面评论:
>>> train_data[0]
[1, 14, 22, 16, ... 178, 32]
>>> train_labels[0]
1
影评的词汇量为 10,000 个常用词,其索引最大值不超过 10,000:
>>> max([max(sequence) for sequence in train_data])
9999
把影评解码成英文单词,这样做:
word_index = imdb.get_word_index()
reverse_word_index = dict([(value, key) for (key, value) in word_index.items()])
decoded_review = ' '.join([reverse_word_index.get(i - 3, '?') for i in train_data[0]])
神经网络不能食用数字列表,必须把它们转成张量。有 2 个办法:
把整数序列编成二值矩阵
import numpy as np
def vectorize_sequences(sequences, dimension=10000):
results = np.zeros((len(sequences), dimension))
for i, sequence in enumerate(sequences):
results[i, sequence] = 1.
return results
x_train = vectorize_sequences(train_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(16,activation='relu')。
传递给 Dense 层的参数 16 是隐藏单元的数目。隐藏单元是本层表达空间的维度。Dense 层的激活函数 relu 实现以下张量运算:
output = relu(dot(W, input) + b)
16 个隐藏单元意味着权重矩阵 W 的形状是(输入维度,16):W 的点积运算在 16 维度的表达空间内产生数据(然后,加上偏置向量 b,进行 relu 运算)。更多的表达空间可使网络学习更复杂的表达式,但也要付出更多的计算代价,甚至导致不需要的学习模式(模式可以提高训练成绩,但不能提高测试成绩)。
决定 Dense 层架构的有两个要素:
在第 4 章,你将学习确定 Dense 层架构的指导原则。目前的选择是:
中间层用 relu 作为激活函数,最后一层用 S 型函数激活输出一概率值(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'))
如果没有激活函数,比如 relu,Dense 层成了两个线性运算 — 点积和加法:
output = dot(W, input) + b
于是,Danse 层只会学习对输入数据进行线性转换。多层网络因此失去意义。
最后,你需要选择一个损失函数。因为正在处理的二值分类问题,最好使用损失函数 binary_crossentropy。
编译模型
model.compile(optimizer='rmsprop', # 优化器
loss='binary_crossentropy', # 损失函数
metrics=['accuracy']) # 测控指标
优化器、损失函数、测控指标均可自行设置。例如:
设置优化器
from keras import optimizers
model.compile(optimizer=optimizers.RMSprop(lr=0.001),
loss='binary_crossentropy',
metrics=['accuracy'])
用户定义损失函数和测控指标
from keras import losses
from keras import metrics
model.compile(optimizer=optimizers.RMSprop(lr=0.001),
loss=losses.binary_crossentropy,
metrics=[metrics.binary_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:]
你将训练模型 20 轮次。也就是说,你要对张量 x_train 和 y_train 中的全部样本,迭代训练 20 次。同时,你将对分割的 10,000 个样本产生的损失和精度进行测控。为此,你要使用变参 validation_data 输入验证用样本。
训练你的模型
model.compile(optimizer='rmsprop',
loss='binary_crossentropy',
metrics=['acc'])
history = model.fit(partial_x_train,
partial_y_train,
epochs=20,
batch_size=512,
validation_data=(x_val, y_val))
注意, model.fit() 返回的对象 history 是个字典,保存着训练过程中的各种数据:
>>> history_dict = history.history
>>> history_dict.keys()
[u'acc', u'loss', u'val_acc', u'val_loss']
字典包括监测到的 4 个指标:前 2 个是训练的精度和损失;后 2 个是验证的精度和损失。可用 Matplotlib 画出这些指标。
画出训练和验证的损失
import matplotlib.pyplot as plt
history_dict = history.history
loss_values = history_dict['loss']
val_loss_values = history_dict['val_loss']
epochs = range(1, len(acc) + 1)
plt.plot(epochs, loss_values, 'bo', label='Training loss')
plt.plot(epochs, val_loss_values, 'b', label='Validation loss')
plt.title('Training and validation loss')
plt.xlabel('Epochs')
plt.ylabel('Loss')
plt.legend()
plt.show()
画出训练和验证的精度
plt.clf()
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()
你会看到,随着每次迭代训练的损失下降,训练的精度上升。这正是你所期待的梯度下降优化。不过,损失和精度的验证是另外一回事:它们似乎在第 4 次迭代时达到峰值。这个例子正是我们曾经警告过的:训练成绩好的模型,使用未曾见过的数据时,表现未必好。确切地说,这种现象是过度拟合。
在此,为了阻止过度拟合,第 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'))
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)
# 最终结果如下:
>>> results
[0.2929924130630493, 0.88327999999999995]
用稚朴的办法可达到 88% 的精度。 用最先进的技术可接近 95% 的精度。
你可以用 predict 方法预测影评属于正面的可能性:
>>> model.predict(x_test)
array([[ 0.98006207]
[ 0.99758697]
[ 0.99975556]
...,
[ 0.82167041]
[ 0.02885115]
[ 0.65371346]], dtype=float32)
如你所见,网络对一些样本的确信度高,如 0.99 或 0.01 等;也有的确信度低,如 0.6, 0.4 等。
以下的实验会帮你确信,以上网络架构虽有改进空间但已相当合理:
以下是你应该从本范例学到的: