原作者: Jon C-13
链接: https://medium.com/@jon.froiland/python-deep-learning-part-3-9d9e4cf9035c
深入到一个实际的Keras例子
二类分类,或称二元分类,可能是应用最广泛的一类机器学习问题。在本例中,您将学习根据影评的文本内容,将影评分为正面影评和负面影评。
您将使用IMDB数据集:一组来自Internet电影数据库的50000条高度两极分化的评论。他们分为25000个培训和25000个测试评估,每组包括50%的负面和50%的正面评估。
与MNIST数据集一样,IMDB数据集也附带Keras。它已经过了预处理:评论(单词序列)已经变成了整数序列,其中每个整数代表字典中的特定单词。
以下代码将加载数据集(当您第一次运行它时,大约80 MB的数据将下载到您的计算机上)。
>>> 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是评论列表;每个评论是单词索引列表(编码单词序列)。列车标签和测试标签是0和1的列表,其中0表示阴性,1表示阳性:
>>> train_data[0]
[1, 14, 22, 16, ... 178, 32]
>>> train_labels[0]
1
因为您将自己限制在前10000个最常用的单词,所以没有单词索引将超过10000个:
>>> max([max(sequence) for sequence in train_data])
9999
对于kicks,以下是如何快速将这些评论之一解码为英语单词的方法:
>>> word_index = imdb.get_word_index()
Downloading data from https://s3.amazonaws.com/text-datasets/imdb_word_index.json
1646592/1641221 [==============================] - 1s 0us/step
>>> 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]])
准备数据
你不能把整数表输入神经网络,你得把你的单子变成张量。以下有两种方法:
- 填充列表,使其具有相同的长度,将其转换为形状的整数张量(示例、字索引),然后将能够处理此类整数张量的层(嵌入层)用作网络中的第一层。
- 一种是对列表进行热编码,将其转换为0和1的向量。这意味着,例如,将序列[3,5]转换为10000维向量,除了索引3和5(即1)之外,其余的都是0。然后,可以使用密集层作为网络的第一层,能够处理浮点向量数据。
让我们使用后一种解决方案对数据进行矢量化,您将手动执行此操作以获得最大的清晰度。>>> 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):这是您遇到的最简单的设置。在这种问题上表现良好的一种网络是一个简单的完全连接(Dense)层堆栈,具有relu激活:密集(16,activation='relu')
传递给每个密集层(16)的参数是该层的隐藏单元数。隐藏单元是图层表示空间中的维度。从以下方程式:
output = relu(dot(W, input) + b)
具有16个隐藏单元意味着权重矩阵W将具有形状(input_dimension, 16):带W的点积将把输入数据投影到16维表示空间(然后添加偏移向量b并应用relu操作)。你可以直观地理解你的表现空间的维度是“当你学习内部表现时,你允许网络有多大的自由。”拥有更多的隐藏单元(更高维度的表现空间)可以让你的网络学习更复杂的表现,但这使得网络的计算成本更高,并可能导致学习不需要的模式(模式将提高训练数据的性能,而不是测试数据的性能)。
对于这样一堆Dense层,有两个关键的架构决策:
- 要使用多少层?
- 每层要选择多少个隐藏单元?
我们将进行以下操作: - 两个中间层,每个层有16个隐藏单元。
- 第三层,输出关于当前评论情绪的标量预测。
中间层将使用relu作为其激活函数,最后一层将使用sigmoid激活以输出概率(分数介于0和1之间,指示样本有多可能具有目标“1”:审查有多可能是阳性)。
relu(校正线性单位)是指将负值归零的函数:
而sigmoid“squashes”任意值到[0,1]区间,输出一些可以解释为概率的东西:。
从高层来看,网络就是这样:
下面是Keras实现,类似于前面看到的MNIST示例。
>>> 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'))
最后,您需要选择一个loss函数和一个优化器。因为你正面临一个二进制分类问题,并且你的网络输出是一个概率(你用一个单单位层结束你的网络并激活一个sigmoid),所以最好使用二进制交叉熵损失。这并不是唯一可行的选择:例如,可以使用mean_squared_error。但在处理输出概率的模型时,交叉熵通常是最佳选择。交叉熵是信息论领域的一个量,它测量概率分布之间的距离,或者在这种情况下,测量地面真实分布和你的预测之间的距离。
下面是使用rmsprop优化器和二进制交叉熵损失函数配置模型的步骤。请注意,您还将在训练期间监控准确性。
>>> model.compile(optimizer='rmsprop', loss='binary_crossentropy', metrics=['accuracy'])
您将优化器、损失函数和度量作为字符串传递,这是可能的,因为rmsprop、二进制交叉熵和准确性打包为Keras的一部分。有时,您可能需要配置优化器的参数,或传递自定义损失函数或度量函数。前者可以通过传递优化器类实例作为优化器参数来完成:
>>> from keras import optimizers
>>> model.compile(optimizer=optimizers.RMSprop(lr=0.001), loss='binary_crossentropy', metrics=['accuracy'])
后者可以通过将函数对象作为lossand或metrics参数传递来完成:
>>> from keras import losses
>>> from keras import metrics
>>> model.compile(optimizer=optimizers.RMSprop(lr=0.001), loss=losses.binary_crossentropy, metrics=[metrics.binary_accuracy])
验证您的方法
为了在训练期间监视模型对其以前从未见过的数据的准确性,您将通过从原始训练数据中分离10000个样本来创建验证集。
>>> 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_列和y_列张量中的所有样本进行20次迭代)。同时,您将监视您设置的10000个样本的丢失和准确性。通过将验证数据作为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))
Train on 15000 samples, validate on 10000 samples
Epoch 1/20
2018-08-31 12:21:39.007625: I tensorflow/core/platform/cpu_feature_guard.cc:141] Your CPU supports instructions that this TensorFlow binary was not compiled to use: AVX2 FMA
15000/15000 [==============================] - 3s 176us/step - loss: 0.5084 - acc: 0.7813 - val_loss: 0.3797 - val_acc: 0.8684
Epoch 2/20
15000/15000 [==============================] - 2s 114us/step - loss: 0.3004 - acc: 0.9047 - val_loss: 0.3004 - val_acc: 0.8897
Epoch 3/20
15000/15000 [==============================] - 2s 113us/step - loss: 0.2179 - acc: 0.9285 - val_loss: 0.3085 - val_acc: 0.8711
Epoch 4/20
15000/15000 [==============================] - 2s 114us/step - loss: 0.1750 - acc: 0.9437 - val_loss: 0.2840 - val_acc: 0.8832
Epoch 5/20
15000/15000 [==============================] - 2s 113us/step - loss: 0.1427 - acc: 0.9543 - val_loss: 0.2841 - val_acc: 0.8872
Epoch 6/20
15000/15000 [==============================] - 2s 114us/step - loss: 0.1150 - acc: 0.9650 - val_loss: 0.3165 - val_acc: 0.8772
Epoch 7/20
15000/15000 [==============================] - 2s 113us/step - loss: 0.0980 - acc: 0.9705 - val_loss: 0.3127 - val_acc: 0.8845
Epoch 8/20
15000/15000 [==============================] - 2s 113us/step - loss: 0.0807 - acc: 0.9763 - val_loss: 0.3859 - val_acc: 0.8649
Epoch 9/20
15000/15000 [==============================] - 2s 115us/step - loss: 0.0661 - acc: 0.9821 - val_loss: 0.3635 - val_acc: 0.8782
Epoch 10/20
15000/15000 [==============================] - 2s 119us/step - loss: 0.0561 - acc: 0.9853 - val_loss: 0.3843 - val_acc: 0.8792
Epoch 11/20
15000/15000 [==============================] - 2s 118us/step - loss: 0.0440 - acc: 0.9893 - val_loss: 0.4153 - val_acc: 0.8778
Epoch 12/20
15000/15000 [==============================] - 2s 115us/step - loss: 0.0382 - acc: 0.9919 - val_loss: 0.4524 - val_acc: 0.8690
Epoch 13/20
15000/15000 [==============================] - 2s 115us/step - loss: 0.0300 - acc: 0.9928 - val_loss: 0.4698 - val_acc: 0.8728
Epoch 14/20
15000/15000 [==============================] - 2s 115us/step - loss: 0.0247 - acc: 0.9946 - val_loss: 0.5023 - val_acc: 0.8730
Epoch 15/20
15000/15000 [==============================] - 2s 115us/step - loss: 0.0175 - acc: 0.9980 - val_loss: 0.5347 - val_acc: 0.8695
Epoch 16/20
15000/15000 [==============================] - 2s 114us/step - loss: 0.0150 - acc: 0.9979 - val_loss: 0.5709 - val_acc: 0.8699
Epoch 17/20
15000/15000 [==============================] - 2s 115us/step - loss: 0.0147 - acc: 0.9969 - val_loss: 0.6024 - val_acc: 0.8692
Epoch 18/20
15000/15000 [==============================] - 2s 115us/step - loss: 0.0085 - acc: 0.9993 - val_loss: 0.6854 - val_acc: 0.8628
Epoch 19/20
15000/15000 [==============================] - 2s 113us/step - loss: 0.0062 - acc: 0.9997 - val_loss: 0.7195 - val_acc: 0.8582
Epoch 20/20
15000/15000 [==============================] - 2s 115us/step - loss: 0.0110 - acc: 0.9973 - val_loss: 0.6993 - val_acc: 0.8659
在CPU上,每一个历元只需要不到几秒的时间——训练在20秒内结束。在每个历元结束时,当模型计算10000个验证数据样本的损失和准确性时,会有一个轻微的停顿。
注意,对model.fit()的调用返回一个历史对象。此对象有一个成员历史记录,它是一个字典,包含有关培训期间发生的所有事情的数据。让我们来看看:
>>> history_dict = history.history
>>> history_dict.keys()
dict_keys(['val_loss', 'loss', 'val_acc', 'acc'])
字典包含四个条目:在培训和验证期间监控的每个度量值一个。在下面的两个清单中,让我们使用Matplotlib并排绘制培训和验证损失。
绘制培训和验证损失:
>>> import matplotlib.pyplot as plt
>>> history_dict = history.history
>>> loss_values = history_dict['loss']
>>> val_loss_values = history_dict['val_loss']
>>> acc = history_dict['acc']
>>> val_acc = history_dict['val_acc']
>>> 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()
>>> 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()
注意:由于网络的随机初始化不同,您自己的结果可能略有不同。
如你所见,训练损失随历元而减少,训练精度随历元而增加。这就是你在运行梯度下降优化时所期望的——每次迭代你试图最小化的数量应该更少。但验证的损失和准确性并非如此:它们似乎在第四纪达到顶峰。这是我们之前警告过的一个例子:一个在训练数据上表现更好的模型不一定会在以前从未见过的数据上表现更好。准确地说,您看到的是过度拟合:在第二个纪元之后,您对训练数据进行了过度优化,最终学习的是特定于训练数据的表示,而不是泛化到训练集之外的数据。
在这种情况下,为了防止过度训练,你可以在三个阶段后停止训练。一般来说,可以使用一系列技术来减轻过度拟合。
让我们从头开始训练一个新的网络四个阶段,然后根据测试数据对其进行评估。
>>> 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)
Epoch 1/4
25000/25000 [==============================] - 2s 88us/step - loss: 0.4749 - acc: 0.8217
Epoch 2/4
25000/25000 [==============================] - 2s 83us/step - loss: 0.2658 - acc: 0.9097
Epoch 3/4
25000/25000 [==============================] - 2s 81us/step - loss: 0.1982 - acc: 0.9299
Epoch 4/4
25000/25000 [==============================] - 2s 79us/step - loss: 0.1679 - acc: 0.9404
>>> results = model.evaluate(x_test, y_test)
25000/25000 [==============================] - 2s 88us/step
最终结果如下:
>>> results
[0.3231498848247528, 0.87352]
这种相当简单的方法可以达到88%的准确率。使用最先进的方法,您应该能够接近95%。
使用经过训练的网络对新数据生成预测
训练完网络后,你会想在实际的环境中使用它。通过使用预测方法,您可以生成正面评论的可能性:
>>> model.predict(x_test)
array([[0.14028223],
[0.9997029 ],
[0.29558158],
...,
[0.07234909],
[0.04342629],
[0.48159957]], dtype=float32)
如您所见,网络对某些样本(0.99或更高,或0.01或更低)有信心,但对其他样本(0.29,0.48)没有信心。
以下实验将有助于说服您,您所做的架构选择(使用两个隐藏层)都是相当合理的,尽管仍有改进的余地:
- 尝试使用一个或三个隐藏层,看看这样做如何影响验证和测试精度。
- 尝试使用具有更多隐藏单位或更少隐藏单位的层:32个单位、64个单位,等等。
- 尝试使用mse损失函数而不是二进制交叉熵。
- 尝试使用tanh激活(在神经网络早期很流行的一种激活)而不是relu。
以下是您应该从这个示例中获得的信息: - 通常需要对原始数据进行大量的预处理,才能将其作为张量输入到神经网络中。单词序列可以编码为二进制向量,但也有其他编码选项。
- 具有relu激活的Dense层堆栈可以解决很多问题(包括情绪分类),您可能会经常使用它们。
- 在二进制分类问题(两个输出类)中,网络应该以一个单元的密集层和一个sigmoid激活结束:网络的输出应该是0到1之间的标量,编码概率。
- 对于二元分类问题的这种标量sigmoid输出,应该使用的损失函数是二元交叉熵。
- 无论您遇到什么问题,rmsprop优化器通常都是一个足够好的选择。你少担心一件事。
- 随着训练数据的改善,神经网络最终开始过度拟合,最终在从未见过的数据上获得越来越差的结果。确保始终监视训练集之外的数据的性能。
Chollet, François. Deep learning with Python. Shelter Island, NY: Manning Publications Co, 2018. Print.