TensorFlow深度学习实战——基于循环神经网络的情感分析模型
TensorFlow深度学习实战——基于循环神经网络的情感分析模型
- 0. 前言
- 1. 数据处理
- 2. 模型构建与训练
- 3. 模型评估
- 相关链接
0. 前言
情感分析 (Sentiment Analysis
) 是自然语言处理中的一项技术,旨在识别和提取文本中的情感信息,通常是分析一段文本中是否存在积极、消极或中立的情绪,广泛应用于社交媒体监控、客户反馈分析、产品评论分析等领域。我们已经学习了使用 TensorFlow 构建简单情感分析模型,在本节中,将实现基于循环神经网络的情感分析模型,进一步提高情感分析性能。
1. 数据处理
本节构建的基于循环神经网络的情感分析模型属于多对一结构,以句子为输入并预测其情感是正面还是负面。数据集是来自 UCI
机器学习库上的 Sentiment-labeled sentences
数据集,包含来自亚马逊、IMDb
和 Yelp
的 3,000
条评论句子,句子标记为 0
表示评论表达了负面情感,标记为 1
表示评论表达了正面情感。
(1) 首先,导入所需库:
import numpy as np
import os
import shutil
import tensorflow as tf
from sklearn.metrics import accuracy_score, confusion_matrix
(2) 数据集以压缩文件提供,解压后包含三个文件,每个文件包含一个提供者的标记句子,每行包含一个句子和一个标签,句子和标签之间用制表符分隔。首先下载压缩文件,然后将文件解析为(句子, 标签)对的列表:
def clean_logs(data_dir):logs_dir = os.path.join(data_dir, "logs")shutil.rmtree(logs_dir, ignore_errors=True)return logs_dirdef download_and_read(url):local_file = url.split('/')[-1]local_file = local_file.replace("%20", " ")p = tf.keras.utils.get_file(local_file, url, extract=True, cache_dir=".")local_folder = os.path.join("datasets", local_file.split('.')[0])labeled_sentences = []for labeled_filename in os.listdir(local_folder):if labeled_filename.endswith("_labelled.txt"):with open(os.path.join(local_folder, labeled_filename), "r") as f:for line in f:sentence, label = line.strip().split('\t')labeled_sentences.append((sentence, label))return labeled_sentences# clean up log area
data_dir = "./data"
logs_dir = clean_logs(data_dir)# download and read data into data structures
labeled_sentences = download_and_read("https://archive.ics.uci.edu/ml/machine-learning-databases/00331/sentiment%20labelled%20sentences.zip")
sentences = [s for (s, l) in labeled_sentences]
labels = [int(l) for (s, l) in labeled_sentences]
(3) 模型训练的目标是,使其能够根据输入的句子预测相应的情感标签。每个句子都是一个单词序列。然而,为了将其输入模型,我们必须将其转换为一个整数序列。序列中的每个整数指向一个单词,整数与单词的映射称为词汇表。因此,我们需要对句子进行词元化 (tokenize
) 并生成词汇表:
# tokenize sentences
tokenizer = tf.keras.preprocessing.text.Tokenizer()
tokenizer.fit_on_texts(sentences)
vocab_size = len(tokenizer.word_counts)
print("vocabulary size: {:d}".format(vocab_size))word2idx = tokenizer.word_index
idx2word = {v:k for (k, v) in word2idx.items()}
本节的词汇表包含 5,271
个唯一的单词。可以通过丢弃出现次数低于某个阈值的单词来缩小词汇表的大小,该阈值可以通过检查 tokenizer.word_counts
字典确定。在这种情况下,我们需要为词汇表添加一个用于表示未知单词 (UNK
) 的项,用以替换所有在词汇表中未找到的单词。
构建查找字典,以便在单词和索引之间进行转换。第一个字典在训练期间用于构建整数序列,以供网络使用。第二个字典用于将单词索引转换回单词,以便在后续的预测代码中使用。
(4) 每个句子的单词数可能不同。我们的模型需要为每个句子提供相同长度的整数序列。为了满足这个要求,通常会选择一个足够大的最大序列长度,以容纳训练集中大多数句子。较短的句子将用零填充,而较长的句子将被截断。选择最大序列长度的一个简单方法是查看不同百分位数位置的句子长度(即单词数):
seq_lengths = np.array([len(s.split()) for s in sentences])
print([(p, np.percentile(seq_lengths, p)) for p in [75, 80, 90, 95, 99, 100]])
输出结果如下所示:
[(75, 16.0), (80, 18.0), (90, 22.0), (95, 26.0), (99, 36.0), (100, 71.0)]
可以看到,最大句子长度为 71
个单词,但 99%
的句子长度都在 36
个单词以下,如果选择 64
作为阈值,大多数情况下不需要截断句子。可以多次交互式地运行以上代码块,以选择合适的词汇表大小和最大序列长度的值。在本节中,我们选择保留所有单词(即 vocab_size = 5271
),并将 max_seqlen
设置为 64
。
(5) 接下来,创建模型可用的数据集。首先使用训练好的词元分析器 (tokenizer
),将每个句子从单词序列 (sentences
) 转换为整数序列 (sentences_as_ints
),其中每个整数对应于词元分析器中单词的索引 (tokenizer.word_index
),然后对其进行截断并用 0
进行填充。标签同样转换为 NumPy
数组 labels_as_ints
,最后,将张量 sentences_as_ints
和 labels_as_ints
合并为一个 TensorFlow
数据集:
max_seqlen = 64# create dataset
sentences_as_ints = tokenizer.texts_to_sequences(sentences)
sentences_as_ints = tf.keras.preprocessing.sequence.pad_sequences(sentences_as_ints, maxlen=max_seqlen)
labels_as_ints = np.array(labels)
dataset = tf.data.Dataset.from_tensor_slices((sentences_as_ints, labels_as_ints))
(6) 将数据集的三分之一用于模型评估,剩余数据中,使用 10%
作为验证数据集,验证数据集将用于在训练过程中评估模型的进展,剩余部分作为训练数据集。最后,将每个数据集的批大小设为 64
:
# split into train and test
dataset = dataset.shuffle(10000)
test_size = len(sentences) // 3
val_size = (len(sentences) - test_size) // 10
test_dataset = dataset.take(test_size)
val_dataset = dataset.skip(test_size).take(val_size)
train_dataset = dataset.skip(test_size + val_size)batch_size = 64
train_dataset = train_dataset.batch(batch_size)
val_dataset = val_dataset.batch(batch_size)
test_dataset = test_dataset.batch(batch_size)
2. 模型构建与训练
定义模型。模型的每个输入句子是一个大小为 max_seqlen
(64
) 的整数序列,该序列输入到嵌入层中,该层将每个单词转换为一个向量,大小为 (vocab_size + 1)
, 其中 vocab_size
表示词汇表大小。额外的单词用于处理在 pad_sequences()
调用中引入的填充整数 0
。每个时间步的 64
个向量送入一个双向长短期记忆 (Long Short Term Memory
, LSTM
) 层,该层将每个单词转换为大小为 (64,)
的向量。LSTM
在每个时间步的输出送入一个使用 ReLU
激活的全连接层,生成一个大小为 (64, )
的向量。这个全连接层的输出再送入另一个全连接层,该层通过 sigmoid
激活在每个时间步输出一个大小为 (1, )
的向量。模型使用二元交叉熵损失函数和 Adam
优化器编译,然后训练 10
个 epochs
:
class SentimentAnalysisModel(tf.keras.Model):def __init__(self, vocab_size, max_seqlen, **kwargs):super(SentimentAnalysisModel, self).__init__(**kwargs)self.embedding = tf.keras.layers.Embedding(vocab_size, max_seqlen)self.bilstm = tf.keras.layers.Bidirectional(tf.keras.layers.LSTM(max_seqlen))self.dense = tf.keras.layers.Dense(64, activation="relu")self.out = tf.keras.layers.Dense(1, activation="sigmoid")def call(self, x):x = self.embedding(x)x = self.bilstm(x)x = self.dense(x)x = self.out(x)return xmodel = SentimentAnalysisModel(vocab_size+1, max_seqlen)
model.build(input_shape=(batch_size, max_seqlen))
model.summary()# compile
model.compile(loss="binary_crossentropy",optimizer="adam", metrics=["accuracy"]
)# train
best_model_file = os.path.join(data_dir, "best_model.h5")
checkpoint = tf.keras.callbacks.ModelCheckpoint(best_model_file,save_weights_only=True,save_best_only=True)
tensorboard = tf.keras.callbacks.TensorBoard(log_dir=logs_dir)
num_epochs = 10
history = model.fit(train_dataset, epochs=num_epochs, validation_data=val_dataset,callbacks=[checkpoint, tensorboard])
可以看到,模型在训练集上的准确率达到了 99.17%
,验证集的准确率约为 99.00%
。观察训练过程的损失变化情况,以了解模型何时开始在训练集上过拟合。可以看到,训练损失持续下降,但验证损失在经历最初的下降后开始上升。当验证损失开始上升时,模型在训练集上出现过拟合。
模型训练过程中在训练集和验证集上的准确率和损失变化情况如下所示:
3. 模型评估
(1) 检查点回调根据最低验证集损失值保存最佳模型,可以重新加载保存的最佳模型评估保留的测试集:
# evaluate with test set
best_model = SentimentAnalysisModel(vocab_size+1, max_seqlen)
best_model.build(input_shape=(batch_size, max_seqlen))
best_model.load_weights(best_model_file)
best_model.compile(loss="binary_crossentropy",optimizer="adam", metrics=["accuracy"]
)
(2) 对模型进行评估的最简单方法是调用 model.evaluate()
:
test_loss, test_acc = best_model.evaluate(test_dataset)
print("test loss: {:.3f}, test accuracy: {:.3f}".format(test_loss, test_acc))
输出结果如下所示:
test loss: 0.044, test accuracy: 0.990
(3) 还可以使用 model.predict()
获取预测结果,将它们与标签逐个比较,并使用外部工具(如 scikit-learn
)评估模型预测结果:
# predict on batches
labels, predictions = [], []
idx2word[0] = "PAD"
is_first_batch = True
for test_batch in test_dataset:inputs_b, labels_b = test_batchpred_batch = best_model.predict(inputs_b)predictions.extend([(1 if p > 0.5 else 0) for p in pred_batch])labels.extend([l for l in labels_b])if is_first_batch:for rid in range(inputs_b.shape[0]):words = [idx2word[idx] for idx in inputs_b[rid].numpy()]words = [w for w in words if w != "PAD"]sentence = " ".join(words)print("{:d}\t{:d}\t{:s}".format(labels[rid], predictions[rid], sentence))is_first_batch = Falseprint("accuracy score: {:.3f}".format(accuracy_score(labels, predictions)))
print("confusion matrix")
print(confusion_matrix(labels, predictions))
对于测试数据集中第一个批次的 64
个句子,我们重建句子并显示标签(第一列)以及模型的预测(第二列)。可以看到,模型在大多数句子中的预测都正确:
评估整个测试数据集的结果。可以看到,测试准确率与调用 evaluate()
方法的结果相同。生成混淆矩阵,在 1000
个测试样本中,情感分析网络正确预测了987次,错误预测了 13
次:
accuracy score: 0.987
confusion matrix
[[497 4][ 9 490]]
相关链接
TensorFlow深度学习实战(1)——神经网络与模型训练过程详解
TensorFlow深度学习实战(2)——使用TensorFlow构建神经网络
TensorFlow深度学习实战(3)——深度学习中常用激活函数详解
TensorFlow深度学习实战(4)——正则化技术详解
TensorFlow深度学习实战(5)——神经网络性能优化技术详解
TensorFlow深度学习实战(7)——分类任务详解
TensorFlow深度学习实战(8)——卷积神经网络
TensorFlow深度学习实战(12)——词嵌入技术详解
TensorFlow深度学习实战(13)——神经嵌入详解
TensorFlow深度学习实战(14)——循环神经网络详解
TensorFlow深度学习实战——基于循环神经网络的文本生成模型