#gStore-weekly | gAnswer源码解析 查询图生成

简介

gAnswer通过将自然语言问题转化成查询图,再和图数据库中的RDF图做匹配以生成用于查询的SPARQL语句。今天讲解的就是生成查询图的主要代码。

// step 2: build query graph (structure construction, relation extraction, top-k join) 
t = System.currentTimeMillis();
BuildQueryGraph step2 = new BuildQueryGraph();
step2.process(qlog);
qlog.timeTable.put("step2", (int)(System.currentTimeMillis()-t));

这是生成查询图的函数入口部分,如注释中所示,该部分主要分为三个子模块:构建查询图结构、关系提取和子图匹配(top-k join)。在构建查询图结构之前,已经通过节点提取模块(Node Extraction)确定了图中的所有节点,因此构图的过程其实就是确定每两个点之间是否有连边的过程,通过在依存分析树(Dependency Parsing Tree)上深度优先搜索,可在(为树的大小)时间内完成。接下来,通过一种基于依存分析树和离线构造词典的方法完成关系提取(Relation Extraction)后,gAnswer会采用一种子图匹配的方法将生成的查询图和RDF图匹配,从而生成候选SPARQL.

因节点提取、关系提取在以往的文章中已经讲过,因此本文聚焦于构建查询图结构和子图匹配的过程。

构建查询图结构

在构建查询图之前,需要完成四个准备工作:fix stop nodes, detect modified node, detect target和coreference resolution.

Fix stop nodes和detect modified node主要是为了提高准确率针对英文语境和DBpedia数据集的一些经验性的修改,比如问句中出现短语"take place",则实体"place"一定不是图中的节点。而detect modified node是针对一些名词词组(比如"Apollo 11 mission" "De Beers company"),选择其中一个词代表整个词组作为图中的节点。

在detect modified node中分为两个部分:检测连续的名词词组(getTheModifiedWordBySentence方法)和离散的名词词组(getDiscreteModifiedWordBySentence方法),但这两个部分的思想是一致的,即根据句子结构定义一些经验性的规则。在未来可通过训练一个节点识别模型替换掉当前的启发式规则。

public Word getDiscreteModifiedWordBySentence(Sentence s, Word curWord)
{ 
    int curPos = curWord.position - 1;
    
    //[ent1](cur) 's [ent2], ent1->ent2, usually do NOT appear in SPARQL | eg:Show me all books in Asimov 's Foundation_series
    if(curPos+2 < s.words.length && curWord.mayEnt && s.words[curPos+1].baseForm.equals("'s") && s.words[curPos+2].mayEnt)
        return curWord.modifiedWord = s.words[curPos+2];
    
    //[ent1] by [ent2](cur), ent2->ent1, usually do NOT appear in SPARQL | eg: Which museum exhibits The Scream by Munch?
    if(curPos-2 >=0 && (curWord.mayEnt||curWord.mayType) && s.words[curPos-1].baseForm.equals("by") && (s.words[curPos-2].mayEnt||s.words[curPos-2].mayType))
        return curWord.modifiedWord = s.words[curPos-2];
    
    return curWord.modifiedWord;
}

getDiscreteModifiedWordBySentence方法

准备工作的第三步是detect target,即寻找问句的目标变量是什么。gAnswer通过判断Wh-word(即what, where等)来确定问句的类别,并结合句子和依存树的结构来确定目标变量。

if(target.word.baseForm.equals("where"))
{
    int curPos = target.word.position - 1;
    
    //!Where is the residence of
    if(words[curPos+1].baseForm.equals("be") && words[curPos+2].posTag.equals("DT"))
    {
        for(int i=curPos+4;i<words.length;i++)
            if(words[i-1].posTag.startsWith("N") && words[i].posTag.equals("IN"))
            {
                target.word.represent = words[i-1];
                target = ds.getNodeByIndex(i);
                break;
            }
        
    }
}

如上图所示,以"where"为例,除了简单的问句,最常见的问法是"where is the xxx of ...",即where后面跟着be动词、限定词、名词和介词,那么我们会将这个名词作为目标,而不是where本身。

对于形如"Where is xxx from?"这样的简单问句,gAnswer认为目标就是"where",即查询图中的两个节点是"where"和"xxx".

最后一步是coreference resolution,即识别并解决指代关系(anaphora resolution)和指代消解(cataphora resolution),gAnswer目前只提供了一个基于规则的简单方法。

准备工作完成后,在构建时,首先建立一个队列,并将目标节点放进队列中。随后每次取出队首,并用深度优先搜索的方法在依存树上查找周围节点,直到遇到了其他中的节点,则认为这两个节点相连,并将这些节点放进队列。重复以上流程直到队列为空,至此的完整结构就构建出来了。

public void dfs(DependencyTreeNode head, DependencyTreeNode cur, ArrayList<DependencyTreeNode> ret)
{
    if(cur == null)
        return;
    visited.add(cur);
    
    if(isNode(cur) && head!=cur)
    {
        ret.add(cur);
        return;
    }
    
    if(cur.father!=null && !visited.contains(cur.father))
    {
        dfs(head,cur.father,ret);
    }
    for(DependencyTreeNode child: cur.childrenList)
    {
        if(!visited.contains(child))
            dfs(head,child,ret);
    }
    return;
}

深度优先搜索过程

需要注意的是,这样建立的图是一个超图,即在和RDF图进行子图匹配时,我们允许有的边失配,这种构图方法也一定程度上解决了语言歧义性的问题。

之后会通过节点提取方法为每条边寻找对应的谓词,从而生成完整的查询图。

子图匹配

子图匹配过程即生成SPARQL过程,调用方法部分如下图所示:

//step3: item mapping & top-k join
t = System.currentTimeMillis();
SemanticItemMapping step5 = new SemanticItemMapping();
step5.process(qlog, qlog.semanticRelations); //top-k join (generate SPARQL queries), disambiguation
qlog.timeTable.put("BQG_topkjoin", (int)(System.currentTimeMillis()-t));

子图匹配的算法的主体是top-k join,即匹配过程中取得分前高的子图作为答案。因子图匹配需要不断和图数据库进行交互,通信成本较高,因此gAnswer采用检查预先离线处理的"fragment"来模拟子图匹配的过程。

fragment:相当于将RDF图的信息离线存储在本地,每个fragment相当于图中的一个节点,包含该节点信息,以及该节点的出边信息等。

提取的信息后(点的信息存储在entityPhrasesList中,边的信息存储在predicatePhraseList中),top-k join算法首先枚举图中的所有节点的mention对应的实体,再枚举这些节点之间是否有边,以及边对应的谓词。这些全部枚举出来后,会通过scoringAndRanking方法判断枚举出来的的子图是否是RDF的子图,以及为其打分。

在scoringAndRanking中,首先会判断子图是否连通(若边的数量大于1,且存在一对节点他们的度数都为1,说明不连通)。若子图连通,会根据fragment判断每条边是否为RDF图上的边。因为RDF图是有向图,因此判断的过程即判断RDF图中是否存在 这样的三元组,若存在,会为这个三元组新建一个Triple类的对象,认为其是一条SPARQL三元组。

if(sr.isArg1Constant && (sr.arg1Word.mayEnt || sr.arg1Word.mayType) ) // Constant 
{
    // For subject, entity has higher priority.
    if(sr.arg1Word.mayEnt)
    {
        EntityMapping em = currentEntityMappings.get(sr.arg1Word.hashCode());
        subjId = em.entityID;
        sub = em.entityName;
        score *= em.score;
    }
    else
    {
        TypeMapping tm = sr.arg1Word.tmList.get(0);
        subjId = Triple.TYPE_ROLE_ID;
        sub = tm.typeName;
        score *= (tm.score*100); // Generalization. type score: [0,1], entity score: [0,100].
    }
}
else // Variable
{
    subjId = Triple.VAR_ROLE_ID;
    sub = "?" + sr.arg1Word.originalForm;
}

通过fragement进行子图匹配过程(以判断subject节点为例)

至此,gAnswer根据构建的超图生成了对应的SPARQL,只要在对应的图数据库中执行便能获得问题的答案了。

相关推荐
脆皮泡泡几秒前
Ultiverse 和web3新玩法?AI和GameFi的结合是怎样
人工智能·web3
机器人虎哥4 分钟前
【8210A-TX2】Ubuntu18.04 + ROS_ Melodic + TM-16多线激光 雷达评测
人工智能·机器学习
码银11 分钟前
冲破AI 浪潮冲击下的 迷茫与焦虑
人工智能
何大春15 分钟前
【弱监督语义分割】Self-supervised Image-specific Prototype Exploration for WSSS 论文阅读
论文阅读·人工智能·python·深度学习·论文笔记·原型模式
uncle_ll23 分钟前
PyTorch图像预处理:计算均值和方差以实现标准化
图像处理·人工智能·pytorch·均值算法·标准化
宋1381027972023 分钟前
Manus Xsens Metagloves虚拟现实手套
人工智能·机器人·vr·动作捕捉
SEVEN-YEARS27 分钟前
深入理解TensorFlow中的形状处理函数
人工智能·python·tensorflow
世优科技虚拟人30 分钟前
AI、VR与空间计算:教育和文旅领域的数字转型力量
人工智能·vr·空间计算
tatasix32 分钟前
MySQL UPDATE语句执行链路解析
数据库·mysql
cloud studio AI应用37 分钟前
腾讯云 AI 代码助手:产品研发过程的思考和方法论
人工智能·云计算·腾讯云