在 TensorFlow.org 上查看
|
在 Google Colab 中运行
|
在 GitHub 上查看源码
|
下载笔记本
|
本文本分类教程在 IMDB 大型电影评论数据集上训练了一个循环神经网络 (RNN),用于情感分析。
设置
import numpy as np
import tensorflow_datasets as tfds
import tensorflow as tf
tfds.disable_progress_bar()
导入 matplotlib 并创建一个用于绘制图形的辅助函数
import matplotlib.pyplot as plt
def plot_graphs(history, metric):
plt.plot(history.history[metric])
plt.plot(history.history['val_'+metric], '')
plt.xlabel("Epochs")
plt.ylabel(metric)
plt.legend([metric, 'val_'+metric])
设置输入流水线
IMDB 大型电影评论数据集是一个二分类数据集——所有的评论要么是正面情感,要么是负面情感。
使用 TFDS 下载数据集。有关如何手动加载此类数据的详细信息,请参阅加载文本教程。
dataset, info = tfds.load('imdb_reviews', with_info=True,
as_supervised=True)
train_dataset, test_dataset = dataset['train'], dataset['test']
train_dataset.element_spec
最初,这会返回一个(文本,标签对)的数据集
for example, label in train_dataset.take(1):
print('text: ', example.numpy())
print('label: ', label.numpy())
接下来,对训练数据进行混洗,并创建这些 (文本, 标签) 对的批次
BUFFER_SIZE = 10000
BATCH_SIZE = 64
train_dataset = train_dataset.shuffle(BUFFER_SIZE).batch(BATCH_SIZE).prefetch(tf.data.AUTOTUNE)
test_dataset = test_dataset.batch(BATCH_SIZE).prefetch(tf.data.AUTOTUNE)
for example, label in train_dataset.take(1):
print('texts: ', example.numpy()[:3])
print()
print('labels: ', label.numpy()[:3])
创建文本编码器
由 tfds 加载的原始文本在用于模型之前需要进行处理。处理训练文本的最简单方法是使用 TextVectorization 层。该层具有许多功能,但本教程仅使用默认行为。
创建该层,并将数据集的文本传递给该层的 .adapt 方法
VOCAB_SIZE = 1000
encoder = tf.keras.layers.TextVectorization(
max_tokens=VOCAB_SIZE)
encoder.adapt(train_dataset.map(lambda text, label: text))
.adapt 方法会设置层的词汇表。这是前 20 个 token。在填充(padding)和未知 token 之后,它们按频率排序
vocab = np.array(encoder.get_vocabulary())
vocab[:20]
词汇表设置完成后,该层可以将文本编码为索引。索引张量会针对批次中最长的序列进行 0 填充(除非你设置了固定的 output_sequence_length)
encoded_example = encoder(example)[:3].numpy()
encoded_example
使用默认设置时,该过程并非完全可逆。主要原因有三个
preprocessing.TextVectorization的standardize参数的默认值为"lower_and_strip_punctuation"(转换为小写并去除标点符号)。- 有限的词汇量和缺乏基于字符的备选方案导致了一些未知 token 的产生。
for n in range(3):
print("Original: ", example[n].numpy())
print("Round-trip: ", " ".join(vocab[encoded_example[n]]))
print()
创建模型

上图是该模型的示意图。
此模型可以构建为
tf.keras.Sequential。第一层是
encoder,它将文本转换为 token 索引序列。编码器之后是一个嵌入层(Embedding layer)。嵌入层为每个单词存储一个向量。当被调用时,它将单词索引序列转换为向量序列。这些向量是可训练的。经过训练(在足够的数据上)后,含义相似的单词通常具有相似的向量。
这种索引查找比通过
tf.keras.layers.Dense层传递独热编码(one-hot encoded)向量的等效操作要高效得多。循环神经网络 (RNN) 通过迭代元素来处理序列输入。RNN 将一个时间步的输出传递给下一个时间步作为输入。
tf.keras.layers.Bidirectional封装器也可以与 RNN 层一起使用。它将输入通过 RNN 层向前和向后传播,然后拼接最终输出。双向 RNN 的主要优点是,输入开头的信号不需要经过每个时间步的处理就能影响输出。
双向 RNN 的主要缺点是,当单词被添加到末尾时,你无法高效地流式传输预测结果。
在 RNN 将序列转换为单个向量后,两个
layers.Dense层进行最后的处理,并从这种向量表示转换为作为分类输出的单个 logit。
实现该功能的代码如下
model = tf.keras.Sequential([
encoder,
tf.keras.layers.Embedding(
input_dim=len(encoder.get_vocabulary()),
output_dim=64,
# Use masking to handle the variable sequence lengths
mask_zero=True),
tf.keras.layers.Bidirectional(tf.keras.layers.LSTM(64)),
tf.keras.layers.Dense(64, activation='relu'),
tf.keras.layers.Dense(1)
])
请注意,此处使用了 Keras 序贯模型(sequential model),因为模型中的所有层都只有单个输入并产生单个输出。如果你想使用有状态(stateful)的 RNN 层,你可能需要使用 Keras 函数式 API 或模型子类化来构建模型,以便你可以检索和重用 RNN 层状态。请查阅 Keras RNN 指南了解更多详细信息。
嵌入层使用掩码(masking)来处理不同的序列长度。Embedding 之后的所有层都支持掩码
print([layer.supports_masking for layer in model.layers])
为了确认其按预期工作,请对一个句子进行两次评估。首先,单独评估,这样就没有需要掩盖的填充
# predict on a sample text without padding.
sample_text = ('The movie was cool. The animation and the graphics '
'were out of this world. I would recommend this movie.')
predictions = model.predict(np.array([sample_text]))
print(predictions[0])
现在,在一个较长的句子批次中再次评估它。结果应该是相同的
# predict on a sample text with padding
padding = "the " * 2000
predictions = model.predict(np.array([sample_text, padding]))
print(predictions[0])
编译 Keras 模型以配置训练过程
model.compile(loss=tf.keras.losses.BinaryCrossentropy(from_logits=True),
optimizer=tf.keras.optimizers.Adam(1e-4),
metrics=['accuracy'])
训练模型
history = model.fit(train_dataset, epochs=10,
validation_data=test_dataset,
validation_steps=30)
test_loss, test_acc = model.evaluate(test_dataset)
print('Test Loss:', test_loss)
print('Test Accuracy:', test_acc)
plt.figure(figsize=(16, 8))
plt.subplot(1, 2, 1)
plot_graphs(history, 'accuracy')
plt.ylim(None, 1)
plt.subplot(1, 2, 2)
plot_graphs(history, 'loss')
plt.ylim(0, None)
对一个新句子运行预测
如果预测值 >= 0.0,则为正面,否则为负面。
sample_text = ('The movie was cool. The animation and the graphics '
'were out of this world. I would recommend this movie.')
predictions = model.predict(np.array([sample_text]))
堆叠两个或多个 LSTM 层
Keras 循环层有两种可用模式,由 return_sequences 构造函数参数控制
如果为
False,则仅返回每个输入序列的最后一个输出(形状为 (batch_size, output_features) 的二维张量)。这是默认设置,在之前的模型中使用。如果为
True,则返回每个时间步的连续输出的完整序列(形状为(batch_size, timesteps, output_features)的三维张量)。
以下是 return_sequences=True 时信息流的样子

使用带有 return_sequences=True 的 RNN 的有趣之处在于,输出仍然具有 3 个轴(与输入一样),因此它可以传递给另一个 RNN 层,如下所示
model = tf.keras.Sequential([
encoder,
tf.keras.layers.Embedding(len(encoder.get_vocabulary()), 64, mask_zero=True),
tf.keras.layers.Bidirectional(tf.keras.layers.LSTM(64, return_sequences=True)),
tf.keras.layers.Bidirectional(tf.keras.layers.LSTM(32)),
tf.keras.layers.Dense(64, activation='relu'),
tf.keras.layers.Dropout(0.5),
tf.keras.layers.Dense(1)
])
model.compile(loss=tf.keras.losses.BinaryCrossentropy(from_logits=True),
optimizer=tf.keras.optimizers.Adam(1e-4),
metrics=['accuracy'])
history = model.fit(train_dataset, epochs=10,
validation_data=test_dataset,
validation_steps=30)
test_loss, test_acc = model.evaluate(test_dataset)
print('Test Loss:', test_loss)
print('Test Accuracy:', test_acc)
# predict on a sample text without padding.
sample_text = ('The movie was not good. The animation and the graphics '
'were terrible. I would not recommend this movie.')
predictions = model.predict(np.array([sample_text]))
print(predictions)
plt.figure(figsize=(16, 6))
plt.subplot(1, 2, 1)
plot_graphs(history, 'accuracy')
plt.subplot(1, 2, 2)
plot_graphs(history, 'loss')
查看其他现有的循环层,例如 GRU 层。
如果你有兴趣构建自定义 RNN,请参阅 Keras RNN 指南。
在 TensorFlow.org 上查看
在 Google Colab 中运行
在 GitHub 上查看源码
下载笔记本