TensorFlow 2之RNN LSTM 进行IMDB评论文本分类

  |   0 评论   |   0 浏览

背景

循环神经网络(Recurrent Neural Network, RNN)广泛适用于自然语言处理领域(Natural Language Processing, NLP)。

RNN有什么显著的特点呢?

普通的神经网络,每一层的输出是下一层的输入,每一层之间是相互独立的,没有关系。但是对于语言来说,一句话中的单词顺序不同,整个语义就完全变了。因此自然语言处理往往需要能够更好地处理序列信息的神经网络,RNN 能够满足这个需求。

RNN 中,隐藏层的状态,不仅取决于当前输入层的输出,还和上一步隐藏层的状态有关。

总结一下,RNN可以处理序列信息,普通的神经网络不能处理序列信息。

长短期记忆模型(Long short-term memory, LSTM)是一种特殊的RNN,主要是为了解决长序列训练过程中的梯度消失和梯度爆炸问题。简单来说,就是相比普通的RNN,LSTM能够在更长的序列中有更好的表现。

初体验

单层LSTM

使用单层LSTM,可以达到 0.8446 的准确率。

import matplotlib.pyplot as plt
import tensorflow_datasets as tfds
import tensorflow as tf
from tensorflow.keras import Sequential, layers

# 第一步 准备数据集
ds, info = tfds.load('imdb_reviews/subwords8k',
                     with_info=True,
                     as_supervised=True)
train_dataset, test_dataset = ds['train'], ds['test']

BUFFER_SIZE, BATCH_SIZE = 10000, 64
train_dataset = train_dataset.shuffle(BUFFER_SIZE)
train_dataset = train_dataset.padded_batch(
    BATCH_SIZE, tf.compat.v1.data.get_output_shapes(train_dataset))
test_dataset = test_dataset.padded_batch(
    BATCH_SIZE, tf.compat.v1.data.get_output_shapes(test_dataset))


# 第二步 预处理
#  通过 tfds 获取到的数据已经经过了文本预处理,即 Tokenizer,向量化文本(将文本转为数字序列)。
#  接下来我们看一看是如何转换的。
tokenizer = info.features['text'].encoder
# print('词汇个数:', tokenizer.vocab_size)
# # 词汇个数: 8185

# sample_str = 'hello world.'
# tokenized_str = tokenizer.encode(sample_str)
# print('向量化文本:', tokenized_str)
# # 向量化文本: [3618, 222, 562, 7975]

# for ts in tokenized_str:
#     print(ts, '-->', tokenizer.decode([ts]))
# # 3618 --> hell
# # 222 --> o
# # 562 --> world
# # 7975 --> .

#   可以看到,有些单词被拆分了。
#   因为 tokenizer 中不可能包含所有可能出现的单词,如果在 tokenizer 中没有的单词,就会被拆分。

#   文本预处理有很多种方式,比如我们在TensorFlow 2 中文文档 - IMDB 文本分类中使用了预训练好的字词嵌入模型 _google/tf2-preview/gnews-swivel-20dim/1_,
#   来直接将影评文本转换为向量;
#   还有非常出名的自然语言处理工具包 ntlk 在文本预处理环节提供了非常强大的功能。

# 第三步 搭建模型
#   搭建 RNN 模型
#   第一层使用了tf.keras.layers.Embedding, 这是由于 IMDB 数据集的预处理是按照单词在 tokenizer 中的下标来处理的,
#     维度(tokenizer.vocab_size)很高也很稀疏,经过 Embedding 层的转换,将产生大小固定为64的向量。
#     这个转换是可训练的,经过足够的训练之后,相似语义的句子将产生相似的向量。
#   在 LSTM 层外面套了一个壳(层封装器, layer wrappers): tf.keras.layers.Bidirectional,
#     这是 RNN 的双向封装器,用于对序列进行前向和后向计算。
model = Sequential([
    layers.Embedding(tokenizer.vocab_size, 64),
    layers.Bidirectional(layers.LSTM(64)),
    layers.Dense(64, activation='relu'),
    layers.Dense(1, activation='sigmoid')
])

# 第四步 编译模型
model.compile(loss='binary_crossentropy', optimizer='adam',
              metrics=['accuracy'])

# 第五步 训练模型
history1 = model.fit(train_dataset, epochs=3, validation_data=test_dataset)

# 第六步 评估准确率
loss, acc = model.evaluate(test_dataset)
print('准确率:', acc)  # 0.8446

# Epoch 1/3
# 391/391 [==============================] - 627s 2s/step - loss: 0.5206 - accuracy: 0.7319 - val_loss: 0.4284 - val_accuracy: 0.8260
# Epoch 2/3
# 391/391 [==============================] - 586s 1s/step - loss: 0.3277 - accuracy: 0.8692 - val_loss: 0.3980 - val_accuracy: 0.8301
# Epoch 3/3
# 391/391 [==============================] - 634s 2s/step - loss: 0.2513 - accuracy: 0.9043 - val_loss: 0.3879 - val_accuracy: 0.8446
# 391/391 [==============================] - 130s 333ms/step - loss: 0.3879 - accuracy: 0.8446
# 准确率: 0.8446400165557861

# 第七步 训练过程可视化
plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False
plt.rcParams['font.size'] = 20


def plot_graphs(history, name):
    plt.plot(history.history[name])
    plt.plot(history.history['val_' + name])
    plt.xlabel("Epochs")
    plt.ylabel(name)
    plt.legend([name, 'val_' + name])
    plt.show()


plot_graphs(history1, 'accuracy')

多层LSTM

在模型中,使用了双层 LSTM 模型,使正确率达到了0.8076。很遗憾,在我这里,使用双层 LSTM 模型,正确率并没有提升。

import matplotlib.pyplot as plt
import tensorflow_datasets as tfds
import tensorflow as tf
from tensorflow.keras import Sequential, layers

# 第一步 准备数据集
ds, info = tfds.load('imdb_reviews/subwords8k',
                     with_info=True,
                     as_supervised=True)
train_dataset, test_dataset = ds['train'], ds['test']

BUFFER_SIZE, BATCH_SIZE = 10000, 64
train_dataset = train_dataset.shuffle(BUFFER_SIZE)
train_dataset = train_dataset.padded_batch(
    BATCH_SIZE, tf.compat.v1.data.get_output_shapes(train_dataset))
test_dataset = test_dataset.padded_batch(
    BATCH_SIZE, tf.compat.v1.data.get_output_shapes(test_dataset))


# 第二步 预处理
#  通过 tfds 获取到的数据已经经过了文本预处理,即 Tokenizer,向量化文本(将文本转为数字序列)。
#  接下来我们看一看是如何转换的。
tokenizer = info.features['text'].encoder
# print('词汇个数:', tokenizer.vocab_size)
# # 词汇个数: 8185

# sample_str = 'hello world.'
# tokenized_str = tokenizer.encode(sample_str)
# print('向量化文本:', tokenized_str)
# # 向量化文本: [3618, 222, 562, 7975]

# for ts in tokenized_str:
#     print(ts, '-->', tokenizer.decode([ts]))
# # 3618 --> hell
# # 222 --> o
# # 562 --> world
# # 7975 --> .

#   可以看到,有些单词被拆分了。
#   因为 tokenizer 中不可能包含所有可能出现的单词,如果在 tokenizer 中没有的单词,就会被拆分。

#   文本预处理有很多种方式,比如我们在TensorFlow 2 中文文档 - IMDB 文本分类中使用了预训练好的字词嵌入模型 _google/tf2-preview/gnews-swivel-20dim/1_,
#   来直接将影评文本转换为向量;
#   还有非常出名的自然语言处理工具包 ntlk 在文本预处理环节提供了非常强大的功能。

# 第三步 搭建模型
#   搭建 RNN 模型,本例中,使用了双层 LSTM 模型。
#   第一层使用了tf.keras.layers.Embedding, 这是由于 IMDB 数据集的预处理是按照单词在 tokenizer 中的下标来处理的,
#     维度(tokenizer.vocab_size)很高也很稀疏,经过 Embedding 层的转换,将产生大小固定为64的向量。
#     这个转换是可训练的,经过足够的训练之后,相似语义的句子将产生相似的向量。
#   在 LSTM 层外面套了一个壳(层封装器, layer wrappers): tf.keras.layers.Bidirectional,
#     这是 RNN 的双向封装器,用于对序列进行前向和后向计算。
model = Sequential([
    layers.Embedding(tokenizer.vocab_size, 64),
    layers.Bidirectional(layers.LSTM(64, return_sequences=True)),
    layers.Bidirectional(layers.LSTM(32)),
    layers.Dense(64, activation='relu'),
    layers.Dense(1, activation='sigmoid')
])

# 第四步 编译模型
model.compile(loss='binary_crossentropy', optimizer='adam',
              metrics=['accuracy'])

# 第五步 训练模型
history1 = model.fit(train_dataset, epochs=3, validation_data=test_dataset)

# 第六步 评估准确率
loss, acc = model.evaluate(test_dataset)
print('准确率:', acc)  # 0.8076

# Epoch 1/3

# 391/391 [==============================] - 1256s 3s/step - loss: 0.5585 - accuracy: 0.7110 - val_loss: 0.6382 - val_accuracy: 0.6795

# Epoch 2/3

# 391/391 [==============================] - 1200s 3s/step - loss: 0.4193 - accuracy: 0.8183 - val_loss: 0.4558 - val_accuracy: 0.8122

# Epoch 3/3

# 391/391 [==============================] - 1223s 3s/step - loss: 0.4220 - accuracy: 0.8156 - val_loss: 0.4314 - val_accuracy: 0.8076

# 391/391 [==============================] - 251s 642ms/step - loss: 0.4314 - accuracy: 0.8076

# 准确率: 0.807640016078949


# 第七步 训练过程可视化
plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False
plt.rcParams['font.size'] = 20


def plot_graphs(history, name):
    plt.plot(history.history[name])
    plt.plot(history.history['val_' + name])
    plt.xlabel("Epochs")
    plt.ylabel(name)
    plt.legend([name, 'val_' + name])
    plt.show()


plot_graphs(history1, 'accuracy')
`

## 参考

* [TensorFlow 2 中文文档 - RNN LSTM 文本分类](https://geektutu.com/post/tf2doc-rnn-lstm-text.html)