深度学习自然语言处理教程(三)
五、研究论文实现:情感分类
第五章以一篇研究论文中情感分析的实现结束了这本书。本章的第一节详细介绍了所提到的方法,接下来的第二节将使用 TensorFlow 专门介绍其实现。为了确保我们使用的实际论文和我们的结果之间存在差异,我们选择了不同的数据集进行测试,因此我们的结果的准确性可能与实际研究论文中的结果有所不同。
正在使用的数据集可供公众使用,并作为样本数据集包含在 Keras 库中。本章将第二章和第三章中分享的理论和实践范例联系起来,并使用研究论文中遵循的建模方法创建了一个附加层。
我们的实现工作的成功归功于论文“结构化的自我关注句子嵌入”( https://arxiv.org/pdf/1703.03130.pdf ),该论文由来自 IBM Watson 和蒙特利尔大学( Université de Montréal )的蒙特利尔学习算法研究所(MILA)的研究科学家团队在 ICLR 2017(第五届国际学习表示会议)上提交,并随后发表。
本文提出了一种新的建模技术,通过引入自我注意机制来提取可解释的句子嵌入。该模型使用二维矩阵代替向量来表示句子嵌入,其中每个矩阵表示句子的不同片段。此外,还提出了一种自关注机制和一个独特的正则项。所提出的嵌入方法可以容易地被可视化,以计算出句子的哪些特定部分最终被编码到句子嵌入中。所进行的研究分享了所提出的模型在三种不同类型的任务上的性能评估。
- 作者简介
- 情感分类
- 文本蕴涵
与目前的其他句子嵌入技术相比,该模型对于前面三种类型的任务都非常有前途。
自我注意句子嵌入
先前已经提出了各种有监督和无监督的句子嵌入模型,例如跳过思想向量、段落向量、递归自编码器、顺序去噪自编码器、FastSent 等。,但该论文中提出的方法使用了一种新的自我注意机制,允许它将句子的不同方面提取到多个向量表示中。带有惩罚项的矩阵结构赋予模型更大的能力来从输入句子中解开潜在信息。
此外,语言结构不用于指导句子表示模型。此外,使用这种方法,人们可以很容易地创建可视化,这有助于对所学表征的解释。
skip-thought vector 是一个通用分布式句子编码器的无监督学习。利用书籍中文本的连续性,训练一个编码器-解码器模型,试图重建一段编码文章的周围句子。因此,共享语义和句法属性的句子被映射到相似的向量表示。有关这方面的进一步信息,请参阅原文,可在 https://arxiv.org/abs/1506.06726 查阅。
段落向量是一种无监督算法,它从可变长度的文本片段(如句子、段落和文档)中学习固定长度的特征表示。该算法用密集向量来表示每个文档,该密集向量被训练来预测文档中的单词。论文中的实证结果表明,段落向量优于词袋模型以及其他文本表示技术。关于这一点的更详细的解释包含在原始研究论文中,可在 https://arxiv.org/abs/1405.4053 获得。
图 5-1 显示了一个样本模型结构,用于展示句子嵌入模型与全连接和 softmax 层结合进行情感分析时的情况。
Note
蓝色代表隐藏表示,红色代表权重、注释或输入/输出。
图 5-1
The sentence-embedding model is computed as multiple weighted sums of hidden states from a bidirectional long short-term memory (LSTM) (h1, …, hn)
提议的方法
这一节包括建议的自我注意句子嵌入模型和正则化项。这两个概念都在单独的小节中解释,就像实际论文中提到的那样。读者可以选择参考原始论文以获得更多信息,尽管本节中介绍的内容足以对建议的方法有一个总体的理解。
所提出的注意机制仅执行一次,并且它直接关注对于辨别目标有意义的语义。它不太关注单词之间的关系,而是更关注每个单词所构成的整个句子的语义。在计算方面,该方法随着句子长度的增加而增加,因为它不需要 LSTM 计算所有先前单词的注释向量。
模型
在“结构化自我注意句子嵌入”中提出的句子嵌入模型由两部分组成:
- 双向 LSTM
- 自我注意机制
自我关注机制为 LSTM 隐藏状态提供了一组求和权向量(图 5-2 )。
图 5-2
The summation weights (Ai1, …, Ain) are computed as illustrated
求和权重向量的集合点缀有 LSTM 隐藏状态,并且得到的加权 LSTM 隐藏状态被认为是句子的嵌入。例如,它可以与多层感知器(MLP)相结合,应用于下游应用。所示的图属于一个示例,其中所提出的句子嵌入模型被应用于情感分析,与全连接层和 softmax 层相结合。
Note
对于情感分析练习,上图中使用的数字足以描述所需的模型。
(可选)除了使用完全连接的层之外,在该论文中还提出了一种通过利用矩阵句子嵌入的二维结构来修剪权重连接的方法,并在其附录 a 中进行了详细描述
假设我们有一个有 n 个标记的句子,用单词嵌入序列表示。
这里 w i 是一个向量,代表嵌入句子中第 I 个单词的 d 维单词。因此,s 是一个表示为二维矩阵的序列,它将所有的单词嵌入连接在一起。s 的形状应该是 n 乘 d。
现在,序列 S 中的每个条目都是相互独立的。为了在单个句子中的相邻单词之间获得一些依赖性,我们使用双向 LSTM 来处理句子
,然后我们将每个与
连接起来,以获得隐藏状态 h t 。设每个单向 LSTM 的隐藏单元号为 u。为简单起见,我们将所有 n 个 h t s 记为 H,其大小为 n 乘 2u。
H = (h 1 ,h 2 ,…,h n
我们的目标是将一个变长的句子编码成一个固定大小的嵌入。我们通过选择 h 中的 n 个 LSTM 隐藏向量的线性组合来实现这一点。计算线性组合需要自我注意机制。注意机制将所有 LSTM 隐藏状态 H 作为输入,并输出权重 a 的向量,如下:
这里 W s1 是一个形状为 da-x-2u 的权重矩阵,W s2 是一个大小为 d a 的参数向量,其中 d a 是一个我们可以任意设置的超参数。因为 H 的大小为 n 乘 2u,所以注释向量 a 的大小为 softmax()确保所有计算的权重相加为 1。然后,我们根据 a 提供的权重将 LSTM 隐藏状态 H 相加,以获得输入句子的向量表示 m。
这种向量表示通常关注句子的特定组成部分,例如一组特殊的相关单词或短语。因此,它应该反映句子语义的一个方面或组成部分。然而,一个句子中可以有多个组成部分,它们共同构成句子的整体语义,特别是对于长句。(例如,两个分句由 and 连接在一起。)因此,为了表示句子的整体语义,我们需要多个 m 来关注句子的不同部分。因此,我们必须进行多次注意力跳跃。假设我们想从句子中提取 r 个不同的部分。为此,我们将 W s2 扩展成一个 r 乘 d a 矩阵,记为 W s2 ,得到的标注向量 a 成为标注矩阵 a
形式上
这里,softmax()沿着其输入的第二维度执行。我们可以把前面的方程看作是一个无偏差的两层 MLP,其隐单元数为 d a ,参数为{W s2 ,W s1 }。
嵌入向量 m 然后变成 r 乘 2u 的嵌入矩阵 m。我们通过将注释矩阵 A 和 LSTM 隐藏状态 h 相乘来计算 r 加权和。得到的矩阵是句子嵌入:
M = A H
惩罚条款
如果注意机制总是为所有 r 跳提供相似的求和权重,则嵌入矩阵 M 可能遭受冗余问题。因此,我们需要一个惩罚项,以鼓励不同注意力跳跃的加权向量总和的多样性。
评估差异的最佳方式无疑是任意两个总权重向量之间的 Kullback Leibler 散度(KL)。
KL 散度用于度量同一变量 x 上两个概率分布的差异,它与交叉熵和信息散度有关。对于给定的两个概率分布 p(x)和 q(x),KL 散度作为 q(x)与 p(x)的散度的非对称度量,被表示为 D KL (p(x),q(x)),并且是当 q(x)被用来逼近 p(x)时丢失的信息的度量。
对于一个离散的随机变量 x,若 p(x)和 q(x)是它的两个概率分布,则 p(x)和 q(x)之和均为 1,且 p(x) > 0 和 q(x) > 0 对于 x 中的任意一个 X.
其中,
,当且仅当,P = Q
当使用基于 q(x)的码而不是使用基于 p(x)的码时,KL 散度测量对来自 p(x)的样本进行编码所需的额外比特的预期数量。通常,p(x)代表观测值的“实际”数据分布,或精确计算的理论分布,q(x)代表理论,或模型,或 p(x)的近似值。与离散型相似,KL 散度也有连续型。
KL 散度不是距离度量,即使它度量两个分布之间的“距离”,因为它不是度量。此外,它本质上是不对称的,即,在大多数情况下,从 p(x)到 q(x)的 KL 散度值不同于从 q(x)到 p(x)的 KL 散度值。此外,它可能不满足三角不等式。
然而,在这种情况下,这不是非常稳定的,因为在这里,正试图最大化一组 KL 散度(而不是通常情况下仅最小化一个),并且当执行注释矩阵 A 的优化时,为了在不同的 softmax 输出单元处具有许多足够小的值或者甚至零值,大量的零使得训练不稳定。KL divergence 没有提供的另一个特性是当前需要的,即每一行只关注语义的一个方面。这要求注释 softmax 输出中的概率质量更加集中,但是使用 KL 散度惩罚,这将达不到目的。
因此,引入了一个新的惩罚项,它克服了前面提到的缺点。与 KL 散度惩罚相比,这一项只消耗三分之一的计算量。从单位矩阵中减去 A 及其转置的点积,作为冗余度的度量。
在上式中,代表矩阵的 Frobenius 范数。像添加 L2 正则化项一样,这个惩罚项 P 将乘以一个系数,我们将其与原始损失一起最小化,这取决于下游应用。
让我们考虑 A 中的两个不同的求和向量,a i 和 a j ,由于 softmax,A 中任何求和向量内的所有条目加起来应该是 1。因此,它们可以被视为离散概率分布中的概率质量。对于 A.A T 矩阵中的任何非对角元素 a ij (i ≠ j),它对应于两个分布的逐元素乘积的求和:
其中 a k i 和 a k j 是 a i 和 Aj 中的第 k 个元素在最极端的情况下,当两个概率分布 a i 和 a j 之间没有重叠时,相应的 a ij 将为 0,否则它将具有正值。在另一个极端,如果两个分布是相同的,并且都集中在一个单词上,那么它将具有最大值 1。我们从 A.A T 中减去一个单位矩阵,这迫使 A.A T 对角线上的元素近似为 1,这鼓励每个求和向量 a i 关注尽可能少的单词数,迫使每个向量关注单个方面,而所有其他元素为 0,这惩罚了不同求和向量之间的冗余。
形象化
一般情况可视化呈现作者分析任务的结果,并显示正在使用的两种类型的可视化。第二个案例是关于情感分析的,利用了第二种可视化手段,即 Yelp 上的评论热图。
一般情况
由于注释矩阵 a 的存在,句子嵌入的解释非常简单。对于句子嵌入矩阵 M 中的每一行,其对应的注释向量 a i 都存在。该向量中的每个元素对应于该位置上的令牌的 LSTM 隐藏状态的贡献大小。因此,可以为嵌入矩阵 m 的每一行绘制热图
这种可视化方法暗示了在嵌入的每个部分中编码了什么,增加了一个额外的解释层。图 5-3 显示了在 Twitter Age 数据集上训练的两个模型的热图( http://pan.webis.de/clef16/pan16-web/author-profiling.html )。
图 5-3
Heatmaps of six random detailed attentions from 30 rows of matrix embedding, and for two models without and with 1.0 penalization
第二种可视化方法可以通过将所有注释向量相加,然后将得到的权重向量归一化为 1 来实现。因为它把一个句子的语义的所有方面都加了起来,所以它产生了一个嵌入主要关注什么的总体视图。人们可以计算出哪些单词是嵌入考虑最多的,哪些是嵌入跳过的。图 5-4 通过将所有 30 个注意力权重向量相加来表示整体注意力的概念,包括惩罚和不惩罚。
图 5-4
Overall attention without penalization and with 1.0 penalization
情感分析案例
对于研究论文,已经为情感分析任务选择了 Yelp 数据集( www.yelp.com/dataset_challenge )。它由 270 万条 Yelp 评论组成,从中随机选择了 50 万条评论星对作为训练集,2000 条用于开发集,2000 条用于测试集。将评论作为输入,并且根据用户为对应于企业商店的每个评论实际写了什么来预测星级的数量。
使用 100 维的 word2vec 来初始化单词嵌入,并且在训练期间进一步调整嵌入。目标星的数量是在[1,2,3,4,5]的范围内的整数,包括 1,2,3,4,5,并且因此,该任务被视为分类任务,即,将评论文本分类为五个类别之一,并且分类精度被用于测量。对于两个基线模型,使用的批量大小为 32,输出 MLP 中的隐藏单位数选择为 3,000。
作为对已学习句子嵌入的解释,下面使用第二种可视化方式,为数据集中的一些评论绘制热图。随机选择三篇评论。如图 5-5 所示,该模型主要学习捕捉评论中的一些关键因素,这些因素强烈地表明了句子背后的情感。对于大多数短评论,该模型设法捕捉所有促成极端得分的关键因素,但是对于较长评论,该模型仍然不能捕捉所有相关因素。正如在第一篇评论中所反映的那样,很多注意力都放在了一个单一的因素上,“没什么特别的”,而很少注意到其他的关键点,如“令人讨厌的事情”,“这么硬/冷”等。
图 5-5
Attention of sentence embedding on three different Yelp reviews, trained without and with 1.0 penalization
研究成果
本文引入了一个固定大小的矩阵句子嵌入,具有自我注意机制,有助于解释该模型中的句子嵌入深度。引入注意力机制允许最终句子嵌入通过注意力总和直接访问先前的 LSTM 隐藏状态。因此,LSTM 不需要把每一条信息都带到它最后隐藏的状态。相反,每个 LSTM 隐藏状态仅被期望提供关于每个单词的短期上下文信息,而需要长期依赖性的高级语义可以被注意力机制直接拾取。这种设置减轻了 LSTM 继续长期依赖的负担。将注意力机制中的元素相加的概念非常原始。它可以比这更复杂,这将允许对 LSTM 的隐藏状态进行更多的操作。
该模型可以将任何可变长度的序列编码成固定大小的表示形式,而不会遇到长期依赖问题。这为模型带来了很大的可扩展性,无需任何重大修改,就可以直接应用于更长的内容,比如段落、文章等。
实现情感分类
我们利用互联网电影数据库,俗称 IMDb ( www.imdb.com ),为情感分类问题选择数据集。它提供了大量的数据集,包括图像和文本,这对深度学习和数据分析的多种研究活动非常有用。
对于情感分类,我们使用了一组 25,000 条电影评论,这些评论附有它们的正面和负面标签。公开可用的评论已经被预处理并被编码为单词索引序列,即整数。单词基于它们在数据集中的总频率进行排序,即,具有第二高频率的单词或单词被索引为 2,等等。将这样的索引附加到单词上将有助于根据单词的频率将单词列入候选名单,例如挑选出前 2000 个最常用的单词或删除前 10 个最常用的单词。下面是查看训练数据集示例的代码。
from keras.datasets import imdb
(X_train,y_train), (X_test,y_test) = imdb.load_data(num_words=1000, index_from=3)
# Getting the word index used for encoding the sequences
vocab_to_int = imdb.get_word_index()
vocab_to_int = {k:(v+3) for k,v in vocab_to_int.items()} # Starting from word index offset onward
# Creating indexes for the special characters : Padding, Start Token, Unknown words
vocab_to_int["<PAD>"] = 0
vocab_to_int["<GO>"] = 1
vocab_to_int["<UNK>"] = 2
int_to_vocab = {value:key for key,value in vocab_to_int.items()}
print(' '.join(int_to_vocab[id] for id in X_train[0] ))
>
<GO> this film was just brilliant casting <UNK> <UNK> story direction <UNK> really <UNK> the part they played and you could just imagine being there robert <UNK> is an amazing actor and now the same being director <UNK> father came from the same <UNK> <UNK> as myself so i loved the fact there was a real <UNK> with this film the <UNK> <UNK> throughout the film were great it was just brilliant so much that i <UNK> the film as soon as it was released for <UNK> and would recommend it to everyone to watch and the <UNK> <UNK> was amazing really <UNK> at the end it was so sad and you know what they say if you <UNK> at a film it must have been good and this definitely was also <UNK> to the two little <UNK> that played the <UNK> of <UNK> and paul they were just brilliant children are often left out of the <UNK> <UNK> i think because the stars that play them all <UNK> up are such a big <UNK> for the whole film but these children are amazing and should be <UNK> for what they have done don't you think the whole story was so <UNK> because it was true and was <UNK> life after all that was <UNK> with us all
情感分类代码
这本书的最后一节涵盖了在前面提到的论文中描述的概念的实现及其在所选 IMDb 数据集的情感分类中的使用。所需的 IMDb 数据集可通过以下代码自动下载。如果需要,也可以从以下网址下载数据集,并查看可用的评论集: https://s3.amazonaws.com/text-datasets/imdb_full.pkl 。
Note
在运行代码之前,请确保计算机上有打开的互联网连接,以启用数据集下载和 TensorFlow 版本 1.3.0。
“0”未用于编码任何单词,因为它用于编码词汇表中的未知单词。
导入所需的包,并在需要时检查包的版本。
# Importing TensorFlow and IMDb dataset from keras library
from keras.datasets import imdb
import tensorflow as tf
> Using TensorFlow backend.
# Checking TensorFlow version
print(tf.__version__)
> 1.3.0
from __future__ import print_function
from tensorflow.python.ops import rnn, rnn_cell
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
%matplotlib inline
下一步是从 IMDb 的评论数据集创建训练/测试数据集。Keras datasets 为它提供了一个内置函数,该函数返回以下带有序列和标签列表的元组对:
X_train、X_test:这些是具有索引列表的序列列表,即分配给每个单词的标准整数。如果在导入数据集时,指定了num_words参数,则选择的最大可能索引值是num_words-1,如果指定了maxlen参数,则它用于选择最大可能序列长度。y_train、y_test:这些是整数标签的列表,分别为正面和负面评论指定 1 或 0。
imdb.load_data()函数采用八个参数来自定义检查数据集选择。以下是对这些论点的详细解释:
path:如果数据不在本地的 Keras 数据集文件夹中,它将被下载到指定的位置。num_words:(类型:integer或None)选择要考虑用于建模目的的最频繁出现的单词。超出此范围且频率低于此范围的单词将被替换为序列数据中的oov_char值。skip_top: (Type:integer)这将跳过选择中最常用的单词。这种被忽略的字在序列数据中被替换为oov_char值。maxlen:(类型:int)用于指定序列的最大长度。超过指定长度的序列将被截断。seed:(类型:int)设置种子以再现数据洗牌start_char:(类型:int)该字符标记一个序列的开始。它被设置为 1,因为 0 通常用于填充字符。oov_char: (Type:int)由num_words或skip_top参数删除的单词将被替换为此字符。index_from:(类型:int)索引实际单词等。它是一个单词索引偏移量。
# Creating Train and Test datasets from labeled movie reviews
(X_train, y_train), (X_test, y_test) = imdb.load_data(path="imdb_full.pkl",num_words=None, skip_top=0, maxlen=None, seed=113, tart_char=1, oov_char=2, index_from=3)
> Downloading data from https://s3.amazonaws.com/text-datasets/imdb.npz
评论集中的每个序列的长度为 200,并且已经从训练数据集中创建了进一步的词汇。图 5-6 显示评论中的字数分布。
图 5-6
Distribution of word counts in each of the reviews
X_train[:2]
> array([ list([1, 14, 22, 16, 43, 530, 973, 1622, 1385, 65, 458, 4468, 66, 3941, 4, 173, 36, 256, 5, 25, 100, 43, 838, 112, 50, 670, 22665, ....
t = [item for sublist in X_train for item in sublist]
vocabulary = len(set(t))+1
a = [len(x) for x in X_train]
plt.plot(a)
为从句子中选择的序列指定一个最大长度,如果检查长度小于该长度,则在新创建的序列后添加填充,直到达到最大长度。
max_length = 200 # specifying the max length of the sequence in the sentence
x_filter = []
y_filter = []
# If the selected length is lesser than the specified max_length, 200, then appending padding (0), else only selecting desired length only from sentence
for i in range(len(X_train)):
if len(X_train[i])<max_length:
a = len(X_train[i])
X_train[i] = X_train[i] + [0] * (max_length - a)
x_filter.append(X_train[i])
y_filter.append(y_train[i])
elif len(X_train[i])>max_length:
X_train[i] = X_train[i][0:max_length]
用单词嵌入大小、隐藏单元数量、学习速率、批量大小和训练迭代总数来声明模型超参数。
#declaring the hyper params
embedding_size = 100 # word vector size for initializing the word embeddings
n_hidden = 200
learning_rate = 0.06
training_iters = 100000
batch_size = 32
beta =0.0001
声明与当前模型架构和数据集相关的附加参数,max_length,要分类的类的数量,自关注 MLP 的隐藏层中的单元数量,以及矩阵嵌入中的行数。
n_steps = max_length # timestepswords
n_classes = 2 # 0/1 : binary classification for negative and positive reviews
da = 350 # hyper-parameter : Self-attention MLP has hidden layer with da units
r = 30 # count of different parts to be extracted from sentence (= number of rows in matrix embedding)
display_step =10
hidden_units = 3000
将训练数据集值和标签分别转换为所需的数组后转换和编码格式。
y_train = np.asarray(pd.get_dummies(y_filter))
X_train = np.asarray([np.asarray(g) for g in x_filter])
创建内部文件夹来记录日志。
logs_path = './recent_logs/'
创建一个DataIterator类,以产生给定批量的随机数据。
class DataIterator:
""" Collects data and yields bunch of batches of data
Takes data sources and batch_size as arguments """
def __init__(self, data1,data2, batch_size):
self.data1 = data1
self.data2 = data2
self.batch_size = batch_size
self.iter = self.make_random_iter()
def next_batch(self):
try:
idxs = next(self.iter)
except StopIteration:
self.iter = self.make_random_iter()
idxs = next(self.iter)
X =[self.data1[i] for i in idxs]
Y =[self.data2[i] for i in idxs]
X = np.array(X)
Y = np.array(Y)
return X, Y
def make_random_iter(self):
splits = np.arange(self.batch_size, len(self.data1), self.batch_size)
it = np.split(np.random.permutation(range(len(self.data1))), splits)[:-1]
return iter(it)
初始化权重和偏差,并在下一步输入占位符。设置神经网络中权重的一般规则是接近零,但不能太小。一个好的做法是在[y,y]范围内开始您的权重,其中 y = 1/ (n 是给定神经元的输入数量)。
############ Graph Creation ################
# TF Graph Input
with tf.name_scope("weights"):
Win = tf.Variable(tf.random_uniform([n_hidden*r, hidden_units],-1/np.sqrt(n_hidden),1/np.sqrt(n_hidden)), name='W-input')
Wout = tf.Variable(tf.random_uniform([hidden_units, n_classes],-1/np.sqrt(hidden_units),1/np.sqrt(hidden_units)), name='W-out')
Ws1 = tf.Variable(tf.random_uniform([da,n_hidden],-1/np.sqrt(da),1/np.sqrt(da)), name="Ws1")
Ws2 = tf.Variable(tf.random_uniform([r,da],-1/np.sqrt(r),1/np.sqrt(r)), name="Ws2")
with tf.name_scope("biases"):
biasesout = tf.Variable(tf.random_normal([n_classes]), name='biases-out')
biasesin = tf.Variable(tf.random_normal([hidden_units]), name='biases-in')
with tf.name_scope('input'):
x = tf.placeholder("int32", [32,max_length], name='x-input')
y = tf.placeholder("int32", [32, 2], name='y-input')
用嵌入的向量在相同的默认图形上下文中创建张量。这需要嵌入矩阵和输入张量,例如检查向量。
with tf.name_scope('embedding'):
embeddings = tf.Variable(tf.random_uniform([vocabulary, embedding_size],-1, 1), name="embeddings")
embed = tf.nn.embedding_lookup(embeddings,x)
def length(sequence):
# Computing maximum of elements across dimensions of a tensor
used = tf.sign(tf.reduce_max(tf.abs(sequence), reduction_indices=2))
length = tf.reduce_sum(used, reduction_indices=1)
length = tf.cast(length, tf.int32)
return length
使用以下方法重复使用权重和偏差:
with tf.variable_scope('forward',reuse=True):
lstm_fw_cell = rnn_cell.BasicLSTMCell(n_hidden)
with tf.name_scope('model'):
outputs, states = rnn.dynamic_rnn(lstm_fw_cell,embed,sequence_length=length(embed),dtype=tf.float32,time_major=False)
# in the next step we multiply the hidden-vec matrix with the Ws1 by reshaping
h = tf.nn.tanh(tf.transpose(tf.reshape(tf.matmul(Ws1,tf.reshape(outputs,[n_hidden,batch_size*n_steps])), [da,batch_size,n_steps]),[1,0,2]))
# in this step we multiply the generated matrix with Ws2
a = tf.reshape(tf.matmul(Ws2,tf.reshape(h,[da,batch_size*n_steps])),[batch_size,r,n_steps])
def fn3(a,x):
return tf.nn.softmax(x)
h3 = tf.scan(fn3,a)
with tf.name_scope('flattening'):
# here we again multiply(batch) of the generated batch with the same hidden matrix
h4 = tf.matmul(h3,outputs)
# flattening the output embedded matrix
last = tf.reshape(h4,[-1,r*n_hidden])
with tf.name_scope('MLP'):
tf.nn.dropout(last,.5, noise_shape=None, seed=None, name=None)
pred1 = tf.nn.sigmoid(tf.matmul(last,Win)+biasesin)
pred = tf.matmul(pred1, Wout) + biasesout
# Define loss and optimizer
with tf.name_scope('cross'):
cost = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(logits =pred, labels = y) + beta*tf.nn.l2_loss(Ws2) )
with tf.name_scope('train'):
optimizer = tf.train.AdamOptimizer(learning_rate=learning_rate)
gvs = optimizer.compute_gradients(cost)
capped_gvs = [(tf.clip_by_norm(grad,0.5), var) for grad, var in gvs]
optimizer.apply_gradients(capped_gvs)
optimized = optimizer.minimize(cost)
# Evaluate model
with tf.name_scope('Accuracy'):
correct_pred = tf.equal(tf.argmax(pred,1), tf.argmax(y,1))
accuracy = tf.reduce_mean(tf.cast(correct_pred, tf.float32))
tf.summary.scalar("cost", cost)
tf.summary.scalar("accuracy", accuracy)
> <tf.Tensor 'accuracy:0' shape=() dtype=string>
# merge all summaries into a single "summary operation" which we can execute in a session
summary_op =tf.summary.merge_all()
# Initializing the variables
train_iter = DataIterator(X_train,y_train, batch_size)
init = tf.global_variables_initializer()
# This could give warning if in case the required port is being used already
# Running the command again or releasing the port before the subsequent run should solve the purpose
开始训练模型。确保batch_size足以满足系统要求。
with tf.Session() as sess:
sess.run(init)
# Creating log file writer object
writer = tf.summary.FileWriter(logs_path, graph=tf.get_default_graph())
step = 1
# Keep training until reach max iterations
while step * batch_size < training_iters:
batch_x, batch_y = train_iter.next_batch()
sess.run(optimized, feed_dict={x: batch_x, y: batch_y})
# Executing the summary operation in the session
summary = sess.run(summary_op, feed_dict={x: batch_x, y: batch_y})
# Writing the values in log file using the FileWriter object created above
writer.add_summary(summary, step*batch_size)
if step % display_step == 2:
# Calculate batch accuracy
acc = sess.run(accuracy, feed_dict={x: batch_x, y: batch_y})
# Calculate batch loss
loss = sess.run(cost, feed_dict={x: batch_x, y: batch_y})
print ("Iter " + str(step*batch_size) + ",
Minibatch Loss= " + "{:.6f}".format(loss) + ", Training Accuracy= " + "{:.2f}".format(acc*100) + "%")
step += 1
print ("Optimization Finished!")
> Iter 64, Minibatch Loss= 68.048653, Training Accuracy= 50.00%
> Iter 384, Minibatch Loss= 69.634018, Training Accuracy= 53.12%
> Iter 704, Minibatch Loss= 50.814949, Training Accuracy= 46.88%
> Iter 1024, Minibatch Loss= 39.475891, Training Accuracy= 56.25%
> Iter 1344, Minibatch Loss= 11.115482, Training Accuracy= 40.62%
> Iter 1664, Minibatch Loss= 7.060193, Training Accuracy= 59.38%
> Iter 1984, Minibatch Loss= 2.565218, Training Accuracy= 43.75%
> Iter 2304, Minibatch Loss= 18.036911, Training Accuracy= 46.88%
> Iter 2624, Minibatch Loss= 18.796995, Training Accuracy= 43.75%
> Iter 2944, Minibatch Loss= 56.627518, Training Accuracy= 43.75%
> Iter 3264, Minibatch Loss= 29.162407, Training Accuracy= 43.75%
> Iter 3584, Minibatch Loss= 14.335728, Training Accuracy= 40.62%
> Iter 3904, Minibatch Loss= 1.863467, Training Accuracy= 53.12%
> Iter 4224, Minibatch Loss= 7.892468, Training Accuracy= 50.00%
> Iter 4544, Minibatch Loss= 4.554517, Training Accuracy= 53.12%
> Iter 95744, Minibatch Loss= 28.283163, Training Accuracy= 59.38%
> Iter 96064, Minibatch Loss= 1.305542, Training Accuracy= 50.00%
> Iter 96384, Minibatch Loss= 1.801988, Training Accuracy= 50.00%
> Iter 96704, Minibatch Loss= 1.896597, Training Accuracy= 53.12%
> Iter 97024, Minibatch Loss= 2.941552, Training Accuracy= 46.88%
> Iter 97344, Minibatch Loss= 0.693964, Training Accuracy= 56.25%
> Iter 97664, Minibatch Loss= 8.340314, Training Accuracy= 40.62%
> Iter 97984, Minibatch Loss= 2.635653, Training Accuracy= 56.25%
> Iter 98304, Minibatch Loss= 1.541869, Training Accuracy= 68.75%
> Iter 98624, Minibatch Loss= 1.544908, Training Accuracy= 62.50%
> Iter 98944, Minibatch Loss= 26.138868, Training Accuracy= 56.25%
> Iter 99264, Minibatch Loss= 17.603979, Training Accuracy= 56.25%
> Iter 99584, Minibatch Loss= 21.715031, Training Accuracy= 40.62%
> Iter 99904, Minibatch Loss= 17.485657, Training Accuracy= 53.12%
> Optimization Finished!
模型结果
使用 TensorFlow 摘要或日志记录建模结果,并在运行模型脚本时保存。为了写入日志,使用了日志写入器FileWriter(),它在内部创建日志文件夹并保存图形结构。TensorBoard 随后将记录的汇总操作用于可视化目的。我们将日志保存在当前工作目录的以下内部文件夹位置:logs_path = './recent_logs/'。
要启动 TensorBoard,请根据您的选择指定端口:tensorboard --logdir=./ --port=6006.
张量板
为了使 TensorBoard 可视化更具可读性,我们在需要的地方添加了占位符和变量的名称。TensorBoard 有助于代码的调试和优化。
我们已经添加了整个模型的图形和它的一些片段,以帮助将代码与 TensorFlow 图形可视化关联起来。所有的片段都可以与前一子部分中它们相应的代码片段相关联。
图 5-7 显示了情感分类的完整网络架构。该图显示了贯穿代码的变量,这有助于理解模型中的数据流和连接。
图 5-7
TensorFlow graph of the overall model
图 5-8 显示了图中的 MLP 分量,用于在最后一层添加 dropout 的加法,以及 sigmoid 函数来预测最终的情感分类结果。最终预测进一步用于收集模型的准确性和成本。
图 5-8
TensorBoard graph for the MLP segment
图 5-9 显示了网络的嵌入组件。用于初始化embeddings变量,由[-1,1]范围内均匀分布的随机值组成。embedding_lookup()技术用于对embeddings张量执行并行查找,该张量进一步用作 LSTM 层的输入。
图 5-9
TensorBoard graph for the embedding segment
模型精度和成本
以下是在 IMDb 数据集上执行的四次模拟以及具有不同平滑过滤器参数值的两种情况的模型精度和成本图表。
Note
平滑过滤器在 TensorBoard 中用作控制窗口大小的加权参数。权重 1.0 表示使用整个数据集的 50%作为窗口,而权重 0.0 表示使用 0 的窗口(因此,用每个点自身替换每个点)。过滤器作为一个额外的参数来彻底解释图形。
案例 1
对于第一种情况,平滑滤波器的值被设置为0.191,我们在四个不同的模拟中比较了模型的精度和成本(图 5-10 和 5-11 )。
图 5-11
TensorBoard graph for the cost parameter
图 5-10
TensorBoard graph for the accuracy parameter
案例 2
对于第二种情况,平滑值被设置为0.645,我们在四个不同的模拟中比较了模型精度和成本(图 5-12 和 5-13 )。
图 5-13
TensorBoard graph for cost parameter
图 5-12
TensorBoard graph for accuracy parameter
改进的余地
从前面的图表中可以推断出,模型的精确度并不高,在某些情况下接近 70%。有几种方法可以进一步改进前面练习中获得的结果,包括改变输入到模型中的训练数据,以及改进模型的超参数。本文中用于情感分析的训练数据集包括 50 万条 Yelp 评论和用于开发和测试目的的 rest。在执行的练习中,我们进行了 25K 次审查。为了进一步提高模型的性能,我们邀请读者对代码进行修改,并比较多次迭代的结果。为改善结果所做的更改应与论文中提到的值一致,从而有助于比较多个数据集的结果。
后续步骤
这本书的最后一章介绍了所选研究论文的情感分析的实现。我们希望所有背景的读者开展这样的活动,并尝试在他们选择的数据集上用他们喜欢的语言复制不同论文和会议上提出的算法和方法。我们相信,这样的练习提高了对研究论文的理解,并拓宽了对不同类型的算法的理解,这些算法可以应用于解决特定问题的相关数据集。
我们希望读者能够享受本书中所有用例的旅程。我们将非常感谢他们的建议,以提高这里提出的代码和理论的质量,我们将确保在我们的代码库中进行任何相关的更改。