Skip to content

Files

Latest commit

8e4ed31 · Jan 10, 2022

History

History
385 lines (236 loc) · 20.4 KB

6.md

File metadata and controls

385 lines (236 loc) · 20.4 KB

六、情感分析

情感分析的目标是检查文本数据,并预测情感是积极的还是消极的。例如,您可能希望以编程方式处理来自某个产品用户的电子邮件,以预测发件人对该产品是否满意。

图 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 网络之间存在紧密耦合,您需要确切了解单词是如何被索引的。

IMDB 计划

代码清单 6-1 显示了生成图 6-1 所示输出的完整程序。该程序首先对程序文件名以及所使用的 Python、TensorFlow 和 Keras 版本进行注释,然后导入 NumPy、Keras、TensorFlow 和 OS 包:

`# 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'`

在非演示场景中,您可能希望在注释中包含更多细节。因为 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():

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) . . .`

在大多数情况下,你希望你的结果是可复制的。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()功能参数很多,但大多数情况下你只需要usecolsdelimiterdtype

注意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 神经网络模型

该程序使用以下代码定义了 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 代码)。您必须将值传递给optimizerloss参数,以便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 个参数中的三个。您可以在这里找到更多信息