2018 年 10 月

33 卷,第 10

此文章由机器翻译

测试运行-使用 CNTK 进行情绪分析

通过James McCaffrey

James McCaffrey假设具有文本数据,例如一系列电子邮件或联机产品评论,并且你想要确定整体感觉为正或负 (或可能是中性)。这是情绪分析的目标。在本文中我介绍您如何创建使用 Microsoft CNTK 代码库的情绪分析系统。

看一看中的屏幕截图图 1若要查看本文所述观点。该演示程序使用 IMDB 电影查看数据集,其中共有 50,000 评审。有在训练集中的 25,000 评审和测试集中的 25,000 评审。每个组具有 12,500 正评审和 12,500 负评审。

使用 CNTK 进行情绪分析
图 1 使用 CNTK 进行情绪分析

该演示程序使用 IMDB 数据集的一小部分 — 有 50 个单词的评审或更少。在后台,演示程序使用 CNTK 库创建长而短期记忆 (LSTM) 神经网络和定型使用 400 迭代。定型后,该模型的准确性上留出测试评审为 60.12%— 不是很好由于小型数据集大小。最后,演示程序会预测之前未查看的"我喜欢此电影"。 数字预测是 (0.2740,0.7259) 和第二个值大于第一个,因为该预测是,这是正回顾。

本文假定您具有中级或更高的编程技能 C 系列语言和基本的了解神经网络,但并不假定您精通 LSTM 网络。在本文中,显示了整个演示代码和关联的数据文件中随附的下载可用。

了解数据

了解情绪分析演示的关键了解数据文件的结构。假设您有两个评审:"我喜欢此电影"和"电影是可怕"。 CNTK 格式数据文件所示:

0 |x 12:1 |y 0 1
0 |x 407:1
0 |x 13:1
0 |x 20:1
1 |x 20:1 |y 1 0
1 |x 9:1
1 |x 387:1

每个行的第一个值是序列号。"| X"和"| y"标记指示开始的输入和输出值,分别。对于输出,负评审编码为 (1,0) 和正查看编码为 (0,1)。每个单词的整数索引值为:"i"= 12,"喜欢"= 407,"this"= 13,"电影"= 20、 9 和"严重""是"= = 387。

LSTM 模型需要独热编码的输入。这是单词的包含除单个的一个值相关联的索引处的全为零的向量。例如,如果整个词汇包含只需四个单词"良好""错误,""极差,""neutral"然后"好"= (1,0,0,0)、"错误"= (0、 1、 0,0)、"难看"= (0,0,1,0) 和"neutral"= (0,0,0,1)。

可以在 Internet 上的多个位置找到 IMDB 电影查看数据集。数据集定型集中具有 129,888 不同的单词。因此,如果使用直接的独热编码方法,每个单词会编码为巨大矢量与 129,887 0 而将单个 1,这是极其低效的。CNTK 支持以稀疏格式。例如,12:1 表示,"索引 12 处放置一个 1 并使所有其他值 0。" 必须推断或提供其他位置的位数总数。

从原理上讲,每个单词的索引值可以是任何内容因为它们仅作为唯一的 Id。但是,该演示程序进行编码使用一种相当常见的自然语言系统的方案的每个单词。根据源语料库,频率进行编码的单词 4 分配到的最常见的单词,其中 5 分配给第二个最常见的单词,依此类推。

值为 0 保留供希望所有的输入的序列为具有相同长度的情况下填充。如果值为 1 用于在其中数据不组织使用显式的分隔符 (通常换行) 的情况下的"序列的 start"。值为 2 用于"带词汇"是未知的因为它们未出现在训练数据集的字词。为自定义的使用保留的值为 3。

开始将使用 LSTM 模型之前,我编写了一个自定义程序来生成训练数据的文件和测试数据的文件。在极高级别伪代码中,以下是它的作用:

read all 50,000 training and test reviews into memory,
  removing punctuation and converting to lowercase.
create a vocabulary collection of all unique words
  in training data.
sort the collection by word frequency,
  1 = most frequent.
loop through each review
  if review has more than 50 words, skip it
  write output to train, test file in CNTK format.
end-loop.

有很多的详细信息,以准备情绪分析模型的数据时,请考虑。我删除了所有标点字符除外的单引号字符,如保留字不和不会。评论中的所有单词已都转换为小写。我没有删除任何非索引字,如"和"和"the"。 最终培训文件包含 620 评审和测试文件具有 667 评审。此外创建了一个小文件包含两个评审的"我喜欢此电影"和"电影是可怕"。

IMDB 电影查看数据集是二进制的因为审阅是正数或负数。对于二进制分类,很常见编码为 0 的否定和肯定为 1。但是,为了使示例程序 (例如,评审其中可以是正数、 负数或非特定语言) 的多类问题的适用性我编码为负数 (1,0) 和正为 (0,1)。 

了解 LSTM 网络

句子是单词的序列。在大多数情况下一个词的含义是高度依赖于前面的单词。例如,假设有的"我希望我可以说这是很好的电影。"的评审 一个幼稚的做法,只需在单个词看起来将"太好了",并可能得出结论: 评审为正。LSTM 网络有内存,因此它们可以处理顺序数据,特别是句子。

有许多变体 LSTM 网络和密切相关的网络,例如封闭循环单位 (GRUs)。就可以了解 Lstm 通过检查中的关系图的工作原理图 2。在关系图显示了简化的 LSTM 单元。稍后将看到,LSTM 网络,包括一个或多个 LSTM 单元以及其他基本功能。

简化的 LSTM 单元 
图 2: 简化 LSTM 单元

点是在时间 t 的输入进行情绪分析的是一个句子中的字。H (t) 是在时间 t 的输出。C(t) 是单元状态或在时间 t 的内存。在图中,单元格状态显示为具有输出向量,具有相同的大小,但在大多数情况下的单元格状态将较大 (因此 LSTM 需要一些附加组件才能正确缩放矢量大小)。请注意,在时间 t 输出取决于的输入和单元状态,又在输出时间 t-1 和次 t-1 参与单元状态和单元格输出单元状态。

程序的整体结构

安装 CNTK 涉及两个主要步骤。首先,安装 Python 分发版本,然后将 CNTK 安装为一个加载项包。Python 分发版包含核心 Python 解释器以及多个已验证的工作与 Python 版本和与每个其他的几百个有用包的特定版本。在开放源代码领域中,版本不兼容性可能是噩梦。

我使用了 Anaconda3 4.1.1 分发包含 Python 3.5.2。可以通过执行"anaconda 存档。"的 Internet 搜索找到此 安装 Anaconda 之后, 我"cntk python"搜索和 CNTK 2.4 找到.whl 安装程序文件并下载到本地计算机。您可以将.whl 文件作为某种程度上类似于 Windows 的.msi 文件。我通过启动命令行界面,然后使用 pip 实用工具安装 CNTK 2.4:

> pip install C:\MyWheels\cntk-2.4-cp35-cp35m-win_amd64.whl

演示程序使用了少量小幅改动,以节省空间,结构所示图 3。我使用记事本作为我 CNTK 选的编辑器,但我的大多数同事更喜欢使用更复杂的内容。

图 3 整体程序结构

# imdb_lstm.py
import numpy as np
import cntk as C
def create_reader(path, input_dim, output_dim,
  is_random, sweeps):
def main():
  # get started
  print("Begin IMDB demo")
  np.random.seed(1)
  # define LSM model
  # train model
  # evaluate model
  # make a prediction
if __name__ == "__main__":
  main()

程序定义的函数 create_reader 寻找标记"| x"和"| y"CNTK 格式数据文件中。定义所示图 4

图 4 create_reader 定义

def create_reader(path, input_dim, output_dim,
  is_random, sweeps):
  x_strm = C.io.StreamDef(field='x', shape=input_dim,
    is_sparse=True)
  y_strm = C.io.StreamDef(field='y',
   shape=output_dim, is_sparse=False)
  streams = C.io.StreamDefs(x_src=x_strm,
    y_src=y_strm)
  deserial = C.io.CTFDeserializer(path, streams)
  mb_source = C.io.MinibatchSource(deserial,
    randomize=is_random, max_sweeps=sweeps)
  return mb_source

请注意,x 输入值表示为稀疏,但 y 输出值不是。主函数设置全局 NumPy 随机数生成器种子为 1,以便结果的可重现。然后演示将设置关键变量:

train_file = ".\\Data\\imdb_sparse_train_50w.txt"
test_file = ".\\Data\\imdb_sparse_test_50w.txt"
input_dim = 129888 + 4
output_dim = 2
X = C.sequence.input_variable(shape=input_dim,
  is_sparse=True)
Y = C.ops.input_variable(output_dim)

回想一下 IMDB 训练数据集具有 129,888 不同的单词和数据文件使用偏移量为 4。如果要在你自己的数据执行情绪分析,您将需要记得在创建您的培训和测试数据文件时记录的不同的单词数。接下来,演示将设置定型数据的数据读取器:

rdr = create_reader(train_file, input_dim,
  output_dim, is_random=True, sweeps=C.io.INFINITELY_REPEAT)
imdb_map = {
  X : rdr.streams.x_src,
  Y : rdr.streams.y_src
}

务必按随机顺序处理训练数据。扫描参数,以便训练数据可以遍历多个时间设置。

定义 LSTM 模型

此演示程序通过以下语句创建 LSTM 模型:

my_init = C.initializer.glorot_uniform(seed=1)
lstm = C.layers.Sequential([
  C.layers.Embedding(50, init=my_init),
  C.layers.Recurrence(C.layers.LSTM(25)),
  C.sequence.last,
  C.layers.Dense(output_dim, init=my_init)])
model = lstm(X)

LSTM 网络通常是对如何初始化模型权重高度敏感的。通常使用 glorot_uniform 算法,但有多种替代方法,包括随机统一和随机正常。

嵌入层很关键。从理论上讲,可以通过直接向 LSTM 网络,表示单词的原始索引值,但在实践中应将每个单词转换成多个值的向量。在这种情况下,嵌入层会将每个输入的单词转换到 50 个值的矢量。

代码中设置了单个 LSTM 单元格的单元格状态/内存大小为 25。嵌入的矢量大小和 LSTM 单元格大小是自由参数,必须通过反复试验来确定正确值。模型的最后一层使用密集的函数,因此输出限于两个值,因为目标输出为 (1,0) 或 (0,1)。

定型和评估 LSTM 模型

演示准备培训使用以下语句:

max_iter = 400
batch_size = 10 * 40  # ~9 sequences
lr = C.learning_parameter_schedule_per_sample(0.1)
sgd = C.sgd(model.parameters, lr)
tr_loss = C.cross_entropy_with_softmax(model, Y)
tr_accu = C.classification_error(model, Y)
trainer = C.Trainer(model, (tr_loss,tr_accu), [sgd])

因为平均评审长度约 40 个单词,设置 batch_size 为 400 将提取大约 9 或 10 评审每个培训批处理。为简单起见,此演示程序使用基本的随机梯度下降算法,这是很少 LSTM 网络的最佳选择。Adam,AdaGrad 和 RMSprop 算法通常更好地工作。图 5说明如何执行培训。

图 5 LSTM 模型定型

for i in range(max_iter):
  mb = rdr.next_minibatch(batch_size, input_map=imdb_map)
  trainer.train_minibatch(mb)
  if i % int(max_iter/5) == 0:
    print("i = %d" % i)
    num_seqs = mb[Y].num_sequences
    print("curr mini-batch has %d sequences" % num_seqs)
    curr_class_err = (1.0 - \
trainer.previous_minibatch_evaluation_average) * 100
    print("accuracy curr mb = %0.2f%%" % curr_class_err)
    curr_loss = trainer.previous_minibatch_loss_average
    print("loss curr mb = %0.4f" % curr_loss)

将显示进度消息每 400 迭代 / 5 = 80 到监视器丢失和准确性的定型项当前批次迭代。完成定型后,演示程序通过将其应用到测试数据评估模型。首先,创建一个新的读取器对象:

rdr = create_reader(test_file, input_dim,
  output_dim, is_random=False, sweeps=1)
imdb_map = {
  X : rdr.streams.x_src,
  Y : rdr.streams.y_src
}

请注意,无需遍历测试数据按随机顺序,并且只需遍历一次。计算的分类准确性,并将其显示:

num_to_test = 500 * 25000
all_test = rdr.next_minibatch(num_to_test,
  input_map=imdb_map)
class_acc = (1.0 - trainer.test_minibatch(all_test)) * 100
print("Classification accuracy on all test items = \
 %0.2f%%" % class_acc)

Num_to_test 变量设置为捕获不是测试数据中有更多评审。由于读取器配置为仅一次遍历测试数据,将忽略多余的计数。CNTK 细微古怪之一是,没有分类误差函数 (百分比不正确) 而不是分类准确性函数,因此要获取精确到 1 中减去该演示。

进行预测

若要使上一个新的情绪预测,之前未的审阅,最简单的方法是设置具有相同的结构的定型或测试数据的文件。此演示程序设置了该文件的读取器:

review_file = ".\\Data\\my_reviews_50w.txt"
rdr = create_reader(review_file, input_dim,
  output_dim, is_random=False, sweeps=1)
imdb_map = {
  X : rdr.streams.x_src,
  Y : rdr.streams.y_src
}

接下来,一次评审读取到内存中作为 CNTK 微批处理对象,并使用 eval 方法计算输出:

review_mb = rdr.next_minibatch(1, input_map=imdb_map)
model = C.ops.softmax(model)
predicted = model.eval(review_mb[X])
print(predicted)

调用 eval 之前, 使用 softmax 函数修改模型。这将强制的两个输出值总和为 1.0,这使结果更容易解释。

总结

为大约 24 个月前,最近创建的自定义的情绪分析模型已考虑深度学习的极先进的应用程序和你可能必须使用 Azure 认知服务等内置的解决方案。但不断涌现的 CNTK 库,以及类似的库,如 Keras/TensorFlow、 进行自定义的情绪分析可行。当然,情绪分析不是简单的问题,但即便如此,这篇文章中介绍的演示程序将应使您需要开始生产质量系统上的所有信息。


Dr.James McCaffrey 供职于华盛顿地区雷蒙德市沃什湾的 Microsoft Research。他参与过多个 Microsoft 产品的工作,包括 Internet Explorer 和必应。Scripto可通过 jamccaff@microsoft.com 与 McCaffrey 取得联系。

衷心感谢以下 Microsoft 技术专家对本文的审阅:Joey Carson、 Si Qing Chen、 Eunice Kim、 Lucas Meyer


在 MSDN 杂志论坛讨论这篇文章