本文会将文本形式的影评分为“正面”或“负面”影评。这是一个二元分类(又称为两类分类)的示例,也是一种重要且广泛适用的机器学习问题。
我们将使用包含来自网络电影数据库的50,000条电影评论文本的IMDB数据集,这些被分为25,000条训练评论和25,000条评估评论,训练和测试集是平衡的,这意味着它们包含相同数量的正面和负面评论。
本章节使用tf.keras,这是一个高级API,用于在TensorFlow中构建和训练模型,有关使用tf.keras的更高级文本分类教程,请参阅MLCC文本分类指南。
x1from __future__ import absolute_import, division, print_function, unicode_literals
2
3import tensorflow as tf
4from tensorflow import keras
5
6import numpy as np
7
8print(tf.__version__)
2.0.0-alpha0
IMDB数据集与TensorFlow一起打包,它已经被预处理,使得评论(单词序列)已被转换为整数序列,其中每个整数表示字典中的特定单词。
以下代码将IMDB数据集下载到您的计算机(如果您已经下载了它,则使用缓存副本):
xxxxxxxxxx
31imdb = keras.datasets.imdb
2
3(train_data, train_labels), (test_data, test_labels) = imdb.load_data(num_words=10000)
参数 num_words=10000
保留训练数据中最常出现的10,000个单词,丢弃罕见的单词以保持数据的大小可管理。
我们花一点时间来理解数据的格式,数据集经过预处理:每个示例都是一个整数数组,表示电影评论的单词。每个标签都是0或1的整数值,其中0表示负面评论,1表示正面评论。
xxxxxxxxxx
11print("Training entries: {}, labels: {}".format(len(train_data), len(train_labels)))
Training entries: 25000, labels: 25000
评论文本已转换为整数,其中每个整数表示字典中的特定单词。以下是第一篇评论的内容:
xxxxxxxxxx
11print(train_data[0])
[1, 14, 22, 16, 43, 530, 973, ...., 32, 15, 16, 5345, 19, 178, 32]
电影评论的长度可能不同,以下代码显示了第一次和第二次评论中的字数。由于对神经网络的输入必须是相同的长度,我们稍后需要解决此问题。
xxxxxxxxxx
11len(train_data[0]), len(train_data[1])
(218, 189)
了解如何将整数转换回文本可能很有用。 在这里,我们将创建一个辅助函数来查询包含整数到字符串映射的字典对象:
xxxxxxxxxx
141# 将单词映射到整数索引的字典
2word_index = imdb.get_word_index()
3
4# 第一个指数是保留的
5word_index = {k:(v+3) for k,v in word_index.items()}
6word_index["<PAD>"] = 0
7word_index["<START>"] = 1
8word_index["<UNK>"] = 2 # unknown
9word_index["<UNUSED>"] = 3
10
11reverse_word_index = dict([(value, key) for (key, value) in word_index.items()])
12
13def decode_review(text):
14 return ' '.join([reverse_word_index.get(i, '?') for i in text])
现在我们可以使用decode_review
函数显示第一次检查的文本:
xxxxxxxxxx
11decode_review(train_data[0])
"
影评(整数数组)必须转换为张量,然后才能馈送到神经网络中。我们可以通过以下两种方法实现这种转换:
num_words * num_reviews
的矩阵。max_length * num_reviews
的整数张量。我们可以使用一个能够处理这种形状的嵌入层作为网络中的第一层。在本教程中,我们将使用第二种方法。
由于电影评论的长度必须相同,我们将使用pad_sequences函数来标准化长度:
xxxxxxxxxx
91train_data = keras.preprocessing.sequence.pad_sequences(train_data,
2 value=word_index["<PAD>"],
3 padding='post',
4 maxlen=256)
5
6test_data = keras.preprocessing.sequence.pad_sequences(test_data,
7 value=word_index["<PAD>"],
8 padding='post',
9 maxlen=256)
我们再看一下数据的长度:
xxxxxxxxxx
11len(train_data[0]), len(train_data[1])
(256, 256)
并查看数据:
xxxxxxxxxx
11print(train_data[0])
xxxxxxxxxx
51[ 1 14 22 16 43 530 973 1622 1385 65 458 4468 66 3941
2 4 173 36 256 5 25 100 43 838 112 50 670 2 9
3 ...
4 0 0 0 0 0 0 0 0 0 0 0 0 0 0
5 0 0 0 0]
神经网络通过堆叠层创建而成,这需要做出两个架构方面的主要决策:
在本示例中,输入数据由字词-索引数组构成。要预测的标签是 0 或 1。接下来,我们为此问题构建一个模型:
xxxxxxxxxx
101# 输入形状是用于电影评论的词汇计数(10,000字)
2vocab_size = 10000
3
4model = keras.Sequential()
5model.add(keras.layers.Embedding(vocab_size, 16))
6model.add(keras.layers.GlobalAveragePooling1D())
7model.add(keras.layers.Dense(16, activation='relu'))
8model.add(keras.layers.Dense(1, activation='sigmoid'))
9
10model.summary()
xxxxxxxxxx
161Model: "sequential"
2_________________________________________________________________
3Layer (type) Output Shape Param #
4=================================================================
5embedding (Embedding) (None, None, 16) 160000
6_________________________________________________________________
7global_average_pooling1d (Gl (None, 16) 0
8_________________________________________________________________
9dense (Dense) (None, 16) 272
10_________________________________________________________________
11dense_1 (Dense) (None, 1) 17
12=================================================================
13Total params: 160,289
14Trainable params: 160,289
15Non-trainable params: 0
16_________________________________________________________________
这些层按顺序堆叠以构建分类器:
Embedding
层。该层采用整数编码的词汇表,并查找每个词索引的嵌入向量。这些向量是作为模型训练学习的,向量为输入数组添加维度,生成的维度为:(batch, sequence, embedding)
.GlobalAveragePooling1D
层通过对序列维度求平均值,针对每个样本返回一个长度固定的输出向量。这样,模型便能够以尽可能简单的方式处理各种长度的输入。sigmoid
激活函数后,结果是介于 0 到 1 之间的浮点值,表示概率或置信水平。上述模型在输入和输出之间有两个中间层(也称为“隐藏”层)。输出(单元、节点或神经元)的数量是相应层的表示法空间的维度。换句话说,该数值表示学习内部表示法时网络所允许的自由度。
如果模型具有更多隐藏单元(更高维度的表示空间)和/或更多层,则说明网络可以学习更复杂的表示法。不过,这会使网络耗费更多计算资源,并且可能导致学习不必要的模式(可以优化在训练数据上的表现,但不会优化在测试数据上的表现)。这称为过拟合,我们稍后会加以探讨。
模型需要一个损失函数和一个用于训练的优化器。由于这是一个二元分类问题,并且模型输出概率(网络最后一层使用sigmoid 激活函数,仅包含一个单元),那么最好使用binary_crossentropy
(二元交叉熵)损失。
这不是损失函数的唯一选择,例如,您可以选择mean_squared_error
(均方误差)。但对于输出概率值的模型,交叉熵(crossentropy)往往是最好
的选择。交叉熵是来自于信息论领域的概念,用于衡量概率分布之间的距离,在这个例子中就是真实分布与预测值之间的距离。。
在后面,当我们探索回归问题(比如预测房子的价格)时,我们将看到如何使用另一种称为均方误差的损失函数。
现在,配置模型以使用优化器和损失函数:
xxxxxxxxxx
31model.compile(optimizer='adam',
2 loss='binary_crossentropy',
3 metrics=['accuracy'])
在训练时,我们想要检查模型在以前没有见过的数据上的准确性。通过从原始训练数据中分离10,000个示例来创建验证集。(为什么不立即使用测试集?我们的目标是仅使用训练数据开发和调整我们的模型,然后仅使用测试数据来评估我们的准确性)。
xxxxxxxxxx
51x_val = train_data[:10000]
2partial_x_train = train_data[10000:]
3
4y_val = train_labels[:10000]
5partial_y_train = train_labels[10000:]
以512个样本的小批量训练模型40个周期,这是x_train
和y_train
张量中所有样本的40次迭代。在训练期间,监控模型在验证集中的10,000个样本的损失和准确性:
xxxxxxxxxx
61history = model.fit(partial_x_train,
2 partial_y_train,
3 epochs=40,
4 batch_size=512,
5 validation_data=(x_val, y_val),
6 verbose=1)
Epoch 40/40
15000/15000 [==============================] - 1s 54us/sample - loss: 0.0926 - accuracy: 0.9771 - val_loss: 0.3133 - val_accuracy: 0.8824
让我们看看模型的表现,将返回两个值,损失(表示我们的错误的数字,更低的值更好)和准确性。
xxxxxxxxxx
31results = model.evaluate(test_data, test_labels)
2
3print(results)
25000/25000 [==============================] - 1s 45us/sample - loss: 0.3334 - accuracy: 0.8704
[0.33341303256988525, 0.87036]
这种相当简单的方法实现了约87%的准确度,使用更先进的方法,模型应该接近95%。
model.fit()
返回一个History
对象,其中包含一个字典,其中包含训练期间发生的所有事情:
xxxxxxxxxx
21history_dict = history.history
2history_dict.keys()
xxxxxxxxxx
11 dict_keys(['loss', 'val_loss', 'accuracy', 'val_accuracy'])
有四个条目:在训练和验证期间,每个条目对应一个监控指标,我们可以使用这些来绘制训练和验证损失以进行比较,以及训练和验证准确性:
xxxxxxxxxx
191import matplotlib.pyplot as plt
2
3acc = history_dict['accuracy']
4val_acc = history_dict['val_accuracy']
5loss = history_dict['loss']
6val_loss = history_dict['val_loss']
7
8epochs = range(1, len(acc) + 1)
9
10# "bo" is for "blue dot"
11plt.plot(epochs, loss, 'bo', label='Training loss')
12# b is for "solid blue line"
13plt.plot(epochs, val_loss, 'b', label='Validation loss')
14plt.title('Training and validation loss')
15plt.xlabel('Epochs')
16plt.ylabel('Loss')
17plt.legend()
18
19plt.show()
<Figure size 640x480 with 1 Axes>
xxxxxxxxxx
101plt.clf() # clear figure
2
3plt.plot(epochs, acc, 'bo', label='Training acc')
4plt.plot(epochs, val_acc, 'b', label='Validation acc')
5plt.title('Training and validation accuracy')
6plt.xlabel('Epochs')
7plt.ylabel('Accuracy')
8plt.legend()
9
10plt.show()
在该图中,点表示训练损失和准确度,实线表示验证损失和准确度。
可以注意到,训练损失随着周期数的增加而降低,训练准确率随着周期数的增加而提高。在使用梯度下降法优化模型时,这属于正常现象(该方法应在每次迭代时尽可能降低目标值)。
验证损失和准确率的变化情况并非如此,它们似乎在大约 20 个周期后达到峰值。这是一种过拟合现象:模型在训练数据上的表现要优于在从未见过的数据上的表现。在此之后,模型会过度优化和学习特定于训练数据的表示法,而无法泛化到测试数据。
对于这种特殊情况,我们可以在大约 20 个周期后停止训练,防止出现过拟合。稍后,您将了解如何使用回调自动执行此操作。
最新版本:https://www.mashangxue123.com/tensorflow/tf2-tutorials-keras-basic_text_classification.html 英文版本:https://tensorflow.google.cn/beta/tutorials/keras/basic_text_classification 翻译建议PR:https://github.com/mashangxue/tensorflow2-zh/edit/master/r2/tutorials/keras/basic_text_classification.md