【tf.keras】basic 02: Movie reviews Text classification

本文是 tf.keras教程的第二篇,使用Keras对电影评论(IMDB数据集)进行二分类(积极和消极)。数据集包含来自电影数据库的50,000个电影评论的文本。这些内容中消极和积极评论是平衡的,即各有25000条。



代码环境:

python version: 3.7.6 
tensorflow version: 2.1.0

导入必要的包:

import numpy as np
import tensorflow as tf
from tensorflow import keras

1. 数据处理

1.1 查看数据

imdb = keras.datasets.imdb
(train_data, train_labels), (test_data, test_labels) = imdb.load_data(num_words=10000)

参数 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)

1.2 将数据编码转换为单词

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

# 一个映射单词到整数索引的词典
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])

使用 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"

1.3 准备数据

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

  • 将数组转换为表示单词出现与否的由 0 和 1 组成的向量,类似于 one-hot 编码。例如,序列[3, 5]将转换为一个 10,000 维的向量,该向量除了索引为 3 和 5 的位置是 1 以外,其他都为 0。然后,将其作为网络的首层——一个可以处理浮点型向量数据的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)

[pad_sequences官方文档]

查看文本长度:

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

检查首条评论是否填充:

[   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]

2. 建模

2.1 顺序堆叠模型

神经网络由堆叠的层来构建,这需要从两个主要方面来进行体系结构决策:

  • 模型里有多少层?
  • 每个层里有多少隐层单元(hidden units)?
    在此样本中,输入数据包含一个单词索引的数组。要预测的标签为 0 或 1为该问题建模:
# 输入形状是用于电影评论的词汇数目(10,000 词)
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
_________________________________________________________________

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

  • 第一层是嵌入(Embedding)层。该层采用整数编码的词汇表,并查找每个词索引的嵌入向量(embedding vector)。这些向量是通过模型训练学习到的。向量向输出数组增加了一个维度。得到的维度为:(batch, sequence, embedding)。
  • 接下来,GlobalAveragePooling1D 将通过对序列维度求平均值来为每个样本返回一个定长输出向量。这允许模型以尽可能最简单的方式处理变长输入。
  • 该定长输出向量通过一个有 16 个隐层单元的全连接(Dense)层传输。
  • 最后一层与单个输出结点密集连接。使用 Sigmoid 激活函数,其函数值为介于 0 与 1 之间的浮点数,表示概率或置信度。

2.2 隐层单元数量设置

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

如果模型具有更多的隐层单元(更高维度的表示空间)和/或更多层,则可以学习到更复杂的表示。但是,这会使网络的计算成本更高,并且可能导致学习到不需要的模式——一些能够在训练数据上而不是测试数据上改善性能的模式,即过拟合(overfitting)。


2.3 损失函数与优化器选择

模型需要损失函数和优化器来进行训练。由于这是一个二分类问题且模型输出概率值(一个使用 sigmoid 激活函数的单一单元层),所以使用 binary_crossentropy 损失函数(二分类交交叉熵损失函数)。

这不是损失函数的唯一选择,例如,也可以选择 mean_squared_error (均方误差损失,mse)。但是,一般来说 binary_crossentropy 更适合处理概率——它能够度量概率分布之间的“距离”,本例中指的是度量 ground-truth 分布与预测值之间的“距离”。

配置模型来使用优化器和损失函数:

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

2.4 设置验证集

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

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

2.5 训练模型

history = model.fit(partial_x_train,
                    partial_y_train,
                    epochs=40,
                    batch_size=512,
                    validation_data=(x_val, y_val),
                    verbose=1)

输出:

Epoch 40/40
15000/15000 [==============================] - 1s 65us/sample - loss: 0.0891 - accuracy: 0.9779 - val_loss: 0.3147 - val_accuracy: 0.8814

2.6 评估模型

results = model.evaluate(test_data,  test_labels, verbose=2)
print(results)

输出:

25000/25000 - 2s - loss: 0.3356 - accuracy: 0.8718
[0.33557795378684996, 0.8718]

这种十分朴素的方法得到了约 87% 的准确率(accuracy)。若采用更好的方法,模型的准确率应当接近 95%。


3. 查看准确率和损失曲线

model.fit() 返回一个 History 对象,该对象包含一个字典,其中包含训练阶段所发生的一切事件:

history_dict = history.history
history_dict.keys()

输出:

dict_keys(['loss', 'accuracy', 'val_loss', 'val_accuracy'])

有四个条目:在训练和验证期间,每个条目对应一个监控指标。可以使用这些条目来绘制训练与验证过程的损失值(loss)和准确率(accuracy),以便进行比较。

损失曲线:

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)

plt.figure(figsize=(6,4), dpi=150)
# “bo”代表 "蓝点"
plt.plot(epochs, loss, 'bo', label='Training loss')
# b代表“蓝色实线”
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()

在这里插入图片描述
在该图中,蓝色圆点代表训练损失值(loss)与准确率(accuracy),蓝色实线代表验证损失值(loss)与准确率(accuracy)。

注意训练损失值随每一个 epoch 下降而训练准确率(accuracy)随每一个 epoch 上升。这在使用梯度下降优化所可预期的——理应在每次迭代中最小化期望值。

验证过程的损失值(loss)与准确率(accuracy)的情况却并非如此——它们似乎在 20 个 epoch 后达到峰值。这是过拟合的一个实例:模型在训练数据上的表现比在新数据上的表现要更好。在此之后,模型过度优化并学习特定于训练数据的表示,而不能够泛化到测试数据。直观上来看就是,训练损失不断下降,验证损失先下降后有上升趋势;训练准确率不断上升,验证准确率先上升后有下降趋势。可以认为这就是出现了过拟合,从而导致泛化能力很差。

避免过拟合的策略下篇介绍。


参考:
https://www.tensorflow.org/tutorials/keras/text_classification#

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章