公众号关注 “ML_NLP
设为 “星标”,重磅干货,第一时间送达!
来自 | 知乎
地址 | https://zhuanlan.zhihu.com/p/31096913
作者 | Datartisan
编辑 | 机器学习算法与自然语言处理公众号
本文仅作学术分享,若侵权,请联系后台删文处理
本文将通过使用TensorFlow中的LSTM神经网络方法探索高效的深度学习方法。

基于LSTM方法的情感分析

在这篇笔记中,我们将研究如何将深度学习技术应用在情感分析任务中。情感分析可以理解为择取段落、文档或任意一种自然语言的片段,然后决定文本的情绪色彩是正面的、负面的还是中性的。
这篇笔记将会讲到数个话题,如词向量,时间递归神经网络和长短期记忆等。对这些术语有了好的理解后,我们将在最后详细介绍具体的代码示例和完整的Tensorflow情绪分类器。
在进入具体细节之前,让我们首先来讨论深度学习适用于自然语言处理(NLP)任务的原因。

深度学习在自然语言处理方面的应用

自然语言处理是关于处理或“理解”语言以执行某些任务的创造系统。这些任务可能包括:
  • 问题回答 - Siri,Alexa和Cortana等技术的主要工作
  • 情绪分析 - 确定一段文本背后的情绪色调
  • 图像到文本映射 - 生成输入图像的说明文字
  • 机器翻译 - 将一段文本翻译成另一种语言
  • 语音识别 - 电脑识别口语
在深度学习时代,NLP是一个蓬勃发展中的领域,取得了很多不同的进步。然而,在上述任务的所有成就中,需要做很多特征工程的工作,因此需要有很多语言领域的专业知识。作为从业人员需要掌握对音素和语素等术语,乃至花费四年读取学位专门学习这个领域。近几年来,深度学习取得了惊人的进步,大大消除了对丰富专业知识要求。由于进入门槛较低,对NLP的应用已成为深度学习研究的最大领域之一。

词向量

为了理解如何应用深度学习,可以思考应用在机器学习或深度学习模型中的所有不同数据形式。卷积神经网络使用像素值向量,逻辑线性回归使用量化特征,强化学习模型使用回馈信号。共同点是都需要标量或者标量矩阵来作为输入。当你思考NLP任务时,可能会在你的思路中出现这样的数据管道。
这种通道是有问题的。我们无法在单个字符串上进行像点乘或者反向传播这样的常见操作。我们需要把句子中的每个单词转换成一个向量而不是仅仅输入字符串。
你可以将情绪分析模块的输入看做一个16 x D维矩阵。
我们希望以方便表示单词及其上下文、意义和语义的方式来创建这些向量。例如,我们希望“爱”和“崇拜”这些向量驻留在向量空间中相对相同的区域中,因为它们都具有相似的定义,并且在相似的上下文中使用。一个单词的向量表示也称为词嵌入。

Word2Vec

为了创建这些单词嵌入,我们将使用通常被称为“Word2Vec”的模型。模型通过查看语句在句子中出现的上下文来创建词矢量而忽略细节。具有相似上下文的单词将在向量空间中放置在相近的位置。在自然语言中,当尝试确定其含义时,单词的上下文可能非常重要。正如我们之前”崇拜“和”爱“的例子,
从句子的上下文可以看出,这两个词通常用于具有正面内涵的句子,通常在名词或名词短语之前。这表明这两个词都有一些共同点,可能是同义词。考虑句子中的语法结构时,语境也很重要。大多数句子将遵循具有动词跟随名词的传统范例,形容词先于名词等等。因此,该模型更有可能将名词与其他名词相同。该模型采用大量句子数据集(例如英文维基百科),并为语料库中的每个不同词输出向量。Word2Vec模型的输出称为嵌入矩阵(embedding matrix)。
该嵌入矩阵将包含训练语料库中每个不同单词的向量。按照传统做法,嵌入矩阵可以包含超过300万个字向量。
Word2Vec模型是通过将数据集中的每个句子进行训练,在其上滑动固定大小的窗口,并尝试根据给出的其他单词预测窗口中心的单词。使用损失函数和优化程序,模型为每个不同词生成向量。这个训练过程的具体细节可能会有点复杂,所以我们现在要跳过细节,但重要的是,任何深度学习方法对NLP任务的都很可能会有词矢量作为输入。
有关Word2Vec背后的理论以及如何创建自己的嵌入矩阵的更多信息,请查看Tensorflow的教程

递归神经网络(RNNs)

现在我们用我们的词向量作为输入,首先来看看将要建立的实际网络架构。NLP数据的独特之处在于它有一个时间方面的差异。一句话中的每一个词的含义都很大程度上依赖于发生在过去还是未来。
你很快就会看到,递归神经网络结构和传统的前馈神经网络有点不同。前馈神经网络由输入节点,隐藏单元和输出节点组成。
前馈神经网络和递归神经网络的主要区别在于后者的时间性。在RNN中,输入序列的每个单词都与特定的时间步长相关联。实际上,时间步长的数量将等于最大序列长度。
一个称为隐藏状态向量\(ht\)的新组件也与每个时间步相联系。从高层次来看,这个向量旨在封装并总结在之前的时间步中看到的所有信息。就像\(xt\)是封装特定单词的所有信息的向量,htht是一个向量,总结了之前时间步长的所有信息。
隐藏状态是当前词向量和前一时间步的的函数。σ表示两项和代入一个激活函数(通常为S形或tanh)
上述公式中的2个W项代表权重矩阵。如果你仔细看看上标,你会看到有一个权重矩阵WXWX ,它将与我们的输入相乘,并且有一个循环权重矩阵WHWH,它将与上一时间步的隐藏状态相乘。WHWH是在所有时间步长中保持不变的矩阵,权重矩阵WXWX相对于每个输入是不同的。
这些权重矩阵的大小会影响当前隐藏状态或之前隐藏状态所影响的变量。作为练习,参考上面的公式,思考WXWX或者WHWH的值大小变化对htht有怎样的影响。
来看一个简单的例子,当WHWH很大而WXWX很小时,我很知道\(ht\)很大程度上受\(h{t-1}\)的影响而受xtxt影响较小。换句话说,当前隐藏状态向量对应的单词在句子全局上是无关紧要的,那么它和上一时间步的向量值基本相同。
权重矩阵通过称为反向传播的优化过程随着时间进行更新。
末尾时间步处的隐藏状态向量被馈送到二进制softmax分类器中,在其中与另一个权重矩阵相乘,并经过softmax函数(输出0和1之间的值)的处理,有效地给出情绪偏向正面或负面的概率。

长短期记忆单元(LSTM)

长短期记忆单元式放置在递归神经网络中的模块。在高层次上,它们确定隐藏状态向量h能够在文本中封装有关长期依赖关系的信息。正如我们上一节所见,在传统RNN方法中h的构想相对简单。但这种方法无法有效地将由多个时间步长分开的信息连接在一起。我们可以通过QA问答系统(question answering)阐明处理长期依赖关系的思路。QA问答系统的功能是提出一段文本,然后根据这段文本的内容提出问题。我们来看下面的例子:
我们可以看出中间的句子对被提出的问题没有影响。然而,第一句和第三句之间有很强的联系。使用经典的RNN,网络末端的隐藏状态向量可能存储有关狗的句子的更多信息,而不是关于该数字的第一句。从根本上来说,额外的LSTM单元会增加可能性来查明应该被导入隐藏状态向量的正确有用信息。
从更技术的角度来看LSTM单元,单元导入当前的词向量\(xt\)并输出隐藏状态向量\(ht\)。在这些单元中,htht的构造将比典型的RNN更复杂一点。计算分为4个组件,一个输入门(input gate),一个遗忘门(forget gate),一个输出门(output gate)和一个新的存储容器。
每个门将使用\(xt\)和\(ht\)(图中未显示)作为输入,并对它们执行一些计算以获得中间状态。每个中间状态被反馈到不同的管道中并最终把信息聚合成\(ht\)的形式。为了简便起见,我们不会对每个门的具体构造进行说明,但值得注意的是,每个门都可以被认为是LSTM内的不同模块,每个模块各有不同的功能。输入门决定了每个输入的权重,遗忘门决定我们将要丢弃什么样的信息,输出门决定最终基于中间状态的\(ht\)。若想了解不同门的功能和全部方程式,更详细的信息请查看Christopher Olah的博客文章(译者注:或者中文译文)。
回顾第一个例子,问题是“两个数字的和是多少”,该模型必须接受相似问答的训练,然后,LSTM单位将能认识到没有数字的任何句子可能不会对问题的答案产生影响,因此该单位将能够利用其遗忘门来丢弃关于狗的不必要的信息,而保留有关数字的信息。

把情绪分析表述为深度学习问题

如前所属,情绪分析的任务主要是输入一序列句子并判断情绪是正面的、负面的还是中性的。我们可以将这个特别的任务(和大多数其他NLP任务)分成5个不同的步骤。
  1. 1. 训练一个词向量生成模型(比如Word2Vec)或者加载预训练的词向量
  2. 2. 为我们的训练集建立一个ID矩阵(稍后讨论)
  3. 3. RNN(使用LSTM单元)图形创建
  4. 4. 训练
  5. 5. 测试

加载数据

首先,我们要创建词向量。为简单起见,我们将使用预训练好的模型。
作为机器学习这个游戏中的最大玩家,Google能够在包含超过1000亿个不同单词的大规模Google新闻训练集上训练Word2Vec模型!从那个模型来看,Google能够创建300万个词向量,每个向量的维数为300。
在理想情况下,我们将使用这些向量,但由于词向量矩阵相当大(3.6GB!),我们将使用一个更加可管理的矩阵,该矩阵由一个类似的词向量生成模型Glove训练。矩阵将包含40万个词向量,每个维数为50。
我们将要导入两个不同的数据结构,一个是一个40万个单词的Python列表,一个是拥有所有单词向量值得40万x50维嵌入矩阵。
为了确保一切都已正确加载,我们可以查看词汇列表的维度和嵌入矩阵的维度。
我们还可以搜索单词列表中的一个单词,如“棒球”,然后通过嵌入矩阵访问其对应的向量。
现在我们有了自己的向量,首先是输入一个句子,然后构造它的向量表示。假如我们有输入句子“I thought the movie was incredible and inspiring”。为了获取词向量,我们可以使用Tensorflow的内嵌查找函数。这个函数需要两个参数,一个是嵌入矩阵(在我们的例子中为词向量矩阵),一个用于每个单词的id。id向量可以认为是训练集的整数表示。这基本只是每个单词的行索引。让我们来看一个具体的例子,使之具体化。
数据流水线如下图所示。
10 x 50的输出应包含序列中10个单词中的每一个的50维字向量。
在为整个训练集创建id矩阵之前,首先花一些时间为拥有的数据类型做一下可视化。这会帮助我们确定设定最大序列长度的最佳值。在先前的例子中,我们用的最大长度为10,但这个值很大程度取决于你的输入。
我们要使用的训练集是Imdb电影评论数据集。这个集合中有25000个电影评论,12,500次正面评论和12,500次评论。每个评论都存储在我们需要解析的txt文件中。积极的评论存储在一个目录中,负面评论存储在另一个目录中。以下代码将确定每个评论中的平均字数和总和。
我们还可以使用Matplot库以直方图的形式来显示数据。
从直方图及每个文件的平均字数来看,我们可以确定大多数评论低于250词,这时我们设置最大序列长度值。maxSeqLength = 250 下面将展示如何将一个单一的文件转换成id矩阵。如下是一条看起来像文本文件格式的评论。

现在,转换成一个id矩阵
现在,对我们这25000条评论做同样的工作。加载电影训练集并整理它以获得一个25000 x 250的矩阵。这是一个计算上昂贵的过程,因此,你不用再次运行整个程序,我们将加载预先计算的ID矩阵。

辅助函数

下面你会发现一些在之后神经网络训练过程中很有用的辅助函数。

RNN模型

现在,我们准备开始创建我们的Tensorflow图。首先要定义一些超参数,例如批处理大小,LSTM单元数,输出类数和训练次数。
与大多数Tensorflow图一样,我们现在需要指定两个占位符,一个用于输入到网络中,一个用于标签。定义这些占位符的最重要的部分是了解每个维度。
标签占位符是一组值,每个值分别为[1,0]或[0,1],具体取决于每个训练示例是正还是负。输入占位符中的整数每一行代表着我们在批处理中包含的每个训练示例的整数表示。
一旦我们有了输入数据占位符,我们将调用tf.nn.lookup()函数来获取词向量。对该函数的调用会通过词向量的维度返回长达最大序列长度的批大小(batch size)的3-D张量。为了可视化这个3-D张量,你可以简单的把整数化输入张量中的每个数据点看做对应的相关D维向量。
现在我们有了想要的形式的数据,尝试如何把这些输入填充进LSTM网络。我们将调用tf.nn.rnn_cell.BasicLSTMCell函数。这个函数输入一个整数代表我们要用到的LSTM单元数。这是用来调整以利于确定最优值的超参数之一。然后我们将LSTM单元包装在一个退出层,以防止网络过拟合。
最后,我们将充满输入数据的LSTM单元和3-D张量引入名为tf.nn.dynamic_rnn的函数中。该函数负责展开整个网络,并为数据流过RNN图创建路径。
作为一个备注,另一个更先进的网络架构选择是将多个LSTM神经元堆叠在一起。也就是说第一个LSTM神经元最后一个隐藏状态向量导入第二个LSTM神经元。堆叠这些神经元是帮助模型保留更多长期以来信息的一个很好的方法,但也会在模型中引入更多的参数,从而可能增加训练时间,增加更多对训练样本的需求和过拟合的概率。有关如何把堆叠LSTM加入模型的更多信息,请查看Tensorflow文档。
动态RNN函数的第一个输出可以被认为是最后一个隐藏的状态向量。该向量将重新定形,然后乘以最终权重矩阵和偏置项以获得最终输出值。
接下来,我们将定义正确的预测和精度指标,以跟踪网络的运行情况。正确的预测公式通过查看2个输出值的最大值的索引,然后查看它是否与训练标签相匹配来工作。
我们将基于最终预测值上的激活函数层定义标准交叉熵,使用Adam优化器,默认学习率为0.01。
如果你想使用Tensorboard来显示损失和准确度的值,还可以运行和修改以下代码。

超参数调优
为您的超参数选择正确的值是有效训练深层神经网络的关键部分。您会发现,您的训练损失曲线可能因您选择的优化器(Adam,Adadelta,SGD等),学习率和网络架构而不同。特别是使用RNN和LSTM时,要注意其他一些重要因素,包括LSTM单元的数量和字向量的大小。
  • 由于有着大量的时间步,RNN的难以训练臭名昭著。学习率变得非常重要,因为我们不希望权重值因为学习率高而波动,也不想由于学习率低而需要缓慢地训练。默认值为0.001是个好的开始,如果训练损失变化非常缓慢,你应该增加此值,如果损失不稳定,则应减少。
  • 优化器:在研究人员之间尚没有一致的选择,但是由于具有自适应学习速率这个属性,Adam很受欢迎(请记住,优化学习率可能随着优化器的选择而不同)。
  • LSTM单位数:该值在很大程度上取决于输入文本的平均长度。虽然更多的单位会使模型表达地更好,并允许模型存储更多的信息用于较长的文本,但网络将需要更长的时间才能训练,并且计算费用昂贵。
  • 词向量大小:词向量的维度一般在50到300之间。更大的尺寸意味着词向量能够封装更多关于该词的信息,但模型也将花费更多计算量。

训练

训练循环的基本思路是首先定义一个Tensorflow session,然后加载一批评论及其相关标签。接下来,我们调用session的run函数,该函数有两个参数,第一个被称为“fetches”参数,它定义了我们想要计算的期望值,我们希望优化器能够计算出来,因为这是使损失函数最小化的组件。第二个参数需要输入我们的feed_dict,这个数据结构是我们为所有占位符提供输入的地方。我们需要提供评论和标签的批次,然后这个循环在一组训练迭代器上重复执行。
我们将会加载一个预训练模型而不是在这款notebook上训练网络(这需要几个小时)。
如果你决定在自己的机器上训练这个模型,你可以使用TensorBoard来跟踪训练过程。当以下代码在运行时,使用你的终端进入此代码的执行目录,输入tensorboard --logdir=tensorboard,并使用浏览器访问http://localhost:6006/,以对训练过程保持关注。

加载预训练模型

我们的预训练模型在训练过程中的精度和损失曲线如下所示。
查看如上训练曲线,似乎模型的训练进展顺利。亏损稳步下降,准确率接近100%。然而,在分析训练曲线时,我们还应该特别注意模型对训练数据集过拟合的可能。过拟合是机器学习中的常见现象,模型变得适合于训练模型而失去了推广到测试集的能力。这意味着训练一个网络直达到0训练损失可能不是一个最好的方式,来获取在一个从未见过的数据集上表现良好的准确模型。早停(early stopping)是一种直观的技术,普遍应用于LSTM网络来解决过拟合问题。基本思想是在训练集上训练模型,同时还可以一次次在测试集上测量其性能。一旦测试错误停止了稳定的下降并开始增加,我们会知道该停止训练,以为这是神经网络开始过拟合的信号。
加载预训练模型涉及定义另一个Tensorflow会话,创建Saver对象,然后使用该对象调用恢复功能。此函数接受2个参数,一个用于当前会话,另一个用于保存模型的名称。
然后我们将从测试集加载一些电影评论,注意,这些评论是模型从未训练过的。运行以下代码时可以看到每批测试的精准度。

结论

在这篇笔记中,我们对情绪分析进行了深入地学习。我们研究了整个流程中涉及的不同组件,然后研究了在实践中编写Tensorflow代码来实现模型的过程。最后,我们对模型进行了培训和测试,以便能够对电影评论进行分类。
在Tensorflow的帮助下,你可以创建自己的情绪分类器,以了解世界上大量的自然语言,并使用结果形成具有说服力的论点。感谢您的阅读。
重磅!忆臻自然语言处理-学术微信交流群已成立
可以扫描下方二维码,小助手将会邀请您入群交流,
注意:请大家添加时修改备注为 [学校/公司 + 姓名 + 方向]
例如 —— 哈工大+张三+对话系统。
号主,微商请自觉绕道。谢谢!
继续阅读
阅读原文