情感分析是一种自然语言处理问题,可以理解文本并预测潜在意图。
在本文中,你将了解如何使用 Keras 深度学习库将电影评论的情绪预测为正面或负面。
看完这篇文章,你会知道:
让我们开始吧。
该数据集是 Large Movie Review Dataset,通常称为 IMDB 数据集。
IMDB 数据集包含 25,000 条高度极端的电影评论(好或坏)用于训练,同样数量用于测试。问题是确定给定的电影评论是否具有正面或负面情绪。
Keras 提供对IMDB 数据集的内置访问。
keras.datasets.imdb.load_data() 允许你以可用于神经网络和深度学习模型的格式加载数据集。
这些词已被表示该词在数据集中的绝对流行度的整数所取代。因此,每条评论中的句子都由一系列整数组成。
第一次调用 imdb.load_data() 会将 IMDB 数据集下载到你的计算机,并将其文件存储在 ~/.keras/datasets/imdb.pkl 下的主目录中。
imdb.load_data() 提供了额外的参数,包括要加载的最前面的词的数量、要跳过的最前面的词的数量,以及要支持的评论的最大长度。
让我们加载数据集并计算它的一些属性。你将从加载一些库和整个 IMDB 数据集作为训练数据集开始。
import numpy as np
from tensorflow.keras.datasets import imdb
import matplotlib.pyplot as plt
# load the dataset
(X_train, y_train), (X_test, y_test) = imdb.load_data()
X = np.concatenate((X_train, X_test), axis=0)
y = np.concatenate((y_train, y_test), axis=0)
...
接下来,你可以显示训练数据集的形状。
...
# summarize size
print("Training data: ")
print(X.shape)
print(y.shape)
运行此代码段,你可以看到有 50,000 条记录。
Training data:
(50000,)
(50000,)
你还可以打印唯一的类值。
...
# Summarize number of classes
print("Classes: ")
print(np.unique(y))
你可以看到它是评论中好情绪和坏情绪的二元分类问题。
Classes:
[0 1]
接下来,你可以了解数据集中唯一单词的总数。
...
# Summarize number of words
print("Number of words: ")
print(len(np.unique(np.hstack(X))))
有趣的是,你可以看到整个数据集中只有不到 100,000 个单词。
Number of words:
88585
最后,你可以了解平均评论长度。
...
# Summarize review length
print("Review length: ")
result = [len(x) for x in X]
print("Mean %.2f words (%f)" % (np.mean(result), np.std(result)))
# plot review length
plt.boxplot(result)
plt.show()
你可以看到平均评论不到 300 个单词,标准偏差刚刚超过 200 个单词。
Review length:
Mean 234.76 words (172.911495)
自然语言处理领域最近的一项突破称为词嵌入。离散词被映射到连续数字的向量。这在使用神经网络和深度学习模型处理自然语言问题时非常有用,因为它们需要数字作为输入。
Keras 提供了一种方便的方法,可以通过嵌入层将单词的正整数表示转换为单词嵌入。
该层采用定义映射的参数,包括预期单词的最大数量,也称为词汇表大小。该层还允许你指定每个词向量的维度,称为输出维度。
假设你只对数据集中前 5,000 个最常用的单词感兴趣。因此,你的词汇量将为 5,000。你可以选择使用 32 维向量来表示每个单词。最后,你可以选择将最大评论长度限制在 500 个单词,截断超过该长度的评论并用 0 值填充比该长度短的评论。
你将按如下方式加载 IMDB 数据集:
...
imdb.load_data(nb_words=5000)
然后,你将使用 Keras 实用程序使用 sequence.pad_sequences() 函数将数据集截断或填充到长度为 500 的每个观察值。
...
X_train = sequence.pad_sequences(X_train, maxlen=500)
X_test = sequence.pad_sequences(X_test, maxlen=500)
最后,模型的第一层将是使用 Embedding 类创建的词嵌入层,如下所示:
...
Embedding(5000, 32, input_length=500)
你可以从开发一个具有单个隐藏层的简单多层感知器模型开始。
让我们首先导入该模型所需的类和函数,并将随机数生成器初始化为常量值,以确保你可以轻松重现结果。
# MLP for the IMDB problem
from tensorflow.keras.datasets import imdb
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Dense
from tensorflow.keras.layers import Flatten
from tensorflow.keras.layers import Embedding
from tensorflow.keras.preprocessing import sequence
...
接下来,你将加载 IMDB 数据集。你将按照词嵌入部分中讨论的那样简化数据集——仅加载前 5,000 个词。
你还将使用 50/50 的数据集拆分为训练集和测试集。这是一个很好的标准拆分方法。
...
# load the dataset but only keep the top n words, zero the rest
top_words = 5000
(X_train, y_train), (X_test, y_test) = imdb.load_data(num_words=top_words)
你会将评论限制在 500 字以内,截断较长的评论并用零填充较短的评论。
...
max_words = 500
X_train = sequence.pad_sequences(X_train, maxlen=max_words)
X_test = sequence.pad_sequences(X_test, maxlen=max_words)
现在,你可以创建模型了。你将使用嵌入层作为输入层,将词汇量设置为 5,000,将词向量大小设置为 32 维,并将 input_length 设置为 500。第一层的输出将是一个 32×500 大小的矩阵,如上一节。
你会将嵌入式层的输出展平为一维,然后使用一个包含 250 个单元的密集隐藏层和整流器激活函数。输出层有一个神经元,将使用 sigmoid 激活输出 0 和 1 的值作为预测。
该模型使用对数损失,并使用高效的 ADAM 优化程序进行优化。
...
# create the model
model = Sequential()
model.add(Embedding(top_words, 32, input_length=max_words))
model.add(Flatten())
model.add(Dense(250, activation='relu'))
model.add(Dense(1, activation='sigmoid'))
model.compile(loss='binary_crossentropy', optimizer='adam', metrics=['accuracy'])
model.summary()
你可以拟合模型并在训练时使用测试集作为验证。该模型过拟合非常快,因此你将使用很少的训练时期,在这种情况下,只需 2 个。
数据很多,因此你将使用 128 的批量大小。训练模型后,你将评估其在测试数据集上的准确性。
...
# Fit the model
model.fit(X_train, y_train, validation_data=(X_test, y_test), epochs=2, batch_size=128, verbose=2)
# Final evaluation of the model
scores = model.evaluate(X_test, y_test, verbose=0)
print("Accuracy: %.2f%%" % (scores[1]*100))
将所有这些结合在一起,下面提供了完整的代码清单。
# MLP for the IMDB problem
from tensorflow.keras.datasets import imdb
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Dense
from tensorflow.keras.layers import Flatten
from tensorflow.keras.layers import Embedding
from tensorflow.keras.preprocessing import sequence
# load the dataset but only keep the top n words, zero the rest
top_words = 5000
(X_train, y_train), (X_test, y_test) = imdb.load_data(num_words=top_words)
max_words = 500
X_train = sequence.pad_sequences(X_train, maxlen=max_words)
X_test = sequence.pad_sequences(X_test, maxlen=max_words)
# create the model
model = Sequential()
model.add(Embedding(top_words, 32, input_length=max_words))
model.add(Flatten())
model.add(Dense(250, activation='relu'))
model.add(Dense(1, activation='sigmoid'))
model.compile(loss='binary_crossentropy', optimizer='adam', metrics=['accuracy'])
model.summary()
# Fit the model
model.fit(X_train, y_train, validation_data=(X_test, y_test), epochs=2, batch_size=128, verbose=2)
# Final evaluation of the model
scores = model.evaluate(X_test, y_test, verbose=0)
print("Accuracy: %.2f%%" % (scores[1]*100))
你可以看到,这个非常简单的模型以最小的努力获得了 87% 的分数。
Epoch 1/2
196/196 - 4s - loss: 0.5579 - accuracy: 0.6664 - val_loss: 0.3060 - val_accuracy: 0.8700 - 4s/epoch - 20ms/step
Epoch 2/2
196/196 - 4s - loss: 0.2108 - accuracy: 0.9165 - val_loss: 0.3006 - val_accuracy: 0.8731 - 4s/epoch - 19ms/step
Accuracy: 87.31%
卷积神经网络旨在尊重图像数据中的空间结构,同时对场景中学习对象的位置和方向具有鲁棒性。
同样的原则也可以用在序列上,比如电影评论中的一维单词序列。使 CNN 模型对学习识别图像中的对象具有吸引力的相同属性可以帮助学习单词段落中的结构,即对特征特定位置的不变性技术。
Keras 分别支持 Conv1D 和 MaxPooling1D 类的一维卷积和池化。
同样,让我们导入此示例所需的类和函数,并将你的随机数生成器初始化为常量值,以便你可以轻松地重现结果。
# CNN for the IMDB problem
from tensorflow.keras.datasets import imdb
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Dense
from tensorflow.keras.layers import Flatten
from tensorflow.keras.layers import Conv1D
from tensorflow.keras.layers import MaxPooling1D
from tensorflow.keras.layers import Embedding
from tensorflow.keras.preprocessing import sequence
你还可以像以前一样加载和准备 IMDB 数据集。
...
# load the dataset but only keep the top n words, zero the rest
top_words = 5000
(X_train, y_train), (X_test, y_test) = imdb.load_data(num_words=top_words)
# pad dataset to a maximum review length in words
max_words = 500
X_train = sequence.pad_sequences(X_train, maxlen=max_words)
X_test = sequence.pad_sequences(X_test, maxlen=max_words)
你现在可以定义你的卷积神经网络模型。这次,在 Embedding 输入层之后,插入一个 Conv1D 层。这个卷积层有 32 个特征图,一次读取嵌入词表示的词嵌入的三个向量元素。
卷积层之后是一维最大池化层,其长度和步幅为 2,将卷积层的特征映射大小减半。网络的其余部分与上面的神经网络相同。
...
# create the model
model = Sequential()
model.add(Embedding(top_words, 32, input_length=max_words))
model.add(Conv1D(filters=32, kernel_size=3, padding='same', activation='relu'))
model.add(MaxPooling1D(pool_size=2))
model.add(Flatten())
model.add(Dense(250, activation='relu'))
model.add(Dense(1, activation='sigmoid'))
model.compile(loss='binary_crossentropy', optimizer='adam', metrics=['accuracy'])
model.summary()
你还将像以前一样适应网络。
...
# Fit the model
model.fit(X_train, y_train, validation_data=(X_test, y_test), epochs=2, batch_size=128, verbose=2)
# Final evaluation of the model
scores = model.evaluate(X_test, y_test, verbose=0)
print("Accuracy: %.2f%%" % (scores[1]*100))
将所有这些结合在一起,下面提供了完整的代码清单。
# CNN for the IMDB problem
from tensorflow.keras.datasets import imdb
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Dense
from tensorflow.keras.layers import Flatten
from tensorflow.keras.layers import Conv1D
from tensorflow.keras.layers import MaxPooling1D
from tensorflow.keras.layers import Embedding
from tensorflow.keras.preprocessing import sequence
# load the dataset but only keep the top n words, zero the rest
top_words = 5000
(X_train, y_train), (X_test, y_test) = imdb.load_data(num_words=top_words)
# pad dataset to a maximum review length in words
max_words = 500
X_train = sequence.pad_sequences(X_train, maxlen=max_words)
X_test = sequence.pad_sequences(X_test, maxlen=max_words)
# create the model
model = Sequential()
model.add(Embedding(top_words, 32, input_length=max_words))
model.add(Conv1D(32, 3, padding='same', activation='relu'))
model.add(MaxPooling1D())
model.add(Flatten())
model.add(Dense(250, activation='relu'))
model.add(Dense(1, activation='sigmoid'))
model.compile(loss='binary_crossentropy', optimizer='adam', metrics=['accuracy'])
model.summary()
# Fit the model
model.fit(X_train, y_train, validation_data=(X_test, y_test), epochs=2, batch_size=128, verbose=2)
# Final evaluation of the model
scores = model.evaluate(X_test, y_test, verbose=0)
print("Accuracy: %.2f%%" % (scores[1]*100))
运行该示例,你首先会看到网络结构的摘要。你可以看到你的卷积层保留了 32 维输入的嵌入输入层的维度,最多包含 500 个单词。池化层通过将其减半来压缩该表示。
运行该示例比上面的神经网络模型有轻微但受欢迎的改进,准确率为 87%。
Epoch 1/2
196/196 - 5s - loss: 0.4661 - accuracy: 0.7467 - val_loss: 0.2763 - val_accuracy: 0.8860 - 5s/epoch - 24ms/step
Epoch 2/2
196/196 - 5s - loss: 0.2195 - accuracy: 0.9144 - val_loss: 0.3063 - val_accuracy: 0.8764 - 5s/epoch - 24ms/step
Accuracy: 87.64%
同样,还有很多进一步优化的机会,例如使用更深和/或更大的卷积层。
一个有趣的想法是将最大池化层设置为使用 500 的输入长度。这会将每个特征映射压缩为一个 32 长度的向量,并可能提高性能。
在本文中,学习了如何开发用于情感分析的深度学习模型,包括:
你对情绪分析或这篇文章有任何疑问吗?在评论中提出你的问题,我会尽力回答。