Question Answering over Freebase via Attentive RNN with Similarity Matrix based论文解读

这篇论文主要提出了attentive recurrent neural network with similarity matrix based convolutional neural network (AR-SMCNN) model,通过知识图谱回答single-relation问题。
[论文下载地址](https://arxiv.org/vc/arxiv/papers/1804/1804.03317v2.pdf),好几个人在评论说找不到论文?(今天2019-2-19还是可以下载)
本文主要翻译论文中的模型,以及研究作者开源的项目代码(地址见论文脚注)。

1、数据准备

SimpleQuestions数据集

包含108,442 个人工提出的简单问题,提取自freebase。其中70%为训练集(75910),10%为校验集(10845),剩余20%为测试集。
文件名:annotated_fb_data_{train, valid, test}.txt
每个文件中的每一行格式如下:Subject-entity [tab] relationship [tab] Object-entity [tab] question。
例:

www.freebase.com/m/0f3xg_	www.freebase.com/symbols/namesake/named_after	www.freebase.com/m/0cqt90	Who was the trump ocean club international hotel and tower named after

FB2M、FB5M数据集

每一行描述一个事实。
文件名:freebase-{FB2M, FB5M}.txt
文件格式:Subject-entity [tab] relationship [tab] a list of Object-entities,其中多个宾语实体用空格分隔。
例:

www.freebase.com/m/0n1vy1h	www.freebase.com/people/person/gender	www.freebase.com/m/05zppz

FB5M.name数据集

每一行指示FB5M中的实体的名称。
文件名:FB5M.name.txt
例:

<fb:m.0f8v12b>	<fb:type.object.name>	"blue christmas"	.

数据转换

将原数据集中的www形式转换为fb:形式。读取freebase-FB2M.txt,写入FB2M.core.txt
例:

<fb:m.0crxxsx>	<fb:film.film.genre>	<fb:m.02kdv5l>	.

写入数据库

将FB2M数据写入virtuoso数据库

预处理训练、校验、测试数据

开启多线程,将SimpleQuestions数据中的每一行用列表存储至QAdata中,再将数据序列化存储至文件。
源文件:annotated_fb_data_{train,valid,test}.txt,目标文件:QAData.{train,valid,test}.pkl
目标文件中每一行数据包括:question, sub, rel, obj, length。其中question用“.”分隔,length表示词的数量。
QAdata中对问题和主体进行reverse_link()操作,即从问题中匹配最长的实体字符串,如果匹配到实体,则填写text_subject,并将该实体对应的tokens的索引标记到text_attention_indices中,再将匹配到的实体字符串替换为“X”,设置为question_pattern。

代码文件:process_rawdata.py
def reverse_link(question, subject):
……
    for res in sorted(res_list, key = lambda res: len(res), reverse = True):
        pattern = r'(^|\s)(%s)($|\s)' % (re.escape(res))
        if re.search(pattern, question):
            text_subject = res
            text_attention_indices = get_indices(tokens, res.split())
            break

将实体与关系的node_id的列表,用序列化的方式写入FB2M.{ent,rel}.pkl中。再将上述列表按照字符排序后以字符串的形式写入FB2M.{ent,rel}.txt中,用作字典。
例:

fb:m.05zppz

创建词典

下载glove.42B.300d.txt
读取FB2M.{ent,rel}.txt,使用torch.save()方法分别写入vocab.{ent,rel}.pt中。
读取FB2M.rel.txt,将每一个关系分割为2部分,最后一个“.”之前为rel1,最后一个“.”之后为rel2,分别创建两部分的词典rel1_vocab, rel2_vocab,使用torch.save()方法将这两个词典写入vocab.rel.sep.pt中。
分别读取QAData.{train,valid,test}.pkl,将每一个question字符串按空格分割,得到词列表,都存入词典word_rel_vocab中。
读取FB2M.rel.pkl,将每一个关系按照“.”和“_”分割得到的每一个词继续存入word_rel_vocab中。
再将word_rel_vocab用torch.save()方法写入vocab.word&rel.pt中。

创建实体检测训练数据

加载词典vocab.word&rel.pt
源文件:QAData.{train,valid,test}.pkl,目标文件:{train,valid,test}.entity_detection.pt。同时将结果以日志形式存储至{train,valid,test}.entity_detection.txt
对每一个问题,分割为词,将词的tokens映射为词典中的id,并以torch.LongTensor存入seqs列表中。将text_attention_indices标记出的实体对应的tokens存入seq_labels中。最后将(seqs, seq_labels)保存到文件{train,valid,test}.entity_detection.pt中。

2、训练实体检测模型

实体检测过程如下图所示。给定一个问题Q,我们将一个双向的LSTM网络像一个连续的标签任务一样训练,它可以被看作是一个二进制分类问题,它可以预测一个句子中的每个单词是否属于实体提及。然后,我们得到一组带有正标签的单词,它表示为C,这些词可能不是连续的,然后我们使用启发式方法得到实体提到X。
image
(1)在C中合并相邻的单词(忽略间隙<=1),形成一个子串S。如果有多个子串,保留最长的子串。
(2)找出所有在Freebase中名称或别名与S完全相等的实体。这些实体构成实体候选E,S被视为实体提及。如果没有匹配,继续步骤(3)。
(3)事实上,S中的词最有可能构成实体提及X,因此基于S生成X的可能性也就很高。所以我们以S为中心,在它相邻位置找X。具体地说,我们在S附近扩展或缩小至多2个单词以获得S’,再使用S’来查找相应的实体。一旦匹配,就确定了E和X。

文件entity_detection/predict.py
def get_candidate_sub(question_tokens, pred_tag):
……
    sub_list = []
    shift = [0,-1,1,-2,2]
    pred_sub = []
    for left in shift:
        for right in shift:
            for i in range(len(starts)):
                if starts[i]+left < 0:continue
                if ends[i]+1+right > len(question_tokens):continue
                text = question_tokens[starts[i]+left:ends[i]+1+right]
                subject = virtuoso.str_query_id(' '.join(text))
                if left==0 and right==0:
                    pred_sub = subject
                sub_list.extend(subject)
            if sub_list:
                return pred_sub, sub_list

(4)如果仍然没有找到匹配的对象,那么我们将使用S中的每个单词来搜索名称中包含单词的实体。我们将拥有最长公共子序列的实体作为实体候选E,而公共子序列将是X,这一步与之前的方法相似,但这里发生的概率小于0.2%。
我们假设如果一个词被预测为负标签,而它的相邻词却是正的,那么这个预测就是错的。基于这个假设,我们在步骤(1)中将分散的词组合在一起,再进行后续步骤可以提升召回率。主体在实体候选列表中是按照它连接的关系总数排序的。
经过上述4个步骤,我们可以得到一个实体候选集合,再据此生成问题模式。

3、训练关系检测模型

给定一个问题模式P,对于关系候选池R中的每个关系 r k r^k rk,我们计算一个匹配得分 S ( P , r k ) S(P,r^k) S(P,rk)来表示它们之间的相关性,最终预测结果 r ^ k \hat{r}^k r^k表示为
r ^ k = arg ⁡ m a x r k ∈ R ( S ( P , r k ) ) \hat{r}^k =\underset{r^k\in R}{\arg max}(S(P,r^k)) r^k=rkRargmax(S(P,rk))
我们的AR-SMCNN模型在semantic-level和literal-level两个粒度上都考虑到了P和 r k r^k rk的相关性。

Semantic-level

为了在问题和关系之间进行语义层面的匹配,我们构建了一个attentive RNN作为encoder-compare框架。我们发现Freebase中的关系包含两个方面的信息,一个表示主体的类型,另一个描述了主体和客体之间的关系。例如,问题模式“which language is the film in”与<film.film.language >关系相关,关系的前两部分“film.film”表示主体的类型,最后一部分“language”表示主体和答案“German”之间的真实关系。这两部分与问题中的不同词相关联,因此每个词在编码过程中应该赋予不同的权重。鉴于此,我们对关系的这两部分分别进行编码,并且在问题编码器中使用注意力机制来匹配它们。
我们提出的AR-SMCNN模型如下图所示,左边是attentive BiGRU,右边是CNN on similarity matrix,最后将特征 z 1 , z 2 , z 3 , z 4 z_1,z_2,z_3,z_4 z1,z2,z3,z4连接在一起并通过一个线性层得到最终评分 S ( P , r k ) S(P,r^k) S(P,rk)
image

关系编码

对于R中的每个关系,我们将其分割成两个部分,并将每个部分转换成随机初始化的可训练的embedding,以得到它们的向量表示 r 1 r_1 r1 r 2 r_2 r2

问题模式编码

将问题中的每个词转换成word embedding { q 1 , ⋯ &ThinSpace; , q L } \{q_1,\cdots,q_L\} { q1,,qL},然后将embedding送到双向GRU网络中以获得隐藏表示 H 1 : L = [ h 1 ; ⋯ &ThinSpace; ; h L ] H_{1:L}=[h_1;\cdots;h_L] H1:L=[h1;;hL](每个向量 h i h_i hi是在时间 i i i的前向/后向表示之间的级联)。关系的每一部分对问题给予不同的关注,并决定问题的表示。注意力的程度被用作问题中每个词的权重。因此,对于关系表示 r i r_i ri,其对应的问题模式表示 p i p_i pi计算方法如下:

p i = ∑ j = 1 L α i j h j &ThinSpace; p_i = \sum_{j=1}^{L}\alpha_{ij}h_j\, pi=j=1Lαijhj α i j = exp ⁡ ( w i j ) ∑ k = 1 L exp ⁡ ( w i k ) &ThinSpace; \alpha_{ij}=\frac{\exp(w_{ij})}{\sum_{k=1}^{L}\exp(w_{ik})}\, αij=k=1Lexp(wik)exp(wij) w i j = v T tanh ⁡ ( W T [ h j ; r i ] + b ) &ThinSpace; w_{ij}=v^T\tanh(W^T[h_j;r_i]+b)\, wij=vTtanh(WT[hj;ri]+b)

其中 i ∈ { 1 , 2 } i\in\{1,2\} i{ 1,2} α i j \alpha_{ij} αij是指关系表示 r i r_i ri在问题中第 j j j个词的注意力权重。 L L L是问题的长度。设 r i r_i ri的维度为 m m m h i h_i hi的维度为 n n n,那么 W W W v v v是要进行训练的参数,有 W ∈ R c × ( m + n ) , v ∈ R 1 × c W\in R^{c\times(m+n)},v\in R^{1\times c} WRc×(m+n),vR1×c,其中 c c c是超参数。

相似性度量

现在我们得到了问题模式和关系的表示,它们的相似度可以用以下公式计算,
z i = p i ⨂ r i &ThickSpace;&ThickSpace;&ThickSpace;&ThickSpace; ( i = 1 , 2 ) z_i = p_i\bigotimes r_i\;\;\;\;(i=1,2) zi=piri(i=1,2)
这里的 ⨂ \bigotimes 是指两个向量的点积运算。我们在计算它们的相互关系时还使用了诸如余弦、双线性、曼哈顿距离和GESD方法,但是这些方法并不能提高准确率,反而会降低训练速度。所以对于这个任务来说,简单的点积足够了。
image
上图是相似性矩阵的两个例子。图中方格的颜色越深表示两个词的相似度越高。图中我们可以看到不同类型的匹配模式(红框标记),卷积核可以提取到这些特征。

Literal-level

在字面层面考虑它们的相似性时,可以把这个问题看做是文本匹配问题。我们发现一些词(或短语)即使表示同一个含义,但是在问题和关系中有着不同的表达方式(或顺序)。例如在上图中,词对(musical,music)(type,genre)(studio,companies)都有着相近主题。编码器比较模型不能捕捉这些词的交互信息,因为编码后的表示只保留高级语义信息。我们构造了一个相似性矩阵,它的元表示问题词和关系词之间的相似性,将其看作是一个二维图像,利用卷积层来捕捉匹配特征。

相似性矩阵

构造一个相似性矩阵 M M M,其中每个元素表示 u i u_i ui j v j_v jv之间的相似度( u i u_i ui表示问题中的第 i i i个词嵌入, j v j_v jv表示关系中的第 j j j个词嵌入),则有:
M i j = u i ⨂ v j M_{ij} = u_i \bigotimes v_j Mij=uivj
这里的 ⨂ \bigotimes 表示余弦计算以得到相似度。与严格的文本匹配不同,这个矩阵可以捕捉到有着不同形式的词的相似度。

卷积层

一个典型的卷积核可以提取到不同级别的匹配模式,如词级别或者短语级别。更确切的说,第 k k k个核 w k w^k wk扫描整个相似性矩阵 M M M来生成特征映射 g k g^k gk

g i , j k = σ ( ∑ s = 0 r k − 1 ∑ t = 0 r k − 1 ) w s , t k ⋅ M i + s , j + t + b k ) g_{i,j}^k = \sigma(\sum_{s=0}^{r_k -1}\sum_{t=0}^{r_k -1})w_{s,t}^k \cdot M_{i+s,j+t} +b^k) gi,jk=σ(s=0rk1t=0rk1)ws,tkMi+s,j+t+bk)
其中 r k r_k rk表示第 k k k个核的大小,这里我们使用square kernels,激活函数 σ \sigma σ为ReLU。

双向最大池化层

我们在特征映射 g k g^k gk的顶部使用两个不同的池核,它们的大小分别为 1 × d 1 1\times d_1 1×d1 d 2 × 1 d_2 \times 1 d2×1,其中 d 1 d_1 d1表示矩阵的列数, d 2 d_2 d2表示矩阵的行数。

y i ( 1 , k ) = max ⁡ 0 ⩽ t &lt; d 1 g i , t k y_i^{(1,k)} =\max_{0\leqslant t&lt;d_1}g_{i,t}^k yi(1,k)=0t<d1maxgi,tk y j ( 2 , k ) = max ⁡ 0 ⩽ t &lt; d 2 g t , j k y_j^{(2,k)} =\max_{0\leqslant t&lt;d_2}g_{t,j}^k yj(2,k)=0t<d2maxgt,jk
其关键思想是从问题和关系的角度保持最大匹配特征。即对于问题中的每一个词,从关系词中找到最大匹配得分;对于关系中的每一个词,从问题词找到最大匹配得分。这种方法优于使用平方池内核,因为在这个任务里我们更加强调单个单词的最大匹配分数。

全连接层

我们使用一个多层感知机来得到最终特征。对于两个池的结果,我们采用两层感知器。

z 3 = W 2 σ ( w 1 [ y ( 1 , 0 ) ; y ( 1 , K ) ] + b 1 ) + b 2 , z_3 =W_2 \sigma(w_1[y^{(1,0)};y^{(1,K)}]+b_1)+b_2, z3=W2σ(w1[y(1,0);y(1,K)]+b1)+b2, z 4 = W 2 σ ( w 1 [ y ( 2 , 0 ) ; y ( 2 , K ) ] + b 1 ) + b 2 , z_4 =W_2 \sigma(w_1[y^{(2,0)};y^{(2,K)}]+b_1)+b_2, z4=W2σ(w1[y(2,0);y(2,K)]+b1)+b2,
其中 K K K表示核的总数, [ y ( i , 0 ) ; y ( i , K ) ] [y^{(i,0)};y^{(i,K)}] [y(i,0);y(i,K)] K K K个池化层的输出的级联, W i W_i Wi是第 i i i个MLP层的权重, σ \sigma σ是ReLU激活函数。

组合

经过前面两个层面的匹配,我们得到了四个特征 ( z 1 , z 2 , z 3 , z 4 ) (z_1,z_2,z_3,z_4) (z1,z2,z3,z4) z 1 z_1 z1 z 2 z_2 z2分别表示主体类型和关系的语义相关性, z 3 z_3 z3 z 4 z_4 z4表示问题和关系的字面匹配程度。我们用一个线性层来分别学习它们对于全局匹配得分的贡献:

S ( P , r k ) = S i g m i o d ( W T [ z 1 ; z 2 ; z 3 ; z 4 ] + b ) S(P,r^k) = Sigmiod(W^T[z_1;z_2;z_3;z_4]+b) S(P,rk)=Sigmiod(WT[z1;z2;z3;z4]+b)
这个模型通过排序损失来进行训练,以最大化关系候选池R中的最优关系 r + r^+ r+和其它关系 r − r^- r之间的差距。

l o s s ( P , r + , r − ) = ∑ ( P , r + ) ∈ D max ⁡ ( 0 , γ + S ( P , r − ) − S ( P , r + ) ) loss(P,r^+,r^-) = \sum_{(P,r^+)\in D}\max(0,\gamma +S(P,r^-)-S(P,r^+)) loss(P,r+,r)=(P,r+)Dmax(0,γ+S(P,r)S(P,r+))
其中 γ \gamma γ是一个常数。

4、代码勘误

注:论文作者所用pytorch版本0.2,我用的pytorch版本为0.4
1)文件relation_ranking/attention.py,76行,pack_seq函数。

    def pack_seq(seq):
        """
        :param seq: (batch_size, max_len, seq_size)
        :return: (batch_size * max_len, seq_size)
        """
        #return seq.view(seq.size(0) * seq.size(1), -1)
        return seq.contiguous().view(seq.size(0) * seq.size(1), -1)

报错:RuntimeError: invalid argument 2: View size is not compatible with input tensor’s size and stride
在view前加上contiguous才可正确运行。
2)文件relation_ranking/model.py,171行,dynamic_pooling_index函数。

def dynamic_pooling_index(self, len1, len2, max_len1, max_len2):
        def dpool_index_(batch_idx, len1_one, len2_one, max_len1, max_len2):
            #stride1 = 1.0 * max_len1 / len1_one
            #stride2 = 1.0 * max_len2 / len2_one
            stride1 = 1.0 * max_len1 / len1_one.item()
            stride2 = 1.0 * max_len2 / len2_one.item()
            idx1_one = [int(i/stride1) for i in range(max_len1)]
            idx2_one = [int(i/stride2) for i in range(max_len2)]
            return idx1_one, idx2_one
        batch_size = len(len1)
        index1, index2 = [], []
        for i in range(batch_size):
            idx1_one, idx2_one = dpool_index_(i, len1[i], len2[i], max_len1, max_len2)
            index1.append(idx1_one)
            index2.append(idx2_one)
        index1 = torch.LongTensor(index1)
        index2 = torch.LongTensor(index2)
        if self.config.cuda:
            index1 = index1.cuda()
            index2 = index2.cuda()
        return Variable(index1), Variable(index2)

报错RuntimeError: reciprocal is not implemented for type torch.LongTensor
给len1_one添加.item()才可正确运行。
3)文件relation_ranking/predict.py,64行,rel_pruned函数。

def rel_pruned(neg_score, data):
    neg_rel = data.cand_rel
    #print (neg_score.size)
    if neg_score.size==1:
        #neg_score=[neg_score]
        pred_rel_scores =(neg_rel[0],neg_score.item())
        #print (pred_rel_scores)
    else:
        pred_rel_scores = sorted(zip(neg_rel, neg_score), key=lambda i:i[1], reverse=True)
    pred_rel = pred_rel_scores[0][0]
    pred_sub = []
    for i, rels in enumerate(data.sub_rels):
        if pred_rel in rels:
            pred_sub.append((data.cand_sub[i], len(rels)))
    pred_sub = [sub[0] for sub in sorted(pred_sub, key = lambda sub:sub[1], reverse=True)]
    return pred_rel, pred_rel_scores, pred_sub

当neg_score的大小为1时,传入zip方法会报错,经过以上修改后才可以正确运行。

猜你喜欢

转载自blog.csdn.net/KanShiMeKan/article/details/80483845