6.3 序列分类
序列分类的任务是为整个输入序列预测一个类别标签。在许多领域中,包括基因和金融领域,这样的问题都极为常见。自然语言处理中的一个突出例子是情绪分析,即从用户撰写的文字预测他对某个给定话题的态度。例如,可以预测提到选举中某位候选人的推文的情绪,并用它来预测选举结果。另一个例子是依据评论预测产品或电影的评分。在NLP社区,这已成为一项基准任务,因为评论中通常包含有数值评分,可以方便地作为目标值。
我们将使用一个来自国际电影数据库(International Movie Database)的影评数据集,该数据集的目标值是二元的——正面的和负面的。在该数据集中,任何只查看单词是否出现的朴素方法都将失效,因为在语言中通常存在大量否定、反语和模糊性。我们将构建一个可对来自上一节的词向量进行操作的循环神经网络。这个循环网络将逐个单词地查看每条评论。依据最后的那个单词的活性值,将训练一个用于预测整条评论的情绪的分类器。由于是按照端到端的方式训练模型,RNN将从单词中收集那些对于最终分类最有价值的信息,并进行编码。
6.3.1 Imdb影评数据集
这个影评数据集是由斯坦福大学的人工智能实验室提供的:
http://ai.stanford.edu/~amaas/data/sentiment/,它是一个经过压缩的tar文档,其中正面的和负面的评论可从分列于两个文件夹中的文本文件中获取。我们对这些文本进行了与上一节完全相同的处理:利用正则表达式提取纯文本,并将其中的字母全部转换为小写。
6.3.2 使用词向量嵌入
在词向量嵌入一节中,曾解释过,嵌入表示比独热编码的词语具有更丰富的语义。因此,如果使RNN工作在影评的被嵌入的而非独热编码的单词上,则有助于RNN获得更好的性能。为此,可使用上一节中计算得到的词汇表和嵌入表示。相关的代码应当非常简单。我们仅利用词汇表确定单词的索引,并利用该索引找到正确的词向量。下面展示的这个类还会对序列进行填充,使它们都拥有相同的长度,以便将更容易地将多个影评数据批量送入网络。
6.3.3 序列标注模型
我们希望对文本序列所体现的情绪进行分类。由于这是一个有监督学习问题,所以为该模型传入两个占位符:一个用于输入数据data(或输入序列),另一个用于目标值target(或情绪)。此外,还传入了包含配置参数(如循环层的尺寸、单元架构(LSTM、GRU等))的params对象,以及所要使用的优化器。下面具体实现相关属性并对其进行详细讨论。
首先获取当前批数据中各序列的长度。该信息是必须要了解的,因为数据是以单个张量的形式到来的,各序列需要以最长的影评长度为准进行长度补0处理。我们并不追踪每条影评的序列长度,而是在TensorFlow中动态计算。为了获取每个序列的长度,利用绝对值中的最大值对词向量进行缩减。对于零向量,所得到的标量为0;而对任意实型词向量,对应的标量为一个大于0的实数。然后利用tf.sign()将这些值离散化为0或1,并将这些结果沿时间步相加,从而得到每个序列的长度。最终得到的张量的长度与批数据容量相同,且以标量形式包含了每个序列的长度。
6.3.4 来自最后相关活性值的softmax层
对于预测,我们仍像往常一样定义一个RNN。不过,我们希望通过将一个softmax层堆叠到最后一个活性值之上,以实现对RNN的结构扩充。对于RNN,我们使用params对象中定义的单元类型和单元数量。利用已定义的length属性仅向RNN提供批数据的至多length行。之后,获取每个序列的最后输出活性值,并将其送入一个softmax层。如果读者一直在跟随本书学习,现在定义softmax层应当是轻而易举的事。
请注意,对于训练批数据中的每个序列,RNN最后的相关输出活性值都有一个不同的索引。这是因为每条影评的长度都不同。我们已经知道了每个序列的长度,那么如何利用它对最后的活性值进行选择?这里的问题在于希望在时间步这个维度上,也就是在批数据形状sequences×time_steps×word_vectors的第2个维度上建立索引。
截至本书撰写之时,TensorFlow仅支持用tf.gather()沿第1维建立索引。因此,我们将输出活性值的形状sequences×time_steps×word_vectors的前两维扁平化(flatten),并向其添加序列长度。实际上,我们只需添加length-1,这样便可选择最后的有效时间步。
现在即将能够用TensorFlow进行整个模型的端到端训练,将误差经过softmax层和所使用的RNN时间步反向传播。训练中唯一缺少的是一个代价函数。
6.3.5 梯度裁剪
对于序列分类问题,我们可使用任何于分类有意义的代价函数,因为模型的输出只是一个在可用的所有类别上的概率分布。在本例中,两个类别分别是正面情绪和负面情绪。我们准备采用上一章介绍的标准交叉熵代价函数。
为将代价函数最小化,可使用配置中定义的优化器。但是,我们准备通过增加梯度裁剪(gradient clipping)对目前所学习到的结果进行改善。RNN训练难度较大,而且如果不同超参数搭配不当,权值极容易发散。梯度裁剪的主要思想是将梯度值限制在一个合理的范围内。按照这种方式,便可对最大权值的更新进行限制。
TensorFlow支持利用每个优化器实例提供的compute_gradients()函数进行推演。这样,就可以对梯度进行修改,并通过apply_gradients()函数应用权值的变化。对于梯度裁剪,如果梯度分量小于-limit,则将它们设置为-limit;若梯度分量大于limit,则将它们设置为limit。唯一需要一点技巧的地方是TensorFlow中的导数可取为None,表示某个变量与代价函数没有关系。虽然从数学上讲,这些导数应为零向量,但使用None却有利于内部的性能优化。对于那些情形,我们仅将None值传回。
6.3.6 训练模型
下面开始训练上一节中定义的模型。正如之前所说的,我们准备将影评逐个单词地送入循环神经网络,因此每个时间步都是一个由词向量构成的批数据。对上一节中的batched()函数进行改造,使其可查找词向量,并将所有的序列进行长度补齐。
现在就可轻松地开始训练模型了,具体步骤包括:定义超参数、加载数据集和词向量,并将模型运行在经过预处理的训练批数据上。
此时,模型能够成功训练不但取决于网络结构和超参数,而且也取决于词向量的质量。如果没有像上一节所描述的那样训练自己的词向量,可从实现了skip-gram模型的word2vec项目[1]加载预训练的词向量,也可从与之非常类似的来自斯坦福NLP研究组的Glove模型[2]加载词向量。无论选择哪一种模型,都可从网上找到Python加载器。
这样就拥有了这个模型,那么可以用它做哪些事?在Kaggle这个著名的主办数据科学挑战赛的网站上有一个开放学习竞赛,它采用的是与本节中完全相同的IMDB影评数据。因此,如果你有兴趣将自己的预测结果与他人的进行比较,可在他们的测试集上运行该模型,并将结果上载至https://www.kaggle.com/c/word2vec-nlp-tutorial。
[1] https://code.google.com/archive/p/word2vec/
[2] http://nlp.stanford.edu/projects/glove/
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论