机器理解(machinechensition,mc),给定文本资料,根据资料回答问题,这就像我们学生时代的阅读理解,我们需要对给定的文本context和query,给出答案这复杂的过程进行建模。
注意力机制推动了及机器理解的进展,以往工作中的注意力机制通常具有以下一个或多个特征。
论文地址:https://arxiv.org/pdf/1704.04368.pdf
本文介绍了双向注意流(bidaf)网络,它是一个多阶段的层次化过程,它在不同的粒度级别上表示上下文,并使用双向注意流机制在不进行早期总结的情况下获得一个查询感知的上下文表示。
模型的主要组成部分
我们先简单的模拟一下bidaf的过程,char、word进行embedding只是单词进行空间映射,假设我们给定:
为了简化,这里设定维度是3,进行映射后:
那么文本内容就变为
context 句子长度为4,query句子长度为3,则映射后的形状分别3×4和3×3
(paper中的d在这里就是3,T就是4,J就是3)
接下来是Contextual Embedding ,其实就是经过了双向的LSTM的处理计算,因为双向维度增加一倍,我在这里没有真正模拟lstm_cell计算,只是简单给出了一样的值:
经过三层的embedding处理后,计算相似矩阵,咱们进行简单模拟,对context进行转置与query相乘,即得到相似矩阵S:
Context-to-query 注意力计算,就是计算query中每个word与context中word的相似度,相似度越高,重要度也越重要,利用计算得到的相似矩阵,如:
得到context的一种新表示 U ~ \widetilde{U} U
Query-to-context 的注意力计算,取出每一行最大值,在这里,由于我们初始值设置简单,正好是最后一列,在这里每一列取出最大值,找出query中每个词对应的context中最相关(相近的词),这样可以保留整体的相关性
用context中的word进行计算,最终得到 H ~ \widetilde{H} H
将原始的context矩阵H、Context-to-query 注意力计算得到的 U ~ \widetilde{U} U 、Query-to-context 的注意力计算得到的 H ~ \widetilde{H} H 进行拼接,得到了 G : t G_{:t} G:t:
G : t G_{:t} G:t:进入Modeling层,经过lstm处理,进行上下文表示,得到M,M输入到output层,经过softmax处理给出答案的起始位置与结束位置。
将每个词看作字符级一维的图片处理,经过CNN处理,得到单词在字符级别的向量表示。
词嵌入层是将每个词映射到高维向量空间。本文使用的是Glove,大家也可尝试word2vec。
将两种映射后的结果进行拼接,这里使用的是双层的Highway Network。
经过上述复杂运算就得到了整合了词和字符信息的context和query表示
上下文嵌入层利用来自周围单词的上下文提示来优化文字的嵌入。我们使用一个长期短期记忆网络(lstm)在前几层提供的嵌入之上,来模拟单词之间的时间交互。我们在两个方向上放置一个LSTM,并连接两个LSTM的输出。因此得到,
注意,H和U的每一列向量都是二维的,因为前向和后向LSTM的输出是串联的,每一列都有d维的输出。
注意流层。注意力流层负责链接和融合上下文和query词中的信息。与以前流行的注意力机制不同,注意力流层不用于将查询和上下文总结为单个特征向量。相反,每个时间步骤的注意向量,以及来自前一层的嵌入,都可以流到下一个建模层。这减少了早期总结造成的信息损失。
首先本层的输入为上层的输出即:
引入共享的相似度矩阵S,S的大小是T乘以J,即
S矩阵中的元素代表的含义:
本文中选择的标量函数是:
接下来我们用s来获得注意点和注意向量:
Context-to-query Attention(C2Q):
Context-to-query Attention(C2Q):针对context中的每个单词,计算query中每个单词与context当前单词t的相关度。用 a t ∈ R J a_t\in R^J at∈RJ来表示query中words在单词t上的权重weight。 ∑ a t j = 1 ∑a_{tj} = 1 ∑atj=1所有注意力和为1,这里做了归一化。注意力的计算通过softmax实现:
a t = s o f t m a x ( S t : ) ∈ R J a_t=softmax (S_{t:})\in R^J at=softmax(St:)∈RJ
那么每个被注意到的query vector的计算如下
U ~ t : = ∑ j a t j U : j \widetilde{U}_{t:}=∑_ja_{tj}U_{:j} U t:=∑jatjU:j
U ~ \widetilde{U} U 是2d×T的,表示整个context相关query vector
Query-to-context Attention(Q2C):
Query-to-context Attention(Q2C)则是表示针对query中的每个单词,计算context中的每个单词与query当前词j的相关度,这里的相关其实就是哪一个上下文词与其中一个query词最相似,因此对于回答最为重要,
b = s o f t m a x ( m a x c o o l ( S ) ) ∈ R T b=softmax(max_{cool}(S)) \in R^T b=softmax(maxcool(S))∈RT
上式的含义是先取S中每一列的最大值形成一个新的向量 , 然后对这个新的向量求相关度,:
h ~ t : = ∑ t b t H : t ∈ R 2 d \widetilde{h}_{t:}=∑_tb_{t}H_{:t} \in R^{2d} h t:=∑tbtH:t∈R2d
然后 h ~ \widetilde{h} h 重复 T 次形成 T 列, 形成 H ~ ∈ R 2 d \widetilde{H}\in R^{2d} H ∈R2d , 其实就是类似 Q2C 矩阵。
此时,我们将这些信息综合, 其实就我看来就是将 Query 的信息融入到 Context 中,如下:
G : t = β ( H : t , U ~ : t , H ~ : t ) ∈ R d G G_{:t}=β(H_{:t}, \widetilde{U}_{:t},\widetilde{H}_{:t}) \in R^{d_G} G:t=β(H:t,U :t,H :t)∈RdG
β只是拼接的函数,大家可以尝试不容函数拼接形式。
建模层的输入是G,它对上下文词的查询感知表示进行编码。建模层的输出捕获基于query的上下文词之间的交互。这与上下文嵌入层不同,上下文嵌入层捕获独立于查询的上下文词之间的交互。我们使用两层双向LSTM,每个方向的输出大小为d。因此,我们得到一个矩阵 M ∈ R 2 d × T M\in R^{2d×T} M∈R2d×T,传递到输出层来预测答案。M 的每个列向量都应该包含关于单词的上下文信息,这些信息与整个上下文context和query有关。
输出层是特定于应用程序的。bidaf的模块化特性允许我们根据任务轻松地交换输出层,而架构的其余部分保持完全相同。
该层其实是根据任务而定, 对于Squad 数据集而言,是为了寻找信息片段的开始位置和结束位置, 有很多种方式, 本文用到的是:
p 1 = s o f t m a x ( w ( p 1 ) T [ G ; M ] ) p^1=softmax(w^T_{(p^1)}[G;M]) p1=softmax(w(p1)T[G;M])
w ( p 1 ) T ∈ R 10 d w^T_{(p^1)} \in R^{10d} w(p1)T∈R10d
p 2 = s o f t m a x ( w ( p 2 ) T [ G ; M 2 ] ) p^2=softmax(w^T_{(p^2)}[G;M^2]) p2=softmax(w(p2)T[G;M2])
其中 M 2 ∈ R 2 d × T M^2\in R^{2d×T} M2∈R2d×T通过一个双向的LSTM得到的。
最后的损失函数,采用交叉熵:
L ( θ ) = − 1 N ∑ i N log ( p y i 1 1 ) + log ( p y i 2 2 ) L(θ)=-\frac{1}{N}\sum_{ i}^{ N}\log(p^1_{y_i^1})+\log(p^2_{y_i^2}) L(θ)=−N1∑iNlog(pyi11)+log(pyi22)
这里给出tensorflow1.2后的版本,原有版本太低,需要修改代码太多,https://github.com/allenai/bi-att-flow/tree/dev
如果是低版本注意:代码调整:
低版本与高版本代码中都缺少部分参数:在basic.cli中参加参数,我这里是随机给定的参数默认值
flags.DEFINE_string("out_dir", "", "Shared path []")
flags.DEFINE_string("save_dir", "", "Shared path []")
flags.DEFINE_string("log_dir", "", "Shared path []")
flags.DEFINE_string("eval_dir", "", "Shared path []")
flags.DEFINE_string("answer_dir", "", "Shared path []")
flags.DEFINE_integer("max_num_sents", 256, "para size th [256]")
flags.DEFINE_integer("max_sent_size", 256, "para size th [256]")
flags.DEFINE_integer("max_ques_size", 256, "para size th [256]")
flags.DEFINE_integer("max_word_size", 256, "para size th [256]")
flags.DEFINE_integer("max_para_size", 256, "para size th [256]")
flags.DEFINE_integer("word_emb_size", 256, "para size th [256]")
flags.DEFINE_integer("word_vocab_size", 256, "para size th [256]")
flags.DEFINE_integer("char_vocab_size", 256, "para size th [256]")
flags.DEFINE_integer("emb_mat", 256, "para size th [256]")
flags.DEFINE_float("new_emb_mat", 0.5, "Exponential")
Model 重要参数
我的运行环境是gpu,16G。 batch_size = 60, 如果内存小,建议调小batch_size,不然会有oom。初始学习率 = 0.5, epoch = 12, dropout = 0.2,hidden_size=100,num_steps=20000,
输出文件保存在当前的out目录下: