版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u014665013/article/details/87980745
我觉得这篇文章的文笔真的有点不敢恭维,首先向量矩阵的维度不说清楚还能脑补,但是这边前后不同层之间用一样的变量名是什么意思啊(这么说出来会不会被MSRA鄙视,以后的简历都过不了了,ORZ),本文中尽量避免这种情况。嗯嗯,文章还是不错的^@^
标记版文章下载地址:zsweet github
原始文章下载地址:
Gated Self-Matching Networks for Reading Comprehension and Question Answering
R-NET: MACHINE READING COMPREHENSION WITH SELF-MATCHING NETWORKS
这里先总结下几个特点:
- 同时使用了char-embedding和word-embedding,不同的是char-embedding是通过将char放入双向gru之后,最终的是通过gru的最终状态来得到的。
- 在attention之后添加gate,主要用在Question-Passage Matching和Passage Self-Matching之后。
- 对文章本身进行self-attention。
1.模型概述
其实我觉得整体结构就是math-lstm和transformer模型的融合,然后再加了一点的小trick.
模型整体可以分为如下几个模块:
- embedding层
- RNN网络分别对question和passage单独编码 (本文中将前两部分放在一个部分介绍)
- 基于门限的注意力循环神经网络(gated-attention based recurrent network)匹配question和passage,获取问题的相关段落表示(question-aware passage representation)
- 基于自匹配注意力机制的循环神经网络(self-matching attention network),将passage和它自己匹配,从而实现整个段落的高效编码
- 基于指针网络(pointer-network)定位答案所在位置
模型结构如下:
2.passage和question编码层
输入问题
Q=wtQt=1m和段落
P=wtPt=1n,分别进行word-level编码和character-level编码,得到向量
e和
c。这里character-level编码主要是为了应对OOV的影响,以往OOV词向量直接就是0,这里可以缓和OOV的影响。之后,利用两个双向RNN网络分别对question和passage再编码。而之前多数都是用的CNN卷积和highway。另外,作者在这里选用了GRU单元,而不是LSTM,原因在于GRU计算量更小。
utQ=BiGRU(ut−1Q,[etQ,ctQ])
utP=BiGRU(ut−1P,[etP,ctP])
其中:
et,ct分别表示词向量和字向量,编码后的passage为
[u1Q,u2Q...umQ],query为
[u1P,u2P...unP]
3.Gated Attention-based RNN
首先对query做attention:
sjt=vTtanh(WuQujQ+WuPutP+WvPvt−1P),j=1,⋯,m
αjt=softmax(sjt)
扫描二维码关注公众号,回复:
5373256 查看本文章
ct=i=1∑mαituiQ
上述的attention可记为
ct=attn(uQ,[utP,vt−1P])。
上面的attention与match-lstm一样,但是这里又增加了一个gate:
gt=sigmoid(Wg[utP,ct])
[utP,ct]∗=gt⊙[utP,ct]
然后再像match-lstm一样放入RNN:
vtP=BiGRU(vt−1P,[utP,ct]∗)
每个
vtP动态地合并了来自整个Q的匹配信息。
可以看到,这一步骤和match-lstm的唯一区别就是增加了这个
gt来控制passage和attention之后的query的输出量。
Gate RNN的门机制:
- 与GRU和LSTM不同
- 门机制是基于当前
Pt和它的对应的
Q的注意力向量
ct(包含当前pt和Q的关系)
- 模拟了阅读理解中,只有P的一部分才与问题相关的特点
其实这个gate我觉得是很像那个highway的,在transformer里面用的是residual,但是这里和下面的self-attention之后换成了伪highway,甚至我觉得这个地方换成residual也未必不好,但是我没尝试过。
4.Self-Matching Attention
为了充分利用Passage的上下文信息。增加对passage的self-attention :
sjt=vTtanh(WvPvjP+WvPˉvtP),j=1,⋯,n
βjt=softmax(sjt)
dt=i=1∑nβitviP
上述的attention可记为
dt=att(vP,vtP))
同样在这里添加gate:
gt′=sigmoid(Wg[vtP,dt])
[vtP,dt]∗=gt′⊙[vtP,dt]
同样放入RNN计算:
htP=BiGRU(ht−1P,[vtP,dt]∗)
Self-Matching根据当前p单词,从整个Passage中提取信息。最终得到Passage的表达
HP。
我奇怪的是这里在计算
sjt的时候为什么没有使用RNN中t-1时刻的输出
ht−1P?难道效果不好?
5.output layer
类似于match-lstm中的最后输出层Ptr-net:
计算
t时刻的attention-pooling passage (注意力
ct)
sjt=vTtanh(WhPhjP+Whaht−1a)
γit=softmax(sjt)
ft=i=1∑nγithiP
pt=argimax(γit)
上述的attention可记为
ft=att(hP,ht−1)
RNN前向计算
hta=GRU(ht−1a,ft)
基于注意力权值去选择位置。
上面的这是match-lstm中的Ptr,在R-net中是增加了RNN中的初始状态初始化,
初始hidden state是Question的attention-pooling vector:
h0Q=rQ
基于Q的编码和一组参数
VrQ,利用注意力机制计算
rQ
sj=vTtanh(WuQujQ+WvQVrQ),j=1,⋯,m
δi=softmax(si)
rQ=i=1∑mδiuiQ
上述的attention可记为
rQ=attn(uQ,VrQ)
同样这里有点奇怪的是,按说这里的
VrQ是一个向量的,并且
WvQVrQ也是个向量,这不就相当于一个bias向量吗?为什么写的这么麻烦?
之后从网上看到这个:
这里肯定会有很多人困惑这个V_r^Q是怎么来的,文中并没有介绍,其实它是RNN的首状态,大部分RNN的第一个输入都是这么处理的。看代码还会发现它就是一个通过tf.contrib.layers.xavier_initializer()初始化的参数,那为什么不把这个乘法写成一个参数呢,因为两个都是可学习的参数完全可以合并,我觉得是因为要和attention的输入保持一致(attention函数有两个输入)。
所以按照match-lstm中boundary方案,每次
ft=att(hP,ht−1)产生的权重就可以作为start-probability和end-probability了
目标函数:
−n=1∑Nlogp(an∣Pn,Qn)
本文用的是边界模型,所以不用预测完整的序列,只预测开始和结束位置就可以了。
p(a∣Hr)=p(as∣Hr)⋅p(ae∣as,Hr)
6.源码和参数
模型细节:
- 训练集80%,验证集10%,测试10%
- 分词用的斯坦福的CoreNLP中的tokenizer
- 预训练好的Glove Vectors。训练中保持不变。
- 单层的双向GRU,末尾隐状态作为该单词的字符向量
- BiRNN编码Question和Passage中使用3层的双向GRU
- Hidden Size大小,所有都是75
- 每层之间的DropOut比例是0.2
- 优化器使用AdaDelta。初始学习率为1,衰减率β=0.95,ϵ=1e−6
源码:
原文中还提了很多的尝试,其中有个sentence rank的,之前我也在Dureader里面尝试过,尝试的是passage rank,效果也不好,其他的尝试可以参考文章,别人踩过的坑还是不要再踩一次了,当然这只是针对SQuAD
参考链接