情感分析的目标是检查文本数据,并预测情感是积极的还是消极的。例如,您可能希望以编程方式处理来自某个产品用户的电子邮件,以预测发件人对该产品是否满意。
图 6-1:使用 Keras 的情感分析
图 6-1 中的截图展示了情感分析的演示。演示程序首先将 620 个训练数据项和 667 个测试数据项加载到内存中。每个项目都是一个最多 50 个单词的电影评论,评论可以是正面的(1)也可以是负面的(0)。
在幕后,演示程序创建了一个 LSTM(长短期记忆)神经网络。LSTM 网络有一个嵌入层,可以将评论中的每个单词转换成一个有 32 个值的数值向量。LSTM 网络的存储单元大小为 100。该网络总共有 4,209,845 个必须确定的权重和偏差值。
LSTM 模型使用五个时期进行训练(为了保持图 6-1 中截图的小尺寸,这个数字很小)。训练后,演示程序根据测试数据计算模型的准确性(81.71%或 67 个正确率中的 54 个)。演示最后预测了一个新的、以前看不到的评论“这部电影是对我时间的极大浪费”,并正确地预测了情感是负面的。
IMDB(互联网电影数据库)电影评论数据集由总共 50,000 条评论组成。有 25000 个培训回顾和 25000 个测试回顾。每套有 12,500 个正面评价和 12,500 个负面评价。原始数据是作为研究项目的一部分收集的,可以在这里找到。
将原始数据转换为可用的格式是一项重大挑战,因为每次审查时数据都被组织为一个文件。这里有一个正面评价的例子:
当我读到其他评论时,我决定看这部电影...
第一,专投迈克尔·马德森和塔默·卡拉达格利;够好...
电影,非常的聪明有趣因为,剧组有很多国际特别是欧洲的男女演员喜欢来自土耳其和俄罗斯...
第二,故事是基本的,你可以猜,但如果你有趣的动作好剧,你会喜欢在我看来...
第三,《最终章》并不特别也不有趣,和其他动作片一样有规律...
最后,我推荐看这部电影...我希望你会喜欢,享受:D
注意有拼错词、语法标点错误、大小写不一致、嵌入 HTML <br/>
标签等因素需要处理。当处理自然语言问题时,数据预处理步骤通常会花费构建预测模型所需的 90%或更多的时间和精力。
Keras 库有一个内置版本的 IMDB 数据集,可以像这样加载到内存中:
from keras.datasets import imdb (x_train, y_train), (x_test, y_test) = imdb.load_data()
然而,在我看来,使用这种方法有些人为,并且隐藏了许多重要的细节。为了简单起见,我创建了一个训练数据文件和一个测试数据文件,其中每个电影评论的长度最多为 50 个单词。结果数据如下所示:
0 0 0 0 0 0 13 510 4 115 1331 363 . . . 1708 298 0 0 0 12 28 111 6 172 7 32188 9 4 88 31 . . . 1487 151 0
每行一篇评论。每行的前几个值是零,用于填充,以便所有评论都正好有 50 个值。每行最后一个值是情感:0 表示负面评价,1 表示正面评价。
每个单词都使用与内置的 Keras IMDB 数据集相同的方案进行编码。值 0 到 3 有特殊意义。值 0 用于填充。值 1 用于指示在数据未被换行符分隔的情况下开始审阅。值 2 用于词汇外(OOV)——测试数据中训练数据中没有的单词。值 3 保留给自定义使用。
此外,所有单词都转换为小写,所有标点符号都被删除,除了单引号字符,这对于像不要和不要这样的缩写很重要。
每个单词 ID 基于该单词在训练数据中的出现频率,其中 4 是最频繁的(“the”),5 是第二频繁的(“a”),以此类推。训练数据总共有 129,888 个不同的单词,因此词汇表中的最后一个单词的索引为 129,888 + 4 - 1 = 129,891。
正如您将很快看到的,当使用 LSTM 网络解决情感分析等自然语言问题时,数据编码和 LSTM 网络之间存在紧密耦合,您需要确切了解单词是如何被索引的。
代码清单 6-1 显示了生成图 6-1 所示输出的完整程序。该程序首先对程序文件名以及所使用的 Python、TensorFlow 和 Keras 版本进行注释,然后导入 NumPy、Keras、TensorFlow 和 OS 包:
`# imdb_lstm.py
import numpy as np import keras as K import tensorflow as tf import os os.environ['TF_CPP_MIN_LOG_LEVEL']='2'`
在非演示场景中,您可能希望在注释中包含更多细节。因为 Keras 和 TensorFlow 正在快速发展,所以您应该始终记录正在使用的版本。在使用 Keras 和其他开源软件时,版本不兼容可能是一个严重的问题。
代码清单 6-1: IMDB 电影评论情感分析程序
#
imdb_lstm.py
#
Python 3.5.2, TensorFlow 2.1.5, Keras 1.7.0
#
==================================================================================
import numpy as np
import keras as K
import tensorflow as tf
import os
os.environ['TF_CPP_MIN_LOG_LEVEL']='2'
def main():
# 0\. get started
print("\nIMDB
sentiment analysis using Keras/TensorFlow ")
np.random.seed(1)
tf.set_random_seed(1)
# 1\. load data
max_review_len = 50
print("Loading
train and test data, max len = %d words\n" % max_review_len)
train_x = np.loadtxt(".\\Data\\imdb_train_50w.txt", delimiter=" ",
usecols=range(0,max_review_len), dtype=np.float32)
train_y = np.loadtxt(".\\Data\\imdb_train_50w.txt", delimiter=" ",
usecols=[max_review_len], dtype=np.float32)
test_x = np.loadtxt(".\\Data\\imdb_test_50w.txt", delimiter=" ",
usecols=range(0,max_review_len), dtype=np.float32)
test_y = np.loadtxt(".\\Data\\imdb_test_50w.txt", delimiter=" ",
usecols=max_review_len, dtype=np.float32)
# 2\. define model
e_init = K.initializers.RandomUniform(-0.01, 0.01, seed=1)
init = K.initializers.glorot_uniform(seed=1)
simple_adam = K.optimizers.Adam()
nw = 129892 # must be
> vocabulary size (don't forget +4)
embed_vec_len = 32 #
values per word -- 100-500 is typical
model = K.models.Sequential()
model.add(K.layers.embeddings.Embedding(input_dim=nw, output_dim=embed_vec_len,
embeddings_initializer=e_init, mask_zero=True))
model.add(K.layers.LSTM(units=100, kernel_initializer=init, dropout=0.2))
model.add(K.layers.Dense(units=1, kernel_initializer=init, activation='sigmoid'))
model.compile(loss='binary_crossentropy', optimizer=simple_adam, metrics=['acc'])
print(model.summary())
# 3\. train model
bat_size = 10
max_epochs = 5
print("\nStarting
training ")
model.fit(train_x, train_y, epochs=max_epochs, batch_size=bat_size,
shuffle=True, verbose=1)
print("Training
complete \n")
# 4\. evaluate model
loss_acc = model.evaluate(test_x, test_y, verbose=0)
print("Test
data: loss = %0.6f accuracy = %0.2f%% " % \
(loss_acc[0], loss_acc[1]*100))
# 5\. save model
print("Saving model
to disk \n")
mp = ".\\Models\\imdb_model.h5"
model.save(mp)
# 6\. use model
print("Sentiment
for \"the movie was a great waste of my time\"")
rev = np.array([[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, 0, 0,
0, 4, 20, 16, 6, 86, 425, 7, 58, 64]], dtype=np.float32)
prediction = model.predict(rev)
print("Prediction
(0 = negative, 1 = positive) = ", end="")
print("%0.4f" % prediction[0][0])
#
==================================================================================
if __name__ == "__main__":
main()
程序导入整个 Keras 包并分配一个别名K
。另一种方法是只导入您需要的模块,例如:
从 keras.models 导入顺序 从 keras.layers 导入密集,激活
即使 Keras 使用 TensorFlow 作为其后端引擎,您也不需要显式导入 TensorFlow,除非是为了设置其随机种子。导入操作系统包只是为了抑制恼人的 TensorFlow 启动警告消息。
程序结构由一个主函数组成,没有辅助函数。该计划始于:
`def main():
print("\nIMDB sentiment analysis using Keras/TensorFlow ") np.random.seed(1) tf.set_random_seed(1)
max_review_len = 50 print("Loading train and test data, max len = %d words\n" % max_review_len)
train_x = np.loadtxt(".\Data\imdb_train_50w.txt", delimiter=" ", usecols=range(0,max_review_len), dtype=np.float32) train_y = np.loadtxt(".\Data\imdb_train_50w.txt", delimiter=" ", usecols=[max_review_len], dtype=np.float32) . . .`
在大多数情况下,你希望你的结果是可复制的。Keras 库广泛使用了 NumPy 全局随机数生成器,因此设置种子值是一个很好的做法。程序中使用的种子值 1 是任意的。同样,因为 Keras 使用 TensorFlow,所以您通常也想要设置它的种子。然而,即使您设置了所有随机种子,程序结果通常也不完全可再现,部分原因是并行任务的数字舍入顺序。
由于页面宽度的限制,我用两个空格而不是通常的四个空格来缩进。所有正常的错误检查都被删除了,以保持主要思想尽可能清晰。
使用相同的技术将测试数据读入存储器:
test_x = np.loadtxt(".\\Data\\imdb_test_50w.txt", delimiter=" ", usecols=range(0,max_review_len), dtype=np.float32) test_y = np.loadtxt(".\\Data\\imdb_test_50w.txt", delimiter=" ", usecols=max_review_len, dtype=np.float32)
程序假设训练和测试数据文件位于名为Data
的子目录中。该程序没有任何关于数据文件结构的信息。我强烈建议您包括描述您的数据格式的程序注释。编写程序时,数据格式信息很容易记住,但几周后就很难记住了。
使用 NumPy loadtxt()
功能将训练数据读入存储器。将数据读入内存的方法有很多,但loadtxt()
功能的通用性足以满足大多数问题场景。NumPy genfromtxt()
函数非常相似,但是它给了你一些额外的选项,比如处理丢失的数据。loadtxt()
功能参数很多,但大多数情况下你只需要usecols
、delimiter
和dtype
。
注意usecols
可以接受[max_review_len]
这样的列表或者range(0,max_review_len)
这样的 Python 范围。如果使用range()
功能,注意记住第一个参数是包含的,第二个参数是排他的。
默认dtype
参数值为numpy.float
,是 Python float
的别名,与numpy.float64
完全相同。但是几乎所有 Keras 函数的默认数据类型都是numpy.float32
,所以程序指定了这种类型。其思想是,对于大多数机器学习问题,使用 64 位值获得的精度优势不值得内存和性能损失。
一种不同的方法是使用 Pandas(“面板数据”或“Python 数据分析库”)库,该库具有许多高级数据操作功能,而不是使用诸如loadtxt()
的 NumPy 函数将数据读入内存。然而,熊猫有一个显著的学习曲线。
该程序使用以下代码定义了 LSTM 神经网络:
`# 2. define model e_init = K.initializers.RandomUniform(-0.01, 0.01, seed=1) init = K.initializers.glorot_uniform(seed=1) simple_adam = K.optimizers.Adam() nw = 129892 embed_vec_len = 32
model = K.models.Sequential() model.add(K.layers.embeddings.Embedding(input_dim=nw, output_dim=embed_vec_len, embeddings_initializer=e_init, mask_zero=True)) model.add(K.layers.LSTM(units=100, kernel_initializer=init, dropout=0.2)) model.add(K.layers.Dense(units=1, kernel_initializer=init, activation='sigmoid')) model.compile(loss='binary_crossentropy', optimizer=simple_adam, metrics=['acc'])`
print(model.summary())
这里发生了很多事,请原谅我。LSTM 有两个主要部分和几个次要部分。第一个主要组成部分是Embedding()
层。当使用自然语言时,您可以直接向 LSTM 网络输入单词索引,例如“the”为 4,“movie”为 20。然而,这种方法并没有给出很好的结果。更好的方法是将每个单词索引转换成一个实值向量,如(0.4508,1.3233,.。0.9305).
向量必须以这样一种方式构建,即语义相近的单词,如“优秀”和“精彩”,其向量的数值相近。有三种主要的方法来构建一组单词嵌入。首先,您可以使用一个单独的工具,如 Word2Vec 库,基于您的训练数据创建一组自定义嵌入。其次,你可以使用一组基于标准语料库的预建单词嵌入,比如来自谷歌的几十万个故事的大型新闻提要,或者所有维基百科文章的文本。演示程序使用第三种方法,即使用训练数据动态计算单词嵌入。这是一个困难的问题,并对模型的 4,209,845 个权重和偏差中的 4,156,444 个负责。
注意Embedding()
构造函数需要最大的词索引值。演示使用 129,892 而不是 129,891 来表示如果您愿意,可以有额外的索引。演示程序指定嵌入向量长度为 32。该值是一个自由参数。对于较大的问题,典型的向量长度是 100 到 500。表 6-1 总结了Embedding()
层的七个参数。
表 6-1:嵌入层参数
名称 | 描述 |
---|---|
input_dim |
词汇的大小,即最大整数索引+ 1 |
output_dim |
密集嵌入的维数 |
embeddings_initializer |
嵌入矩阵的初始值设定项 |
embeddings_regularizer |
正则函数在嵌入矩阵中的应用 |
embeddings_constraint |
应用于嵌入矩阵的约束函数 |
mask_zero |
输入值 0 是否为填充值 |
input_length |
当输入序列的长度不变时 |
LSTM 网络的第二个主要组成部分是LSTM()
层。LSTMs 是非常复杂的软件模块,但关键的想法是它们有内存,或者等价地说,它们有状态。假设你知道一个句子中的一个词是“很少”,你想预测下一个词。你肯定得胡乱猜测一下。但是如果你知道前面的单词是“不打碎几个鸡蛋就做不成煎蛋卷……”那么你几乎肯定会预测下一个单词是“鸡蛋”简而言之,LSTM 网络具有状态,可以很好地处理输入单词序列。
通过查看图 6-2 中的图表,您可以大致了解什么是 LSTM 细胞。
图 6-2:简化的 LSTM 单元
在图 6-2 中,x(t)是时间 t 的输入,h(t)是相应的输出。向量 c(t)是细胞状态或记忆。输出 h(t)取决于电流输入和电池状态。LSTM 细胞的内部管道非常复杂,但幸运的是,当使用 Keras 时,您只需要 23 个LSTM()
参数中的几个。
演示程序通过units=100
参数指定内存。内存大小是一个自由参数。由于LSTM()
层的复杂性,您不能通过使用标准的Dropout()
层来应用丢弃,因此有一个内部丢弃机制,该演示将其作为dropout=0.2
参数来应用。
在LSTM()
层之后,模型有一个带有乙状结肠激活的单一Dense()
层。这里的想法是将LSTM()
层的输出压缩到 0.0 到 1.0 之间的单个值,这可以解释为预测类= 1 的概率。换句话说,这意味着如果产出小于 0.5,模型预测 0 =负面情感;否则,模型预测 1 =积极情感。
LSTM 模型是使用二元交叉熵作为损失函数编译的,因为类标签是 0 或 1。在你有三个或三个以上类别标签的情感分析场景中,比如负= (1,0,0),中性= (0,1,0)和正= (0,0,1),你可以将最后一个网络层的激活函数从sigmoid
改为softmax
,并将分类交叉熵用于loss
函数。
你可以粗略地把编译过程想象成把 Keras 代码翻译成 TensorFlow 代码(或者 CNTK 代码或者 antano 代码)。您必须将值传递给optimizer
和loss
参数,以便fit()
方法知道如何训练模型。metrics
参数是可选的。该程序传递一个只包含'acc'
的 Python 列表,以指示应该在训练期间计算分类精度(正确预测的百分比)。
演示程序使用summary()
功能显示 LSTM 模型的概要。使用summary()
的主要目的是检查你的模型有多少权重和偏差,这让你知道训练需要多长时间。有可能构建无法训练的深层网络,因为它们有太多的权重和偏见。
将训练数据读入内存并创建 LSTM 网络后,演示程序使用以下语句训练模型:
# 3\. train model bat_size = 10 max_epochs = 5 print("\nStarting training ") model.fit(train_x, train_y, epochs=max_epochs, batch_size=bat_size, shuffle=True, verbose=1) print("Training complete \n")
批量设置为 10,称为在线培训。批量是一个自由参数,必须通过反复试验来确定。我的一些同事喜欢用 2 的幂作为他们的批量:4、8、16 等。,但没有研究证据表明这种做法是好是坏。根据一般经验,LSTM 神经网络对批量非常敏感。
max_epochs
变量控制将用于训练的迭代次数。fit()
功能中的shuffle
参数表示训练项目应该随机处理。默认值为True
,因此该参数可以省略。verbose
参数控制训练时显示多少信息:0 表示不显示信息,1 表示显示全部信息,2 表示显示中等信息量。
fit()
函数返回一个记录了训练历史的字典对象。演示程序没有捕获这些信息。
培训后,演示程序根据测试数据评估模型:
# 4\. evaluate model loss_acc = model.evaluate(test_x, test_y, verbose=0) print("Test data: loss = %0.6f accuracy = %0.2f%% " % \ (loss_acc[0], loss_acc[1]*100))
evaluate()
函数返回一个值列表。索引[0]处的第一个值是在compile()
函数中指定的所需loss
函数的始终值,在本例中为二进制交叉熵。列表中的其他值是来自compile()
功能的任意可选metrics
。在本例中,'acc'
被通过,因此索引[1]处的值保持分类精度。该程序乘以 100,将精度从比例(如 0.8123)转换为百分比(如 81.23%)。
在大多数情况下,你会想要保存一个训练好的模型,尤其是如果训练花费了几个小时甚至更长的时间。演示程序将训练好的模型保存如下:
# 5\. save model print("Saving model to disk \n") mp = ".\\Models\\imdb_model.h5" model.save(mp)
Keras save()
函数使用分层数据格式(HDF)版本 5 保存一个训练好的模型。它是二进制格式,所以保存的模型不能用文本编辑器检查。除了保存整个模型,您还可以只保存模型权重和偏差,这有时很有用。您还可以保存没有权重的公正模型架构。
您可以从不同的程序加载已保存的 Keras 模型,如下所示:
print("Loading saved IMDB sentiment model") mp = ".\\Models\\imdb_model.h5" model = K.models.load_model(mp)
创建和训练模型的全部意义在于,它可以用来预测新的、以前看不到的数据:
# 6\. use model print("Sentiment for \"the movie was a great waste of my time\"") rev = np.array([[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, 0, 0, 0, 4, 20, 16, 6, 86, 425, 7, 58, 64]], dtype=np.float32) prediction = model.predict(rev) print("Prediction (0 = negative, 1 = positive) = ", end="") print("%0.4f" % prediction[0][0])
因为 LSTM 模型是使用长度填充到 50 个编码单词的评论来训练的,所以在进行预测时,您必须使用相同的格式将新的评论传递给predict()
方法。“这部电影是对我时间的极大浪费”的编码值是硬编码的。然而,在非演示场景中,当您创建训练和测试数据文件时,您会将编码保存到一个文本文件中,通常命名为类似 vocab.txt 的内容,大致如下:
第 4 浪费 425 时间第 64 。。。
然后,您可以编写一个脚本,打开词汇表文件,并将该文件读入字典对象,其中单词是字典键,编码索引是字典值。
要创建输入是一系列文本(如句子)的分类预测模型,可以使用由一个或多个 LSTM 像元加上一些附加管道(如密集层)组成的 LSTM 网络。
当处理文本输入时,单词应该被编码为数字向量,这一过程称为嵌入。您可以在预处理阶段创建嵌入,也可以使用Embedding()
层动态创建嵌入。
LSTM 模型的自由参数包括权重初始化算法、优化算法及其参数、辍学率、批量和训练迭代次数。
你可以在这里找到演示程序使用的训练和测试数据。
演示程序只使用了LSTM()
构造器的 23 个参数中的三个。您可以在这里找到更多信息。