教程 | TensorFlow 2.0 教程 ——机器学习基础 —— 文本分类

更新至 TensorFlow 2.0 alpha 版本

译自:官方 TensorFlow 教程


本教程使用评论文本对电影评论进行分类(正面或负面)。这是二分类的一个例子,一种重要且广泛适用的机器学习问题。

我们将使用包含来自 IMDB 的 50,000 条电影评论文本的 IMDB 数据集。它们被分为 25,000 条训练评论和 25,000 条测试评论。训练和测试集是平衡的,这意味着它们包含相同数量的正面和负面评论。

本教程使用 tf.keras,一个高级API,用于在 TensorFlow 中构建和训练模型。有关使用 tf.keras 进行更高级的文本分类教程,请参阅 MLCC 文本分类指南

!pip install -q tensorflow==2.0.0-alpha0
from __future__ import absolute_import, division, print_function

import tensorflow as tf
from tensorflow import keras

import numpy as np

print(tf.__version__)
2.0.0-alpha0

下载 IMDB 数据集

IMDB 数据集已包含在 TensorFlow 中,并且经过预处理,使得评论(单词序列)已经被转换为整数序列,其中每个整数表示字典中的一个特定单词。

以下代码将 IMDB 数据集下载到你的计算机中(如果你已经下载了它,则使用缓存副本):

imdb = keras.datasets.imdb

(train_data, train_labels), (test_data, test_labels) = imdb.load_data(num_words=10000)
Downloading data from https://storage.googleapis.com/tensorflow/tf-keras-datasets/imdb.npz
17465344/17464789 [==============================] - 0s 0us/step

参数 num_words=10000 保留了训练数据中最常出现的 10,000 个单词。丢弃少见的单词以保持数据大小的可管理性。


探索数据

让我们花一点时间来了解数据的格式。数据集经过预处理:每个样本都是一个整数数组,表示电影评论的单词。每个标签都是 0 或 1 的整数值,其中 0 表示负面评论,1 表示正面评论。

print("Training entries: {}, labels: {}".format(len(train_data), len(train_labels)))
Training entries: 25000, labels: 25000

评论文本已转换为整数,其中每个整数表示字典中的特定单词。以下是第一篇评论的内容:

print(train_data[0])
[1, 14, 22, 16, 43, 530, 973, 1622, 1385, 65, 458, 4468, 66, 3941, 4, 173, 36, 256, 5, 25, 100, 43, 838, 112, 50, 670, 2, 9, 35, 480, 284, 5, 150, 4, 172, 112, 167, 2, 336, 385, 39, 4, 172, 4536, 1111, 17, 546, 38, 13, 447, 4, 192, 50, 16, 6, 147, 2025, 19, 14, 22, 4, 1920, 4613, 469, 4, 22, 71, 87, 12, 16, 43, 530, 38, 76, 15, 13, 1247, 4, 22, 17, 515, 17, 12, 16, 626, 18, 2, 5, 62, 386, 12, 8, 316, 8, 106, 5, 4, 2223, 5244, 16, 480, 66, 3785, 33, 4, 130, 12, 16, 38, 619, 5, 25, 124, 51, 36, 135, 48, 25, 1415, 33, 6, 22, 12, 215, 28, 77, 52, 5, 14, 407, 16, 82, 2, 8, 4, 107, 117, 5952, 15, 256, 4, 2, 7, 3766, 5, 723, 36, 71, 43, 530, 476, 26, 400, 317, 46, 7, 4, 2, 1029, 13, 104, 88, 4, 381, 15, 297, 98, 32, 2071, 56, 26, 141, 6, 194, 7486, 18, 4, 226, 22, 21, 134, 476, 26, 480, 5, 144, 30, 5535, 18, 51, 36, 28, 224, 92, 25, 104, 4, 226, 65, 16, 38, 1334, 88, 12, 16, 283, 5, 16, 4472, 113, 103, 32, 15, 16, 5345, 19, 178, 32]

电影评论的长度可能不同。以下代码显示了第一个和第二个评论中的字数。由于神经网络的输入必须是相同的长度,因此我们稍后需要解决此问题。

len(train_data[0]), len(train_data[1])
(218, 189)

将整数转换回单词

了解如何将整数转换回文本可能很有用。这里,我们将创建一个辅助函数来查询包含整数到字符串映射的字典对象:

# 获取单词到整数索引的映射,格式为字典
word_index = imdb.get_word_index()

# 保留第一个索引
word_index = {k:(v+3) for k,v in word_index.items()} 
word_index["<PAD>"] = 0
word_index["<START>"] = 1
word_index["<UNK>"] = 2  # unknown
word_index["<UNUSED>"] = 3

reverse_word_index = dict([(value, key) for (key, value) in word_index.items()])

def decode_review(text):
    return ' '.join([reverse_word_index.get(i, '?') for i in text])
Downloading data from https://storage.googleapis.com/tensorflow/tf-keras-datasets/imdb_word_index.json
1646592/1641221 [==============================] - 0s 0us/step

现在我们可以使用 decode_review 函数显示第一个评论的文本:

decode_review(train_data[0])
"<START> this film was just brilliant casting location scenery story direction everyone's really suited the part they played and you could just imagine being there robert <UNK> is an amazing actor and now the same being director <UNK> father came from the same scottish island as myself so i loved the fact there was a real connection with this film the witty remarks throughout the film were great it was just brilliant so much that i bought the film as soon as it was released for <UNK> and would recommend it to everyone to watch and the fly fishing was amazing really cried at the end it was so sad and you know what they say if you cry at a film it must have been good and this definitely was also <UNK> to the two little boy's that played the <UNK> of norman and paul they were just brilliant children are often left out of the <UNK> list i think because the stars that play them all grown up are such a big profile for the whole film but these children are amazing and should be praised for what they have done don't you think the whole story was so lovely because it was true and was someone's life after all that was shared with us all"

准备数据

评论 —— 整数数组 —— 必须在输入神经网络之前转换为张量。这种转换可以通过以下两种方式完成:

  • 对数组进行独热编码,将其转换为 0 和 1 的向量。例如,序列 [3, 5] 将变成 10,000 维向量,除了索引 3 和 5(它们是1)之外全部为零。然后,将其作为我们网络中的第一层 —— 一个可以处理浮点矢量数据的 Dense 层。然而,这种方法是存储密集型的,需要 num_words * num_reviews 大小的矩阵。
  • 或者,我们可以填充数组,使它们都具有相同的长度,然后创建一个整数张量,大小为 max_length * num_reviews。我们可以使用能够处理这种大小的 Embedding 层作为我们网络中的第一层。

本教程中,我们将使用第二种方法。

由于电影评论的长度必须相同,我们将使用 pad_sequences 函数来标准化评论长度:

train_data = keras.preprocessing.sequence.pad_sequences(train_data,
                                                        value=word_index["<PAD>"],
                                                        padding='post',
                                                        maxlen=256)

test_data = keras.preprocessing.sequence.pad_sequences(test_data,
                                                       value=word_index["<PAD>"],
                                                       padding='post',
                                                       maxlen=256)

我们现在看一下样本的长度:

len(train_data[0]), len(train_data[1])
(256, 256)

并检查(填充后的)第一个评论:

print(train_data[0])
[   1   14   22   16   43  530  973 1622 1385   65  458 4468   66 3941
    4  173   36  256    5   25  100   43  838  112   50  670    2    9
   35  480  284    5  150    4  172  112  167    2  336  385   39    4
  172 4536 1111   17  546   38   13  447    4  192   50   16    6  147
 2025   19   14   22    4 1920 4613  469    4   22   71   87   12   16
   43  530   38   76   15   13 1247    4   22   17  515   17   12   16
  626   18    2    5   62  386   12    8  316    8  106    5    4 2223
 5244   16  480   66 3785   33    4  130   12   16   38  619    5   25
  124   51   36  135   48   25 1415   33    6   22   12  215   28   77
   52    5   14  407   16   82    2    8    4  107  117 5952   15  256
    4    2    7 3766    5  723   36   71   43  530  476   26  400  317
   46    7    4    2 1029   13  104   88    4  381   15  297   98   32
 2071   56   26  141    6  194 7486   18    4  226   22   21  134  476
   26  480    5  144   30 5535   18   51   36   28  224   92   25  104
    4  226   65   16   38 1334   88   12   16  283    5   16 4472  113
  103   32   15   16 5345   19  178   32    0    0    0    0    0    0
    0    0    0    0    0    0    0    0    0    0    0    0    0    0
    0    0    0    0    0    0    0    0    0    0    0    0    0    0
    0    0    0    0]

建立模型

神经网络是通过堆叠网络层创建的 —— 这需要两个主要的架构决策:

  • 模型中要使用多少层?
  • 每层使用多少隐藏单元?

在此示例中,输入数据由单词索引数组组成。要预测的标签是 0 或 1。让我们为这个问题建立一个模型:

# 输入大小是电影评论的词汇数量 (10,000 words)
vocab_size = 10000

model = keras.Sequential()
model.add(keras.layers.Embedding(vocab_size, 16))
model.add(keras.layers.GlobalAveragePooling1D())
model.add(keras.layers.Dense(16, activation='relu'))
model.add(keras.layers.Dense(1, activation='sigmoid'))

model.summary()
Model: "sequential"
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
embedding (Embedding)        (None, None, 16)          160000    
_________________________________________________________________
global_average_pooling1d (Gl (None, 16)                0         
_________________________________________________________________
dense (Dense)                (None, 16)                272       
_________________________________________________________________
dense_1 (Dense)              (None, 1)                 17        
=================================================================
Total params: 160,289
Trainable params: 160,289
Non-trainable params: 0
_________________________________________________________________

这些层按顺序堆叠以构建分类器:

  1. 第一层是 Embedding 层。该层采用整数编码的词汇表,并查找每个词索引的嵌入向量。这些向量在模型训练中进行学习,并为输出数组添加一个维度:(batch, sequence, embedding)
  2. 接下来,GlobalAveragePooling1D 层通过对序列维度求平均,使每个样本返回固定长度的输出向量。这允许模型以最简单的方式处理可变长度的输入。
  3. 这个固定长度的输出向量通过一个全连接的带有 16 个隐藏单元的 Dense 层传输。
  4. 最后一层与单个输出节点密集连接。使用 sigmoid 激活函数,输出是介于 0 和 1 之间的浮点数,表示概率或置信度。

隐藏单元

上述模型在输入和输出之间有两个中间层,或称为“隐藏”层。输出的数量(单元,节点或神经元)是网络层的表征空间维度。换句话说,在学习内部表征时允许网络的自由度。

如果模型具有更多隐藏单元(更高维度的表征空间),或者更多的网络层,则网络可以学习更复杂的表征。但是,它使网络的计算成本更高,并且可能导致学习到不需要的模式 —— 这些模式可以提高训练数据的性能,但不会提高测试数据的性能。这称为过拟合,我们稍后会进行探讨。

损失函数和优化器

模型需要一个损失函数和一个用于训练的优化器。由于这是二分类问题,模型输出的是概率(具有 sigmoid 激活的单个单元层),我们将使用 binary_crossentropy 损失函数。

这不是损失函数的唯一选择,例如,你可以选择 mean_squared_error。但是,通常用 binary_crossentropy 处理概率更好 —— 它度量概率分布之间的“距离”,在本例中,测量真实分布和预测之间的“距离”。

稍后,当我们探索回归问题(比如预测房子的价格)时,我们将看到如何使用另一种称为均方误差的损失函数。

现在,配置模型以使用优化器和损失函数:

model.compile(optimizer='adam',
              loss='binary_crossentropy',
              metrics=['accuracy'])

创建验证集

在训练时,我们想要检查模型在以前没有见过的数据上的准确性。通过从原始训练数据中分离 10,000 个样本来创建验证集。(为什么不使用测试集?我们的目标是仅使用训练数据开发和调整我们的模型,然后仅使用测试数据来评估我们的准确性)。

x_val = train_data[:10000]
partial_x_train = train_data[10000:]

y_val = train_labels[:10000]
partial_y_train = train_labels[10000:]

训练模型

使用包含 512 个样本的小批量训练模型 40 个周期。x_trainy_train 张量中所有样本需要进行 40 次迭代。在训练期间,监控模型在验证集中的 10,000 个样本的损失和准确率:

history = model.fit(partial_x_train,
                    partial_y_train,
                    epochs=40,
                    batch_size=512,
                    validation_data=(x_val, y_val),
                    verbose=1)
Train on 15000 samples, validate on 10000 samples
Epoch 1/40
15000/15000 [==============================] - 1s 43us/step - loss: 0.6987 - acc: 0.5008 - val_loss: 0.6940 - val_acc: 0.4948
Epoch 2/40
15000/15000 [==============================] - 0s 27us/step - loss: 0.6912 - acc: 0.5518 - val_loss: 0.6901 - val_acc: 0.5675
...
Epoch 39/40
15000/15000 [==============================] - 0s 26us/step - loss: 0.1819 - acc: 0.9370 - val_loss: 0.2885 - val_acc: 0.8860
Epoch 40/40
15000/15000 [==============================] - 0s 27us/step - loss: 0.1773 - acc: 0.9395 - val_loss: 0.2876 - val_acc: 0.8854

评估模型

让我们看看模型的表现,evaluate 方法将返回两个值。损失(代表我们的误差的值,值越低越好)和准确率。

results = model.evaluate(test_data, test_labels)

print(results)
25000/25000 [==============================] - 1s 45us/sample - loss: 0.3145 - accuracy: 0.8742
[0.3145076360368729, 0.87424]

这种相当简单的方法可以达到约 87% 的准确度率,采用更高级的方法,模型应该可以接近 95%。


创建准确率和损失的图表

model.fit() 返回一个包含字典的 History 对象,其中包括训练期间产生的所有数据:

history_dict = history.history
history_dict.keys()
dict_keys(['val_accuracy', 'accuracy', 'loss', 'val_loss'])

有四个元素:每个元素对应在训练和验证期间受监控的指标。我们可以使用这些来绘制训练和验证损失以及训练和验证准确率,以进行比较:

import matplotlib.pyplot as plt

acc = history_dict['accuracy']
val_acc = history_dict['val_accuracy']
loss = history_dict['loss']
val_loss = history_dict['val_loss']

epochs = range(1, len(acc) + 1)

# "bo" 表示 "blue dot(蓝点)"
plt.plot(epochs, loss, 'bo', label='Training loss')
# "b" 表示 "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()

这里写图片描述(原文缺少此图像)

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()

在这里插入图片描述

在上两图中,点表示训练损失和准确率,实线表示验证损失和准确率。

请注意,训练损失随着每个周期而减少,并且训练准确率随着每个周期而增加。这在使用梯度下降优化时是预料之中的 —— 它应该在每次迭代时最小化期望的数量。

这和验证损失和验证准确率不同 —— 它们似乎在大约二十个周期之后达到峰值。这是过拟合的一个例子:模型在训练数据上的表现比在以前从未见过的数据上表现得更好。在此之后,模型过度优化并学习特定于训练数据的表征,这些表征没有泛化至测试数据。

对于这种特殊情况,我们可以通过在大约二十个周期之后停止训练来防止过拟合。稍后,你将看到如何使用回调自动执行此操作。

  • 4
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值